Versioned consumer ABI profiles
On this page 5
These profiles describe exact downstream consumers. A profile is supported only when its checked-in symbol/type contract, compile-link-runtime fixture, and revision checks pass. Unknown profile IDs and mismatched source revisions are errors; zig-js does not silently approximate a moving private ABI.
Home public C profile
home-public-c-7ed99c02 pins Home revision
7ed99c02e50034f869d0db6d487115bb44332fe4. Its M1 Zig embedding layer declares
50 public JavaScriptCore C functions. The profile records all 50 functions, the
C calling convention, target-native pointer/usize layout, opaque pointer
types, enum backing types and values, source hashes, and semantic lifetime
assumptions.
Run the fast checked-in audit and real Zig consumer fixture with:
zig build home-public-abi-audit
zig build test-home-public-abi
When the pinned Home checkout is available, also prove that its revision, source hashes, and declarations still match:
zig build home-public-abi-audit -Dhome-source-root="$HOME/Code/Home/lang"
This profile is deliberately public_c_embedding. It proves only that the
pinned Home public consumer compiles, links, and runs against zig-js. It does
not claim support for Home's JSC__*, Bun__*, generated-class, JSC object
layout, LLInt, or other private interfaces; those require separate private_abi
profiles tracked by GitHub issues #140, #163, and #164.
Home private inventory
home-private-7ed99c02 uses a comment/string-aware scanner over the pinned
Home packages/runtime/src/jsc tree. It records each unique symbol declared as
extern fn or extern "c"/"C" fn, including alternate declaration sites,
normalized signatures and digests, classification, status, and effective
calling convention. The exact current denominator is:
| Classification | Symbols |
|---|---|
| Private JSC/Bun/WebCore ABI under #163 | 447 (447 implemented, 0 pending) |
| Overlap with zig-js's completed public C target | 59 |
| Platform libc imports | 7 |
| Consumer-provided definitions | 25 |
| Total | 538 |
The 538 symbols come from 66 pinned source files; 15 repeated imports retain
alternate declaration provenance and none is unclassified. Of the canonical
contracts, 442 use default C linkage, 91 explicitly link c/C, four spell
.c, and one uses Home's jsc.conv.
zig build home-private-abi-audit
zig build home-private-abi-audit -Dhome-source-root="$HOME/Code/Home/lang"
This inventory is the denominator, not a claim that the whole surface works.
All 447 private entries are implemented with pinned type/layout contracts,
shims, and consumer evidence under #163.
The consumer-provider contract
pins 24 Bun/Home host definitions and rejects duplicate zig-js exports.
JSFunctionCall is the 25th external entry: each runtime-generated FFI module
defines it and resolves it locally after relocation.
Home revisions 5e829ad483bb9e5ccb19766997df6462edd8e167,
38702f9e43b3aecbee7d5b7aa48cc66d41cabde7, and
4389ddeea0445f28f400f86995435b473b8aa167 are supported as the explicit
home-private-5e829ad4, home-private-38702f9e, and
home-private-4389ddee aliases. None is a silent
repin: the full packages/runtime/src/jsc diff against 7ed99c02 has zero
changed files, and each audit rechecks all 66 source hashes plus all 538
normalized symbol contracts, alternate declarations, classifications, and
calling conventions against the immutable base inventory. All three alias manifests report zero
additions, removals, signature changes, and calling-convention changes.
zig build test-home-private-abi \
-Dhome-private-abi-profile=home-private-4389ddee \
-Dhome-source-root="$HOME/Code/Home/lang"
JSC64 value boundary
Private shims must use private_abi.EncodedValue; they must never expose
zig-js's internal Value.rawBits() as though it were JavaScriptCore. The two
types are both eight-byte NaN boxes but intentionally use different tags.
The pinned boundary implements the Home/Bun JSC64 encodings exactly:
| Value | Encoded word |
|---|---|
| empty / thrown sentinel | 0x0 |
| null | 0x2 |
| deleted-property sentinel | 0x4 |
| false / true | 0x6 / 0x7 |
| undefined | 0xa |
| int32 tag | 0xfffe000000000000 | uint32(payload) |
| double | raw IEEE-754 bits plus 1 << 49, wrapping |
| cell | aligned non-null pointer word |
zig build test-private-abi-value runs the focused layout/codec tests and a
real bridge executable against js.Value. It covers the complete signed int32
range boundaries, finite doubles, infinities, negative zero, positive and
negative noncanonical NaNs, cell validation, every primitive conversion, and
the requirement that string/object conversion first acquire an external cell
handle.
The first completed shim slices export the pinned signatures for
JSC__JSValue__eqlCell, JSC__JSValue__eqlValue,
JSC__JSValue__toBoolean, JSC__JSValue__toInt32,
JSC__JSValue__fromInt64NoTruncate,
JSC__JSValue__fromUInt64NoTruncate, and
JSC__JSValue__toUInt64NoTruncate, plus
JSC__JSValue__isStrictEqual and JSC__JSValue__isSameValue. Raw primitive
words use the codec directly; cell words resolve through context-owned public
C-API boxes without exposing the internal NaN-box layout. The two constructors
produce real context-owned BigInt cells. Extraction returns the BigInt modulo
2^64 and preserves the pinned int32 and exact non-negative Int52 number
fallbacks. The equality pair implements JavaScript === and SameValue,
including string and BigInt value equality, object identity, NaN equivalence
only for SameValue, signed-zero distinction only for SameValue, and safe
rejection of foreign-context cells. The
compile-link-runtime gate is:
zig build test-home-private-abi
zig build test-home-private-abi -Dtsan=true
The TSan form instruments both the library and consumer executable. The matrix covers empty/immediate/int32/double/NaN/negative-zero behavior, boxed empty/nonempty strings, object identity/truthiness, signed minimum and unsigned maximum BigInts, negative modulo extraction, exact number fallbacks, and every invalid/non-exact boundary. Public accounting stays unchanged at 117 functions and 22 extensions; private-profile exports are counted only in their pinned inventories.
Home and Bun use distinct private JSType tag layouts. Focused fixtures always
link the library compiled for their own profile, even when both run in one Zig
build invocation. The mixed-profile gate runs two instances of each boundary
fixture together and may be invoked with either command-line profile:
zig build test-private-abi-mixed-profiles -Dprivate-abi-consumer=bun
zig build test-private-abi-mixed-profiles -Dprivate-abi-consumer=home
The opaque BigInt cell slice additionally exports JSC__JSBigInt__fromJS, the
three signed/unsigned/double ordering functions, and JSC__JSBigInt__toInt64.
It reuses zig-js's arbitrary-precision integer comparison rather than narrowing
through f64: the runtime matrix covers values beyond i128, the 2^53 rounding
boundary, positive and negative fractional comparisons, the minimum positive
subnormal, infinities, a 10^400 BigInt against floatMax, and signed
modulo-2^64 extraction. JSC__JSBigInt__toString now mirrors the pinned
24-byte BunString tagged union and returns a fresh, owned, 8-bit
WTFStringImpl decimal result with refcount one. The fixture covers signed and
oversized values, same-VM sibling realms, fresh allocation identity, atomic
retain/release, exact layout/flags, and first-pending-exception preservation.
The cold destroy export also supports Bun's Rust-side inline atomic release.
The non-URL BunString conversion slice implements BunString__toJS,
BunString__toJSWithLength, BunString__transferToJS, and
BunString__createArray. It decodes Empty and Dead tags, 8/16-bit
WTFStringImpl storage, and Latin-1/UTF-8/UTF-16 pointer-tagged ZigStrings.
UTF-16 code units—including lone surrogates—are preserved exactly; the length
variant can split an astral pair at a requested code-unit boundary. Transfer
invalidates and releases owned storage only after successful conversion, while
array construction preserves order, selected-realm prototypes, zero-length
null-pointer input, and failure atomicity.
The four non-DOM ZigString error constructors reuse the same tagged decoder and
create fresh native Error, TypeError, RangeError, and SyntaxError
instances. Latin-1, UTF-8, UTF-16, empty, astral, and lone-surrogate messages
retain their exact JavaScript text. Prototype selection comes from the chosen
realm's intrinsic error family rather than mutable globals, and a pre-existing
VM exception prevents allocation without being replaced.
The value-level BigInt slice exports JSC__JSValue__asBigIntCompare,
JSC__JSValue__bigIntSum, and JSC__JSValue__fromTimevalNoTruncate. It returns
the pinned equal/undefined/greater/less enum values, including undefined for
NaN; compares arbitrary-size BigInts against BigInts, exact and fractional
doubles, signed zero, and infinities; and adds without narrowing. The timeval
constructor deliberately matches the pinned consumer formula
sec * 1_000_000 + nsec despite its parameter name, with signed i64 extremes
covered by the runtime fixture. Invalid and foreign-context cells are rejected.
The JSCell/JSString slice adds exact opaque downcast, equality, storage-width, UTF-16 length, object access, and object-coercion boundaries. Its fixture covers ASCII and Latin-1 8-bit strings, BMP and astral strings, lone-surrogate WTF-8, equal content in distinct cells, non-string rejection, ordinary-object identity, and String/Symbol/BigInt boxing. Foreign-context coercion is rejected rather than accepting a handle from another VM.
The ordinary-object foundation exports fresh Object.prototype-backed and true null-prototype constructors plus boxed-primitive unwrapping. The capacity input remains an allocation hint with no observable semantic effect. Unwrapping canonicalizes the complete int32 range, preserves out-of-range doubles, negative zero and NaN, returns exact String/Boolean/BigInt primitives, leaves ordinary objects and existing primitives unchanged, and rejects foreign-context wrapper cells.
The object-coercion slice exports JSC__JSValue__toObject and
JSC__JSValue__getPrototype. Ordinary objects retain identity; Number,
Boolean, String, Symbol, and BigInt primitives receive selected-realm wrappers;
and null/undefined fail through the private empty boundary. Prototype queries
return exact ordinary, wrapper, function, null, and proxy-observed prototypes,
while invalid and foreign-context values are rejected.
The numeric DateInstance slice exports
JSC__JSValue__dateInstanceFromNumber and
JSC__JSValue__getUnixTimestamp. The constructor creates a fresh selected-realm
Date cell around an already-computed internal double, deliberately bypassing
JavaScript constructor TimeClip. Fractional values, negative zero, NaN,
infinities, and values outside ±8.64e15 are preserved; the getter returns NaN
for non-Date cells.
The Date parsing/formatting slice adds
JSC__JSValue__dateInstanceFromNullTerminatedString,
JSC__JSValue__getUTCTimestamp, JSC__JSValue__toISOString, and
JSC__JSValue__DateNowISOString. It parses the complete NUL-terminated input
into a fresh selected-realm Date, preserves invalid parses as NaN Dates, accepts
same-VM sibling values for UTC extraction and ISO formatting, rejects foreign
VMs, and writes exact 24-byte ordinary-year or 27-byte extended-year UTC text
without a terminator. All failures return -1 without modifying the output
buffer. JavaScript Date construction and Date.now() now use the same real
Unix wall clock as the Date-now writer.
The pinned consumer sources contain three defects that the name-based inventory
cannot express: Bun's Zig declaration gives DateNowISOString the incompatible
(*JSGlobalObject, f64) JSValue signature even though its wrapper and C++ body
use (*JSGlobalObject, *[28]u8) c_int, and getUTCTimestamp is declared but has
no C++ definition. Both pinned Zig RegularExpression declarations also omit
the BunString argument from searchRev, while the C++ implementation and
current Rust binding expose searchRev(RegularExpression*, BunString). The
runtime fixture therefore pins the executable writer, coherent owned-Date UTC
internal-time, and two-argument reverse-search contracts rather than claiming
those source inconsistencies match.
The six-symbol Yarr boundary owns a compiled zig-regex expression and mirrors JavaScriptCore's stateful validity and last-match-length behavior. BunString patterns and inputs preserve UTF-16 code units through WTF-8, so legacy dot can consume one surrogate half while Unicode dot consumes a valid pair atomically; all returned positions and lengths are UTF-16 offsets. Reverse search scans forward from successive code-unit positions exactly like JSC, retaining the last later non-subset match and handling overlaps and zero-width matches without looping. Null BunStrings and invalid patterns remain non-matches. The fixture uses the real two-argument executable ABI and covers flags, astral input, overlap, subset replacement, empty patterns, invalid state, and match-state reset. The underlying empty-pattern and Unicode-surrogate corrections are tracked and completed in zig-regex #11 and zig-regex #12.
The five-symbol WTF helper slice mirrors WebKit's stateless runtime boundary.
Decimal parsing accepts the longest signed decimal/fraction/exponent prefix,
reports its exact byte count, rejects leading whitespace and Infinity/NaN
spellings, and preserves signed zero and overflow-to-infinity behavior. The ES5
date parser is separate from JavaScript Date.parse: it implements WebKit's
strict date/time field widths, lenient space-separated and fractional forms,
timezone variants, leap-year validation, leap-second normalization, and NaN
failure contract. CPU discovery reports at least one online processor and
clamps to c_int. FastMalloc release is already satisfied because zig-js owns
no WTF allocator or per-thread FastMalloc cache. HTTP dates use exact 29-byte
RFC 7231 IMF-fixdate text, timestamp-zero suppression, and bounded
snprintf-style truncation with a terminator and full required-length return.
The seven-symbol Uint8Array/Buffer/ArrayBuffer slice constructs live
selected-realm values. Copy constructors own isolated backings and preserve
empty input; the ArrayBuffer path is a true fixed-length ArrayBuffer, while the
Uint8Array path can persist Bun Buffer subclass identity separately from its
ordinary byte-view kind. The two allocation-first exports expose an
uninitialized writable backing pointer only after the result is complete.
Pinned JSC's historically named Bun__allocArrayBufferForCopy actually returns
a Buffer-subclassed Uint8Array, and zig-js preserves that result rather than
normalizing the misleading name. JSBuffer__isBuffer reads the stored identity
without accepting lookalikes.
The two default-allocator constructors adopt the caller's non-empty byte slab
without copying as either Uint8Array or ArrayBuffer and attach it to the same
idempotent external-buffer owner, so GC or realm teardown invokes mi_free
exactly once. Empty values allocate independent zero-length backing metadata
without taking ownership of the caller's sentinel pointer. A weak libc fallback
keeps standalone builds linkable; Home/Bun's strong mimalloc export replaces it
in consumer builds. Invalid sources, oversized allocations, and construction
failures publish no pointer or half-created value and use the normal private
pending-exception boundary.
The remaining three JSBuffer constructors complete the same ownership model.
Signed lengths reject negative and engine-oversized requests as pending
RangeErrors while valid lengths return zero-filled Buffer-identified views.
External pointers retain their exact address and caller-supplied finalizer
context; non-empty storage releases exactly once from GC or realm teardown,
while zero-length transferred allocations invoke the finalizer immediately as
required by the pinned Bun leak fix. Mmap-backed Buffers similarly keep the
mapping live without a copy and call munmap/UnmapViewOfFile exactly once.
Invalid non-empty pointer/deallocator pairs fail explicitly rather than
publishing a dangling view.
The explicit uninitialized Uint8Array export uses the same aligned backing
allocator, GC byte accounting, metadata ownership, selected-realm prototype,
and finalization path as ordinary typed arrays, but deliberately skips only the
byte-zeroing step. That behavior is confined to the pinned native ABI for
callers that overwrite the complete view. JavaScript new Uint8Array(...),
ArrayBuffer construction, resizing, transfer, and every other allocation path
continue to use the unchanged zero-filled allocator. Oversized requests fail
through the private pending RangeError boundary before publishing a view.
The generic no-copy ArrayBuffer and TypedArray adopters cover all 12 pinned
numeric tags, including Float16 and both BigInt views. They preserve the exact
external pointer and full backing byte length; typed views use
floor(byteLength / elementSize), so trailing bytes stay owned by the backing
ArrayBuffer without making the view invalid. Empty storage remains a live
zero-length object. Caller callbacks, including null callbacks, use the same
idempotent owner record and run at most once; invalid tags and non-empty null
pointers fail atomically and release transferred input before publishing the
private pending exception.
ArrayBuffer__fromSharedMemfd imports one caller-owned descriptor without
copying or consuming it. It duplicates the descriptor to close the validation/
mapping race, requires a regular file large enough for the declared total,
maps that complete extent read/write and MAP_PRIVATE, and exposes only the
overflow-checked requested slice as the profile-selected ArrayBuffer or
Uint8Array type. The short-lived duplicate closes after mapping; GC, arena
teardown, and every later construction failure converge on one idempotent owner
that unmaps the complete original extent exactly once. Unsupported platforms,
invalid descriptors/ranges/sizes, and every other JSType fail empty without a
partial JS value or pending exception. Focused tests cover nonzero slices,
write isolation from the file, caller-fd survival, GC, teardown, and invalid
inputs; the compiled consumer exercises both result types.
The JSValue ArrayBuffer projection fills Home's exact 40-byte borrowed-view record for ArrayBuffer, every numeric TypedArray, and DataView. It reports the live offset-adjusted pointer, element and byte lengths, original encoded cell, profile-selected Home/Bun JSType tag, shared state, and resizable/growable state. Detached or out-of-bounds views return a successful zero-length/null projection without touching stale bytes. Invalid and foreign cells leave the caller output unchanged, and an existing VM exception blocks projection without being replaced.
The pinned IDLArrayBufferRef contract
follows Home and Bun's RefPtr<JSC::ArrayBuffer> conversion through the
generated producer's leakRef() transfer. The resulting backing handle has
atomic, independently bounded external references and separate wrapper and VM
tracking ownership, so its storage can outlive the JavaScript wrapper, realm,
and VM without an over-deref stealing another ownership domain. Owned bytes
resize in place at the handle boundary, shared storage retains shared identity,
and external storage invokes its callback exactly once after the final owner.
asBunArrayBuffer fills the exact 40-byte record with stable pointer/length
pairs plus the shared and resizable flags. The generated fixture adopts the
producer through required, optional, and union fields and covers explicit
clone/drop, resize, teardown, over-release, renewed references, and last-owner
external finalization.
The private typeof projection returns the exact JavaScriptCore small-string
classification for every encoded primitive and cell: undefined, boolean,
number, string, symbol, bigint, function, or object. Null remains
object, while callable [[IsHTMLDDA]] objects remain undefined. Each result
is one stable JSString cell owned by the context group, so sibling realms share
identity while separate VMs do not. Invalid and foreign cells return null; the
operation invokes no user code and neither consumes nor replaces a pending VM
exception.
The VM entry-state query mirrors JavaScriptCore's live entry-scope predicate. It returns true while any realm in the context group has an active interpreter, including nested/reentrant entries, and remains true until the final entry leaves. The read takes each realm's existing active-interpreter registry lock, keeps separate VMs isolated, returns false for null handles, and never changes exception or termination state.
The owning-VM query resolves any realm—including sibling realms in one context
group—to the single VM identity every JSC__VM__* boundary consumes, matching
the pointer JSC__JSGlobalObject__vm publishes. It allocates nothing, rejects
invalid handles with null and separate context groups by identity, and never
touches exception or termination state.
The private JSFunction boundary exposes the parser-captured source span rather
than reconstructing text through Function.prototype.toString. Ordinary,
arrow, method, generator, async, and class-constructor functions return stable
group-lifetime Latin-1 or UTF-16 ZigString views; native, bound, non-function,
invalid, and exception cells fail without modifying the output. Its tier-up
control is scheduling-only: an eligible cold bytecode chunk is primed so the
next invocation claims native-tier consideration, while the request itself
does no allocation or code generation. Tree-walk functions and already
settled tiers remain unchanged, as do pending VM exceptions.
The internal length projection follows the pinned type switch instead of
blindly reading .length. Strings use UTF-16 code units; Arrays use logical
length; numeric TypedArrays and ArrayBuffers use live view/byte lengths; and
Map, Set, and WeakMap report internal entry counts even when user code spoofs a
length property. Detached storage reports zero. Other objects perform one
ordinary length lookup followed by ToNumber, so inherited values, accessors,
proxies, conversion hooks, and abrupt completion remain observable. Missing or
unsupported cells return positive Infinity, while primitive/invalid/foreign
inputs return zero and an existing VM exception remains first.
The private property-path export reproduces Bun's pinned permissive Jest
grammar over UTF-16 code units: dots and brackets separate segments, while
empty, leading/trailing, and consecutive-dot cases retain their exact empty-key
behavior. Array paths accept only string/number entries and use JSC's
ToLength-plus-indexed-Get traversal, including holes, inherited entries,
proxies, and exact number-to-string keys. Each target segment is boxed through
ToObject and read once without a preliminary has lookup, preserving present
undefined, accessors, abrupt completion, and first-exception state.
The six shared property-iterator exports retain their VM and snapshot exact
enumerable string/Symbol names in JSC order. Own-only and non-index modes,
prototype traversal, enumerable-name de-duplication, mutation-stable borrowed
BunStrings, and UTF-16 longest-name length follow the pinned boundary.
Observable reads perform ordinary Get; VM inquiry walks only direct ordinary
data slots and stops at accessors, Proxies, or custom/opaque objects without
executing user code. Focused and independently compiled consumers cover both
paths, thrown getters, symbols, sibling/foreign VMs, and final iterator-owned
VM teardown.
Four class/display-name projections separate raw class-info metadata,
constructor-derived calculated class names, function/internal names, and Bun's
display-name result. Class calculation uses non-invoking VM-inquiry-style data
slots, while the name-property and display-name fallbacks perform exactly one
observable @@toStringTag read. Borrowed class/name output has context-group
lifetime; BunString display names own exact Latin-1 or UTF-16 storage,
including astral and lone-surrogate content.
The two private JSON writers use the complete runtime serializer with the exact
pinned space distinction: unsigned indentation clamped to ten spaces, or
undefined for compact/fast output. They preserve observable toJSON, getters,
proxies, property order, omission/null rules, BigInt and circular errors,
Unicode/lone-surrogate escaping, and selected-realm execution. Successful
output owns its Latin-1 or UTF-16 BunString backing; an unstringifiable
top-level value produces the empty representation without an exception.
The native fromEntries and putRecord boundaries construct records directly
from copied ZigStrings. fromEntries returns an ordinary selected-realm object,
keeps duplicate last-value and integer-key enumeration semantics, and leaves
both clone modes independent of caller-buffer mutation. putRecord maps zero,
one, and multiple values to an empty array, scalar string, and ordered string
array, then installs an all-true own data descriptor without invoking inherited
setters. Null/oversized/foreign/OOM paths are failure-atomic and the shared
pending-exception boundary remains first-wins.
The three value-key/BunString property exports add observable one-shot
ToPropertyKey own-property inquiry plus direct own writes. They preserve
Symbol/index/proxy semantics, replace configurable accessors without invoking
prototype setters, and implement Bun's missing/scalar/array upsert progression:
value, then [old, new], then direct array pushes. Foreign values, invalid
targets, coercion failures, and pre-existing exceptions publish no partial
mutation and retain first-exception identity.
The JSX element predicate performs one ordinary $$typeof Get and compares
only against the VM registry identities for react.element and
react.transitional.element. Inherited properties, getters, proxies, and
same-VM sibling realms preserve their observable behavior. Local Symbols,
same-description impostors, primitives, foreign cells, thrown accessors, and
pre-existing exceptions retain the pinned false/abrupt boundary.
The core deep-equality pair implements the pinned Bun structural engine for SameValue primitives, active cycle pairs, enumerable string/Symbol properties, unordered Maps/Sets, arrays and sparse holes, boxed strings, Date, RegExp, Error/cause, ArrayBuffer, DataView, and every numeric TypedArray. Strict mode adds calculated-class, property-count, missing/undefined, cause-presence, and bitwise-float distinctions. Getter/proxy failures, sibling realms, foreign cells, recursion limits, and existing pending exceptions retain the native boundary behavior.
The three Jest modes enable right-first asymmetric dispatch for the pinned
anything/any, string-containing/matching, array/object-containing, close-to,
promise, negation, and custom-marker behaviors. jestDeepMatch performs one
existing-property lookup per subset key, handles Symbols and independent cycle
sets, requires exact arrays, keeps nested object-containing exhaustive, and can
replace a matched data/accessor property directly. The non-Jest pair remains
matcher-hook-free.
The five remote-inspector process controls expose atomic, idempotent state for one-way auto-start disable, explicit start, console logging, and the default inspection policy. Apple-family targets deterministically follow modern JSC's disabled default and non-Apple targets default enabled; explicit policy changes remain separate from each context's inspectability flag.
The pinned hot-reload boundary broadcasts the exact Bun.canReload inspector
event to every live connection in process creation order, including sessions in
different VMs and sessions with no enabled protocol domain. Snapshot lifetime
references allow callbacks to detach themselves or later sessions and to
rebroadcast recursively without stale delivery or use-after-free. Both profile
libraries compile and run the same exact consumer through
zig build test-private-hot-reload.
The shared ErrorCode diagnostic boundary returns a freshly owned BunString for
the exact null/undefined, Number, Boolean, arbitrary BigInt, Symbol, callable,
string-preview, and object-constructor forms used in Bun/Home argument errors.
String previews apply the 28/25 UTF-16-code-unit rule without repairing lone
surrogates; quote selection scans the complete input and only embedded double
quotes are escaped. Callable naming is non-observable, whereas object
constructor, name, and name coercion are ordinary gets in order. Getter and
Proxy throws, foreign VMs, allocation failure, and existing pending exceptions
return a dead string without publishing partial output. Both exact profile
libraries run the same consumer via zig build test-private-error-code.
When the observable constructor lookup is falsy, the boundary follows the
pinned Bun inspector path: null-prototype identity, own-key order, descriptor
tags, nested quoting, functions, Symbols, BigInts, sparse arrays, cycles,
Proxy-target projection, depth limits, and nodejs.util.inspect.custom are
formatted without leaking constructor. The exact upstream files, digests,
call chain, and semantic checklist are recorded in
bun-diagnostic-inspector-4982b91e.json
and are rechecked by zig build bun-private-abi-audit -Dbun-source-root=$HOME/Code/bun.
The shared LifecycleReporter/TestReporter slice maps each private agent handle
to one live inspector session. It implements exact domain gates, reload and
structured-error events, prevent-exit state, test discovery/start/end events,
optional source locations, pinned enums, and safe invalid, detached, or
reentrantly released handles. Both profile libraries run the same external
consumer via zig build test-private-inspector-agents; the upstream source,
protocol hashes, and semantic checklist are pinned in
bun-inspector-agents-4982b91e.json.
The same consumer also exercises the five HTTPServer request-lifecycle exports:
per-session domain gates, parsed flat header/parameter arrays, exact nested
request/response/chunk/error payloads, optional fields, invalid input, OOM, and
reentrant release. Bun 4982b91e, its WebKit cd821fec protocol, and the complete
contract are pinned in
bun-http-server-inspector-4982b91e.json.
The proxy internal-field projection returns exact live target/handler cells
without ordinary property access or userland traps. Revoked fields become
JavaScript null; invalid field IDs and non-proxies return the empty ABI value.
A per-VM canonical object-handle table keeps re-published EncodedJSValues
bit-identical across sibling realms while isolating independent VMs. The table
uses its own lazy secure exact-identity hash context, reserves native capacity
before allocating an arena-resident wrapper, and publishes neither state on a
failed first allocation. Precise-GC relocation rekeys the existing canonical
wrapper without allocating or changing its encoded handle.
The process-wide private-global lifecycle table is separately mutex-protected and uses its own lazy secure exact-pointer hash context. First publication is failure-atomic, and create-for-isolation rollback, successful replacement, and destruct-on-exit all use the same exact removal path. Removing the last live record releases and resets the table, so a later lifecycle starts with fresh entropy while null, forged, foreign, stale, and duplicate handles remain inert.
The script-execution-context identifier boundary lazily assigns a stable,
nonzero 32-bit process identifier to each global context. The process registry
uses an independent lazy secure hash seed with exact integer equality; its first
mapping and context publish together only after successful allocation. Parallel
creation stays unique and resolves only live contexts; explicit Home worker
removal and every natural teardown retire the mapping before context storage is
freed. Home's INT32_MAX macro sentinel consumes a generated process ID rather
than becoming visible, and the monotonic namespace refuses to rebind retired or
skipped IDs. Sibling realms remain isolated, while zero, unknown, and
already-retired IDs are inert. The pinned teardown/UAF-prevention contract is in
home-script-execution-context-7ed99c02.json.
The two retired module-registry snapshot exports exactly preserve pinned JSC's
documented no-caller shims: get returns null and reset returns false without
inspecting either argument or VM state. Their immutable contract is
bun-module-registry-shims-4982b91e.json.
The three heap-profiler exports share one VM-wide strong graph across sibling
realms. The graph preserves cycles, follows property/index/variable/internal
edges, omits weak-only targets, and uses relocation-stable zig-gc identities or
VM-lifetime arena identities. It is emitted as WebKit GCDebugging JSON parsed
into a genuine JS object, Chrome/V8 heap-snapshot JSON, or a complete Markdown
cell/edge inventory with ArrayBuffer external bytes and dominator-derived
retained sizes. Home receives an owned StringImpl pointer while Bun uses its
by-value BunString. The pinned sources and cross-profile fixture are in
heap-snapshot-serialization-403.json;
run zig build test-private-heap-snapshot.
The CPU profiler keeps running state and samples per VM while the VM-less
interval setter publishes a validated atomic default. A cooperative checkpoint
copies the activation chain only after its source location is current, so it
composes with debugger and worker hooks without a timer-thread frame race. Stop
emits Chrome CPU-profile JSON and a Markdown hot-function/call/file report from
one sample set, with exact Home pointer and Bun by-value ownership. The pinned
contract is cpu-profile-sampling-404.json;
run zig build test-private-cpu-profile.
The six ReadableStream consumers share a realm-local default stream,
controller, reader, and pull-driven queue. They preserve asynchronous ordering,
lock/error/cancel transitions, split UTF-8, byte ownership, JSON rejection,
Blob creation, and URL-encoded or multipart FormData across same-VM realms.
The exact Home/Bun source and behavior contract is
readable-stream-consumption-405.json.
Fetch Response and Request bodies retain those streams as managed edges;
buffered bodies project a stable stream lazily, every Body conversion uses the
same consumer machinery, and cloning tees live streams with combined
cancellation. The pinned lifecycle contract is
fetch-body-lifecycle-407.json.
WebAssembly.compileStreaming and instantiateStreaming consume that body
path, enforce the official status and exact application/wasm MIME boundary,
and feed each normalized chunk into the VM-owned compiler lifecycle before EOF.
They then enter the ordinary parser/linker with its stable byte snapshot. The pinned
specification and Home/Bun evidence is
wasm-streaming-api-408.json; the unobservable
incremental private compiler feed uses a never-dereferenced, VM-affine token
registry with idempotent finalization and teardown. Its exact symbol, lifecycle
extensions, sources, and external Home/Bun fixtures are pinned in
wasm-streaming-compiler-feed-409.json,
and the direct Response-chunk bridge is pinned in
wasm-streaming-response-feed-410.json.
The pure fatal-diagnostic stringifier handles exact Number thresholds and
special values, booleans, null, undefined, arbitrary-size BigInts, and
described/undescribed Symbols. Strings retain their original encoded identity;
all other objects become [object Object] without conversion hooks, getters,
proxy traps, mutable globals, or pending-exception changes.
The unhandled-rejection classifier performs the pinned JSC own-stack
descriptor query. Own data and accessor descriptors return true without a
getter call, inherited-only properties and primitives return false, and Proxy
[[GetOwnProperty]] traps execute exactly once with their abrupt completion
published through the VM's first-wins pending-exception state.
The process-warning boundary accepts the pinned string/Error and options shapes, installs non-enumerable name/code/detail metadata, and queues warning listeners in FIFO order with the selected realm's Error prototype. Unhandled rejection warnings deliver the projected reason first and the exact Bun warning Error second; throwing stack reads, pure fallback formatting, exception clearing, and listener failures are covered.
The process rejection and fatal-dispatch boundary preserves exact reason and
Promise identity, orders uncaughtExceptionMonitor before capture or ordinary
handlers, gives the capture callback precedence, and returns the pinned handled
status. The rejection wrapper has the exact UnhandledPromiseRejection name,
code, and message. Promise checkpoints cover early-handler suppression, one
unhandled notification, one identity-preserving late-handled notification, and
duplicate-checkpoint suppression. The same realm-local store backs repeatable
beforeExit and one-shot exit dispatch.
Bun__onSignalForJS synchronously emits the canonical SIG* name and native
number through only the selected realm's process EventEmitter. The pinned
platform map, alias order, listener semantics, and failure boundaries are in
bun-process-signal-4982b91e.json.
The two process next-tick exports feed a distinct realm-owned FIFO rather than
the PromiseJobs queue. One- and two-argument calls retain their exact arity and
identity, all next ticks drain before microtasks, and the checkpoint repeats
after Promise jobs only when they scheduled more next-tick work. Reentrant
enqueue, uncaught monitor/handler dispatch, resumable tails after listener
failure, foreign-VM rejection, and _exiting suppression are covered with
precise queue and active-batch roots.
The three IPC process-event exports check for a listener before decoding any
payload. message receives the exact value and handle, error receives one
value without taking the unhandled-error branch when absent, and disconnect
receives no arguments. The fixture covers same-VM sibling identity, once
removal, foreign-VM no-op versus observed rejection, and listener throws.
The four debugger async-call exports follow the pinned
AsyncCallType contract: five exact u8
call types, a no-agent return before enum conversion, and direct
schedule/cancel/will-dispatch/did-dispatch forwarding. With an enabled agent,
owned scheduling frames are keyed by (type, callbackId) without rooting the
source realm, nested active calls restore in LIFO order, duplicate schedules
replace their prior snapshot, and single-shot work retires only after matching
dispatch completion. Debugger.paused exposes the copied asyncStackTrace
only while that task is active; cancellation, final-agent disable/detach, and
teardown release every dormant and active snapshot.
The native iterable callback boundary executes the pinned @@iterator method,
caches the returned iterator's next function, and observes IteratorStep and
IteratorValue in order. Every yielded value retains stable encoded identity and
receives exact VM/global/context metadata; callback exceptions close an open
iterator while a throwing return() cannot replace the original exception.
The non-indexed property boundary exposes exact JSType 7 GetterSetter and JSType 8 CustomGetterSetter cells plus their four null predicates. Traversal visits own string and Symbol keys in pinned order, filters indices, length, constructor, private/internal keys, and non-enumerable special cases, never invokes ordinary or C-class accessors, clears property-read failures where JSC does, and stops on callback-published exceptions. Descriptor identity survives sibling realms, GC, and reentry; the 327/327 compiled fixture additionally covers every accessor shape, proxies, Symbols, filters, and foreign inputs.
The ZigString JSON boundary decodes every tagged representation and constructs
the parsed graph with selected-realm intrinsics. Its pinned exceptional contract
returns the SyntaxError value after clearing the transient parse exception; an
input longer than 2^32 - 1 returns ERR_STRING_TOO_LONG without touching the
untrusted span.
The three external ZigString constructors retain the caller's original
Latin-1 or UTF-16 allocation until the last engine string dies. zig-js keeps a
canonical WTF-8 view internally without releasing that external obligation;
callbacks run exactly once after sweep locks are released, or during arena
teardown, and may re-enter GC. toExternalU16 resolves the consumer's strong
ZigString__freeGlobal; standalone links use the weak libc fallback.
The custom-inspect boundary invokes the supplied callable with the inspected
value as this and exact (depth, options, inspect) arguments. The realm-owned
options expose stylize, depth, and colors in pinned insertion order; the
callable helper, ANSI styles, pending exceptions, cross-VM rejection, and roots
across user-triggered GC are covered in both focused and compiled consumers.
The owned serialization boundary returns the pinned 24-byte
{bytes,size,handle} layout and keeps its structured-clone frame stable until
an idempotent secure-entropy capability release. Handles are never dereferenced;
the exact owner registry uses a separate secure hash seed, rejects live token
collisions, and publishes no partial owner on entropy or allocation failure.
Ordinary graphs round-trip cycles, aliases, and typed data; default-mode
SharedArrayBuffers retain process-local backing, while storage and cross-process
flags reject them rather than publishing non-portable tokens. Malformed input,
foreign values, uncloneable values, and allocation failure use the shared
pending-exception channel.
The cached-bytecode boundary returns an immutable, versioned ZJSCBC01
artifact owned by its opaque handle. The endian-stable header records the
module/CommonJS kind, UTF-16 source URL length, source length, and SHA-256
integrity digest; the payload retains the exact URL units and source bytes.
Generation runs the production parser first, publishes no partial outputs on
failure, and never claims compatibility with JavaScriptCore's private cache
serialization. The byte view remains valid until CachedBytecode__deref.
The VM exception slice exports the shared JSGlobalObject/VM pending-state
boundary plus exception-cell conversion and classification. Sibling realms in
one context group observe the same VM pointer and pending cell; taking or
clearing through either realm clears the shared state. Throws preserve the
original primitive or Error identity, retain the first pending exception, and
keep the thrown value rooted until clear/take. Exception cells remain distinct
from ordinary values and can be safely rethrown while owned by the VM or
explicitly protected. The VM traces and relocates the actual exception and
encoded-value handle slots, with no root-registration allocation at publication.
The cached termination exception remains rooted after pending state is cleared.
Bun__JSValue__protect counts protections for exception cells as well as ordinary
values; protecting an exception retains and relocates its value projection too.
After clear/take, hosts must protect an exception before the next collection if
they intend to retain or rethrow it. Final unprotect releases that ownership,
including a retired sibling realm retained by the exception's managed graph.
The structured exception-stack slice retains frames when an Error or
DOMException is created, independently of the public formatted stack string.
Tree-walker, bytecode, generator/async, constructor, global, and module paths
maintain a lightweight activation chain without allocating inspector scope
mirrors. JSC__Exception__getStackTrace truncates to the caller's u8
capacity and fills owned function/source BunStrings, zero-based line/column,
line-start bytes, code type, async state, and stable indices through
compile-pinned 48-byte ZigStackTrace, 72-byte ZigStackFrame, and 12-byte
position layouts. Matching upstream's OnlyPosition call, source-line arrays
remain empty and no source provider is retained. The consumer fixture covers a
line-41 named script, nested function/global frames, same-VM sibling access,
foreign rejection, capacity truncation, and explicit string release.
Full ZigException projection uses a retained source provider for its separate
source-line pass. The returned static BunStrings borrow the provider's copied
script source, so they remain valid across precise collection and terminal VM
teardown. Replacement releases the previous provider, final release may occur
on another thread, and null release is inert. The exact ownership and upstream
source hashes are pinned by the source-provider contract.
Bun__attachAsyncStackFromPromise adds the complementary native-error path:
pending Promises point to exact suspended async frames and transparent parent
links, while queued reactions retain activations only until delivery. The
bounded walker follows direct awaits and each plain forwarding segment for at
most 32 hops, stops at combinators/settled links, respects realm
Error.stackTraceLimit, and
never overwrites an existing or materialized stack or pending VM exception.
Focused coverage includes nested awaits, source positions, sibling realms, GC,
hop/limit boundaries, forwarding, and link clearing on completion.
The complete exception-projection slice pins the 216-byte ZigException
record and implements both inventoried follow-up exports plus the adjacent
JSC__JSValue__toZigException entry used by the native binding. It projects
owned name/message/system fields, exact error code and cause runtime type,
stable exception-cell identity, and the retained frame buffer without invoking
user getters. ZigException__collectSourceLines performs the second upstream
pass against the exact retained script ID, copying a capped current/preceding
source window into caller storage with zero-based numbers. Owned line strings
make the provider pointer deliberately null while preserving the consumer's
normal per-string deinit contract. The fixture covers Error, SyntaxError,
DOMException, primitive and system-like values, one- and three-line windows,
sibling lookup, foreign rejection, by-value conversion, and release.
The top-exception/termination slice adds all six pinned scope operations in the caller-provided 8-byte release or 56-byte verification buffer, both 8-aligned. Pure reads never process a termination request; trap-aware reads materialize one stable VM-owned termination exception shared by sibling realms. Atomic request, notification, clear, and set-only execution-forbidden controls preserve the pinned VM behavior. Selective clear removes normal exceptions but retains termination until explicit termination clear. OOM creation returns a fresh selected-realm OutOfMemoryError without throwing; OOM and stack-overflow throw helpers publish exact error kinds without replacing an existing exception.
The VM heap-control slice reports one context-group view of live heap bytes,
GC-owned external backing, and saturating embedder-reported extra memory.
collectAsync defers work to a runtime checkpoint; both pinned runGC paths
complete a full collection and return its post-sweep size. Weak-release and
footprint-shrink operations run real collection checkpoints, and opportunistic
work drains deferred GC plus live-realm microtasks only for a positive duration.
Precise heaps use zig-gc's race-safe live/last-full accounting API; arena VMs
report committed arena capacity. Sibling/foreign isolation, null boundaries,
counter saturation, deferred job execution, and first-exception preservation
are covered.
The standalone VM boundary owns one refcounted context group for either pinned
heap tag. deinit releases that owner exactly once only when its global belongs
to the same VM, while retained realms remain usable. deferGC is synchronous
and nestable: every collection attempt inside the callback is suppressed, one
queued intent survives, and it becomes runnable after the outermost return.
Control-flow-profiler enablement is stored per VM. Focused tests cover both heap
tags, foreign/double teardown, retained realms, nested callbacks, collection
epochs, and every context/group allocation failure.
The VM execution-control slice adds the pinned API-lock trio and
execution-time-limit trio. The group API lock maps JSC::VM::apiLock() onto a
recursive per-group mutex: same-thread nesting increments depth without
deadlock while foreign threads block on a real atomic mutex, and the deprecated
callback form runs under that hold. The execution-time limit maps
JSC::Watchdog onto a lazily spawned host-watchdog thread per context group
that naps to the armed monotonic deadline, then raises the shared termination
request and cooperatively interrupts running evaluation through the documented
host-watchdog entry point. Arming mirrors the pinned timeout mapping (+inf is
noTimeLimit, non-positive values fire at the next watchdog tick, NaN stays
armed but never fires, finite values saturate instead of overflowing), one
arming fires at most once, the limit stays reported after firing, and clearing
never clears an already-requested termination. Per zig-js's documented
host-watchdog contract the interruption is terminal for the context. The
fixture covers armed-state sharing across sibling realms, foreign-VM isolation,
null tolerance, recursive reentry, cross-thread exclusion, and a real 30ms
interruption of an unbounded loop attributed to the watchdog through the
termination request.
The process-wide default-timezone boundary mirrors WTF::setTimeZoneOverride:
empty input clears the override, unknown names return false without disturbing
state, and accepted names are case-normalized, alias-resolved, and stored in
canonical IANA form exactly like the engine's Intl pipeline. The override
supplies the default zone for Intl.DateTimeFormat — and therefore
Date#toLocaleString — and for Temporal.Now, while explicit options keep
precedence; foreign context groups observe the same process-wide zone, and the
pinned date-cache reset is vacuous because zig-js caches none. zig-js Date
local-time methods remain UTC-coincident by engine design.
The VM trap-notification slices implement all three pinned VMTraps
notifications used by zig-js consumers. JSC__VM__notifyNeedWatchdogCheck sets a VM-wide trap
bit that the running interpreter consumes at its next step checkpoint,
re-checking the armed execution deadline on the executing thread and
terminating once it has elapsed — the synchronous half of the watchdog,
intentionally redundant with the host-watchdog thread exactly like JSC's trap
bit and Watchdog timer. Consumed with no armed limit or a future deadline
the trap is a no-op, so bounded evaluation is never disturbed.
JSC__VM__notifyNeedDebuggerBreak requests a debugger pause with reason
"pause" at the next statement boundary on every realm of the VM with an
enabled debugger session — the same path as CDP Debugger.pause, with
continuation ownership still chosen per pause — and stays inert while no
debugger is attached, matching a JSC trap without one. The inspector's
pause-request word is atomic end-to-end so a cross-thread notify cannot race
the runtime thread's consume, both exports tolerate a null VM, and the
fixture covers disarmed, future, and elapsed trap consumption against bounded
and unbounded loops plus termination attribution.
JSC__VM__notifyNeedShellTimeoutCheck now has its own VM-wide trap word. Every
execution tier consumes it at the same checkpoint, publishes the ordinary
termination request, and throws once without disturbing another VM.
The Bun-only object-marshalling boundary mirrors JSC::constructEmptyObject
followed by the consumer initializer (bindings.cpp:2477), the path Bun's
struct-to-POJO conversion uses. A fresh empty object with the realm
Object.prototype is handed to the callback exactly once, synchronously,
before the export returns, along with the exact pass-through context pointer,
the stable canonical cell handle the returned EncodedJSValue exposes at the
private cell boundary, and the same global handle. The capacity argument
stays a performance-only hint — JSC clamps it to maxInlineCapacity, zig-js
shapes grow on demand — a null initializer still creates the object, and a
null VM returns empty without invoking the callback.
The URLSearchParams boundary is thin plumbing over the engine's existing
builtin: URLSearchParams__create decodes the ZigString query through the
exact new URLSearchParams(string) path (URLSearchParams.cpp:34) — the
form-urlencoded parse strips one leading ? and applies the +/percent
codec — so the result is a realm-prototype instanceof URLSearchParams
object whose methods all work. URLSearchParams__fromJS is the
WebCoreCast<JSURLSearchParams, URLSearchParams> downcast
(URLSearchParams.cpp:41): zig-js has no separate native object, the instance
IS the JS object carrying the engine's \x00usp pairs slot, so acceptance is
exactly "object with the pairs slot" — native-created or JS-constructed
alike, in any realm of the group — and the returned pointer is the canonical
cell handle per the object-marshalling convention above.
URLSearchParams__toString (URLSearchParams.cpp:49) serializes the pairs
exactly like the JS toString method and invokes the callback once with a
stable borrowed ZigString view, recovering the owner realm from the handle
because Bun's signature carries no global object. All three exports tolerate
null handles, and the full WHATWG URL parser surface (URL__*, DOMURL__*,
BunString__toURL/toJSDOMURL) stays deferred with the URL.zig cluster.
The ten-symbol DOMFormData boundary (#374) has one canonical engine
representation: every genuine FormData carries a branded \x00fd entry list
of [USVString, USVString|File] pairs, whether constructed by JavaScript or a
private export. createFromURLQuery performs percent decoding followed by the
Encoding Standard UTF-8 replacement state machine; serialization applies the
form-urlencoded percent set and omits File entries exactly like WebCore.
JavaScript append/set follows the current create-an-entry algorithm: File
identity is retained only when no replacement filename is supplied, while a
plain Blob or explicit filename creates a new File over the same bytes.
Native BlobImpl* values remain opaque identity tokens on engine-tagged File
wrappers and round-trip byte-for-byte through DOMFormData__forEach; zig-js
never dereferences them or invents an empty body. VM-scoped cast_, canonical
fromJS, duplicate order/count, callback ZigString lifetimes, lone-surrogate
replacement, plus/percent/invalid-UTF-8 parsing, JS-origin Blob-token
roundtrips, and Blob omission are covered by both the 388-symbol Home fixture
and Bun's focused 10-symbol fixture.
The one-symbol CommonStrings boundary (#378) maps all 13 pinned enum values to their exact Bun strings. Each value has one stable encoded cell per VM across sibling realms, while independent VMs remain isolated; invalid enum bytes and null globals return empty without disturbing a pending exception.
The complete 25-symbol FetchHeaders boundary (#376, #380, #382, #383) uses the same
branded, ref-counted header record for JavaScript Headers and native handles.
Its 21 core symbols validate WebIDL mutations, merge ordinary duplicates
(Cookie uses ; ), preserve separate Set-Cookie rows, cache one wrapper per
VM, and expose checked sorted count/copyTo rows. The Pico adapter copies the
pinned nested {ptr,len} layout without retaining foreign bytes: empty rows
are skipped, known duplicates use HTTPHeaderMap::add, uncommon duplicates are
last-value-wins, and parsed values are not WebIDL-trimmed. Null, misaligned, and
invalid nullable spans return an empty native handle. The three opaque UWS/H3
calls use a copied, version/size-checked consumer table from
FetchHeadersBridge.h. Request
visitors invoke the real pinned C++ iteration APIs while zig-js copies each row;
missing callbacks, invalid spans, and aborts discard partial imports. Response
rows are projected as Set-Cookie occurrences, known/common insertion order,
then uncommon insertion order; the pinned adapter applies the exact TCP/SSL/H3
writeMark and Content-Length/Date/Transfer-Encoding state rules. The consumer
sources live in integration/bun-4982b91e.
The URL native-record boundary (#308, first URL-cluster sub-slice) maps Bun's
context-free WTF::URL* exactly: because URL__fromString carries no global
object, the parsed URL is a native heap record (c_allocator, magic-validated
like the StringBuilder boundary) owning its component bytes in one backing
allocation — never a JS object. The engine's WHATWG parse layer
(urlParse/urlSerialize and helpers) was refactored from
interpreter-first to allocator-first with zero behavior change — those
functions only ever used the interpreter for its arena — so the JS URL
builtin, fetch Request/Response/redirect paths, and the new exports share
one parser. URL__fromString parses with no base and returns null for
invalid input (WTF validity: a scheme is required); URL__deinit frees the
record and clears its magic. The eleven component getters follow Bun/WTF
semantics rather than the JS getters wherever the two diverge: protocol is
the scheme without a colon, search keeps the leading ? for a
present-but-empty query, host excludes the port while hostname includes it
(the inversion Home documents explicitly), port is maxInt(u32) when unset,
and hash keeps # only for a non-empty fragment. Every returned BunString is
an independently owned wtf-impl string, so results outlive the record and
any later parse. The remaining URL-cluster symbols (URL__fromJS,
URL__getHref*, the file-URL helpers, BunString__toURL/toJSDOMURL,
DOMURL__*, and URL__originLength) stay pending as follow-up sub-slices.
The URL JS-value and static string helpers (#309, second URL-cluster
sub-slice) complete the context-free and coercing halves of the cluster.
URL__fromJS and URL__getHrefFromJS coerce any same-VM value through
ToString: a throwing coercion publishes the pending exception and yields
null/Dead (Bun's RETURN_IF_EXCEPTION rule), a foreign-VM value publishes a
TypeError, and empty/invalid input yields null/Dead with no exception —
fromJS returns the #308 native record on success. The static helpers need
no realm: URL__getHref re-serializes with no base, URL__getHrefJoin
resolves the relative reference against the parsed base through the existing
urlParse(rel, base) path, URL__getFileURLString prefixes file:// and
percent-encodes each /-separated segment with the WHATWG path encode set
(slashes preserved, dot segments NOT resolved — WTF sets the path
post-parse), and URL__pathFromFileURL returns the percent-decoded path
(%XX only — + stays literal) with no scheme check, matching
url.fileSystemPath(). Engine support stayed minimal and allocator-first:
urlPercentEncode gained pub, and one new pub urlPercentDecode helper
mirrors the form decoder without the + rule. Remaining URL-cluster symbols
after this slice: BunString__toURL/toJSDOMURL + DOMURL__* (JS DOMURL
object creation) and URL__originLength (Home declares it un-wrapped).
URL__originLength (#312, third URL-cluster sub-slice) closes the standalone
URL helpers: the latin-1 slice is widened to WTF-8 and parsed with no base,
and the returned pathStart mirrors urlSerialize's prefix emission exactly
— scheme: plus, when a host component is present, // + userinfo
(user[:pw]@, only when non-empty) + host + (:port when set), plus the
/. sentinel for a non-opaque path that begins with //. Empty/invalid
input and a null slice yield 0 where Bun dereferences unconditionally. Only
the DOMURL object-creation group (BunString__toURL/toJSDOMURL +
WebCore__DOMURL__cast_/href_/pathname_/fileSystemPath) remains in
the cluster.
The DOMURL object boundary (#313, final URL-cluster sub-slice) completes the
cluster. BunString__toJSDOMURL and its ZigString-input sibling
BunString__toURL build the realm's URL-interface object exactly like
new URL(str) minus the new-target check — URL prototype, hidden component
slots, live searchParams snapshot — and, because DOMURL::create is
ExceptionOr, publish the TypeError and return an empty handle on invalid
input instead of creating empty-href objects. WebCore__DOMURL__cast_ is
the VM-scoped downcast: it validates the VM handle zig-js publishes from
JSC__JSGlobalObject__vm (the context group), decodes with VM affinity, and
applies the hidden-slot predicate; the returned DOMURL* borrows the live
JS object (Bun's wrapper-owned impl pointer), so href_/pathname_ read
through it as borrowed ZigString views interned in a process-global
latin1/UTF-16 store, and fileSystemPath stays context-free — file:
scheme required (error 3), non-empty host rejected (error 1),
case-insensitive %2f rejected (error 2) — returning the percent-decoded
path as an owned BunString and writing error codes only on failure, exactly
like Bun.
Seven shared job/registry imports implement selected-realm native callbacks and
encoded jobs, selected-realm and VM-wide microtask checkpoints, explicit
rejected-promise notification, exact ZigString module-entry deletion, and
delete-all-code. Native callback context/function bits remain queued until one
execution; encoded callables and arguments retain same-VM identity, empty
arguments normalize to undefined, and reentrant jobs drain to quiescence.
Throws publish the first VM exception and restore the untouched FIFO tail for a
later checkpoint. The persistent module registry is realm-local and traced
under the same lock used for deletion. Code deletion drains jobs, clears the
chosen realm's module/source caches, waits for every native execution and
compilation lease, resets all published tiers before unmapping pages, and
permits safe bytecode fallback or later recompilation.
Eight shared strong/weak reference imports implement the exact opaque embedding
handle boundary. Strong handles keep the EncodedJSValue word at offset zero for
Bun's inline get, trace the paired internal value, accept sibling-realm sets,
and reject foreign VMs without replacing the existing root. Weak handles accept
the two pinned owner kinds, retain the owner type/context, and use zig-gc atomic
external weak slots so get/clear cannot race collector clearing. A collected
FetchResponse target invokes its consumer finalizer exactly once outside the
collector weak lock; explicit clear/delete suppress that callback. Both handle
kinds retain their VM until delete, synchronize root-list mutation with tracing,
and cover null, invalid type/value, idempotent clear, GC, and finalization paths.
The array/index slice exports exact-length empty-array construction, direct
indexed put/push/read, and an observable indexed read. Logical holes are not
materialized as undefined; an explicit undefined remains present. Direct
writes bypass inherited setters, sparse writes advance length, index 2^32-1
does not, and push at maximum length publishes a RangeError. Observable reads
perform ToObject and normal prototype/getter lookup, publishing thrown getter
values through the VM exception boundary. JSArray__constructArray validates
the complete encoded input slice before allocating the observable result, then
preserves packed order and owned-cell identity; sibling-realm values from the
same VM are accepted while foreign-VM values fail atomically with TypeError.
JSArray__constructEmptyArray preserves exact hole-only logical lengths through
the maximum u32 boundary. The two contiguous-vector exports return independent
stable JSC64 snapshots only for eligible packed Int32/boxed arrays. Revalidation
checks the exact array, vector, length, backing identity, current encodings, and
prototype safety; replacement, growth, holes, accessors, double/undecided
storage, pollution, or mismatched pointers fall back without dereference. The
VM-owned vector registry has an independent lazy secure exact-identity hash
context; allocation or entropy failure publishes no partial registry entry and
frees the complete candidate snapshot.
Bun__JSValue__toNumber implements full ToNumber:
primitive conversions, number-hint ToPrimitive hook order, Symbol/BigInt
TypeError, same-VM sibling values, foreign-value rejection, and exceptional NaN
with VM pending state while ordinary NaN remains non-exceptional. The private
has-instance predicate performs JSC's internal-capability precheck before
ordinary, custom, host, or proxy behavior; its counterpart implements JSC
hasIteratorMethod, rejecting primitives and running object GetMethod with
getter/callability exceptions. Private string inclusion applies full ToString
in receiver/search order and searches UTF-16 code units, preserving surrogate
substrings and publishing either coercion failure. Class classification follows
JSC call-data rules for JS classes, native constructors, bound functions, and
proxies; AggregateError checks immutable internal error kind. Shared C-API
realms reuse VM well-known Symbols and the Symbol registry. Private Object
keys/values create fresh selected-realm arrays of
own enumerable string properties in ECMAScript order; keys never read values,
while values re-check enumerability and perform Get in order. Proxy traps,
getters, abrupt completion, same-VM siblings, foreign VMs, and UTF-16 string
wrapper indices are covered. The native Promise slice adds the ten pinned
creation, direct-settlement, downcast, and callback-wrap symbols shared by Home
and Bun. InternalPromise is the pinned alias of JSPromise; constructors select
the requested realm and preserve exact value/reason identity without thenable
assimilation. JSPromise__wrap passes native promises through and converts
returned Errors or pending callback exceptions into rejections, whereas
AnyPromise__wrap settles an existing Promise through normal resolution and
therefore assimilates thenables and rejects self-resolution. Invalid or
foreign-VM inputs fail safely, callback exceptions are cleared exactly once,
and already-settled targets remain unchanged. JSC__JSValue___then adds the
detached reaction bridge shared by Home and Bun: the selected JSHostFn runs
asynchronously with exact (settlement value, retained context) JSC64
arguments. The handlers and context remain precise roots across pending
settlement and collection; sibling realms, FIFO/reentrant registration,
callback throws, non-Promise no-ops, and first-exception preservation are
covered. The Home-only JSMap slice adds all
seven direct native operations. It creates selected-realm Map cells and bypasses
mutable userland prototypes while preserving SameValueZero keys, exact stored
identity, insertion/reinsertion order, live size, sibling values, foreign-VM
failure atomicity, and first-exception behavior. The two shared FFI slow paths
decode validated JSC64 cells and apply exact signed/unsigned modulo-2^64 BigInt
conversion, including values beyond i128. CommonAbortReason conversion creates
fresh selected-realm TimeoutError/AbortError DOMExceptions with the pinned
messages and legacy codes and preserves a pre-existing VM exception. The
ZigString DOMException bridge implements the full pinned 0-through-40 code
matrix, including WebCore metadata and legacy codes, Bun's code-9 SyntaxError
divergence, all non-DOM special branches and Node-style codes, caller-message
override, and the unknown-code empty-name fallback. It constructs in the
selected realm and never replaces an existing exception. Four shared string
constructors copy every tagged ZigString representation, validate the pinned raw
UTF-8 path, intern atom backing per VM, and concatenate ordered ToString results
as exact UTF-16 sequences. Their tests cover source mutation, sibling realms,
foreign VMs, abrupt completion, and first-exception preservation. Two output
bridges cache stable group-lifetime borrowed views, using untagged
Latin-1 for 8-bit strings and tagged UTF-16 for all other strings. Direct
JSString output validates the cell/VM; JSValue output performs full ToString and
publishes Symbols, thrown values, and foreign-VM failures through the shared
exception slot. Five error factories construct fresh selected-realm Error,
TypeError, and RangeError instances from ZigString message/code pairs and every
BunString representation. They preserve the pinned writable TypeError code
and read-only RangeError code descriptors, omit empty codes, reject dead
strings, and retain the first exception. Three AggregateError bridges create
fresh ordered error arrays from encoded
slices or preserve an exact existing array and cause. Standard message/errors/
cause descriptors, direct own errors reads, selected realms, foreign-input
rejection, and failure atomicity are covered. Both pinned SystemError bridges
consume the exact 160-byte extern struct without owning its BunString fields.
The ordinary boundary creates a fresh Error with the exact message (including an
own empty message) and installs only populated
code/path/dest/syscall/hostname fields, a non-negative fd, and
unconditional errno in pinned order, all writable/enumerable but
non-configurable, with optional-field conversion failures cleared and skipped.
The node:os companion creates the ERR_SYSTEM_ERROR shape: a
SystemError-named Error with non-enumerable code, the composed summary
message, and a plain info object carrying unconditional
code/syscall/message/errno shared with the error's own DontDelete
fields. Eight property-boundary exports
add selected-realm two-key object creation with key-2-first definition order,
direct ZigString own writes, exact ToPropertyKey writes, ordinary deletion,
prototype-aware and Object.prototype-cutoff lookup, and own-only BunString or
value-key reads. Numeric/Symbol keys, accessors, proxies, duplicate insertion
order, Latin-1 names, sentinels, sibling realms, foreign values, and pending
exceptions are covered.
Three shared fast reads pin all 24 built-in IDs and distinguish direct data,
own-slot resolution, and pollution-mitigated lookup. Bun additionally exposes
a pure inherited-data code inquiry that rejects accessors, custom slots, and
proxies without executing them.
Three Symbol bridges share one registry across C-API sibling realms and expose
stable description/registry-key views for exact Latin-1 and UTF-16 content;
local and well-known Symbols correctly fail registry-key lookup. The native
StringBuilder slice adds all 13 pinned entry points with its 24-byte/8-byte
layout, exact UTF-16 and BunString decoding, shortest numeric formatting,
WebKit JSON escaping, sticky overflow/OOM, and non-destructive conversion.
The six TextCodec entry points implement the pinned fallback registry rather
than the browser-wide encoding universe: exact labels and canonical names,
replacement and x-user-defined codecs, ten single-byte tables, and seven
incremental CJK decoders. SHA-256-pinned WebKit tables are generated into a
compact embedded index, so behavior is cross-platform and adds no ICU, iconv,
or other runtime dependency. The fixture covers stable canonical-name storage,
owned results, split input, flush and stop-on-error state, deletion, and the
registry's no-op BOM hook.
The 14 shared AbortSignal exports attach a native owner only to genuine engine
signals, preserving one identity and reason across JS and native calls. Native
callbacks are exchanged before invocation for exact-once ordered reentrancy;
context-selective cleanup, common-reason sentinels, dependent premarking, and
ref/pending-activity roots share the same lifecycle. AbortSignal.timeout()
uses an unrefed, monotonic, generation-scoped context timer. Bun's additional
WebCore__AbortSignal__getTimeout borrows its exact four-field timeout record
only while active; cancellation invalidates it before native or JS callbacks.
The five rooted native-container entry points add callback-scoped marked
arguments and per-realm CommonJS function registries with precise-GC rooting,
cross-VM rejection, and exact append/set/swap-remove behavior. The private
module-loader slice adds persistent supplied/file sources, canonical relative
resolution, cache/namespace identity, exact Promise and exception channels,
top-level-await settlement, and complete module-graph tracing. The JSString
backing iterator adds pinned callback-layout validation and exact Latin-1 or
UTF-16 unit delivery. JSFunction__createFromZig creates native functions with
owned names and exact call/construct CallFrame delivery.
The three native CallFrame metadata exports layer a nested thread-local active
descriptor over that unchanged register layout. Exact frame and VM identity
gate an owned caller URL, one-based line/column, Bun-main origin detection, and
a bounded NUL-terminated description; the fixture covers sibling globals,
foreign VMs, stale/null pointers, constructors, and reentrant restoration.
The four FFI-function exports reuse that frame implementation with a distinct
validated function brand. Their names and arity are owned, the same callback
serves call and construct, nullable dataPtr mutation is atomic, and the
optional read-only/enumerable/configurable ptr property preserves the exact
callback-address bits. Dynamic-library metadata is stored separately; get/set
reject ordinary host functions and immediate values while accepting any valid
FFI cell regardless of VM ownership.
JSC__Exception__getStackTrace fills the caller-owned exact-layout frame buffer
from retained creation-time metadata rather than parsing .stack; the
position-only path owns its function/URL BunStrings and returns no source-line
provider. Full ZigException projection and its second source-line pass retain
the same frame/script identity; source-line strings borrow its retained provider
while all other returned strings are owned. The combined
runtime fixtures cover these semantics; the two profile-selected JSType exports
retain their separate Home/Bun runtime fixtures.
Profile-selectable JSType layout
private-jstype-layouts.json pins the complete
private enum from both consumer sources: 97 Home members and 98 Bun members.
All 97 Home names are shared, but Bun inserts
WebAssemblyStreamingContext = 27, renumbering 70 later shared tags. For
example, FinalObject is 34 for Home and 35 for Bun. The source revisions,
paths, SHA-256 digests, all member values, and the exact added/removed/renumbered
comparison are audited rather than inferred from declaration compatibility.
The library therefore requires an explicit compile-time profile whenever Bun numbering is wanted; Home is the default and every other value is rejected:
zig build private-jstype-abi-audit
zig build private-jstype-abi-audit \
-Dhome-source-root="$HOME/Code/Home/lang" \
-Dbun-source-root="$HOME/Code/bun"
zig build test-private-jstype
zig build test-private-jstype -Dprivate-abi-consumer=bun
JSC__JSValue__jsType and JSC__JSCell__getType return the selected exact
tags without exposing zig-js object flags. Both separately compiled runtime
fixtures cover 20 real cell kinds, including GetterSetter,
CustomGetterSetter, strings, Symbols, BigInts,
ordinary objects, JavaScript/native functions, errors, arrays, buffers,
typed arrays, DataView, RegExp, Date, Promise, Map/Set/weak collections, and a
boxed string.
Bun core private inventory
bun-private-core-4982b91e pins Bun revision
4982b91e3702094330f3be3883354c52b8c01323 and scopes the first Bun profile to
src/jsc. Bundled C libraries, N-API, wider runtime/WebCore code, and generated
bindings are deliberately excluded until separately inventoried. The core
profile contains 484 unique symbols from 59 hashed files:
| Classification | Symbols |
|---|---|
| Private JSC/Bun/WebCore ABI under #164 | 437 (437 implemented, 0 pending) |
| Public-C overlap | 22 |
| Consumer-provided definitions | 25 |
| Total | 484 |
The checked-in comparison proves that Home cannot stand in for Bun: 481 symbol names are shared, Bun has 3 core-only symbols, Home has 57 core-only symbols, and 37 shared names have different normalized signatures. The inventory lists every name in each category rather than reducing that result to counts.
zig build bun-private-abi-audit
zig build bun-private-abi-audit -Dbun-source-root="$HOME/Code/bun"
zig build test-bun-private-property-iterator -Dprivate-abi-consumer=bun
zig build test-bun-private-c-api-extensions -Dprivate-abi-consumer=bun
zig build test-bun-private-array-buffer -Dprivate-abi-consumer=bun
zig build test-bun-private-dom-form-data -Dprivate-abi-consumer=bun
zig build test-bun-private-vm-lifecycle -Dprivate-abi-consumer=bun
zig build test-bun-private-sql-structure -Dprivate-abi-consumer=bun
zig build test-private-global-lifecycle
zig build test-private-process-initialization
zig build test-private-hot-reload
zig build test-private-error-code
zig build test-private-inspector-agents
The SQL structure contract implements the
opaque JSC__createStructure descriptor and its object/offset consumers with
exact 32-byte column identifiers, the 62-entry cutoff, realm/VM validation,
shared engine shapes, GC barriers, and a compiled runtime fixture.
The global-object lifecycle contract owns real VM/realm creation, same-VM test isolation, execution-context identity transfer, precise old-realm reclamation, protected-handle lifetime, exact-once external finalization, and wrong-thread or foreign-handle rejection. Independent Home and Bun executables exercise the pinned signatures.
The process initialization contract pins first-call option publication and the distinct shell-timeout trap across the tree walker, bytecode VM, quickened regions, and native checkpoints.
The audit rejects revision, provider hash, declaration digest, classification, calling-convention, implementation-status, duplicate exports, and Home-comparison drift. The pinned Bun core profile has zero missing imports; wider/generated runtime profiles remain separate work under #140/#134.