From 4b47b09f2f8ff417f8a5b0efd0d27ead3b54e68f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Thu, 18 Jun 2026 06:46:21 +0200 Subject: [PATCH 1/2] perf(codegen): elide GC write barrier for non-pointer class-field stores (#5334 lever D) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The boxed class-field-SET fast path emitted a generational write barrier (`js_write_barrier_slot`) unconditionally. But the barrier only matters when the stored value is a heap pointer — it records the parent→child reference so the minor GC scans the parent. Storing a value that is a non-pointer by construction (number / bool / undefined / null / comparison / arithmetic) creates no such reference, so the barrier is a semantic no-op. Skip it in that case, reusing `expr_produces_non_pointer_bits_by_construction` — the same predicate the array-store paths already trust for barrier elision, so the GC soundness standard is unchanged. The barrier flag is computed before the block builder is borrowed; the LAYOUT NOTE is kept regardless (it tracks 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). Verified: a numeric-heavy class drops 6 of 9 boxed field-store barriers (numeric/boolean stores), keeps all 3 genuine pointer (string) stores, and runs to the correct result under a 2M-iteration GC-exercising loop. New unit test asserts both directions (numeric → no barrier, string literal → barrier kept); full perry-codegen suite green (incl. large_object_barriers). Tier-2 lever of the IR-efficiency roadmap (#5334, lever D). --- crates/perry-codegen/src/expr/property_set.rs | 18 ++++++++- crates/perry-codegen/tests/typed_feedback.rs | 39 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/crates/perry-codegen/src/expr/property_set.rs b/crates/perry-codegen/src/expr/property_set.rs index 2936d8b4f..08005bc65 100644 --- a/crates/perry-codegen/src/expr/property_set.rs +++ b/crates/perry-codegen/src/expr/property_set.rs @@ -38,7 +38,8 @@ 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, + 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, @@ -370,6 +371,19 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { .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(); @@ -393,7 +407,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { true, &obj_bits, &field_addr, - true, + field_set_barrier_needed, ); } blk.br(&merge_label); diff --git a/crates/perry-codegen/tests/typed_feedback.rs b/crates/perry-codegen/tests/typed_feedback.rs index 9ad0fad33..70673f2d5 100644 --- a/crates/perry-codegen/tests/typed_feedback.rs +++ b/crates/perry-codegen/tests/typed_feedback.rs @@ -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)]); From 4ab7e3a1fa2eb9caaef73dcca02239907a66ae4a Mon Sep 17 00:00:00 2001 From: Ralph Date: Wed, 17 Jun 2026 22:34:14 -0700 Subject: [PATCH 2/2] style: rustfmt import block in property_set.rs Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/perry-codegen/src/expr/property_set.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/perry-codegen/src/expr/property_set.rs b/crates/perry-codegen/src/expr/property_set.rs index 08005bc65..47cb71ea3 100644 --- a/crates/perry-codegen/src/expr/property_set.rs +++ b/crates/perry-codegen/src/expr/property_set.rs @@ -39,9 +39,9 @@ use super::{ 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, 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, + 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,