Skip to content

feat: implement JIT runtime#139

Open
DaniPopes wants to merge 202 commits intomainfrom
dani/runtime
Open

feat: implement JIT runtime#139
DaniPopes wants to merge 202 commits intomainfrom
dani/runtime

Conversation

@DaniPopes
Copy link
Copy Markdown
Collaborator

@DaniPopes DaniPopes commented Mar 18, 2026

Adds a reusable revmc::runtime backend for revm/revmc embedders with O(1) compiled-function lookup and background compilation.

The runtime keeps compiled programs in a resident DashMap keyed by code hash and spec. Lookups only probe that map and enqueue best-effort miss events; a backend thread owns admission state, hotness tracking, AOT artifact loading/storing, eviction, and worker-result publication. Compilation runs on a bounded Rayon worker pool with per-worker LLVM compiler state for JIT and AOT jobs. JIT code lifetime is owned by ORCv2 resource trackers, while AOT programs keep their loaded libraries alive through the compiled-program backing.

The public API supports explicit JIT compilation, blocking lookup mode, batch AOT preparation, resident/persisted cache clearing, stats, and optional compilation callbacks. Tests cover startup, lookup behavior, worker admission, blocking mode, AOT persistence/loading, eviction, clearing, and zero-worker behavior.

Startup AOT preload from ArtifactStore::load_all() into immutable
FxHashMap. O(1) lookup via JitCoordinatorHandle. Fire-and-forget
lookup-observed events to coordinator thread via bounded sync_channel.
Add CompileMode::Runtime that boots a JitCoordinator with an empty
store, runs all lookups through JitCoordinatorHandle::lookup(), and
falls back to CompileCache for misses (including CREATE/CREATE2).
Exercises the full lookup → miss → event tracking path.
On Linux, load shared libraries via memfd_create + dlopen(/proc/self/fd/{fd})
instead of writing to temp files. Entirely in-memory, no filesystem I/O.
Non-Linux platforms fall back to tempfile.
Remove memfd/tempfile machinery from the coordinator. The storage trait
now owns dylib files on disk and returns paths. The coordinator simply
dlopen's the path and resolves symbols.

- StoredArtifact: dylib_bytes Vec<u8> -> dylib_path PathBuf
- ArtifactStore::store() takes raw bytes, store writes to disk
- LoadedLibraryOwner simplified to LoadedLibrary (no backing variants)
- Remove rustix and tempfile runtime dependencies
Use a done-signal channel with recv_timeout instead of blocking
indefinitely on thread::join. Configurable via RuntimeTuning::shutdown_timeout
(default 5s).
- Coordinator tracks per-key hotness from lookup miss events.
- When hotness crosses jit_hot_threshold (default 8), enqueue JIT compile.
- Worker threads own long-lived EvmCompiler<EvmLlvmBackend> instances.
- Workers block on exit until all Arc<WorkerBacking> refs are dropped,
  ensuring JIT function pointers remain valid.
- Resident map uses ArcSwap for lock-free reads + coordinator-only writes.
- Coordinator publishes new map snapshot after each successful JIT.
- Miss events carry bytecode (Arc<[u8]>) so coordinator can compile.
- New tuning knobs: jit_hot_threshold, max_pending_jit_jobs,
  jit_worker_count, jit_worker_queue_capacity, jit_opt_level.
Coordinator inserts directly into the shared DashMap. Handles read
from it via DashMap::get. No more snapshot cloning or publish step.
Remove custom RuntimeError and StorageError types in favor of
eyre::Result throughout the runtime module.
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 18, 2026

Merging this PR will not alter performance

✅ 65 untouched benchmarks
⏩ 24 skipped benchmarks1


Comparing dani/runtime (40dde4e) with main (b70076a)

Open in CodSpeed

Footnotes

  1. 24 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Workers now handle AOT jobs separately from JIT jobs. AOT jobs create
a temporary AOT compiler, translate bytecode, write an object file,
link it to a shared library, and return the raw .so bytes to the
coordinator.

The coordinator persists the artifact via ArtifactStore::store(), then
loads it back (dlopen + symbol resolution) into the resident map as
a ProgramKind::Aot entry.

Adds aot_opt_level to RuntimeTuning (defaults to Aggressive per plan).
- prepare_aot_persist_and_load: single AOT compile, persist, and load.
- prepare_aot_batch_persist_and_load: batch of 2.
- aot_artifacts_survive_restart: compile+persist, shut down, restart
  coordinator, verify artifact preloaded from store at startup.

Adds TempDirStore, a test ArtifactStore backed by a temp directory.
RuntimeHandler now borrows &CompileCache instead of cloning an Arc.
Eliminated the duplicate cache/cache_arc parameter threading.
…test

- Call clear_ir() after each JIT compilation in worker loop to reset
  the finalized module state, allowing subsequent compilations.
- Add get_compiled() to JitCoordinatorHandle for event-free resident
  map lookups.
- Rewrite statetest runtime mode to use the coordinator's JIT pipeline
  directly instead of CompileCache. Contracts are enqueued via
  compile_jit() and polled via get_compiled().
- Handle empty bytecode (EOA calls) by falling back to the interpreter
  instead of blocking forever on compilation.
Backend thread held Arc<BackendInner>, preventing BackendInner::Drop
from ever firing while the thread was alive. The thread loop only exits
on Shutdown, sent by Drop -> deadlock; tests leaked threads and the
process tear-down occasionally raced with backend work, causing heap
corruption (SIGABRT in malloc consolidation).

Split shared state (resident, events, stats) into BackendShared. The
backend thread now holds Arc<BackendShared> only. BackendInner owns
thread/lazy_spawn/shutdown_timeout/tx and drops cleanly when the last
JitBackend clone is released.
The previous test polled lookup() expecting a Compiled state, but with
resident_code_cache_bytes: 1 the entry is evicted as fast as it's
inserted — there's no observable Compiled window. Drive promotion
directly via lookup() and assert on stats.evictions instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant