From 1c29a183787247840b4d9ac9669ad5f5e9ef0400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 24 Jul 2026 06:25:00 +0200 Subject: [PATCH 1/3] perf: add bounded polymorphic write PIC --- .../followup-polymorphic-shapes.md | 16 +++++ changelog.d/6812-polymorphic-write-pic.md | 6 ++ crates/perry-codegen/src/codegen/closure.rs | 2 +- crates/perry-codegen/src/codegen/entry.rs | 4 +- crates/perry-codegen/src/codegen/function.rs | 2 +- crates/perry-codegen/src/codegen/method.rs | 4 +- .../perry-codegen/src/expr/proxy_reflect.rs | 59 ++++++++++++++++++- .../tests/native_proof_regressions.rs | 8 ++- 8 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 benchmarks/object-write-6812/followup-polymorphic-shapes.md create mode 100644 changelog.d/6812-polymorphic-write-pic.md diff --git a/benchmarks/object-write-6812/followup-polymorphic-shapes.md b/benchmarks/object-write-6812/followup-polymorphic-shapes.md new file mode 100644 index 0000000000..a93ade7459 --- /dev/null +++ b/benchmarks/object-write-6812/followup-polymorphic-shapes.md @@ -0,0 +1,16 @@ +# Bounded polymorphic write PIC follow-up + +The write PIC now has two bounded shape entries. The second entry is consulted +only after the first entry has been primed; all existing mutable receiver and +slot guards remain on both hit paths. Measurements below are three alternating +Node/Perry samples with matching checksums. + +| Cell | Node median | Perry median | Writes | Checksum | +| --- | ---: | ---: | ---: | ---: | +| `shape_monomorphic` | 133 ms | 123 ms | 120,000,000 | 122,876,400 | +| `shape_two` | 133 ms | 667 ms | 96,000,000 | 98,876,400 | +| `shape_four` | 110 ms | 2,978 ms | 60,000,000 | 62,876,400 | + +The two-shape case improves substantially over the prior monomorphic-cache +fallback (~5.3 s), while four-shape receivers remain intentionally outside the +two-entry bound for a later measured extension. diff --git a/changelog.d/6812-polymorphic-write-pic.md b/changelog.d/6812-polymorphic-write-pic.md new file mode 100644 index 0000000000..e1a0ce655a --- /dev/null +++ b/changelog.d/6812-polymorphic-write-pic.md @@ -0,0 +1,6 @@ +## Bounded polymorphic object-write PIC + +Static object writes now retain a second shape/slot cache entry. Stable +two-shape receiver sites can stay on the guarded direct-store path while +exotic, mutable, and higher-polymorphism cases continue through the complete +runtime miss path. diff --git a/crates/perry-codegen/src/codegen/closure.rs b/crates/perry-codegen/src/codegen/closure.rs index 484d267294..2836076fee 100644 --- a/crates/perry-codegen/src/codegen/closure.rs +++ b/crates/perry-codegen/src/codegen/closure.rs @@ -967,7 +967,7 @@ pub(super) fn compile_closure( } for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [2 x i64] zeroinitializer", + "@{} = private global [4 x i64] zeroinitializer", ic_name )); } diff --git a/crates/perry-codegen/src/codegen/entry.rs b/crates/perry-codegen/src/codegen/entry.rs index 063d050852..48cbdcc1c9 100644 --- a/crates/perry-codegen/src/codegen/entry.rs +++ b/crates/perry-codegen/src/codegen/entry.rs @@ -1111,7 +1111,7 @@ pub(super) fn compile_module_entry( } for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [2 x i64] zeroinitializer", + "@{} = private global [4 x i64] zeroinitializer", ic_name )); } @@ -1510,7 +1510,7 @@ pub(super) fn compile_module_entry( // three symbols must be defined exactly once per shared library. for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [2 x i64] zeroinitializer", + "@{} = private global [4 x i64] zeroinitializer", ic_name )); } diff --git a/crates/perry-codegen/src/codegen/function.rs b/crates/perry-codegen/src/codegen/function.rs index d4e92faf0a..8e39b5b3d3 100644 --- a/crates/perry-codegen/src/codegen/function.rs +++ b/crates/perry-codegen/src/codegen/function.rs @@ -741,7 +741,7 @@ pub(super) fn compile_function( } for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [2 x i64] zeroinitializer", + "@{} = private global [4 x i64] zeroinitializer", ic_name )); } diff --git a/crates/perry-codegen/src/codegen/method.rs b/crates/perry-codegen/src/codegen/method.rs index 3ca6b456d9..4e41cf433a 100644 --- a/crates/perry-codegen/src/codegen/method.rs +++ b/crates/perry-codegen/src/codegen/method.rs @@ -984,7 +984,7 @@ pub(super) fn compile_method( } for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [2 x i64] zeroinitializer", + "@{} = private global [4 x i64] zeroinitializer", ic_name )); } @@ -1593,7 +1593,7 @@ pub(super) fn compile_static_method( } for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [2 x i64] zeroinitializer", + "@{} = private global [4 x i64] zeroinitializer", ic_name )); } diff --git a/crates/perry-codegen/src/expr/proxy_reflect.rs b/crates/perry-codegen/src/expr/proxy_reflect.rs index bb9251f5b7..45fb0815d1 100644 --- a/crates/perry-codegen/src/expr/proxy_reflect.rs +++ b/crates/perry-codegen/src/expr/proxy_reflect.rs @@ -286,12 +286,18 @@ fn lower_put_value_static_write_ic( let above_handles = ctx.block().icmp_ugt(I64, &target_handle, "1048575"); // 0x100000 let heap_candidate = ctx.block().and(I1, &pointer_tag, &above_handles); let guard_idx = ctx.new_block("put.pic.guard"); + let guard2_idx = ctx.new_block("put.pic.guard2"); + let fallback_idx = ctx.new_block("put.pic.fallback"); let hit_idx = ctx.new_block("put.pic.hit"); let miss_idx = ctx.new_block("put.pic.miss"); + let miss2_idx = ctx.new_block("put.pic.miss2"); let merge_idx = ctx.new_block("put.pic.merge"); let guard_label = ctx.block_label(guard_idx); + let guard2_label = ctx.block_label(guard2_idx); + let fallback_label = ctx.block_label(fallback_idx); let hit_label = ctx.block_label(hit_idx); let miss_label = ctx.block_label(miss_idx); + let miss2_label = ctx.block_label(miss2_idx); let merge_label = ctx.block_label(merge_idx); ctx.block() .cond_br(&heap_candidate, &guard_label, &miss_label); @@ -374,21 +380,52 @@ fn lower_put_value_static_write_ic( hit = ctx.block().and(I1, &hit, &token_nonzero); hit = ctx.block().and(I1, &hit, &slot_in_bounds); - ctx.block().cond_br(&hit, &hit_label, &miss_label); + ctx.block().cond_br(&hit, &hit_label, &fallback_label); + + // A second bounded cache entry handles stable polymorphism without + // changing the miss ABI. The first entry is filled initially; only after + // it contains a different shape do we consult/prime the second entry. + ctx.current_block = fallback_idx; + let first_empty = ctx.block().icmp_eq(I64, &cached_token, "0"); + ctx.block() + .cond_br(&first_empty, &miss_label, &guard2_label); + + ctx.current_block = guard2_idx; + let cached2_token_ptr = ctx.block().gep(I64, &cache_ref, &[(I64, "2")]); + let cached2_token = ctx.block().load(I64, &cached2_token_ptr); + let cached2_slot_ptr = ctx.block().gep(I64, &cache_ref, &[(I64, "3")]); + let slot2 = ctx.block().load(I64, &cached2_slot_ptr); + let token2_match = ctx.block().icmp_eq(I64, &shape_token, &cached2_token); + let token2_nonzero = ctx.block().icmp_ne(I64, &shape_token, "0"); + let slot2_in_bounds = ctx.block().icmp_ult(I64, &slot2, &inline_limit); + let mut hit2 = ctx.block().and(I1, &heap_candidate, &gc_object); + hit2 = ctx.block().and(I1, &hit2, ¬_forwarded); + hit2 = ctx.block().and(I1, &hit2, &flags_clear); + hit2 = ctx.block().and(I1, &hit2, ®ular); + hit2 = ctx.block().and(I1, &hit2, &class_nonzero); + hit2 = ctx.block().and(I1, &hit2, ¬_native_module); + hit2 = ctx.block().and(I1, &hit2, &token2_match); + hit2 = ctx.block().and(I1, &hit2, &token2_nonzero); + hit2 = ctx.block().and(I1, &hit2, &slot2_in_bounds); + ctx.block().cond_br(&hit2, &hit_label, &miss2_label); ctx.current_block = hit_idx; + let selected_slot = ctx + .block() + .phi(I64, &[(&slot, &guard_label), (&slot2, &guard2_label)]); + let pointer_possible = !(is_numeric_expr(ctx, value) || expr_produces_non_pointer_bits_by_construction(ctx, value)); { let header_size = crate::target_layout::object_header_size_bytes(ctx.target_triple).to_string(); let blk = ctx.block(); - let slot_offset = blk.shl(I64, &slot, "3"); + let slot_offset = blk.shl(I64, &selected_slot, "3"); let fields_base = blk.add(I64, &target_handle, &header_size); let field_addr = blk.add(I64, &fields_base, &slot_offset); let field_ptr = blk.inttoptr(I64, &field_addr); if pointer_possible { - let slot_i32 = blk.trunc(I64, &slot, I32); + let slot_i32 = blk.trunc(I64, &selected_slot, I32); emit_jsvalue_slot_store_scalar_aware_on_block( blk, &field_ptr, @@ -428,12 +465,28 @@ fn lower_put_value_static_write_ic( let miss_end_label = ctx.block().label.clone(); ctx.block().br(&merge_label); + ctx.current_block = miss2_idx; + let miss2_value = ctx.block().call( + DOUBLE, + "js_put_value_set_ic_miss", + &[ + (DOUBLE, &target_value), + (I64, &key_handle), + (DOUBLE, &stored_value), + (I32, strict_i32), + (PTR, &cached2_token_ptr), + ], + ); + let miss2_end_label = ctx.block().label.clone(); + ctx.block().br(&merge_label); + ctx.current_block = merge_idx; let result = ctx.block().phi( DOUBLE, &[ (&stored_value, &hit_end_label), (&miss_value, &miss_end_label), + (&miss2_value, &miss2_end_label), ], ); Ok(Some(result)) diff --git a/crates/perry-codegen/tests/native_proof_regressions.rs b/crates/perry-codegen/tests/native_proof_regressions.rs index 309d035b64..385228be19 100644 --- a/crates/perry-codegen/tests/native_proof_regressions.rs +++ b/crates/perry-codegen/tests/native_proof_regressions.rs @@ -13481,6 +13481,10 @@ fn static_put_value_uses_write_pic_for_call_free_rhs() { ir.contains("4611686018427387904") && ir.contains("1073741824"), "the write PIC must mirror the read PIC's discriminated, never-reused ShapeId token" ); + assert!( + ir.contains("put.pic.guard2") && ir.contains("put.pic.miss2"), + "the write PIC should retain a bounded second shape entry" + ); } #[test] @@ -13746,8 +13750,8 @@ fn nested_same_shape_object_writes_version_one_through_four_fields() { rejected .matches("call double @js_put_value_set_ic_miss") .count(), - 5, - "the bounded rejection must preserve all five semantic write sites:\n{rejected}" + 10, + "the bounded rejection must preserve both cache miss entries for all five semantic write sites:\n{rejected}" ); let mut nonfinite_body = loop_body(1); From d8fa924a4415a65fe132129d364b1f89d97d0a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 24 Jul 2026 06:29:47 +0200 Subject: [PATCH 2/3] perf: extend write PIC to four shapes --- .../followup-polymorphic-shapes.md | 14 ++- crates/perry-codegen/src/codegen/closure.rs | 2 +- crates/perry-codegen/src/codegen/entry.rs | 4 +- crates/perry-codegen/src/codegen/function.rs | 2 +- crates/perry-codegen/src/codegen/method.rs | 4 +- .../perry-codegen/src/expr/proxy_reflect.rs | 106 +++++++++++++++++- .../tests/native_proof_regressions.rs | 11 +- 7 files changed, 123 insertions(+), 20 deletions(-) diff --git a/benchmarks/object-write-6812/followup-polymorphic-shapes.md b/benchmarks/object-write-6812/followup-polymorphic-shapes.md index a93ade7459..48d17ee924 100644 --- a/benchmarks/object-write-6812/followup-polymorphic-shapes.md +++ b/benchmarks/object-write-6812/followup-polymorphic-shapes.md @@ -1,9 +1,10 @@ # Bounded polymorphic write PIC follow-up -The write PIC now has two bounded shape entries. The second entry is consulted -only after the first entry has been primed; all existing mutable receiver and -slot guards remain on both hit paths. Measurements below are three alternating -Node/Perry samples with matching checksums. +The write PIC now has four bounded shape entries. Later entries are consulted +only after earlier entries have been primed; all existing mutable receiver and +slot guards remain on every hit path. Measurements below are three alternating +Node/Perry samples with matching checksums from the two-entry implementation; +the four-entry extension has an additional correctness-only parity run below. | Cell | Node median | Perry median | Writes | Checksum | | --- | ---: | ---: | ---: | ---: | @@ -12,5 +13,6 @@ Node/Perry samples with matching checksums. | `shape_four` | 110 ms | 2,978 ms | 60,000,000 | 62,876,400 | The two-shape case improves substantially over the prior monomorphic-cache -fallback (~5.3 s), while four-shape receivers remain intentionally outside the -two-entry bound for a later measured extension. +fallback (~5.3 s). The four-entry extension also produced exact parity in a +correctness run (`shape_four`: 60,000,000 writes, checksum 62,876,400); a +fresh release-mode timing sweep is still required before claiming its speedup. diff --git a/crates/perry-codegen/src/codegen/closure.rs b/crates/perry-codegen/src/codegen/closure.rs index 2836076fee..63fee9e795 100644 --- a/crates/perry-codegen/src/codegen/closure.rs +++ b/crates/perry-codegen/src/codegen/closure.rs @@ -967,7 +967,7 @@ pub(super) fn compile_closure( } for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [4 x i64] zeroinitializer", + "@{} = private global [8 x i64] zeroinitializer", ic_name )); } diff --git a/crates/perry-codegen/src/codegen/entry.rs b/crates/perry-codegen/src/codegen/entry.rs index 48cbdcc1c9..3dd22d3564 100644 --- a/crates/perry-codegen/src/codegen/entry.rs +++ b/crates/perry-codegen/src/codegen/entry.rs @@ -1111,7 +1111,7 @@ pub(super) fn compile_module_entry( } for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [4 x i64] zeroinitializer", + "@{} = private global [8 x i64] zeroinitializer", ic_name )); } @@ -1510,7 +1510,7 @@ pub(super) fn compile_module_entry( // three symbols must be defined exactly once per shared library. for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [4 x i64] zeroinitializer", + "@{} = private global [8 x i64] zeroinitializer", ic_name )); } diff --git a/crates/perry-codegen/src/codegen/function.rs b/crates/perry-codegen/src/codegen/function.rs index 8e39b5b3d3..fc6119a10d 100644 --- a/crates/perry-codegen/src/codegen/function.rs +++ b/crates/perry-codegen/src/codegen/function.rs @@ -741,7 +741,7 @@ pub(super) fn compile_function( } for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [4 x i64] zeroinitializer", + "@{} = private global [8 x i64] zeroinitializer", ic_name )); } diff --git a/crates/perry-codegen/src/codegen/method.rs b/crates/perry-codegen/src/codegen/method.rs index 4e41cf433a..96adc92abc 100644 --- a/crates/perry-codegen/src/codegen/method.rs +++ b/crates/perry-codegen/src/codegen/method.rs @@ -984,7 +984,7 @@ pub(super) fn compile_method( } for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [4 x i64] zeroinitializer", + "@{} = private global [8 x i64] zeroinitializer", ic_name )); } @@ -1593,7 +1593,7 @@ pub(super) fn compile_static_method( } for ic_name in &ic_globals { llmod.add_raw_global(format!( - "@{} = private global [4 x i64] zeroinitializer", + "@{} = private global [8 x i64] zeroinitializer", ic_name )); } diff --git a/crates/perry-codegen/src/expr/proxy_reflect.rs b/crates/perry-codegen/src/expr/proxy_reflect.rs index 45fb0815d1..47377ef3fd 100644 --- a/crates/perry-codegen/src/expr/proxy_reflect.rs +++ b/crates/perry-codegen/src/expr/proxy_reflect.rs @@ -287,17 +287,29 @@ fn lower_put_value_static_write_ic( let heap_candidate = ctx.block().and(I1, &pointer_tag, &above_handles); let guard_idx = ctx.new_block("put.pic.guard"); let guard2_idx = ctx.new_block("put.pic.guard2"); + let guard3_idx = ctx.new_block("put.pic.guard3"); + let guard4_idx = ctx.new_block("put.pic.guard4"); let fallback_idx = ctx.new_block("put.pic.fallback"); + let dispatch3_idx = ctx.new_block("put.pic.dispatch3"); + let dispatch4_idx = ctx.new_block("put.pic.dispatch4"); let hit_idx = ctx.new_block("put.pic.hit"); let miss_idx = ctx.new_block("put.pic.miss"); let miss2_idx = ctx.new_block("put.pic.miss2"); + let miss3_idx = ctx.new_block("put.pic.miss3"); + let miss4_idx = ctx.new_block("put.pic.miss4"); let merge_idx = ctx.new_block("put.pic.merge"); let guard_label = ctx.block_label(guard_idx); let guard2_label = ctx.block_label(guard2_idx); + let guard3_label = ctx.block_label(guard3_idx); + let guard4_label = ctx.block_label(guard4_idx); let fallback_label = ctx.block_label(fallback_idx); + let dispatch3_label = ctx.block_label(dispatch3_idx); + let dispatch4_label = ctx.block_label(dispatch4_idx); let hit_label = ctx.block_label(hit_idx); let miss_label = ctx.block_label(miss_idx); let miss2_label = ctx.block_label(miss2_idx); + let miss3_label = ctx.block_label(miss3_idx); + let miss4_label = ctx.block_label(miss4_idx); let merge_label = ctx.block_label(merge_idx); ctx.block() .cond_br(&heap_candidate, &guard_label, &miss_label); @@ -407,12 +419,66 @@ fn lower_put_value_static_write_ic( hit2 = ctx.block().and(I1, &hit2, &token2_match); hit2 = ctx.block().and(I1, &hit2, &token2_nonzero); hit2 = ctx.block().and(I1, &hit2, &slot2_in_bounds); - ctx.block().cond_br(&hit2, &hit_label, &miss2_label); + ctx.block().cond_br(&hit2, &hit_label, &dispatch3_label); + + ctx.current_block = dispatch3_idx; + let second_empty = ctx.block().icmp_eq(I64, &cached2_token, "0"); + ctx.block() + .cond_br(&second_empty, &miss2_label, &guard3_label); + + ctx.current_block = guard3_idx; + let cached3_token_ptr = ctx.block().gep(I64, &cache_ref, &[(I64, "4")]); + let cached3_token = ctx.block().load(I64, &cached3_token_ptr); + let cached3_slot_ptr = ctx.block().gep(I64, &cache_ref, &[(I64, "5")]); + let slot3 = ctx.block().load(I64, &cached3_slot_ptr); + let token3_match = ctx.block().icmp_eq(I64, &shape_token, &cached3_token); + let token3_nonzero = ctx.block().icmp_ne(I64, &shape_token, "0"); + let slot3_in_bounds = ctx.block().icmp_ult(I64, &slot3, &inline_limit); + let mut hit3 = ctx.block().and(I1, &heap_candidate, &gc_object); + hit3 = ctx.block().and(I1, &hit3, ¬_forwarded); + hit3 = ctx.block().and(I1, &hit3, &flags_clear); + hit3 = ctx.block().and(I1, &hit3, ®ular); + hit3 = ctx.block().and(I1, &hit3, &class_nonzero); + hit3 = ctx.block().and(I1, &hit3, ¬_native_module); + hit3 = ctx.block().and(I1, &hit3, &token3_match); + hit3 = ctx.block().and(I1, &hit3, &token3_nonzero); + hit3 = ctx.block().and(I1, &hit3, &slot3_in_bounds); + ctx.block().cond_br(&hit3, &hit_label, &dispatch4_label); + + ctx.current_block = dispatch4_idx; + let third_empty = ctx.block().icmp_eq(I64, &cached3_token, "0"); + ctx.block() + .cond_br(&third_empty, &miss3_label, &guard4_label); + + ctx.current_block = guard4_idx; + let cached4_token_ptr = ctx.block().gep(I64, &cache_ref, &[(I64, "6")]); + let cached4_token = ctx.block().load(I64, &cached4_token_ptr); + let cached4_slot_ptr = ctx.block().gep(I64, &cache_ref, &[(I64, "7")]); + let slot4 = ctx.block().load(I64, &cached4_slot_ptr); + let token4_match = ctx.block().icmp_eq(I64, &shape_token, &cached4_token); + let token4_nonzero = ctx.block().icmp_ne(I64, &shape_token, "0"); + let slot4_in_bounds = ctx.block().icmp_ult(I64, &slot4, &inline_limit); + let mut hit4 = ctx.block().and(I1, &heap_candidate, &gc_object); + hit4 = ctx.block().and(I1, &hit4, ¬_forwarded); + hit4 = ctx.block().and(I1, &hit4, &flags_clear); + hit4 = ctx.block().and(I1, &hit4, ®ular); + hit4 = ctx.block().and(I1, &hit4, &class_nonzero); + hit4 = ctx.block().and(I1, &hit4, ¬_native_module); + hit4 = ctx.block().and(I1, &hit4, &token4_match); + hit4 = ctx.block().and(I1, &hit4, &token4_nonzero); + hit4 = ctx.block().and(I1, &hit4, &slot4_in_bounds); + ctx.block().cond_br(&hit4, &hit_label, &miss4_label); ctx.current_block = hit_idx; - let selected_slot = ctx - .block() - .phi(I64, &[(&slot, &guard_label), (&slot2, &guard2_label)]); + let selected_slot = ctx.block().phi( + I64, + &[ + (&slot, &guard_label), + (&slot2, &guard2_label), + (&slot3, &guard3_label), + (&slot4, &guard4_label), + ], + ); let pointer_possible = !(is_numeric_expr(ctx, value) || expr_produces_non_pointer_bits_by_construction(ctx, value)); @@ -480,6 +546,36 @@ fn lower_put_value_static_write_ic( let miss2_end_label = ctx.block().label.clone(); ctx.block().br(&merge_label); + ctx.current_block = miss3_idx; + let miss3_value = ctx.block().call( + DOUBLE, + "js_put_value_set_ic_miss", + &[ + (DOUBLE, &target_value), + (I64, &key_handle), + (DOUBLE, &stored_value), + (I32, strict_i32), + (PTR, &cached3_token_ptr), + ], + ); + let miss3_end_label = ctx.block().label.clone(); + ctx.block().br(&merge_label); + + ctx.current_block = miss4_idx; + let miss4_value = ctx.block().call( + DOUBLE, + "js_put_value_set_ic_miss", + &[ + (DOUBLE, &target_value), + (I64, &key_handle), + (DOUBLE, &stored_value), + (I32, strict_i32), + (PTR, &cached4_token_ptr), + ], + ); + let miss4_end_label = ctx.block().label.clone(); + ctx.block().br(&merge_label); + ctx.current_block = merge_idx; let result = ctx.block().phi( DOUBLE, @@ -487,6 +583,8 @@ fn lower_put_value_static_write_ic( (&stored_value, &hit_end_label), (&miss_value, &miss_end_label), (&miss2_value, &miss2_end_label), + (&miss3_value, &miss3_end_label), + (&miss4_value, &miss4_end_label), ], ); Ok(Some(result)) diff --git a/crates/perry-codegen/tests/native_proof_regressions.rs b/crates/perry-codegen/tests/native_proof_regressions.rs index 385228be19..b90a08cc4f 100644 --- a/crates/perry-codegen/tests/native_proof_regressions.rs +++ b/crates/perry-codegen/tests/native_proof_regressions.rs @@ -13482,8 +13482,11 @@ fn static_put_value_uses_write_pic_for_call_free_rhs() { "the write PIC must mirror the read PIC's discriminated, never-reused ShapeId token" ); assert!( - ir.contains("put.pic.guard2") && ir.contains("put.pic.miss2"), - "the write PIC should retain a bounded second shape entry" + ir.contains("put.pic.guard2") + && ir.contains("put.pic.guard3") + && ir.contains("put.pic.guard4") + && ir.contains("put.pic.miss4"), + "the write PIC should retain four bounded shape entries" ); } @@ -13750,8 +13753,8 @@ fn nested_same_shape_object_writes_version_one_through_four_fields() { rejected .matches("call double @js_put_value_set_ic_miss") .count(), - 10, - "the bounded rejection must preserve both cache miss entries for all five semantic write sites:\n{rejected}" + 20, + "the bounded rejection must preserve all four cache miss entries for all five semantic write sites:\n{rejected}" ); let mut nonfinite_body = loop_body(1); From 18c500499fa40351156f08ba7d1f64dd33ed5c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 24 Jul 2026 06:34:42 +0200 Subject: [PATCH 3/3] perf: measure four-shape write PIC --- .../object-write-6812/followup-polymorphic-shapes.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/benchmarks/object-write-6812/followup-polymorphic-shapes.md b/benchmarks/object-write-6812/followup-polymorphic-shapes.md index 48d17ee924..a7c9827885 100644 --- a/benchmarks/object-write-6812/followup-polymorphic-shapes.md +++ b/benchmarks/object-write-6812/followup-polymorphic-shapes.md @@ -10,9 +10,13 @@ the four-entry extension has an additional correctness-only parity run below. | --- | ---: | ---: | ---: | ---: | | `shape_monomorphic` | 133 ms | 123 ms | 120,000,000 | 122,876,400 | | `shape_two` | 133 ms | 667 ms | 96,000,000 | 98,876,400 | -| `shape_four` | 110 ms | 2,978 ms | 60,000,000 | 62,876,400 | +| `shape_four` | 110 ms | 461 ms | 60,000,000 | 62,876,400 | The two-shape case improves substantially over the prior monomorphic-cache -fallback (~5.3 s). The four-entry extension also produced exact parity in a -correctness run (`shape_four`: 60,000,000 writes, checksum 62,876,400); a -fresh release-mode timing sweep is still required before claiming its speedup. +fallback (~5.3 s). The four-entry extension beats the prior four-shape +fallback (~3.0 s) while preserving exact parity. + +The final 15-pair raw samples were Node +`[108, 110, 110, 109, 113, 109, 109, 110, 108, 110, 113, 108, 109, 111, 111]` +and Perry +`[461, 464, 461, 461, 463, 472, 460, 492, 459, 461, 459, 460, 460, 460, 463]`.