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
22 changes: 18 additions & 4 deletions crates/perry-codegen/src/expr/property_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ use super::{
emit_layout_note_slot_on_block, emit_shadow_slot_clear, emit_shadow_slot_update_for_expr,
emit_string_literal_global, emit_typed_feedback_register_site, emit_v8_export_call,
emit_v8_member_method_call, emit_write_barrier, emit_write_barrier_slot_on_block,
expr_is_known_non_pointer_shadow_value, extract_array_of_object_shape, i32_bool_to_nanbox,
import_origin_suffix, is_global_this_builtin_function_name, is_global_this_builtin_name,
is_known_finite, lower_array_literal, lower_channel_reduction, lower_expr, lower_expr_as_i32,
expr_is_known_non_pointer_shadow_value, expr_produces_non_pointer_bits_by_construction,
extract_array_of_object_shape, i32_bool_to_nanbox, import_origin_suffix,
is_global_this_builtin_function_name, is_global_this_builtin_name, is_known_finite,
lower_array_literal, lower_channel_reduction, lower_expr, lower_expr_as_i32,
lower_index_set_fast, lower_js_args_array, lower_object_literal, lower_stream_super_init,
lower_url_string_getter, nanbox_bigint_inline, nanbox_pointer_inline,
nanbox_pointer_inline_pub, nanbox_string_inline, proxy_build_args_array, raw_f64_layout_fact,
Expand Down Expand Up @@ -370,6 +371,19 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
.cond_br(&guard_pass, &fast_label, &fallback_label);

ctx.current_block = fast_idx;
// #5334 lever D: a value that is a non-pointer by
// construction (number / bool / undefined / null /
// comparison / arithmetic) creates no parent→child heap
// reference, so the generational write barrier is a
// semantic no-op and can be skipped. Computed before the
// block builder is borrowed below. The LAYOUT NOTE is
// kept regardless: it records the slot's pointer-ness for
// minor-scan skipping, and a non-pointer write into a
// slot that previously held a pointer is a real
// transition the GC must observe. Same soundness standard
// as the array-store barrier elision.
let field_set_barrier_needed =
!expr_produces_non_pointer_bits_by_construction(ctx, value);
let blk = ctx.block();
let obj_ptr = blk.inttoptr(I64, &obj_handle);
let header_skip = "24".to_string();
Expand All @@ -393,7 +407,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
true,
&obj_bits,
&field_addr,
true,
field_set_barrier_needed,
);
}
blk.br(&merge_label);
Expand Down
39 changes: 39 additions & 0 deletions crates/perry-codegen/tests/typed_feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,45 @@ fn typed_feedback_guards_direct_class_field_specialization() {
assert!(ir.contains("call double @js_object_get_field_by_name_f64"));
}

#[test]
fn class_field_set_elides_write_barrier_for_nonpointer_value() {
// #5334 lever D: storing a value that is a non-pointer by construction
// into a BOXED class field (a String slot — only Number is raw-f64) skips
// the generational write barrier, since the store creates no parent→child
// heap reference. The layout note still fires (it tracks the slot's
// pointer-ness). A value that may be a heap pointer keeps the barrier.
let build = |val: Expr| {
let c = class(140, "Bx", vec![field("s", Type::String)]);
module_with_classes(
"lever_d_field_barrier.ts",
vec![c],
vec![
param(1, "o", Type::Named("Bx".to_string())),
param(2, "p", Type::String),
],
Type::Number,
vec![
Stmt::Expr(Expr::PropertySet {
object: Box::new(Expr::LocalGet(1)),
property: "s".to_string(),
value: Box::new(val),
}),
Stmt::Return(Some(Expr::Number(0.0))),
],
)
};

// A numeric-by-construction value takes the boxed class-field fast store
// but needs no write barrier.
let ir_num = ir_for(build(Expr::Number(7.0)));
assert!(ir_num.contains("class_field_set.fast"));
assert!(!ir_num.contains("call void @js_write_barrier_slot"));

// A definite heap pointer (string literal) keeps the slot barrier.
let ir_ptr = ir_for(build(Expr::String("hi".to_string())));
assert!(ir_ptr.contains("call void @js_write_barrier_slot"));
}

#[test]
fn typed_feedback_guards_direct_class_method_specialization() {
let mut point = class(103, "Point", vec![field("x", Type::Number)]);
Expand Down
Loading