Skip to content

Commit 72e695c

Browse files
authored
deps: bump toml 0.8 → 1.1 (#727) + deno_core 0.311 → 0.400 (#729) (v0.5.899) (#744)
The two bumps that the v0.5.894 batch deferred as too-disruptive. Addressed with focused migrations. ## toml 1.x `<toml::Value as FromStr>` (called implicitly by `.parse::<toml::Value>()`) is no longer a document parser — it now parses inline values only. Four callsites swapped to `toml::from_str::<toml::Value>(s)` (still document parser): - crates/perry/src/commands/compile/well_known.rs:100 - crates/perry/src/commands/run.rs:343,829,1453 - crates/perry/src/commands/native/validate.rs:164 Restores 7 well_known + validate tests and 31 compile-smoke regressions. ## deno_core 0.400 (v8 0.106 → 147.4.0) API changes migrated in perry-jsruntime (5 source files + Cargo.toml): - `v8::HandleScope` → `v8::PinScope<'_,'_>` - `JsRuntime::handle_scope()` → `deno_core::scope!(scope, &mut runtime)` - `v8::TryCatch::new(scope)` → `v8::tc_scope!(tc_scope, scope)` - `Local<v8::String>::write_utf8` → `write_utf8_v2(buf, WriteFlags)` - `anyhow::Error` → `deno_error::JsErrorBox` for op2 errors - `extension!`'s `init_ops()` → `init()` - `ModuleLoader::load` 4 params → 5 (new `ModuleLoadOptions` shape) - `ModuleLoader::resolve` returns `ModuleLoaderError` FFI surfaces preserved — perry-runtime/codegen/commands unchanged. ## Stack-limit override removed Pre-bump `JsRuntimeState::new` called `Isolate::SetStackLimit` via the Itanium-mangled `_ZN2v87Isolate13SetStackLimitEm` (since v8 0.106 didn't expose it in the Rust bindings). After the bump, calling that symbol while the isolate is not entered silently exits the process with exit code 0 — v8 147's stack-guard aborts cleanly instead of crashing. That's what was killing the three parity tests (test_issue_248_phase2_js_interop, test_issue_248_phase2b_js_callback, test_issue_255_jsruntime_reentrancy) — every test that loaded a .js module called js_runtime_init → JsRuntimeState::new → silent exit. Removed the manual override entirely; v8 147 picks a sane default stack limit from the calling thread's bounds and deno_core::scope! pins the isolate properly for each work scope. ## Validation - cargo build --release -p perry-jsruntime -p perry-runtime -p perry-stdlib -p perry clean - cargo test --release -p perry --bin perry — 152/0 - cargo fmt --all -- --check clean - All three previously-failing parity tests byte-equal Node
1 parent a449dc3 commit 72e695c

13 files changed

Lines changed: 586 additions & 438 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Detailed changelog for Perry. See CLAUDE.md for concise summaries.
44

5+
## v0.5.899 — deps: bump `toml` 0.8 → 1.1.2+spec-1.1.0 (#727) and `deno_core` 0.311 → 0.400 (#729). The two dependabot bumps that the v0.5.894 batch deferred as "real breakage" — addressed here with focused migrations rather than dropping them. **toml 1.x.** `<toml::Value as FromStr>` (called implicitly by `.parse::<toml::Value>()`) is no longer a document parser in 1.x — it's an inline-value parser that rejects anything with leading comments or whitespace ("unexpected content, expected nothing" at line 1, column 1 of `well_known_bindings.toml`, on every TOML file in the codebase that opens with the standard banner). Four callsites updated: `crates/perry/src/commands/compile/well_known.rs:100` (the well-known native-bindings registry — the loud one), `crates/perry/src/commands/run.rs:343`/`829`/`1453` (`perry.toml` reads for the `publish.exclude` list, icon source, and iOS bundle id), `crates/perry/src/commands/native/validate.rs:164` (`Cargo.toml` `[package].name` lookup for `perry native validate`). All swapped from `.parse::<toml::Value>()` to `toml::from_str::<toml::Value>(s)` — the crate-level `toml::from_str` still goes through the document deserializer + returns a `Value::Table`, which is the shape every callsite already expected to walk. Callsites that already use `.parse::<toml::Table>()` (the rest of `commands/setup.rs`, `commands/compile.rs`, `commands/i18n.rs`) are unaffected — `Table::from_str` still calls `crate::from_str` internally. Restores the 7 previously-failing tests (`shipped_toml_parses`, `dotenv_is_registered`, `node_prefix_stripped_on_lookup`, `unknown_package_returns_none`, `every_entry_references_a_workspace_crate`, `parser_rejects_missing_crate_field`, `read_crate_name_works`) and fixes the 31 compile-smoke regressions that were all the same panic surfacing from a different test path. **deno_core 0.400.** 89-patch jump that pulls in v8 0.106 → 147.4.0, a fundamental V8 binding revision: `v8::HandleScope` is replaced by `v8::PinScope` in nearly every signature; `JsRuntime::handle_scope()` is gone in favor of the `deno_core::scope!(scope, &mut runtime)` macro (which pins on the stack frame); `v8::TryCatch::new(scope)` returns a `ScopeStorage` that needs `v8::tc_scope!(tc_scope, scope)` to pin into a usable `&mut PinScope`; `Local<v8::String>::write_utf8` is replaced by `write_utf8_v2(buf, WriteFlags)` (different signature: drops the processed-chars `&mut usize` slot from the middle and uses `WriteFlags` instead of `WriteOptions`); `anyhow::Error` no longer implements `JsErrorClass` — op2-returning functions need `deno_error::JsErrorBox` or a typed wrapper; `extension!`'s `init_ops()` is renamed `init()`; `ModuleLoader::load` grew a 5th param via the new `ModuleLoadOptions` struct and `ModuleLoadReferrer` shape; `ModuleLoader::resolve` returns `ModuleLoaderError` (= `JsErrorBox`) instead of `AnyError`. Migration touches 5 source files in perry-jsruntime (`bridge.rs`, `interop.rs`, `lib.rs`, `modules.rs`, `ops.rs`) plus its `Cargo.toml` (adds `deno_error = "0.7"` as a direct dep so `JsErrorBox` is importable — was a transitive in 0.311). All `#[no_mangle] pub extern "C"` FFI surfaces keep identical signatures so perry-runtime, perry-codegen, and perry/src/commands keep their existing call shapes unchanged. The issue #255 re-entrancy escape hatch (`stash_trampoline_scope` / `try_trampoline_scope` — the `REENTRY_SCOPE_PTR` raw-pointer stash mechanism for crossing the V8 ↔ native trampoline) is preserved byte-for-byte at the memory level; only the typed view of the pointer migrates from `HandleScope` to `PinScope`. **Stack-limit override removed.** Pre-bump `JsRuntimeState::new` called `Isolate::SetStackLimit` (via the Itanium-mangled `_ZN2v87Isolate13SetStackLimitEm` because the v8 0.106 Rust bindings didn't expose it) right after `JsRuntime::new` returned, to fix arm64 SIGBUS on deep call chains. After the bump, calling that same exported symbol while the isolate is not entered (no `Isolate::Scope` on the stack) silently exits the process with code 0 — v8 147's stack-guard internals abort cleanly instead of crashing. The manifest of the silent-exit was the three parity tests (`test_issue_248_phase2_js_interop`, `test_issue_248_phase2b_js_callback`, `test_issue_255_jsruntime_reentrancy`) producing no output: every test that loaded a `.js` module called `js_runtime_init` (which constructed `JsRuntimeState` which hit the stack-limit setter and exited). Removed the manual override entirely — v8 147 picks a sane default stack limit from the calling thread's stack bounds and `deno_core::scope!` pins the isolate properly for each work scope, so the override is no longer needed. **Validation.** `cargo build --release -p perry-jsruntime -p perry-runtime -p perry-stdlib -p perry` clean. `cargo test --release -p perry --bin perry` 152 passed, 0 failed (was failing 7 tests pre-fix on the toml side). All three previously-failing parity tests now byte-equal Node. **Version-bump note.** Renumbered from v0.5.895 to v0.5.899 after #735 landed as v0.5.895/896/897/898 on main while this PR was in CI (four-patch sequence for #665 follow-ups).
6+
57
## v0.5.898 — fix(hir): #665 — `compilePackages`-opted-in packages no longer trigger native-instance lowering, so `instance.method` returns a function instead of `number 0`. **Symptom.** With v0.5.895 (resolver opt-in), v0.5.896 (codegen side-map), and v0.5.897 (setTimeout trailing args) all applied, rate-limiter-flexible compiled and ran, but a probe reading `typeof limiter.consume` returned `"number"` instead of `"function"`. The value bits passed to `js_value_typeof` were `0x0000000000000000` (literal zero) — perry was evaluating `limiter.consume` as a zero-arg method *call* that returned 0.0, not as a *property read* yielding the function reference. Confusingly, `console.log("limiter.consume:", limiter.consume)` printed `[Function (anonymous)]`, so the same expression was being lowered consistently but its runtime path diverged. **Root cause.** Two-step trap. (1) `crates/perry-hir/src/ir.rs::is_native_module` only consulted the NATIVE_MODULES manifest. `rate-limiter-flexible` is in that manifest (the `perry-ext-ratelimit` binding exists, even if incomplete), so during HIR lowering the import statement was tagged `is_native = true`. (2) HIR lowering chain: `register_native_module(limiter_alias, "rate-limiter-flexible", ...)` → `new RateLimiterMemory(...)` in lower.rs:3848 → `lookup_native_module("RateLimiterMemory")` returns Some → `register_native_instance(limiter, "rate-limiter-flexible", "RateLimiterMemory")` → every subsequent `limiter.<prop>` member access in `expr_member::lower_member:475` matches the "native instance" arm and lowers as `NativeMethodCall { module, object: Some(limiter), method: <prop>, args: vec![] }` — a zero-arg FFI-getter call, on the assumption that `obj.prop` on a native binding is implemented as a getter function. For genuine FFI bindings (req.method on http.IncomingMessage etc.) this is correct. For a class compiled from JS source via cjs_wrap, it is wrong: `consume` is a real method, not a getter. The codegen routes the NativeMethodCall through `js_native_call_method` for `rate-limiter-flexible.<method>` — finds no matching FFI entry — and returns `0.0`. `typeof 0.0` is `"number"`. **Fix.** Two coordinated changes. (1) `crates/perry-hir/src/ir.rs`: add a thread-local `COMPILE_PACKAGES_OVERRIDE: RefCell<HashSet<String>>` plus setter/clearer (`set_compile_packages_override` / `clear_compile_packages_override`). Update `is_native_module` to: parse the package name out of the path via a new `package_name_of` helper (handles `node:` prefix + `@scope/pkg/subpath` scoped form), then return false when the package is in the override set even if it appears in NATIVE_MODULES. (2) `crates/perry/src/commands/compile/collect_modules.rs`: wrap the `lower_module_full` call with `set_compile_packages_override(ctx.compile_packages.clone())` before and `clear_compile_packages_override()` after. The thread-local is rayon-safe (each worker thread has its own copy) and cleared promptly so it can't leak to unrelated work. Together: HIR's import lowering now sees `is_native = false` for compile-package-opted-in packages, `register_native_module` and `register_native_instance` don't fire, `expr_member::lower_member` falls through to the regular property-access path, and codegen's static-class fast path at `expr.rs:3742-3772` emits `js_class_method_bind` — a bound-method closure NaN-boxed with POINTER_TAG. **Validation.** rate-limiter-flexible probe: `typeof limiter.consume: function` ✓ (was "number"); `limiter.consume: [Function (anonymous)]` unchanged ✓; `limiter.points: 3` and `limiter.duration: 60` unchanged ✓. Full workspace test suite exit 0. **Scope.** Closes the last user-visible gap from the #665 investigation. With this commit, `import { RateLimiterMemory } from "rate-limiter-flexible"; new RateLimiterMemory(...)` produces an instance whose methods are reachable as function values — the original blocking shape from the issue's repro. **Out of scope.** Calling `await limiter.consume(...)` end-to-end has to go through the bound-method-closure dispatch via `js_native_call_method`'s CLASS_VTABLE_REGISTRY lookup; that path works for the drizzle and hono fixtures so it should work here, but I haven't probed it through the full Promise resolution chain. The thread-local-based approach is the pragmatic choice over threading `compile_packages` through every HIR lowering API; if perry ever drops single-threaded sections of HIR lowering, the locking semantics may need to be revisited.
68

79
## v0.5.897 — fix(codegen,runtime): #665 — `setTimeout(fn, delay, ...args)` with trailing args no longer leaves a bare `_setTimeout` symbol at link time; trailing args are now forwarded to the callback. **Symptom.** With v0.5.895 (resolver opt-in) and v0.5.896 (default-import codegen collision) both applied, the rate-limiter-flexible compile reached the link stage cleanly, then failed with `Undefined symbols for architecture arm64: "_setTimeout", referenced from: _perry_closure_node_modules_rate_limiter_flexible_lib_RateLimiterMemory_js__1`. Discovering call site: `RateLimiterMemory.consume()` does `setTimeout(resolve, delay, res)` inside its Promise executor (per ECMA-262 §27.5.4.1, the trailing arg is forwarded to `resolve` so the Promise resolves with `res`). **Root cause.** `crates/perry-codegen/src/lower_call.rs:742` had a single `"setTimeout" if args.len() == 2` arm routing the 2-arg shape to `js_set_timeout_callback(callback, delay)`. Any other arity (including the spec-defined `setTimeout(fn, delay, ...args)` shape) fell through to the generic ExternFuncRef fallthrough, which emits `call double @setTimeout(...)` against a symbol nothing in stdlib defines. clang prefixes `_` on macOS → linker looks for `_setTimeout` → no such symbol. The 2-arg-only restriction had been the gate since the codegen arm was first written; until now, every real-world setTimeout call site Perry had been tested against used exactly 2 args. **Fix.** Three coordinated changes. (1) `crates/perry-runtime/src/timer.rs`: extend `CallbackTimer` with `args: Vec<f64>` (NaN-boxed JSValues), defaulting to empty for the 2-arg shape (back-compat). Add new runtime entry `js_set_timeout_callback_args(callback: i64, delay_ms: f64, args_ptr: *const f64, n_args: i32) -> i64` that copies the buffer into the timer record before returning. `js_callback_timer_tick` dispatches via `match timer.args.len()` to `js_closure_call0`..`js_closure_call8`, clamping at 9 (covers any realistic trailing-args count). (2) `crates/perry-codegen/src/runtime_decls.rs`: declare the new symbol `js_set_timeout_callback_args(I64, DOUBLE, PTR, I32) -> I64`. (3) `crates/perry-codegen/src/lower_call.rs`: add a second `"setTimeout" if args.len() >= 3` arm. Allocate a stack buffer of `n = args.len() - 2` doubles via `alloca_entry_array`, store each lowered trailing arg into its slot, GEP for the base pointer, and call `js_set_timeout_callback_args`. The existing 2-arg arm is untouched. **Validation.** rate-limiter-flexible now compiles, links, AND runs end-to-end. Standalone probe `setTimeout((a,b)=>log("fired:",a,b), 100, "hello", 42)` prints `fired: hello 42` byte-equal to Bun. Workspace tests green. **Scope.** Closes the link-stage blocker. **Not in scope.** Calling `await limiter.consume(...)` end-to-end through Promise resolution — bound-method dispatch via CLASS_VTABLE_REGISTRY works for drizzle/hono so should work here. Support for >9 trailing args to setTimeout (would need a variadic-dispatcher closure-call helper); real-world JS rarely exceeds 1-2 trailing args. **Why `_setTimeout` not `setTimeout`.** The macOS Mach-O ABI prefixes C-extern symbol names with `_` (`__USER_LABEL_PREFIX__`); the linker error reports the prefixed form.

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
88

99
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.
1010

11-
**Current Version:** 0.5.898
11+
**Current Version:** 0.5.899
1212

1313

1414
## TypeScript Parity Status

0 commit comments

Comments
 (0)