From 55882825e94b7fe1f890a86e6131d00c871ecf08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 07:24:49 +0200 Subject: [PATCH 1/4] fix(gc-matrix): give the numarray-growth probe churn that reaches the collector (#7016) Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- .../test_gap_repsel_p4a3_numarray_growth.ts | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/test-files/test_gap_repsel_p4a3_numarray_growth.ts b/test-files/test_gap_repsel_p4a3_numarray_growth.ts index def67e3483..9cb7622f32 100644 --- a/test-files/test_gap_repsel_p4a3_numarray_growth.ts +++ b/test-files/test_gap_repsel_p4a3_numarray_growth.ts @@ -100,3 +100,73 @@ function holesSurviveGrowth(): string { return probe + " len=" + a.length; } console.log(holesSurviveGrowth()); + +// 6) GROWTH ACROSS A COLLECTION (#7016). +// +// Sections 1-5 above are the correctness content of this file, and they are +// deliberately untouched. They are also, measurably, unable to collect: the +// whole file allocates inside one 1 MB arena block and makes no `gc_malloc` +// calls, so `PERRY_GC_DIAG=1` printed NOTHING and every GC arm of +// `scripts/gc_repsel_matrix.sh` scored the file UNVER — 19 of 19 cells +// asserting a `Ptr` property about a collector that never ran. +// Lowering `PERRY_GC_HEAP_LIMIT` could not fix it: `gc_trigger_absolute_ +// ceiling_bytes` is budget/4 with a floor, so 2 MB is already the bottom. +// +// So the churn is added here rather than folded into the functions above, +// which keeps their "fully contained, therefore promoted" shape exactly as it +// was. The shape below is `test_gap_repsel_gc_stress`'s: an escaping, +// module-level sink that is grown and dropped, so the arena genuinely grows and +// genuinely produces garbage — with the numeric-array local initialized BEFORE +// the churn, grown by `push` past several capacity doublings WHILE the churn +// runs, and read AFTER the churn in the same iteration. A collection landing at +// any allocation point must therefore find the local's storage live, and a +// stale head cached across the relocation surfaces in the checksum. +let churnSink: unknown[] = []; +let churnEpochs = 0; + +function churn(i: number): void { + churnSink.push({ i: i, s: "g" + (i & 511), a: [i, i + 1] }); + if (churnSink.length > 2048) { + churnEpochs = (churnEpochs + 1) | 0; + churnSink = []; + } +} + +// The numeric-array local is grown by `push` across the churn, and both the +// ORIGINAL slot (index 0, written before any growth) and the newest slot are +// read after every churn call. +function growAcrossCollections(n: number): number { + const a: number[] = []; + a.push(0.25); + let acc = 0; + for (let i = 0; i < n; i++) { + churn(i); + a.push(i * 0.5); + acc += a[0] || 0; // pre-growth slot, after a relocation may have happened + acc += a[a.length - 1] || 0; // the slot just pushed + } + let tail = 0; + for (let i = 0; i < a.length; i++) { + tail += a[i] || 0; + } + return acc + tail + a.length; +} + +// A second shape: `new Array(n)` provenance, statically in-bounds reads of the +// pre-allocated region kept correct across churn-driven collections. +function allocGrowAcrossCollections(n: number): number { + const a: number[] = new Array(4); + a[0] = 1.5; + a[3] = 2.5; + let acc = 0; + for (let i = 0; i < n; i++) { + churn(i); + a.push(i * 0.125); + acc += (a[0] || 0) + (a[3] || 0); // same slots, possibly relocated + } + return acc + a.length; +} + +console.log(growAcrossCollections(20000)); +console.log(allocGrowAcrossCollections(20000)); +console.log("churn epochs " + churnEpochs); From 3f7d745d7e2c674c2a5137d714b172209268a49b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 07:37:44 +0200 Subject: [PATCH 2/4] fix(gc-matrix): the collect arms require a productive cycle, and four inert-arm entries retire (#7017) Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- .../7675-gc-matrix-collect-liveness.md | 107 ++++++++++++++++++ scripts/gc_matrix_liveness_check.py | 64 ++++++++++- scripts/gc_repsel_matrix.sh | 93 ++++++++++++--- test-parity/gc_matrix_inert_arms.txt | 54 +++++---- 4 files changed, 277 insertions(+), 41 deletions(-) create mode 100644 changelog.d/7675-gc-matrix-collect-liveness.md diff --git a/changelog.d/7675-gc-matrix-collect-liveness.md b/changelog.d/7675-gc-matrix-collect-liveness.md new file mode 100644 index 0000000000..bd491ffc53 --- /dev/null +++ b/changelog.d/7675-gc-matrix-collect-liveness.md @@ -0,0 +1,107 @@ +**gc-matrix: the `collect` arms now require a cycle that reclaimed something, the numarray-growth probe reaches the collector, and four known-inert entries are retired (#7016, #7017; #7018 closed as not reproducing).** + +Three issues, one disease: a check that could not fail. + +### #7016 — the probe never collected, so 19 cells measured nothing + +`test_gap_repsel_p4a3_numarray_growth` was the one corpus row still `UNVER` on +every GC arm. Reproduced: **0 cycles**, and `PERRY_GC_DIAG=1` printed *nothing at +all* — the file allocates inside one 1 MB arena block and makes no `gc_malloc` +calls, so the arena trigger never arms. `PERRY_GC_HEAP_LIMIT` cannot reach it +(`gc_trigger_absolute_ceiling_bytes` is budget/4 with a floor). **The probe was +at fault, not the predicate.** + +Sections 1–5 are untouched, so their "fully contained, therefore promoted" shape +is unchanged; a section 6 adds `test_gap_repsel_gc_stress`'s escaping +module-level churn sink, with the numeric-array local initialized *before* the +churn, grown by `push` past several capacity doublings *while* it runs, and read +*after* it in the same iteration. + +Measured across all 13 arms: **PASS on every one**, output byte-identical to the +pinned Node 26.5.1 oracle in each. `default` 4 cycles / **74,404 objects copied**; +`gen_gc_off` and the other `collect` arms 7 cycles; `shipped_default` unchanged +at 0, as its control role requires. Per #7666, a probe that merely allocates a +lot can still run zero copying minors — this one was checked for the copying +minor specifically, not just for a cycle. + +### #7017 — `cycles > 0` counted a teardown cycle + +A `collect` cell was `PASS` on any cycle. On a small corpus file that cycle lands +at the event-loop boundary *after* the program's last output and reclaims +nothing, because everything allocated after it armed was born black. That scored +identically to a run that collected mid-program while the test's +representation-selected locals were live — the property the matrix exists to +assert. **The predicate was at fault.** + +`collect` now requires a **productive** cycle. Following #7657's widening of the +gc-ratchet rule to `copied + promoted > 0`, the counter names a *destination* +rather than a single number: `reclaimed` sums `sweep_freed`, `block_reclaim`, +`eden_dead_bytes`, `freed_bytes` and `dead_bytes`, in **both** the `k=N` and JSON +`"k": N` spellings. Reading only `k=N` scored +`test_gap_gc_symbol_local_rooting` — 86 malloc-count-triggered cycles that free +31.9 MB of symbols — as reclaiming zero, because the malloc sweep's bytes appear +only in the JSON trace. + +It is a conservative proxy and the script says so: a mid-program cycle over a +heap that is entirely live reclaims nothing and reads `UNVER`. Under-claiming is +the safe direction for a liveness gate; `cycles` over-claimed. + +Both halves move together. `reclaimed` is threaded into the per-cell JSON, and +`gc_matrix_liveness_check.py`'s `REQUIREMENTS["collect"]` reads it. Three new +self-test cases pin the change — a cycle that reclaimed nothing must *not* +satisfy `collect`, a productive one must, and `scavenged`/`evacuated` must not +stand in for reclamation on an arm that is non-moving by construction. Reverting +the counter to `cycles` fails exactly two of them, so the fix is shown able to +fail. The per-run liveness table now prints `reclaimed` beside `collected` so the +gap between them stays on screen: measured at **collected 3/3, reclaimed 1/3** +for `gen_gc_off` on the Phase 4a.3 slice. + +Corpus-wide under `gen_gc_off`, of 58 files: 12 never collect (already `UNVER`), +**14 collect but reclaim nothing** — these were `PASS` and are now honestly +`UNVER` — and 32 are productive, which keeps every `collect` arm live. + +### The four known-inert arms were live, and CI had been saying so + +`test-parity/gc_matrix_inert_arms.txt` registered `default`, `verify_evac`, +`cons_scan_off` and `cons_scan_off_force` as inert because +`PERRY_GC_MOVING_LOOP_POLLS` is default-off, so the copying minor is "ineligible +by construction". **The poll flag has not changed and all four scavenge anyway.** + +`gc-stress` on `main`, run 31240304595 (2026-08-08): each satisfied +`requires=scavenge` on **41 of 58 cells**, `default` at `counter=1384046`, and +the job was red with four `STALE-REGISTRY` lines. Reproduced locally on +`test_gap_repsel_gc_stress` — a file this PR does not touch, under a predicate +this PR does not change — at 1/1 live per arm, `default` copying 228,181 objects; +`shipped_default`, with no pressure knob and no GC env at all, copies 340,956. +The shipped configuration relocates today. + +All four entries are deleted, with the measurement recorded in their place. What +changed is the collector around the flag (#7370's statepoint default, #7432, +#7657, #7666), not the flag — the entries' stated cause outlived its truth by +exactly the mechanism the registry was created to catch, which is why they are +deleted rather than re-worded. The registry is now empty and `gc-stress` passes +its liveness gate again. + +### #7018 — not reproducing, and the hypothesis is structurally refuted + +`PERRY_GC_TRACE=1` was reported to SIGSEGV `test_gap_repsel_scalar_replaced_locals` +under the evacuating arms. 20 runs across both link modes: **0 crashes**, stdout +byte-identical with and without the flag. + +The first link mode was vacuous and is reported as such: auto-optimize relinks +the runtime `--no-default-features`, so `diagnostics` is off and `GcCycleTrace::emit` +falls to its stub — all 113 `[gc] cycle` lines were "diagnostics feature +disabled", and the real tracer never ran. Re-run with the diagnostics archive: +**110 real `"event":"gc_cycle"` objects, 466,424 objects copied** — the arm is +demonstrably live and still does not crash. Four other corpus files behave the +same. + +Structurally, the hypothesis cannot hold. `PERRY_GC_TRACE` is read in exactly one +place (`gc_trace_enabled()`, `gc/policy.rs`) with two call sites: a scalar +counter snapshot, and a thread-local `u64` counter bump. Emission serialises +already-accumulated scalars. **Nothing on that path dereferences a heap object, +walks the object graph, or reads a `GcHeader`**, so it cannot dereference a +forwarded pointer and shares nothing with #6998/#6995. The issue's description — +"the tracer runs inside a collection, walking structures the collector is mid-way +through mutating" — describes `gc/trace.rs`, the *marking* tracer, which runs on +every collection regardless of the flag. A name collision, not a defect. diff --git a/scripts/gc_matrix_liveness_check.py b/scripts/gc_matrix_liveness_check.py index caadb67d51..5101714b24 100755 --- a/scripts/gc_matrix_liveness_check.py +++ b/scripts/gc_matrix_liveness_check.py @@ -59,10 +59,29 @@ MATRIX = REPO_ROOT / "scripts" / "gc_repsel_matrix.sh" # requires= value -> (human name, cell counter key(s) summed to decide "it bit") +# +# #7017: `collect` counted `cycles`, and a cycle is not evidence that the +# collector saw anything. On a small corpus file the shipped configuration +# completes exactly one cycle at the event-loop boundary, AFTER the program's +# last output, reclaiming nothing — everything allocated after the cycle armed +# was born black. That scored identically to a run that collected twice, +# mid-program, while the test's representation-selected locals were live, which +# is the property the matrix exists to assert. +# +# So the counter is `reclaimed`: the sum of every reclamation counter the run +# emitted (`sweep_freed`, `block_reclaim`, `eden_dead_bytes`, `freed_bytes`, +# `dead_bytes`, in both the `k=N` and JSON `"k": N` spellings — see the +# derivation in gc_repsel_matrix.sh). Like #7657's widening of the gc-ratchet +# rule to `copied + promoted > 0`, it names a DESTINATION rather than a single +# counter, so one collector change cannot pin it permanently false. +# +# It is a conservative proxy and the matrix says so: a mid-program cycle over a +# heap that is entirely live reclaims nothing and reads UNVER. Under-claiming is +# the safe direction for a liveness gate; `cycles` over-claimed. REQUIREMENTS = { "scavenge": ("copying young-gen minor", ("scavenged",)), "move": ("any relocation", ("evacuated", "scavenged")), - "collect": ("any GC cycle", ("cycles",)), + "collect": ("a productive GC cycle", ("reclaimed",)), "none": ("nothing (explicit control)", ()), } @@ -260,7 +279,7 @@ def _report(arms, cells): def _cell(arm, **kw): base = {"test": kw.pop("test", "t"), "arm": arm, "result": kw.pop("result", "PASS")} - base.update({"cycles": 0, "evacuated": 0, "scavenged": 0}) + base.update({"cycles": 0, "evacuated": 0, "scavenged": 0, "reclaimed": 0}) base.update(kw) return base @@ -354,6 +373,45 @@ def expect(name, violations, want): ), True, ) + # #7017, the whole point: a cycle that reclaimed NOTHING is the teardown + # shape, and must no longer satisfy `collect`. This is the assertion that + # would have failed before the counter changed. + expect( + "a cycle that reclaimed nothing does NOT satisfy collect", + check_report( + _report([{"id": "c", "requires": "collect"}], [_cell("c", cycles=1, reclaimed=0)]), + {}, + sink, + ), + True, + ) + expect( + "a productive cycle satisfies collect", + check_report( + _report( + [{"id": "c", "requires": "collect"}], + [_cell("c", cycles=2, reclaimed=1048576)], + ), + {}, + sink, + ), + False, + ) + # ...and relocation counters must not stand in for reclamation: a `collect` + # arm is non-moving by construction (PERRY_GEN_GC=0 / PERRY_WRITE_BARRIERS=0), + # so a cell reporting `scavenged` under one is reporting something else. + expect( + "collect is NOT satisfied by scavenged/evacuated alone", + check_report( + _report( + [{"id": "c", "requires": "collect"}], + [_cell("c", cycles=9, scavenged=500, evacuated=500)], + ), + {}, + sink, + ), + True, + ) expect( "an unknown requires= value is rejected", check_report( @@ -416,7 +474,7 @@ def expect(name, violations, want): for failure in failures: print("SELF-TEST FAIL: %s" % failure, file=sys.stderr) - print("self-test: %d checks, %d failures" % (12 + 6 + 3, len(failures))) + print("self-test: %d checks, %d failures" % (15 + 6 + 3, len(failures))) return 1 if failures else 0 diff --git a/scripts/gc_repsel_matrix.sh b/scripts/gc_repsel_matrix.sh index c92d89b46a..352c06379d 100755 --- a/scripts/gc_repsel_matrix.sh +++ b/scripts/gc_repsel_matrix.sh @@ -121,7 +121,11 @@ RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[0;33m'; NC=$'\033[0m' # copying minors. Any arm whose subject is the relocating young-gen # minor #7019 shipped must use THIS, not `move`. # move the arm claims to evacuate -> require moved/copied objects > 0 -# collect the arm claims to collect -> require at least one GC cycle +# collect the arm claims to collect -> require a PRODUCTIVE cycle: one that +# reclaimed something (#7017). `cycles>0` alone counts a cycle that +# lands at the event-loop boundary AFTER the program's last output +# and frees nothing, which cannot have observed the test's live +# locals -- the property the matrix exists to assert. # none no GC claim of its own (an explicit control) # # %P% expands to the pressure env (PERRY_GC_HEAP_LIMIT=) unless @@ -186,19 +190,19 @@ RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[0;33m'; NC=$'\033[0m' # inert. The liveness gate exists because that one is invisible on screen. # --------------------------------------------------------------------------- ARMS=( -"default||%P%|scavenge|as-shipped GC configuration under allocation pressure. ***INERT AT THE MOMENT, AND REGISTERED AS SUCH*** in test-parity/gc_matrix_inert_arms.txt. #7024 made this a relocating arm (the alloc-point trigger defers to js_gc_loop_safepoint -> gc_safepoint_moving_minor, which runs the copying minor on precise rewritable roots); #7161 then flipped PERRY_GC_MOVING_LOOP_POLLS default-OFF pending #7154, and that one env gates BOTH halves of the route -- perry-codegen's moving_safepoint_polls_enabled decides whether the back-edge polls are emitted at all, and perry-runtime's gc_moving_loop_polls_enabled decides whether the trigger defers to them. A default binary has neither, so the minor runs behind ManualGcScanGuard::force_full_scan and the copying minor is ineligible by construction. requires=scavenge STAYS: it is what the shipped default is FOR, the registry entry names what blocks it, and the liveness gate fails the day it scavenges again so the entry cannot outlive its cause. safepoint_minor carries the relocating claim meanwhile." +"default||%P%|scavenge|as-shipped GC configuration under allocation pressure. ***LIVE AGAIN AS OF 2026-08-09*** -- its known-inert entry was deleted in test-parity/gc_matrix_inert_arms.txt. #7024 made this a relocating arm (the alloc-point trigger defers to js_gc_loop_safepoint -> gc_safepoint_moving_minor, which runs the copying minor on precise rewritable roots); #7161 then flipped PERRY_GC_MOVING_LOOP_POLLS default-OFF pending #7154, and that one env gates BOTH halves of the route -- perry-codegen's moving_safepoint_polls_enabled decides whether the back-edge polls are emitted at all, and perry-runtime's gc_moving_loop_polls_enabled decides whether the trigger defers to them. A default binary has neither, so the minor runs behind ManualGcScanGuard::force_full_scan and the copying minor is ineligible by construction. requires=scavenge STAYS: it is what the shipped default is FOR, the registry entry names what blocks it, and the liveness gate fails the day it scavenges again so the entry cannot outlive its cause. safepoint_minor carries the relocating claim meanwhile." "safepoint_minor|PERRY_GC_MOVING_LOOP_POLLS=1|%P% PERRY_GC_MOVING_LOOP_POLLS=1|scavenge|THE SOUND RELOCATING ARM, and what keeps the #6993 defect class reachable per-PR while #7161's stopgap holds. Sets the poll flag at BOTH compile and run time (same env on both sides -- keyed into the object cache as env_gc_moving_loop_polls, so a warm cache cannot serve poll-free objects). The copying minor then runs at js_gc_loop_safepoint -> gc_safepoint_moving_minor, where the loop body has completed and every live heap value is a named local on the shadow stack: precise, rewritable roots. No %E%, no force -- this is exactly what default was between #7024 and #7161, and what default becomes again when the stopgap lifts. NOT a replacement for the %E% arms: a back-edge poll only fires while user JS runs, so it cannot expose an unrooted local inside runtime code that never re-enters user JS (#7249). evac_minor and force_verify remain in the PR subset for that." "evac_minor||%P% %E%|move|THE evacuating arm, and the STRONGER acceptance route (#7249): the automatic alloc-point collection as a COPYING minor that relocates survivors at a register-imprecise point, which is where an unrooted runtime-side local is exposed. No stress knob -- this is the collector's own moving path." "force_evac||%P% %E% PERRY_GC_FORCE_EVACUATE=1|move|stress-copy every marked non-pinned nursery object" -"verify_evac||%P% PERRY_GC_VERIFY_EVACUATION=1|scavenge|panic if a live slot still points at a forwarded object. requires=scavenge: a verifier that runs over zero relocations verifies nothing. REGISTERED KNOWN-INERT (#7161) -- same route as default, same blocker, and the same reason the declaration is not being weakened to hide it." +"verify_evac||%P% PERRY_GC_VERIFY_EVACUATION=1|scavenge|panic if a live slot still points at a forwarded object. requires=scavenge: a verifier that runs over zero relocations verifies nothing. LIVE AGAIN as of 2026-08-09 (41/58 cells in CI run 31240304595); its #7161 known-inert entry is deleted." "force_verify||%P% %E% PERRY_GC_FORCE_EVACUATE=1 PERRY_GC_VERIFY_EVACUATION=1|move|force + verify" "gen_gc_off||%P% PERRY_GEN_GC=0|collect|full mark-sweep only; no nursery => no evacuation by construction" "wb_off|PERRY_WRITE_BARRIERS=0|%P% PERRY_WRITE_BARRIERS=0|collect|no codegen write barriers => copying nursery ineligible by construction" "gen_off_verify||%P% PERRY_GEN_GC=0 PERRY_GC_VERIFY_EVACUATION=1|collect|full mark-sweep + evacuation verifier" "wb_off_force|PERRY_WRITE_BARRIERS=0|%P% PERRY_WRITE_BARRIERS=0 PERRY_GC_FORCE_EVACUATE=1|collect|force-evacuate is a documented no-op without barriers (barriers_inactive)" "all_four|PERRY_WRITE_BARRIERS=0|%P% PERRY_GEN_GC=0 PERRY_WRITE_BARRIERS=0 PERRY_GC_FORCE_EVACUATE=1 PERRY_GC_VERIFY_EVACUATION=1|collect|every escape hatch at once" -"cons_scan_off||%P% PERRY_CONSERVATIVE_STACK_SCAN=off|scavenge|PRECISE ROOTS ONLY -- removes the conservative-stack pinning that the alloc-point fallback otherwise forces (ManualGcScanGuard::force_full_scan). An arm that can observe a missing shadow-slot binding. REGISTERED KNOWN-INERT (#7161): precise roots beat that guard, but with the incremental stepper at its default the nursery trigger never reaches the direct arm in the first place -- registered_root_scanners_block_budgeted_gc() reduces to 'any copy-only scanner' under gc_incremental_enabled(), a compiled program has none, so the trigger goes to the budgeted stepper, which is non-moving by construction. Adding PERRY_GC_INCREMENTAL=0 is what turns it live, and that arm is evac_minor." -"cons_scan_off_force||%P% PERRY_CONSERVATIVE_STACK_SCAN=off PERRY_GC_FORCE_EVACUATE=1 PERRY_GC_VERIFY_EVACUATION=1|scavenge|precise roots + force/verify evacuation. REGISTERED KNOWN-INERT (#7161), same reason as cons_scan_off: PERRY_GC_FORCE_EVACUATE is read on a minor path this arm never reaches, which is the #6942/#6946 shape exactly." +"cons_scan_off||%P% PERRY_CONSERVATIVE_STACK_SCAN=off|scavenge|PRECISE ROOTS ONLY -- removes the conservative-stack pinning that the alloc-point fallback otherwise forces (ManualGcScanGuard::force_full_scan). An arm that can observe a missing shadow-slot binding. WAS registered known-inert (#7161), deleted 2026-08-09 after it scavenged on 41/58 cells: the argument was that precise roots beat that guard but the nursery trigger never reaches the direct arm in the first place -- registered_root_scanners_block_budgeted_gc() reduces to 'any copy-only scanner' under gc_incremental_enabled(), a compiled program has none, so the trigger goes to the budgeted stepper, which is non-moving by construction. Adding PERRY_GC_INCREMENTAL=0 is what turns it live, and that arm is evac_minor." +"cons_scan_off_force||%P% PERRY_CONSERVATIVE_STACK_SCAN=off PERRY_GC_FORCE_EVACUATE=1 PERRY_GC_VERIFY_EVACUATION=1|scavenge|precise roots + force/verify evacuation. was registered known-inert (#7161) on the same argument as cons_scan_off; deleted 2026-08-09 when it, too, scavenged. The #6942/#6946 shape it was said to have -- PERRY_GC_FORCE_EVACUATE read on a minor path the arm never reaches -- no longer applies, because the arm reaches it." "loop_polls|PERRY_GC_MOVING_LOOP_POLLS=1|%P% %E% PERRY_GC_MOVING_LOOP_POLLS=1 PERRY_GC_FORCE_EVACUATE=1|move|defer the alloc-point collection to a loop back-edge precise-root safepoint, where the copying minor may MOVE survivors" "rep_i32_off|PERRY_CANONICAL_I32_LOCALS=0|%P% %E% PERRY_GC_FORCE_EVACUATE=1|move|repsel Phase 1 OFF x evacuation" "rep_str_off|PERRY_CANONICAL_STR_LOCALS=0|%P% %E% PERRY_GC_FORCE_EVACUATE=1|move|repsel Phase 3a OFF x evacuation" @@ -414,7 +418,7 @@ triage_reason() { # $1 test, $2 arm END { exit(found ? 0 : 1) }' } -CELLS=(); EVID=(); CYC=(); EVA=(); SCA=() +CELLS=(); EVID=(); CYC=(); EVA=(); SCA=(); REC=() n_pass=0; n_unver=0; n_fail=0; n_xfail=0 ai=0 while [ "$ai" -lt "$NARMS" ]; do @@ -459,9 +463,48 @@ while [ "$ai" -lt "$NARMS" ]; do | grep -oE '[0-9]+$' | awk '{s+=$1} END {print s+0}') scavenged=$(grep -oE '\[gc-copy-minor\] ran copied_objects=[0-9]+' "$WORK/out/$b.$id.err" 2>/dev/null \ | grep -oE '[0-9]+$' | awk '{s+=$1} END {print s+0}') - : "${cycles:=0}"; : "${evacuated:=0}"; : "${scavenged:=0}" + # #7017: `cycles>0` cannot tell a mid-program collection from a + # teardown one. On a small corpus file the shipped configuration + # completes exactly one cycle, at the event-loop boundary AFTER the + # program's last output, and it reclaims NOTHING -- everything + # allocated after the cycle armed was born black: + # + # 45:8 <- last program output + # [gc-step] pre_in_use=479816 post_in_use=483472 sweep_freed=0 ... + # [gc] cycle + # + # Both shapes scored PASS. A cycle that traversed only the retained + # graph at rest cannot have observed the test's live locals, which + # is the property the matrix was built to assert -- the #6942 / + # #6946 / #6950 lesson, one level up. + # + # So `collect` now requires a PRODUCTIVE cycle. Like the gc-ratchet + # rule widened in #7657 (`copied + promoted > 0`, because pinning it + # to one counter would have pinned it permanently false), this names + # a DESTINATION rather than a counter, and sums every reclamation + # counter the run emits: + # + # sweep_freed / block_reclaim [gc-step] (PERRY_GC_DIAG) + # eden_dead_bytes [gc-tenuring] (PERRY_GC_DIAG) + # freed_bytes [gc] blocks: (PERRY_GC_DIAG) + # "freed_bytes"/"dead_bytes" JSON trace (diagnostics build) + # + # BOTH spellings are read, `k=N` and `"k": N`. Only the JSON form + # carries the malloc sweep's bytes, and reading only `k=N` scored + # `test_gap_gc_symbol_local_rooting` -- 86 malloc-count-triggered + # cycles that free 31.9 MB of symbols -- as reclaiming zero. + # + # This is a conservative proxy and says so: a mid-program cycle over + # a heap that is entirely live reclaims nothing and reads UNVER. It + # can under-claim, never over-claim, which is the safe direction for + # a liveness gate. Do not weaken it to `cycles>0` to make a cell + # green -- that is the state #7017 was filed about. + reclaimed=$(grep -ohE '(sweep_freed|block_reclaim|eden_dead_bytes|freed_bytes|dead_bytes)("?[:=] ?)[0-9]+' \ + "$WORK/out/$b.$id.err" 2>/dev/null \ + | grep -oE '[0-9]+$' | awk '{s+=$1} END {print s+0}') + : "${cycles:=0}"; : "${evacuated:=0}"; : "${scavenged:=0}"; : "${reclaimed:=0}" moved=$((evacuated + scavenged)) - ev="cycles=$cycles evacuated=$evacuated scavenged=$scavenged" + ev="cycles=$cycles evacuated=$evacuated scavenged=$scavenged reclaimed=$reclaimed" if [ "$rc" -ne 0 ]; then result="FAIL"; ev="exit=$rc $ev" elif ! cmp -s "$WORK/out/$b.$id.out" "$WORK/oracle/$b.out"; then @@ -473,7 +516,11 @@ while [ "$ai" -lt "$NARMS" ]; do # must not go green on a C4b mark-sweep evacuation. scavenge) [ "$scavenged" -gt 0 ] && result="PASS" || result="UNVER" ;; move) [ "$moved" -gt 0 ] && result="PASS" || result="UNVER" ;; - collect) [ "$cycles" -gt 0 ] && result="PASS" || result="UNVER" ;; + # #7017: a cycle, AND that cycle reclaimed something. See + # the `reclaimed=` derivation above for why the counter is + # not `cycles`. + collect) [ "$cycles" -gt 0 ] && [ "$reclaimed" -gt 0 ] \ + && result="PASS" || result="UNVER" ;; *) result="PASS" ;; esac fi @@ -487,6 +534,7 @@ while [ "$ai" -lt "$NARMS" ]; do # The liveness gate reads these as NUMBERS, not by re-parsing `$ev`: # a triage reason is free text and has already contained `=`. CYC[$idx]="$cycles"; EVA[$idx]="$evacuated"; SCA[$idx]="$scavenged" + REC[$idx]="$reclaimed" case "$result" in PASS) n_pass=$((n_pass+1)) ;; UNVER) n_unver=$((n_unver+1)) ;; @@ -528,24 +576,33 @@ echo echo "arm liveness across the corpus (cells where the arm actually bit):" ai=0 while [ "$ai" -lt "$NARMS" ]; do - tot=0; livec=0; livem=0; lives=0; ti=0 + tot=0; livec=0; livem=0; lives=0; liver=0; ti=0 while [ "$ti" -lt "${#CORPUS[@]}" ]; do - ev="${EVID[$((ti*NARMS+ai))]:-}" - cy="$(printf '%s' "$ev" | sed -nE 's/.*cycles=([0-9]+).*/\1/p')" - evac="$(printf '%s' "$ev" | sed -nE 's/.*evacuated=([0-9]+).*/\1/p')" - scav="$(printf '%s' "$ev" | sed -nE 's/.*scavenged=([0-9]+).*/\1/p')" + idx=$((ti*NARMS+ai)) + # Read the NUMBERS, not the free-text evidence: a triage reason has + # already contained `=` (the reason CYC/EVA/SCA/REC exist). + cy="${CYC[$idx]:-0}"; evac="${EVA[$idx]:-0}"; scav="${SCA[$idx]:-0}" + recl="${REC[$idx]:-0}" tot=$((tot+1)) [ "${cy:-0}" -gt 0 ] 2>/dev/null && livec=$((livec+1)) [ $(( ${evac:-0} + ${scav:-0} )) -gt 0 ] 2>/dev/null && livem=$((livem+1)) [ "${scav:-0}" -gt 0 ] 2>/dev/null && lives=$((lives+1)) + [ "${recl:-0}" -gt 0 ] 2>/dev/null && liver=$((liver+1)) ti=$((ti+1)) done # #7025: `copy-minor` is reported separately from `moved-objects` because the # latter counts BOTH collectors. An evacuating arm showing a healthy # moved-objects count but `copy-minor 0/N` did not run the path it exists to # test -- that is the shape #7024 describes, and summing the two hid it. - printf ' %-24s requires=%-8s collected %2d/%2d moved-objects %2d/%2d copy-minor %2d/%2d\n' \ - "${ARM_IDS[$ai]}" "${ARM_LIVES[$ai]}" "$livec" "$tot" "$livem" "$tot" "$lives" "$tot" + # #7017: `reclaimed` is reported next to `collected` because they differ, + # and the difference is the whole finding. A cell can collect and reclaim + # nothing -- that is a cycle at the event-loop boundary after the program's + # last output, which cannot have observed the test's live locals. `collect` + # arms are scored on `reclaimed`; `collected` is kept beside it so the gap + # stays visible instead of being folded away. + printf ' %-24s requires=%-8s collected %2d/%2d reclaimed %2d/%2d moved-objects %2d/%2d copy-minor %2d/%2d\n' \ + "${ARM_IDS[$ai]}" "${ARM_LIVES[$ai]}" "$livec" "$tot" "$liver" "$tot" \ + "$livem" "$tot" "$lives" "$tot" ai=$((ai+1)) done @@ -587,9 +644,9 @@ JSON_REPORT="${JSON_OUT:-$WORK/matrix.json}" # characters that would make this invalid JSON, so a malformed # report can never be the reason the gate fails. ev_json="$(printf '%s' "${EVID[$idx]:-}" | tr '"\\' "''")" - printf '{"test":"%s","arm":"%s","result":"%s","cycles":%d,"evacuated":%d,"scavenged":%d,"evidence":"%s"}' \ + printf '{"test":"%s","arm":"%s","result":"%s","cycles":%d,"evacuated":%d,"scavenged":%d,"reclaimed":%d,"evidence":"%s"}' \ "${CORPUS[$ti]}" "${ARM_IDS[$ai]}" "${CELLS[$idx]:-?}" \ - "${CYC[$idx]:-0}" "${EVA[$idx]:-0}" "${SCA[$idx]:-0}" "$ev_json" + "${CYC[$idx]:-0}" "${EVA[$idx]:-0}" "${SCA[$idx]:-0}" "${REC[$idx]:-0}" "$ev_json" ai=$((ai+1)) done ti=$((ti+1)) diff --git a/test-parity/gc_matrix_inert_arms.txt b/test-parity/gc_matrix_inert_arms.txt index bee95645fd..07608c9e13 100644 --- a/test-parity/gc_matrix_inert_arms.txt +++ b/test-parity/gc_matrix_inert_arms.txt @@ -20,23 +20,37 @@ # DO NOT add an entry to make a table green. An arm that went quiet without # anyone deciding it should is the defect this file was created to surface. -# --- #7161's stopgap: the shipped default's minor is NON-MOVING ------------- -# -# #7019 made the evacuating young-gen minor the default and #7024 made it -# reachable under the pressure knob; `PERRY_GC_MOVING_LOOP_POLLS` is the gate for -# BOTH halves of that route — perry-codegen's `moving_safepoint_polls_enabled` -# decides whether `js_gc_loop_safepoint` back-edge polls are emitted at all, and -# perry-runtime's `gc_moving_loop_polls_enabled` decides whether the alloc-point -# nursery trigger defers to them. #7161 flipped it default-OFF pending #7154 -# (a use-after-free that corrupts the heap in the DEFAULT config). #7154 itself -# is closed, but its last measurement says the stopgap stays until the -# from-space protector is clean, so a default binary has no back-edge polls and -# its alloc-point minor runs behind a forced conservative scan, which makes the -# copying minor ineligible by construction. -# -# DELETE ALL FOUR when `PERRY_GC_MOVING_LOOP_POLLS` goes default-ON again. The -# liveness gate will demand it: the entries go stale the moment the arms bite. -default | #7161 | Shipped config + pressure. No back-edge polls are emitted into a default binary and the runtime does not defer to them, so the #7024 route (alloc-point trigger -> js_gc_loop_safepoint -> gc_safepoint_moving_minor) does not exist. It still COLLECTS; it cannot scavenge. `safepoint_minor` carries the relocating claim while this is listed. -verify_evac | #7161 | Same route as `default`, plus PERRY_GC_VERIFY_EVACUATION=1. A verifier that runs over zero relocations verifies nothing — which is exactly what it does today. -cons_scan_off | #7161 | Precise-roots-only (PERRY_CONSERVATIVE_STACK_SCAN=off) beats the alloc-point `force_full_scan`, but with the incremental stepper at its default the nursery trigger never reaches the direct arm: `registered_root_scanners_block_budgeted_gc()` reduces to "any copy-only scanner" under `gc_incremental_enabled()`, and a compiled program has none, so the trigger is handed to the budgeted stepper, which is non-moving by construction (`low_pause_non_moving = is_budgeted()`). With polls off there is no deferral to a safepoint either. Adding PERRY_GC_INCREMENTAL=0 is what turns it live — that arm is `evac_minor`. -cons_scan_off_force | #7161 | Same as `cons_scan_off`, plus force/verify. PERRY_GC_FORCE_EVACUATE is read on the minor path the arm never reaches (#6942/#6946), so the force knob does not rescue it. +# --- RETIRED 2026-08-09: #7161's four entries, deleted because they bit ------ +# +# `default`, `verify_evac`, `cons_scan_off` and `cons_scan_off_force` were +# registered here on the ground that `PERRY_GC_MOVING_LOOP_POLLS` is default-OFF +# (#7161's stopgap for #7154), so a default binary has no back-edge polls and its +# alloc-point minor runs behind a forced conservative scan, making the copying +# minor "ineligible by construction". +# +# ***THAT MECHANISM NO LONGER HOLDS, AND THE POLL FLAG DID NOT CHANGE.*** All +# four arms now scavenge. Measured three ways, on files nobody edited to make it +# so: +# +# * CI, `gc-stress` on `main`, run 31240304595 (2026-08-08): each of the four +# satisfied `requires=scavenge` on **41 of 58 cells**, `default` at +# `counter=1384046`. The gate has been reporting STALE-REGISTRY, and +# `gc-stress` has been red for it, since at least that run. +# * locally, `--arms default,verify_evac,cons_scan_off,cons_scan_off_force +# --filter test_gap_repsel_gc_stress`: 1/1 live on every arm, +# copy-minor 1/1, `default` copying 228,181 objects. +# * `shipped_default` (requires=none, no pressure knob, no GC env at all) +# scavenges 340,956 objects on the same file. The SHIPPED configuration +# relocates today. +# +# What changed is not the poll flag but the collector around it (statepoint root +# lowering became the default in #7370; #7432's adaptive tenuring; #7657's seed; +# #7666's escalation floor). The entries' stated cause outlived its truth by the +# same mechanism the header above describes for the prose it replaced — which is +# precisely why the gate is bidirectional, and why these are deleted rather than +# re-worded. +# +# All four are already in PR_ARMS except `cons_scan_off_force`; none needed to be +# put back. If the copying minor ever stops running in the shipped configuration +# again, the gate fails in the OTHER direction and says so on the same line. + From 9f6f236551df2140f7383be88be3efa1efcd7aa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 07:41:54 +0200 Subject: [PATCH 3/4] docs(changelog): key the gc-matrix fragment to its PR number Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- ...rix-collect-liveness.md => 7676-gc-matrix-collect-liveness.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{7675-gc-matrix-collect-liveness.md => 7676-gc-matrix-collect-liveness.md} (100%) diff --git a/changelog.d/7675-gc-matrix-collect-liveness.md b/changelog.d/7676-gc-matrix-collect-liveness.md similarity index 100% rename from changelog.d/7675-gc-matrix-collect-liveness.md rename to changelog.d/7676-gc-matrix-collect-liveness.md From 7ae4ea7bcd673a3c5c2f2aee36ffecb632198362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 08:51:55 +0200 Subject: [PATCH 4/4] chore: bump version to 0.5.1388 Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- CLAUDE.md | 2 +- Cargo.lock | 152 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index fa43b90fdf..e210a62376 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1387 +**Current Version:** 0.5.1388 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 89fab40384..e11ba57e7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5547,7 +5547,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "base64", @@ -5607,14 +5607,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "cc", "libc", @@ -5622,7 +5622,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "inkwell", @@ -5639,7 +5639,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "perry-hir", @@ -5647,7 +5647,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "perry-hir", @@ -5655,7 +5655,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "perry-dispatch", @@ -5664,7 +5664,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "perry-hir", @@ -5672,7 +5672,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "base64", @@ -5684,7 +5684,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "perry-hir", @@ -5692,7 +5692,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "async-trait", @@ -5721,14 +5721,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "serde", "serde_json", @@ -5736,7 +5736,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1387" +version = "0.5.1388" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5747,7 +5747,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "clap", @@ -5762,7 +5762,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "block2", "objc2", @@ -5772,7 +5772,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "argon2", "perry-ffi", @@ -5780,7 +5780,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "reqwest", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "bcrypt", "perry-ffi", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "rusqlite", @@ -5805,7 +5805,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "scraper", @@ -5813,7 +5813,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "perry-runtime", @@ -5821,7 +5821,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "chrono", "cron", @@ -5831,7 +5831,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "chrono", "perry-ffi", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "rust_decimal", @@ -5847,7 +5847,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "serde_json", @@ -5855,7 +5855,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "perry-runtime", @@ -5871,14 +5871,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "bytes", "http-body-util", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "bytes", "lazy_static", @@ -5909,7 +5909,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "bytes", "h2", @@ -5933,7 +5933,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "lazy_static", "perry-ffi", @@ -5943,7 +5943,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "base64", "jsonwebtoken", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "lru", "perry-ffi", @@ -5963,7 +5963,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "chrono", "perry-ffi", @@ -5971,7 +5971,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "bson", "futures-util", @@ -5983,7 +5983,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "chrono", "perry-ffi", @@ -5993,7 +5993,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "nanoid", "perry-ffi", @@ -6002,7 +6002,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "bytes", "perry-ffi", @@ -6015,7 +6015,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6034,7 +6034,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "lettre", "perry-ffi", @@ -6044,7 +6044,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "printpdf", @@ -6052,7 +6052,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "sqlx", @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "governor", "perry-ffi", @@ -6069,7 +6069,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "fast_image_resize", "image", @@ -6079,14 +6079,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "lazy_static", "perry-ffi", @@ -6095,7 +6095,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "perry-runtime", @@ -6104,7 +6104,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "uuid", @@ -6112,7 +6112,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ffi", "regex", @@ -6122,7 +6122,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "futures-util", "lazy_static", @@ -6135,7 +6135,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "brotli", "flate2", @@ -6145,7 +6145,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "dashmap", "once_cell", @@ -6154,7 +6154,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "perry-api-manifest", @@ -6172,7 +6172,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "perry-diagnostics", @@ -6184,7 +6184,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "base64", @@ -6226,14 +6226,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6328,14 +6328,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "perry-hir", @@ -6344,14 +6344,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "base64", "itoa", @@ -6368,7 +6368,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "rand 0.10.1", "serde", @@ -6378,7 +6378,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6401,7 +6401,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "base64", "block2", @@ -6417,7 +6417,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "base64", "block2", @@ -6432,7 +6432,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1387" +version = "0.5.1388" [[package]] name = "perry-ui-test" @@ -6443,11 +6443,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1387" +version = "0.5.1388" [[package]] name = "perry-ui-tvos" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "base64", "block2", @@ -6463,7 +6463,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "base64", "block2", @@ -6479,7 +6479,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "block2", "libc", @@ -6492,7 +6492,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "base64", "libc", @@ -6509,14 +6509,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "anyhow", "base64", @@ -6532,7 +6532,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1387" +version = "0.5.1388" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 7ce89f6827..1b1079b63e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1387" +version = "0.5.1388" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry"