Collapse #[repr(transparent)] newtypes in SPIR-V type translation#566
Collapse #[repr(transparent)] newtypes in SPIR-V type translation#566novacrazy wants to merge 2 commits intoRust-GPU:mainfrom
#[repr(transparent)] newtypes in SPIR-V type translation#566Conversation
LegNeato
left a comment
There was a problem hiding this comment.
Thank you! Don't worry about AI use, thank you for calling it out. The compiletests are failing.
|
Seems to be a (good) side effect of this change. I've found in my own testing that |
|
Tests should pass now. |
|
Yeah the codegen is not sophisticated at all. We've been discussing the best way to make it better. |
Firestar99
left a comment
There was a problem hiding this comment.
This is looking good, but I do want to add some disassembly tests to check it actually does the correct thing.
I do not like our codegen type system at all, our own sizeof() function is just heresy waiting to blow up at some point... I would like to burn it all down, whenever I find the time to build a suitable replacement.
| // We omit the `eq_up_to_validity` check on `backend_repr`: the | ||
| // outer wrapper always has `BackendRepr::Memory` here while the | ||
| // inner field may have a different repr (e.g. a type annotated | ||
| // with `#[rust_gpu::spirv(vector)]` goes through the intrinsic | ||
| // path above and returns an `OpTypeVector`). The reprs will | ||
| // never match, but `#[repr(transparent)]` guarantees full ABI | ||
| // identity, so the structural checks in the helper suffice. |
There was a problem hiding this comment.
This part is AI BS, if you add eq_up_to_validity here you'll get the same result. In general, this patch just allows #[transparent] to inline glam vecs (rather #[rust_gpu::vector::v1] annotated structs) from what I can tell.
My entire library ecosystem depends on #[repr(transparent)] working as intended, and rust-gpu was failing that.
Could you explain what exactly fails? Since this doesn't seem to be about optimization, but about something failing compilation in a certain context?
There was a problem hiding this comment.
Also #[spirv(Block)] is deprecated and doesn't need to be checked
|
I don't seem to have permissions to push to this pull request, so I pushed my changes to https://github.com/Rust-GPU/rust-gpu/tree/collapse-repr-transparent. Also includes a commit that fixes I would still like @eddyb to have a another look over this, cause screwing up ABI in suttle ways is really bad (we may also want to delay the merge until we release the alpha this week) |
|
@Firestar99 In my library, I define SPIR-V vectors as my "Registers", then use them in a However, not always, and now I'm actually having trouble reproducing it exactly on the same version of rust-gpu I had before. Weird. I remember something about rust-gpu erroring because I wasn't using a direct SPIR-V type for shader inputs, but now that I try to reproduce it it's not complaining. Something I changed along the way must have accidentally helped. I just know that at the time, the changes to This change still reduces extra wrapping and access chains, though, even in the best cases, and fixed the weird codegen I experienced before even if I can't replicate it. |
And then the drivers see a vector op and "scalarizes" it again into 4 add ops ( So vectors actually being vectors is mostly a validation thing, as some instrinsics and built-ins do require actual vectors (or scalars) to operate on. That requirement is also being softened with |
Motivation
When a
#[repr(transparent)]struct wraps a SPIR-V vector, rust-gpu currently wraps the inner type in anOpTypeStruct, causing downstream usage to scalarize every operation, along with lots of composite extract and construct operations. This happened in a project of mine that works by defining low-level "Register" types (originally for CPU SIMD registers, but now also SPIR-V vectors), then wrapping them in a high-levelVector<R: Register>(Storage<R>)struct to implement generic traits and operator overloads on. My entire library ecosystem depends on#[repr(transparent)]working as intended, and rust-gpu was failing that.Full disclosure, this is a hobby project of mine, and I didn't have time to dig into rust-gpu myself deeply. I used Claude to find the problem area and refine it into what it is now. The rest of this PR describing the changes is written by it. Do note I am not an inept "vibe coder", so if anything does need fixing I can look into it, but this does work for at least my library.
Change
In
TyAndLayout::spirv_type, when the outer layout hasBackendRepr::Memoryand the Rust type is#[repr(transparent)]with exactly one non-ZST field matching the outer's size/align/offset, reuse the inner field's SPIR-V type directly instead of emitting a wrappingOpTypeStruct.The structural check is the same newtype-unpacking logic already used for
BackendRepr::ScalarPair- factored into a sharedsole_structural_newtype_fieldhelper. The two call sites differ only in how they prove ABI identity:ScalarPairusesBackendRepr::eq_up_to_validity.Memoryrelies on#[repr(transparent)], which guarantees full ABI identity by construction. The reprs themselves won't match here (outer isMemory, inner may be anything), so the repr check doesn't apply.Guardrails
#[repr(transparent)]types. Opaque single-field structs likestruct Viewport { rect: Vec4 }keep theirOpTypeStructidentity (important for push-constantBlockdeclarations, interface matching,OpMemberDecorate).#[spirv(block)], since that decoration needs anOpTypeStructto land on.blockis the only type-level SPIR-V attribute at present (besidesintrinsic_type, which short-circuits earlier); if more are added, they'll need adding to this guard.OpNameon the collapsed inner id (newname_type_idhelper), so disassembly still showsVector,Position, etc. The existing per-(id, name_key)dedup already supported multiple names per id, so this piggybacks on the same mechanism.Incidental
Transparent unions (
#[repr(transparent)] union { a: T, b: () }) also collapse correctly, since the new guard sits at theBackendReprlevel and doesn't care aboutFieldsShape. Previously those went through the "largest case" path intrans_aggregate, which happened to produce the same result when alignments matched but wrapped otherwise.