logicaffeine_compile/vm/bg_aot.rs
1//! Background AOT-native compilation (HOTSWAP §Axis-3 / P18).
2//!
3//! A very-hot function's OPTIMIZED form is compiled to a cdylib and loaded on a WORKER
4//! thread — `rustc` is seconds, so it must never block the interpreter — then the loaded
5//! function is installed via `Vm::install_aot_native` at a drain point and runs at
6//! compiled-binary speed from then on. This is the AOT path to the coarse-tiered
7//! "optimize hot functions during the run" goal: it reuses the proven on-demand build
8//! ([`crate::compile::aot_build_function`]) off-thread, the same mpsc worker shape as
9//! the forge background compiler (§6), and the existing `NativeSlot::Ready` dispatch —
10//! no new VM seam. Because an AOT function is all-or-nothing per call (it bails to the
11//! lower tier on a signature mismatch rather than precise-deopting), it sidesteps the
12//! warm-bytecode run-loop change the forge coarse path would need.
13//!
14//! Native-only: the browser uses pre-bundled wasm, and there is no `rustc` there.
15
16#![cfg(not(target_arch = "wasm32"))]
17
18use std::path::PathBuf;
19use std::sync::mpsc::{Receiver, Sender};
20use std::thread::JoinHandle;
21
22use super::native_tier::NativeFn;
23
24/// A request to background-compile function `fi` (named `fn_name`) from `source`,
25/// caching the artifact under `cache_dir`. All fields owned so the request crosses to
26/// the worker thread.
27pub struct AotRequest {
28 pub fi: usize,
29 pub source: String,
30 pub fn_name: String,
31 pub cache_dir: PathBuf,
32}
33
34/// The worker's reply: the function index and the loaded AOT function (or `None` if the
35/// function was outside the scalar subset or the build/load failed — the interpreter
36/// then keeps it on VM+JIT).
37pub struct AotResult {
38 pub fi: usize,
39 pub nf: Option<Box<dyn NativeFn>>,
40}
41
42/// The interpreter-side handle to the background AOT compiler. `submit` is
43/// non-blocking; `try_drain` is polled at profiling points and `drain_blocking` is the
44/// determinism hook tests use. The worker detaches on drop.
45pub struct BgAotCompiler {
46 req_tx: Sender<AotRequest>,
47 res_rx: Receiver<AotResult>,
48 inflight: usize,
49 _worker: JoinHandle<()>,
50}
51
52impl BgAotCompiler {
53 /// Spawn the AOT build worker.
54 pub fn new() -> Self {
55 let (req_tx, req_rx) = std::sync::mpsc::channel::<AotRequest>();
56 let (res_tx, res_rx) = std::sync::mpsc::channel::<AotResult>();
57 let worker = std::thread::spawn(move || {
58 while let Ok(req) = req_rx.recv() {
59 let nf = crate::compile::aot_build_function(&req.source, &req.fn_name, &req.cache_dir);
60 if res_tx.send(AotResult { fi: req.fi, nf }).is_err() {
61 break; // interpreter gone — stop quietly
62 }
63 }
64 });
65 BgAotCompiler { req_tx, res_rx, inflight: 0, _worker: worker }
66 }
67
68 /// Queue an AOT build. Non-blocking; the interpreter keeps running on the lower
69 /// tier (bytecode / forge) while `rustc` works.
70 pub fn submit(&mut self, req: AotRequest) {
71 if self.req_tx.send(req).is_ok() {
72 self.inflight += 1;
73 }
74 }
75
76 /// Take one finished AOT build if ready — polled at the profiling points.
77 pub fn try_drain(&mut self) -> Option<AotResult> {
78 match self.res_rx.try_recv() {
79 Ok(r) => {
80 self.inflight = self.inflight.saturating_sub(1);
81 Some(r)
82 }
83 Err(_) => None,
84 }
85 }
86
87 /// Whether any submitted build is still outstanding.
88 pub fn is_idle(&self) -> bool {
89 self.inflight == 0
90 }
91
92 /// Block until every outstanding build has come back — the determinism hook for
93 /// the differential gates (a single AOT build is seconds, so tests opt in).
94 pub fn drain_blocking(&mut self) -> Vec<AotResult> {
95 let mut out = Vec::with_capacity(self.inflight);
96 while self.inflight > 0 {
97 match self.res_rx.recv() {
98 Ok(r) => {
99 self.inflight -= 1;
100 out.push(r);
101 }
102 Err(_) => break,
103 }
104 }
105 out
106 }
107}
108
109impl Default for BgAotCompiler {
110 fn default() -> Self {
111 Self::new()
112 }
113}