Module loading: remove unsafe from ModuleLoader, AsyncModule and RuntimeTranspilerStore

JA
2mo ago
+1265-1635

What

Same programme as #40055 … #40284, applied to the JS-thread ↔ transpile-worker module-loading path in src/jsc/.

ModuleLoader.rs 9 → 1 and AsyncModule.rs 25 → 1 (residuals are all-safe fn extern blocks), RuntimeTranspilerStore.rs 50 → 3, job.rs 50 → 17. Collateral: jsc_hooks.rs −10, VirtualMachine.rs −8, JsAffine impls across glob/dns/Image/CompressionStreamCoder/read_file become plain impls.

  • ModuleLoader: __bun_transpile_source_code is a safe fn(&mut VirtualMachine, TranspileArgs<'_, '_>); TranspileArgs/TranspileExtra carry &mut Log/&mut BufferPrinter/Option<&PromiseSlot> instead of raw pointers; Bun__fetchBuiltinModule, ModuleLoader__isBuiltin, Bun__getDefaultLoader, Bun__runVirtualModule are HOST_EXPORTs.
  • AsyncModule: WakeContext no longer holds *mut Queue — the package manager wakes the JS thread with VmHandle::post_ping(kind, tag); Queue::on_dependency_error(&mut self) is reached through the current thread's VM; resume_loading_module uses a SourceCodePrinter RAII loan (no detach_lifetime).
  • RuntimeTranspilerStore: the hive-slot TranspilerJob + intrusive queue + raw *mut VM become bun_jsc::Job<TranspileJob>; VM inputs are snapshotted on the JS thread (TranspileInput); shared state is BackRef<SavedSourceMap> / BackRef<AtomicU32>; the worker printer is a safe thread-local loan; path text is interned rather than boxed with a fake 'static; the RuntimeTranspilerStore task tag and release_queued_jobs_for_teardown are gone.
  • job.rs: JsAffine is a safe marker (nothing unsafe relies on it); erased dispatch goes through a safe erase fn + one unsafe fn erased_from_raw used by dispatch.rs; complete/release_unrun/into_halves take Box<Self>; Completion::finish uses a ManuallyDrop'd obligation ZST instead of ptr::read.
  • Primitives: bun_event_loop::{Task::ping, BoxedTask + boxed_task!}, VmHandle::post_ping, SavedSourceMap over Guarded with &self methods (it was already used from three threads through aliased &mut), SourceMapHandlerGetter::new(&SavedSourceMap, inline) + write_inline_trailer, bun_ast::ErasedEntry + take_entry, bun_bundler::transpiler::{JobTranspiler, JobTranspilerCall} (per-call arena/log re-aim guard, also used by JSTranspiler), jsc_hooks::intern_path (single-hash).

Residuals and their blockers — RuntimeTranspilerStore.rs (3): unsafe impl Send for TranspileWork, JobTranspiler::new, and one &mut ImportWatcher for add_file; root causes are that Transpiler has no per-call-session split (parse/print need &mut) and Watcher::add_file takes &mut self though it is mutex-serialised — both are bundler/watcher refactors. job.rs (17) is the primitive (JsPtr, intrusive JobList, pool container_of entry, erased_from_raw, Send for Completion). hot_reloader.rs is untouched here (covered by a sibling PR).

Perf (release, taskset -c 56-63, interleaved ×20, perf stat -e instructions:u, median; A-vs-A noise ±0.07 %): 300-TS-file cold import +0.08 %, import() ×50 +0.15 %, setImmediate 1e6 +0.03 %, Bun.sleep 1e6 −0.02 %, fs.promises.readFile ×100k −0.45 %. Malloc counts (uprobes): import300 35,990 → 35,772; readFile 620,668 → 620,633.

Testing

Debug+ASAN, --timeout 60000: test/js/bun/resolve 361 pass (3 baseline-identical reds: root-user ×2, one load timeout), test/js/node/module 107/107, test/cli/run (autoinstall 9/12 — same 3 network reds on baseline; transpiler-cache 13/13; preload 2/2), test/cli/hot 17/17, fs.test.ts 520/520, fs.promises 30/30, sleep, timers (setImmediate 3/3; RSS-leak/LSAN cases identical on baseline debug), plugin 48/48, bundler/transpiler 190/190, cyclic-imports/require-extensions regressions; source lints 163/163 (vm-thread-door inventory regenerated). Hand-driven: 300-file cold/warm, syntax error at file #150 (correct path/line, exit 1), import() ×50, TLA rejected import, 20 Workers × 50 imports terminated mid-import × 5 (no ASAN, RSS flat), bun --hot 30 rapid edits (31 reloads), auto-install -i/--install=fallback/force. clippy clean on bun_ast/bun_event_loop/bun_bundler/bun_jsc/bun_runtime.