Skip to main content

candela/compiler/
compiler_data.rs

1use super::expr::Expr;
2use super::expr::Span;
3use super::registers::get_tgt_ids;
4use super::type_system::DataType;
5use crate::compiler::Namespace;
6use crate::data::Data;
7use crate::data::NULL;
8use crate::instr::Instr;
9use rustc_hash::FxHashMap;
10use rustc_hash::FxHashSet;
11use smol_strc::SmolStr;
12use std::rc::Rc;
13
14// Runtime data types moved to `crate::rt` so the VM can be built without the
15// compiler. Re-exported here to keep the compiler's `compiler_data::X` paths
16// working unchanged.
17pub use crate::rt::{
18    DynamicLibFn, EnumType, EnumVariant, ErrorCatch, HostFnSig, InstrSrc, Pools, Source, Struct,
19};
20
21#[derive(Debug)]
22pub struct Function {
23    pub name: SmolStr,
24    pub args: Box<[(SmolStr, Option<DataType>)]>,
25    pub code: Rc<[Expr]>,
26    pub impls: Vec<FunctionImpl>,
27    pub is_recursive: Option<bool>,
28    pub returns_null: bool,
29    pub src_file: u16,
30    /// Cache of return types from track_returns, keyed by Box<arg types>
31    pub return_type_cache: Vec<(Box<[DataType]>, DataType)>,
32    pub direct_calls: Box<[SmolStr]>,
33    pub name_span: Span,
34    /// The declared `-> Type` return annotation with the span it was written
35    /// at.
36    ///
37    /// `None` leaves the return type inferred from the body. When present, the
38    /// inferred return type of every specialisation is checked against it.
39    pub return_type: Option<(DataType, Span)>,
40}
41
42#[derive(Debug)]
43pub struct FunctionImpl {
44    pub loc: u16,
45    pub args_loc: Box<[u16]>,
46    pub arg_types: Box<[DataType]>,
47}
48
49#[derive(Debug)]
50pub struct FnSignature {
51    pub name: SmolStr,
52    pub args: Box<[DataType]>,
53    pub return_type: DataType,
54    pub id: u16,
55    /// When true (host functions only) the call site accepts any number of
56    /// arguments of any type and forwards them to the registered closure as a
57    /// slice; `args` is empty and no arity/type checking is performed.
58    pub variadic: bool,
59}
60
61#[derive(Debug)]
62pub struct Dynamiclib {
63    pub name: SmolStr,
64    pub fns: Box<[FnSignature]>,
65    /// When true, this namespace's functions are backed by Rust closures
66    /// registered on the embedding `Engine` (a `host "..."` block) rather than
67    /// by C symbols loaded from a shared object. Host calls compile to
68    /// [`crate::instr::Instr::CallHostFunc`] instead of `CallDynamicLibFunc`, and
69    /// their `FnSignature::id` indexes the program's host-function table.
70    pub is_host: bool,
71}
72
73#[derive(Clone, Copy)]
74pub struct Ctx {
75    pub block_id: u16,
76    /// Whether the code being compiled is within a recursive function
77    pub is_compiling_recursive: bool,
78    /// Whether the code being compiled is guaranteed to run at most once
79    pub single_run: bool,
80    /// Index of the current file in State's `sources`
81    pub file_idx: u16,
82    /// Instruction offset that's only used when compiling a function
83    pub offset: u16,
84}
85
86impl Ctx {
87    #[inline(always)]
88    #[must_use]
89    pub const fn no_single_run(self) -> Self {
90        Self {
91            single_run: false,
92            ..self
93        }
94    }
95    #[inline(always)]
96    #[must_use]
97    pub const fn advance_offset(self, output_len: u16) -> Self {
98        Self {
99            offset: self.offset + output_len,
100            ..self
101        }
102    }
103    #[inline(always)]
104    #[must_use]
105    pub const fn set_offset(self, offset: u16) -> Self {
106        Self { offset, ..self }
107    }
108}
109
110pub struct State<'a> {
111    pub registers: &'a mut Vec<Data>,
112    pub fns: &'a mut Vec<Function>,
113    pub structs: &'a mut Vec<Struct>,
114    pub enums: &'a mut Vec<EnumType>,
115    pub pools: &'a mut Pools,
116    pub instr_src: &'a mut Vec<InstrSrc>,
117    pub fn_registers: &'a mut Vec<Vec<u16>>,
118    pub dyn_libs: &'a mut Vec<Dynamiclib>,
119    pub allocated_arg_count: &'a mut usize,
120    pub allocated_call_depth: &'a mut usize,
121    pub const_registers: &'a mut FxHashMap<Data, u16>,
122    pub free_registers: &'a mut Vec<u16>,
123    pub sources: &'a mut Vec<Source>,
124    pub reserved_registers: FxHashSet<u16>,
125    pub namespace: &'a mut Namespace,
126}
127
128impl State<'_> {
129    /// Marks a register as free, allowing it to later be reused by `alloc_reg`.
130    /// The register is marked as free iff:
131    /// - the register isn't tied to any variable
132    /// - the register isn't a constant register
133    /// - the register isn't reserved in `reserved_registers`
134    /// - the register isn't already marked as free
135    pub fn free_reg(&mut self, id: u16, v: &[Variable]) {
136        if !v.iter().any(|var| var.register_id == id)
137            && !self.const_registers.values().any(|&reg| reg == id)
138            && !self.reserved_registers.contains(&id)
139            && !self.free_registers.contains(&id)
140        {
141            self.free_registers.push(id);
142        }
143    }
144    /// Allocates a register. It `free_registers` isn't empty, it will reuse the latest one. Else, it will allocate a new one.
145    pub fn alloc_reg(&mut self) -> u16 {
146        if let Some(reg) = self.free_registers.pop() {
147            reg
148        } else {
149            self.registers.push(NULL);
150            (self.registers.len() - 1) as u16
151        }
152    }
153    /// Allocates a register, reusing `tgt_id` if it holds some register id.
154    /// If `tgt_id == None`, it calls `alloc_reg()`.
155    #[inline(always)]
156    pub fn alloc_reg_tgt(&mut self, tgt_id: Option<u16>) -> u16 {
157        if let Some(id) = tgt_id {
158            id
159        } else {
160            self.alloc_reg()
161        }
162    }
163    /// Frees registers that are written by instructions in scope_instrs.
164    pub fn free_scope_registers(
165        &mut self,
166        regs_before: u16,
167        scope_instrs: &[Instr],
168        v: &[Variable],
169    ) {
170        for id in get_tgt_ids(scope_instrs) {
171            if id >= regs_before {
172                self.free_reg(id, v);
173            }
174        }
175    }
176
177    /// Similar to free_scope_registers, but also frees CloneArray template registers. Only call this after a loop ends.
178    pub fn free_loop_scope_registers(
179        &mut self,
180        regs_before: u16,
181        scope_instrs: &[Instr],
182        v: &[Variable],
183    ) {
184        self.free_scope_registers(regs_before, scope_instrs, v);
185        // Free CloneArray template registers
186        for instr in scope_instrs {
187            if let Instr::CloneArray(template_reg, _, _) = instr
188                && *template_reg >= regs_before
189            {
190                self.free_reg(*template_reg, v);
191            } else if let Instr::CloneStruct(template_reg, _) = instr
192                && *template_reg >= regs_before
193            {
194                self.free_reg(*template_reg, v);
195            } else if let Instr::CloneEnum(template_reg, _) = instr
196                && *template_reg >= regs_before
197            {
198                self.free_reg(*template_reg, v);
199            }
200        }
201    }
202    /// Associates the last instruction in `output` with `span` and adds the `InstrSrc` to `instr_src`.
203    /// This allows runtime errors to be traced back to `span` in the source code.
204    #[inline(always)]
205    pub fn add_to_src(&mut self, ctx: Ctx, output: &[Instr], span: Span) {
206        self.instr_src.push(InstrSrc {
207            instr: unsafe { *output.last().unwrap_unchecked() },
208            span,
209            file_id: ctx.file_idx,
210        });
211    }
212}
213
214#[derive(Debug)]
215pub struct Variable {
216    pub name: SmolStr,
217    pub register_id: u16,
218    pub var_type: DataType,
219}