From d828864703fcc1286003f2bf26ef49221509eab2 Mon Sep 17 00:00:00 2001 From: Yunus Mutlu Date: Sat, 26 Sep 2026 18:06:16 +0300 Subject: [PATCH] fix(mir-importer): lower from-end ConstantIndex after Subslice Signed-off-by: Yunus Mutlu --- .../src/translator/rvalue/place_addr.rs | 32 +++-- .../src/translator/rvalue/place_iter.rs | 98 ++++++++++----- .../examples/subslice_projection/README.md | 9 +- .../examples/subslice_projection/src/main.rs | 116 ++++++++++++++++++ 4 files changed, 213 insertions(+), 42 deletions(-) diff --git a/cuda-oxide/crates/mir-importer/src/translator/rvalue/place_addr.rs b/cuda-oxide/crates/mir-importer/src/translator/rvalue/place_addr.rs index 0b09daa48c..d8d3e5f6fe 100644 --- a/cuda-oxide/crates/mir-importer/src/translator/rvalue/place_addr.rs +++ b/cuda-oxide/crates/mir-importer/src/translator/rvalue/place_addr.rs @@ -173,7 +173,7 @@ pub(super) fn emit_slice_len_extract( /// rustc defines the offset as 1-based from the end, so the index is /// `slice_len - offset`. The MIR pattern-length test dominates this place, /// therefore the subtraction cannot underflow on an executed path. -fn emit_from_end_slice_index( +pub(super) fn emit_from_end_slice_index( ctx: &mut Context, slice_len: Value, offset: u64, @@ -593,11 +593,10 @@ pub(super) fn emit_array_subslice_value( /// out an address that cannot honor the storage coercion. /// `Subslice` is handled for both sized arrays and slice fat pointers. Array /// subslices keep an in-memory address; slice subslices advance the data -/// pointer and rebuild metadata as `len - (from + to)`, and can continue into -/// element Index/forward-ConstantIndex projections. From-end `ConstantIndex` -/// is accepted only when the immediately preceding fat-slice deref supplied -/// runtime length metadata; other bases (including a from-end index after a -/// Subslice) punt instead of guessing a length. +/// pointer and rebuild metadata as `len - (from + to)`. Element Index and +/// ConstantIndex projections can continue from that rebuilt slice. A from-end +/// ConstantIndex uses the rebuilt slice's runtime length metadata, so its index +/// is computed relative to the subslice rather than the original fat slice. /// /// Returns `Ok(Some((addr, last_op)))` on success, `Ok(None)` if the /// projection chain contains an element this helper doesn't know how to @@ -779,9 +778,7 @@ fn translate_place_addr_from_slot( match &projection[proj_idx + 2] { mir::ProjectionElem::Index(_) - | mir::ProjectionElem::ConstantIndex { - from_end: false, .. - } => { + | mir::ProjectionElem::ConstantIndex { .. } => { let Some(data_ptr_ty) = erased_slice_data_pointer_type(ctx, subslice, elem_ty) else { @@ -804,6 +801,22 @@ fn translate_place_addr_from_slot( current = extract_ptr.deref(ctx).get_result(0); current_prev_op = Some(extract_ptr); current_is_slice_data = true; + current_slice_len = if matches!( + &projection[proj_idx + 2], + mir::ProjectionElem::ConstantIndex { from_end: true, .. } + ) { + let (len, len_op) = emit_slice_len_extract( + ctx, + subslice, + block_ptr, + current_prev_op, + loc.clone(), + ); + current_prev_op = Some(len_op); + Some(len) + } else { + None + }; consumed_slice_subslice = true; continue; } @@ -1342,6 +1355,7 @@ fn translate_place_addr_from_slot( if *from_end && consumed_slice_subslice { consumed_slice_subslice = false; current_is_slice_data = true; + current_slice_len = entered_slice_len; continue; } // A slice Subslice must be lowered while the fat-pointer diff --git a/cuda-oxide/crates/mir-importer/src/translator/rvalue/place_iter.rs b/cuda-oxide/crates/mir-importer/src/translator/rvalue/place_iter.rs index 2beb14d9cd..e54e992b7a 100644 --- a/cuda-oxide/crates/mir-importer/src/translator/rvalue/place_iter.rs +++ b/cuda-oxide/crates/mir-importer/src/translator/rvalue/place_iter.rs @@ -8,7 +8,8 @@ use super::const_bytes::translate_zero_sized_constant_value; use super::const_enum::create_ghost_enum_default; use super::place_addr::{ - emit_array_subslice_value, emit_slice_len_extract, emit_slice_subslice_value, + emit_array_subslice_value, emit_from_end_slice_index, emit_slice_len_extract, + emit_slice_subslice_value, }; use super::place_read::{ apply_deref_projection, apply_enum_field_projection, apply_field_addr_and_load, @@ -536,14 +537,44 @@ pub fn translate_place_iterative( ) ); } - if *from_end { - return input_err!( - loc, - TranslationErr::unsupported( - "ConstantIndex with from_end=true not yet supported" - ) + let runtime_index = if *from_end { + // A from-end ConstantIndex needs runtime slice length. + // After a slice Subslice, current_value is intentionally + // kept as the rebuilt `(data_ptr, new_len)` fat value. + let current_ty = current_value.get_type(ctx); + if !current_ty + .deref(ctx) + .is::() + { + return input_err!( + loc, + TranslationErr::unsupported( + "from-end ConstantIndex requires preserved slice metadata" + .to_string() + ) + ); + } + + let (slice_len, len_op) = emit_slice_len_extract( + ctx, + current_value, + block_ptr, + current_prev_op, + loc.clone(), ); - } + let (index_value, index_op) = emit_from_end_slice_index( + ctx, + slice_len, + *offset, + block_ptr, + Some(len_op), + loc.clone(), + )?; + current_prev_op = Some(index_op); + Some(index_value) + } else { + None + }; let index = *offset as usize; // A projected unsized slice tail is represented as a fat @@ -618,30 +649,35 @@ pub fn translate_place_iterative( current_prev_op = Some(extract_op.get_operation()); } Ok(ConstIndexKind::Ptr { element_ty, ptr_ty }) => { - // Create constant index value - let i32_ty = IntegerType::get(ctx, 32, Signedness::Signless); - let apint = APInt::from_u32(index as u32, NonZeroUsize::new(32).unwrap()); - let index_attr = - pliron::builtin::attributes::IntegerAttr::new(i32_ty, apint); - use dialect_mir::ops::MirConstantOp; - let const_op = Operation::new( - ctx, - MirConstantOp::get_concrete_op_info(), - vec![i32_ty.into()], - vec![], - vec![], - 0, - ); - const_op.deref_mut(ctx).set_loc(loc.clone()); - let const_mir = MirConstantOp::new(const_op); - const_mir.set_attr_value(ctx, index_attr); - if let Some(prev) = current_prev_op { - const_mir.get_operation().insert_after(ctx, prev); + let index_value = if let Some(index_value) = runtime_index { + index_value } else { - const_mir.get_operation().insert_at_front(block_ptr, ctx); - } - current_prev_op = Some(const_mir.get_operation()); - let index_value = const_mir.get_operation().deref(ctx).get_result(0); + // Create constant index value. + let i32_ty = IntegerType::get(ctx, 32, Signedness::Signless); + let apint = + APInt::from_u32(index as u32, NonZeroUsize::new(32).unwrap()); + let index_attr = + pliron::builtin::attributes::IntegerAttr::new(i32_ty, apint); + use dialect_mir::ops::MirConstantOp; + let const_op = Operation::new( + ctx, + MirConstantOp::get_concrete_op_info(), + vec![i32_ty.into()], + vec![], + vec![], + 0, + ); + const_op.deref_mut(ctx).set_loc(loc.clone()); + let const_mir = MirConstantOp::new(const_op); + const_mir.set_attr_value(ctx, index_attr); + if let Some(prev) = current_prev_op { + const_mir.get_operation().insert_after(ctx, prev); + } else { + const_mir.get_operation().insert_at_front(block_ptr, ctx); + } + current_prev_op = Some(const_mir.get_operation()); + const_mir.get_operation().deref(ctx).get_result(0) + }; // Pointer offset let offset_op = Operation::new( diff --git a/cuda-oxide/crates/rustc-codegen-cuda/examples/subslice_projection/README.md b/cuda-oxide/crates/rustc-codegen-cuda/examples/subslice_projection/README.md index 476d1b693c..5436351e2b 100644 --- a/cuda-oxide/crates/rustc-codegen-cuda/examples/subslice_projection/README.md +++ b/cuda-oxide/crates/rustc-codegen-cuda/examples/subslice_projection/README.md @@ -7,11 +7,11 @@ Rustc emits `Subslice` for the `middle @ ..` portion of array and slice patterns - arrays use `Subslice { from, to, from_end: false }`; the result is the sized array place `[T; to - from]` starting at element `from`; - slices use `Subslice { from, to, from_end: true }`; the result keeps a data pointer advanced by `from` elements and metadata `old_len - from - to`. -Before this fix, the place walkers under `cuda-oxide/crates/mir-importer/src/translator/rvalue/` had no `Subslice` lowering. The value walker reported `Projection element ... not yet implemented in iterative mode`, while the address walker returned `Ok(None)`. Mutable borrows then failed loudly because falling back to a reference to a copy would lose write-through semantics. +`Subslice` lowering already supports sized arrays and slice fat pointers. This regression extends that support to `ConstantIndex { from_end: true }` immediately following a slice `Subslice`. The index must use the rebuilt subslice metadata: `subslice_len = old_len - from - to`, then `index = subslice_len - offset`. Coverage includes both value reads and address-based mutable writes, plus a field projection after the from-end index. ## Coverage -The example contains five cases: +The example contains seven cases: | Case | MIR property checked | |---|---| @@ -20,6 +20,8 @@ The example contains five cases: | `array mutable ref` | mutable reference writes through to original array storage | | `slice metadata` | slice data pointer advances and length becomes `old_len - from - to` | | `slice mutable ref` | mutable slice subslice writes through to original storage | +| `slice subslice from-end field` | rebuilt slice metadata drives a from-end index followed by struct field projections | +| `slice subslice from-end mutable` | two distinct from-end offsets write through the rebuilt subslice while unrelated elements remain unchanged | The helper functions are `#[inline(never)]` so optimized MIR retains the relevant projection in a separate body. @@ -28,6 +30,9 @@ Typical MIR shapes are expected to include: ```text Subslice { from: 1, to: 3, from_end: false } # [u32; 4] -> [u32; 2] Subslice { from: 1, to: 1, from_end: true } # [u32] -> [u32] +Subslice -> ConstantIndex { offset: 1, from_end: true } +Subslice -> ConstantIndex { offset: 2, from_end: true } +Subslice -> ConstantIndex { offset: 1, from_end: true } -> Field ``` ## Run diff --git a/cuda-oxide/crates/rustc-codegen-cuda/examples/subslice_projection/src/main.rs b/cuda-oxide/crates/rustc-codegen-cuda/examples/subslice_projection/src/main.rs index a156a8f26e..3a3103fd1f 100644 --- a/cuda-oxide/crates/rustc-codegen-cuda/examples/subslice_projection/src/main.rs +++ b/cuda-oxide/crates/rustc-codegen-cuda/examples/subslice_projection/src/main.rs @@ -26,6 +26,23 @@ use cuda_device::{DisjointSlice, kernel, thread}; use cuda_host::cuda_module; use std::sync::Arc; +#[derive(Copy, Clone)] +struct Pair { + x: u32, + y: u32, +} + +#[inline(never)] +fn middle_last_field(values: &[Pair]) -> u32 { + let [_, middle @ .., _] = values else { + return 0; + }; + let [.., last] = middle else { + return 0; + }; + last.x + last.y +} + #[inline(never)] fn array_middle_value(values: [u32; 4]) -> [u32; 2] { let [_, middle @ .., _] = values; @@ -53,10 +70,61 @@ fn bump_slice_middle(values: &mut [u32]) { } } +#[inline(never)] +fn bump_slice_middle_last(values: &mut [u32]) { + if let [_, middle @ .., _] = values { + if let [.., last] = middle { + *last += 11; + } + } +} + +#[inline(never)] +fn bump_slice_middle_second_last(values: &mut [u32]) { + if let [_, middle @ .., _] = values { + if let [.., second_last, _] = middle { + *second_last += 13; + } + } +} + #[cuda_module] mod kernels { use super::*; + #[kernel] + pub fn test_slice_subslice_from_end_field(input: &[[u32; 4]], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if i >= input.len() { + return; + } + + let row = input[i]; + let values = [ + Pair { + x: row[0], + y: row[0] + 100, + }, + Pair { + x: row[1], + y: row[1] + 100, + }, + Pair { + x: row[2], + y: row[2] + 100, + }, + Pair { + x: row[3], + y: row[3] + 100, + }, + ]; + + if let Some(slot) = out.get_mut(idx) { + *slot = middle_last_field(&values); + } + } + #[kernel] pub fn test_array_subslice_value(input: &[[u32; 4]], mut out: DisjointSlice) { let idx = thread::index_1d(); @@ -124,6 +192,34 @@ mod kernels { } } + #[kernel] + pub fn test_slice_subslice_from_end_mut(input: &[[u32; 4]], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if i >= input.len() { + return; + } + + let mut values = input[i]; + let before = values; + + // Exercise two distinct from-end ConstantIndex offsets on the rebuilt + // middle subslice: offset 1 selects original element 2, offset 2 selects + // original element 1. + bump_slice_middle_last(&mut values); + bump_slice_middle_second_last(&mut values); + + if let Some(slot) = out.get_mut(idx) { + let d0 = values[0] - before[0]; + let d1 = values[1] - before[1]; + let d2 = values[2] - before[2]; + let d3 = values[3] - before[3]; + + // Pack every element's delta so collateral writes also fail the test. + *slot = d0 | (d1 << 8) | (d2 << 16) | (d3 << 24); + } + } + #[kernel] pub fn test_slice_subslice_mut(input: &[[u32; 4]], mut out: DisjointSlice) { let idx = thread::index_1d(); @@ -190,6 +286,16 @@ fn main() { let mut all_pass = true; + all_pass &= run_and_report( + "slice subslice from-end field", + &stream, + |s, cfg, i, o| { + // SAFETY: launch shape/resources match the kernel; buffers cover its accesses. + unsafe { module.test_slice_subslice_from_end_field(s, cfg, i, o) }.expect("launch") + }, + |i| 160 + 2 * i as u32, + ); + all_pass &= run_and_report( "array value", &stream, @@ -240,6 +346,16 @@ fn main() { |_| 9, ); + all_pass &= run_and_report( + "slice subslice from-end mutable", + &stream, + |s, cfg, i, o| { + // SAFETY: launch shape/resources match the kernel; buffers cover its accesses. + unsafe { module.test_slice_subslice_from_end_mut(s, cfg, i, o) }.expect("launch") + }, + |_| (13 << 8) | (11 << 16), + ); + if all_pass { println!("\nSUCCESS: all Subslice projection cases passed"); } else {