-
Notifications
You must be signed in to change notification settings - Fork 190
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
Traitify push_next and adding more utility methods for next chains #953
Conversation
Reducing code size is nice, but in and of itself has only limited value; the set of generated code will be huge no matter what. Is there a measurable impact on the compile time for ash or downstream code?
Could we improve caller safety by making having (provided) safe cast and getter methods on this trait, rather than making it a pure marker?
Could these be separate PRs, so we can review and merge them in small independent units?
I disagree with this assumption. Wildcard imports are a stability hazard: new elements in the imported module are not semver-breaks, but can easily break code that performs a wildcard import. This is an especially large hazard for "prelude" modules which are by their nature grab-bags of random items that might grow unpredictably. Further, there is currently exactly one definition in None of that necessarily means that moving these helpers into traits is a bad idea (we already have |
That is correct, but that is also not the main benefit offered by this PR. Here's the train of thoughts:
Now, going back to the original motivation: Why is this needed? Why do we need more next-chain utility functions other than Traditionally, ash is used by applications to call into Vulkan implementations. However, ash is also useful for creating Vulkan implementations and layers. These applications require the ability to inspect Vulkan structures passed in by the user. One example might be: fn getPhysicalDeviceFeatures2(
physicalDevice: vk::PhysicalDevice,
pFeatures: &vk::PhysicalDeviceFeatures2) {
if let Some(ray_tracing_features) = pFeatures
.iter_nexts_mut()
.find(|next: &mut TaggedObject| next.tag() == vk::StructureType::PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR) {
// Fill in ray_tracing_features
ray_tracing_features.ray_tracing_pipeline = vk::TRUE;
}
} The ability to iterate on the next chain is quite essential to Vulkan implementations and layers and can be similarly justified like Clearly, there is demand for other types of next-chain related utility functions (#907) However, as we end up with more and more next-chain utility functions, the existing infrastructure in With the introduction of
This is clearly going to be a semvar-breaking change. The user impact is that they will have to As such, we might as well do some of the other breaking changes simultaneously: #905, renaming
I don't think we're supposed to have functions and definitions in prelude modules either. We can discuss how to clean that up later on. But in general,
We can discuss implementation details and review procedures once the project maintainers are onboard with the spirit of this change. I would prefer not having to do wasted work if the PR ends up in limbo like my previous contribution.
Sure, but imo the changes introduced in this PR are pretty self-contained. Without demonstrating an implementation of cc @Friz64 |
I agree that having methods for traversing pointer chains is appealing, as is providing pointer chain methods with a single definition. Making the addition of new pointer chain methods more palatable is a good argument for this change. I'm still interested in what the compile time impact is.
Are you working on one of these?
This introduces other problems, as I noted above. I don't think having to explicitly import a trait is a deal-breaker on its own, particularly with the prevalence of rust-analyzer to do so automatically.
This seems useful, but we should be careful to ensure that downcasting from a reference that only covers the header of a type is sound under current proposed provenance rules. If it isn't, we may need to switch to a newtyped raw pointer or something to that effect. |
This change brings the number of lines in definition.rs from 59910 to 59449. On my machine there is no observable impact on build time - both the master branch and my branch are around 4.5 seconds when compiling with
Yes.
These casting does not involve pointer to integer casts, so I believe that they should be sound under my understanding of the Rust provenance rules. As an additional note, because This PR should be ready for review. |
My concern is the conversion, via raw pointers, from a reference to a small amount of memory to a reference to a larger amount of memory. |
The conversion from a large amount of memory to a smaller amount of memory is obviously ok, as it can be thought as taking a slice of the original memory. The conversion from a smaller amount of memory to a larger amount of memory is more tricky. I would like to quote the following from the strict provenance docs:
in general, the strict_provenance API really just applies two lint rules that prohibits integer to pointer conversions, so we’re mostly fine for now. All of our conversions are pointer to pointer casts so we maintain an uninterrupted chain of custody. However, for the sake of argument, let’s assume that we’re converting from a This is still ok, because even if we’re converting a usize to a pointer, we always “keep around a pointer that has sufficient provenance to perform that read/write itself”. This is because So based on my understanding, this is fine even with strict provenance. In the future if we want to allow |
Thanks; I'm satisfied that this approach is sound. I think under stacked borrows it would be forbidden, but tree borrows permits it and is more representative of the long term direction of Rust. |
Correction: Ralf specifically calls this pattern out as a UB risk: rust-lang/unsafe-code-guidelines#256. Hence, we must not include it in a release. Instead, please always go through raw pointers; never construct a reference to a base structure that we might later use to access the derived structure. |
@Ralith it's been a while and it sounds like you guys will do this on your own. Should I close this now? Or should I work on updating the PR |
Currently, we implement
push_next
methods for all base types in the generator. This has two problems:extend_next
(Addextend_next
method to extendable structs #907) oriter_nexts
becomes more difficult, as the additional utility methods will bloat the generated code sizes further.This PR solves the problem by doing the following two things:
BaseTaggedStructure
marker trait instead of thepush_next
functions.Extends<BaseType>
trait instead ofExtendsXXXXX
trait. This was suggested in Switch fromimpl ExtendsC for E
toimpl ExtendableFrom<E> for C
#879NextChainExt
.Some of the additional, nice-to-have things I've done in this PR:
TaggedObject
, an abstraction overvk::BaseOutStructure
, which allows you to use it in the same way as a &dyn Any, but without the vtable overheads since casting is just trivial pointer casts.push_next
to bewith_next
to be consistent with Rust naming conventions.push_next
now has the same behavior asVec::push
: it takes&mut self
and returns()
.push_next
no longer chases the p_next chain for the extension structs; instead it asserts that the p_next field in the extension structure is null. This addressespush_next
being safe to call is unsound #905.Drawbacks:
I would like to address the "major drawbacks" comment made by @Ralith in #879.
NextChainExt
trait toash::prelude
, so if the user hasuse ash::prelude::*
(like they should), they should not be impacted. Imo this is a minor inconvenience compared to the benefit of having more usable next chain utility methods and significantly reduced generated code sizes.BaseTaggedStructure
andExtends
are marker traits only. The actual utility methods are implemented separately inNextChainExt
with a blanket implementation over relevant types.Resolves #905
Resolves #907
Resolves #879