Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions crates/rustc_codegen_spirv/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
69 changes: 69 additions & 0 deletions tests/compiletests/ui/dis/issue-615.rs
Original file line number Diff line number Diff line change
@@ -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<u32, TryFromIntError>` 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));
}
44 changes: 44 additions & 0 deletions tests/compiletests/ui/dis/issue-615.stderr
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions tests/compiletests/ui/lang/issue-615.rs
Original file line number Diff line number Diff line change
@@ -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<u32, TryFromIntError>` 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<u32, TryFromIntError>` 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;
}
Loading