From e9e4b0a2b5d14e140915270c28df57665b627770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 17:31:12 +0200 Subject: [PATCH 1/6] fix(gc): make the moving-loop poll default ON in the code, not just the doc (#7690) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #7690 wrote the entire default-ON argument into two doc comments — the runtime's `moving_loop_polls_enabled_from_env` and codegen's `moving_safepoint_polls_enabled` — and changed neither body. Both still matched `1|on|true`, i.e. default OFF, and no test pinned the default in either direction, even though the runtime predicate had been factored out expressly to make it "unit-testable without touching process env". That is not a slower configuration, it is a different collector. Nursery pressure has exactly two precise collection points, the loop back-edge poll and the outermost microtask-pump boundary. With no poll emitted, a compute-only program reaches neither, so every nursery collection happened at the register-imprecise allocation point — where #7687 had just made it correctly non-moving. The shipped result was a collector with no nursery evacuation at all. Measured on the quiet bench host, best-of-3, `PERRY_NO_AUTO_OPTIMIZE=1` with a pinned `PERRY_RUNTIME_DIR`, against `a853135aa` binaries rerun back-to-back on the same host: | bench | main | this | a853135aa | |---|--:|--:|--:| | churn | 1.01 | 0.45 | 0.66 | | churn_alloc | 0.90 | 0.42 | 0.36 | | push_cls | 0.89 | 0.40 | 0.34 | | retain | 2.33 | 1.37 | 1.33 | | tree | 5.06 | 1.63 | 5.97 | | tree_wide | 7.26 | 2.11 | 12.38 | | cycles | 0.29 | 0.19 | 0.96 | `churn_alloc` ran 13 whole-arena full collections (0.477 s of pause) where the same program at `a853135aa` ran 105 copying minors (0.016 s). `tree`'s GC pause falls 4.107 s -> 0.626 s and its max pause 266 ms -> 23 ms; `trace_worklist` drops from 2,877 ms out of the top six phases entirely. The #7161 blocker that made polls-off a stopgap is separately discharged: a poll at every back-edge defeated the #7480 element-shape fast clone, and step 4 of that work now refuses to emit a poll inside a call-free-by-construction clone. Measured both ways, `churn_read` is 0.02 s. Costs, measured rather than argued: `deeplist` 0.03 -> 0.33 and `retain1` 0.03 -> 0.42. Both are workloads whose heap stays under the initial 64 MB threshold, so they previously ran ZERO collections and the moving nursery is pure added cost; both still beat `a853135aa` (1.09 / —). `push_num` 0.16 -> 0.17. Three tests pin what was unpinned: `polls_default_is_on` and its codegen mirror `moving_safepoint_poll_default::unset_emits_the_poll` each pin one half against the full spelling table, and `polls_default_matches_codegen_mirror` pins that the two crates agree — the disagreement is silent in both directions, so it needs its own assertion rather than being left to two doc comments claiming they match. --- crates/perry-codegen/src/stmt/loops.rs | 46 +++++++++++- crates/perry-runtime/src/gc/policy.rs | 32 +++++---- crates/perry-runtime/src/gc/tests/triggers.rs | 70 +++++++++++++++++++ 3 files changed, 132 insertions(+), 16 deletions(-) diff --git a/crates/perry-codegen/src/stmt/loops.rs b/crates/perry-codegen/src/stmt/loops.rs index 22ee1a9624..12dc8c2913 100644 --- a/crates/perry-codegen/src/stmt/loops.rs +++ b/crates/perry-codegen/src/stmt/loops.rs @@ -5277,13 +5277,53 @@ fn moving_safepoint_polls_enabled() -> bool { use std::sync::OnceLock; static CACHED: OnceLock = OnceLock::new(); *CACHED.get_or_init(|| { - matches!( - std::env::var("PERRY_GC_MOVING_LOOP_POLLS").as_deref(), - Ok("1") | Ok("on") | Ok("true") + moving_safepoint_polls_enabled_from_env( + std::env::var("PERRY_GC_MOVING_LOOP_POLLS").ok().as_deref(), ) }) } +/// Pure env→emit decision, factored out so the default is testable without +/// touching process env or the cached `OnceLock`. +/// +/// **Byte-for-byte the same predicate as the runtime's +/// `policy::moving_loop_polls_enabled_from_env`.** They are two crates and +/// cannot share a symbol, so `polls_default_matches_codegen_mirror` in +/// perry-runtime pins the pair against a copy of this table instead. Keep the +/// two in step: a codegen default of OFF against a runtime default of ON is not +/// a lost optimisation, it is a collector that defers nursery pressure to a +/// safepoint that is never emitted (see #7690's regression). +pub(crate) fn moving_safepoint_polls_enabled_from_env(value: Option<&str>) -> bool { + !matches!(value, Some("0") | Some("off") | Some("false")) +} + +#[cfg(test)] +mod moving_safepoint_poll_default { + use super::moving_safepoint_polls_enabled_from_env as emit; + + /// Pins codegen's half of the pair. The runtime half is + /// `gc::tests::triggers::polls_default_is_on`, and + /// `polls_default_matches_codegen_mirror` pins that the two tables agree. + /// + /// Emitting no poll is not "one fewer instruction": it removes the only + /// precise safepoint a compute-only loop ever reaches, so a nursery + /// collection the runtime deferred has nowhere to drain. + #[test] + fn unset_emits_the_poll() { + assert!(emit(None), "unset must emit the loop safepoint poll"); + for kill in ["0", "off", "false"] { + assert!(!emit(Some(kill)), "{kill} must remain the kill switch"); + } + for on in ["1", "on", "true"] { + assert!(emit(Some(on)), "{on} must stay an explicit opt-in"); + } + assert!( + emit(Some("yes")), + "only the documented kill-switch spellings may suppress the poll" + ); + } +} + /// Emit a `js_gc_loop_safepoint()` after an allocating loop segment has /// completed and only where the block is not terminated. Body calls must run /// after `clear_loop_body_shadow_slots`; control calls run after their result diff --git a/crates/perry-runtime/src/gc/policy.rs b/crates/perry-runtime/src/gc/policy.rs index 5b21ae88ce..8c6a43ab0a 100644 --- a/crates/perry-runtime/src/gc/policy.rs +++ b/crates/perry-runtime/src/gc/policy.rs @@ -474,22 +474,22 @@ pub(crate) fn gc_incremental_enabled() -> bool { /// of collecting non-moving mid-expression, so reallocation-heavy loops evacuate /// (bounded RSS) instead of leaking. /// -/// **DEFAULT OFF (stopgap for #7154).** This was flipped default-ON in #7019, but -/// the evacuating minor it makes primary has a use-after-free: a young closure -/// referenced from a dynamically-added object field (`field[1]`, holders built in -/// `proxy::create_or_update_receiver_property`) is reclaimed while still live, so -/// the field dangles and a later call dies with `TypeError: value is not a -/// function`. This reproduces in the DEFAULT config (no env) — the shipped binary -/// corrupts the heap. `PERRY_GC_MOVING_LOOP_POLLS=0` is confirmed to eliminate it, -/// so until #7154 is root-caused we default OFF (restoring the previously-correct -/// non-moving minor) and keep the moving-loop path behind an explicit -/// `PERRY_GC_MOVING_LOOP_POLLS=1`/`on`/`true` opt-in. Reverting the default costs -/// #7019's minor-GC RSS/throughput win but not correctness. +/// **DEFAULT ON.** The kill switch is `PERRY_GC_MOVING_LOOP_POLLS=0`/`off`/`false`. +/// See [`moving_loop_polls_enabled_from_env`] for the decision and its evidence; +/// #7161's stopgap default-OFF (pending #7154) is discharged there. /// /// MUST match codegen `moving_safepoint_polls_enabled` (same env) so the deferral /// and the polls that drain it stay coherent — a runtime default that disagrees /// with the codegen default would defer collections that never drain (or drain -/// collections that were never deferred). +/// collections that were never deferred). That disagreement is not hypothetical: +/// it shipped. #7690 wrote the default-ON argument into the doc below and left +/// both bodies matching `1|on|true`, so the runtime deferred nursery pressure to +/// a safepoint codegen never emitted. Combined with #7687 (the alloc-point minor +/// must not move), the shipped collector had NO nursery evacuation at all — +/// `churn_alloc` ran 13 whole-arena full collections where it had run 105 copying +/// minors, and `tree` spent 4.1 s of its 5.1 s wall in GC pause. Both predicates +/// are now pinned by tests, and `polls_default_matches_codegen_mirror` pins that +/// they agree. pub(crate) fn gc_moving_loop_polls_enabled() -> bool { // Test-only mode override (see `force_legacy_gc_pacing`). Consulted BEFORE // the process-wide OnceLock so a single test can pin a specific pacing mode @@ -538,8 +538,14 @@ pub(crate) fn gc_moving_loop_polls_enabled() -> bool { /// collection therefore happened at the register-imprecise allocation point, /// where #7682 showed it must not move. So "polls off" does not mean "collect /// later, precisely"; it means "never collect precisely at all". +/// +/// #7690 wrote every paragraph above and then did not change this line, and +/// nothing failed — the function was factored out expressly to make the default +/// "unit-testable without touching process env", and no test ever pinned it in +/// either direction. `polls_default_is_on` and its codegen mirror exist so that +/// the next edit to this predicate has to be deliberate. pub(crate) fn moving_loop_polls_enabled_from_env(value: Option<&str>) -> bool { - matches!(value, Some("1") | Some("on") | Some("true")) + !matches!(value, Some("0") | Some("off") | Some("false")) } #[cfg(test)] diff --git a/crates/perry-runtime/src/gc/tests/triggers.rs b/crates/perry-runtime/src/gc/tests/triggers.rs index 404582ee3d..06642565a8 100644 --- a/crates/perry-runtime/src/gc/tests/triggers.rs +++ b/crates/perry-runtime/src/gc/tests/triggers.rs @@ -597,3 +597,73 @@ fn test_memory_pressure_collects_and_clamps_trigger() { GC_TRIGGER_ARMED.with(|c| c.set(prev_armed)); assert_eq!(js_gc_memory_pressure(0), 0); } + +/// #7690 shipped its entire default-ON argument in doc comments and left the +/// predicate matching `1|on|true`. Nothing failed, because the function that was +/// factored out expressly to make the default "unit-testable without touching +/// process env" had no test. This is that test. +/// +/// A default of OFF is not a slower configuration, it is a different collector: +/// with no poll there is no precise safepoint, so after #7687 made the +/// alloc-point minor non-moving the nursery is never evacuated at all. Measured +/// on the quiet bench host at `12e48edd6`, `churn_alloc` ran 13 whole-arena full +/// collections (0.477 s of pause) where the same program at `a853135aa` ran 105 +/// copying minors (0.016 s), and `tree` spent 4.1 s of a 5.1 s wall in GC. +#[test] +fn polls_default_is_on() { + assert!( + moving_loop_polls_enabled_from_env(None), + "unset PERRY_GC_MOVING_LOOP_POLLS must select the moving-loop path — \ + a default of OFF leaves the nursery with no precise safepoint to evacuate at" + ); + for kill in ["0", "off", "false"] { + assert!( + !moving_loop_polls_enabled_from_env(Some(kill)), + "PERRY_GC_MOVING_LOOP_POLLS={kill} must remain the kill switch" + ); + } + for on in ["1", "on", "true"] { + assert!( + moving_loop_polls_enabled_from_env(Some(on)), + "PERRY_GC_MOVING_LOOP_POLLS={on} must stay an explicit opt-in" + ); + } + // An unrecognised value is ON, like every other non-kill value. Spelled out + // because the pre-#7690 predicate answered OFF here, so this is the arm that + // silently changes meaning if the `matches!` is ever inverted back. + assert!( + moving_loop_polls_enabled_from_env(Some("yes")), + "only the documented kill-switch spellings may disable polls" + ); +} + +/// The runtime decides whether to DEFER a nursery collection; codegen decides +/// whether to emit the poll that DRAINS it. They read the same env var from two +/// crates that cannot share a symbol, and a disagreement is silent in both +/// directions — polls nothing consumes, or a deferral nothing drains. #7690 left +/// them agreeing at OFF while documenting ON; this pins them as a pair against a +/// copy of codegen's table. +#[test] +fn polls_default_matches_codegen_mirror() { + // Mirrors `perry_codegen::stmt::loops::moving_safepoint_polls_enabled_from_env`. + fn codegen_mirror(value: Option<&str>) -> bool { + !matches!(value, Some("0") | Some("off") | Some("false")) + } + for value in [ + None, + Some("0"), + Some("off"), + Some("false"), + Some("1"), + Some("on"), + Some("true"), + Some(""), + Some("yes"), + ] { + assert_eq!( + moving_loop_polls_enabled_from_env(value), + codegen_mirror(value), + "runtime and codegen must agree on PERRY_GC_MOVING_LOOP_POLLS={value:?}" + ); + } +} From 0a5abb70aec24a80931b937eb70ebed5c809834c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 17:37:26 +0200 Subject: [PATCH 2/6] perf(gc): stop paying a shape-layout hash lookup per traced object for a disabled counter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `heap_payload_slot_selection` runs once per traced object per GC walk (mark, rewrite, verify). For every GC_TYPE_OBJECT it computed `raw_numeric_object_slots` via `with_typed_descriptor_for_query` — a per-object map probe plus, for every class instance, a `SHAPE_LAYOUTS` hash lookup behind a TLS RefCell borrow. That number has exactly one consumer, `record_layout_raw_numeric_object_field_range_skipped`, which returns on its first line unless PERRY_GC_LAYOUT_SCAN_TRACE armed the counter. So the shipped collector paid a hash lookup per object to produce a number nothing read — the same shape as #7702, where a facility disabled at runtime was still having its arguments evaluated. Gate the computation on `layout_scan_trace_active()`. Second item, same walk: `shape_shared_pointer_mask` returned `shape_shared_descriptor(user_ptr).map(|d| d.pointer_mask)`, cloning the whole `TypedLayoutDescriptor` to keep one of its two masks. `LayoutSlotMask` is `Heap(Vec)` above 64 slots, so a traced wide object allocated and freed a second vector — the `raw_f64_mask` — on every walk. Borrow through `with_shape_shared_descriptor` and clone only the mask returned; `shape_shared_descriptor` had no other caller and is removed rather than left as dead code. --- crates/perry-runtime/src/gc/layout.rs | 34 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/crates/perry-runtime/src/gc/layout.rs b/crates/perry-runtime/src/gc/layout.rs index cc2f4894d4..aa627329de 100644 --- a/crates/perry-runtime/src/gc/layout.rs +++ b/crates/perry-runtime/src/gc/layout.rs @@ -432,13 +432,6 @@ unsafe fn with_shape_shared_descriptor( Some(f(desc)) } -/// Cloning form of [`with_shape_shared_descriptor`], for the callers that need -/// to keep the descriptor past the `SHAPE_LAYOUTS` borrow. -#[inline] -unsafe fn shape_shared_descriptor(user_ptr: usize) -> Option { - with_shape_shared_descriptor(user_ptr, |desc| desc.clone()) -} - /// Answer a *query* about `user_ptr`'s current canonical typed layout, whichever /// map holds it: the per-object `TYPED_LAYOUTS` entry (objects that diverged /// from their shape, or carry no keys_array), else — and only while the object @@ -492,7 +485,11 @@ unsafe fn shape_shared_pointer_mask( if (*header)._reserved & GC_OBJ_TYPED_LAYOUT_INTACT == 0 { return None; } - shape_shared_descriptor(user_ptr).map(|d| d.pointer_mask) + // Clone the ONE mask we return, not the whole descriptor. `LayoutSlotMask` + // is `Heap(Vec)` above 64 slots, so `shape_shared_descriptor`'s + // `desc.clone()` allocated and freed a second vector — the `raw_f64_mask` + // we immediately drop — once per traced wide object per GC walk. + with_shape_shared_descriptor(user_ptr, |d| d.pointer_mask.clone()) } /// Install `descriptor` as the canonical layout for `keys` and set the object's @@ -1712,11 +1709,22 @@ pub(super) unsafe fn heap_payload_slot_selection( return HeapPayloadSlotSelection::Empty; } let user_ptr = (header as *mut u8).add(GC_HEADER_SIZE) as usize; - let raw_numeric_object_slots = if (*header).obj_type == GC_TYPE_OBJECT { - layout_typed_raw_f64_slot_count_for_user(user_ptr, payload.slot_count()) - } else { - 0 - }; + // `raw_numeric_object_slots` feeds exactly one consumer: + // `record_layout_raw_numeric_object_field_range_skipped`, a counter that + // returns on its first line unless `PERRY_GC_LAYOUT_SCAN_TRACE` armed + // `layout_scan_trace_active()`. Computing it costs + // `with_typed_descriptor_for_query` — a per-object map probe and, for every + // class instance, a `SHAPE_LAYOUTS` hash lookup behind a TLS `RefCell` + // borrow — and this function runs once per traced object per GC walk + // (mark, rewrite, verify). So the shipped collector paid a hash lookup per + // object to produce a number nothing read. Same shape as #7702: a facility + // already disabled at runtime, whose *argument* was still being evaluated. + let raw_numeric_object_slots = + if (*header).obj_type == GC_TYPE_OBJECT && layout_scan_trace_active() { + layout_typed_raw_f64_slot_count_for_user(user_ptr, payload.slot_count()) + } else { + 0 + }; match (*header)._reserved & GC_LAYOUT_STATE_MASK { GC_LAYOUT_POINTER_FREE => HeapPayloadSlotSelection::PointerFree { emitted: false, From cc4fb534d2a53cb4501f6def7ac14d3955511e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 18:22:20 +0200 Subject: [PATCH 3/6] test(gc): declare the pacing the alloc-point rooting tests actually assert at MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four `runtime_roots` tests took no pacing guard, so they inherited the process default — which this stack changes. They are not asserting about the default; they are asserting that a specific runtime helper's object survives a collection that happens at the allocation point, and they reach that collection through the direct alloc-point minor. Under moving-loop polls that pressure is deferred to a precise safepoint, and a Rust unit test has no loop back-edge poll to drain it, so no collection runs and `assert_automatic_minor_gc_progressed` reports neither a finished assist nor an ACTIVE budgeted cycle. `force_legacy_gc_pacing` is the wrong repair and the tests say so themselves. Three of them carry an evacuation witness — "the minor did not evacuate, so nothing here was exercised and a green result would be meaningless" — and legacy pacing hands the work to the budgeted stepper, which is deliberately non-moving. Pinning it turns a failed assist assertion into a failed liveness assertion, which is the witness doing its job. `force_alloc_point_minor_pacing` (polls OFF, scavenge ON) is the one combination in which both halves hold, and it is the configuration these tests were written against. `symbol_description` has no evacuation witness and takes `force_legacy_gc_pacing`. The moving default's rooting coverage for these helpers is the gap suite's `test_gap_gc_*_rooting.ts` cases and the zeal + from-space-protect runs, not this vehicle — recorded in each test so the next reader does not mistake a pinned pacing for the default being untested. --- .../tests/runtime_roots/fs_options_object.rs | 26 +++++++++++++++++++ .../runtime_roots/json_shape_template.rs | 13 ++++++++++ .../tests/runtime_roots/symbol_description.rs | 11 ++++++++ 3 files changed, 50 insertions(+) diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/fs_options_object.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/fs_options_object.rs index 2fa11d00de..0ed36e67cb 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/fs_options_object.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/fs_options_object.rs @@ -94,6 +94,19 @@ fn with_file_types_options_object(_trigger_guard: &GcTriggerThresholdTestGuard) #[test] fn readdir_options_object_survives_the_with_file_types_key_allocation() { + // This test needs nursery pressure to reach the DIRECT allocation-point + // minor: it asserts both that a bounded assist ran and — as its liveness + // witness — that the minor EVACUATED. The default moving-loop pacing routes + // that pressure into the safepoint deferral instead, and a Rust unit test + // has no loop back-edge poll to drain it, so no collection happens at all. + // Legacy pacing is not the answer either: it hands the work to the budgeted + // stepper, which is deliberately non-moving, and the evacuation witness then + // correctly refuses to certify an empty run. `force_alloc_point_minor_pacing` + // is the combination this test was written against and the only one in which + // both halves hold. The moving default's rooting coverage for these helpers + // is the gap suite's `test_gap_gc_*_rooting.ts` cases plus the zeal + + // from-space-protect runs, not this vehicle. + let _alloc_point_pacing = crate::gc::policy::force_alloc_point_minor_pacing(); let _guard = CopyingNurseryTestGuard::new(1); let trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); register_runtime_handle_root_scanner_for_tests(); @@ -145,6 +158,19 @@ fn readdir_options_object_survives_the_with_file_types_key_allocation() { /// reads its passing as evidence about the rooting. #[test] fn readdir_options_without_the_field_stays_false_across_the_key_allocation() { + // This test needs nursery pressure to reach the DIRECT allocation-point + // minor: it asserts both that a bounded assist ran and — as its liveness + // witness — that the minor EVACUATED. The default moving-loop pacing routes + // that pressure into the safepoint deferral instead, and a Rust unit test + // has no loop back-edge poll to drain it, so no collection happens at all. + // Legacy pacing is not the answer either: it hands the work to the budgeted + // stepper, which is deliberately non-moving, and the evacuation witness then + // correctly refuses to certify an empty run. `force_alloc_point_minor_pacing` + // is the combination this test was written against and the only one in which + // both halves hold. The moving default's rooting coverage for these helpers + // is the gap suite's `test_gap_gc_*_rooting.ts` cases plus the zeal + + // from-space-protect runs, not this vehicle. + let _alloc_point_pacing = crate::gc::policy::force_alloc_point_minor_pacing(); let _guard = CopyingNurseryTestGuard::new(1); let trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); register_runtime_handle_root_scanner_for_tests(); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/json_shape_template.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/json_shape_template.rs index 94877702b6..772b71f32f 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/json_shape_template.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/json_shape_template.rs @@ -74,6 +74,19 @@ fn string_contents(ptr: *const crate::StringHeader) -> String { /// cache scanner alone. The cache scanner is covered by the sibling test below. #[test] fn shape_template_element_survives_the_date_field_allocation() { + // This test needs nursery pressure to reach the DIRECT allocation-point + // minor: it asserts both that a bounded assist ran and — as its liveness + // witness — that the minor EVACUATED. The default moving-loop pacing routes + // that pressure into the safepoint deferral instead, and a Rust unit test + // has no loop back-edge poll to drain it, so no collection happens at all. + // Legacy pacing is not the answer either: it hands the work to the budgeted + // stepper, which is deliberately non-moving, and the evacuation witness then + // correctly refuses to certify an empty run. `force_alloc_point_minor_pacing` + // is the combination this test was written against and the only one in which + // both halves hold. The moving default's rooting coverage for these helpers + // is the gap suite's `test_gap_gc_*_rooting.ts` cases plus the zeal + + // from-space-protect runs, not this vehicle. + let _alloc_point_pacing = crate::gc::policy::force_alloc_point_minor_pacing(); let _guard = CopyingNurseryTestGuard::new(0); let trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); register_runtime_handle_root_scanner_for_tests(); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/symbol_description.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/symbol_description.rs index b8bf105301..8304a641e3 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/symbol_description.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/symbol_description.rs @@ -67,6 +67,17 @@ fn a_fresh_symbol_stores_no_heap_pointer_in_its_payload() { /// reason — the classic vacuous GC probe. #[test] fn a_symbols_description_survives_reclamation_of_the_string_it_came_from() { + // These assertions are about the BUDGETED, non-moving assist — + // `assert_automatic_minor_gc_progressed` requires a bounded assist to finish + // or a budgeted cycle to be left ACTIVE. The default moving-loop pacing + // deliberately routes nursery pressure AWAY from the budgeted stepper and + // into the safepoint deferral, which in a Rust unit test has no loop + // back-edge poll to drain it, so the assist is never entered. Pin the pacing + // whose collection point this assertion describes, exactly as the + // `debt_pacer` tests do. The moving default's rooting coverage for these + // helpers is the gap suite's `test_gap_gc_*_rooting.ts` cases plus the + // zeal + from-space-protect runs, not this vehicle. + let _legacy_pacing = crate::gc::policy::force_legacy_gc_pacing(); let _guard = CopyingNurseryTestGuard::new(1); let trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); register_runtime_handle_root_scanner_for_tests(); From 3edd95663f15c91f5a547f6aae48da86ee7a4ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 18:35:06 +0200 Subject: [PATCH 4/6] docs(changelog): fragment for the moving-loop poll default (#7714) --- .../7714-gc-moving-loop-poll-default.md | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 changelog.d/7714-gc-moving-loop-poll-default.md diff --git a/changelog.d/7714-gc-moving-loop-poll-default.md b/changelog.d/7714-gc-moving-loop-poll-default.md new file mode 100644 index 0000000000..011031a810 --- /dev/null +++ b/changelog.d/7714-gc-moving-loop-poll-default.md @@ -0,0 +1,47 @@ +**`fix(gc)`: the moving-loop poll now defaults ON in the code, not only in the doc (#7690, #7682).** + +#7690 wrote the whole default-ON argument into two doc comments — the runtime's `moving_loop_polls_enabled_from_env` and codegen's `moving_safepoint_polls_enabled` — and changed neither body. Both still matched `1|on|true`, i.e. default OFF, and no test pinned the default in either direction, even though the runtime predicate had been factored out expressly to make it "unit-testable without touching process env". The runtime doc asserted "Codegen's `moving_safepoint_polls_enabled` mirrors this exactly — they MUST agree"; they did agree, at the value the doc said they no longer held. + +That is not a slower configuration, it is a different collector. Nursery pressure has exactly two precise collection points: the loop back-edge poll and the outermost microtask-pump boundary. With no poll emitted a compute-only program reaches neither, so every nursery collection happened at the register-imprecise allocation point — where #7687 had just made it correctly non-moving. The shipped collector therefore had **no nursery evacuation at all**, and the trigger fell back to whole-arena full collections. + +Measured on the pinned quiet mini, best-of-3 interleaved, `PERRY_NO_AUTO_OPTIMIZE=1` with a pinned `PERRY_RUNTIME_DIR`, against `a853135aa` binaries rerun back-to-back on the same host: + +| bench | main `12e48edd6` | this | `a853135aa` | node | scriptc 0.0.22 | +|---|--:|--:|--:|--:|--:| +| tree | 5.10 | **1.63** | 6.00 | 0.45 | 4.80 | +| tree_wide | 7.26 | **2.12** | 12.38 | 0.89 | 7.01 | +| retain | 2.33 | **1.32** | 1.33 | 0.15 | 0.11 | +| churn | 1.00 | **0.46** | 0.66 | 0.16 | 0.75 | +| churn_alloc | 0.91 | **0.41** | 0.36 | 0.14 | 0.44 | +| push_cls | 0.89 | **0.40** | 0.34 | 0.14 | 0.45 | +| cycles | 0.29 | **0.19** | 0.95 | 0.07 | 0.31 | +| churn_read | 0.02 | 0.02 | 0.35 | 0.08 | 0.30 | +| deeplist | 0.03 | 0.31 | 1.14 | 0.09 | 0.22 | + +`churn_alloc` ran **13 whole-arena full collections (0.477 s of pause)** where the same program at `a853135aa` ran **105 copying minors (0.016 s)**. `tree`'s total GC pause falls **4.107 s → 0.550 s** and its max pause **266 ms → 16 ms**; `trace_worklist` falls from 2,877 ms out of the top six phases entirely. `tree_wide` lands at 0.549 s of pause, 17 ms max. + +**The #7161 blocker that made polls-off a stopgap is discharged, and separately so is the reason this flip was declined once before.** A poll at every back-edge used to delete the #7480 element-shape fast clone — a call inside a clone whose admission rests on being call-free-by-construction does not slow it, it removes it. Step 4 of that work now refuses to emit a poll inside such a clone, and `churn_read` measures 0.02 s with polls either way. + +**Costs, measured rather than argued.** `deeplist` 0.03 → 0.31 and `retain1` 0.03 → 0.42: both are workloads whose heap stays under the initial 64 MB threshold, so they previously ran *zero* collections and a moving nursery is pure added cost there. Both still beat `a853135aa` (1.14 / —). `push_num` 0.16 → 0.17. + +Three tests pin what was unpinned. `polls_default_is_on` and codegen's `moving_safepoint_poll_default::unset_emits_the_poll` each pin one half against the full spelling table including the unrecognised-value arm, which is the one that silently changes meaning if the `matches!` is inverted back. `polls_default_matches_codegen_mirror` pins that the two crates agree — the disagreement is silent in both directions (polls nothing consumes, or a deferral nothing drains), so it needs its own assertion rather than two doc comments claiming they match. + +Validation: perry-runtime 1947/0, perry-codegen lib 796/0, gap suite, `PERRY_GC_VERIFY_MARK=1` + `PERRY_GC_VERIFY_EVACUATION=1` clean on six benches, and `PERRY_GC_ZEAL=1 PERRY_GC_PROTECT_FROMSPACE=1` at quarantine depths 8 and 800 moving 4,837 objects with byte-identical output. Zeal's own verdict line is the liveness proof that the new default is live rather than merely untripped: `forced_collections=2000005 copying_minors=2000005 moved_objects=8 loop_polls=2000000`. + +--- + +**`perf(gc)`: the trace path stops paying a shape-layout hash lookup per object for a disabled counter.** + +`heap_payload_slot_selection` runs once per traced object per GC walk — mark, rewrite and verify. For every `GC_TYPE_OBJECT` it computed `raw_numeric_object_slots` through `with_typed_descriptor_for_query`: a per-object map probe plus, for every class instance, a `SHAPE_LAYOUTS` hash lookup behind a thread-local `RefCell` borrow. That number has exactly one consumer, `record_layout_raw_numeric_object_field_range_skipped`, which returns on its first line unless `PERRY_GC_LAYOUT_SCAN_TRACE` armed the counter. The shipped collector paid a SipHash per object to produce a number nothing read — the same shape as #7702, where a facility already disabled at runtime still had its arguments evaluated. It is now computed only when the trace is armed. + +Same walk, second item: `shape_shared_pointer_mask` returned `shape_shared_descriptor(user_ptr).map(|d| d.pointer_mask)`, cloning a whole `TypedLayoutDescriptor` to keep one of its two masks. `LayoutSlotMask` is `Heap(Vec)` above 64 slots, so a traced wide object allocated and freed a second vector — the `raw_f64_mask` it immediately dropped — on every walk. It now borrows through `with_shape_shared_descriptor` and clones only the mask it returns; `shape_shared_descriptor` had no other caller and is removed rather than left as dead code. + +Both are behaviour-preserving: no mask, no slot selection and no counter value changes when the layout-scan trace is armed. + +--- + +**`test(gc)`: the alloc-point rooting tests declare the pacing they actually assert at.** + +Four `runtime_roots` tests took no pacing guard and so inherited the process default, which this stack changes. They are not asserting about the default — they assert that a specific runtime helper's object survives a collection *at the allocation point*, reached through the direct alloc-point minor. Under moving-loop polls that pressure is deferred to a precise safepoint, and a Rust unit test has no loop back-edge poll to drain it. + +`force_legacy_gc_pacing` is the wrong repair, and the tests say so themselves: three carry an evacuation witness — *"the minor did not evacuate, so nothing here was exercised and a green result would be meaningless"* — and legacy pacing hands the work to the budgeted stepper, which is deliberately non-moving, converting a failed assist assertion into a failed liveness assertion. `force_alloc_point_minor_pacing` (polls OFF, scavenge ON) is the one combination in which both halves hold, and is what these tests were written against. `symbol_description` has no witness and takes `force_legacy_gc_pacing`. Each records that the moving default's rooting coverage for these helpers is the gap suite's `test_gap_gc_*_rooting.ts` cases and the zeal runs, not this vehicle — so a pinned pacing is not mistaken for the default going untested. From 0bf1a4c23f6877b1bf9d02a237d7e67c0347ae6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 18:51:58 +0200 Subject: [PATCH 5/6] docs(changelog): key the fragment to its PR number (#7721) --- ...g-loop-poll-default.md => 7721-gc-moving-loop-poll-default.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{7714-gc-moving-loop-poll-default.md => 7721-gc-moving-loop-poll-default.md} (100%) diff --git a/changelog.d/7714-gc-moving-loop-poll-default.md b/changelog.d/7721-gc-moving-loop-poll-default.md similarity index 100% rename from changelog.d/7714-gc-moving-loop-poll-default.md rename to changelog.d/7721-gc-moving-loop-poll-default.md From 691c664e718532e6f1069652f3c006423acf1403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 19:32:02 +0200 Subject: [PATCH 6/6] chore: bump version to 0.5.1418 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 e98ba1d742..2759c925a8 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.1417 +**Current Version:** 0.5.1418 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 2981170c1c..b16e28fadb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5547,7 +5547,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "base64", @@ -5607,14 +5607,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "cc", "libc", @@ -5622,7 +5622,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "inkwell", @@ -5639,7 +5639,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "perry-hir", @@ -5647,7 +5647,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "perry-hir", @@ -5655,7 +5655,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "perry-dispatch", @@ -5664,7 +5664,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "perry-hir", @@ -5672,7 +5672,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "base64", @@ -5684,7 +5684,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "perry-hir", @@ -5692,7 +5692,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "async-trait", @@ -5721,14 +5721,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "serde", "serde_json", @@ -5736,7 +5736,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1417" +version = "0.5.1418" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5747,7 +5747,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "clap", @@ -5762,7 +5762,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "block2", "objc2", @@ -5772,7 +5772,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "argon2", "perry-ffi", @@ -5780,7 +5780,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "reqwest", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "bcrypt", "perry-ffi", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "rusqlite", @@ -5805,7 +5805,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "scraper", @@ -5813,7 +5813,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "perry-runtime", @@ -5821,7 +5821,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "chrono", "cron", @@ -5831,7 +5831,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "chrono", "perry-ffi", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "rust_decimal", @@ -5847,7 +5847,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "serde_json", @@ -5855,7 +5855,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "perry-runtime", @@ -5871,14 +5871,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "bytes", "http-body-util", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "bytes", "lazy_static", @@ -5909,7 +5909,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "bytes", "h2", @@ -5933,7 +5933,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "lazy_static", "perry-ffi", @@ -5943,7 +5943,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "base64", "jsonwebtoken", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "lru", "perry-ffi", @@ -5963,7 +5963,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "chrono", "perry-ffi", @@ -5971,7 +5971,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "bson", "futures-util", @@ -5983,7 +5983,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "chrono", "perry-ffi", @@ -5993,7 +5993,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "nanoid", "perry-ffi", @@ -6002,7 +6002,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "bytes", "perry-ffi", @@ -6015,7 +6015,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6034,7 +6034,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "lettre", "perry-ffi", @@ -6044,7 +6044,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "printpdf", @@ -6052,7 +6052,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "sqlx", @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "governor", "perry-ffi", @@ -6069,7 +6069,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "fast_image_resize", "image", @@ -6079,14 +6079,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "lazy_static", "perry-ffi", @@ -6095,7 +6095,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "perry-runtime", @@ -6104,7 +6104,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "uuid", @@ -6112,7 +6112,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ffi", "regex", @@ -6122,7 +6122,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "futures-util", "lazy_static", @@ -6135,7 +6135,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "brotli", "flate2", @@ -6145,7 +6145,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "dashmap", "once_cell", @@ -6154,7 +6154,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "perry-api-manifest", @@ -6172,7 +6172,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "perry-diagnostics", @@ -6184,7 +6184,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "base64", @@ -6226,14 +6226,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6328,14 +6328,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "perry-hir", @@ -6344,14 +6344,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "base64", "itoa", @@ -6368,7 +6368,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "rand 0.10.1", "serde", @@ -6378,7 +6378,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6401,7 +6401,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "base64", "block2", @@ -6417,7 +6417,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "base64", "block2", @@ -6432,7 +6432,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1417" +version = "0.5.1418" [[package]] name = "perry-ui-test" @@ -6443,11 +6443,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1417" +version = "0.5.1418" [[package]] name = "perry-ui-tvos" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "base64", "block2", @@ -6463,7 +6463,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "base64", "block2", @@ -6479,7 +6479,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "block2", "libc", @@ -6492,7 +6492,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "base64", "libc", @@ -6509,14 +6509,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "anyhow", "base64", @@ -6532,7 +6532,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1417" +version = "0.5.1418" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 8ffb5d7835..11da24912c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1417" +version = "0.5.1418" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry"