feat: porffor backend - #76
Open
littledivy wants to merge 1 commit into
Open
Conversation
Adds src/porffor/, a fourth backend. The three existing engines are
interpreters; porffor is an ahead-of-time compiler, so this is the first
backend where "compile this source string at runtime" has no meaning.
v8__Script__Compile can only ever resolve source already compiled into
the binary, which is why the target is flattened bundles rather than
arbitrary input.
Build shape follows the hermes spike: `engine_porffor` alone is a
pure-Rust stub backend that links with zero porffor dependency;
`link_porffor` makes build.rs run `porf c --lib` over src/porffor/seed.js
and link the emitted object. There is no engine library to link against
-- we compile the runtime itself out of porffor.
Because porffor only emits builtins a compilation references, seed.js is
the ABI contract: what it mentions is what the backend can reach.
Handles use the QuickJS arena shape for the same reason (a porffor jsval
is {f64, i32}, not a pointer), but the discipline differs: QuickJS slots
own a refcount, porffor slots must be GC roots. The arena lives on the
porffor side and registers with the collector in whole blocks, so a
handle scope costs no per-handle bookkeeping.
75 symbols implemented (isolate, handle scope, context, primitives,
strings incl. ValueView, objects, Global<T>, platform lifecycle);
745 still stubbed. ArrayBuffer::Allocator is reused from
src/quickjs/allocator.rs via #[path] -- it has no engine coupling.
tools/gen_porffor_shims.sh differs from the hermes generator in one
way: it emits cfg(not(feature = "link_porffor")) stubs for symbols
implemented in link-gated files instead of dropping them, so a symbol
getting a real body for the first time needs no hand-added gate.
Note: porffor emits one global heap per binary, so isolate liveness is
process-wide, not per-thread. Creating a second live isolate panics
rather than silently sharing the heap.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
src/porffor/, a fourth backend. Draft-quality scaffold, not a working engine — 75 symbols implemented, 745 still stubbed.Why this one is different
JSC, QuickJS and Hermes are interpreters: hand them a source string at runtime and they run it. Porffor is an ahead-of-time compiler — it lowers JS to C and hands that to a C compiler, with nothing interpreted or JIT-compiled.
So
v8__Script__Compilecannot mean "compile this string now". It can only resolve source already compiled into the binary. That makes the target flattened bundles (a celld Wrangler bundle, adeno compilegraph) rather than arbitrary runtime input — and it is the reason this is worth trying at all: it tests whether the "generatedrusty_v8ABI is the portability boundary" claim holds against an engine with no runtime compiler.Build shape
Follows the hermes spike:
--features porffor→ pure-Rust stub backend, links with zero porffor dependency--features porffor,link_porffor→ build.rs runsporf c --liboversrc/porffor/seed.jsand links the emitted objectThere is no engine library to link against. We compile the runtime itself out of porffor — its heap, collector and builtins — via a new
--libtarget on the porffor side.PORFFOR_DIRpoints at a porffor checkout (default../porffor).seed.jsis the ABI contract. Porffor only emits builtins a compilation actually references, so a builtin absent from the seed is absent from the linked object. Every entry costs binary size in every consumer, which is the one axis porffor is unambiguously winning on (~500 KB object for a broad builtin surface, vs ~1 MB for QuickJS static and ~48 MB for JSC).Handles
Same problem QuickJS has — a porffor
jsvalis{ f64 val; i32 type; }, 16 bytes, not a pointer — so the same solution: handles are arena slots and the slot address is the v8 handle.The discipline differs. QuickJS arena slots own exactly one refcount. Porffor has a tracing collector, so slots must be GC roots. The arena lives on the porffor side and registers with the collector in whole blocks rather than per handle, so entering and leaving a scope costs no per-handle root bookkeeping.
What works
cargo test --no-default-features --features porffor,link_porffor --test porffor_smokeIsolate, handle scope, context, primitives, strings (including
ValueView, zero-copy into porffor's heap — safe because the collector is non-moving and the handle roots the string), objects,Global<T>, platform lifecycle.values_survive_a_collectionchurns 20k allocations then forces a full GC and reads the handle back, which is the test that actually exercises the root-range wiring.Notes for review
AtomicBool, not per-thread. A second live isolate panics rather than silently sharing the heap. The smoke test serializes on its own lock instead of relying on--test-threads=1.ArrayBuffer::Allocatoris reused fromsrc/quickjs/allocator.rsvia#[path]— it has zero engine coupling (plain calloc/free plus the Rust allocator vtable), so a second copy would only drift.tools/gen_porffor_shims.shdiffers from the hermes generator in one way: it emitscfg(not(feature = "link_porffor"))stubs for symbols implemented in link-gated files instead of dropping them outright. A symbol getting a real body for the first time therefore needs no hand-added gate. (The hermesmisc.rscarries a hand-written block to compensate for the old behaviour; porffor's copy has that block removed, with a comment explaining why.)tests/status/baselines/porffor/— adding a fourth column to the dashboard felt like a separate decision. Happy to add it.Cargo.toml(+13),build.rs(+51),src/lib.rs(+5). Nothing undersrc/jsc/orsrc/quickjs/is modified.Depends on an unlanded porffor change
link_porfforneeds a--libtarget in porffor that suppressesmainand emits a stableporf_embed_*C ABI. That is not upstream yet — the stub-only build (--features porffor) is unaffected and works against any checkout.Next
v8__Function__Newoverporf_embed_function_new. Porffor's side is already built (ajsvalwhose call target is a C function pointer, dispatched throughporf_call_dynamic), and it is what celld's 126FunctionCallbackArgumentssites bind to.