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    ThrowDeclared = 115 => [],
219    TryCatchPreserve = 116 => [JumpU16, StringConstantU16],
220    Rethrow = 117 => [],
221}
222
223impl Op {
224    /// Whether the portable kernel has an explicit execution arm for this
225    /// opcode. Artifact validation uses this closed classification so an opcode
226    /// addition cannot become browser-executable by omission.
227    pub const fn portability(self) -> Portability {
228        match self {
229            Self::Constant
230            | Self::Nil
231            | Self::True
232            | Self::False
233            | Self::RootHarness
234            | Self::GetVar
235            | Self::DefLet
236            | Self::DefVar
237            | Self::DefCell
238            | Self::SetVar
239            | Self::PushScope
240            | Self::PopScope
241            | Self::Add
242            | Self::Sub
243            | Self::Mul
244            | Self::Div
245            | Self::Mod
246            | Self::Pow
247            | Self::Negate
248            | Self::Equal
249            | Self::NotEqual
250            | Self::Less
251            | Self::Greater
252            | Self::LessEqual
253            | Self::GreaterEqual
254            | Self::Not
255            | Self::Jump
256            | Self::JumpIfFalse
257            | Self::JumpIfTrue
258            | Self::Pop
259            | Self::Call
260            | Self::TailCall
261            | Self::Return
262            | Self::Closure
263            | Self::BuildList
264            | Self::BuildDict
265            | Self::Subscript
266            | Self::SubscriptOpt
267            | Self::Slice
268            | Self::GetProperty
269            | Self::GetPropertyOpt
270            | Self::SetProperty
271            | Self::SetSubscript
272            | Self::SetLocalSlotProperty
273            | Self::SetLocalSlotSubscript
274            | Self::MethodCall
275            | Self::MethodCallOpt
276            | Self::Concat
277            | Self::Throw
278            | Self::ThrowDeclared
279            | Self::Rethrow
280            | Self::TryCatchSetup
281            | Self::TryCatchPreserve
282            | Self::PopHandler
283            | Self::IterInit
284            | Self::IterNext
285            | Self::PopIterator
286            | Self::GetArgc
287            | Self::CallBuiltin
288            | Self::CallBuiltinSpread
289            | Self::Dup
290            | Self::Swap
291            | Self::Contains
292            | Self::AddInt
293            | Self::SubInt
294            | Self::MulInt
295            | Self::DivInt
296            | Self::ModInt
297            | Self::AddFloat
298            | Self::SubFloat
299            | Self::MulFloat
300            | Self::DivFloat
301            | Self::ModFloat
302            | Self::EqualInt
303            | Self::NotEqualInt
304            | Self::LessInt
305            | Self::GreaterInt
306            | Self::LessEqualInt
307            | Self::GreaterEqualInt
308            | Self::EqualFloat
309            | Self::NotEqualFloat
310            | Self::LessFloat
311            | Self::GreaterFloat
312            | Self::LessEqualFloat
313            | Self::GreaterEqualFloat
314            | Self::EqualBool
315            | Self::NotEqualBool
316            | Self::EqualString
317            | Self::NotEqualString
318            | Self::GetLocalSlot
319            | Self::DefLocalSlot
320            | Self::SetLocalSlot
321            | Self::ConcatAssignLocal
322            | Self::BuildEnum
323            | Self::MatchEnum
324            | Self::TryUnwrap
325            | Self::TryWrapOk
326            | Self::AssertBindingType => Portability::Executable,
327
328            Self::Pipe
329            | Self::Parallel
330            | Self::ParallelMap
331            | Self::ParallelMapStream
332            | Self::ParallelSettle
333            | Self::Spawn
334            | Self::SyncMutexEnter
335            | Self::SyncMutexEnterKeyed
336            | Self::TaskScopeEnter
337            | Self::TaskScopeExit
338            | Self::Import
339            | Self::SelectiveImport
340            | Self::NamespaceImport
341            | Self::NamespaceImportMembers
342            | Self::DeadlineSetup
343            | Self::DeadlineEnd
344            | Self::CheckType
345            | Self::CallSpread
346            | Self::MethodCallSpread
347            | Self::Yield => Portability::Deferred,
348        }
349    }
350
351    pub const fn is_executable(self) -> bool {
352        matches!(self.portability(), Portability::Executable)
353    }
354}
355
356/// Artifact format version whose golden opcode fingerprint is pinned below.
357pub const OPCODE_ABI_ARTIFACT_VERSION: u16 = 5;
358
359/// Golden BLAKE3 digest of opcode bytes, names, and operand-role tags for v4.
360///
361/// Changing the schema requires an intentional artifact-version bump and a new
362/// named fingerprint rather than silently rewriting existing bytecode.
363///
364/// v4 keeps the opcode schema of v3 (including [`Op::AssertBindingType`]) and
365/// bumps the artifact / semantic ABI so construction of a declared struct
366/// asserts each annotated field against the value that lands in it (harn#6268).
367/// The digest matches v3 because no opcode or operand role changed.
368pub const OPCODE_ABI_FINGERPRINT_V4: [u8; 32] = [
369    0x8b, 0xbc, 0xdf, 0x24, 0x31, 0x39, 0x01, 0x8e, 0xc3, 0x48, 0xc3, 0x7d, 0xab, 0x00, 0x82, 0x9c,
370    0xa9, 0x34, 0x3d, 0xb6, 0xb2, 0xfe, 0x3d, 0x22, 0x45, 0x4a, 0xfa, 0x0a, 0xe8, 0x88, 0xa6, 0x79,
371];
372
373/// Opcode fingerprint for declared throws and provenance-preserving rethrows.
374pub const OPCODE_ABI_FINGERPRINT_V5: [u8; 32] = [
375    0x1d, 0xda, 0xef, 0x46, 0x83, 0x8b, 0x2c, 0x6a, 0xdf, 0xdf, 0x07, 0x24, 0xd2, 0x91, 0x47, 0xe1,
376    0xfc, 0xbe, 0x46, 0xc2, 0xae, 0x97, 0xcf, 0x99, 0xf2, 0xc9, 0xb7, 0x22, 0xf8, 0x83, 0xaa, 0xc7,
377];
378
379/// Compute the fingerprint of the compiled opcode schema.
380pub fn opcode_abi_fingerprint() -> [u8; 32] {
381    let mut hasher = blake3::Hasher::new();
382    for op in Op::ALL {
383        hasher.update(&[*op as u8]);
384        hasher.update(op.name().as_bytes());
385        hasher.update(&[0]);
386        for operand in op.operands() {
387            hasher.update(&[operand.abi_tag()]);
388        }
389        hasher.update(&[0xff]);
390    }
391    *hasher.finalize().as_bytes()
392}
393
394#[cfg(test)]
395mod tests {
396    use super::{
397        opcode_abi_fingerprint, Op, OPCODE_ABI_ARTIFACT_VERSION, OPCODE_ABI_FINGERPRINT_V5,
398    };
399
400    #[test]
401    fn byte_mapping_is_explicit_dense_and_stable() {
402        for (byte, op) in Op::ALL.iter().copied().enumerate() {
403            assert_eq!(Op::from_byte(byte as u8), Some(op));
404            assert_eq!(op as usize, byte);
405        }
406        assert_eq!(Op::from_byte(Op::COUNT as u8), None);
407    }
408
409    #[test]
410    fn opcode_schema_matches_artifact_v5_golden() {
411        assert_eq!(OPCODE_ABI_ARTIFACT_VERSION, crate::ARTIFACT_VERSION);
412        assert_eq!(opcode_abi_fingerprint(), OPCODE_ABI_FINGERPRINT_V5);
413    }
414}