Skip to content

add support for unions#569

Open
39ali wants to merge 2 commits into
Rust-GPU:mainfrom
39ali:unions
Open

add support for unions#569
39ali wants to merge 2 commits into
Rust-GPU:mainfrom
39ali:unions

Conversation

@39ali

@39ali 39ali commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Rust unions require reinterpreting the bits of one type as another — e.g. reading [f32; 5] as struct { a: i32, b: [f32; 3], c: u32 }. In SPIR-V this would naively lower to an OpBitcast between two composite types, which is illegal by the spec: OpBitcast only accepts scalar, vector, or pointer operands, never structs or arrays.

previously, any union field access involving layout-compatible but differently-typed SPIR-V composites would either produce invalid SPIR-V or fail to compile.

solution:

implement two new codegen paths in builder_methods.rs that handle type-mismatched copies without relying on composite OpBitcast:

memcpy_const_size — used by the memcpy / OpCopyMemory path (pointer-to-pointer copies).

try_store_composite_scalar_leaves — used by the store path (value-to-pointer writes).

Both work by:

decomposing both the source and destination types recursively into scalar leaves (collect_scalar_leaves), collecting the OpAccessChain index path, SPIR-V type, and bit width for each leaf.

grouping leaves by equal total bit widths (group_leaves_by_bits), this handles both 1:1 (same scalar count) and N:M (different counts, same total bits) cases.

emitting element-wise copies per group:

1→1: OpLoad + optional OpBitcast + OpStore
N→1 (pack): load N scalars, OpCompositeConstruct into an intermediate uN×k vector, OpBitcast to the wider dst type, OpStore
1→N (unpack): OpLoad, OpBitcast to an intermediate uN×k vector, OpCompositeExtract each component, OpStore each

solves #241

@Firestar99 Firestar99 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like the simplicity of the implementation, this was pretty easy to review, but it does ring "AI impl" bells.

/// support union field access, e.g. `[f32; 5]` -> `struct { a: f64, b: [f32; 3] }`.
/// handles both 1:1 (same scalar count) and N:M (different counts but same total bits)
/// returns `false` if the types are not compatible.
fn memcpy_const_size(&mut self, dst: SpirvValue, src: SpirvValue) -> bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's the difference between memcpy_const_size() and try_store_composite_scalar_leaves()? Both functions seem to be almost the exact same, apart from memcpy's arg being src: SpirvValue as a pointer and the other one val: SpirvValue as a value.

}
}

_ => return false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see an implementation for 1 => 1, 1 => N, N => 1 but no N => M?

This compiletest fails:

// build-pass
// compile-flags: -C target-feature=+Int64

use spirv_std::spirv;

union MyUnion {
    a: (u32, u64),
    b: (u64, u32),
}

#[spirv(fragment)]
pub fn union_mixed_types(
    out: &mut u64,
) {
    let bla = MyUnion { a: (42, 123) };
    *out = unsafe { bla.b }.0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants