Skip to content

Introduce safer layer of sugar for nsStyleUnion#12521

Merged
bors-servo merged 3 commits intoservo:masterfrom
Manishearth:safer-coord
Jul 21, 2016
Merged

Introduce safer layer of sugar for nsStyleUnion#12521
bors-servo merged 3 commits intoservo:masterfrom
Manishearth:safer-coord

Conversation

@Manishearth
Copy link
Copy Markdown
Member

@Manishearth Manishearth commented Jul 20, 2016

This routes (almost) all access to nsStyleUnion through a largely safe interface, CoordData.

It also introduces a corresponding Rust enum, CoordDataValue, which can be used when interacting with CoordData

LLVM should optimize the costs away in release mode. eddyb tested this a bit, and LLVM has no trouble threading matches together with inlining -- so all of the matches using enums here will have the same generated code as the old matches on the units.

Some unresolved questions:

Should I provide convenience methods like set_coord, set_auto, etc on CoordData? .set_enum(CoordDataValue::Something) should optimize down to the same thing, but the convenience methods look cleaner and won't load the optimizer as much.

Also, we're converting immutable references to mutable ones, which can be used to cause unsafety using some acrobatics. Perhaps a trait-based approach is better?
The issue is that in some places we only have a &CoordData (eg copy_from), but CoordData internally is *mut. It would be nice if CoordData could parametrize over its mutability, but Rust doesn't let us do that.

The alternate approach is to make CoordData a trait (also CoordDataMut). The trait requires you to implement get_union() and get_unit(), and gives you the rest of the functions for free. nsStyleCoord would directly implement both traits. nsStyleSides would have data_at(idx) and data_at_mut(idx) methods which return a struct SidesData containing a reference to the Sides and the index, which itself implements the CoordData and CoordDataMut trait (we need two traits here because there will have to be two SidesData structs).

I decided not to implement the traits approach first since it's pretty trivial to change this code to use traits, and the current design is more straightforward.

Thoughts?

r? @bholley

cc @emilio @heycam


This change is Reviewable

@highfive
Copy link
Copy Markdown

Heads up! This PR modifies the following files:

  • @bholley: components/style/properties/gecko.mako.rs, components/style/gecko_values.rs

@highfive highfive added the S-awaiting-review There is new code that needs to be reviewed. label Jul 20, 2016
@highfive
Copy link
Copy Markdown

warning Warning warning

  • These commits modify unsafe code. Please review it carefully!
  • These commits modify style code, but no tests are modified. Please consider adding a test!

@Manishearth
Copy link
Copy Markdown
Member Author

Oh, another issue is that we need to make all unit/union fields private and add unsafe accessors. This will need to be done through bindgen changes, which I'll need to look into.

For now, we're directly setting the mUnit/mValue fields only for z-index, and there's a justifying comment.

*(*self.union).mInt.as_mut() = i as i32;
}
Calc(calc) => {
*self.unit = eStyleUnit_Calc;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line shouldn't be here, since we assert otherwise in set_calc_value.

@Manishearth Manishearth force-pushed the safer-coord branch 2 times, most recently from 303e9af to afd05df Compare July 20, 2016 13:50
@Manishearth
Copy link
Copy Markdown
Member Author

I went ahead and made the traits changes. They're in a separate commit so that we can still pick the old way of doing it if we want to.

It might be possible to clean up some of the impls in that file with macros. Not sure if it's necessary though.

@Manishearth
Copy link
Copy Markdown
Member Author

rust-lang/rust-bindgen#20 will make it possible to make this even more safe 😀

@bholley
Copy link
Copy Markdown
Contributor

bholley commented Jul 20, 2016

So much nicer!

r=me with those comments addressed, but please smoketest this a bit. :-)

@bors-servo delegate+


Reviewed 1 of 1 files at r1, 1 of 4 files at r2, 3 of 3 files at r3.
Review status: all files reviewed at latest revision, 11 unresolved discussions, some commit checks failed.


components/style/properties/gecko.mako.rs, line 371 [r3] (raw file):

    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        v.0.width.to_gecko_style_coord(&mut self.gecko.${gecko_ffi_name}.data_at_mut(${x_index}));
        v.0.width.to_gecko_style_coord(&mut self.gecko.${gecko_ffi_name}.data_at_mut(${y_index}));

Don't you mean height here?


components/style/properties/gecko.mako.rs, line 377 [r3] (raw file):

        self.gecko.${gecko_ffi_name}.data_at_mut(${x_index})
                  .copy_from(&other.gecko.${gecko_ffi_name}.data_at(${x_index}));
        self.gecko.${gecko_ffi_name}.data_at_mut(${x_index})

And don't you want y_index here?


ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 188 [r2] (raw file):

Previously, Manishearth (Manish Goregaokar) wrote…

This line shouldn't be here, since we assert otherwise in set_calc_value.

I don't see where Gecko_SetStyleCoordCalcValue is implemented (maybe hasn't landed yet?), but I'd think we should only pass the union and let servo take care of setting the unit. But why do we need this function at all? Given that we're already reset()ing above, can't we just set the pointer and call the FFI function to addref?

ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 53 [r3] (raw file):

impl<'a> CoordData for SidesData<'a> {
    fn unit(&self) -> nsStyleUnit {
        self.sides.mUnits[self.index]

Given that this isn't marked unsafe, I'm presuming that something in the rust type system guarantees that we can't have index > 3. Out of curiosity, how does that work?


ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 122 [r3] (raw file):

#[derive(Copy, Clone)]
/// Enum representing the tagged union that is CoordData

The general commenting style we've been using so far uses periods in places like this (like Gecko does). Unless there's a rust convention to omit them, I'd rather be consistent. This appears in various places in this PR.


ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 125 [r3] (raw file):

CoordDataValues

I'd think we should call this CoordDataValue?


ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 177 [r3] (raw file):

    #[inline(always)]
    fn set_enum(&mut self, value: CoordDataValues) {

Given that 'enum' is one of the values of the enumerated type itself, I'd prefer to just call this something more generic like "set".


ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 250 [r3] (raw file):

    #[inline]
    unsafe fn as_calc_mut(&mut self) -> &mut nsStyleCoord_Calc {
        transmute(*self.union().mPointer.as_mut() as *mut nsStyleCoord_Calc)

Can we debug_assert the unit here?


ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 268 [r3] (raw file):

    #[inline(always)]
    fn as_enum(&self) -> CoordDataValues {

Same thing with the word 'enum' - maybe .get() or .as_value()? Something consistent with what we do for set.


ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 296 [r3] (raw file):

    /// While this should usually be called with the unit checked,
    /// it is not an intrinsically unsafe operation to call this function
    /// with the wrong unit

Aside from the fact that we're interpreting potentially-garbage memory as a float? That seems bad enough that I would nix this comment and add debug_asserts to all these methods. We might as well just mark them unsafe, because the only callers are already in unsafe blocks.


ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 319 [r3] (raw file):

as_calc

The distinction between as_calc and get_calc is weird. Maybe we should call these get_calc and get_calc_value respectively?


Comments from Reviewable

@bors-servo
Copy link
Copy Markdown
Contributor

✌️ @Manishearth can now approve this pull request

@Manishearth
Copy link
Copy Markdown
Member Author

Review status: 2 of 6 files reviewed at latest revision, 11 unresolved discussions.


components/style/properties/gecko.mako.rs, line 371 [r3] (raw file):

Previously, bholley (Bobby Holley) wrote…

Don't you mean height here?

Yep. Copy-paste error 😄

ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 188 [r2] (raw file):

Previously, bholley (Bobby Holley) wrote…

I don't see where Gecko_SetStyleCoordCalcValue is implemented (maybe hasn't landed yet?), but I'd think we should only pass the union and let servo take care of setting the unit. But why do we need this function at all? Given that we're already reset()ing above, can't we just set the pointer and call the FFI function to addref?

I generally prefer calling constructors from C++ instead of emulating it in Rust. We can't just set the pointer because Rust doesn't auto-construct uninitialized values. Especially because in this case the refcounting fields need to be initialized too.

I also have to allocate here, which I want to avoid doing on the Rust side.

In general wherever an allocation or constructor is involved I'd like to wrap as much of the operation as possible in a single Gecko FFI call.


ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 53 [r3] (raw file):

Previously, bholley (Bobby Holley) wrote…

Given that this isn't marked unsafe, I'm presuming that something in the rust type system guarantees that we can't have index > 3. Out of curiosity, how does that work?

It panics!

Since both the index and the length are known at compile time, this can get optimized away.


ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 122 [r3] (raw file):

Previously, bholley (Bobby Holley) wrote…

The general commenting style we've been using so far uses periods in places like this (like Gecko does). Unless there's a rust convention to omit them, I'd rather be consistent. This appears in various places in this PR.

Rust convention is usually to have periods only when there are multiple sentences, but it's a loose one that's inconsistently followed

ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 177 [r3] (raw file):

Previously, bholley (Bobby Holley) wrote…

Given that 'enum' is one of the values of the enumerated type itself, I'd prefer to just call this something more generic like "set".

set_value, since set is already defined by gecko_values.rs

ports/geckolib/gecko_bindings/sugar/ns_style_coord.rs, line 296 [r3] (raw file):

Previously, bholley (Bobby Holley) wrote…

Aside from the fact that we're interpreting potentially-garbage memory as a float? That seems bad enough that I would nix this comment and add debug_asserts to all these methods. We might as well just mark them unsafe, because the only callers are already in unsafe blocks.

Generally Rust has a very specific definition of `unsafe`, and you're not supposed to use it for other invariants. I personally disagree with this philosophy though -- so I'll mark these unsafe 😄

Comments from Reviewable

@Manishearth
Copy link
Copy Markdown
Member Author

@bors-servo r=bholley

I didn't address the Gecko_SetStyleCoordCalcValue one (reasons given in a comment above). If you still think it necessary, I'll do it in a separate PR coupled with a Gecko change to remove that function.

@bors-servo
Copy link
Copy Markdown
Contributor

📌 Commit 73d7db0 has been approved by bholley

@highfive highfive added S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. and removed S-awaiting-review There is new code that needs to be reviewed. labels Jul 21, 2016
@bors-servo
Copy link
Copy Markdown
Contributor

⌛ Testing commit 73d7db0 with merge 4a77cbd...

bors-servo pushed a commit that referenced this pull request Jul 21, 2016
Introduce safer layer of sugar for nsStyleUnion

This routes (almost) all access to nsStyleUnion through a largely safe interface, CoordData.

It also introduces a corresponding Rust enum, CoordDataValue, which can be used when interacting with CoordData

LLVM should optimize the costs away in release mode. eddyb tested this a bit, and LLVM has no trouble threading matches together with inlining -- so all of the matches using enums here will have the same generated code as the old matches on the units.

Some unresolved questions:

Should I provide convenience methods like `set_coord`, `set_auto`, etc on CoordData? `.set_enum(CoordDataValue::Something)` should optimize down to the same thing, but the convenience methods look cleaner and won't load the optimizer as much.

Also, we're converting immutable references to mutable ones, which can be used to cause unsafety using some acrobatics. Perhaps a trait-based approach is better?
The issue is that in some places we only have a `&CoordData` (eg copy_from), but CoordData internally is `*mut`. It would be nice if CoordData could parametrize over its mutability, but Rust doesn't let us do that.

The alternate approach is to make CoordData a trait (also CoordDataMut). The trait requires you to implement `get_union()` and `get_unit()`, and gives you the rest of the functions for free. `nsStyleCoord` would directly implement both traits. `nsStyleSides` would have `data_at(idx)` and `data_at_mut(idx)` methods which return a struct `SidesData` containing a reference to the Sides and the index, which itself implements the `CoordData` and `CoordDataMut` trait (we need two traits here because there will have to be two `SidesData` structs).

I decided not to implement the traits approach first since it's pretty trivial to change this code to use traits, and the current design is more straightforward.

Thoughts?

r? @bholley

cc @emilio @heycam

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/12521)
<!-- Reviewable:end -->
@bors-servo
Copy link
Copy Markdown
Contributor

☀️ Test successful - android, arm32, arm64, linux-dev, linux-rel, mac-dev-unit, mac-rel-css, mac-rel-wpt, windows-dev

@bors-servo bors-servo merged commit 73d7db0 into servo:master Jul 21, 2016
@highfive highfive removed the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Jul 21, 2016
@Manishearth Manishearth deleted the safer-coord branch July 21, 2016 09:19
bors-servo pushed a commit to rust-lang/rust-bindgen that referenced this pull request Jul 26, 2016
Add support for 'unsafe fields'

Needed for the next step of servo/servo#12521

These are fields which are private but have unsafe accessor functions. Since we generate bindings in a separate module, we can't touch private fields from other parts of the module (an alternative is to inject a footer with these private impls). `pub(restricted)` exists, but is not stable.

r? @emilio
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants