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
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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::<dialect_mir::types::MirSliceType>()
{
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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|---|---|
Expand All @@ -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.

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<u32>) {
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<u32>) {
let idx = thread::index_1d();
Expand Down Expand Up @@ -124,6 +192,34 @@ mod kernels {
}
}

#[kernel]
pub fn test_slice_subslice_from_end_mut(input: &[[u32; 4]], mut out: DisjointSlice<u32>) {
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<u32>) {
let idx = thread::index_1d();
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down