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
19 changes: 16 additions & 3 deletions crates/perry-codegen/src/expr/property_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,10 +436,23 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {

ctx.current_block = fallback_idx;
let blk = ctx.block();
blk.call_void("js_typed_feedback_record_fallback_call", &[(I64, &site_id)]);
// #5334 lever A: the guard already ran and FAILED in the
// entry block, so this cold arm is a pure guard-miss
// fallback. Outline the two operations it used to emit
// inline (record_fallback + by-name set) into ONE
// `js_class_field_set_fallback` call. Semantics are
// byte-identical; only the emitted IR shrinks (cold path
// → zero hot-loop cost). `obj_bits` keeps the full
// NaN-box tag; `key_raw` is POINTER_MASK-stripped — the
// same operands the two calls received.
blk.call_void(
"js_object_set_field_by_name",
&[(I64, &obj_bits), (I64, &key_raw), (DOUBLE, &val_double)],
"js_class_field_set_fallback",
&[
(I64, &site_id),
(I64, &obj_bits),
(I64, &key_raw),
(DOUBLE, &val_double),
],
);
blk.br(&merge_label);
if requires_raw_f64 {
Expand Down
8 changes: 8 additions & 0 deletions crates/perry-codegen/src/runtime_decls/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ pub fn declare_phase_b_objects(module: &mut LlModule) {
I32,
&[I64, DOUBLE, I32, I64, I64, I32, DOUBLE, I32],
);
// #5334 lever A: class-field-SET guard-MISS fallback, outlined. The cold arm
// of the default diamond collapses from two calls (record_fallback +
// set_field_by_name) to this one. Args: (site_id, obj_bits, key_raw, value).
module.declare_function(
"js_class_field_set_fallback",
VOID,
&[I64, I64, I64, DOUBLE],
);
module.declare_function(
"js_typed_feedback_class_field_get_guard",
I32,
Expand Down
15 changes: 13 additions & 2 deletions crates/perry-codegen/tests/typed_feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,14 @@ fn typed_feedback_guards_direct_class_field_specialization() {
assert!(ir.contains("class_field_get.fallback"));
assert!(ir.contains("store double"));
assert!(!ir.contains("call void @js_gc_note_slot_layout"));
// #5334 lever A: the SET fallback arm collapses to one outlined call; the
// by-name SET it replaced is no longer emitted at the set site.
assert!(ir.contains("call void @js_class_field_set_fallback"));
assert!(!ir.contains("call void @js_object_set_field_by_name"));
// `record_fallback_call` is still present — but from the class-field-GET
// fallback block below, not the SET site (the SET copy is now folded into
// js_class_field_set_fallback).
assert!(ir.contains("call void @js_typed_feedback_record_fallback_call"));
assert!(ir.contains("call void @js_object_set_field_by_name"));
assert!(ir.contains("call double @js_object_get_field_by_name_f64"));
}

Expand Down Expand Up @@ -340,7 +346,12 @@ fn typed_feedback_guards_direct_class_method_specialization() {
assert!(ir.contains("js_typed_feedback_method_direct_call_guard"));
assert!(ir.contains("method_direct.fast"));
assert!(ir.contains("method_direct.fallback"));
assert!(ir.contains("call void @js_typed_feedback_record_fallback_call"));
// #5334 lever A: this class has a field `x` whose synthesized field-set
// routes its guard-miss arm through the outlined fallback. (The
// method-direct fallback only records when its site_id is Some, which it
// isn't here — the old `record_fallback_call` assertion was incidentally
// satisfied by the field-set fallback that is now folded into this call.)
assert!(ir.contains("call void @js_class_field_set_fallback"));
assert!(ir.contains("call double @js_native_call_method"));
}

Expand Down
37 changes: 37 additions & 0 deletions crates/perry-runtime/src/typed_feedback/guards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,42 @@ pub extern "C" fn js_typed_feedback_class_field_set_guard(
}
}

/// Class-field-SET guard-MISS fallback, outlined (#5334, lever A).
///
/// The default class-field-set diamond runs the inline
/// `js_typed_feedback_class_field_set_guard` in its entry block; on a guard
/// PASS it stores the slot inline, on a MISS it branches to the fallback arm.
/// That arm used to emit TWO inline calls per set site —
/// `js_typed_feedback_record_fallback_call` then `js_object_set_field_by_name`.
/// Since the guard has already run and FAILED (that failure is what branched
/// control here), nothing is left to decide: this helper just reproduces those
/// two operations, collapsed into ONE call so the cold arm costs a single
/// instruction per site instead of two.
///
/// Byte-identical semantics to the old inline pair:
/// 1. record the miss for typed feedback, then
/// 2. route the write by name (handles frozen / accessor / non-writable /
/// setter-in-chain). `obj_bits` keeps the full NaN-box tag (the by-name
/// setter inspects it for proxy/exotic dispatch before masking to the
/// heap address); `key_raw` is the POINTER_MASK-stripped key handle.
///
/// Cold-path only, so the extra call frame has zero hot-loop cost; the win is
/// purely in emitted IR size.
#[no_mangle]
pub extern "C" fn js_class_field_set_fallback(
site_id: u64,
obj_bits: u64,
key_raw: u64,
value: f64,
) {
crate::typed_feedback::js_typed_feedback_record_fallback_call(site_id);
crate::object::js_object_set_field_by_name(
obj_bits as *mut ObjectHeader,
key_raw as *const crate::StringHeader,
value,
);
}

#[no_mangle]
pub unsafe extern "C" fn js_typed_feedback_native_call_method(
site_id: u64,
Expand Down Expand Up @@ -835,6 +871,7 @@ mod keep_guard_symbols {
use super::*;
#[used] static G0: extern "C" fn(u64, f64, u32, *const ArrayHeader, *const crate::StringHeader, u32, i32) -> i32 = js_typed_feedback_class_field_get_guard;
#[used] static G1: extern "C" fn(u64, f64, u32, *const ArrayHeader, *const crate::StringHeader, u32, f64, i32) -> i32 = js_typed_feedback_class_field_set_guard;
#[used] static G1C: extern "C" fn(u64, u64, u64, f64) = js_class_field_set_fallback;
#[used] static G2: unsafe extern "C" fn(u64, f64, u32, *const ArrayHeader, *const i8, usize, *const u8) -> i32 = js_typed_feedback_method_direct_call_guard;
#[used] static G3: extern "C" fn(u64, f64, *const u8, u32, u32) -> i32 = js_typed_feedback_closure_direct_call_guard;
}
Loading