Skip to content

Commit 7553529

Browse files
Haofeiclaude
andcommitted
JIT: cover option/result/match/closures; clarify it's a method-based baseline JIT
run_jit + jit_supported_instruction now also handle MakeSome, LoadNone, MakeClosure, MatchOption/Result/Variant, MatchMapGet, UnwrapSome, UnwrapVariantValue, RuntimeError (exact copies of drive's arms; shared helpers keep them gap-free). The 3-way parity suite (interp == jit == compiled) exercises these across option/result/match-heavy fixtures. docs/jit-todo.md: mark Match* done; document that this is a per-function (method-based) baseline JIT — not a tracing JIT, and not native codegen yet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 757b113 commit 7553529

2 files changed

Lines changed: 151 additions & 2 deletions

File tree

docs/jit-todo.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ VM interpreter, the JIT, and the compiled (Rust-lowering) backend.
66
Design invariants (must hold at every step): see `docs/jit-roadmap.md`.
77
Status legend: `[x]` done · `[ ]` todo · `[~]` in progress.
88

9+
## What kind of JIT is this?
10+
11+
**Method-based (per-function) baseline JIT — not a tracing JIT, not native (yet).**
12+
13+
- **Per-function, not tracing:** it compiles whole functions, deciding eligibility
14+
up front. A tracing JIT instead records hot linear execution *traces* (across
15+
call/loop boundaries) and compiles those with guards; we don't do that.
16+
- **Tier-0 = specializing executor:** today it executes the supported instruction
17+
subset via a reduced-dispatch loop (`RegVm::run_jit`) that reuses the
18+
interpreter's exact semantics, with per-function fallback. This is gap-free but
19+
only a modest speedup (no native code, values still boxed `VmValue`).
20+
- **Next = native method JIT:** Cranelift codegen + unboxed numeric registers in a
21+
separate crate (see below) is where real performance comes from. Tracing could
22+
be a later alternative/addition, but the method JIT is the chosen path.
23+
924
## Done
1025

1126
- [x] **N-way differential framework**`Backend` trait (interpreter / jit /
@@ -21,7 +36,10 @@ Status legend: `[x]` done · `[ ]` todo · `[~]` in progress.
2136
- [x] **Covered instruction subset (tier-0):** loads (unit/int/float/bool/string),
2237
move, deep-copy, manage, int arithmetic/bitwise/shift, int comparisons,
2338
equal/not-equal, jumps (uncond / if-bool / if-int-compare), get/set field,
24-
make struct/variant/list/object/map, return.
39+
make struct/variant/list/object/map, option/result (make-some, load-none,
40+
unwrap-some, unwrap-variant-value), match (option/result/variant/map-get),
41+
make-closure, runtime-error, return.
42+
- [x] **Option/Result/match coverage** verified three-way across the parity suite.
2543
- [x] **Out-of-range int literal rejected at the frontend** (RS0033) — keeps the
2644
three backends consistent.
2745

@@ -32,7 +50,7 @@ Status legend: `[x]` done · `[ ]` todo · `[~]` in progress.
3250
`try_exec_pure(instr, base, &mut ip)` used by both (structural gap-freeness;
3351
currently the two copies are guarded only by the differential).
3452
- [ ] Collection get/set + index ops (List/Map get/set) in the eligible subset.
35-
- [ ] `Match*` (option/result/variant) in the eligible subset.
53+
- [x] `Match*` (option/result/variant/map-get) in the eligible subset.
3654
- [ ] Cross-function: let JIT-compiled code call other functions (needs frame
3755
push integration) — currently any `Call` makes a function fall back.
3856
- [ ] Float / string / bytes ops parity coverage in the generator.

src/reg_vm.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ fn jit_supported_instruction(instr: &RegInstr) -> bool {
9999
| RegInstr::MakeList { .. }
100100
| RegInstr::MakeObject { .. }
101101
| RegInstr::MakeMap { .. }
102+
| RegInstr::MakeSome { .. }
103+
| RegInstr::LoadNone { .. }
104+
| RegInstr::MakeClosure { .. }
105+
| RegInstr::MatchOption { .. }
106+
| RegInstr::MatchResult { .. }
107+
| RegInstr::MatchVariant { .. }
108+
| RegInstr::MatchMapGet { .. }
109+
| RegInstr::UnwrapSome { .. }
110+
| RegInstr::UnwrapVariantValue { .. }
111+
| RegInstr::RuntimeError { .. }
102112
| RegInstr::AddInt { .. }
103113
| RegInstr::SubInt { .. }
104114
| RegInstr::MulInt { .. }
@@ -5070,6 +5080,127 @@ impl RegVm {
50705080
ip = *target;
50715081
}
50725082
}
5083+
RegInstr::MakeSome { dst, value } => {
5084+
let value = self.reg(base + *value).clone();
5085+
self.set_reg(base + *dst, VmValue::OptionSome(Box::new(value)));
5086+
}
5087+
RegInstr::LoadNone { dst } => {
5088+
self.set_reg(base + *dst, VmValue::OptionNone);
5089+
}
5090+
RegInstr::MakeClosure {
5091+
dst,
5092+
function: callee,
5093+
captures,
5094+
} => {
5095+
let mut captured = Vec::with_capacity(captures.len());
5096+
for reg in captures {
5097+
captured.push(self.reg(base + *reg).clone());
5098+
}
5099+
self.set_reg(
5100+
base + *dst,
5101+
VmValue::Closure(Rc::new(VmClosure {
5102+
function: *callee,
5103+
captures: captured,
5104+
})),
5105+
);
5106+
}
5107+
RegInstr::MatchOption {
5108+
src,
5109+
some_ip,
5110+
none_ip,
5111+
} => match self.reg(base + *src) {
5112+
VmValue::OptionSome(_) => ip = *some_ip,
5113+
VmValue::OptionNone => ip = *none_ip,
5114+
other => {
5115+
return Err(EvalError::Runtime(format!(
5116+
"reg VM Option match expected Option, got `{}`.",
5117+
other.display()
5118+
)));
5119+
}
5120+
},
5121+
RegInstr::MatchResult { src, ok_ip, err_ip } => match self.reg(base + *src) {
5122+
VmValue::Variant(data) if data.name.as_ref() == "Ok" => ip = *ok_ip,
5123+
VmValue::Variant(data) if data.name.as_ref() == "Err" => ip = *err_ip,
5124+
other => {
5125+
return Err(EvalError::Runtime(format!(
5126+
"reg VM Result match expected Result, got `{}`.",
5127+
other.display()
5128+
)));
5129+
}
5130+
},
5131+
RegInstr::MatchVariant {
5132+
src,
5133+
expected,
5134+
match_ip,
5135+
else_ip,
5136+
} => match self.reg(base + *src) {
5137+
VmValue::Variant(data) if data.name.as_ref() == expected.as_str() => {
5138+
ip = *match_ip
5139+
}
5140+
VmValue::Variant(_) => ip = *else_ip,
5141+
other => {
5142+
return Err(EvalError::Runtime(format!(
5143+
"reg VM variant match expected `{expected}`, got `{}`.",
5144+
other.display()
5145+
)));
5146+
}
5147+
},
5148+
RegInstr::MatchMapGet {
5149+
map,
5150+
key,
5151+
value_dst,
5152+
some_ip,
5153+
none_ip,
5154+
} => {
5155+
let map = expect_map_ref(self.reg(base + *map))?;
5156+
let key = map_key_from_value(self.reg(base + *key))?;
5157+
if let Some(value) = map.borrow().get(&key).cloned() {
5158+
self.set_reg(base + *value_dst, value);
5159+
ip = *some_ip;
5160+
} else {
5161+
ip = *none_ip;
5162+
}
5163+
}
5164+
RegInstr::UnwrapSome { dst, src } => {
5165+
let value = match self.reg(base + *src) {
5166+
VmValue::OptionSome(value) => (**value).clone(),
5167+
other => {
5168+
return Err(EvalError::Runtime(format!(
5169+
"reg VM Some binding expected Some, got `{}`.",
5170+
other.display()
5171+
)));
5172+
}
5173+
};
5174+
self.set_reg(base + *dst, value);
5175+
}
5176+
RegInstr::UnwrapVariantValue { dst, src, expected } => {
5177+
let value = match self.reg(base + *src) {
5178+
VmValue::Variant(data) if data.name.as_ref() == expected.as_str() => data
5179+
.fields
5180+
.get("value")
5181+
.cloned()
5182+
.or_else(|| {
5183+
(data.fields.len() == 1)
5184+
.then(|| data.fields.values().next().cloned())
5185+
.flatten()
5186+
})
5187+
.ok_or_else(|| {
5188+
EvalError::Runtime(format!(
5189+
"reg VM `{expected}` variant is missing value."
5190+
))
5191+
})?,
5192+
other => {
5193+
return Err(EvalError::Runtime(format!(
5194+
"reg VM expected `{expected}` variant, got `{}`.",
5195+
other.display()
5196+
)));
5197+
}
5198+
};
5199+
self.set_reg(base + *dst, value);
5200+
}
5201+
RegInstr::RuntimeError { message } => {
5202+
return Err(EvalError::Runtime(message.clone()));
5203+
}
50735204
RegInstr::Return { src } => return Ok(self.take_reg(base + *src)),
50745205
// Eligibility (`jit_supported_instruction`) guarantees only the
50755206
// instructions above reach here. A mismatch would be an internal

0 commit comments

Comments
 (0)