Skip to main content

harn_kernel/
program.rs

1use std::collections::HashMap;
2use std::fmt;
3use std::sync::Arc;
4
5use harn_parser::TypeExpr;
6use serde::{Deserialize, Serialize};
7
8use crate::{BuiltinId, Op};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub enum Constant {
12    Int(i64),
13    Float(f64),
14    String(String),
15    Bool(bool),
16    Nil,
17    Duration(i64),
18}
19
20impl fmt::Display for Constant {
21    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            Self::Int(value) => write!(formatter, "{value}"),
24            Self::Float(value) => write!(formatter, "{value}"),
25            Self::String(value) => write!(formatter, "\"{value}\""),
26            Self::Bool(value) => write!(formatter, "{value}"),
27            Self::Nil => formatter.write_str("nil"),
28            Self::Duration(value) => write!(formatter, "{value}ms"),
29        }
30    }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct LocalSlotInfo {
35    pub name: String,
36    pub mutable: bool,
37    pub scope_depth: usize,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ParamSlot {
42    pub name: String,
43    pub type_expr: Option<TypeExpr>,
44    pub has_default: bool,
45}
46
47impl ParamSlot {
48    pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
49        Self::from_typed_param_with_type(param, param.type_expr.clone())
50    }
51
52    pub(crate) fn from_typed_param_with_type(
53        param: &harn_parser::TypedParam,
54        type_expr: Option<TypeExpr>,
55    ) -> Self {
56        Self {
57            name: param.name.clone(),
58            type_expr,
59            has_default: param.default_value.is_some(),
60        }
61    }
62
63    pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
64        params.iter().map(Self::from_typed_param).collect()
65    }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct CompiledFunction {
70    pub name: String,
71    pub type_params: Vec<String>,
72    pub nominal_type_names: Vec<String>,
73    pub params: Vec<ParamSlot>,
74    pub default_start: Option<usize>,
75    pub chunk: Arc<Chunk>,
76    pub is_generator: bool,
77    pub is_stream: bool,
78    pub has_rest_param: bool,
79    pub has_runtime_type_checks: bool,
80}
81
82impl CompiledFunction {
83    pub(crate) fn has_runtime_type_checks_for_params(params: &[ParamSlot]) -> bool {
84        params.iter().any(|param| param.type_expr.is_some())
85    }
86
87    pub fn param_names(&self) -> impl Iterator<Item = &str> {
88        self.params.iter().map(|param| param.name.as_str())
89    }
90
91    pub fn required_param_count(&self) -> usize {
92        self.default_start.unwrap_or(self.params.len())
93    }
94
95    pub fn declares_type_param(&self, name: &str) -> bool {
96        self.type_params.iter().any(|param| param == name)
97    }
98
99    pub fn has_nominal_type(&self, name: &str) -> bool {
100        self.nominal_type_names.iter().any(|ty| ty == name)
101    }
102}
103
104#[derive(Debug, Serialize, Deserialize)]
105pub struct Chunk {
106    pub code: Vec<u8>,
107    pub constants: Vec<Constant>,
108    #[serde(skip)]
109    constant_index: Option<HashMap<ConstantKey, u16>>,
110    pub lines: Vec<u32>,
111    pub columns: Vec<u32>,
112    pub source_file: Option<String>,
113    #[doc(hidden)]
114    pub current_col: u32,
115    pub functions: Vec<Arc<CompiledFunction>>,
116    #[doc(hidden)]
117    pub local_slots: Vec<LocalSlotInfo>,
118    #[doc(hidden)]
119    pub references_outer_names: bool,
120    #[cfg(debug_assertions)]
121    #[serde(skip)]
122    balance_depth: i32,
123    #[cfg(debug_assertions)]
124    #[serde(skip)]
125    balance_nonlinear: u32,
126}
127
128impl Clone for Chunk {
129    fn clone(&self) -> Self {
130        Self {
131            code: self.code.clone(),
132            constants: self.constants.clone(),
133            constant_index: self.constant_index.clone(),
134            lines: self.lines.clone(),
135            columns: self.columns.clone(),
136            source_file: self.source_file.clone(),
137            current_col: self.current_col,
138            functions: self.functions.clone(),
139            local_slots: self.local_slots.clone(),
140            references_outer_names: self.references_outer_names,
141            #[cfg(debug_assertions)]
142            balance_depth: self.balance_depth,
143            #[cfg(debug_assertions)]
144            balance_nonlinear: self.balance_nonlinear,
145        }
146    }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Hash)]
150enum ConstantKey {
151    Int(i64),
152    Float(u64),
153    String(String),
154    Bool(bool),
155    Nil,
156    Duration(i64),
157}
158
159impl From<&Constant> for ConstantKey {
160    fn from(value: &Constant) -> Self {
161        match value {
162            Constant::Int(value) => Self::Int(*value),
163            Constant::Float(value) => Self::Float(value.to_bits()),
164            Constant::String(value) => Self::String(value.clone()),
165            Constant::Bool(value) => Self::Bool(*value),
166            Constant::Nil => Self::Nil,
167            Constant::Duration(value) => Self::Duration(*value),
168        }
169    }
170}
171
172#[cfg(debug_assertions)]
173#[derive(Clone, Copy)]
174pub(crate) struct BalanceProbe {
175    depth: i32,
176    nonlinear: u32,
177}
178
179impl Default for Chunk {
180    fn default() -> Self {
181        Self::new()
182    }
183}
184
185impl Chunk {
186    pub(crate) fn from_artifact_parts(
187        code: Vec<u8>,
188        constants: Vec<Constant>,
189        lines: Vec<u32>,
190        columns: Vec<u32>,
191        source_file: Option<String>,
192        functions: Vec<Arc<CompiledFunction>>,
193        local_slots: Vec<LocalSlotInfo>,
194        references_outer_names: bool,
195    ) -> Self {
196        Self {
197            code,
198            constants,
199            constant_index: None,
200            lines,
201            columns,
202            source_file,
203            current_col: 0,
204            functions,
205            local_slots,
206            references_outer_names,
207            #[cfg(debug_assertions)]
208            balance_depth: 0,
209            #[cfg(debug_assertions)]
210            balance_nonlinear: 0,
211        }
212    }
213
214    pub fn new() -> Self {
215        Self {
216            code: Vec::new(),
217            constants: Vec::new(),
218            constant_index: Some(HashMap::new()),
219            lines: Vec::new(),
220            columns: Vec::new(),
221            source_file: None,
222            current_col: 0,
223            functions: Vec::new(),
224            local_slots: Vec::new(),
225            references_outer_names: false,
226            #[cfg(debug_assertions)]
227            balance_depth: 0,
228            #[cfg(debug_assertions)]
229            balance_nonlinear: 0,
230        }
231    }
232
233    pub fn set_column(&mut self, column: u32) {
234        self.current_col = column;
235    }
236
237    pub fn add_constant(&mut self, constant: Constant) -> u16 {
238        let index = self.constant_index.get_or_insert_with(|| {
239            self.constants
240                .iter()
241                .enumerate()
242                .filter_map(|(index, value)| {
243                    u16::try_from(index)
244                        .ok()
245                        .map(|index| (ConstantKey::from(value), index))
246                })
247                .collect()
248        });
249        let key = ConstantKey::from(&constant);
250        if let Some(existing) = index.get(&key) {
251            return *existing;
252        }
253        let slot =
254            u16::try_from(self.constants.len()).expect("constant pool exceeded u16 operand space");
255        self.constants.push(constant);
256        index.insert(key, slot);
257        slot
258    }
259
260    pub fn emit(&mut self, op: Op, line: u32) {
261        self.note_balance(op, 0);
262        self.push_bytes(&[op as u8], line);
263        if reads_outer_name(op) {
264            self.references_outer_names = true;
265        }
266    }
267
268    pub fn emit_u16(&mut self, op: Op, value: u16, line: u32) {
269        self.note_balance(op, value);
270        self.push_bytes(&[op as u8, (value >> 8) as u8, value as u8], line);
271        if reads_outer_name(op) {
272            self.references_outer_names = true;
273        }
274    }
275
276    /// Emit an instruction whose complete operand list consists of `u16`
277    /// values. The opcode schema remains the authority for arity and width.
278    pub fn emit_u16_operands(&mut self, op: Op, values: &[u16], line: u32) {
279        debug_assert_eq!(op.operands().len(), values.len());
280        debug_assert!(op.operands().iter().all(|operand| operand.width() == 2));
281        self.note_balance(op, values.first().copied().unwrap_or_default());
282        let mut bytes = Vec::with_capacity(op.instruction_len());
283        bytes.push(op as u8);
284        for value in values {
285            bytes.extend_from_slice(&value.to_be_bytes());
286        }
287        self.push_bytes(&bytes, line);
288        if reads_outer_name(op) {
289            self.references_outer_names = true;
290        }
291    }
292
293    pub fn emit_u8(&mut self, op: Op, value: u8, line: u32) {
294        self.note_balance(op, u16::from(value));
295        self.push_bytes(&[op as u8, value], line);
296        if reads_outer_name(op) {
297            self.references_outer_names = true;
298        }
299    }
300
301    pub fn emit_call_builtin(&mut self, id: BuiltinId, name: u16, argc: u8, line: u32) {
302        self.note_balance(Op::CallBuiltin, u16::from(argc));
303        let mut bytes = vec![Op::CallBuiltin as u8];
304        bytes.extend_from_slice(&id.raw().to_be_bytes());
305        bytes.extend_from_slice(&name.to_be_bytes());
306        bytes.push(argc);
307        self.push_bytes(&bytes, line);
308        self.references_outer_names = true;
309    }
310
311    pub fn emit_call_builtin_spread(&mut self, id: BuiltinId, name: u16, line: u32) {
312        let mut bytes = vec![Op::CallBuiltinSpread as u8];
313        bytes.extend_from_slice(&id.raw().to_be_bytes());
314        bytes.extend_from_slice(&name.to_be_bytes());
315        self.push_bytes(&bytes, line);
316        self.references_outer_names = true;
317    }
318
319    pub fn emit_method_call(&mut self, name: u16, argc: u8, line: u32) {
320        self.emit_method_call_inner(Op::MethodCall, name, argc, line);
321    }
322
323    pub fn emit_method_call_opt(&mut self, name: u16, argc: u8, line: u32) {
324        self.emit_method_call_inner(Op::MethodCallOpt, name, argc, line);
325    }
326
327    fn emit_method_call_inner(&mut self, op: Op, name: u16, argc: u8, line: u32) {
328        self.note_balance(op, u16::from(argc));
329        self.push_bytes(&[op as u8, (name >> 8) as u8, name as u8, argc], line);
330    }
331
332    pub fn emit_set_local_slot_property(&mut self, property: u16, slot: u16, line: u32) {
333        self.note_balance(Op::SetLocalSlotProperty, 0);
334        self.push_bytes(
335            &[
336                Op::SetLocalSlotProperty as u8,
337                (property >> 8) as u8,
338                property as u8,
339                (slot >> 8) as u8,
340                slot as u8,
341            ],
342            line,
343        );
344    }
345
346    fn push_bytes(&mut self, bytes: &[u8], line: u32) {
347        self.code.extend_from_slice(bytes);
348        self.lines.extend(std::iter::repeat_n(line, bytes.len()));
349        self.columns
350            .extend(std::iter::repeat_n(self.current_col, bytes.len()));
351    }
352
353    pub fn current_offset(&self) -> usize {
354        self.code.len()
355    }
356
357    pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
358        self.note_balance(op, 0);
359        let patch = self.code.len() + 1;
360        self.push_bytes(&[op as u8, 0xff, 0xff], line);
361        patch
362    }
363
364    pub fn patch_jump(&mut self, patch: usize) {
365        self.patch_jump_to(patch, self.code.len());
366    }
367
368    pub fn patch_jump_to(&mut self, patch: usize, target: usize) {
369        // The compiler's final addressability guard turns this provisional
370        // truncation into a structured compile error before the image escapes.
371        let target = target as u16;
372        self.code[patch..patch + 2].copy_from_slice(&target.to_be_bytes());
373    }
374
375    pub fn read_u16(&self, position: usize) -> u16 {
376        u16::from_be_bytes([self.code[position], self.code[position + 1]])
377    }
378
379    pub(crate) fn add_local_slot(
380        &mut self,
381        name: String,
382        mutable: bool,
383        scope_depth: usize,
384    ) -> u16 {
385        let slot = u16::try_from(self.local_slots.len()).expect("local slot count exceeded u16");
386        self.local_slots.push(LocalSlotInfo {
387            name,
388            mutable,
389            scope_depth,
390        });
391        slot
392    }
393
394    #[cfg(debug_assertions)]
395    pub(crate) fn balance_probe(&self) -> BalanceProbe {
396        BalanceProbe {
397            depth: self.balance_depth,
398            nonlinear: self.balance_nonlinear,
399        }
400    }
401
402    #[cfg(debug_assertions)]
403    pub(crate) fn balance_delta_since(&self, probe: BalanceProbe) -> Option<i32> {
404        (self.balance_nonlinear == probe.nonlinear).then_some(self.balance_depth - probe.depth)
405    }
406
407    #[cfg(debug_assertions)]
408    fn note_balance(&mut self, op: Op, count: u16) {
409        match stack_delta(op, count) {
410            Some(delta) => self.balance_depth += delta,
411            None => self.balance_nonlinear += 1,
412        }
413    }
414
415    #[cfg(not(debug_assertions))]
416    fn note_balance(&mut self, _op: Op, _count: u16) {}
417
418    pub fn disassemble(&self, name: &str) -> String {
419        let mut output = format!("== {name} ==\n");
420        let mut ip = 0;
421        while let Some(byte) = self.code.get(ip).copied() {
422            let offset = ip;
423            let line = self.lines.get(ip).copied().unwrap_or(0);
424            let Some(op) = Op::from_byte(byte) else { break };
425            ip += 1;
426            let rendered = self.disassemble_instruction(op, &mut ip);
427            debug_assert_eq!(
428                ip,
429                offset + op.instruction_len(),
430                "disassembler operand width drifted for {}",
431                op.name(),
432            );
433            output.push_str(&format!("{offset:04} [{line:>4}] {rendered}\n"));
434        }
435        output
436    }
437
438    fn disassemble_instruction(&self, op: Op, ip: &mut usize) -> String {
439        let label = opcode_label(op.name());
440        let read_u16 = |position: usize| {
441            self.code
442                .get(position..position + 2)
443                .map(|bytes| u16::from_be_bytes([bytes[0], bytes[1]]))
444        };
445        if matches!(
446            op,
447            Op::Constant
448                | Op::GetVar
449                | Op::DefLet
450                | Op::DefVar
451                | Op::DefCell
452                | Op::SetVar
453                | Op::GetProperty
454                | Op::GetPropertyOpt
455                | Op::Import
456        ) {
457            let Some(index) = read_u16(*ip) else {
458                return label;
459            };
460            *ip += 2;
461            return match self.constants.get(index as usize) {
462                Some(value) => format!("{label} {index:>4} ({value})"),
463                None => format!("{label} {index:>4}"),
464            };
465        }
466        if op == Op::SetProperty {
467            let Some(property) = read_u16(*ip) else {
468                return label;
469            };
470            let Some(binding) = read_u16(*ip + 2) else {
471                return label;
472            };
473            *ip += 4;
474            let render = |index: u16| {
475                self.constants
476                    .get(index as usize)
477                    .map(ToString::to_string)
478                    .unwrap_or_default()
479            };
480            return format!(
481                "{label} property {property:>4} ({}) binding {binding:>4} ({})",
482                render(property),
483                render(binding)
484            );
485        }
486        if matches!(
487            op,
488            Op::GetLocalSlot
489                | Op::DefLocalSlot
490                | Op::SetLocalSlot
491                | Op::SetLocalSlotSubscript
492                | Op::ConcatAssignLocal
493        ) {
494            let Some(slot) = read_u16(*ip) else {
495                return label;
496            };
497            *ip += 2;
498            return match self.local_slots.get(slot as usize) {
499                Some(info) => format!("{label} {slot:>4} ({})", info.name),
500                None => format!("{label} {slot:>4}"),
501            };
502        }
503        if op == Op::SetLocalSlotProperty {
504            let Some(property) = read_u16(*ip) else {
505                return label;
506            };
507            let Some(slot) = read_u16(*ip + 2) else {
508                return label;
509            };
510            *ip += 4;
511            let property = self
512                .constants
513                .get(property as usize)
514                .map(ToString::to_string)
515                .unwrap_or_default();
516            return format!("{label} {slot:>4} {property}");
517        }
518        if matches!(op, Op::MethodCall | Op::MethodCallOpt) {
519            let Some(name) = read_u16(*ip) else {
520                return label;
521            };
522            let Some(argc) = self.code.get(*ip + 2).copied() else {
523                return label;
524            };
525            *ip += 3;
526            let name = self
527                .constants
528                .get(name as usize)
529                .map(ToString::to_string)
530                .unwrap_or_default();
531            return format!("{label} {argc:>4} ({name})");
532        }
533        if matches!(op, Op::Call | Op::TailCall) {
534            let Some(argc) = self.code.get(*ip).copied() else {
535                return label;
536            };
537            *ip += 1;
538            return format!("{label} {argc:>4}");
539        }
540        let width = instruction_len(op, &self.code[(*ip).saturating_sub(1)..]).unwrap_or(1);
541        if width == 3 {
542            let Some(value) = read_u16(*ip) else {
543                return label;
544            };
545            *ip += 2;
546            return format!("{label} {value:>4}");
547        }
548        if width > 1 {
549            *ip = (*ip).saturating_add(width - 1).min(self.code.len());
550        }
551        label
552    }
553}
554
555fn opcode_label(name: &str) -> String {
556    let mut output = String::new();
557    for (index, ch) in name.chars().enumerate() {
558        if index > 0 && ch.is_ascii_uppercase() {
559            output.push('_');
560        }
561        output.push(ch.to_ascii_uppercase());
562    }
563    output
564}
565
566fn reads_outer_name(op: Op) -> bool {
567    matches!(
568        op,
569        Op::GetVar
570            | Op::SetVar
571            | Op::Call
572            | Op::TailCall
573            | Op::Pipe
574            | Op::CheckType
575            | Op::CallSpread
576            | Op::CallBuiltin
577            | Op::CallBuiltinSpread
578    )
579}
580
581pub fn instruction_len(op: Op, _remaining: &[u8]) -> Option<usize> {
582    Some(op.instruction_len())
583}
584
585#[cfg(debug_assertions)]
586fn stack_delta(op: Op, count: u16) -> Option<i32> {
587    use Op::*;
588    let count = i32::from(count);
589    Some(match op {
590        Constant | Nil | True | False | RootHarness | GetVar | GetArgc | GetLocalSlot | Closure
591        | Dup => 1,
592        DefLet | DefVar | DefCell | SetVar | DefLocalSlot | SetLocalSlot | SetProperty
593        | SetLocalSlotProperty | ConcatAssignLocal | Pop => -1,
594        Negate | Not | GetProperty | GetPropertyOpt | CheckType | TryUnwrap | TryWrapOk | Swap
595        | PushScope | PopScope | PopIterator | PopHandler => 0,
596        Add | Sub | Mul | Div | Mod | Pow | AddInt | SubInt | MulInt | DivInt | ModInt
597        | AddFloat | SubFloat | MulFloat | DivFloat | ModFloat | Equal | NotEqual | Less
598        | Greater | LessEqual | GreaterEqual | EqualInt | NotEqualInt | LessInt | GreaterInt
599        | LessEqualInt | GreaterEqualInt | EqualFloat | NotEqualFloat | LessFloat
600        | GreaterFloat | LessEqualFloat | GreaterEqualFloat | EqualBool | NotEqualBool
601        | EqualString | NotEqualString | Contains | Subscript | SubscriptOpt => -1,
602        IterInit => -1,
603        Slice | SetSubscript | SetLocalSlotSubscript => -2,
604        BuildList | Concat | CallBuiltin => 1 - count,
605        BuildDict => 1 - 2 * count,
606        Call | MethodCall | MethodCallOpt => -count,
607        Jump
608        | JumpIfFalse
609        | JumpIfTrue
610        | IterNext
611        | Return
612        | TailCall
613        | Throw
614        | TryCatchSetup
615        | Spawn
616        | Pipe
617        | Parallel
618        | ParallelMap
619        | ParallelMapStream
620        | ParallelSettle
621        | SyncMutexEnter
622        | SyncMutexEnterKeyed
623        | TaskScopeEnter
624        | TaskScopeExit
625        | Import
626        | SelectiveImport
627        | NamespaceImport
628        | NamespaceImportMembers
629        | DeadlineSetup
630        | DeadlineEnd
631        | BuildEnum
632        | MatchEnum
633        | Yield
634        | CallSpread
635        | CallBuiltinSpread
636        | MethodCallSpread => return None,
637    })
638}