From 12afa222d9b1f585d6f51ea35902d7e8ee1560cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Thu, 13 Aug 2026 10:03:55 +0200 Subject: [PATCH 1/3] test(gc): pin the #7990 Map comparison regression --- .../perry-codegen/src/expr/compare_tests.rs | 25 +++++++++++++++++++ crates/perry-runtime/src/gc/copying.rs | 7 ++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/crates/perry-codegen/src/expr/compare_tests.rs b/crates/perry-codegen/src/expr/compare_tests.rs index 4d296306fc..216182ab39 100644 --- a/crates/perry-codegen/src/expr/compare_tests.rs +++ b/crates/perry-codegen/src/expr/compare_tests.rs @@ -101,6 +101,31 @@ fn strict_eq_rereads_its_left_operand_below_an_allocating_right_operand() { ); } +/// #7990 surfaced the same stale comparison-operand class through a different +/// consumer: the copier reached bytes reporting the impossible combination +/// `GC_TYPE_MAP | GC_FLAG_INTERNED`. Keep the reported typed-Map population in +/// the regression. The generic object case above would stay green if a future +/// type-analysis shortcut accidentally classified Maps as non-pointers. +#[test] +fn strict_eq_rereads_a_map_operand_below_an_allocating_right_operand() { + let ir = cmp_ir( + "streq_rooted_map_operand_7990", + CompareOp::Eq, + Expr::MapNew, + Expr::Object(vec![("right".to_string(), Expr::Number(2.0))]), + ); + let left = call_operand_of(&ir, "js_eq", 0); + let right = call_operand_of(&ir, "js_eq", 1); + let left_producer = producer_line(&ir, &left); + let right_producer = producer_line(&ir, &right); + assert!( + left_producer > right_producer, + "js_eq's Map operand ({left}) is produced at line {left_producer}, above the right \ + operand ({right}) at line {right_producer}. An intervening collection would leave \ + the comparison holding a retired Map address (#7990).\n{ir}" + ); +} + /// The complementary cost assertion: even with an allocating right operand, /// a proven-number left operand cannot be invalidated by relocation and must /// stay in its original register. A blanket "root every comparison" fix would diff --git a/crates/perry-runtime/src/gc/copying.rs b/crates/perry-runtime/src/gc/copying.rs index d7463b7d8b..399e62f8c5 100644 --- a/crates/perry-runtime/src/gc/copying.rs +++ b/crates/perry-runtime/src/gc/copying.rs @@ -1033,8 +1033,11 @@ pub(super) fn scan_remembered_dirty_slots_copying( } /// The young-pin latch was clear, the preflight was skipped on that proof, and -/// the copier then met a pinned young object anyway — so the latch is -/// incomplete and a pin site exists that does not go through `gc::pin_object`. +/// the copier then met bytes that describe a pinned young object anyway. +/// This is the instant relocation would become unsafe, but it does not by +/// itself identify the violated invariant. In #7990 the header was internally +/// impossible (`GC_TYPE_MAP | GC_FLAG_INTERNED`), and the fault disappeared +/// when comparison operands were rooted; the pin latch itself was complete. /// /// There is no recovery: leaving the object in from-space strands the /// referring slot on memory `copying_reset_from_spaces_and_flip` is about to From 13c1c1c75901f505c1b431c7b2e522b6d6b738e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Thu, 13 Aug 2026 10:04:54 +0200 Subject: [PATCH 2/3] docs(changelog): record the #7990 regression --- .../8021-7990-map-comparison-regression.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 changelog.d/8021-7990-map-comparison-regression.md diff --git a/changelog.d/8021-7990-map-comparison-regression.md b/changelog.d/8021-7990-map-comparison-regression.md new file mode 100644 index 0000000000..9b924b8a31 --- /dev/null +++ b/changelog.d/8021-7990-map-comparison-regression.md @@ -0,0 +1,15 @@ +**Fixed: the zod dependency corpus no longer reaches the young-pin latch +through a stale comparison operand (#7990).** + +The abort's `GC_TYPE_MAP | GC_FLAG_INTERNED` header was internally impossible: +the interned flag is only created on strings. A comparison operand had kept an +old SSA address across an allocating right-hand operand, then handed recycled +bytes to the copying collector. The comparison-rooting fix in #8011 removed +that window; a patch-only A/B completed 26 of 26 rate-1 corpus runs clean, with +about 6,400 copying minors and 855,000 moved objects per run. + +An LLVM-dataflow regression now covers the reported typed-Map population and +proves that its value is re-read from a GC root below the allocating operand. +The collector still aborts before an unsafe relocation, but its internal +documentation no longer assumes an incomplete pin latch is the only possible +cause. From 55c63b0aff5466b61a44950025f94bfd85f4130c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Thu, 13 Aug 2026 10:38:04 +0200 Subject: [PATCH 3/3] test(gc): correct the #7990 assertion diagnostic --- crates/perry-codegen/src/expr/compare_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/perry-codegen/src/expr/compare_tests.rs b/crates/perry-codegen/src/expr/compare_tests.rs index 216182ab39..052ab1ee91 100644 --- a/crates/perry-codegen/src/expr/compare_tests.rs +++ b/crates/perry-codegen/src/expr/compare_tests.rs @@ -120,7 +120,7 @@ fn strict_eq_rereads_a_map_operand_below_an_allocating_right_operand() { let right_producer = producer_line(&ir, &right); assert!( left_producer > right_producer, - "js_eq's Map operand ({left}) is produced at line {left_producer}, above the right \ + "js_eq's Map operand ({left}) is produced at line {left_producer}, below the right \ operand ({right}) at line {right_producer}. An intervening collection would leave \ the comparison holding a retired Map address (#7990).\n{ir}" );