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    PortableV1,
26    UnsupportedV1,
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],
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}
211
212impl Op {
213    /// Whether Portable Kernel v1 has an explicit execution arm for this
214    /// opcode. Artifact validation uses this closed classification so an opcode
215    /// addition cannot become browser-executable by omission.
216    pub const fn portability(self) -> Portability {
217        match self {
218            Self::Constant
219            | Self::Nil
220            | Self::True
221            | Self::False
222            | Self::RootHarness
223            | Self::GetVar
224            | Self::DefLet
225            | Self::DefVar
226            | Self::DefCell
227            | Self::SetVar
228            | Self::PushScope
229            | Self::PopScope
230            | Self::Add
231            | Self::Sub
232            | Self::Mul
233            | Self::Div
234            | Self::Mod
235            | Self::Pow
236            | Self::Negate
237            | Self::Equal
238            | Self::NotEqual
239            | Self::Less
240            | Self::Greater
241            | Self::LessEqual
242            | Self::GreaterEqual
243            | Self::Not
244            | Self::Jump
245            | Self::JumpIfFalse
246            | Self::JumpIfTrue
247            | Self::Pop
248            | Self::Call
249            | Self::TailCall
250            | Self::Return
251            | Self::Closure
252            | Self::BuildList
253            | Self::BuildDict
254            | Self::Subscript
255            | Self::SubscriptOpt
256            | Self::Slice
257            | Self::GetProperty
258            | Self::GetPropertyOpt
259            | Self::MethodCall
260            | Self::MethodCallOpt
261            | Self::Concat
262            | Self::Throw
263            | Self::TryCatchSetup
264            | Self::PopHandler
265            | Self::GetArgc
266            | Self::CallBuiltin
267            | Self::CallBuiltinSpread
268            | Self::Dup
269            | Self::Swap
270            | Self::Contains
271            | Self::AddInt
272            | Self::SubInt
273            | Self::MulInt
274            | Self::DivInt
275            | Self::ModInt
276            | Self::AddFloat
277            | Self::SubFloat
278            | Self::MulFloat
279            | Self::DivFloat
280            | Self::ModFloat
281            | Self::EqualInt
282            | Self::NotEqualInt
283            | Self::LessInt
284            | Self::GreaterInt
285            | Self::LessEqualInt
286            | Self::GreaterEqualInt
287            | Self::EqualFloat
288            | Self::NotEqualFloat
289            | Self::LessFloat
290            | Self::GreaterFloat
291            | Self::LessEqualFloat
292            | Self::GreaterEqualFloat
293            | Self::EqualBool
294            | Self::NotEqualBool
295            | Self::EqualString
296            | Self::NotEqualString
297            | Self::GetLocalSlot
298            | Self::DefLocalSlot
299            | Self::SetLocalSlot
300            | Self::ConcatAssignLocal => Portability::PortableV1,
301
302            Self::SetProperty
303            | Self::SetSubscript
304            | Self::SetLocalSlotProperty
305            | Self::SetLocalSlotSubscript
306            | Self::IterInit
307            | Self::IterNext
308            | Self::Pipe
309            | Self::Parallel
310            | Self::ParallelMap
311            | Self::ParallelMapStream
312            | Self::ParallelSettle
313            | Self::Spawn
314            | Self::SyncMutexEnter
315            | Self::SyncMutexEnterKeyed
316            | Self::TaskScopeEnter
317            | Self::TaskScopeExit
318            | Self::Import
319            | Self::SelectiveImport
320            | Self::NamespaceImport
321            | Self::DeadlineSetup
322            | Self::DeadlineEnd
323            | Self::BuildEnum
324            | Self::MatchEnum
325            | Self::PopIterator
326            | Self::CheckType
327            | Self::TryUnwrap
328            | Self::TryWrapOk
329            | Self::CallSpread
330            | Self::MethodCallSpread
331            | Self::Yield => Portability::UnsupportedV1,
332        }
333    }
334
335    pub const fn is_portable_v1(self) -> bool {
336        matches!(self.portability(), Portability::PortableV1)
337    }
338}
339
340/// Artifact format version whose golden opcode fingerprint is pinned below.
341pub const OPCODE_ABI_ARTIFACT_VERSION: u16 = 1;
342
343/// Golden BLAKE3 digest of opcode bytes, names, and operand-role tags for v1.
344///
345/// Changing the schema requires an intentional artifact-version bump and a new
346/// named fingerprint rather than silently rewriting existing bytecode.
347pub const OPCODE_ABI_FINGERPRINT_V1: [u8; 32] = [
348    0x76, 0x1f, 0x93, 0x67, 0xa5, 0x69, 0xd4, 0x18, 0x8b, 0x00, 0xf7, 0xaf, 0x36, 0xe5, 0x51, 0x20,
349    0xcb, 0xb6, 0xed, 0x92, 0xf3, 0x47, 0x95, 0xa0, 0x59, 0x3d, 0x02, 0x9e, 0x57, 0xbe, 0x20, 0x0e,
350];
351
352/// Compute the fingerprint of the compiled opcode schema.
353pub fn opcode_abi_fingerprint() -> [u8; 32] {
354    let mut hasher = blake3::Hasher::new();
355    for op in Op::ALL {
356        hasher.update(&[*op as u8]);
357        hasher.update(op.name().as_bytes());
358        hasher.update(&[0]);
359        for operand in op.operands() {
360            hasher.update(&[operand.abi_tag()]);
361        }
362        hasher.update(&[0xff]);
363    }
364    *hasher.finalize().as_bytes()
365}
366
367#[cfg(test)]
368mod tests {
369    use super::{
370        opcode_abi_fingerprint, Op, OPCODE_ABI_ARTIFACT_VERSION, OPCODE_ABI_FINGERPRINT_V1,
371    };
372
373    #[test]
374    fn byte_mapping_is_explicit_dense_and_stable() {
375        for (byte, op) in Op::ALL.iter().copied().enumerate() {
376            assert_eq!(Op::from_byte(byte as u8), Some(op));
377            assert_eq!(op as usize, byte);
378        }
379        assert_eq!(Op::from_byte(Op::COUNT as u8), None);
380    }
381
382    #[test]
383    fn opcode_schema_matches_artifact_v1_golden() {
384        assert_eq!(OPCODE_ABI_ARTIFACT_VERSION, crate::ARTIFACT_VERSION);
385        assert_eq!(opcode_abi_fingerprint(), OPCODE_ABI_FINGERPRINT_V1);
386    }
387}