Repository navigation
tests: 21 stale codegen IR assertions and the product element-identity test true again (exact-int32 guard, call-site stack checks, counter-versioned loops, P3 console.log) - #14599
Merged
Conversation
added 9 commits
November 13, 2026 01:45
…), a stack check rejoins its edge, out-of-range counter stores sit behind the versioned loop's entry guard
…nchor (their console.log of two proven Numbers binds nothing since P3)
…ing trampoline, out of line or (36a74ff) preinlined
…ack check (the layout note's block sits behind one since 36a74ff)
…the -0 test anded into one predicate
|
Caution Review failedThe pull request is closed.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No lowering, fact or runtime code changes. Every test below was red on main e147126 (and all but the perry test on
base 4283f71). For each: what the assertion looked for, what the IR has now and why, and how the adapted test
states the same property; the negative controls patch the LOWERING in a scratch tree (never committed) and show the
adapted test fails.
1.
invalidation::inclusive_local_length_bound_does_not_use_local_length_bound_fact,invalidation::negative_loop_counter_does_not_use_local_length_bound_fact(#7505 class; "the invalidated proof leaked through")Verdict: stale assertion, no leak. The fixtures are
const n = 8; const buf = Buffer.alloc(n); for (i = 0; i <= n; i++) buf[i] = 1and the same loop fromi = -1withi < n. The local length sourcencannot certify these stores(i = n, i = -1 are out of range). The tests asserted "no native buffer element address anywhere in
probe". Sincefa6faf8 / 97ce0b7 (11-08, counter-versioned loops) the loop has two copies: the CHECKED copy keeps
js_buffer_set(with its own bound check), and the FAST copy takesgetelementptr inboundsstores, entered only whencounter_entry_checkpasses. Its conjuncts, in the IR of both fixtures: counter's entry value>= 0, step>= 0,last + step <= i32::MAX, andlast + 1 <= length, wherelast = boundfor<=(bound - 1for<) andlengthisthe slot stored from
js_buffer_length(buf)at the view's birth (the runtime length, not the local source). For thefixtures that is
9 <= 8(false) resp.-1 >= 0(false): the fast copy is never entered.As real programs (qb10 fx/inv_buffer_bounds.ts: the two fixtures, both with a runtime-unknown
n, start -5, aUint8Array variant, an in-bounds positive control, canary buffers allocated before and after each buffer, and a
2 000-round stress of out-of-range stores between allocations): output = node in default mode, in check mode
(
PERRY_VERIFY_FACTS=1, rc 0) and under GC schedule stress (instrumented runtime, seeds 1 / 7 / 42, rate 1, conservativescan auto and off); every canary intact,
buf[-1]undefined, no element past the end written.The tests now use
assert_out_of_range_counter_store_is_entry_guarded: either the store is the checkedjs_buffer_setalone, or 8326a00'sassert_stale_length_store_is_checked_or_runtime_versionedholds (every nativeGEP in the fast copy, a guard conjunct
end <= zext(load(slot written only from js_buffer_length))) AND the guard hasa conjunct
icmp sge i32 (load counter slot), 0(the counter slot is the one the fast copy's condition compares) AND,for
<=,end = add (sext bound), 1(notbound - 1).Negative controls:
counter_nonnegreplaced bytrueincounter_entry_check-> both tests FAIL ("the fast copy'sguard must test the counter's entry value >= 0"); the
<=case lowered as<(last = bound - 1) -> the inclusivetest FAILS. (The helper of 8326a00 alone would have passed the first sabotage on the negative-counter fixture.)
2. 13 entry-guard tests +
typed_i32_closure_clone_accepts_immutable_i32_capture(Int32 lane)Stale. The predicates
guards_i32_arg/unboxes_i32_arglooked for the INT32-tag identity test(
icmp eq i64 %, 9222809086901354496+ range fcmps) and the tag/doubleselect; two tests also looked for the INT32tag constant as the boxing of the raw result. P3 (#14353 369605c, P3 B3 09454cd, both before base) made a Number
one encoding: the guard is
emit_typed_i32_plain_guard_and_raw(%raw = fptosi.sat(%v),sitofp %raw == %v,bits(%v) != -0), the unbox onefptosibehind it, the result boxed withsitofp i32 to double. The IR of everyfailing test has exactly that form and still routes to
$typed_i32/$spec_*and$generic.guards_i32_argnow follows the data flow: afptosi.satof%v, whosesitofpisfcmp oeqwith the SAME%v, and anicmp ne i64 (bitcast %v), -9223372036854775808.unboxes_i32_arg:fptosi double % to i32or the guard's saturating conversion.boxes_i32_result(ir, callee): the clone'scall i32result goes throughsitofp i32 %r to double.Negative control: the
-0conjunct dropped fromemit_typed_i32_plain_guard_and_raw-> the representative testsFAIL.
3.
static_method_shape_guard_retains_canonical_layout_clone,typed_f64_receiver_method_clone_raw_loads_after_composed_guards("raw-f64 receiver clone", undetermined)Stale. Both read the targets of a guard's branch as block names. Since 36a74ff (11-07, chess profile: the
stack check moves from the entry to the calling blocks) an edge into a calling block enters
stack_guard.call.Nfirst, whose overflow arm calls
js_stack_overflowand which rejoins the edge's own target. In the failing IR themethod-direct guard branches to
stack_guard.call.16/.19, and the raw-f64 field guard's success edge to a stackcheck in front of the block that calls
$typed_f64_recv. New helperthrough_stack_checkfollows astack_guard.call.*edge to the one target it rejoins (it asserts there is exactly one); the tests compare thattarget as before. The static test's last assertion (
publicwrappernoinline) is also stale for the same commit:a small spec trampoline is preinlined (
alwaysinline,PERRY_SPEC_INLINE); it now asserts the public entry is therouting trampoline (guard,
$spec_*,$generic) with no copy of the method-direct guard, out of line or preinlined.Negative control: the method-direct deref branch's fast / fallback labels swapped -> the static test FAILS.
4.
large_object_barriers::large_local_array_push_inbounds_store_emits_precise_slot_barrier("missing slot-layout note", GC write-barrier class)Stale; the note and the barrier are emitted. The test walks the CFG region from
apush.inbounds.*throughapush.*blocks. In the IR the in-bounds block stores the element, then branches: the fast arm (value is a pointerAND the array header's layout state already makes the collector scan its slots,
flags & 0xF880 == 0xA000) skips thenote; otherwise
apush.pointer_layout.bookkeepingcallsjs_gc_note_slot_layoutandjs_array_note_numeric_write;both rejoin
apush.pointer_layout.done, thenapush.barriercallsjs_write_barrier_slot_validated_parentfor anold parent (or an incremental mark). The edge into the bookkeeping block goes through a
stack_guard.call.Ncheck(36a74ff, as in 3), so the
apush.*-only walk never reached it. The walk now passes throughstack_guard.*blocks. As a real program (qb10 fx/large_push_barrier.ts: a 2 050-element literal Array (outside the small-literal
path) receiving young objects through the optimized push, 64 pushes per array with allocation churn between them,
some arrays kept across 5 000 more allocations, every child checked): output = node in default and check mode and
under GC schedule stress (seeds 1 / 7 / 42, rate 1, scan auto and off: 1 496 forced copying minors, 241 926 objects
moved per run). Negative control: the bookkeeping block's
js_gc_note_slot_layoutnot emitted -> the test FAILS.5.
scalar_replaced_slot_roots::numeric_only_scalar_replaced_{object_emits_no_rooting,literal_emits_no_entry_rooting}Stale premise (vacuity guard fired). The witness that the module binds at all was
console.log(p.x, p.y)'spooled argument accumulator. Since P3 those two arguments are proven Numbers and the call passes them in a plain
stack array: no bind anywhere, so the
== 0claims had no subject. The fixtures now addrooting_anchor()(
console.log({k: 1}, {k: 1}): the first argument is held in a pooled temp root while the second allocates; ittouches no scalar-replaced local); the zero value-slot binds / barriers and the non-vacuity witness hold. Negative
control:
Expr::Numberno longer a known non-pointer inexpr_is_known_non_pointer_shadow_value-> both FAIL.6. perry bin
product_element_identity_preserves_codegen_factsStale expectation of this effort's facts. The test compiles a program with and without element identity
(
element_offsetcleared) and asserts equal elision facts. Two fields differ:number_element_reads {} vs {75: false}(P3 B1, 59925ca, 11-09: first bad by reading; the long gate saw it red at 1343403) andprimitive_elements {} vs {75}(#14537, 91a26fd). Both are keyed byelement_offsetby design: a site with offset0 has no key, so without identity the fact cannot be recorded. The test now empties every element-keyed map
(
element_reads,number_element_reads,int32_element_reads,element_floors,element_keys,key_signs,wide_keys,inbounds_stores,rest_reads,rest_writes,quiet_elements,primitive_elements), asserts theyare all empty without identity and that every key with identity is an element site of the program, and asserts every
other fact equal (sites, work, pops, constraints and digest as before).
Also verified, no change needed
constructor_key_order::constructor_stores_do_not_declare_fields: green on main (fixed by b71c3ca / verify-facts: the F3 slot witness states #14485's layout claim (a key's overflow entry, the hole behind the read's compare); S0 arm; stale hir test #14570).num_seeds_audit: 2/0 on main.Run (qb10)
native_proof_regressions275 / 22,large_object_barriers2 / 1,scalar_replaced_slot_roots9 / 2, perry bin
product_element_identity_preserves_codegen_factsFAILED.native_proof_regressions293 / 4,large_object_barriers3 / 0,scalar_replaced_slot_roots11 / 0, perry binproduct_element_identity1 / 0,scripts/check_file_size.shOK(
native_proof_regressions.rsre-recorded 15688 -> 15809: the helpers above).native_proof_regressionsare NOT stale tests of this PR and stay red:invalidation::loop_length_effect_artifact_records_consumed_preservation_fact,invalidation::non_mutating_array_alias_preserves_length_and_bounds_proofs,packed_f64_loop_store_update_versions_with_side_exit,packed_f64_loop_unary_math_store_versions_with_side_exit. First bad commit by build: 91a26fd (wpfacts, codegen: a loop without calls keeps a length only when its sites run no program code (quiet sites) #14537, quietsites; its parent passes all four), the same commit turns
typed_shape_descriptors::bounded_integer_array_store_omits_layout_note_and_barrierand 7 codegen lib tests (
bounded_array_hole_tests,bounded_array_index_tests, 5barrier_stem_census_tests) red.Reported to its owner, not changed here.
counter_nonneg= true -> both invalidation tests FAIL;<=as<-> the inclusive test FAILS;
-0conjunct dropped -> the 14 Int32 tests FAIL; method-direct deref edges swapped ->the static test FAILS;
Expr::Numbernot a known non-pointer -> the 3 numeric scalar tests FAIL; no layout note ineither push bookkeeping block ->
large_local_array_push_inbounds_store_emits_precise_slot_barrierFAILS.fx/large_push_barrier.ts = node in default, check mode and GC schedule stress (seeds 1 / 7 / 42, scan auto / off).
cargo check -p perry-codegen -p perry --lib --tests --binson the merged head.No lowering, fact or runtime change: no switch; chess not measured. Not run: IR census, tssrc, Supabase, skin size.