From faed3135168cd014ca50bafa8f3514b47e61e155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Thu, 6 Aug 2026 05:54:46 +0200 Subject: [PATCH 1/3] =?UTF-8?q?perf(codegen):=20lower=20temp=20roots=20ont?= =?UTF-8?q?o=20pooled=20frame=20allocas=20=E2=80=94=203=20FFI=20calls=20pe?= =?UTF-8?q?r=20temporary=20become=20a=20store=20and=20a=20load?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The #6951 temp-root contract cost three runtime calls per protected temporary — js_gc_temp_root_push, a mandatory re-read, truncate — and after #7474 those three were 206 of the 542 remaining _tlv_get_addr-attributed samples on churn.ts. But named locals already demonstrate the cheap form of the same root: an entry alloca bound to a shadow-frame slot, written and re-read with plain stores and loads, upgraded by the RS4GC/stack-map lowering into a relocated addrspace(1) slot. A temp needs nothing a local does not. TempRootPool (per-function, compile-time-only) lowers the same API onto that mechanism: push = store + the identical bind/root-shading emission a named local's store uses (emit_shadow_slot_bind_ptr, extracted from emit_shadow_slot_bind_for_local); get = load; set = store+bind; truncate = zero the slot, clear the frame mirror, release the pool entries at and above it — the same drop-everything-above contract the FFI stack imposed. Handles keep the String currency, so every caller (RootedOperands, StoreOperandGuard, rooting.rs's call_rooted) compiles unchanged. The pool reuses slots by stack watermark; frame slots are reserved on demand via reserve_shadow_slot, whose in-place slot-count rewrite is what rules out the #7184 out-of-frame-bounds shape. The store-then-bind order at the push site is the #7192 dominance invariant, stated at the emission. When shadow-stack emission is off (reserve_shadow_slot -> None) the whole function falls back to the FFI stack byte-for-byte; ShadowSavepoint's temp-depth restore keeps working on both arms (the FFI stack simply stays empty in alloca mode). js_array_push_f64_temp_rooted remains for the fallback arm only — in alloca mode the fused form is load + js_array_push_f64 + store, which is cheaper than the call it was fusing. Refs #7469. --- crates/perry-codegen/src/codegen/closure.rs | 1 + crates/perry-codegen/src/codegen/entry.rs | 2 + crates/perry-codegen/src/codegen/function.rs | 1 + crates/perry-codegen/src/codegen/method.rs | 2 + crates/perry-codegen/src/expr/mod.rs | 5 + crates/perry-codegen/src/expr/shadow_slot.rs | 24 ++- crates/perry-codegen/src/expr/temp_root.rs | 158 ++++++++++++++++++- crates/perry-codegen/src/rooting.rs | 11 +- 8 files changed, 190 insertions(+), 14 deletions(-) diff --git a/crates/perry-codegen/src/codegen/closure.rs b/crates/perry-codegen/src/codegen/closure.rs index 0565e45fa2..3d893aa969 100644 --- a/crates/perry-codegen/src/codegen/closure.rs +++ b/crates/perry-codegen/src/codegen/closure.rs @@ -973,6 +973,7 @@ pub(super) fn compile_closure( // Conservative: treat every slot as possibly-bound (param binds are // emitted before FnCtx exists here), so clears never get skipped. shadow_slots_bound: shadow_slot_map.values().copied().collect(), + temp_roots: crate::expr::temp_root::TempRootPool::default(), shadow_slot_map, persistent_shadow_slots: std::collections::HashSet::new(), shadow_slot_clears_after_stmt, diff --git a/crates/perry-codegen/src/codegen/entry.rs b/crates/perry-codegen/src/codegen/entry.rs index 682251cbb2..78f3824364 100644 --- a/crates/perry-codegen/src/codegen/entry.rs +++ b/crates/perry-codegen/src/codegen/entry.rs @@ -780,6 +780,7 @@ pub(super) fn compile_module_entry( not_bigint_locals: main_native_facts.not_bigint_locals(), unsigned_i32_locals: main_native_facts.unsigned_i32_locals(), shadow_slots_bound: main_shadow_slot_map.values().copied().collect(), + temp_roots: crate::expr::temp_root::TempRootPool::default(), shadow_slot_map: main_shadow_slot_map, persistent_shadow_slots: std::collections::HashSet::new(), shadow_slot_clears_after_stmt: main_shadow_slot_clears_after_stmt, @@ -1444,6 +1445,7 @@ pub(super) fn compile_module_entry( not_bigint_locals: init_native_facts.not_bigint_locals(), unsigned_i32_locals: init_native_facts.unsigned_i32_locals(), shadow_slots_bound: init_shadow_slot_map.values().copied().collect(), + temp_roots: crate::expr::temp_root::TempRootPool::default(), shadow_slot_map: init_shadow_slot_map, persistent_shadow_slots: std::collections::HashSet::new(), shadow_slot_clears_after_stmt: init_shadow_slot_clears_after_stmt, diff --git a/crates/perry-codegen/src/codegen/function.rs b/crates/perry-codegen/src/codegen/function.rs index 3d11fb52ad..45dddb8a4b 100644 --- a/crates/perry-codegen/src/codegen/function.rs +++ b/crates/perry-codegen/src/codegen/function.rs @@ -770,6 +770,7 @@ pub(super) fn compile_function( persistent_shadow_slots: std::collections::HashSet::new(), shadow_slot_clears_after_stmt, shadow_slots_bound: bound_param_slots, + temp_roots: crate::expr::temp_root::TempRootPool::default(), arena_state_slot: None, class_keys_slots: HashMap::new(), cached_lengths: HashMap::new(), diff --git a/crates/perry-codegen/src/codegen/method.rs b/crates/perry-codegen/src/codegen/method.rs index 25d628ecde..f759cb6688 100644 --- a/crates/perry-codegen/src/codegen/method.rs +++ b/crates/perry-codegen/src/codegen/method.rs @@ -504,6 +504,7 @@ pub(super) fn compile_method( // Conservative: treat every slot as possibly-bound (param binds are // emitted before FnCtx exists here), so clears never get skipped. shadow_slots_bound: shadow_slot_map.values().copied().collect(), + temp_roots: crate::expr::temp_root::TempRootPool::default(), shadow_slot_map, persistent_shadow_slots: std::collections::HashSet::new(), shadow_slot_clears_after_stmt, @@ -1562,6 +1563,7 @@ pub(super) fn compile_static_method( // Conservative: treat every slot as possibly-bound (param binds are // emitted before FnCtx exists here), so clears never get skipped. shadow_slots_bound: shadow_slot_map.values().copied().collect(), + temp_roots: crate::expr::temp_root::TempRootPool::default(), shadow_slot_map, persistent_shadow_slots: std::collections::HashSet::new(), shadow_slot_clears_after_stmt, diff --git a/crates/perry-codegen/src/expr/mod.rs b/crates/perry-codegen/src/expr/mod.rs index 043a74f63e..01c4f5f287 100644 --- a/crates/perry-codegen/src/expr/mod.rs +++ b/crates/perry-codegen/src/expr/mod.rs @@ -701,6 +701,11 @@ pub(crate) struct FnCtx<'a> { /// Seeded at construction with the entry-bound parameter slots. pub shadow_slots_bound: std::collections::HashSet, + /// #7469: pooled frame-rooted allocas for expression temporaries — see + /// [`temp_root::TempRootPool`]. Starts empty; grows on the first + /// protected temporary this function lowers. + pub temp_roots: temp_root::TempRootPool, + /// Cached pointer to this function's `InlineArenaState` slot — /// allocated lazily on the first `new ClassName()` site that uses /// the inline bump-allocator path. The slot lives in the function diff --git a/crates/perry-codegen/src/expr/shadow_slot.rs b/crates/perry-codegen/src/expr/shadow_slot.rs index b41fd6b57a..b7c32c7978 100644 --- a/crates/perry-codegen/src/expr/shadow_slot.rs +++ b/crates/perry-codegen/src/expr/shadow_slot.rs @@ -205,29 +205,43 @@ pub(crate) fn emit_shadow_slot_bind_for_local(ctx: &mut FnCtx<'_>, local_id: u32 let Some(local_slot) = ctx.locals.get(&local_id).cloned() else { return; }; + emit_shadow_slot_bind_ptr(ctx, slot_idx, &local_slot); +} + +/// Bind frame slot `slot_idx` to the root alloca `slot_ptr` — the raw form of +/// [`emit_shadow_slot_bind_for_local`], for roots that are not named locals +/// (#7469: the pooled temp-root allocas in `temp_root.rs`). +/// +/// The caller owns the pairing of `slot_idx` and `slot_ptr`; everything else +/// — the stack-map textual marker, the #7088 inline frame write, the FFI +/// fallback, and the incremental root-shading barrier — is identical to a +/// named local's bind, which is the point: a temp rooted through here is +/// indistinguishable to the collector and to the RS4GC/stack-map lowering +/// from a local. +pub(crate) fn emit_shadow_slot_bind_ptr(ctx: &mut FnCtx<'_>, slot_idx: u32, slot_ptr: &str) { ctx.shadow_slots_bound.insert(slot_idx); if crate::codegen::helpers::native_stack_roots_enabled() { // Kept temporarily as a textual marker: LlFunction's final stack-map - // lowering records `slot_idx -> local_slot` and removes this call. + // lowering records `slot_idx -> slot_ptr` and removes this call. // The incremental root barrier remains real because the native slot // can be updated after an in-flight cycle scanned this frame. ctx.block().call_void( "js_shadow_slot_bind", - &[(I32, &slot_idx.to_string()), (PTR, &local_slot)], + &[(I32, &slot_idx.to_string()), (PTR, slot_ptr)], ); - let value_bits = ctx.block().load(I64, &local_slot); + let value_bits = ctx.block().load(I64, slot_ptr); emit_persistent_shadow_root_barrier(ctx, &value_bits); return; } // #7088: the hot per-store root write. Emitted inline against this // activation's cached `ShadowStackState` pointer when it has one; falls // through to the call otherwise. - if super::shadow_inline::emit_inline_slot_bind(ctx, slot_idx, &local_slot) { + if super::shadow_inline::emit_inline_slot_bind(ctx, slot_idx, slot_ptr) { return; } ctx.block().call_void( "js_shadow_slot_bind", - &[(I32, &slot_idx.to_string()), (PTR, &local_slot)], + &[(I32, &slot_idx.to_string()), (PTR, slot_ptr)], ); } diff --git a/crates/perry-codegen/src/expr/temp_root.rs b/crates/perry-codegen/src/expr/temp_root.rs index 28421a3916..ba35f27975 100644 --- a/crates/perry-codegen/src/expr/temp_root.rs +++ b/crates/perry-codegen/src/expr/temp_root.rs @@ -52,9 +52,119 @@ use crate::types::{DOUBLE, I32, I64}; use super::FnCtx; +/// Per-function pool of frame-rooted temp allocas (#7469). +/// +/// The FFI contract above cost three runtime calls per protected temporary — +/// push, mandatory re-read, truncate — and on `churn.ts` those three were 206 +/// of the 542 remaining `_tlv_get_addr`-attributed samples after #7474. But +/// the function's *named* locals already demonstrate the cheap form of the +/// same root: an entry alloca bound to a shadow-frame slot, written and +/// re-read with plain stores and loads, upgraded by the RS4GC / stack-map +/// lowering into a relocated `addrspace(1)` slot. A temp needs nothing a +/// local doesn't; this pool gives temps the identical mechanism. +/// +/// The pool is **compile-time bookkeeping only** — at runtime there is no +/// stack to balance, no depth to restore on `longjmp` (the slots die with the +/// shadow frame like every local slot), and nothing for +/// `ShadowSavepoint`'s temp-depth restore to do (the FFI stack it snapshots +/// simply stays empty). +/// +/// Slot handles keep the same `String` currency the FFI version used, so +/// every caller — including the nested `RootedOperands` / +/// `StoreOperandGuard` machinery — compiles unchanged. In alloca mode the +/// string is the entry-alloca register; in fallback mode it is the FFI +/// index register, exactly as before. +/// +/// Reuse is a stack watermark, mirroring `js_gc_temp_root_truncate`'s +/// "drop `base` and everything above it" contract: releasing a handle frees +/// it AND every handle acquired after it. That is the discipline the FFI +/// API already imposed on callers (a truncate at index N invalidated all +/// higher indexes), so no caller can observe the difference. +#[derive(Default)] +pub(crate) struct TempRootPool { + /// `(entry alloca register, reserved frame slot index)` in acquisition + /// order. Entries at positions `>= active` are free for reuse. + slots: Vec<(String, u32)>, + /// Stack watermark: the number of live handles. + active: usize, + /// `Some(true)` — this function lowers temps onto frame-rooted allocas. + /// `Some(false)` — shadow-stack emission is off for this build ( + /// `reserve_shadow_slot` returned `None`); every temp uses the runtime + /// FFI stack, byte-for-byte the pre-#7469 emission. Decided at the first + /// acquisition and uniform for the whole function, so `get`/`set`/ + /// `truncate` can interpret handles without per-handle tags. + alloca_mode: Option, +} + +impl TempRootPool { + /// Frame slot index for a live alloca handle. Panics on an unknown + /// handle — that would mean a caller invented a slot string or used one + /// across functions, both of which were equally broken under the FFI + /// contract (a stale index addressed someone else's slot silently; this + /// at least fails loudly at compile time). + fn frame_idx(&self, handle: &str) -> u32 { + self.slots + .iter() + .find(|(alloca, _)| alloca == handle) + .map(|(_, idx)| *idx) + .unwrap_or_else(|| panic!("temp-root handle {handle} not in this function's pool")) + } + + /// Watermark position of a live handle, for release. + fn position(&self, handle: &str) -> Option { + self.slots[..self.active] + .iter() + .position(|(alloca, _)| alloca == handle) + } +} + +/// Acquire a pooled frame-rooted slot, or `None` when this build lowers +/// temps through the runtime FFI stack. +fn temp_pool_acquire(ctx: &mut FnCtx<'_>) -> Option { + if ctx.temp_roots.alloca_mode == Some(false) { + return None; + } + let pos = ctx.temp_roots.active; + if let Some((alloca, _)) = ctx.temp_roots.slots.get(pos) { + let alloca = alloca.clone(); + ctx.temp_roots.active += 1; + return Some(alloca); + } + // Grow the pool: entry alloca + on-demand frame slot. `reserve_shadow_slot` + // rewrites the emitted `js_shadow_frame_enter` slot count in place, so the + // #7184 hazard (slot index outside the pushed frame) cannot arise. + let Some(slot_idx) = ctx.func.reserve_shadow_slot() else { + ctx.temp_roots.alloca_mode = Some(false); + return None; + }; + ctx.temp_roots.alloca_mode = Some(true); + let alloca = ctx.func.alloca_entry(I64); + // Null-init at entry: the slot is scanned from bind onward, and the + // RS4GC retype pass re-emits exactly this `store i64 0` as a null + // `addrspace(1)` store. + ctx.func.entry_allocas_push_store(I64, "0", &alloca); + ctx.temp_roots.slots.push((alloca.clone(), slot_idx)); + ctx.temp_roots.active += 1; + Some(alloca) +} + +/// Root-store for an alloca-mode handle: plain store, then the same +/// bind + root-shading emission every named-local store uses. The bind must +/// be emitted here — after the store, before whatever collects — so the +/// rooted location dominates the collection point (#7192's invariant). +fn temp_slot_store(ctx: &mut FnCtx<'_>, handle: &str, value_i64: &str) { + ctx.block().store(I64, value_i64, handle); + let idx = ctx.temp_roots.frame_idx(handle); + super::shadow_slot::emit_shadow_slot_bind_ptr(ctx, idx, handle); +} + /// Push `value_i64` (a bare heap pointer or NaN-boxed bits) and return the -/// slot-index register. +/// slot handle. pub(crate) fn temp_root_push_i64(ctx: &mut FnCtx<'_>, value_i64: &str) -> String { + if let Some(handle) = temp_pool_acquire(ctx) { + temp_slot_store(ctx, &handle, value_i64); + return handle; + } ctx.block() .call(I32, "js_gc_temp_root_push", &[(I64, value_i64)]) } @@ -66,7 +176,14 @@ pub(crate) fn temp_root_push_double(ctx: &mut FnCtx<'_>, value: &str) -> String } /// Re-read slot `idx` as a raw `i64`. +/// +/// In alloca mode the re-read is a plain load — the collector rewrote the +/// alloca (shadow scan or statepoint relocation), so the load IS the +/// post-collection value, same as a named local's re-read. pub(crate) fn temp_root_get_i64(ctx: &mut FnCtx<'_>, idx: &str) -> String { + if ctx.temp_roots.alloca_mode == Some(true) { + return ctx.block().load(I64, idx); + } ctx.block().call(I64, "js_gc_temp_root_get", &[(I32, idx)]) } @@ -82,6 +199,10 @@ pub(crate) fn temp_root_get_double(ctx: &mut FnCtx<'_>, idx: &str) -> String { /// `concat` accumulator (#6971), where every `js_string_concat` yields a new /// string and the old one stops being the value that must stay alive. pub(crate) fn temp_root_set_i64(ctx: &mut FnCtx<'_>, idx: &str, value_i64: &str) { + if ctx.temp_roots.alloca_mode == Some(true) { + temp_slot_store(ctx, idx, value_i64); + return; + } ctx.block() .call_void("js_gc_temp_root_set", &[(I32, idx), (I64, value_i64)]); } @@ -98,6 +219,27 @@ pub(crate) fn temp_root_set_double(ctx: &mut FnCtx<'_>, idx: &str, value: &str) /// Drop slot `idx` and everything pushed above it. pub(crate) fn temp_root_truncate(ctx: &mut FnCtx<'_>, idx: &str) { + if ctx.temp_roots.alloca_mode == Some(true) { + // Mirror the FFI contract exactly: drop `idx` and everything acquired + // above it. Each released slot is zeroed (dropping its retention) and + // its frame mirror cleared; the pool entry becomes reusable. + // + // A repeated release of an already-released handle (the documented + // `implicit_this_restore` → outer-group interleaving) finds no + // watermark position and is the same harmless no-op the FFI's + // `base < len` guard made it. + let Some(pos) = ctx.temp_roots.position(idx) else { + return; + }; + let released: Vec<(String, u32)> = + ctx.temp_roots.slots[pos..ctx.temp_roots.active].to_vec(); + ctx.temp_roots.active = pos; + for (alloca, slot_idx) in released { + ctx.block().store(I64, "0", &alloca); + super::shadow_slot::emit_shadow_slot_clear(ctx, slot_idx); + } + return; + } ctx.block() .call_void("js_gc_temp_root_truncate", &[(I32, idx)]); } @@ -160,7 +302,21 @@ pub(crate) fn implicit_this_restore(ctx: &mut FnCtx<'_>, save: ImplicitThisSave) /// Push `value` onto the array held in temp-root slot `idx`, writing the /// possibly-reallocated array pointer back into the slot. +/// +/// The fused `js_array_push_f64_temp_rooted` runtime helper exists only to +/// collapse the FFI stack's get+push+set triple into one call; in alloca mode +/// the triple is a load, the push itself, and a store — so the plain +/// `js_array_push_f64` (which roots `value` internally on its grow path) is +/// the cheaper form and the fused helper stays FFI-fallback-only. pub(crate) fn temp_rooted_array_push(ctx: &mut FnCtx<'_>, idx: &str, value: &str) { + if ctx.temp_roots.alloca_mode == Some(true) { + let arr = ctx.block().load(I64, idx); + let new_arr = ctx + .block() + .call(I64, "js_array_push_f64", &[(I64, &arr), (DOUBLE, value)]); + temp_slot_store(ctx, idx, &new_arr); + return; + } ctx.block().call_void( "js_array_push_f64_temp_rooted", &[(I32, idx), (DOUBLE, value)], diff --git a/crates/perry-codegen/src/rooting.rs b/crates/perry-codegen/src/rooting.rs index d77575fd5d..ea3a037aaf 100644 --- a/crates/perry-codegen/src/rooting.rs +++ b/crates/perry-codegen/src/rooting.rs @@ -260,7 +260,6 @@ mod tests { // --------------------------------------------------------------------------- use crate::expr::FnCtx; -use crate::types::{I32, I64}; /// A slot holding a GC-managed pointer for the duration of a lowering. #[derive(Debug, Clone)] @@ -273,14 +272,12 @@ impl RootedSlot { /// only valid until the next emission that can collect, and re-reading is /// cheaper than reasoning about whether one has happened. pub fn read(&self, ctx: &mut FnCtx<'_>) -> String { - ctx.block() - .call(I64, "js_gc_temp_root_get", &[(I32, &self.idx)]) + crate::expr::temp_root::temp_root_get_i64(ctx, &self.idx) } /// Release the slot. Call after the last [`RootedSlot::read`]. pub fn release(self, ctx: &mut FnCtx<'_>) { - ctx.block() - .call_void("js_gc_temp_root_truncate", &[(I32, &self.idx)]); + crate::expr::temp_root::temp_root_truncate(ctx, &self.idx); } } @@ -297,8 +294,6 @@ pub fn call_rooted( args: &[(crate::types::LlvmType, &str)], ) -> RootedSlot { let reg = ctx.block().call(ret_ty, callee, args); - let idx = ctx - .block() - .call(I32, "js_gc_temp_root_push", &[(I64, ®)]); + let idx = crate::expr::temp_root::temp_root_push_i64(ctx, ®); RootedSlot { idx } } From f56b6ffdc0422d4461055a6e97c0566f12cda90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Thu, 6 Aug 2026 08:20:30 +0200 Subject: [PATCH 2/3] docs(changelog): fragment for temp-root frame allocas (#7487) --- changelog.d/7487-temp-root-allocas.md | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 changelog.d/7487-temp-root-allocas.md diff --git a/changelog.d/7487-temp-root-allocas.md b/changelog.d/7487-temp-root-allocas.md new file mode 100644 index 0000000000..aba223d549 --- /dev/null +++ b/changelog.d/7487-temp-root-allocas.md @@ -0,0 +1,38 @@ +### Codegen: temp roots lowered onto pooled frame allocas (#7469) + +The #6951 expression-temporary rooting contract cost three runtime calls per +protected temporary — `js_gc_temp_root_push`, a mandatory re-read, truncate — +and after the hot-TLS consolidation those three calls were 206 of the 542 +remaining `_tlv_get_addr`-attributed profile samples on `churn.ts`. Named +locals already demonstrate the cheap form of the same root: an entry alloca +bound to a shadow-frame slot, written and re-read with plain stores and loads, +upgraded by the RS4GC/stack-map lowering into a relocated `addrspace(1)` slot +the collector rewrites. A temp needs nothing a local doesn't. + +`TempRootPool` (per-function, compile-time bookkeeping only) lowers the same +API onto that mechanism: push = store + the identical bind/root-shading +emission a named local's store uses (`emit_shadow_slot_bind_ptr`, extracted +from `emit_shadow_slot_bind_for_local`); get = load; truncate = zero the slot, +clear its frame mirror, and release the pool entries at and above it — the +same drop-everything-above discipline the FFI stack imposed, so no caller can +observe the difference. Handles keep their `String` currency, so every caller +(`RootedOperands`, `StoreOperandGuard`, `rooting.rs`'s `call_rooted`) compiles +unchanged. Frame slots are reserved on demand through `reserve_shadow_slot`, +whose in-place slot-count rewrite is what rules out the #7184 +out-of-frame-bounds shape; the store-then-bind order at the push site is the +#7192 dominance invariant, stated at the emission site. When shadow-stack +emission is off the whole function falls back to the FFI stack byte-for-byte. + +`churn.ts`'s hot function drops from 9 temp-root FFI calls to 0. Validation: +the #6951/#6971/#7200/#7211 reproducer shapes match Node under default GC, +under `PERRY_CONSERVATIVE_STACK_SCAN=off` (the mode where an unrooted temp is +a live use-after-free), and under `PERRY_GC_ZEAL=1` + +`PERRY_GC_PROTECT_FROMSPACE=1` with `PERRY_GC_MOVING_LOOP_POLLS=1` — with the +instrument proven live (5 quarantined from-space sets per run, so survivors +genuinely moved and the slots were genuinely rewritten). The +`gc_root_dominance` corpus passes with the empty allowlist (0 violations, +2,379 functions / 9,145 root stores) and the stale-register budget improves to +21 of 39. GC ratchet vs `main`: collector accounting identical on all 12 +probes; `09_try_catch_roots` retains **14% less** heap — frame-slot temps are +zeroed at release instead of lingering on the runtime stack until a later +truncate. From b051649af84a9e6593739877eb534db193fd213a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Thu, 6 Aug 2026 08:53:14 +0200 Subject: [PATCH 3/3] chore: bump version to 0.5.1284 --- 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 e6bfb35c8a..f3eaa4dd40 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.1283 +**Current Version:** 0.5.1284 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index d20004fdaa..fed0d8b81e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5547,7 +5547,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "base64", @@ -5607,14 +5607,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "cc", "libc", @@ -5622,7 +5622,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "inkwell", @@ -5638,7 +5638,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "perry-hir", @@ -5646,7 +5646,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "perry-hir", @@ -5654,7 +5654,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "perry-dispatch", @@ -5663,7 +5663,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "perry-hir", @@ -5671,7 +5671,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "base64", @@ -5683,7 +5683,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "perry-hir", @@ -5691,7 +5691,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "async-trait", @@ -5720,14 +5720,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "serde", "serde_json", @@ -5735,7 +5735,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1283" +version = "0.5.1284" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5746,7 +5746,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "clap", @@ -5761,7 +5761,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "block2", "objc2", @@ -5771,7 +5771,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "argon2", "perry-ffi", @@ -5779,7 +5779,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "reqwest", @@ -5788,7 +5788,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "bcrypt", "perry-ffi", @@ -5796,7 +5796,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "rusqlite", @@ -5804,7 +5804,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "scraper", @@ -5812,7 +5812,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "perry-runtime", @@ -5820,7 +5820,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "chrono", "cron", @@ -5830,7 +5830,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "chrono", "perry-ffi", @@ -5838,7 +5838,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "rust_decimal", @@ -5846,7 +5846,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "serde_json", @@ -5854,7 +5854,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5862,7 +5862,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "perry-runtime", @@ -5870,14 +5870,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "bytes", "http-body-util", @@ -5895,7 +5895,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "bytes", "lazy_static", @@ -5908,7 +5908,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "bytes", "h2", @@ -5932,7 +5932,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "lazy_static", "perry-ffi", @@ -5942,7 +5942,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "base64", "jsonwebtoken", @@ -5953,7 +5953,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "lru", "perry-ffi", @@ -5962,7 +5962,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "chrono", "perry-ffi", @@ -5970,7 +5970,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "bson", "futures-util", @@ -5982,7 +5982,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "chrono", "perry-ffi", @@ -5992,7 +5992,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "nanoid", "perry-ffi", @@ -6001,7 +6001,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "bytes", "perry-ffi", @@ -6014,7 +6014,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6033,7 +6033,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "lettre", "perry-ffi", @@ -6043,7 +6043,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "printpdf", @@ -6051,7 +6051,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "sqlx", @@ -6060,7 +6060,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "governor", "perry-ffi", @@ -6068,7 +6068,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "fast_image_resize", "image", @@ -6078,14 +6078,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "lazy_static", "perry-ffi", @@ -6094,7 +6094,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "perry-runtime", @@ -6103,7 +6103,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "uuid", @@ -6111,7 +6111,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ffi", "regex", @@ -6121,7 +6121,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "futures-util", "lazy_static", @@ -6134,7 +6134,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "brotli", "flate2", @@ -6144,7 +6144,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "dashmap", "once_cell", @@ -6153,7 +6153,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "perry-api-manifest", @@ -6171,7 +6171,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "perry-diagnostics", @@ -6183,7 +6183,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "base64", @@ -6225,14 +6225,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6327,14 +6327,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "perry-hir", @@ -6343,14 +6343,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "base64", "itoa", @@ -6367,7 +6367,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "rand 0.10.1", "serde", @@ -6377,7 +6377,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6400,7 +6400,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "base64", "block2", @@ -6416,7 +6416,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "base64", "block2", @@ -6431,7 +6431,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1283" +version = "0.5.1284" [[package]] name = "perry-ui-test" @@ -6442,11 +6442,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1283" +version = "0.5.1284" [[package]] name = "perry-ui-tvos" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "base64", "block2", @@ -6462,7 +6462,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "base64", "block2", @@ -6478,7 +6478,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "block2", "libc", @@ -6491,7 +6491,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "base64", "libc", @@ -6508,14 +6508,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "anyhow", "base64", @@ -6531,7 +6531,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1283" +version = "0.5.1284" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 4a4c562db6..998cfddf54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1283" +version = "0.5.1284" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry"