Skip to content
Merged
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
2 changes: 2 additions & 0 deletions changelog.d/11671-array-region-facts-s3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- perf(codegen): array element reads with a static index range are region facts inside #11650 loop regions: one array guard in the preheader, a bare `base + 8*idx` read with an inline hole select, the base refreshed on the poll arm (array slice S3).
- perf(codegen): a loop region whose nested body region fails its guard leaves the region (G-body) instead of re-checking the array facts every iteration, and comes back when the body guard passes again (array slice S3).
9 changes: 9 additions & 0 deletions crates/perry-codegen/src/expr/index_get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use foreign_counter::{
affine_packed_loop_read, emit_affine_index_i64, foreign_packed_loop_read,
packed_f64_loop_offset_read,
};
pub(crate) use guarded_array::emit_array_region_guard;
mod inline_dyn_typed_array;

use guarded_array::{
Expand Down Expand Up @@ -716,6 +717,10 @@ pub(crate) fn lower_numeric_index_get_for_number_context(
let Expr::IndexGet { object, index } = expr else {
return Ok(None);
};
// A planned-bare region read lowers through the value path's hook.
if crate::stmt::region_loop::is_bare_index_get(ctx, expr) {
return Ok(None);
}
// Masked-window fast path first: the dense range guard proved the whole
// static index window at loop entry, so the read needs neither the static
// layout proof below nor a per-access guard. The fact can only exist for
Expand Down Expand Up @@ -1102,6 +1107,10 @@ fn lower_bounded_array_index_get_checked(
}

pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
// Step 4b / array S3: a planned-bare element read inside a loop region.
if let Some(v) = crate::stmt::region_loop::try_lower_bare_index_get(ctx, expr)? {
return Ok(v);
}
match expr {
Expr::IndexGet { object, index } => {
// #10509: must precede every receiver-proof tier below.
Expand Down
70 changes: 70 additions & 0 deletions crates/perry-codegen/src/expr/index_get/guarded_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,76 @@ fn emit_array_guard_word_ok(blk: &mut crate::block::LlBlock, word: &str) -> Stri
blk.icmp_eq(I32, &masked, ARRAY_READ_GUARD_EXPECT_I32)
}

/// A loop region's array guard (#11650 regions, array slice S3): the S1 guard
/// word, the prototype facts a hole read needs, and `max_index <u capacity`,
/// checked once in the preheader (and at a re-check). On a pass it also
/// derives the element base from the same header and stores it into
/// `base_slot`; F-body's element reads then load `base + 8 * idx` and select
/// `undefined` for a hole. Returns the `i1` pass flag. The receiver is tested
/// against the heap band before anything is dereferenced.
pub(crate) fn emit_array_region_guard(
ctx: &mut FnCtx<'_>,
recv_box: &str,
max_index: u32,
base_slot: &str,
) -> String {
let deref_idx = ctx.new_block("rloop.arr.deref");
let cap_idx = ctx.new_block("rloop.arr.cap");
let join_idx = ctx.new_block("rloop.arr.join");
let deref_label = ctx.block_label(deref_idx);
let cap_label = ctx.block_label(cap_idx);
let join_label = ctx.block_label(join_idx);
let pre_label = ctx.block().label.clone();
let band_offset = {
let blk = ctx.block();
let bits = blk.bitcast_double_to_i64(recv_box);
let band_offset = blk.sub(I64, &bits, HEAP_POINTER_BAND_BASE_I64);
let in_band = blk.icmp_ult(I64, &band_offset, HEAP_POINTER_BAND_SPAN_I64);
blk.cond_br(&in_band, &deref_label, &join_label);
band_offset
};
ctx.current_block = deref_idx;
let handle = {
let blk = ctx.block();
let handle = blk.add(I64, &band_offset, "1048576");
let word = emit_array_guard_word(blk, &handle);
let word_ok = emit_array_guard_word_ok(blk, &word);
blk.cond_br(&word_ok, &cap_label, &join_label);
handle
};
ctx.current_block = cap_idx;
let (pass, base) = {
let blk = ctx.block();
let reserved = emit_array_reserved(blk, &handle);
let proto_ok =
crate::expr::array_proto_guard::emit_array_default_prototype_chain(blk, &reserved);
let capacity_addr = blk.add(I64, &handle, "4");
let capacity_ptr = blk.inttoptr(I64, &capacity_addr);
let capacity = blk.load(I32, &capacity_ptr);
let fits = blk.icmp_ult(I32, &max_index.to_string(), &capacity);
let pass = blk.and(I1, &proto_ok, &fits);
let base = blk.array_elements_addr_with_capacity(&handle, &capacity);
blk.br(&join_label);
(pass, base)
};
ctx.current_block = join_idx;
let blk = ctx.block();
let pass = blk.phi(
I1,
&[
("false", &pre_label),
("false", &deref_label),
(&pass, &cap_label),
],
);
let base = blk.phi(
I64,
&[("0", &pre_label), ("0", &deref_label), (&base, &cap_label)],
);
blk.store(I64, &base, base_slot);
pass
}

pub(super) fn lower_guarded_array_index_get(
ctx: &mut FnCtx<'_>,
arr_box: &str,
Expand Down
3 changes: 2 additions & 1 deletion crates/perry-codegen/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3077,7 +3077,8 @@ mod unary_bigint_tests;
mod unary_bitnot_tests;
pub(crate) use index_get::{
affine_counter_occurrences, affine_index_fits_i64, emit_affine_index_i64_with,
numeric_index_has_integer_array_index_proof, packed_f64_loop_index_parts,
emit_array_region_guard, numeric_index_has_integer_array_index_proof,
packed_f64_loop_index_parts,
};
pub(crate) use masked_window::masked_window_fact_for_index;
/// Rooting coverage for the computed-store arms the TS corpora cannot reach
Expand Down
15 changes: 11 additions & 4 deletions crates/perry-codegen/src/stmt/let_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,17 @@ pub(crate) fn lower_let(
// reused-slot write below (plain, unchecked) overwrites any TAG_TDZ
// sentinel with the real value.
ctx.tdz_boxes.remove(&id);
crate::expr::lower_expr(
ctx,
&perry_hir::Expr::LocalSet(id, Box::new(init_expr.clone())),
)?;
let set = perry_hir::Expr::LocalSet(id, Box::new(init_expr.clone()));
// A region's plan names the original initializer (step 4b).
let aliased = match &set {
perry_hir::Expr::LocalSet(_, v) => {
crate::stmt::region_loop::alias_clone(ctx, init_expr, v)
}
_ => Vec::new(),
};
let lowered = crate::expr::lower_expr(ctx, &set);
crate::stmt::region_loop::unalias_clone(ctx, aliased);
lowered?;
// #10488: a hoisted `var`'s real declaration reaches this branch
// (#1803 predefine-then-declare) and returns before the
// fresh-declaration path's `insert` runs, desyncing
Expand Down
28 changes: 19 additions & 9 deletions crates/perry-codegen/src/stmt/loops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3888,14 +3888,21 @@ fn lower_packed_f64_range_versioned_for(
}

ctx.current_block = slow_pre_idx;
lower_for_after_init(
ctx,
init,
condition,
update,
body,
"for.packed_f64_range_slow",
)?;
// The arrays this tier refused (not packed numbers) may still be region
// arrays (S3): their element reads are then bare in the split loop.
let region = super::region_loop::begin_for_arrays(ctx, condition, body, update)?;
let lowered = super::region_loop::lower_loop(ctx, region, &mut |ctx| {
lower_for_after_init(
ctx,
init,
condition,
update,
body,
"for.packed_f64_range_slow",
)
});
super::region_loop::end(ctx, region);
lowered?;
if !ctx.block().is_terminated() {
ctx.block().br(&merge_label);
}
Expand Down Expand Up @@ -8181,8 +8188,11 @@ fn emit_armed_gc_loop_safepoint(ctx: &mut FnCtx<'_>) {
let handle = blk.and(I64, &bits, crate::nanbox::POINTER_MASK_I64);
blk.store(I64, &handle, &recipe.base_handle_slot);
}
blk.br(&done_label);
}
// Loop regions' array bases (S3), from the same GC-updated roots.
crate::stmt::region_loop::emit_poll_refresh(ctx)
.expect("a region array binding lowers as a plain load");
ctx.block().br(&done_label);
ctx.current_block = done_idx;
}

Expand Down
Loading
Loading