Skip to content

Commit 5aeab2b

Browse files
committed
feat(jit): declarative builtin spec for string_len regex_match array_set
1 parent fd2a7e0 commit 5aeab2b

3 files changed

Lines changed: 327 additions & 116 deletions

File tree

src/vm/jit/builtin_spec.rs

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
//! Declarative metadata for specialized builtin recording.
2+
//!
3+
//! Each `BuiltinSpec` captures the mechanical, per-builtin facts that the
4+
//! recorder needs to select, analyze, and emit a specialized SSA
5+
//! instruction. The goal is to reduce the six-layer touch-point tax
6+
//! (selection → analysis → emit → bridge → codegen → lowering) to a
7+
//! single authoritative spec plus dedicated semantic implementations.
8+
//!
9+
//! Scope: pilot coverage for `StringLen`, `RegexMatch`, and `ArraySet`.
10+
//! Non-pilot builtins continue to use their existing hand-written paths.
11+
12+
use super::ir::SsaValueRepr;
13+
use crate::ValueType;
14+
15+
/// How a builtin interacts with the VM heap and failure domain.
16+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17+
pub(crate) enum BuiltinEffect {
18+
/// Pure read-only operation; no heap mutation, no failure exit.
19+
Pure,
20+
/// Calls a fallible bridge helper; failure triggers a deopt exit.
21+
FallibleHelper,
22+
/// Owned mutation with clone-before-transfer semantics and failure exit.
23+
OwnedMutation,
24+
}
25+
26+
/// Runtime representation requirement for one input operand.
27+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28+
pub(crate) enum InputRepr {
29+
/// Must be `SsaValueRepr::I64` (int).
30+
Int,
31+
/// Must be a heap pointer of the given container type.
32+
HeapPtr(HeapInputKind),
33+
/// Any tagged value (used for owned mutation values).
34+
Tagged,
35+
/// Any representation; used as-is.
36+
Any,
37+
}
38+
39+
/// Heap container kinds relevant to builtin specialization.
40+
#[allow(dead_code)] // Variants used by future builtin families.
41+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42+
pub(crate) enum HeapInputKind {
43+
String,
44+
Bytes,
45+
Array,
46+
Map,
47+
}
48+
49+
impl HeapInputKind {
50+
pub(crate) fn value_type(self) -> ValueType {
51+
match self {
52+
Self::String => ValueType::String,
53+
Self::Bytes => ValueType::Bytes,
54+
Self::Array => ValueType::Array,
55+
Self::Map => ValueType::Map,
56+
}
57+
}
58+
}
59+
60+
/// Output type produced by a specialized builtin.
61+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62+
pub(crate) enum OutputKind {
63+
Int,
64+
Bool,
65+
Tagged(ValueType),
66+
/// Tagged value whose type is not statically known.
67+
#[allow(dead_code)] // Used by future builtin families.
68+
TaggedUnknown,
69+
}
70+
71+
impl OutputKind {
72+
pub(crate) fn repr(self) -> SsaValueRepr {
73+
match self {
74+
Self::Int => SsaValueRepr::I64,
75+
Self::Bool => SsaValueRepr::Bool,
76+
Self::Tagged(_) | Self::TaggedUnknown => SsaValueRepr::Tagged,
77+
}
78+
}
79+
}
80+
81+
/// Declarative specification for one specialized builtin.
82+
///
83+
/// The recorder reads this to drive generic analyze/emit paths.
84+
/// Dedicated lowering implementations in `lower.rs` remain typed and
85+
/// are *not* replaced by this table.
86+
pub(crate) struct BuiltinSpec {
87+
/// Human-readable name for diagnostics.
88+
pub(crate) name: &'static str,
89+
/// Number of arguments popped from the analysis frame (in reverse order).
90+
pub(crate) arity: usize,
91+
/// Input requirements, in pop order (last argument first).
92+
pub(crate) inputs: &'static [InputRepr],
93+
/// Output type.
94+
pub(crate) output: OutputKind,
95+
/// Effect classification.
96+
#[allow(dead_code)] // Read by future lowering/registry generation.
97+
pub(crate) effect: BuiltinEffect,
98+
/// Whether the builtin requires a failure exit on helper error.
99+
#[allow(dead_code)] // Read by future lowering/registry generation.
100+
pub(crate) needs_failure_exit: bool,
101+
}
102+
103+
/// `string.len()` — pure read, scalar result.
104+
pub(crate) const STRING_LEN_SPEC: BuiltinSpec = BuiltinSpec {
105+
name: "string_len",
106+
arity: 1,
107+
inputs: &[InputRepr::HeapPtr(HeapInputKind::String)],
108+
output: OutputKind::Int,
109+
effect: BuiltinEffect::Pure,
110+
needs_failure_exit: false,
111+
};
112+
113+
/// `re_match(pattern, text)` — fallible helper with bridge error.
114+
pub(crate) const REGEX_MATCH_SPEC: BuiltinSpec = BuiltinSpec {
115+
name: "regex_match",
116+
arity: 2,
117+
inputs: &[
118+
InputRepr::HeapPtr(HeapInputKind::String), // text (popped second)
119+
InputRepr::HeapPtr(HeapInputKind::String), // pattern (popped first)
120+
],
121+
output: OutputKind::Bool,
122+
effect: BuiltinEffect::FallibleHelper,
123+
needs_failure_exit: true,
124+
};
125+
126+
/// `array.set(index, value)` — owned mutation, aliasing, failure exit.
127+
///
128+
/// Note: the recorder additionally detects the append-pattern
129+
/// (`index == array.len()`) and rewrites to `ArrayPush`. That
130+
/// optimization is semantic, not mechanical, and stays in the
131+
/// recorder's typed emit path.
132+
pub(crate) const ARRAY_SET_SPEC: BuiltinSpec = BuiltinSpec {
133+
name: "array_set",
134+
arity: 3,
135+
inputs: &[
136+
InputRepr::Any, // value (popped third)
137+
InputRepr::Int, // index (popped second)
138+
InputRepr::Tagged, // array (popped first, must be owned Tagged)
139+
],
140+
output: OutputKind::Tagged(ValueType::Array),
141+
effect: BuiltinEffect::OwnedMutation,
142+
needs_failure_exit: true,
143+
};
144+
145+
/// Look up the pilot spec for a specialized builtin kind, if one exists.
146+
///
147+
/// Returns `None` for builtins not yet covered by the pilot; their
148+
/// hand-written recorder paths remain authoritative.
149+
pub(crate) fn pilot_spec_for(
150+
kind: super::recorder::SpecializedBuiltinKind,
151+
) -> Option<&'static BuiltinSpec> {
152+
match kind {
153+
super::recorder::SpecializedBuiltinKind::StringLen => Some(&STRING_LEN_SPEC),
154+
super::recorder::SpecializedBuiltinKind::RegexMatch => Some(&REGEX_MATCH_SPEC),
155+
super::recorder::SpecializedBuiltinKind::ArraySet => Some(&ARRAY_SET_SPEC),
156+
_ => None,
157+
}
158+
}
159+
160+
#[cfg(test)]
161+
mod tests {
162+
use super::*;
163+
164+
#[test]
165+
fn pilot_specs_have_consistent_arity_and_inputs() {
166+
for spec in [&STRING_LEN_SPEC, &REGEX_MATCH_SPEC, &ARRAY_SET_SPEC] {
167+
assert_eq!(
168+
spec.arity,
169+
spec.inputs.len(),
170+
"{}: arity must match inputs",
171+
spec.name
172+
);
173+
}
174+
}
175+
176+
#[test]
177+
fn effect_classification_is_explicit() {
178+
const {
179+
assert!(matches!(STRING_LEN_SPEC.effect, BuiltinEffect::Pure));
180+
assert!(!STRING_LEN_SPEC.needs_failure_exit);
181+
assert!(matches!(
182+
REGEX_MATCH_SPEC.effect,
183+
BuiltinEffect::FallibleHelper
184+
));
185+
assert!(REGEX_MATCH_SPEC.needs_failure_exit);
186+
assert!(matches!(
187+
ARRAY_SET_SPEC.effect,
188+
BuiltinEffect::OwnedMutation
189+
));
190+
assert!(ARRAY_SET_SPEC.needs_failure_exit);
191+
}
192+
}
193+
}

src/vm/jit/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub(crate) mod builtin_spec;
12
pub(crate) mod deopt;
23
pub(crate) mod inline;
34
pub(crate) mod ir;

0 commit comments

Comments
 (0)