Skip to content
Merged
6 changes: 4 additions & 2 deletions benchmarks/compiler_output/workloads.toml
Original file line number Diff line number Diff line change
Expand Up @@ -625,8 +625,10 @@ detail = "numeric Array.push uses the guarded raw-f64 helper"

[[workloads.numeric_arrays.ir_checks]]
name = "numeric_array_uses_unboxed_get"
contains = "js_array_numeric_get_f64_unboxed"
detail = "numeric indexed read uses the guarded raw-f64 helper"
contains = "js_typed_feedback_numeric_array_index_get_guard"
regex = '''bidx\.num\.fast\.\d+:[\s\S]*?inttoptr i64 %\w+ to ptr\s*\n\s*%\w+ = load double, ptr %\w+[^\n]*\n\s*br label %bidx\.num\.merge'''
regex_none = ["call double @js_array_numeric_get_f64_unboxed"]
detail = "numeric indexed read takes the guarded raw-f64 fast path and loads the slot inline (inttoptr + load double in bidx.num.fast; helper call elided)"

[[workloads.numeric_arrays.ir_checks]]
name = "numeric_array_uses_unboxed_set"
Expand Down
19 changes: 14 additions & 5 deletions crates/perry-codegen/src/expr/index_get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,11 +290,20 @@ fn lower_guarded_array_index_get(
let arr_bits = fast_blk.bitcast_double_to_i64(arr_box);
let arr_handle = fast_blk.and(I64, &arr_bits, POINTER_MASK_I64);
let fast_val = if require_numeric_layout {
fast_blk.call(
DOUBLE,
"js_array_numeric_get_f64_unboxed",
&[(I64, &arr_handle), (I32, idx_i32)],
)
// The `numeric_array_index_get_guard` on the way into this block already
// proved: a plain, non-forwarded `Array`, in raw-f64 numeric layout,
// with `index` in bounds (`plain_array_index_guard(.., in_bounds=true)`
// && `js_array_is_numeric_f64_layout`). So load the slot inline instead
// of calling `js_array_numeric_get_f64_unboxed`, whose hot path
// re-validates exactly those same conditions and then does this load.
// Raw-f64 arrays are dense (no HOLE slots) and the slot holds a raw f64,
// matching the runtime helper's `return *elements_ptr.add(index)`.
let idx_i64 = fast_blk.zext(I32, idx_i32, I64);
let byte_offset = fast_blk.shl(I64, &idx_i64, "3");
let with_header = fast_blk.add(I64, &byte_offset, "8");
let element_addr = fast_blk.add(I64, &arr_handle, &with_header);
let element_ptr = fast_blk.inttoptr(I64, &element_addr);
fast_blk.load(DOUBLE, &element_ptr)
} else {
let idx_i64 = fast_blk.zext(I32, idx_i32, I64);
let byte_offset = fast_blk.shl(I64, &idx_i64, "3");
Expand Down
6 changes: 5 additions & 1 deletion crates/perry-codegen/tests/typed_feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,5 +535,9 @@ fn typed_feedback_guards_computed_numeric_array_index_hot_path() {

assert!(ir.contains("call i32 @js_typed_feedback_numeric_array_index_get_guard"));
assert!(ir.contains("call double @js_typed_feedback_array_index_get_fallback_boxed"));
assert!(ir.contains("call double @js_array_numeric_get_f64_unboxed"));
// The numeric fast path no longer calls `js_array_numeric_get_f64_unboxed`:
// the guard already proved raw-f64 layout + in-bounds, so the slot is loaded
// inline (a direct `load double` from the element address).
assert!(!ir.contains("call double @js_array_numeric_get_f64_unboxed"));
assert!(ir.contains("load double"));
}
19 changes: 18 additions & 1 deletion tests/test_compiler_output_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,11 +928,28 @@ def test_native_rep_unchecked_unknown_bounds_fails_gate(self):
)

def test_generic_native_rep_checks_require_configured_records(self):
# The numeric indexed read is inlined: a guarded fast block computes the
# element pointer (inttoptr) and performs a direct `load double` instead
# of calling js_array_numeric_get_f64_unboxed. Push/set still go through
# their guarded raw-f64 helpers.
ir = """
define i32 @main() {
entry:
call i64 @js_array_numeric_push_f64_unboxed(i64 1, double 2.0)
call double @js_array_numeric_get_f64_unboxed(i64 1, i32 0)
%g = call i32 @js_typed_feedback_numeric_array_index_get_guard(i64 1, double 0.0, double 0.0, i32 0, i32 1)
%gc = icmp ne i32 %g, 0
br i1 %gc, label %bidx.num.fast.1, label %bidx.num.fallback.2

bidx.num.fast.1:
%addr = add i64 1, 8
%p = inttoptr i64 %addr to ptr
%v = load double, ptr %p, align 8
br label %bidx.num.merge.3

bidx.num.fallback.2:
br label %bidx.num.merge.3

bidx.num.merge.3:
call i32 @js_array_numeric_set_f64_unboxed(i64 1, i32 0, double 3.0)
ret i32 0
}
Expand Down