Skip to content

VirtualAddressSpaceIndpendent: use trait bounds in derive macro#2772

Merged
sporksmith merged 6 commits intoshadow:mainfrom
sporksmith:vasi-conditional
Mar 1, 2023
Merged

VirtualAddressSpaceIndpendent: use trait bounds in derive macro#2772
sporksmith merged 6 commits intoshadow:mainfrom
sporksmith:vasi-conditional

Conversation

@sporksmith
Copy link
Copy Markdown
Contributor

@sporksmith sporksmith commented Feb 28, 2023

Extends the VirtualAddressSpaceIndependent Derive macro to handle generics, and replaces manual implementation of the trait by using the derive macro.

(Previously:)

For discussion purposes - I'm a bit stumped how to get this to work. In particular I don't see a way to write the static_assertions for generics.

The current incarnation fails in a doc test that tries to use the macro with a generic type.

The test:

/// #[derive(VirtualAddressSpaceIndependent)]
/// struct MyWrapper<T> {
///   val: T,
/// }

The failure:

 --> src/lib.rs:120:8
  |
6 | struct MyWrapper<T> {
  |                  - type parameter from outer function
7 |   val: T,
  |        ^ use of generic parameter from outer function

The problem is we end up generating something like this, and T isn't allowed in this position:

impl MyWrapper<T> where T: VirtualAddressSpaceIndependent {
  fn _vasi_assertions() {
    assert_impl_all!(T: vasi::VirtualAddressSpaceIndependent);
  }
}

@github-actions github-actions bot added Component: Build Build/install tools and dependencies Component: Libraries Support functions like LD_PRELOAD and logging labels Feb 28, 2023
@sporksmith
Copy link
Copy Markdown
Contributor Author

Oops, was supposed to be a Draft. It looks like there's no way to make a PR into a Draft once it's non-Draft?

@sporksmith
Copy link
Copy Markdown
Contributor Author

An alternative approach I'm tinkering with is to add a method to the Vasi trait, and access the fields by name in the body instead of trying to name the (possibly generic) types of the fields.

It looks like this could actually work but requires additional complexity to deal with unnamed fields, enum variants, union fields (which need unsafe access), ...

Anyway the idea is to change the vasi trait declaration to:

pub unsafe trait VirtualAddressSpaceIndependent {
    /// Used by the derive macro to validate that fields are Vasi.
    fn is_vasi(&self) {}
}

and then in the macro, for MyWrapper above generate something like:

impl <T> VirtualAddressSpaceIndependent for MyWrapper<T> where T: VirtualAddressSpaceIndependent {
  fn is_vasi(&self) {
    VirtualAddressSpaceIndependent::is_vasi(&self.val);
  }
}

@sporksmith
Copy link
Copy Markdown
Contributor Author

@stevenengler if you get a chance to look at this, maybe you have a better idea how to do this?

@sporksmith sporksmith marked this pull request as draft February 28, 2023 23:09
@stevenengler
Copy link
Copy Markdown
Contributor

stevenengler commented Feb 28, 2023

I'm still not sure I understand completely, but could you do something more like the Copy derive where it's a compile-time failure if a non-generic field is not Copy, but then is derived conditionally for generic fields?

So something like:

#[derive(VirtualAddressSpaceIndependent)]
struct MyWrapper<T, K> {
    foo: Option<T>,
    bar: u32,
    asdf: K
}

becomes:

struct MyWrapper<T, K> {
    foo: Option<T>,
    bar: u32,
    asdf: K
}

assert_impl_all!(u32: vasi::VirtualAddressSpaceIndependent);

unsafe impl<T, K> VirtualAddressSpaceIndependent for MyWrapper<T, K>
where
    Option<T>: VirtualAddressSpaceIndependent,
    K: VirtualAddressSpaceIndependent,
{}

@sporksmith
Copy link
Copy Markdown
Contributor Author

My understanding is that the derive macro for Copy is conditional on the generic type parameters being Copy, not the field types being Copy

According to https://stegosaurusdormant.com/understanding-derive-clone/#do-we-have-to-inspect-the-fields this is because making the constraints on the field types can leak private types into the public interface, which results in compilation errors. Maybe we could live with that limitation, though.

@stevenengler
Copy link
Copy Markdown
Contributor

Ah okay, I wondered then what happens if T were Copy but Wrapper<T> were !Copy. It seems like there's maybe some compiler magic that doesn't ever let this happen, so a better example is probably Clone. And your link shows why that would fail (the field access in the derived clone() method will try to call clone() on the field, but it not compile since Wrapper<T> doesn't always implement Clone).

@stevenengler
Copy link
Copy Markdown
Contributor

Here's another idea to avoid requiring a method in the trait. It still needs to access each field though:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=712fb56f0c303a52111503c4d0f34484

@stevenengler
Copy link
Copy Markdown
Contributor

stevenengler commented Mar 1, 2023

@sporksmith
Copy link
Copy Markdown
Contributor Author

I think we're miscommunicating. My understanding (and I think this matches what you're saying) is that e.g. for:

#[derive(Copy, Clone)]
struct MyStruct<T> {
    _x: T,
}

MyStruct is conditionally Copy and Clone. e.g. MyStruct<Box<T>> can be constructed, but won't be Copy since Box is never Copy.

OTOH for a struct definition is

#[derive(Copy, Clone)]
struct MyStruct<T> {
    _x: Box<T>,
}

then there will be a compilation failure since _x isn't Copy even if T is.

i.e. this is different from your earlier example that would emit where Option<T>: VirtualAddressSpaceIndependent,; in particular that approach has the problem of exposing the field types in the public interface, and failing to compile if those types are private.

Anyways, I think I've gotten something working, and the ideas in your gist were definitely helpful - particularly using a trait const instead of a trait method seems a little nicer, and facilitates ensuring the code is never called at runtime (and probably dropped from the final binary).

It's currently checking the field values, though I may revisit your idea that uses the types instead. I'll take another look at that and clean up a bit before marking it for review.

@sporksmith
Copy link
Copy Markdown
Contributor Author

I think this is ready now! Thanks for the help and ideas!

@sporksmith sporksmith marked this pull request as ready for review March 1, 2023 14:25
@sporksmith sporksmith requested a review from stevenengler March 1, 2023 14:25
Copy link
Copy Markdown
Contributor

@stevenengler stevenengler left a comment

Choose a reason for hiding this comment

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

Cool!

This extends the macro to handle types with generic type parameters. It
takes the same approach as e.g. `Copy` and `Clone` in std: the type will
*conditionally* implement the trait if  all of the generic type
parameters do, and will fail to compile if any field isn't actually
VirtualAddressSpaceIndependent.
@sporksmith sporksmith enabled auto-merge March 1, 2023 15:53
@sporksmith sporksmith merged commit 08375bf into shadow:main Mar 1, 2023
@sporksmith sporksmith deleted the vasi-conditional branch March 1, 2023 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Component: Build Build/install tools and dependencies Component: Libraries Support functions like LD_PRELOAD and logging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants