FEATURE: expose V8 ScriptCompiler::CachedData via Context#compile#413
Open
ursm wants to merge 1 commit into
Open
FEATURE: expose V8 ScriptCompiler::CachedData via Context#compile#413ursm wants to merge 1 commit into
ursm wants to merge 1 commit into
Conversation
Adds Context#compile returning a MiniRacer::Script handle that can be run multiple times and exposes V8's per-script bytecode cache. Callers pass `cached_data:` to skip re-parsing on subsequent processes and read `script.cached_data` to persist the produced blob. The MiniRacer::V8_CACHED_DATA_VERSION_TAG constant exposes V8's CachedDataVersionTag() so callers can invalidate their cache when libv8-node is bumped. TruffleRuby ships a shim that falls back to source replay since GraalJS has no equivalent per-script cache reachable from Polyglot::InnerContext. Refs rubyjs#411. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Author
|
For sequencing context: #412 (Module API) is the next planned PR but I'm holding it back until this one lands. The two share a lot of C++ surface (handle table, packet protocol, dispose ordering) so iterating patterns here once will be cheaper than rebasing #412 twice. Flagging in case it helps frame the review. |
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.
Summary
Implements #411 — exposes V8's
ScriptCompiler::CachedDataso callers can persist per-script bytecode cache and skip re-parsing large bundles on subsequent processes.API surface:
MiniRacer::Context#compile(source, filename:, cached_data:)→MiniRacer::ScriptScript#run— executes the compiled script; safe to call multiple timesScript#cached_data— returns the bytecode blob (nil if the suppliedcached_data:was accepted, so callers can skip a redundant disk write)Script#cache_rejected?— boolean signal for cache-key invalidation telemetryScript#dispose/Script#disposed?— eager handle releaseMiniRacer::V8_CACHED_DATA_VERSION_TAG— module-level constant (populated on firstContext.new) wrappingv8::ScriptCompiler::CachedDataVersionTag(); mix into cache keys so a libv8-node bump invalidates blobs automaticallyDesign notes
Context dispose ordering (per @SamSaffron's flag):
State::~State()walksst.scriptsand resets eachv8::Persistent<v8::Script>under the existing Locker/Isolate::Scope beforeisolate->Dispose(). Handle table is owned per-State.Concurrency: compile/run/dispose RPCs go through the existing
rendezvousmutex path; the handle table is only touched from the V8 thread. No new lock surface. Reentrancy isn't a concern here since compile/run don't trigger JS→Ruby callbacks themselves (those go through the existingv8_api_callbackpath).GC finalizer trade-off:
script_freedoes NOT send a dispose RPC — takingrr_mtxfrom a Ruby finalizer thread risks deadlock. Handles freed via finalizer rely onState::~State()walking the table at isolate teardown. Long-lived Contexts with many short-lived Scripts will accumulate handles untilContext#dispose. Documented in README;Script#disposeis available for eager release.CachedData buffer policy: input blob uses
BufferNotOwnedpointing at the ValueDeserializer's ArrayBuffer backing store (valid for the v8_compile call), avoiding a copy of potentially MB-sized blobs.Packet protocol: new tags
'K'(compile),'R'(run),'D'(dispose) added todispatch1.'C'was already taken by call, hence'K'for compile.TruffleRuby: GraalJS has no per-script bytecode cache reachable from
Polyglot::InnerContext#eval(Source.Builder.cached(true)is in-process/Engine-lifetime only; cross-process cache requires GraalVM Enterprise Auxiliary Engine Caching with Native Image, which mini_racer's truffleruby shim explicitly rejects). Shim falls back to source replay — same observable semantics, no compile reuse.cached_data:silently ignored,Script#cached_datareturns nil,V8_CACHED_DATA_VERSION_TAG = 0.Out of scope (deferred follow-ups)
produce_cached_data!RPC (current API keeps the blob on the Script struct; lazy path would help when only some scripts need persistence)UnboundScriptround-trip to move Scripts between ContextskEagerCompileoptionTest plan
test/mini_racer_test.rbcovering compile/run roundtrip, cached_data save/restore across Contexts, rejection path with corrupt blob, encoding validation, dispose semantics (script + cross-context), filename propagation in ParseError, interleave withContext#evalSnapshot.load/warmup_unsafe!no-op pattern intruffleruby.rbso should behave consistently.Maintainer questions
V8_CACHED_DATA_VERSION_TAGinitialization: defined lazily insidecontext_initialize(firstContext.new) soPlatform.set_flags!still applies to the tag. Constant doesn't exist before the first Context — acceptable trade-off, but happy to switch to aMiniRacer.cached_data_version_tagmethod if a constant-with-deferred-init is too quirky.🤖 Generated with Claude Code