-
-
Notifications
You must be signed in to change notification settings - Fork 132
perf(gc): O(1) class-field raw-f64 layout — header bit + inline guard hoist (#5094) #5240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ use crate::lower_string_method::{ | |
| lower_string_concat_chain, lower_string_self_append, | ||
| }; | ||
| #[allow(unused_imports)] | ||
| use super::property_get::emit_inline_class_field_guard; | ||
| use crate::nanbox::{double_literal, POINTER_MASK_I64}; | ||
| use crate::native_value::{ | ||
| BoundsState, BufferAccessMode, LoweredValue, MaterializationReason, NativeRep, SemanticKind, | ||
|
|
@@ -310,35 +311,55 @@ 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" }; | ||
| let (key_raw, guard_ok) = { | ||
| let key_raw = { | ||
| 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)); | ||
| let guard_ok = blk.call( | ||
| I32, | ||
| "js_typed_feedback_class_field_set_guard", | ||
| &[ | ||
| (I64, &site_id), | ||
| (DOUBLE, &recv_box), | ||
| (I32, &expected_class_id_str), | ||
| (I64, &expected_keys), | ||
| (I64, &key_raw), | ||
| (I32, &field_idx_str), | ||
| (DOUBLE, &val_double), | ||
| (I32, requires_raw_f64_str), | ||
| ], | ||
| ); | ||
| (key_raw, guard_ok) | ||
| blk.and(I64, &key_bits, POINTER_MASK_I64) | ||
| }; | ||
| let guard_pass = ctx.block().icmp_ne(I32, &guard_ok, "0"); | ||
| let expected_keys = | ||
| ctx.block().load(I64, &format!("@{}", keys_global_name)); | ||
| let fast_idx = ctx.new_block("class_field_set.fast"); | ||
| let fallback_idx = ctx.new_block("class_field_set.fallback"); | ||
| let merge_idx = ctx.new_block("class_field_set.merge"); | ||
| let fast_label = ctx.block_label(fast_idx); | ||
| let fallback_label = ctx.block_label(fallback_idx); | ||
| let merge_label = ctx.block_label(merge_idx); | ||
|
|
||
| // #5094 Phase 3b: gate the direct raw-f64 store with an | ||
| // inline, LICM-hoistable shape/layout check plus an | ||
| // inline "value is a plain number" check; only a miss | ||
| // pays the out-of-line guard call. Restricted to raw-f64 | ||
| // data fields. On a miss we fall through to today's exact | ||
| // guard path, so an inline `false` is never unsafe. | ||
| if requires_raw_f64 { | ||
| let inline_ok = emit_inline_class_field_guard( | ||
| ctx.block(), | ||
| &recv_box, | ||
| &expected_class_id_str, | ||
| &expected_keys, | ||
| Some(&val_double), | ||
| ); | ||
| let needguard_idx = ctx.new_block("class_field_set.needguard"); | ||
| let needguard_label = ctx.block_label(needguard_idx); | ||
| ctx.block().cond_br(&inline_ok, &fast_label, &needguard_label); | ||
| ctx.current_block = needguard_idx; | ||
|
Comment on lines
+335
to
+346
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inline raw-f64 set fast-path bypasses frozen-object protection. Line 345 can jump straight to the fast store, but the inline predicate does not check 🤖 Prompt for AI Agents |
||
| } | ||
| let guard_ok = ctx.block().call( | ||
| I32, | ||
| "js_typed_feedback_class_field_set_guard", | ||
| &[ | ||
| (I64, &site_id), | ||
| (DOUBLE, &recv_box), | ||
| (I32, &expected_class_id_str), | ||
| (I64, &expected_keys), | ||
| (I64, &key_raw), | ||
| (I32, &field_idx_str), | ||
| (DOUBLE, &val_double), | ||
| (I32, requires_raw_f64_str), | ||
| ], | ||
| ); | ||
| let guard_pass = ctx.block().icmp_ne(I32, &guard_ok, "0"); | ||
| ctx.block() | ||
| .cond_br(&guard_pass, &fast_label, &fallback_label); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 78
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 3111
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 2485
Unsafe dereference happens before tag validation is enforced in control flow.
The function computes
is_obj_tagon line 99 but does not branch on it. Lines 104–123 execute unconditionally in the same basic block, dereferencingobj_ptr(derived from unvalidated bits) regardless of whether the pointer tag check passed. Theis_obj_tagboolean is later combined with other conditions via bitwise AND, but bitwise operations are data dependencies, not control-flow branches—all loads execute before any guard can reject the input. Move the header and object loads (lines 104–123) into a block that only executes whenis_obj_tagis true, or ensure all callsites validate the pointer tag before entering this helper.🤖 Prompt for AI Agents