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 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 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}