Skip to main content

harn_kernel/
opcode.rs

1//! Stable opcode vocabulary shared by every Harn execution target.
2//!
3//! This is the bytecode ABI's single schema. Numeric discriminants and operand
4//! layouts are versioned artifact data, not implementation details. Consumers
5//! must use [`Op::operands`] instead of maintaining byte-width tables.
6
7/// One encoded operand in a Harn bytecode instruction.
8///
9/// The width and semantic role live together so artifact verification can
10/// validate indices and jump targets without another opcode table.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum OperandKind {
13    ImmediateU8,
14    ImmediateU16,
15    BuiltinIdU64,
16    ConstantU16,
17    StringConstantU16,
18    LocalU16,
19    FunctionU16,
20    JumpU16,
21    /// Index into the chunk's binding-type table. Distinct from `LocalU16`
22    /// because a binding assertion is emitted for module-level bindings too,
23    /// which have no local slot.
24    BindingTypeU16,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum Portability {
29    Executable,
30    Deferred,
31}
32
33impl OperandKind {
34    pub const fn width(self) -> usize {
35        match self {
36            Self::ImmediateU8 => 1,
37            Self::ImmediateU16
38            | Self::ConstantU16
39            | Self::StringConstantU16
40            | Self::LocalU16
41            | Self::FunctionU16
42            | Self::JumpU16
43            | Self::BindingTypeU16 => 2,
44            Self::BuiltinIdU64 => 8,
45        }
46    }
47
48    const fn abi_tag(self) -> u8 {
49        match self {
50            Self::ImmediateU8 => 0,
51            Self::ImmediateU16 => 1,
52            Self::BuiltinIdU64 => 2,
53            Self::ConstantU16 => 3,
54            Self::LocalU16 => 4,
55            Self::FunctionU16 => 5,
56            Self::JumpU16 => 6,
57            Self::StringConstantU16 => 7,
58            Self::BindingTypeU16 => 8,
59        }
60    }
61}
62
63macro_rules! define_opcodes {
64    ($($name:ident = $byte:literal => [$($operand:ident),* $(,)?]),+ $(,)?) => {
65        #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
66        #[repr(u8)]
67        pub enum Op { $($name = $byte),+ }
68
69        impl Op {
70            pub const ALL: &'static [Self] = &[$(Self::$name),+];
71            pub const COUNT: usize = Self::ALL.len();
72
73            #[inline]
74            pub fn from_byte(byte: u8) -> Option<Self> {
75                Self::ALL.get(byte as usize).copied()
76            }
77
78            pub const fn name(self) -> &'static str {
79                match self { $(Self::$name => stringify!($name)),+ }
80            }
81
82            pub const fn operands(self) -> &'static [OperandKind] {
83                match self {
84                    $(Self::$name => &[$(OperandKind::$operand),*]),+
85                }
86            }
87
88            pub const fn instruction_len(self) -> usize {
89                let operands = self.operands();
90                let mut index = 0;
91                let mut width = 1;
92                while index < operands.len() {
93                    width += operands[index].width();
94                    index += 1;
95                }
96                width
97            }
98        }
99    };
100}
101
102define_opcodes! {
103    Constant = 0 => [ConstantU16],
104    Nil = 1 => [],
105    True = 2 => [],
106    False = 3 => [],
107    RootHarness = 4 => [],
108    GetVar = 5 => [StringConstantU16],
109    DefLet = 6 => [StringConstantU16],
110    DefVar = 7 => [StringConstantU16],
111    DefCell = 8 => [StringConstantU16],
112    SetVar = 9 => [StringConstantU16],
113    PushScope = 10 => [],
114    PopScope = 11 => [],
115    Add = 12 => [],
116    Sub = 13 => [],
117    Mul = 14 => [],
118    Div = 15 => [],
119    Mod = 16 => [],
120    Pow = 17 => [],
121    Negate = 18 => [],
122    Equal = 19 => [],
123    NotEqual = 20 => [],
124    Less = 21 => [],
125    Greater = 22 => [],
126    LessEqual = 23 => [],
127    GreaterEqual = 24 => [],
128    Not = 25 => [],
129    Jump = 26 => [JumpU16],
130    JumpIfFalse = 27 => [JumpU16],
131    JumpIfTrue = 28 => [JumpU16],
132    Pop = 29 => [],
133    Call = 30 => [ImmediateU8],
134    TailCall = 31 => [ImmediateU8],
135    Return = 32 => [],
136    Closure = 33 => [FunctionU16],
137    BuildList = 34 => [ImmediateU16],
138    BuildDict = 35 => [ImmediateU16],
139    Subscript = 36 => [],
140    SubscriptOpt = 37 => [],
141    Slice = 38 => [],
142    GetProperty = 39 => [StringConstantU16],
143    GetPropertyOpt = 40 => [StringConstantU16],
144    SetProperty = 41 => [StringConstantU16, StringConstantU16],
145    SetSubscript = 42 => [StringConstantU16],
146    SetLocalSlotProperty = 43 => [StringConstantU16, LocalU16],
147    SetLocalSlotSubscript = 44 => [LocalU16],
148    MethodCall = 45 => [StringConstantU16, ImmediateU8],
149    MethodCallOpt = 46 => [StringConstantU16, ImmediateU8],
150    Concat = 47 => [ImmediateU16],
151    IterInit = 48 => [],
152    IterNext = 49 => [JumpU16],
153    Pipe = 50 => [],
154    Throw = 51 => [],
155    TryCatchSetup = 52 => [JumpU16, StringConstantU16],
156    PopHandler = 53 => [],
157    Parallel = 54 => [],
158    ParallelMap = 55 => [],
159    ParallelMapStream = 56 => [],
160    ParallelSettle = 57 => [],
161    Spawn = 58 => [],
162    SyncMutexEnter = 59 => [],
163    SyncMutexEnterKeyed = 60 => [],
164    TaskScopeEnter = 61 => [],
165    TaskScopeExit = 62 => [],
166    Import = 63 => [StringConstantU16],
167    SelectiveImport = 64 => [StringConstantU16, StringConstantU16],
168    NamespaceImport = 65 => [StringConstantU16, StringConstantU16],
169    DeadlineSetup = 66 => [],
170    DeadlineEnd = 67 => [],
171    BuildEnum = 68 => [StringConstantU16, StringConstantU16, ImmediateU16],
172    MatchEnum = 69 => [StringConstantU16, StringConstantU16],
173    PopIterator = 70 => [],
174    GetArgc = 71 => [],
175    CheckType = 72 => [StringConstantU16, StringConstantU16],
176    TryUnwrap = 73 => [],
177    TryWrapOk = 74 => [],
178    CallSpread = 75 => [],
179    CallBuiltin = 76 => [BuiltinIdU64, StringConstantU16, ImmediateU8],
180    CallBuiltinSpread = 77 => [BuiltinIdU64, StringConstantU16],
181    MethodCallSpread = 78 => [StringConstantU16],
182    Dup = 79 => [],
183    Swap = 80 => [],
184    Contains = 81 => [],
185    AddInt = 82 => [],
186    SubInt = 83 => [],
187    MulInt = 84 => [],
188    DivInt = 85 => [],
189    ModInt = 86 => [],
190    AddFloat = 87 => [],
191    SubFloat = 88 => [],
192    MulFloat = 89 => [],
193    DivFloat = 90 => [],
194    ModFloat = 91 => [],
195    EqualInt = 92 => [],
196    NotEqualInt = 93 => [],
197    LessInt = 94 => [],
198    GreaterInt = 95 => [],
199    LessEqualInt = 96 => [],
200    GreaterEqualInt = 97 => [],
201    EqualFloat = 98 => [],
202    NotEqualFloat = 99 => [],
203    LessFloat = 100 => [],
204    GreaterFloat = 101 => [],
205    LessEqualFloat = 102 => [],
206    GreaterEqualFloat = 103 => [],
207    EqualBool = 104 => [],
208    NotEqualBool = 105 => [],
209    EqualString = 106 => [],
210    NotEqualString = 107 => [],
211    Yield = 108 => [],
212    GetLocalSlot = 109 => [LocalU16],
213    DefLocalSlot = 110 => [LocalU16],
214    SetLocalSlot = 111 => [LocalU16],
215    ConcatAssignLocal = 112 => [LocalU16],
216    NamespaceImportMembers = 113 => [StringConstantU16, StringConstantU16, StringConstantU16],
217    AssertBindingType = 114 => [BindingTypeU16],
218}
219
220impl Op {
221    /// Whether the portable kernel has an explicit execution arm for this
222    /// opcode. Artifact validation uses this closed classification so an opcode
223    /// addition cannot become browser-executable by omission.
224    pub const fn portability(self) -> Portability {
225        match self {
226            Self::Constant
227            | Self::Nil
228            | Self::True
229            | Self::False
230            | Self::RootHarness
231            | Self::GetVar
232            | Self::DefLet
233            | Self::DefVar
234            | Self::DefCell
235            | Self::SetVar
236            | Self::PushScope
237            | Self::PopScope
238            | Self::Add
239            | Self::Sub
240            | Self::Mul
241            | Self::Div
242            | Self::Mod
243            | Self::Pow
244            | Self::Negate
245            | Self::Equal
246            | Self::NotEqual
247            | Self::Less
248            | Self::Greater
249            | Self::LessEqual
250            | Self::GreaterEqual
251            | Self::Not
252            | Self::Jump
253            | Self::JumpIfFalse
254            | Self::JumpIfTrue
255            | Self::Pop
256            | Self::Call
257            | Self::TailCall
258            | Self::Return
259            | Self::Closure
260            | Self::BuildList
261            | Self::BuildDict
262            | Self::Subscript
263            | Self::SubscriptOpt
264            | Self::Slice
265            | Self::GetProperty
266            | Self::GetPropertyOpt
267            | Self::SetProperty
268            | Self::SetSubscript
269            | Self::SetLocalSlotProperty
270            | Self::SetLocalSlotSubscript
271            | Self::MethodCall
272            | Self::MethodCallOpt
273            | Self::Concat
274            | Self::Throw
275            | Self::TryCatchSetup
276            | Self::PopHandler
277            | Self::IterInit
278            | Self::IterNext
279            | Self::PopIterator
280            | Self::GetArgc
281            | Self::CallBuiltin
282            | Self::CallBuiltinSpread
283            | Self::Dup
284            | Self::Swap
285            | Self::Contains
286            | Self::AddInt
287            | Self::SubInt
288            | Self::MulInt
289            | Self::DivInt
290            | Self::ModInt
291            | Self::AddFloat
292            | Self::SubFloat
293            | Self::MulFloat
294            | Self::DivFloat
295            | Self::ModFloat
296            | Self::EqualInt
297            | Self::NotEqualInt
298            | Self::LessInt
299            | Self::GreaterInt
300            | Self::LessEqualInt
301            | Self::GreaterEqualInt
302            | Self::EqualFloat
303            | Self::NotEqualFloat
304            | Self::LessFloat
305            | Self::GreaterFloat
306            | Self::LessEqualFloat
307            | Self::GreaterEqualFloat
308            | Self::EqualBool
309            | Self::NotEqualBool
310            | Self::EqualString
311            | Self::NotEqualString
312            | Self::GetLocalSlot
313            | Self::DefLocalSlot
314            | Self::SetLocalSlot
315            | Self::ConcatAssignLocal
316            | Self::BuildEnum
317            | Self::MatchEnum
318            | Self::TryUnwrap
319            | Self::TryWrapOk
320            | Self::AssertBindingType => Portability::Executable,
321
322            Self::Pipe
323            | Self::Parallel
324            | Self::ParallelMap
325            | Self::ParallelMapStream
326            | Self::ParallelSettle
327            | Self::Spawn
328            | Self::SyncMutexEnter
329            | Self::SyncMutexEnterKeyed
330            | Self::TaskScopeEnter
331            | Self::TaskScopeExit
332            | Self::Import
333            | Self::SelectiveImport
334            | Self::NamespaceImport
335            | Self::NamespaceImportMembers
336            | Self::DeadlineSetup
337            | Self::DeadlineEnd
338            | Self::CheckType
339            | Self::CallSpread
340            | Self::MethodCallSpread
341            | Self::Yield => Portability::Deferred,
342        }
343    }
344
345    pub const fn is_executable(self) -> bool {
346        matches!(self.portability(), Portability::Executable)
347    }
348}
349
350/// Artifact format version whose golden opcode fingerprint is pinned below.
351pub const OPCODE_ABI_ARTIFACT_VERSION: u16 = 3;
352
353/// Golden BLAKE3 digest of opcode bytes, names, and operand-role tags for v3.
354///
355/// Changing the schema requires an intentional artifact-version bump and a new
356/// named fingerprint rather than silently rewriting existing bytecode.
357///
358/// v3 adds [`Op::AssertBindingType`] and its [`OperandKind::BindingTypeU16`]
359/// operand role, so that a declared type on a `let` / `const` is checked at
360/// the binding site the same way a declared parameter type is checked at a
361/// call site.
362pub const OPCODE_ABI_FINGERPRINT_V3: [u8; 32] = [
363    0x8b, 0xbc, 0xdf, 0x24, 0x31, 0x39, 0x01, 0x8e, 0xc3, 0x48, 0xc3, 0x7d, 0xab, 0x00, 0x82, 0x9c,
364    0xa9, 0x34, 0x3d, 0xb6, 0xb2, 0xfe, 0x3d, 0x22, 0x45, 0x4a, 0xfa, 0x0a, 0xe8, 0x88, 0xa6, 0x79,
365];
366
367/// Compute the fingerprint of the compiled opcode schema.
368pub fn opcode_abi_fingerprint() -> [u8; 32] {
369    let mut hasher = blake3::Hasher::new();
370    for op in Op::ALL {
371        hasher.update(&[*op as u8]);
372        hasher.update(op.name().as_bytes());
373        hasher.update(&[0]);
374        for operand in op.operands() {
375            hasher.update(&[operand.abi_tag()]);
376        }
377        hasher.update(&[0xff]);
378    }
379    *hasher.finalize().as_bytes()
380}
381
382#[cfg(test)]
383mod tests {
384    use super::{
385        opcode_abi_fingerprint, Op, OPCODE_ABI_ARTIFACT_VERSION, OPCODE_ABI_FINGERPRINT_V3,
386    };
387
388    #[test]
389    fn byte_mapping_is_explicit_dense_and_stable() {
390        for (byte, op) in Op::ALL.iter().copied().enumerate() {
391            assert_eq!(Op::from_byte(byte as u8), Some(op));
392            assert_eq!(op as usize, byte);
393        }
394        assert_eq!(Op::from_byte(Op::COUNT as u8), None);
395    }
396
397    #[test]
398    fn opcode_schema_matches_artifact_v3_golden() {
399        assert_eq!(OPCODE_ABI_ARTIFACT_VERSION, crate::ARTIFACT_VERSION);
400        assert_eq!(opcode_abi_fingerprint(), OPCODE_ABI_FINGERPRINT_V3);
401    }
402}