Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
0fe3fb2
docs(runtime): remove stale tombstone default claim
Sep 4, 2026
d2b36da
docs(changelog): key tombstone comment fix to PR 9747
Sep 4, 2026
7cf8b6a
fix(runtime): match Node radix digit selection
Sep 4, 2026
7ecd3c3
docs(changelog): key radix fix to PR 9746
Sep 4, 2026
3645da9
fix(cache): register concat switch and explain codegen inputs
Sep 4, 2026
69b994d
docs(changelog): key cache guidance fix to PR 9748
Sep 4, 2026
a819ce7
build: let PERRY_OBJECT_CACHE_BUILD_ID pin the object-cache build id
Sep 4, 2026
492c9c2
test: pin the PERRY_OBJECT_CACHE_BUILD_ID parser
Sep 4, 2026
e34ea02
test(runtime): isolate process-global fixtures for parallel runs
Sep 5, 2026
749f1c4
docs: associate isolated runtime fixtures with PR 9767
Sep 5, 2026
b9bee03
fix(runtime): share typed Linux pthread stack bounds
Sep 4, 2026
e493648
docs(changelog): key Linux stack fix to PR 9752
Sep 4, 2026
c520728
fix(hir): recognize platform-provided Bun diagnostics
Sep 4, 2026
30c8afc
docs(changelog): key Bun diagnostics fragment to PR 9754
Sep 4, 2026
6521e76
fix(runtime): preserve dynamic heritage constructor receiver
Sep 4, 2026
7255924
docs(changelog): note dynamic heritage receiver fix
Sep 4, 2026
a1fc362
fix: preserve ordinary prototype property assignments
Sep 4, 2026
a2ad6ff
docs: number changelog fragment for PR 9757
Sep 4, 2026
3a2de9f
fix(codegen): preserve imported class expression property reads
Sep 4, 2026
4cf9ba9
docs: number changelog fragment for PR 9758
Sep 4, 2026
4f27685
fix: preserve per-evaluation class heritage in factories
Sep 4, 2026
324058c
docs: key class heritage changelog to PR 9753
Sep 4, 2026
2479f26
fix(transform): retain imported class dependencies when inlining
Sep 5, 2026
4258571
docs: associate class inlining changelog with PR 9766
Sep 5, 2026
6f897d3
fix: preserve forward closure initializers and TDZ names
Sep 5, 2026
87d5cef
docs: key changelog fragment to PR 9762
Sep 5, 2026
63d0c2a
fix(bun): dispatch import.meta.require synchronously
Sep 5, 2026
7a11bc8
docs: key changelog fragment to PR 9761
Sep 5, 2026
e731238
feat: expose Perry heap statistics through bun:jsc
Sep 5, 2026
f15d3a9
chore: number the heapStats changelog for PR 9760
Sep 5, 2026
8026842
perf(object): borrow ASCII property keys unchecked; ask the async-res…
Sep 5, 2026
bb097e2
perf(regex): content-keyed construction cache, header-authoritative p…
Sep 4, 2026
3c775cb
fix(gc): audit raw TLS holders and pin census window
Sep 4, 2026
c021dd1
docs(changelog): key raw TLS audit fragment to PR 9750
Sep 4, 2026
52deb64
refactor(regex): split flags validation and pattern escaping into sib…
Sep 5, 2026
ef9fb4f
fix: resolve bounded local helper chains for Worker URLs
Sep 5, 2026
ef5f770
chore: number the Worker helper changelog for PR 9759
Sep 5, 2026
59174d3
fix: restore WebSocket server upgrades and ephemeral addresses
Sep 5, 2026
3aabb52
docs: number the WebSocket upgrade changelog for PR 9763
Sep 5, 2026
25b7745
fix(gates): gate the regex flags module and regenerate API docs
Sep 5, 2026
cdba79b
fix(gates): resolve the five gate failures across train124
Sep 5, 2026
3a99466
revert: drop #9762 — regresses tdz_numeric_const_read_is_not_constant…
Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ to the same physical root still deduplicate by canonical path.
### LLVM Type Mismatches
- Loop counter optimization produces i32 — always convert before passing to f64/i64 functions
- Constructor parameters always f64 (NaN-boxed) at signature level
- A new `PERRY_*` environment variable read in codegen must be added to `BUILD_CACHE_ENV_VARS` in `crates/perry/src/commands/compile/build_cache.rs`, or to `BUILD_CACHE_ENV_EXCLUSIONS` with a reason it cannot change emitted code. Otherwise cached objects can silently serve a different setting. Run `cargo test -p perry codegen_env_vars_are_build_cache_inputs` to check registration.

### Async / Threading
- Thread-local arenas: JSValues from tokio workers invalid on main thread
Expand Down
10 changes: 10 additions & 0 deletions changelog.d/9746-number-radix-formatting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Fix non-decimal `Number.prototype.toString(radix)` digit selection. Large
integers now zero-fill digits beyond the double's precision and subtract
remainders before dividing, matching Node for cases such as
`(1e21).toString(36)` and `(9007199254740994).toString(3)`. Fractional conversion
also rounds the final digit when the residual is within the stopping tolerance,
fixing `(0.1).toString(36)`.

Runtime regressions and an end-to-end fixture cover all radices, both signs,
the 2^53 boundary, large exponents, fractional values, dynamic calls, and boxed
numbers. The original formatter fails the new large-integer regression.
3 changes: 3 additions & 0 deletions changelog.d/9747-tombstone-default-comment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Remove the stale default-OFF claim above the object tombstone-delete gate.
The default and its rationale remain documented alongside the environment
parsing, avoiding conflicting descriptions of the shipping behavior.
7 changes: 7 additions & 0 deletions changelog.d/9748-codegen-cache-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Make the codegen environment-variable registration failure name the registry
file, the input and exclusion declaration anchors, and the command to rerun.
Document the cache-registration requirement in the contributor guidance and
beside the OnceLock reader pattern so new switches are registered when added.
The existing missing-input and stale-exclusion checks remain enforced.
Also register `PERRY_CONCAT_SITE_CACHE`, another omission found by running the
gate: toggling its generated concatenation tables must invalidate the cache.
9 changes: 9 additions & 0 deletions changelog.d/9749-dynamic-heritage-this.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
**Dynamic-heritage classes now retain the receiver created by `super()` when
their runtime superclass resolves to `Object`.**

Perry replayed these constructors against a provisional instance but discarded
the constructor's effective return value. Writes after `super()` therefore
landed on the new receiver while `new` returned the abandoned one. Dynamic
construction now propagates the replacement receiver and preserves its derived
prototype and per-evaluation private brand, matching Node for both shared class
references and fresh captured class expressions.
3 changes: 3 additions & 0 deletions changelog.d/9750-raw-tls-holder-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed
- Include raw `thread_local!` declarations in the GC holder inventory, so skipping Perry's TLS convention cannot hide a new opaque holder. Existing uncovered declarations join the identity ratchet as explicit audit debt.
- Give the GC census's deliberately untraced address snapshot a source-pinned, non-moving collection-window contract, and move its production TLS into the hot TLS registry.
2 changes: 2 additions & 0 deletions changelog.d/9752-linux-pthread-stack-attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Fixed
- Use libc's typed pthread attributes for Linux GC and error-stack bounds, fixing conflicting declarations in the warnings gate and replacing manually sized attribute buffers with correctly aligned storage.
7 changes: 7 additions & 0 deletions changelog.d/9753-factory-class-heritage-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Fixed

- Function-body class declarations with dynamic heritage now create a distinct
class for each evaluation, even when their bodies capture no locals. Chained
factories preserve their evaluated superclass and per-class static state.
Prototype reflection, `instanceof`, and inherited method lookup follow each
evaluation's own prototype chain instead of the shared template (#9502).
2 changes: 2 additions & 0 deletions changelog.d/9754-bun-global-diagnostics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Fixed
- Recognize the platform-provided `Bun` global in Bun-mode diagnostics while preserving runtime namespace lookup, lexical shadowing, and default-platform warnings.
1 change: 1 addition & 0 deletions changelog.d/9757-ordinary-prototype-stores.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix statically named `.prototype` assignments on ordinary objects when the receiver is a function parameter or has previously received a computed-key write (#9365). These assignments now use ordinary property semantics, including accessors, proxies, and strict-mode write failures, while preserving function prototype metadata for derived classes. Evaluate the receiver once and keep it rooted while evaluating the assigned value.
1 change: 1 addition & 0 deletions changelog.d/9758-imported-class-expression-prototypes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix property reads on imported class-expression bindings, including `.prototype` returning `undefined` across module boundaries (#9366). Imported variables now use the ordinary property dispatcher after loading their current value, preserving class tags and live bindings through renamed re-exports.
2 changes: 2 additions & 0 deletions changelog.d/9759-static-worker-url-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Fixes
- Resolve bounded module-local helper chains used as Worker entry URLs, including Bun embedded file URLs and `node:worker_threads` constructors. Substitute static string/URL arguments while rejecting effectful, mutable, recursive, opaque or over-budget helper expressions. Keep evaluating the original filename expression when constructing the Worker. Fixes #9744.
2 changes: 2 additions & 0 deletions changelog.d/9760-bun-jsc-heap-stats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Fixes
- Implement `bun:jsc.heapStats()` and `heapStats(true)` across static imports, dynamic imports, and `require`. Reports contain Perry's per-thread heap and allocator counters, including type and pinned-cell counts, with JavaScriptCore differences documented. Fixes #9743.
2 changes: 2 additions & 0 deletions changelog.d/9761-import-meta-require.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Fixes
- Lower direct and computed-literal `import.meta.require()` calls through synchronous compiled-module dispatch. Relative and Bun virtual chunk paths are discovered ahead of time, return their namespace immediately, and initialize once when loaded. Fixes #9742.
4 changes: 4 additions & 0 deletions changelog.d/9763-websocket-server-upgrades.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Fixed
- Attach native `WebSocketServer({ server })` instances to an existing HTTP listener; deliver manual `handleUpgrade` callbacks and connection events with usable client handles and the original request.
- Bind `WebSocketServer({ port: 0 })` to an ephemeral port and expose the actual listening address through `address()`.
- Treat native handle IDs as identities when hashing Sets, including `WebSocketServer.clients`.
3 changes: 3 additions & 0 deletions changelog.d/9766-transitive-class-inlining.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Keep cross-module helpers and methods that depend on imported classes in their source module, preserving constructors, methods, and iterators. Fixes the three failing codehz/ecs comprehensive performance tests (#9023).
3 changes: 3 additions & 0 deletions changelog.d/9767-isolated-runtime-fixtures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Isolate runtime test fixtures that inspect loaded libraries, process-wide box counters, and the composed symbol cache. Prevent neighboring tests from corrupting their assertions, and tighten the box reuse bound (#9197).
24 changes: 24 additions & 0 deletions changelog.d/9768-train124-gate-followups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
**Gate follow-ups for the 20-PR train.** Five gates went red together; each is
fixed at the source rather than baselined away.

- `ic_miss.rs`'s new `#[cold]` IC-miss diagnostic guarded a key pointer with a
bare `< 0x1000` floor, which admits the whole handle band — real addresses on
Linux, hidden by macOS's higher mmap base (#9219 class). It now asks
`addr_class::is_above_handle_band`.
- Six raw-handle reads in `bun_compat/jsc.rs`, `class_registry/construct.rs` and
`construct/class_object.rs` moved into `with_mut_ptr` (#7341).
- `IC_DIAG` and `REGEX_DIAG` are classified. Both are off-by-default diagnostics;
`REGEX_DIAG`'s entry records that its `per_pattern` key is a pattern
`StringHeader` address used only as an opaque grouping id — never
dereferenced — and that a recycled address merges two rows' counters.
- `PASS1_MARKED`'s `non_moving_snapshot` pin was re-audited after #9760 touched
`gc/mod.rs`. That change is `mod heap_stats;` plus a re-export and alters no
mark/sweep control flow; `heap_stats()` runs only from the JS-facing
`bun:jsc.heapStats()`, never inside a cycle. The window is unchanged, so only
that one file's digest was re-pinned.
- The string payload-access ratchet moved the good way (358 → 353 sites) and its
baseline is recorded.

`regex.rs` also crossed the 2000-line cap, so `escape_regexp_source` moved to the
existing `regex/escape.rs` and flags validation to a new `regex/flags.rs`, both
gated on `regex-engine` like their siblings.
28 changes: 28 additions & 0 deletions changelog.d/keystroke-property-key-decode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
### Performance

- **Property reads no longer UTF-8-validate ASCII keys, and no longer copy
the key or take the async-resource registry lock for ordinary receivers.**
The generic read ladder decodes the key `StringHeader` at several layers
per miss (`js_object_get_field_ic_miss`, closure expando lookup, accessor
and reflection probes, the typed-feedback class-field guards, async-
resource dispatch); `core::str::from_utf8` on those decodes was 2 % of the
claude-code keystroke profile, the guard's `String` copy was a `malloc`
per guarded class-field access, and `async_resource_property` copied the
key and locked the registry before asking whether any AsyncResource
handle existed at all.

- `crates/perry-runtime/src/string/mod.rs` — `header_str_checked`: a
header whose `utf16_len == byte_len` is pure ASCII, so it is borrowed
unchecked; anything else takes the `from_utf8` scan it always took
(WTF-8 payloads still answer `None`). Used by `has_own_helpers`,
`closure_dynamic_prop_by_key`, the accessor probes,
`typedarray_props::string_header_str` and the typed-feedback guards
(which now borrow instead of allocating a `String`; every consumer is a
Rust-side table read, so the payload cannot move while borrowed).
- `crates/perry-runtime/src/async_hooks.rs` — `is_async_resource_handle`
answers from the atomic handle count before touching the mutex, and the
IC-miss handler / `async_resource_property` ask it before decoding or
copying the key.

Test: `header_str_checked_matches_from_utf8_on_every_payload_class`
(ASCII, non-ASCII scalar, lone surrogate, empty).
43 changes: 43 additions & 0 deletions changelog.d/keystroke-regex-site-cache.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
### Performance

- **RegExp construction and exec no longer hash or copy the pattern text.**
On the claude-code TUI a keystroke re-runs ink's layout, whose text
measurement (`string-width` / `emoji-regex` / `ansi-regex`) evaluates a
regex literal per text segment — `emojiRegex()` is a fresh ~12 KB `/…/g`
per call. Each `js_regexp_new` copied that pattern three times and
SipHashed it once (the `VALIDATED_PATTERNS` probe key, `owned_pattern`,
the `REGEX_SOURCE_TABLE` entry); the first operation on each header did
the same three more times in `build_and_install_programs`; and, for the
common pattern with no fancy fallback, `lookup_fancy_regex` and
`lookup_repeat_matcher` fell through to a full clone + hash of the pattern
on EVERY exec. SipHash over pattern text was 31 % of the main thread in
the 20 s after a 400-char reply had rendered (regex 38 % inclusive).

- `crates/perry-runtime/src/regex/site_cache.rs` (new) — a thread-local,
content-keyed construction cache: a cheap fingerprint (length, three
8-byte windows, canonical flags) plus a full byte compare, so identity
never depends on an address. A hit skips validation (validity is a pure
function of the pair), shares the owned pattern/flags as `Arc<str>`, and
installs the programs the first executed header compiled — the new
header is born built and never touches the `(pattern, flags)` caches.
Kill switch `PERRY_REGEX_SITE_CACHE=0`.
- `regex.rs` — `lookup_fancy_regex` / `lookup_repeat_matcher` treat a
built header as authoritative (a null program pointer after the build
IS the answer; every install path publishes all three together), so no
per-exec cache probe remains. `REGEX_SOURCE_TABLE` holds `Arc<str>`
pairs; the two address-keyed regex tables use the pointer hasher.
- `regex/exec.rs` — `test` on a global/sticky receiver runs
`regexp_find_advancing`, the find-only twin of `exec`'s engine phase
(same engine order, `lastIndex` advance/reset and sticky anchoring),
instead of materializing a captures array plus one string per capture
that it then discarded.
- `hot_diag.rs` (new) — `PERRY_REGEX_DIAG=<path>` (constructions,
validated/site hits, pattern bytes, compiles, cache clears, lazy builds,
exec/test/match/replace counts, capture bytes, per-pattern table) and
`PERRY_IC_DIAG=<path>` (property-read IC misses by reason and by site).
Snapshots every ~1 s of activity; diagnostic only.

Tests: `site_cache_reconstruction_is_born_built` (fails without the
cache) and `global_test_advances_and_resets_last_index` (every
`lastIndex` branch of the find-only path, all three engines, UTF-16
units) in `regex/tests.rs`.
7 changes: 7 additions & 0 deletions changelog.d/object-cache-build-id-override.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Build

- `PERRY_OBJECT_CACHE_BUILD_ID=<hex>` pins the build-id component of the
per-module object-cache key, so a `perry` built from a runtime-only branch
can reuse the objects a sibling build cached under the same HIR and options
and go straight to the link. Codegen changes still miss through the HIR and
option fields of the key; an unparsable value is ignored.
2 changes: 2 additions & 0 deletions crates/perry-api-manifest/src/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub const NATIVE_MODULES: &[&str] = &[
// #6562: Bun FFI (C-ABI). The `bun:` prefix is part of the specifier
// (unlike `node:`, which is stripped) — `import { dlopen } from "bun:ffi"`.
"bun:ffi",
"bun:jsc",
"ffi", // node:ffi (the node: prefix is normalized away)
"bun:sqlite", // Bun facade over Perry's native SQLite engine
"node-cron", // cron-style scheduler (npm node-cron; aliases `cron`)
Expand Down Expand Up @@ -245,6 +246,7 @@ pub const RUNTIME_ONLY_MODULES: &[&str] = &[
"buffer",
// #6562: bun:ffi is implemented entirely in perry-runtime.
"bun:ffi",
"bun:jsc",
"ffi",
"assert",
"assert/strict",
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-api-manifest/src/entries/part_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[
// #1113 — `wss.handleUpgrade(req, socket, head, cb)` for a
// `new WebSocketServer({ noServer: true })`.
method("ws", "handleUpgrade", true, None),
method("ws", "address", true, None),
method("ws", "emit", true, None),
// Issue #577 Phase 4 — Client-class methods for the upgrade-path wsId.
method("ws", "on", true, Some("Client")),
method("ws", "addListener", true, Some("Client")),
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-api-manifest/src/entries/part_4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,8 @@ pub(crate) const API_MANIFEST_PART_4: &[ApiEntry] = &[
method("bun", "zstdDecompress", false, None),
method("bun", "zstdDecompressSync", false, None),
method("bun", "gc", false, None),
// Perry heap/allocator statistics; numeric approximations are documented.
method("bun:jsc", "heapStats", false, None),
method("bun", "generateHeapSnapshot", false, None),
method("bun", "file", false, None),
method("bun", "write", false, None),
Expand Down
6 changes: 6 additions & 0 deletions crates/perry-codegen/src/codegen/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ pub(crate) fn function_body_returns_generator_object(body: &[perry_hir::Stmt]) -
/// Cached at first call so subsequent compile_* calls skip the
/// env-var lookup.
///
/// When adding a `PERRY_*` reader using this pattern, register it in
/// `BUILD_CACHE_ENV_VARS` in `crates/perry/src/commands/compile/build_cache.rs`.
/// Only readers that cannot change emitted code belong in that file's
/// `BUILD_CACHE_ENV_EXCLUSIONS`, with a reason. The OnceLock caches the reader;
/// the registry keeps compiled objects from being reused across settings.
///
/// Why on by default now: the shadow stack precisely covers every
/// pointer-typed local in compiled JS frames, complementing the
/// conservative C-stack scan. With Phase A complete and the GC
Expand Down
7 changes: 4 additions & 3 deletions crates/perry-codegen/src/collectors/scalar_method_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,10 @@ fn note_prototype_effect(
}
// Function-classic prototypes are keyed by a synthetic class id derived
// from the closure value, and `new <func>()` lowers to `NewDynamic`, so
// these cannot rewrite a declared class's table. `SetFunctionPrototype`
// installs a whole prototype object for such a function — same story.
Expr::RegisterFunctionPrototypeMethod { .. } | Expr::SetFunctionPrototype { .. } => {}
// these cannot rewrite a declared class's table.
Expr::RegisterFunctionPrototypeMethod { .. } => {}
// #9365: this node also performs ordinary stores on arbitrary receivers.
Expr::SetFunctionPrototype { func, .. } => note_prototype_holder(func, facts),
// Any expression that so much as NAMES a prototype object: the value
// can be aliased into a local and written through later.
Expr::PropertyGet {
Expand Down
Loading
Loading