@@ -65,12 +65,7 @@ pub fn reg_vm_eval_source_main_jit(
6565 source: &str,
6666 args: impl IntoIterator<Item = impl Into<String>>,
6767) -> Result<EvalOutput, EvalError> {
68- let executable = reg_vm_compile_source(file, source)?;
69- // Run the eligibility pass on the real program (the JIT "compile" step); its
70- // result drives native codegen in the next tier. Execution stays on the
71- // shared interpreter, so output is identical to `reg_vm_eval_source_main`.
72- let _plan = executable.jit_plan();
73- executable.eval_main_with_args(args)
68+ reg_vm_compile_source(file, source)?.eval_main_with_args_jit(args)
7469}
7570
7671/// Per-program JIT eligibility: how many functions are fully covered by the
@@ -242,6 +237,31 @@ impl RegVmExecutable {
242237 )
243238 }
244239
240+ /// Run `main` with the tier-0 JIT enabled: JIT-eligible functions execute via
241+ /// the specializing executor, the rest via the interpreter. Output is
242+ /// identical to `eval_main_with_args` (verified by the N-way differential).
243+ pub fn eval_main_with_args_jit(
244+ &self,
245+ args: impl IntoIterator<Item = impl Into<String>>,
246+ ) -> Result<EvalOutput, EvalError> {
247+ let mut vm = RegVm::new(
248+ Rc::clone(&self.unit),
249+ args.into_iter().map(Into::into).collect(),
250+ HashMap::new(),
251+ );
252+ vm.jit_enabled = true;
253+ let value = vm.run_program("main")?;
254+ let display_value = value.display();
255+ let native_value = value.native_value();
256+ Ok(EvalOutput {
257+ value: display_value.clone(),
258+ display_value,
259+ native_value,
260+ stdout: vm.stdout,
261+ stderr: vm.stderr,
262+ })
263+ }
264+
245265 pub fn eval_main_with_args_and_native_bindings(
246266 &self,
247267 args: impl IntoIterator<Item = impl Into<String>>,
@@ -4716,6 +4736,12 @@ struct RegVm {
47164736 websockets: HashMap<i64, TcpStream>,
47174737 next_pool_id: i64,
47184738 pools: HashMap<i64, VmResourcePool>,
4739+ /// Tier-0 JIT: when set, JIT-eligible functions run via the specializing
4740+ /// executor `run_jit` (which reuses the interpreter's value/register
4741+ /// semantics, so it is gap-free by construction).
4742+ jit_enabled: bool,
4743+ /// Cached JIT eligibility per function (keyed by `Rc` identity).
4744+ jit_eligibility: HashMap<*const RegFunction, bool>,
47194745}
47204746
47214747#[derive(Debug, Clone)]
@@ -4785,7 +4811,189 @@ impl RegVm {
47854811 websockets: HashMap::new(),
47864812 next_pool_id: 1,
47874813 pools: HashMap::new(),
4814+ jit_enabled: false,
4815+ jit_eligibility: HashMap::new(),
4816+ }
4817+ }
4818+
4819+ /// Whether `func` is fully covered by the tier-0 JIT instruction subset
4820+ /// (cached by `Rc` identity).
4821+ fn is_jit_eligible(&mut self, func: &Rc<RegFunction>) -> bool {
4822+ let key = Rc::as_ptr(func);
4823+ if let Some(&eligible) = self.jit_eligibility.get(&key) {
4824+ return eligible;
4825+ }
4826+ let eligible = func.code.iter().all(jit_supported_instruction);
4827+ self.jit_eligibility.insert(key, eligible);
4828+ eligible
4829+ }
4830+
4831+ /// Tier-0 JIT executor for a JIT-eligible function. Runs the body via the
4832+ /// same shared helpers (`eval_numeric_binary`, `eval_numeric_compare`, …) and
4833+ /// register methods (`reg`/`set_reg`/`take_reg`) the interpreter uses, so its
4834+ /// result is identical to `drive` by construction. Eligible functions contain
4835+ /// no calls, awaits, or blocking ops, so this never suspends or pushes frames.
4836+ fn run_jit(&mut self, func: &RegFunction, base: usize) -> Result<VmValue, EvalError> {
4837+ let mut ip = 0usize;
4838+ while let Some(instr) = func.code.get(ip) {
4839+ ip += 1;
4840+ match instr {
4841+ RegInstr::LoadUnit { dst } => self.set_reg(base + *dst, VmValue::Unit),
4842+ RegInstr::LoadInt { dst, value } => self.set_reg(base + *dst, VmValue::Int(*value)),
4843+ RegInstr::LoadFloat { dst, value } => {
4844+ self.set_reg(base + *dst, VmValue::Float(*value))
4845+ }
4846+ RegInstr::LoadBool { dst, value } => {
4847+ self.set_reg(base + *dst, VmValue::Bool(*value))
4848+ }
4849+ RegInstr::Move { dst, src } => {
4850+ let value = self.reg(base + *src).clone();
4851+ self.set_reg(base + *dst, value);
4852+ }
4853+ RegInstr::DeepCopy { reg } => {
4854+ let copied = deep_copy_value(self.reg(base + *reg));
4855+ self.set_reg(base + *reg, copied);
4856+ }
4857+ RegInstr::AddInt { dst, lhs, rhs } => {
4858+ let value = eval_numeric_binary(
4859+ BinaryOp::Add,
4860+ self.reg(base + *lhs),
4861+ self.reg(base + *rhs),
4862+ )?;
4863+ self.set_reg(base + *dst, value);
4864+ }
4865+ RegInstr::SubInt { dst, lhs, rhs } => {
4866+ let value = eval_numeric_binary(
4867+ BinaryOp::Subtract,
4868+ self.reg(base + *lhs),
4869+ self.reg(base + *rhs),
4870+ )?;
4871+ self.set_reg(base + *dst, value);
4872+ }
4873+ RegInstr::MulInt { dst, lhs, rhs } => {
4874+ let value = eval_numeric_binary(
4875+ BinaryOp::Multiply,
4876+ self.reg(base + *lhs),
4877+ self.reg(base + *rhs),
4878+ )?;
4879+ self.set_reg(base + *dst, value);
4880+ }
4881+ RegInstr::DivInt { dst, lhs, rhs } => {
4882+ let value = eval_numeric_binary(
4883+ BinaryOp::Divide,
4884+ self.reg(base + *lhs),
4885+ self.reg(base + *rhs),
4886+ )?;
4887+ self.set_reg(base + *dst, value);
4888+ }
4889+ RegInstr::ModInt { dst, lhs, rhs } => {
4890+ let value = eval_numeric_binary(
4891+ BinaryOp::Modulo,
4892+ self.reg(base + *lhs),
4893+ self.reg(base + *rhs),
4894+ )?;
4895+ self.set_reg(base + *dst, value);
4896+ }
4897+ RegInstr::BitAndInt { dst, lhs, rhs } => {
4898+ let l = expect_int_ref(self.reg(base + *lhs))?;
4899+ let r = expect_int_ref(self.reg(base + *rhs))?;
4900+ self.set_reg(base + *dst, VmValue::Int(l & r));
4901+ }
4902+ RegInstr::BitOrInt { dst, lhs, rhs } => {
4903+ let l = expect_int_ref(self.reg(base + *lhs))?;
4904+ let r = expect_int_ref(self.reg(base + *rhs))?;
4905+ self.set_reg(base + *dst, VmValue::Int(l | r));
4906+ }
4907+ RegInstr::BitXorInt { dst, lhs, rhs } => {
4908+ let l = expect_int_ref(self.reg(base + *lhs))?;
4909+ let r = expect_int_ref(self.reg(base + *rhs))?;
4910+ self.set_reg(base + *dst, VmValue::Int(l ^ r));
4911+ }
4912+ RegInstr::ShiftLeftInt { dst, lhs, rhs } => {
4913+ let l = expect_int_ref(self.reg(base + *lhs))?;
4914+ let r = expect_int_ref(self.reg(base + *rhs))?;
4915+ self.set_reg(base + *dst, VmValue::Int(l.wrapping_shl(r.max(0) as u32)));
4916+ }
4917+ RegInstr::ShiftRightInt { dst, lhs, rhs } => {
4918+ let l = expect_int_ref(self.reg(base + *lhs))?;
4919+ let r = expect_int_ref(self.reg(base + *rhs))?;
4920+ self.set_reg(base + *dst, VmValue::Int(l.wrapping_shr(r.max(0) as u32)));
4921+ }
4922+ RegInstr::LessInt { dst, lhs, rhs } => {
4923+ let value = eval_numeric_compare(
4924+ RegIntCompare::Less,
4925+ self.reg(base + *lhs),
4926+ self.reg(base + *rhs),
4927+ )?;
4928+ self.set_reg(base + *dst, VmValue::Bool(value));
4929+ }
4930+ RegInstr::LessEqualInt { dst, lhs, rhs } => {
4931+ let value = eval_numeric_compare(
4932+ RegIntCompare::LessEqual,
4933+ self.reg(base + *lhs),
4934+ self.reg(base + *rhs),
4935+ )?;
4936+ self.set_reg(base + *dst, VmValue::Bool(value));
4937+ }
4938+ RegInstr::GreaterInt { dst, lhs, rhs } => {
4939+ let value = eval_numeric_compare(
4940+ RegIntCompare::Greater,
4941+ self.reg(base + *lhs),
4942+ self.reg(base + *rhs),
4943+ )?;
4944+ self.set_reg(base + *dst, VmValue::Bool(value));
4945+ }
4946+ RegInstr::GreaterEqualInt { dst, lhs, rhs } => {
4947+ let value = eval_numeric_compare(
4948+ RegIntCompare::GreaterEqual,
4949+ self.reg(base + *lhs),
4950+ self.reg(base + *rhs),
4951+ )?;
4952+ self.set_reg(base + *dst, VmValue::Bool(value));
4953+ }
4954+ RegInstr::Equal { dst, lhs, rhs } => {
4955+ let eq = self.reg(base + *lhs) == self.reg(base + *rhs);
4956+ self.set_reg(base + *dst, VmValue::Bool(eq));
4957+ }
4958+ RegInstr::NotEqual { dst, lhs, rhs } => {
4959+ let ne = self.reg(base + *lhs) != self.reg(base + *rhs);
4960+ self.set_reg(base + *dst, VmValue::Bool(ne));
4961+ }
4962+ RegInstr::Jump { target } => ip = *target,
4963+ RegInstr::JumpIfBool {
4964+ cond,
4965+ expected,
4966+ target,
4967+ } => {
4968+ if expect_bool_ref(self.reg(base + *cond))? == *expected {
4969+ ip = *target;
4970+ }
4971+ }
4972+ RegInstr::JumpIfIntCompare {
4973+ lhs,
4974+ rhs,
4975+ op,
4976+ expected,
4977+ target,
4978+ } => {
4979+ let l = self.reg(base + *lhs);
4980+ let r = self.reg(base + *rhs);
4981+ if eval_numeric_compare(*op, l, r)? == *expected {
4982+ ip = *target;
4983+ }
4984+ }
4985+ RegInstr::Return { src } => return Ok(self.take_reg(base + *src)),
4986+ // Eligibility (`jit_supported_instruction`) guarantees only the
4987+ // instructions above reach here. A mismatch would be an internal
4988+ // bug; surface it instead of silently diverging.
4989+ other => {
4990+ return Err(EvalError::Runtime(format!(
4991+ "reg VM JIT reached non-eligible instruction `{other:?}`."
4992+ )));
4993+ }
4994+ }
47884995 }
4996+ Ok(VmValue::Unit)
47894997 }
47904998
47914999 /// Grow the shared register stack so that `stack[..upto]` is addressable.
@@ -5203,6 +5411,21 @@ impl RegVm {
52035411 let base = self.frames.last().expect("active frame").base;
52045412 let next_base = base + func.regs;
52055413 let mut ip = self.frames.last().expect("active frame").ip;
5414+
5415+ // Tier-0 JIT: a fresh JIT-eligible frame runs via the specializing
5416+ // executor (which reuses the interpreter's semantics), then completes
5417+ // exactly like the `Return` arm. Eligible functions never suspend, so
5418+ // they are always entered at `ip == 0`.
5419+ if self.jit_enabled && ip == 0 && self.is_jit_eligible(&func) {
5420+ let value = self.run_jit(&func, base)?;
5421+ let frame = self.frames.pop().expect("active frame");
5422+ if self.frames.len() == floor {
5423+ return Ok(Outcome::Completed(value));
5424+ }
5425+ self.set_reg(frame.ret_dst, value);
5426+ continue 'frames;
5427+ }
5428+
52065429 while let Some(instr) = func.code.get(ip) {
52075430 ip += 1;
52085431 match instr {
0 commit comments