ReviewOS

zig-utils/zig-js

Private ABI: JSC__JSObject__create with consumer initializer callback

#306
Closed chris opened this 9 days ago ·
9 days ago

Parent: #140 Tracked by: #134, #164

Symbol

SymbolBun (upstream/src/jsc/JSObject.zig)Bun C++ (upstream/src/jsc/bindings/bindings.cpp)
JSC__JSObject__createJSObject.zig:11 — extern fn JSC__JSObject__create(global_object: *JSGlobalObject, length: usize, ctx: *anyopaque, initializer: InitializeCallback) JSValue;bindings.cpp:2477

Bun-only symbol (not declared by the pinned Home profile). The C++ definition creates an empty object with the realm Object.prototype and inline capacity clamped to JSFinalObject::maxInlineCapacity, then synchronously invokes the consumer initializer as initializer(ctx, object, globalObject) — Bun's struct-marshalling path (createFromStructWithPrototype, JSObject.zig:135) uses it to populate POJOs field by field. InitializeCallback = *const fn (ctx: *anyopaque, obj: *JSObject, global: *JSGlobalObject) callconv(.c) void (JSObject.zig:123).

Acceptance criteria

  • Export creates a fresh empty object with the selected realm's Object.prototype (same observable shape as JSC__JSValue__createEmptyObject), then invokes the initializer exactly once, synchronously, before returning.
  • The initializer receives the exact ctx pass-through, the stable canonical cell handle (the same address the returned EncodedJSValue exposes at the private cell boundary — zig-js encodes objects as their canonical box address), and the same JSGlobalObject handle the export received.
  • length/capacity is documented as a performance-only hint: JSC clamps it to maxInlineCapacity; zig-js shapes grow on demand, so there is no observable difference for any value, including capacity larger than JSC's clamp.
  • Null-tolerant per zig-js private-ABI convention: null global returns .empty without invoking the initializer; null initializer still creates and returns the object.
  • Compiled runtime fixture covers: initializer invocation count/order, ctx/global pass-through identity, cell-handle identity with the returned value, Object.prototype identity with a peer empty object, null-initializer creation, null-global tolerance. Fixture symbol count 290 → 291.
  • Bun inventory 281/421 implemented (Home unchanged: symbol is Bun-only); bun-private-abi-audit + c-api-audit green; full zig build test zero new failures (worktree baseline-vs-candidate against the same pinned base).
9 days ago

Evidence

Commits (on main):

  • d7cb8529 feat(abi): create objects through the consumer initializer boundary (#306)
  • 61f953e5 docs(abi): publish object-initializer boundary evidence (#306)

Implementation

  • src/c_api.zig — JSC__JSObject__create beside JSC__JSValue__createEmptyObject: JSObjectMake for the realm-prototype empty object, then one synchronous initializer(ctx, cell_handle, global) call where the cell handle is the canonical box address the returned EncodedJSValue exposes (matching EncodedValue.cellPointer), capacity documented as a performance-only hint, null-global → .empty without invoking the callback.
  • tests/abi/home_private_value_shims.zig — 1 new extern (290 → 291 symbols), recording callback + assertions for invocation count, ctx/global pass-through, cell-handle identity, Object.prototype identity vs Object.prototype, over-clamp capacity, null-initializer creation, null-VM tolerance.

Validation

  • Fixture: Home private value shims: 291/291 symbols linked; runtime matrix passed
  • Home private ABI audit: implemented-private=288, pending-private=143, unclassified=0 (unchanged — Bun-only symbol)
  • Bun private ABI audit: implemented-private=281, pending-private=140, unclassified=0
  • c-api audit: 117 declarations, 117 complete, 117 public symbols present, 297 private profile exports
  • Home public ABI: 50/50 symbols linked; runtime smoke passed
  • zig build test -Dtest-filter=c_api: 148/148
  • Full zig build test worktree baseline-vs-candidate on pinned base 21d251ec: baseline 1137 passed; 2 failed, candidate 1137 passed; 2 failed — identical pre-existing failures (gc.test.Object fits the 128-byte GC slab…, wasm.decode.test.wasm.decode malformed declarations); zero new failures.

Sign in to comment on this issue.