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
1 change: 1 addition & 0 deletions changelog.d/9091-unboxed-clone-accumulators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reduce accumulators in packed fast clones now live in register-promotable unboxed F64 slots for the clone's duration (write-back at every exit) — `s += a[i]` reduce loops reach node parity or better (literal-bound 0.98 ns/element vs Node's 1.01).
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,7 @@ pub(super) fn compile_closure(
class_field_loop_facts: Vec::new(),
element_shape_loop_facts: Vec::new(),
i32_counter_slots: HashMap::new(),
numeric_accumulator_f64_slots: HashMap::new(),
local_slot_reps: HashMap::new(),
repsel_context_allows_canonical_i32: repsel_allows,
// #7109 split the FIELD out of `repsel_context_allows_canonical_i32`;
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/codegen/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,7 @@ pub(super) fn compile_module_entry(
class_field_loop_facts: Vec::new(),
element_shape_loop_facts: Vec::new(),
i32_counter_slots: HashMap::new(),
numeric_accumulator_f64_slots: HashMap::new(),
local_slot_reps: HashMap::new(),
// #7109: this entry body selects canonical i32/u32/Str on the same
// per-value rules as a function body. Phase 1 (#6903) excluded it
Expand Down Expand Up @@ -1595,6 +1596,7 @@ pub(super) fn compile_module_entry(
class_field_loop_facts: Vec::new(),
element_shape_loop_facts: Vec::new(),
i32_counter_slots: HashMap::new(),
numeric_accumulator_f64_slots: HashMap::new(),
local_slot_reps: HashMap::new(),
// #7109: this entry body selects canonical i32/u32/Str on the same
// per-value rules as a function body. Phase 1 (#6903) excluded it
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,7 @@ pub(super) fn compile_function(
.map(|id| (*id, crate::expr::SlotRep::I32))
.collect(),
i32_counter_slots: spec_i32_param_slots,
numeric_accumulator_f64_slots: HashMap::new(),
repsel_context_allows_canonical_i32: repsel_allows,
// #7109 split the FIELD out of `repsel_context_allows_canonical_i32`;
// #7128 split the VALUE, which is what the knob actually reads. Until
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/codegen/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ pub(super) fn compile_method(
class_field_loop_facts: Vec::new(),
element_shape_loop_facts: Vec::new(),
i32_counter_slots: index_i32_param_slots,
numeric_accumulator_f64_slots: HashMap::new(),
local_slot_reps: HashMap::new(),
repsel_context_allows_canonical_i32: repsel_allows,
// #7109 split the FIELD out of `repsel_context_allows_canonical_i32`;
Expand Down Expand Up @@ -1714,6 +1715,7 @@ pub(super) fn compile_static_method(
class_field_loop_facts: Vec::new(),
element_shape_loop_facts: Vec::new(),
i32_counter_slots: HashMap::new(),
numeric_accumulator_f64_slots: HashMap::new(),
local_slot_reps: HashMap::new(),
repsel_context_allows_canonical_i32: repsel_allows,
// #7109 split the FIELD out of `repsel_context_allows_canonical_i32`;
Expand Down
17 changes: 17 additions & 0 deletions crates/perry-codegen/src/expr/literals_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,12 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
// double round-trip. The double slot is still maintained (for
// closures or escape sites) but mem2reg + DSE will eliminate
// it when the i32 path covers every read.
// Unboxed accumulator redirect (packed fast clones): the
// live value sits in a plain F64 alloca; its bits are the
// nanbox, so the load IS the value.
if let Some(f64_slot) = ctx.numeric_accumulator_f64_slots.get(id).cloned() {
return Ok(ctx.block().load(DOUBLE, &f64_slot));
}
if let Some(i32_slot) = ctx.i32_counter_slots.get(id).cloned() {
let i = ctx.block().load(I32, &i32_slot);
let v = if ctx.unsigned_i32_locals.contains(id) {
Expand Down Expand Up @@ -579,6 +585,17 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
Expr::LocalSet(id, value) => {
super::invalidate_local_write_facts(ctx, *id);
super::record_local_value_alias_for_write(ctx, *id, value.as_ref());
// Unboxed accumulator redirect (packed fast clones): the matcher
// proved every in-clone write numeric-preserving, so the store is
// a bare F64 alloca store — no shadow bookkeeping (the real slot
// holds a stale NUMBER for the clone's duration, consistent with
// whatever shadow state preceded the loop), no barrier (numbers
// carry no heap edge). Exits write the value back.
if let Some(f64_slot) = ctx.numeric_accumulator_f64_slots.get(id).cloned() {
let v = lower_expr(ctx, value)?;
ctx.block().store(DOUBLE, &v, &f64_slot);
return Ok(v);
}
if let Some(v) = lower_pod_local_reassignment(ctx, *id, value)? {
super::record_native_arena_owner_assignment(ctx, *id, value.as_ref());
return Ok(v);
Expand Down
11 changes: 11 additions & 0 deletions crates/perry-codegen/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,17 @@ pub(crate) struct FnCtx<'a> {
/// on hot array-walking loops like `for (let i = 0; i < arr.length;
/// i++) arr[i] = expr`.
pub i32_counter_slots: std::collections::HashMap<u32, String>,
/// Unboxed reduce-accumulator redirect, active only while a packed fast
/// clone is being lowered: local id -> plain (addrspace-0) F64 alloca.
/// The clone's preheader tag-tested the local as a Number and moved its
/// value here; every in-clone read/write of the local goes through this
/// alloca (mem2reg promotes it to a register — the GC-root slot's
/// store-to-load-forward chain was the reduce rows' latency floor), and
/// every clone exit (fall-through and side-exit trampoline) writes the
/// value back to the real slot. A genuine double's bits ARE its nanbox,
/// so no conversion exists on either edge; the stale number left in the
/// root slot during the clone is harmless to a GC scan.
pub numeric_accumulator_f64_slots: std::collections::HashMap<u32, String>,

/// Representation-selection Phase 1 (RFC `docs/representation-selection-
/// rfc.md`): LocalId → selected slot representation. Absent = `Boxed`
Expand Down
Loading
Loading