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
32 changes: 32 additions & 0 deletions crates/perry-codegen/src/expr/property_get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1656,6 +1656,38 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
.as_ref()
.is_some_and(crate::typed_shape::type_is_raw_f64_candidate);
let requires_raw_f64_str = if requires_raw_f64 { "1" } else { "0" };
// #5391 path 2: oversized modules full-outline the entire
// class-field-GET diamond (guard + fast load + fallback +
// phi) to a single `js_class_field_get_ic(...)` call that
// returns the field value. This shrinks large minified
// user functions enough for clang -O0 to compile them
// (the per-function compile time is superlinear in size).
// Mirrors the field-SET full-outline (#5334 lever B).
if crate::codegen::full_outline_ic_enabled() {
let (key_raw, expected_keys) = {
let blk = ctx.block();
let key_box = blk.load(DOUBLE, &key_handle_global);
let key_bits = blk.bitcast_double_to_i64(&key_box);
let key_raw = blk.and(I64, &key_bits, POINTER_MASK_I64);
let expected_keys =
blk.load(I64, &format!("@{}", keys_global_name));
(key_raw, expected_keys)
};
let val = ctx.block().call(
DOUBLE,
"js_class_field_get_ic",
&[
(I64, &site_id),
(DOUBLE, &recv_box),
(I32, &expected_class_id_str),
(I64, &expected_keys),
(I64, &key_raw),
(I32, &field_idx_str),
(I32, requires_raw_f64_str),
],
);
return Ok(val);
}
// #5093: build the guard operands once, up front, so both
// the inline shape pre-check and the guard-call fallback
// can reference them.
Expand Down
9 changes: 9 additions & 0 deletions crates/perry-codegen/src/runtime_decls/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ pub fn declare_phase_b_objects(module: &mut LlModule) {
I32,
&[I64, DOUBLE, I32, I64, I64, I32, I32],
);
// #5391 path 2: class-field-GET inline cache, FULLY outlined. For oversized
// modules the whole get diamond collapses to one call returning the field
// value. Args: (site_id, recv, expected_class_id, expected_keys, key,
// field_index, require_raw_f64). Same signature as the get guard (+ f64 ret).
module.declare_function(
"js_class_field_get_ic",
DOUBLE,
&[I64, DOUBLE, I32, I64, I64, I32, I32],
);
module.declare_function(
"js_typed_feedback_native_call_method",
DOUBLE,
Expand Down
40 changes: 40 additions & 0 deletions crates/perry-codegen/tests/typed_feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,46 @@ fn full_outline_ic_collapses_class_field_set_to_single_call() {
}
}

#[test]
fn full_outline_ic_collapses_class_field_get_to_single_call() {
// #5391 path 2: full-outline collapses the class-field-GET diamond to a
// single `js_class_field_get_ic` call returning the field value.
let build = || {
let point = class(101, "Point", vec![field("x", Type::Number)]);
module_with_classes(
"full_outline_get.ts",
vec![point],
vec![param(1, "p", Type::Named("Point".to_string()))],
Type::Number,
vec![Stmt::Return(Some(Expr::PropertyGet {
object: Box::new(Expr::LocalGet(1)),
property: "x".to_string(),
}))],
)
};

let _lock = ENV_LOCK.lock().unwrap();

// Forced ON: one outlined call, no inline get diamond.
{
let _g = EnvVarGuard::set("PERRY_FULL_OUTLINE_IC", Some("1"));
let ir = ir_for(build());
assert!(ir.contains("call double @js_class_field_get_ic"));
assert!(!ir.contains("class_field_get.fast"));
assert!(!ir.contains("class_field_get.fallback"));
assert!(!ir.contains("call i32 @js_typed_feedback_class_field_get_guard"));
}

// Forced OFF: the inline diamond, no full-outline call.
{
let _g = EnvVarGuard::set("PERRY_FULL_OUTLINE_IC", Some("0"));
let ir = ir_for(build());
assert!(!ir.contains("call double @js_class_field_get_ic"));
assert!(ir.contains("class_field_get.fast"));
assert!(ir.contains("js_typed_feedback_class_field_get_guard"));
}
}

#[test]
fn full_outline_ic_auto_gate_counts_class_methods() {
// #5334 lever B: the auto size-gate counts class CALLABLES (methods,
Expand Down
54 changes: 54 additions & 0 deletions crates/perry-runtime/src/typed_feedback/guards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,59 @@ pub extern "C" fn js_class_field_set_ic(
js_class_field_set_fallback(site_id, obj_bits, key_raw, value);
}

/// Class-field-GET inline cache, FULLY OUTLINED (#5391 path 2 — extends the
/// #5334 lever-B full-outline from field-SET to field-GET). For oversized
/// modules the entire `class_field_get` diamond (inline precheck + guard call +
/// fast slot load + by-name fallback + phi) collapses to this one call,
/// shrinking the large minified user functions enough for `clang -O0` to
/// compile them in practical time.
///
/// Reproduces the diamond's semantics: run the same
/// `js_typed_feedback_class_field_get_guard`; on a PASS read the field slot as
/// `f64` (a plain number is self-boxing in nan-boxing, so raw-f64 and boxed
/// slots read identically — matching the inline `class_field_get.fast` plain
/// `load double`); on a FAIL record the fallback and read by name. The full
/// outline drops the inline path's static raw-number type hint (the result is
/// treated as a general JS value), which is value-correct — acceptable on the
/// size-gated full-outline path.
#[no_mangle]
pub extern "C" fn js_class_field_get_ic(
site_id: u64,
receiver: f64,
expected_class_id: u32,
expected_keys: *const ArrayHeader,
key: *const crate::StringHeader,
expected_field_index: u32,
require_raw_f64: i32,
) -> f64 {
let guard_ok = js_typed_feedback_class_field_get_guard(
site_id,
receiver,
expected_class_id,
expected_keys,
key,
expected_field_index,
require_raw_f64,
);

if guard_ok != 0 {
let object_addr = normalize_raw_object_addr(receiver.to_bits());
unsafe {
let fields_ptr =
(object_addr as *const u8).add(std::mem::size_of::<ObjectHeader>()) as *const f64;
return std::ptr::read(fields_ptr.add(expected_field_index as usize));
}
}

crate::typed_feedback::js_typed_feedback_record_fallback_call(site_id);
let obj_bits = receiver.to_bits();
let key_raw = key as u64 & crate::value::POINTER_MASK;
crate::object::js_object_get_field_by_name_f64(
obj_bits as *const ObjectHeader,
key_raw as *const crate::StringHeader,
)
}

#[no_mangle]
pub unsafe extern "C" fn js_typed_feedback_native_call_method(
site_id: u64,
Expand Down Expand Up @@ -951,6 +1004,7 @@ mod keep_guard_symbols {
#[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 G1D: extern "C" fn(u64, f64, u32, *const ArrayHeader, *const crate::StringHeader, u32, f64, i32) = js_class_field_set_ic;
#[used] static G1E: extern "C" fn(u64, f64, u32, *const ArrayHeader, *const crate::StringHeader, u32, i32) -> f64 = js_class_field_get_ic;
#[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