diff --git a/crates/rustc_codegen_spirv/src/abi.rs b/crates/rustc_codegen_spirv/src/abi.rs index 2654177ef4..f1453a8c5f 100644 --- a/crates/rustc_codegen_spirv/src/abi.rs +++ b/crates/rustc_codegen_spirv/src/abi.rs @@ -740,6 +740,80 @@ fn trans_struct_or_union<'tcx>( } }; } + + // The loop above only emits a multi-variant `enum`'s tag, not its variant + // payloads. `rustc_codegen_ssa` accesses payload fields by byte offset + // (`inbounds_ptradd` after a downcast), so for `BackendRepr::Memory` `enum`s + // the payload fields must be present to recover a valid `OpAccessChain`, + // otherwise it's zombie'd as "cannot offset a pointer to an arbitrary element". + // + // So also emit each variant's non-ZST payload fields at their offsets, + // skipping any that overlap an already-present field (struct fields can't + // overlap). This should eventually be replaced with untyped memory + `qptr`. + if union_case.is_none() + && let Variants::Multiple { variants, .. } = &ty.variants + { + // Track the byte ranges already covered, to reject overlapping payloads. + let mut covered: Vec<(Size, Size)> = field_offsets + .iter() + .zip(&field_types) + .map(|(&offset, &field_ty)| { + let end = cx + .lookup_type(field_ty) + .sizeof(cx) + .map_or(offset, |size| offset + size); + (offset, end) + }) + .collect(); + + let mut extra = Vec::new(); + for variant_idx in variants.indices() { + let variant = ty.for_variant(cx, variant_idx); + for i in variant.fields.index_by_increasing_offset() { + let field = variant.field(cx, i); + if field.is_zst() { + continue; + } + let offset = variant.fields.offset(i); + let end = offset + field.size; + if covered.iter().any(|&(o, e)| offset < e && o < end) { + continue; + } + covered.push((offset, end)); + let name = match ty.ty.kind() { + TyKind::Adt(adt, _) => { + adt.variants()[variant_idx].fields[FieldIdx::new(i)].name + } + _ => Symbol::intern(&format!("variant{}_field{i}", variant_idx.as_usize())), + }; + extra.push((offset, field.spirv_type(span, cx), name)); + } + } + + // Merge in the payload fields, sorted by offset so the tag stays field + // `0` (required by `recover_access_chain_from_offset` and discriminant + // access). + if !extra.is_empty() { + let mut merged: Vec<_> = field_offsets + .iter() + .zip(&field_types) + .zip(&field_names) + .map(|((&offset, &field_ty), &name)| (offset, field_ty, name)) + .collect(); + merged.extend(extra); + merged.sort_by_key(|&(offset, ..)| offset); + + field_offsets.clear(); + field_types.clear(); + field_names.clear(); + for (offset, field_ty, name) in merged { + field_offsets.push(offset); + field_types.push(field_ty); + field_names.push(name); + } + } + } + SpirvType::Adt { def_id: def_id_for_spirv_type_adt(ty), size, diff --git a/tests/compiletests/ui/dis/issue-615.rs b/tests/compiletests/ui/dis/issue-615.rs new file mode 100644 index 0000000000..b5979bc6e5 --- /dev/null +++ b/tests/compiletests/ui/dis/issue-615.rs @@ -0,0 +1,69 @@ +#![crate_name = "issue_615"] + +// Tests the generated SPIR-V type shapes for multi-variant `enum`s whose layout +// is `BackendRepr::Memory` (i.e. not niche- or `ScalarPair`-optimized). Their +// variant payloads must appear as fields of the `OpTypeStruct`, otherwise field +// accesses into a payload can't be codegen'd into a valid `OpAccessChain` (which +// is what regressed in #615, where `Result` became such an +// `enum` once `TryFromIntError` stopped being a ZST). +// +// Here the payloads sit at distinct offsets, so every payload is exposed: +// `Multi` becomes `{ discriminant, u32, u32 }` and `Tri` becomes +// `{ discriminant, u32, u32, u32 }`. + +// build-pass +// compile-flags: -C llvm-args=--disassemble-globals +// normalize-stderr-test "([A-Za-z]:)?/\S*/library/core/src/" -> "$$CORE_SRC/" +// normalize-stderr-test "OpCapability VulkanMemoryModel\n" -> "" +// normalize-stderr-test "OpSource .*\n" -> "" +// normalize-stderr-test "OpExtension .SPV_KHR_vulkan_memory_model.\n" -> "" +// normalize-stderr-test "OpMemoryModel Logical Vulkan" -> "OpMemoryModel Logical Simple" + +// `compiletest` handles `ui\dis\`, but not `ui\\dis\\`, on Windows. +// normalize-stderr-test "ui/dis/" -> "$$DIR/" + +use spirv_std::spirv; + +// `B`'s two payload fields force `BackendRepr::Memory` (no single common +// primitive across variants) and sit at distinct offsets, so both are exposed +// alongside the discriminant. +pub enum Multi { + A(u32), + B(u32, u32), +} + +pub enum Tri { + A(u32), + B(u32, u32), + C(u32, u32, u32), +} + +#[inline(never)] +fn read_multi(e: &Multi) -> u32 { + match e { + Multi::A(x) => *x, + Multi::B(_, b) => *b, + } +} + +#[inline(never)] +fn read_tri(e: &Tri) -> u32 { + match e { + Tri::A(x) => *x, + Tri::B(_, b) => *b, + Tri::C(_, _, c) => *c, + } +} + +#[spirv(fragment)] +pub fn main(#[spirv(flat)] i: u32, out: &mut u32) { + let m = if i > 0 { Multi::A(i) } else { Multi::B(1, 2) }; + let t = if i > 1 { + Tri::A(i) + } else if i == 1 { + Tri::B(1, 2) + } else { + Tri::C(1, 2, 3) + }; + *out = read_multi(&m).wrapping_add(read_tri(&t)); +} diff --git a/tests/compiletests/ui/dis/issue-615.stderr b/tests/compiletests/ui/dis/issue-615.stderr new file mode 100644 index 0000000000..5e1b2cc7a1 --- /dev/null +++ b/tests/compiletests/ui/dis/issue-615.stderr @@ -0,0 +1,44 @@ +OpCapability Shader +OpMemoryModel Logical Simple +OpEntryPoint Fragment %1 "main" %2 %3 +OpExecutionMode %1 OriginUpperLeft +%4 = OpString "$CORE_SRC/num/mod.rs" +%5 = OpString "$DIR/issue-615.rs" +OpName %6 "Multi" +OpMemberName %6 0 "discriminant" +OpMemberName %6 1 "0" +OpMemberName %6 2 "1" +OpName %7 "Tri" +OpMemberName %7 0 "discriminant" +OpMemberName %7 1 "0" +OpMemberName %7 2 "1" +OpMemberName %7 3 "2" +OpName %2 "i" +OpName %3 "out" +OpName %8 "issue_615::read_multi" +OpName %9 "issue_615::read_tri" +OpDecorate %2 Flat +OpDecorate %2 Location 0 +OpDecorate %3 Location 0 +%10 = OpTypeInt 32 0 +%11 = OpTypePointer Input %10 +%12 = OpTypePointer Output %10 +%13 = OpTypeVoid +%14 = OpTypeFunction %13 +%6 = OpTypeStruct %10 %10 %10 +%15 = OpTypePointer Function %6 +%7 = OpTypeStruct %10 %10 %10 %10 +%16 = OpTypePointer Function %7 +%2 = OpVariable %11 Input +%17 = OpTypeBool +%18 = OpConstant %10 0 +%19 = OpTypePointer Function %10 +%20 = OpConstant %10 1 +%21 = OpConstant %10 2 +%22 = OpConstant %10 3 +%23 = OpTypeFunction %10 %15 +%24 = OpTypeInt 32 1 +%25 = OpConstant %24 0 +%26 = OpTypeFunction %10 %16 +%27 = OpUndef %10 +%3 = OpVariable %12 Output diff --git a/tests/compiletests/ui/lang/issue-615.rs b/tests/compiletests/ui/lang/issue-615.rs new file mode 100644 index 0000000000..193297ee07 --- /dev/null +++ b/tests/compiletests/ui/lang/issue-615.rs @@ -0,0 +1,32 @@ +#![crate_name = "issue_615"] + +// Tests that fallible integer conversions through `TryFrom`/`Result` can be +// codegen'd. This regressed after the nightly-2026-05-22 toolchain upgrade with +// errors like "cannot cast between pointer types" and "cannot offset a pointer +// to an arbitrary element" originating from `core::convert::num`. +// +// The root cause: `core::num::TryFromIntError` is no longer a ZST (it carries a +// niche-bearing error-kind `enum`), so `Result` is laid +// out as `BackendRepr::Memory` with its `Ok`/`Err` payloads at distinct offsets. +// Such multi-variant `enum`s previously had their payloads omitted from the +// generated SPIR-V type, breaking payload field accesses. + +// build-pass + +use spirv_std::spirv; + +#[spirv(fragment)] +pub fn main(#[spirv(flat)] i: u32, out: &mut u32) { + let v = i as usize; + + // Direct conversion via `TryFrom` + `Result`/`Option` adapters. + let mut acc = u32::try_from(v).ok().unwrap_or(0); + + // The `Result` also shows up indirectly inside + // `StepBy`'s iteration, which is the form originally reported in #615. + for index in (0..v).step_by(2) { + acc = acc.wrapping_add(index as u32); + } + + *out = acc; +}