-
Notifications
You must be signed in to change notification settings - Fork 192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
References in FFI types/signatures #441
Comments
That'd be a very welcome change indeed! Though we have to keep in mind to not "accidentally" generate this for DSTs like dynamic types and slices.
Any idea if we can do something similar for arrays? Those have been most problematic in our code (even with the builder pattern, ie. Finally, the builders already seem "unnecessary" in hiding the functions behind a separate struct to not have lifetimes on the FFI structs. If we go ahead with this change (and add We should go ahead and make this change, and dog-feed it to our own code as an initial feasibility test. Likewise should we reconsider making |
Unfortunately I think this is impossible for slices, because Vulkan lays out the length inline in the struct, and we have absolutely no guarantee that the resulting layout would match that of a slice even if/when Rust slices do get a defined ABI. However, if we subsumed the builders into methods on Vulkan structs as you suggest, I think we'd have the next best thing: such methods could easily enforce that a passed-in array outlives the struct, just like they do for builders today. It'd be a bit weird to use struct construction syntax for most things and a setter for arrays, but it'd work. |
I don't think this accomplishes anything, for two reasons:
Anyway, it sounds like we have a good case for getting rid of builders entirely (which might help our compile times besides!) |
Would every pointer be replaced with an |
How would that look like? Like this? |
A C pointer to a slice maps to a reference to its first element. So if slices can't go into structs due to ABI issues, then a separate count + reference to first element can work. Of course, this means you lose checking on the length, so methods may still be needed. |
Yep, that's what I was thinking of.
This feels really weird to me. I've also gotten the length wrong far more times than I'd gotten the lifetimes wrong, pre-builders. |
You could store a |
I wasn't planning on laying out the slice one way or another inside Vulkan structs, rather how to model a safe borrow to it in the first place. Would a Lengths are troublesome anyway, consider for example
With this we'd basically be moving the lifetime parameter over to the struct itself, and automatically get rid of the
At least in our codebase we're minimizing the scope of Regardless we have already accepted that Vulkan functions are
Just like how
🥳
That's the purpose of lifetimes and this issue, to prevent users from having objects that reference dropped memory - they have to worry about lifetimes one way or another. In an ideal world we should have all primitives and single-object borrows as public fields, but hide lengths and array pointers behind appropriate slice getters and setters to minimize the possibility for UB; both in terms of pointee-lifetime and slice length mishaps. |
I probably don't want to worry about storing slice lengths separately if they are already encoded in the Vulkan structs itself. However, they might address the above problem of multiple array-pointer-fields sharing the same length field while still being able to re-set a slice field later, with different sizes. |
Yes, but I agree it's a bit confusing. Then again, using setters only for array fields is weird too, unless we keep all the setters, which is a bit disappointing from a code savings perspective but makes for more consistent style. Not sure what the best balance is.
Yes, that's the idea.
Most of my Vulkan code lives in functions that are almost exclusively concerned with making Vulkan calls or invoking methods that have unchecked assumptions about Vulkan state, so any narrowing further than the function level would lead to ~every expression being separately wrapped in
Yes, exactly. I can see both sides here, but my feeling is that using |
Indeed, the end result inevitably has to look like #441 (comment) with fields and a default in FRU followed by one or more explicit setters. It's not consistent but we might as well try this and see what impact it has on our own programs (OTOH that's a lot of effort to implement when we already know what it looks like).
I guess that already is the case for us in our Vulkan backend, |
Adopting references for non-array stuff and moving all setters to the actual Vulkan structs rather than builders should be uncontroversial and a big win on its own; we can then separately consider stripping things down further. |
The only controversy might be in the lifetime parameters that will have to be specified all across the generated functions and function-pointers which use these struct types directly, that could become annoying generator-wise (unless the new generator is used, which seems to have better support for a distinct parsing+analysis phase, separate from the code generation phase). |
Sounds like a good reason to pursue this as part of the new generator, then! |
https://twitter.com/dotstdy/status/1454505264246312980?t=Og0n2mof83gW_mmLQLzOdg Got linked this relevant thread that judges Ash build time and uses exactly the things we proposed here! (except the All in all this is probably something we can start working on right now, as it doesn't seem there's a lot of traction on the new one yet. @MaikKlein what's its status, is it something that everyone just needs to start working on? |
#602 is a big step in this direction. |
@Ralith is this still something you want to chase up? It might be relevant for the few structs one could construct directly without |
I'm pretty bandwidth-starved at the moment, at least. The slice representation problem means we'll always need to rely on setters in some cases, so at least off the cuff I don't mind leaning into them. Is vk.xml precise enough to let us judge where to use |
If we can't eliminate |
I previously linked a Twitter post but it seems to have gotten removed. It had a screenshot of some struct VkFoo {
count: u32, // off=0
_implicit_padding: u32, // off=4
ptr: *const u32, // off=8
} But that padding is not there in the parent struct if the previous members sum up to an uneven multiplier of 4-bytes struct VkFoo {
flags: u32, // off=0
count: u32, // off=4
// No padding needed!
ptr: *const u32, // off=8
} And if I remember correctly there was one more template parameter in the screenshot which must have been driving this.
Only slices would prevent this, right? Then the
That's a question for @oddhack I think, I don't know if TBH I don't think writing On yet another note, especially if we end up going this route of |
I don't think such types can ever be sound. A nested struct is not guaranteed to have the same layout as the equivalent fields inlined into a struct.
Not sure when this would be very important, but it seems harmless since we're inlining them all anyway. |
|
Catching up on this very old issue because of Matrix discussions, I think we might build the slice wrapper from the removed Twitter comment as long as we have different slice wrapper types around with different size/packing rules. That means the generator will have to keep track of the size and alignment of every field to pick the right one. Example lacks an implementation and appropriate lifetimes. And we need a #[repr(C)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Copy, Clone)]
pub struct VulkanSlice1<T> {
size: u32,
// Implicit u32 padding here
pointer: *const T,
}
#[repr(packed(4))]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Copy, Clone)]
pub struct VulkanSlice1Packed<T> {
size: u32,
pointer: *const T,
}
#[repr(C)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Copy, Clone)]
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkDeviceCreateInfo.html>"]
#[must_use]
pub struct DeviceCreateInfoSlices<'a> {
pub s_type: StructureType,
pub p_next: *const c_void,
pub flags: DeviceCreateFlags,
pub queue_create_infos: VulkanSlice1Packed<DeviceQueueCreateInfo<'a>>,
#[deprecated = "functionality described by this member no longer operates"]
pub enabled_layer_names: VulkanSlice1<*const c_char>,
pub enabled_extension_names: VulkanSlice1<*const c_char>,
pub p_enabled_features: *const PhysicalDeviceFeatures,
pub _marker: PhantomData<&'a ()>,
}
/// Ensure the size stays the same
const CHECK: usize = (std::mem::size_of::<DeviceCreateInfoSlices>()
== std::mem::size_of::<DeviceCreateInfo>()) as usize
- 1; Keep in mind that the common pattern to have a size field before the pointer field might not apply everywhere. We'd have to scan through |
Rust's
&'a T
is guaranteed to be interpretable as a nonnull raw pointer. Wrapping them inOption
adds the null case. Given that Vulkan generally does not capture passed-in pointers, I think we could replace most uses of raw pointers to single objects in FFI signatures and structures with Rust references. This would help mitigate the footgun of storing a raw pointer in a struct and then dropping the pointee before passing the struct to Vulkan, and make builders less needed, though still nice for arrays.Can anyone think of a reason not to do this?
The text was updated successfully, but these errors were encountered: