diff --git a/cuda-oxide/crates/mir-lower/src/convert/ops/memory/tests/enums.rs b/cuda-oxide/crates/mir-lower/src/convert/ops/memory/tests/enums.rs index 5658a39cc1..0c1c453e09 100644 --- a/cuda-oxide/crates/mir-lower/src/convert/ops/memory/tests/enums.rs +++ b/cuda-oxide/crates/mir-lower/src/convert/ops/memory/tests/enums.rs @@ -44,6 +44,68 @@ fn unit_variants(n: usize) -> Vec { (0..n).map(|i| EnumVariant::unit(format!("V{i}"))).collect() } +/// Converting a `repr(align(2^29))` byte alignment directly to an LLVM +/// integer bit width wraps at the `u32` boundary: `2^29 * 8 == 2^32`. +/// Keep the storage type's natural alignment within NVPTX's scalar alignment +/// limit; memory operations carry the stronger Rust ABI alignment explicitly. +#[test] +fn enum_conversion_handles_repr_align_bitwidth_boundary() { + use crate::convert::types::{llvm_type_size_align, mir_type_abi_align}; + + for (align, anchor_bits) in [ + (8u64, 64), + (16, 128), + (32, 128), + (64, 128), + (1 << 20, 128), + (1 << 29, 128), + ] { + let mut ctx = make_ctx(); + let ty = make_enum_ty(&mut ctx, "AlignedEnum", 8, unit_variants(2), align, align); + let conv = convert_type(&mut ctx, ty).unwrap(); + let array_ty: TypeHandle = MirArrayType::get(&mut ctx, ty, 3).into(); + let conv_array = convert_type(&mut ctx, array_ty).unwrap(); + let natural_align = u64::from(anchor_bits / 8); + + assert_eq!( + llvm_type_size_align(&ctx, conv), + Some((align, natural_align)), + "enum with {align}-byte alignment keeps rustc size" + ); + assert_eq!( + llvm_type_size_align(&ctx, conv_array), + Some((align * 3, natural_align)), + "enum array with {align}-byte alignment keeps rustc stride" + ); + assert_eq!(mir_type_abi_align(&ctx, ty), Some(align)); + assert_eq!(mir_type_abi_align(&ctx, array_ty), Some(align)); + + let conv_ref = conv.deref(&ctx); + let struct_ty = conv_ref + .downcast_ref::() + .expect("converted enum is a struct"); + let anchor_ty = struct_ty + .fields() + .next() + .expect("expected alignment anchor"); + let anchor_ref = anchor_ty.deref(&ctx); + let anchor = anchor_ref + .downcast_ref::() + .expect("alignment anchor is an array"); + assert_eq!(anchor.size(), 0, "alignment anchor must occupy no bytes"); + let anchor_int = anchor.elem_type(); + let anchor_int_ref = anchor_int.deref(&ctx); + assert_eq!( + anchor_int_ref + .downcast_ref::() + .expect("alignment anchor element is an integer") + .width(), + anchor_bits, + "enum with {align}-byte alignment uses a bounded integer anchor" + ); + } +} + /// Converted enum allocation size must equal rustc's `total_size` for /// every memory-faithful tag shape: that size is what GEP strides by. #[test] diff --git a/cuda-oxide/crates/mir-lower/src/convert/types/enum_layout.rs b/cuda-oxide/crates/mir-lower/src/convert/types/enum_layout.rs index 25eb043d10..0531dc78e2 100644 --- a/cuda-oxide/crates/mir-lower/src/convert/types/enum_layout.rs +++ b/cuda-oxide/crates/mir-lower/src/convert/types/enum_layout.rs @@ -507,12 +507,13 @@ pub(crate) fn build_enum_slot_map( )); } if natural_align < required_align { - // The byte claims alone can under-align the storage, e.g. when the - // only claim is an i8 niche carrier inside a 4-aligned enum. Raise - // the struct's alignment with a zero-length anchor field, the same - // mechanism union storage uses; it occupies no bytes, so every slot - // index simply shifts by one. - let anchor_int = IntegerType::get(ctx, (required_align * 8) as u32, Signedness::Signless); + // Raise the storage's natural alignment with a zero-length anchor; + // it occupies no bytes, so the claim slots only shift by one. + // NVPTX scalar alignment tops out at 16 bytes. Cap the anchor before + // converting alignment to bit width; stronger Rust ABI alignment is + // carried explicitly by memory operations. + let anchor_align = required_align.min(16); + let anchor_int = IntegerType::get(ctx, (anchor_align * 8) as u32, Signedness::Signless); let anchor: TypeHandle = llvm_types::ArrayType::get(ctx, anchor_int.into(), 0).into(); llvm_fields.insert(0, anchor); for slot in &mut slot_of_claim {