mica_language/
bytecode.rs

1//! The bytecode representation of Mica.
2
3use std::collections::{HashMap, HashSet};
4use std::fmt::Debug;
5use std::mem::size_of;
6use std::rc::Rc;
7
8use crate::codegen::TraitBuilder;
9use crate::common::{ErrorKind, Location, RenderedSignature};
10use crate::gc::{Gc, GcRaw, Memory};
11use crate::value::{Closure, RawValue};
12
13/// A 24-bit integer encoding an instruction operand.
14#[derive(Clone, Copy, PartialEq, Eq, Hash)]
15pub struct Opr24 {
16   bytes: [u8; 3],
17}
18
19/// A u32 is too big to fit in an `Opr24`.
20#[derive(Debug)]
21pub struct U32TooBig(());
22
23impl Opr24 {
24   pub const MAX: u32 = (1 << 24);
25
26   /// Tries to construct a new `Opr24`.
27   pub fn new(x: u32) -> Result<Self, U32TooBig> {
28      if x < Self::MAX {
29         Ok(Self {
30            bytes: [
31               (x & 0xFF) as u8,
32               ((x >> 8) & 0xFF) as u8,
33               ((x >> 16) & 0xFF) as u8,
34            ],
35         })
36      } else {
37         Err(U32TooBig(()))
38      }
39   }
40
41   /// Packs `T` into an `Opr24`.
42   pub fn pack<T>(x: T) -> Self
43   where
44      T: PackableToOpr24,
45   {
46      x.pack_to_opr24()
47   }
48
49   /// Unpacks an `Opr24` into `T`.
50   pub fn unpack<T>(self) -> T
51   where
52      T: PackableToOpr24,
53   {
54      T::unpack_from_opr24(self)
55   }
56}
57
58impl From<u8> for Opr24 {
59   fn from(value: u8) -> Self {
60      Self {
61         bytes: [value, 0, 0],
62      }
63   }
64}
65
66impl TryFrom<usize> for Opr24 {
67   type Error = U32TooBig;
68
69   fn try_from(value: usize) -> Result<Self, Self::Error> {
70      Self::new(u32::try_from(value).map_err(|_| U32TooBig(()))?)
71   }
72}
73
74impl From<Opr24> for u32 {
75   fn from(opr: Opr24) -> u32 {
76      (opr.bytes[0] as u32) | ((opr.bytes[1] as u32) << 8) | ((opr.bytes[2] as u32) << 16)
77   }
78}
79
80impl From<Opr24> for usize {
81   fn from(opr: Opr24) -> usize {
82      (opr.bytes[0] as usize) | ((opr.bytes[1] as usize) << 8) | ((opr.bytes[2] as usize) << 16)
83   }
84}
85
86impl std::fmt::Debug for Opr24 {
87   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88      write!(f, "{:x}", u32::from(*self))
89   }
90}
91
92impl std::fmt::Display for Opr24 {
93   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94      write!(f, "{:?}", self)
95   }
96}
97
98pub trait PackableToOpr24 {
99   fn pack_to_opr24(self) -> Opr24;
100   fn unpack_from_opr24(opr: Opr24) -> Self;
101}
102
103impl PackableToOpr24 for (u16, u8) {
104   fn pack_to_opr24(self) -> Opr24 {
105      // SAFETY: This packs the two numbers into 24 bits and will never exceed the range of an
106      // Opr24.
107      unsafe { Opr24::new((self.0 as u32) << 8 | self.1 as u32).unwrap_unchecked() }
108   }
109
110   fn unpack_from_opr24(opr: Opr24) -> Self {
111      let x = u32::from(opr);
112      let big = ((x & 0xFFFF00) >> 8) as u16;
113      let small = (x & 0x0000FF) as u8;
114      (big, small)
115   }
116}
117
118/// A VM opcode.
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120#[repr(u8)]
121pub enum Opcode {
122   /// Doesn't do anything. Used as a default zero value if something goes wrong.
123   /// Also used for backpatching purposes.
124   #[allow(unused)]
125   Nop,
126
127   /// Pushes `nil` onto the stack.
128   PushNil,
129   /// Pushes `true` onto the stack.
130   PushTrue,
131   /// Pushes `false` onto the stack.
132   PushFalse,
133   /// Pushes a number onto the stack. Must be followed by an f64.
134   PushNumber,
135   /// Pushes a string onto the stack. Must be followed by a string.
136   PushString,
137   /// Creates a closure from the function with the given ID and pushes it onto the stack.
138   CreateClosure,
139   /// Creates a unique type that can be later implemented. Must be followed by a string indicating
140   /// the type's name.
141   CreateType,
142   /// Creates a struct instance from the type at the top of the stack, with the specified amount
143   /// of fields.
144   CreateStruct,
145   /// Creates an instance of the trait with the given ID and pushes it onto the stack.
146   CreateTrait,
147   /// Creates a list from `operand` values that are at the top of the stack.
148   CreateList,
149   /// Creates a dict from `operand * 2` values that are at the top of the stack. The values have
150   /// to be arranged in `key, value, key, value...` order, from bottom to top.
151   CreateDict,
152
153   /// Assigns the value at the top of the stack to a global. The value stays on the stack.
154   AssignGlobal,
155   /// Sinks the value at the top of the stack to a global. The value is consumed.
156   SinkGlobal,
157   /// Loads a value from a global.
158   GetGlobal,
159   /// Assigns the value at the top of the stack to a local. The value stays on the stack.
160   AssignLocal,
161   /// Sinks the value at the top of the stack to a local. The value is consumed.
162   SinkLocal,
163   /// Loads a value from a local.
164   GetLocal,
165   /// Assigns the value at the top of the stack to an upvalue. The value stays on the stack.
166   AssignUpvalue,
167   /// Sinks the value at the top of the stack to an upvalue. The value is consumed.
168   SinkUpvalue,
169   /// Loads a value from an upvalue.
170   GetUpvalue,
171   /// Closes a local in its upvalue.
172   CloseLocal,
173   /// Assigns to a field in the struct on the top of the stack. The struct is consumed but the
174   /// value remains on the stack.
175   /// Assumes the second value from top is a struct and not something else.
176   AssignField,
177   /// Sinks to a field in the struct on the top of the stack. Both the struct and the value are
178   /// consumed.
179   SinkField,
180   /// Loads a field from the struct on the top of the stack.
181   /// Assumes the value on top is a struct and not something else.
182   GetField,
183
184   /// Swaps the two values at the top of the stack.
185   Swap,
186   /// Removes the value at the top of the stack.
187   Discard,
188
189   // Note that due to how the VM increments the program counter, forward jump instructions as if
190   // 4 bytes were jumped over implicitly (the actual number of bytes that is jumped over is
191   // `operand + 4`).
192   /// Jumps the program counter forward by an amount of bytes.
193   JumpForward,
194   /// Jumps the program counter forward by an amount of bytes if the value at the top of the stack
195   /// is falsy.
196   JumpForwardIfFalsy,
197   /// Jumps the program counter forward by an amount of bytes if the value at the top of the stack
198   /// is truthy.
199   JumpForwardIfTruthy,
200   /// Jumps the program counter backward by an amount of bytes.
201   /// Due to how the VM increments the program counter, the actual amount is `operand - 4`.
202   JumpBackward,
203   /// Enters a breakable block by pushing the break sentinel value onto the stack.
204   EnterBreakableBlock,
205   /// Exits the n-th breakable block (counted from innermost) by popping values off the stack
206   /// until `.0` sentinels are removed.
207   ExitBreakableBlock,
208
209   /// Calls a function with `.0` arguments.
210   Call,
211   /// Calls the `n`th method with `a` arguments, where `a` is encoded in the lower 8 bits, and
212   /// `n` is encoded in the upper 16 bits of `.0`.
213   CallMethod,
214   /// Returns to the calling function.
215   Return,
216
217   /// Implements a struct according to a prototype identified by the operand.
218   Implement,
219
220   /// Negates a number (prefix `-`).
221   Negate,
222   /// Adds two numbers together (infix `+`).
223   Add,
224   /// Subtracts a number from another number (infix `-`).
225   Subtract,
226   /// Multiplies two numbers together (infix `*`).
227   Multiply,
228   /// Divides a number by another number (infix `/`).
229   Divide,
230
231   /// Flips a boolean-like value (truthy values become `false` and falsy values become `true`).
232   Not,
233   /// Compares two values for equality.
234   Equal,
235   /// Compares two values for less-than relation.
236   Less,
237   /// Compares two values for less-than-or-equal relation.
238   LessEqual,
239
240   /// Halts the interpreter loop.
241   Halt,
242}
243
244/// A jump was constructed whose offset stretched too far.
245#[derive(Debug)]
246pub struct JumpTooFar(());
247
248impl Opcode {
249   /// The size of an instruction (1 byte opcode + 3 bytes operand).
250   pub const INSTRUCTION_SIZE: usize = 4;
251
252   /// Returns the offset of a forward jump instruction.
253   fn forward_jump_offset(from: usize, to: usize) -> Result<Opr24, JumpTooFar> {
254      assert!(to >= from);
255      let offset = to - from - Self::INSTRUCTION_SIZE;
256      if u32::try_from(offset).is_err() {
257         return Err(JumpTooFar(()));
258      }
259      Opr24::new(offset as u32).map_err(|_| JumpTooFar(()))
260   }
261
262   /// Constructs a `JumpForward` instruction.
263   pub fn jump_forward(from: usize, to: usize) -> Result<(Self, Opr24), JumpTooFar> {
264      let offset = Self::forward_jump_offset(from, to)?;
265      Ok((Self::JumpForward, offset))
266   }
267
268   /// Constructs a `JumpForwardIfFalsy` instruction.
269   pub fn jump_forward_if_falsy(from: usize, to: usize) -> Result<(Self, Opr24), JumpTooFar> {
270      let offset = Self::forward_jump_offset(from, to)?;
271      Ok((Self::JumpForwardIfFalsy, offset))
272   }
273
274   /// Constructs a `JumpForwardIfTruthy` instruction.
275   pub fn jump_forward_if_truthy(from: usize, to: usize) -> Result<(Self, Opr24), JumpTooFar> {
276      let offset = Self::forward_jump_offset(from, to)?;
277      Ok((Self::JumpForwardIfTruthy, offset))
278   }
279
280   /// Returns the offset of a backward jump instruction.
281   fn backward_jump_offset(from: usize, to: usize) -> Result<Opr24, JumpTooFar> {
282      assert!(to <= from);
283      let offset = from - to + Self::INSTRUCTION_SIZE;
284      if u32::try_from(offset).is_err() {
285         return Err(JumpTooFar(()));
286      }
287      Opr24::new(offset as u32).map_err(|_| JumpTooFar(()))
288   }
289
290   /// Constructs a `JumpBackward` instruction.
291   pub fn jump_backward(from: usize, to: usize) -> Result<(Self, Opr24), JumpTooFar> {
292      let offset = Self::backward_jump_offset(from, to)?;
293      Ok((Self::JumpBackward, offset))
294   }
295}
296
297/// A chunk of bytecode.
298pub struct Chunk {
299   /// The name of the module where the chunk is located.
300   pub module_name: Rc<str>,
301   /// The actual bytecode.
302   bytes: Vec<u8>,
303   /// Locations. These are placed at multiples of four bytes (Opcode::INSTRUCTION_SIZE).
304   locations: Vec<Location>,
305   /// The location emitted for each quad-byte on calls to `push`.
306   pub codegen_location: Location,
307   /// How many stack slots to preallocate with `nil` values for variable lookups.
308   pub preallocate_stack_slots: u32,
309}
310
311impl Chunk {
312   /// Constructs an empty chunk.
313   pub fn new(module_name: Rc<str>) -> Self {
314      Self {
315         module_name,
316         bytes: Vec::new(),
317         locations: Vec::new(),
318         codegen_location: Location::UNINIT,
319         preallocate_stack_slots: 0,
320      }
321   }
322
323   /// Pushes an encodable piece of data into the chunk. Returns where it's located.
324   pub fn emit(&mut self, what: impl EncodeInstruction) -> usize {
325      let position = self.bytes.len();
326      let mut bytes = [0; 4];
327      what.encode_instruction(&mut bytes);
328      self.bytes.extend_from_slice(&bytes);
329      let end = self.bytes.len();
330      let emitted = end - position;
331      for _ in 0..emitted / 4 {
332         self.locations.push(self.codegen_location);
333      }
334      position
335   }
336
337   /// Pushes a number into the chunk.
338   pub fn emit_number(&mut self, number: f64) {
339      let bytes = number.to_le_bytes();
340      self.bytes.extend_from_slice(&bytes);
341      // 8 bytes, so push twice.
342      self.locations.push(self.codegen_location);
343      self.locations.push(self.codegen_location);
344   }
345
346   /// Pushes a string into the chunk.
347   ///
348   /// The string is padded with zeroes such that opcodes are aligned to four bytes.
349   pub fn emit_string(&mut self, string: &str) {
350      let start = self.len();
351
352      // I don't know of any 128-bit targets so this cast should be OK. Also, it isn't physically
353      // possible to store a string as large as 2^64 bytes.
354      let len = string.len() as u64;
355      let len_bytes: [u8; 8] = len.to_le_bytes();
356      self.bytes.extend_from_slice(&len_bytes);
357      self.bytes.extend(string.as_bytes());
358      let padded_len = (string.len() + 3) & !3;
359      let padding = padded_len - string.len();
360      for _ in 0..padding {
361         self.bytes.push(0);
362      }
363
364      let end = self.len();
365      for _ in (0..end - start).step_by(4) {
366         self.locations.push(self.codegen_location);
367      }
368   }
369
370   /// Patches the instruction at the given position.
371   pub fn patch(&mut self, position: usize, instruction: impl EncodeInstruction) {
372      let mut bytes = [0; 4];
373      instruction.encode_instruction(&mut bytes);
374      self.bytes[position..position + Opcode::INSTRUCTION_SIZE].copy_from_slice(&bytes);
375   }
376
377   /// Reads an instruction.
378   ///
379   /// # Safety
380   /// Assumes that `pc` is within the chunk's bounds and that the opcode at `pc` is valid.
381   pub unsafe fn read_instruction(&self, pc: &mut usize) -> (Opcode, Opr24) {
382      let bytes = &self.bytes[*pc..*pc + Opcode::INSTRUCTION_SIZE];
383      let mut bytes = <[u8; Opcode::INSTRUCTION_SIZE]>::try_from(bytes).unwrap();
384      let opcode: Opcode = std::mem::transmute(bytes[0]);
385      bytes[0] = 0;
386      let operand = Opr24 {
387         bytes: [bytes[1], bytes[2], bytes[3]],
388      };
389      *pc += Opcode::INSTRUCTION_SIZE;
390      (opcode, operand)
391   }
392
393   /// Reads a number.
394   ///
395   /// # Safety
396   /// Assumes that `pc` is within the chunk's bounds, skipping any checks.
397   pub unsafe fn read_number(&self, pc: &mut usize) -> f64 {
398      const SIZE: usize = std::mem::size_of::<f64>();
399      let bytes = &self.bytes[*pc..*pc + SIZE];
400      let bytes: [u8; SIZE] = bytes.try_into().unwrap();
401      let number = f64::from_le_bytes(bytes);
402      *pc += SIZE;
403      number
404   }
405
406   /// Reads a string.
407   ///
408   /// # Safety
409   /// This assumes the original string was encoded as proper UTF-8 (which it should
410   /// have been considering the only way to write a string is to use a `&str` in the first place).
411   pub unsafe fn read_string(&self, pc: &mut usize) -> &str {
412      let len_bytes: [u8; 8] = self.bytes[*pc..*pc + size_of::<u64>()].try_into().unwrap();
413      let len = u64::from_le_bytes(len_bytes);
414      *pc += size_of::<u64>();
415      let string = &self.bytes[*pc..*pc + len as usize];
416      let padded_len = (len + 3) & !3;
417      *pc += padded_len as usize;
418      std::str::from_utf8_unchecked(string)
419   }
420
421   /// Returns the length of the chunk (in bytes).
422   pub fn len(&self) -> usize {
423      self.bytes.len()
424   }
425
426   /// Returns whether the chunk is empty.
427   pub fn is_empty(&self) -> bool {
428      self.len() == 0
429   }
430
431   /// Returns the location (in file) of the program counter.
432   pub fn location(&self, pc: usize) -> Location {
433      let index = pc >> 2;
434      self.locations.get(index).copied().unwrap_or(Location::UNINIT)
435   }
436
437   /// Returns whether the given program counter is at the end of the chunk.
438   pub fn at_end(&self, pc: usize) -> bool {
439      pc >= self.bytes.len()
440   }
441}
442
443impl Debug for Chunk {
444   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445      f.debug_struct("Chunk")
446         .field("module_name", &self.module_name)
447         .field("preallocate_stack_slots", &self.preallocate_stack_slots)
448         .finish()?;
449      writeln!(f)?;
450
451      let mut pc = 0;
452      while !self.at_end(pc) {
453         let location = self.location(pc);
454         let show_pc = pc;
455         let (opcode, operand) = unsafe { self.read_instruction(&mut pc) };
456         write!(f, "{show_pc:06x} {} {opcode:?}({operand:?}) ", location)?;
457
458         #[allow(clippy::single_match)]
459         match opcode {
460            Opcode::PushNumber => write!(f, "{}", unsafe { self.read_number(&mut pc) })?,
461            Opcode::PushString | Opcode::CreateType => {
462               write!(f, "{:?}", unsafe { self.read_string(&mut pc) })?
463            }
464            | Opcode::JumpForward | Opcode::JumpForwardIfFalsy | Opcode::JumpForwardIfTruthy => {
465               write!(
466                  f,
467                  "-> {:06x}",
468                  pc + u32::from(operand) as usize + Opcode::INSTRUCTION_SIZE
469               )?;
470            }
471            Opcode::JumpBackward => {
472               write!(
473                  f,
474                  "-> {:06x}",
475                  pc - u32::from(operand) as usize + Opcode::INSTRUCTION_SIZE
476               )?;
477            }
478            Opcode::CallMethod => {
479               let (method_index, argument_count) = operand.unpack();
480               let operand = u32::from(operand);
481               write!(f, "{operand:06x}:[mi={method_index}, ac={argument_count}]")?;
482            }
483            _ => (),
484         }
485
486         writeln!(f)?;
487      }
488      Ok(())
489   }
490}
491
492/// Types that can be encoded into bytecode.
493pub trait EncodeInstruction {
494   fn encode_instruction(&self, bytes: &mut [u8; Opcode::INSTRUCTION_SIZE]);
495}
496
497impl EncodeInstruction for (Opcode, Opr24) {
498   fn encode_instruction(&self, bytes: &mut [u8; Opcode::INSTRUCTION_SIZE]) {
499      bytes[0] = self.0 as u8;
500      bytes[1] = self.1.bytes[0];
501      bytes[2] = self.1.bytes[1];
502      bytes[3] = self.1.bytes[2];
503   }
504}
505
506impl EncodeInstruction for (Opcode, u16) {
507   fn encode_instruction(&self, bytes: &mut [u8; Opcode::INSTRUCTION_SIZE]) {
508      bytes[0] = self.0 as u8;
509      let ubytes = self.1.to_le_bytes();
510      bytes[1] = ubytes[0];
511      bytes[2] = ubytes[1];
512      bytes[3] = 0;
513   }
514}
515
516impl EncodeInstruction for Opcode {
517   fn encode_instruction(&self, bytes: &mut [u8; Opcode::INSTRUCTION_SIZE]) {
518      (*self, Opr24 { bytes: [0, 0, 0] }).encode_instruction(bytes)
519   }
520}
521
522/// The kind of an upvalue capture.
523#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
524pub enum CaptureKind {
525   /// Capture a local from the current scope.
526   Local(Opr24),
527   /// Capture an existing upvalue from the current closure.
528   Upvalue(Opr24),
529}
530
531/// The ABI of a raw foreign function.
532pub type ForeignFunction = Box<dyn FnMut(&mut Memory, &[RawValue]) -> Result<RawValue, ErrorKind>>;
533
534/// The kind of a controlling function.
535#[derive(Debug, Clone, Copy, PartialEq, Eq)]
536pub enum Control {
537   GcCollect,
538}
539
540/// The kind of the function (bytecode or FFI).
541pub enum FunctionKind {
542   Bytecode {
543      chunk: Rc<Chunk>,
544      captured_locals: Vec<CaptureKind>,
545   },
546   Foreign(ForeignFunction),
547   Control(Control),
548}
549
550impl std::fmt::Debug for FunctionKind {
551   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
552      match self {
553         Self::Bytecode {
554            chunk,
555            captured_locals,
556         } => f
557            .debug_struct("Bytecode")
558            .field("chunk", chunk)
559            .field("captured_locals", captured_locals)
560            .finish(),
561         Self::Foreign(..) => f.debug_struct("Foreign").finish_non_exhaustive(),
562         Self::Control(ctl) => f.debug_tuple("Control").field(ctl).finish(),
563      }
564   }
565}
566
567/// A function prototype.
568#[derive(Debug)]
569pub struct Function {
570   pub name: Rc<str>,
571   pub parameter_count: Option<u16>,
572
573   pub kind: FunctionKind,
574
575   /// Set to `true` if the function is to be hidden in stack traces.
576   ///
577   /// This is useful for functions that are implementation details, such as trait function shims.
578   pub hidden_in_stack_traces: bool,
579}
580
581/// The signature of a function (its name, argument count, and enclosing trait).
582#[derive(Debug, Clone, PartialEq, Eq, Hash)]
583pub struct FunctionSignature {
584   pub name: Rc<str>,
585   /// This arity number does not include the implicit `self` argument.
586   pub arity: Option<u16>,
587   /// The index of the trait this signature belongs to.
588   /// When `None`, the function is free and does not belong to any trait.
589   pub trait_id: Option<Opr24>,
590}
591
592impl FunctionSignature {
593   /// Creates a new function signature for a function that does not belong to a trait.
594   pub fn new(name: impl Into<Rc<str>>, arity: impl Into<Option<u16>>) -> Self {
595      Self {
596         name: name.into(),
597         arity: arity.into(),
598         trait_id: None,
599      }
600   }
601
602   /// Renders this signature into one that can be formatted.
603   pub fn render(&self, env: &Environment) -> RenderedSignature {
604      RenderedSignature {
605         name: Rc::clone(&self.name),
606         // Subtract 1 to omit `self`.
607         arity: self.arity.map(|x| x - 1),
608         trait_name: self
609            .trait_id
610            .and_then(|index| env.get_trait(index))
611            .map(|prototype| Rc::clone(&prototype.name)),
612      }
613   }
614}
615
616/// An environment containing information about declared globals, functions, vtables.
617#[derive(Debug)]
618pub struct Environment {
619   /// Mapping from global names to global slots.
620   globals: HashMap<String, Opr24>,
621   /// Functions in the environment.
622   functions: Vec<Function>,
623   /// Mapping from named function signatures to method indices.
624   method_indices: HashMap<FunctionSignature, u16>,
625   /// Mapping from method indices to function signatures.
626   method_signatures: Vec<FunctionSignature>,
627   /// Dispatch tables for builtin types.
628   pub builtin_dtables: BuiltinDispatchTables,
629   /// `impl` prototypes.
630   prototypes: Vec<Option<Prototype>>,
631   /// `prototypes` indices that have been taken out of the vec.
632   free_prototypes: Vec<Opr24>,
633   /// Trait prototypes.
634   traits: Vec<TraitPrototype>,
635}
636
637impl Environment {
638   /// Creates a new, empty environment.
639   pub fn new(builtin_dtables: BuiltinDispatchTables) -> Self {
640      Self {
641         globals: HashMap::new(),
642         functions: Vec::new(),
643         method_indices: HashMap::new(),
644         method_signatures: Vec::new(),
645         builtin_dtables,
646         prototypes: Vec::new(),
647         free_prototypes: Vec::new(),
648         traits: Vec::new(),
649      }
650   }
651
652   /// Tries to create a global. Returns the global slot number, or an error if there are too many
653   /// globals.
654   pub fn create_global(&mut self, name: &str) -> Result<Opr24, ErrorKind> {
655      if self.globals.contains_key(name) {
656         Ok(*self.globals.get(name).unwrap())
657      } else {
658         let slot = Opr24::try_from(self.globals.len()).map_err(|_| ErrorKind::TooManyGlobals)?;
659         self.globals.insert(name.to_owned(), slot);
660         Ok(slot)
661      }
662   }
663
664   /// Tries to look up a global. Returns `None` if the global doesn't exist.
665   pub fn get_global(&self, name: &str) -> Option<Opr24> {
666      self.globals.get(name).copied()
667   }
668
669   /// Creates a function and returns its ID.
670   pub fn create_function(&mut self, function: Function) -> Result<Opr24, ErrorKind> {
671      let slot = Opr24::try_from(self.functions.len()).map_err(|_| ErrorKind::TooManyFunctions)?;
672      self.functions.push(function);
673      Ok(slot)
674   }
675
676   /// Returns the function with the given ID, as returned by `create_function`.
677   /// This function is for internal use in the VM and does not perform any checks, thus is marked
678   /// `unsafe`.
679   pub(crate) unsafe fn get_function_unchecked(&self, id: Opr24) -> &Function {
680      self.functions.get_unchecked(u32::from(id) as usize)
681   }
682
683   /// Returns the function with the given ID, as returned by `create_function`.
684   /// This function is for internal use in the VM and does not perform any checks, thus is marked
685   /// `unsafe`.
686   pub(crate) unsafe fn get_function_unchecked_mut(&mut self, id: Opr24) -> &mut Function {
687      self.functions.get_unchecked_mut(u32::from(id) as usize)
688   }
689
690   /// Tries to look up the index of a method, based on a function signature. Returns `None` if
691   /// there are too many function signatures in this environment.
692   pub fn get_method_index(&mut self, signature: &FunctionSignature) -> Result<u16, ErrorKind> {
693      // Don't use `entry` here to avoid cloning the signature.
694      if let Some(&index) = self.method_indices.get(signature) {
695         Ok(index)
696      } else {
697         // The number of entries in self.method_indices and self.function_signatures is always
698         // equal, so we can use their `len`s interchangably.
699         let index =
700            u16::try_from(self.method_indices.len()).map_err(|_| ErrorKind::TooManyMethods)?;
701         self.method_indices.insert(signature.clone(), index);
702         self.method_signatures.push(signature.clone());
703         Ok(index)
704      }
705   }
706
707   /// Returns the signature for the method with the given ID, or `None` if the method index is
708   /// invalid.
709   pub fn get_method_signature(&self, method_index: u16) -> Option<&FunctionSignature> {
710      self.method_signatures.get(method_index as usize)
711   }
712
713   /// Creates a prototype and returns its ID.
714   pub(crate) fn create_prototype(&mut self, proto: Prototype) -> Result<Opr24, ErrorKind> {
715      let slot = if let Some(slot) = self.free_prototypes.pop() {
716         self.prototypes[u32::from(slot) as usize] = Some(proto);
717         slot
718      } else {
719         let slot = Opr24::try_from(self.prototypes.len()).map_err(|_| ErrorKind::TooManyImpls)?;
720         self.prototypes.push(Some(proto));
721         slot
722      };
723      Ok(slot)
724   }
725
726   /// Takes out the prototype with the given ID, as returned by `create_prototype`.
727   /// This function is for internal use in the VM and does not perform any checks, thus is marked
728   /// `unsafe`.
729   pub(crate) unsafe fn take_prototype_unchecked(&mut self, id: Opr24) -> Prototype {
730      let proto =
731         self.prototypes.get_unchecked_mut(u32::from(id) as usize).take().unwrap_unchecked();
732      self.free_prototypes.push(id);
733      proto
734   }
735
736   /// Creates a trait and returns its ID. Use `get_trait_mut` afterwards to modify the trait.
737   pub fn create_trait(&mut self, name: Rc<str>) -> Result<Opr24, ErrorKind> {
738      let slot_index = self.traits.len();
739      let slot = Opr24::try_from(slot_index).map_err(|_| ErrorKind::TooManyTraits)?;
740      self.traits.push(TraitPrototype {
741         name,
742         required: HashSet::new(),
743         shims: vec![],
744      });
745      Ok(slot)
746   }
747
748   /// Returns a reference to the trait with the given ID.
749   pub fn get_trait(&self, id: Opr24) -> Option<&TraitPrototype> {
750      self.traits.get(usize::from(id))
751   }
752
753   /// Returns a mutable reference to the trait with the given ID.
754   pub fn get_trait_mut(&mut self, id: Opr24) -> Option<&mut TraitPrototype> {
755      self.traits.get_mut(usize::from(id))
756   }
757}
758
759/// A dispatch table containing functions bound to an instance of a value.
760#[derive(Debug)]
761pub struct DispatchTable {
762   /// The pretty name of the type this dispatch table contains functions for.
763   pub pretty_name: Rc<str>,
764   pub type_name: Rc<str>,
765   /// The "child" dispatch table that holds instance methods.
766   pub instance: Option<GcRaw<DispatchTable>>,
767   /// The functions in this dispatch table.
768   methods: Vec<Option<GcRaw<Closure>>>,
769}
770
771impl DispatchTable {
772   /// Creates a new, empty dispatch table for a type with the given name.
773   fn new(pretty_name: impl Into<Rc<str>>, type_name: impl Into<Rc<str>>) -> Self {
774      Self {
775         pretty_name: pretty_name.into(),
776         type_name: type_name.into(),
777         instance: None,
778         methods: Vec::new(),
779      }
780   }
781
782   /// Creates a new, empty type dispatch table with the given type name.
783   pub fn new_for_type(type_name: impl Into<Rc<str>>) -> Self {
784      let type_name = type_name.into();
785      Self::new(format!("type {type_name}"), type_name)
786   }
787
788   /// Creates a new, empty instance dispatch table with the given type name.
789   pub fn new_for_instance(type_name: impl Into<Rc<str>>) -> Self {
790      let type_name = type_name.into();
791      Self::new(Rc::clone(&type_name), type_name)
792   }
793
794   /// Returns a reference to the method at the given index.
795   pub fn get_method(&self, index: u16) -> Option<GcRaw<Closure>> {
796      self.methods.get(index as usize).into_iter().flatten().copied().next()
797   }
798
799   /// Adds a method into the dispatch table.
800   pub fn set_method(&mut self, index: u16, closure: GcRaw<Closure>) {
801      let index = index as usize;
802      if index >= self.methods.len() {
803         self.methods.resize(index + 1, None);
804      }
805      self.methods[index] = Some(closure);
806   }
807
808   /// Returns an iterator over all methods in this dispatch table.
809   pub(crate) fn methods(&self) -> impl Iterator<Item = GcRaw<Closure>> + '_ {
810      self.methods.iter().copied().flatten()
811   }
812}
813
814/// Dispatch tables for instances of builtin types. These should be constructed by the standard
815/// library.
816#[derive(Debug)]
817pub struct BuiltinDispatchTables {
818   pub nil: Gc<DispatchTable>,
819   pub boolean: Gc<DispatchTable>,
820   pub number: Gc<DispatchTable>,
821   pub string: Gc<DispatchTable>,
822   pub function: Gc<DispatchTable>,
823   pub list: Gc<DispatchTable>,
824   pub dict: Gc<DispatchTable>,
825}
826
827/// Default dispatch tables for built-in types are empty and do not implement any methods.
828impl BuiltinDispatchTables {
829   pub fn empty() -> Self {
830      Self {
831         nil: Gc::new(DispatchTable::new("Nil", "Nil")),
832         boolean: Gc::new(DispatchTable::new("Boolean", "Boolean")),
833         number: Gc::new(DispatchTable::new("Number", "Boolean")),
834         string: Gc::new(DispatchTable::new("String", "String")),
835         function: Gc::new(DispatchTable::new("Function", "Function")),
836         list: Gc::new(DispatchTable::new("List", "List")),
837         dict: Gc::new(DispatchTable::new("Dict", "Dict")),
838      }
839   }
840}
841
842/// IDs of built-in traits and their methods.
843pub struct BuiltinTraits {
844   pub iterator: Opr24,
845   pub iterator_has_next: u16,
846   pub iterator_next: u16,
847}
848
849impl BuiltinTraits {
850   /// Tries to register built-in traits in the given environment.
851   fn try_register_in(env: &mut Environment) -> Result<Self, ErrorKind> {
852      let mut builder = TraitBuilder::new(env, None, Rc::from("Iterator"))?;
853      let iterator_has_next = builder.add_method(Rc::from("has_next"), 0)?;
854      let iterator_next = builder.add_method(Rc::from("next"), 0)?;
855      let (iterator, _) = builder.build();
856
857      Ok(Self {
858         iterator,
859         iterator_has_next,
860         iterator_next,
861      })
862   }
863
864   /// Registers built-in traits in the given environment.
865   ///
866   /// # Panics
867   /// If there are too many traits, methods, or functions registered in the environment.
868   pub fn register_in(env: &mut Environment) -> Self {
869      Self::try_register_in(env).expect("environment should have enough space for built-in traits")
870   }
871}
872
873/// The prototype of a struct. This contains a list of functions, from which closures are
874/// constructed at runtime to form a dispatch table.
875#[derive(Debug, Default)]
876pub(crate) struct Prototype {
877   /// Map of method IDs to instance methods. This doesn't include trait methods, which have
878   /// to be resolved dynamically.
879   pub(crate) instance: HashMap<u16, Opr24>,
880
881   /// List of implemented trait methods. Because an implemented trait in `as` can be any
882   /// expression, we have no way of knowing what the method ID is going to be, so we have to map
883   /// trait methods by `(name, arity, trait_index)` pairs rather than method ID.
884   pub(crate) trait_instance: HashMap<(Rc<str>, u16, u16), Opr24>,
885
886   /// Same as `instance`, but lists methods that are added to the type dtable rather than the
887   /// instance dtable.
888   ///
889   /// Even though we don't support `static` methods in traits, the type is kept the same to reduce
890   /// code duplication.
891   pub(crate) statics: HashMap<u16, Opr24>,
892
893   /// The total number of traits implemented by this struct.
894   pub(crate) implemented_trait_count: u16,
895}
896
897/// The prototype of a trait. Contains a list of all method IDs the trait must implement.
898#[derive(Debug)]
899pub struct TraitPrototype {
900   pub name: Rc<str>,
901   /// List of method IDs that this trait requires.
902   pub required: HashSet<u16>,
903   /// List of `(method_id, function_id)` mappings that make up the dtable of shims for the trait.
904   pub shims: Vec<(u16, Opr24)>,
905}