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
14 changes: 14 additions & 0 deletions changelog.d/8613-guarded-array-i32.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
### Performance

- Propagate runtime-backed numeric-array parameter guards into native
representation selection, allowing Feistel-style accumulators seeded from a
guarded `number[]` to stay in canonical i32 slots after a dominating bitwise
normalization. Nested bitwise expressions now also consume mutable
Number-by-construction locals and bounds-proven typed-array reads without
falling back through `js_number_coerce`.

- On the exact `typed_array` workload from #8606, retired instructions fall
from a 14.68B three-run median to 9.66B (-34.2%), cycles from 8.10B to 5.96B
(-26.5%), and user CPU from 2.73s to 2.01s (-26.4%), with the checksum
unchanged. The specialized `encipherUntyped` function now contains zero
`js_number_coerce` calls.
12 changes: 12 additions & 0 deletions crates/perry-codegen/src/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,17 @@ pub(super) fn compile_function(
.then_some(*id)
})
.collect();
let spec_number_array_params: HashSet<u32> = spec_param_proofs
.iter()
.filter_map(|(id, ty)| {
matches!(ty, perry_hir::types::Type::Array(element)
if matches!(element.as_ref(), perry_hir::types::Type::Number | perry_hir::types::Type::Int32))
.then_some(*id)
})
.collect();
// Unlike source annotations, these proofs are backed by the public
// wrapper's `js_param_type_guard`. The guarded-array seed collector may
// therefore trust that an element is Number-or-undefined in this clone.
// `--opt-report` (#6952): attribute every representation decision the
// collectors below make to this function. No-op when the report is off.
//
Expand Down Expand Up @@ -865,6 +876,7 @@ pub(super) fn compile_function(
&spec_ta_lens,
&spec_i32_params,
&spec_numeric_params,
&spec_number_array_params,
);

// A Number-by-construction local cannot ever hold a GC pointer, so it
Expand Down
45 changes: 37 additions & 8 deletions crates/perry-codegen/src/codegen/ordinary_param_guard_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,6 @@ fn mixed_ta_clone_guards_numeric_array_shape_at_the_direct_call() {
object: Box::new(Expr::LocalGet(10)),
index: Box::new(Expr::Integer(0)),
};
let xor_loaded = || Expr::Binary {
op: BinaryOp::BitXor,
left: Box::new(Expr::LocalGet(12)),
right: Box::new(Expr::Integer(1)),
};
let encipher = Function {
id: 1,
name: "encipher".to_string(),
Expand All @@ -171,7 +166,7 @@ fn mixed_ta_clone_guards_numeric_array_shape_at_the_direct_call() {
id: 12,
name: "value".to_string(),
ty: Type::Any,
mutable: false,
mutable: true,
init: Some(loaded),
},
Stmt::While {
Expand All @@ -181,12 +176,42 @@ fn mixed_ta_clone_guards_numeric_array_shape_at_the_direct_call() {
index: Box::new(Expr::Integer(0)),
})],
},
Stmt::Let {
id: 13,
name: "accumulator".to_string(),
ty: Type::Number,
mutable: true,
init: Some(Expr::Integer(3)),
},
Stmt::Expr(Expr::LocalSet(
13,
Box::new(Expr::Binary {
op: BinaryOp::Add,
left: Box::new(Expr::LocalGet(13)),
right: Box::new(Expr::Integer(1)),
}),
)),
Stmt::Expr(Expr::LocalSet(
12,
Box::new(Expr::Binary {
op: BinaryOp::BitXor,
left: Box::new(Expr::LocalGet(12)),
right: Box::new(Expr::Binary {
op: BinaryOp::BitXor,
left: Box::new(Expr::LocalGet(13)),
right: Box::new(Expr::IndexGet {
object: Box::new(Expr::LocalGet(11)),
index: Box::new(Expr::Integer(0)),
}),
}),
}),
)),
Stmt::Expr(Expr::IndexSet {
object: Box::new(Expr::LocalGet(10)),
index: Box::new(Expr::Integer(0)),
value: Box::new(xor_loaded()),
value: Box::new(Expr::LocalGet(12)),
}),
Stmt::Return(Some(xor_loaded())),
Stmt::Return(Some(Expr::LocalGet(12))),
],
is_async: false,
is_generator: false,
Expand Down Expand Up @@ -243,6 +268,10 @@ fn mixed_ta_clone_guards_numeric_array_shape_at_the_direct_call() {
assert!(init.contains("encipher$spec_b_ta4x4("), "{init}");
assert!(init.contains("@perry_fn_number_array_guard_ts__encipher("));
assert!(!specialized.contains("js_dynamic_bitxor"));
assert!(
!specialized.contains("js_number_coerce"),
"the guarded element must enter a canonical i32 slot before the hot bitwise update:\n{specialized}"
);
assert!(generic.contains("js_dynamic_bitxor"));
}

Expand Down
13 changes: 10 additions & 3 deletions crates/perry-codegen/src/collectors/hir_facts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ pub(crate) fn collect_type_facts(
spec_ta_lens: &HashMap<u32, i64>,
spec_i32_params: &HashSet<u32>,
spec_numeric_params: &HashSet<u32>,
spec_number_array_params: &HashSet<u32>,
) -> TypeFacts {
// #7700: which locals hold a NUMBER, so a `u8[k]` keyed on one is a byte
// read rather than a property read. Computed once here because
Expand All @@ -464,9 +465,10 @@ pub(crate) fn collect_type_facts(
spec_i32_params,
);
// Native-i32 residency for integer-valued locals whose init/writes include a
// possibly-out-of-bounds INT typed-array element read (bcryptjs `_encipher`
// Feistel accumulators `l`/`r`). Sound only under a whole-function
// observation constraint — see `int_valued_ta_locals`. Gated by
// possibly-out-of-bounds INT typed-array element read or a numeric-array
// read backed by a specialized entry guard (bcryptjs `_encipher` Feistel
// accumulators `l`/`r`). Sound only under a whole-function observation
// constraint — see `int_valued_ta_locals`. Gated by
// `PERRY_INT_VALUED_LOCALS` (keyed into the object cache). Boxed / module-
// global locals are excluded (they never take the i32 shadow slot and would
// only pollute the fact for other consumers).
Expand All @@ -477,6 +479,7 @@ pub(crate) fn collect_type_facts(
params,
binding_types,
spec_ta_lens,
spec_number_array_params,
);
// `--opt-report` (#6952) / promotion census (#7106): the win column
// for this analysis, recorded at the ONE site where a candidate
Expand Down Expand Up @@ -758,6 +761,7 @@ pub(crate) fn collect_native_region_fact_graph(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
}

Expand All @@ -780,6 +784,7 @@ pub(crate) fn collect_native_region_fact_graph_with_spec_params(
spec_ta_lens: &HashMap<u32, i64>,
spec_i32_params: &HashSet<u32>,
spec_numeric_params: &HashSet<u32>,
spec_number_array_params: &HashSet<u32>,
) -> NativeRegionFactGraph {
collect_type_facts(
stmts,
Expand All @@ -796,6 +801,7 @@ pub(crate) fn collect_native_region_fact_graph_with_spec_params(
spec_ta_lens,
spec_i32_params,
spec_numeric_params,
spec_number_array_params,
)
}

Expand Down Expand Up @@ -824,6 +830,7 @@ pub(crate) fn collect_hir_facts(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
}

Expand Down
Loading
Loading