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