Skip to main content

candela/compiler/
compiler_data.rs

1// This file is derived from keel (https://github.com/horacehoff/keel),
2// Copyright 2026 Horace Hoff, licensed under the Apache License, Version 2.0.
3// It has been modified by the candela authors. See the NOTICE file.
4use super::expr::Expr;
5use super::expr::Span;
6use super::registers::get_tgt_ids;
7use super::type_system::DataType;
8use super::type_system::Generics;
9use super::type_system::GenericsCheckpoint;
10use super::type_system::ReturnAnnotation;
11use super::type_system::TypeCtx;
12use super::type_system::TypeExpr;
13use super::type_system::TypeParams;
14use crate::compiler::FileNamespaces;
15use crate::compiler::FileNamespacesCheckpoint;
16use crate::compiler::Namespace;
17use crate::data::Data;
18use crate::data::NULL;
19use crate::instr::Instr;
20use crate::instr::LibFunc;
21use crate::rt::FnValue;
22use rustc_hash::FxHashMap;
23use rustc_hash::FxHashSet;
24use smol_strc::SmolStr;
25use std::rc::Rc;
26
27// Runtime data types moved to `crate::rt` so the VM can be built without the
28// compiler. Re-exported here to keep the compiler's `compiler_data::X` paths
29// working unchanged.
30pub use crate::rt::{
31    DynamicLibFn, EnumType, EnumVariant, ErrorCatch, HostFnSig, InstrSrc, Pools, Source, Struct,
32};
33
34/// The type tables a message needs to call a user type by the name the program
35/// gave it.
36///
37/// A [`DataType`] carries only a struct's or an enum's id, so
38/// [`Display`](std::fmt::Display) has nothing to print but the words `struct`
39/// and `enum`: every enum in the program reads the same, and a mismatch reads
40/// "expects `enum[]`, got `int[]`". Pairing a type with these tables through
41/// [`TypeNames::of`] prints `Value[]` instead.
42///
43/// Naming a type is a compile-time job: the names come from the tables a
44/// compile builds, and nothing the VM runs reads them. A frontend that shows a
45/// type to a person reaches for this too, which is why the crate root
46/// re-exports it.
47#[derive(Clone, Copy)]
48pub struct TypeNames<'a> {
49    pub structs: &'a [Struct],
50    pub enums: &'a [EnumType],
51}
52
53impl<'a> TypeNames<'a> {
54    /// `ty` written out with these tables.
55    #[must_use]
56    pub const fn of(self, ty: &'a DataType) -> NamedType<'a> {
57        NamedType { ty, names: self }
58    }
59}
60
61/// A [`DataType`] together with the tables that name its structs and enums, as
62/// produced by [`TypeNames::of`].
63pub struct NamedType<'a> {
64    ty: &'a DataType,
65    names: TypeNames<'a>,
66}
67
68impl std::fmt::Display for NamedType<'_> {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self.ty {
71            DataType::Struct(id) => match self.names.structs.get(*id as usize) {
72                Some(declared) => write!(f, "{}", declared.name),
73                None => write!(f, "{}", self.ty),
74            },
75            DataType::Enum(id) => match self.names.enums.get(*id as usize) {
76                Some(declared) => write!(f, "{}", declared.name),
77                None => write!(f, "{}", self.ty),
78            },
79            DataType::Array(element) => match element {
80                Some(element) => write!(f, "{}[]", self.names.of(element)),
81                None => write!(f, "any[]"),
82            },
83            DataType::Map(entry) => write!(
84                f,
85                "{{{}: {}}}",
86                self.names
87                    .of(entry.0.as_ref().unwrap_or(&DataType::Unknown)),
88                self.names
89                    .of(entry.1.as_ref().unwrap_or(&DataType::Unknown))
90            ),
91            DataType::Union(types) => {
92                for (i, member) in types.iter().enumerate() {
93                    if i > 0 {
94                        write!(f, "|")?;
95                    }
96                    write!(f, "{}", self.names.of(member))?;
97                }
98                Ok(())
99            }
100            // The dynamic slot is spelled `any` in a program, and a
101            // message about one names it the way the program does.
102            DataType::Unknown => write!(f, "any"),
103            other => write!(f, "{other}"),
104        }
105    }
106}
107
108#[derive(Debug)]
109pub struct Function {
110    pub name: SmolStr,
111    pub args: Box<[(SmolStr, Option<DataType>)]>,
112    pub code: Rc<[Expr]>,
113    pub impls: Vec<FunctionImpl>,
114    pub is_recursive: Option<bool>,
115    pub returns_null: bool,
116    pub src_file: u16,
117    /// Cache of return types from track_returns, keyed by Box<arg types>
118    pub return_type_cache: Vec<(Box<[DataType]>, DataType)>,
119    pub direct_calls: Box<[SmolStr]>,
120    pub name_span: Span,
121    /// The declared `-> Type` return annotation with the span it was written
122    /// at.
123    ///
124    /// `None` leaves the return type inferred from the body. When present, the
125    /// inferred return type of every specialisation is checked against it.
126    pub return_type: Option<(DataType, Span)>,
127    /// Set for a function that has type parameters of its own (`fn first<T>`)
128    /// or that came from an `impl` block on a generic type. `None` for an
129    /// ordinary function.
130    pub generics: Option<Box<FnGenerics>>,
131    /// The variables an anonymous function reads from the scope it was written
132    /// in, with the type each had there, in the order the environment records
133    /// them. Empty for a declared function and for a closure that reads only
134    /// its own parameters, which is what keeps that closure lowering the way
135    /// it did before capture existed. The third member is the type the
136    /// variable's `let` declared, which a write inside the body is held to.
137    pub captures: Box<[(SmolStr, DataType, Option<DataType>)]>,
138    /// The register holding where this function's body starts, for a function
139    /// that becomes a value. It is the first slot of every value built for the
140    /// function, and an indirect call jumps to what it holds. `None` for a
141    /// function no value is ever made of, which is every function a program
142    /// only calls by name.
143    pub entry_register: Option<u16>,
144}
145
146impl Function {
147    /// The parameter types a function that annotates every parameter declares,
148    /// which is what a call that cannot be seen passes it. `None` for a
149    /// function with a bare parameter or type parameters, whose types only a
150    /// call site settles.
151    #[must_use]
152    pub fn declared_params(&self) -> Option<Box<[DataType]>> {
153        if self.generics.is_some() {
154            return None;
155        }
156        self.args.iter().map(|(_, ty)| ty.clone()).collect()
157    }
158}
159
160/// The generic side of a function: what its annotations mean once the type
161/// parameters are known.
162///
163/// `args` and `return_type` on the [`Function`] hold the resolution with no
164/// type arguments supplied, where an annotation naming a type parameter is left
165/// un-pinned. The unresolved forms are kept here and resolved again for a call
166/// site that names its type arguments.
167#[derive(Debug)]
168pub struct FnGenerics {
169    /// Type parameters the function declares.
170    pub params: TypeParams,
171    /// The parameter annotations as written.
172    pub arg_types: Box<[Option<TypeExpr>]>,
173    /// The `-> Type` annotation as written.
174    pub return_type: ReturnAnnotation,
175    /// Type parameters already bound because the enclosing type was
176    /// instantiated: `impl Cell<T>` lowered for `Cell<int>` binds `T` to `int`
177    /// in every one of its methods.
178    pub bindings: Box<[(SmolStr, DataType)]>,
179    /// File the declaration was written in, whose scope its annotations resolve
180    /// against.
181    pub file_idx: u16,
182}
183
184#[derive(Debug)]
185pub struct FunctionImpl {
186    pub loc: u16,
187    pub args_loc: Box<[u16]>,
188    pub arg_types: Box<[DataType]>,
189    /// The type arguments this specialisation was compiled for. A type
190    /// parameter that no argument mentions (`fn signal<T>(name: string)`) is
191    /// what makes two calls with the same argument types distinct
192    /// specialisations.
193    pub type_args: Box<[DataType]>,
194    /// The register this specialisation reads its environment out of, for a
195    /// closure that captures. The call site puts the closure value there
196    /// before it jumps.
197    pub env_loc: Option<u16>,
198    /// Set for a specialisation a call through a value can reach. It takes its
199    /// arguments from the shared registers every indirect call writes, so a
200    /// call site that does not know which function it reaches still knows where
201    /// to put them, and it returns the way a recursive call does, since the
202    /// caller saved its registers on the way in.
203    pub indirect: bool,
204    /// The body a release-profile call site copies in place of the call, for a
205    /// specialisation small and simple enough to inline. See
206    /// `inlinable_body` in the user-function compiler for what qualifies.
207    pub inline_body: Option<Box<[Instr]>>,
208}
209
210#[derive(Debug)]
211pub struct FnSignature {
212    pub name: SmolStr,
213    pub args: Box<[DataType]>,
214    pub return_type: DataType,
215    pub id: u16,
216    /// When true (host functions only) the call site accepts any number of
217    /// arguments of any type and forwards them to the registered closure as a
218    /// slice; `args` is empty and no arity/type checking is performed.
219    pub variadic: bool,
220}
221
222#[derive(Debug)]
223pub struct Dynamiclib {
224    pub name: SmolStr,
225    pub fns: Box<[FnSignature]>,
226    /// When true, this namespace's functions are backed by Rust closures
227    /// registered on the embedding `Engine` (a `host "..."` block) rather than
228    /// by C symbols loaded from a shared object. Host calls compile to
229    /// [`crate::instr::Instr::CallHostFunc`] instead of `CallDynamicLibFunc`, and
230    /// their `FnSignature::id` indexes the program's host-function table.
231    pub is_host: bool,
232}
233
234#[derive(Clone, Copy)]
235pub struct Ctx {
236    pub block_id: u16,
237    /// Whether the code being compiled is within a recursive function
238    pub is_compiling_recursive: bool,
239    /// Whether the code being compiled is guaranteed to run at most once
240    pub single_run: bool,
241    /// Whether the code being compiled sits inside a function body. The body of
242    /// `main` is compiled inline at the program's top level, where there is no
243    /// call frame to return to, so a `return` there ends the program instead.
244    pub in_function: bool,
245    /// Index of the current file in State's `sources`
246    pub file_idx: u16,
247    /// Instruction offset that's only used when compiling a function
248    pub offset: u16,
249    /// The downcast a `return` of an `any` value goes through inside a
250    /// function declared to return a concrete type. The declaration types the
251    /// call site, so a dynamic value leaving the body is checked on the way
252    /// out; a value of a known type was already checked at compile time and
253    /// pays nothing.
254    pub return_downcast: Option<ReturnCheck>,
255}
256
257/// How a `return` checks an `any` value against the declared return type.
258#[derive(Clone, Copy)]
259pub enum ReturnCheck {
260    /// The downcast `v as int`, `v as string` and the rest compile to, for a scalar, a
261    /// list or a map.
262    Builtin(LibFunc),
263    /// `AsTypeVal` against these type codes, for a struct, an enum, a
264    /// function or a union. The list the instruction reads is made by the
265    /// first `return` that needs it, so a function whose returns are all
266    /// typed adds nothing to the program.
267    Types(TypeCodes),
268    /// `AsTypeVal` against the list of type codes in this register, for a
269    /// union with more members than [`TypeCodes`] holds.
270    TypesIn(u16),
271}
272
273/// Up to eight type codes, held by value so a [`Ctx`] can carry them.
274#[derive(Clone, Copy)]
275pub struct TypeCodes {
276    pub codes: [i64; 8],
277    pub len: u8,
278}
279
280impl TypeCodes {
281    #[must_use]
282    pub fn as_slice(&self) -> &[i64] {
283        &self.codes[..self.len as usize]
284    }
285}
286
287impl Ctx {
288    #[inline(always)]
289    #[must_use]
290    pub const fn no_single_run(self) -> Self {
291        Self {
292            single_run: false,
293            ..self
294        }
295    }
296    #[inline(always)]
297    #[must_use]
298    pub const fn advance_offset(self, output_len: u16) -> Self {
299        Self {
300            offset: self.offset + output_len,
301            ..self
302        }
303    }
304    #[inline(always)]
305    #[must_use]
306    pub const fn set_offset(self, offset: u16) -> Self {
307        Self { offset, ..self }
308    }
309}
310
311/// The registers an indirect call passes its arguments and the callee's
312/// environment in.
313///
314/// Every function reachable through a value takes its parameters from these, so
315/// a call site that only learns which function it reaches at run time still
316/// knows where to put the arguments. They are allocated once, on first use, and
317/// never handed to anything else.
318#[derive(Debug, Default)]
319pub struct IndirectRegisters {
320    /// One register per parameter position, grown as wider signatures appear.
321    pub args: Vec<u16>,
322    /// Where the callee finds the value it was called through, which is its
323    /// environment.
324    pub env: Option<u16>,
325    /// The functions each variable that holds a function value may hold, one
326    /// entry per such variable.
327    pub value_sets: Vec<FnValueSet>,
328}
329
330/// The functions one variable holding a function value may hold.
331///
332/// A variable grows its set as the code writes more functions into it, and a
333/// closure that captured the variable reads the set rather than the copy it
334/// took, so a function written in after the closure still reaches it.
335#[derive(Debug, Default)]
336pub struct FnValueSet {
337    pub candidates: Vec<u16>,
338    /// The argument types a call through the set has already been compiled
339    /// for. A function joining the set later is compiled for each of them, so
340    /// a call compiled before it arrived still reaches a body.
341    pub used_at: Vec<Box<[DataType]>>,
342}
343
344pub struct State<'a> {
345    pub registers: &'a mut Vec<Data>,
346    pub fns: &'a mut Vec<Function>,
347    pub structs: &'a mut Vec<Struct>,
348    pub enums: &'a mut Vec<EnumType>,
349    pub pools: &'a mut Pools,
350    pub instr_src: &'a mut Vec<InstrSrc>,
351    pub callsite_registers: &'a mut Vec<Vec<u16>>,
352    pub dyn_libs: &'a mut Vec<Dynamiclib>,
353    pub allocated_arg_count: &'a mut usize,
354    pub allocated_call_depth: &'a mut usize,
355    pub const_registers: &'a mut FxHashMap<Data, u16>,
356    pub free_registers: &'a mut Vec<u16>,
357    pub sources: &'a mut Vec<Source>,
358    pub reserved_registers: FxHashSet<u16>,
359    /// The call sites, among those that save registers, whose call returns a
360    /// value. Such a call writes its return register on the way back, so that
361    /// register holds nothing a save would have to keep.
362    pub value_callsites: FxHashSet<u16>,
363    /// Whether this is the release profile. `candela build` compiles with it
364    /// and runs the passes that make the program faster to run; a run from
365    /// source, the REPL and an embedding host compile without it, straight
366    /// from the syntax tree, so the dev loop never waits on a pass.
367    pub optimize: bool,
368    pub namespaces: &'a mut FileNamespaces,
369    pub generics: &'a mut Generics,
370    pub indirect_registers: &'a mut IndirectRegisters,
371    /// One frame per function body whose returns are being worked out, holding
372    /// the operand type of every `?` the walk typed in it. Each is a return of
373    /// that operand's `None` or `Err`.
374    pub propagations: Vec<Vec<DataType>>,
375    /// The function bodies being compiled, innermost last: the name a
376    /// diagnostic uses and the type the body returns, which is what a `?`
377    /// in it has to hand back.
378    pub fn_returns: Vec<(SmolStr, DataType)>,
379    /// The names an `is` being compiled binds that a closure in their scope
380    /// captures, so each is bound in a cell. Set by whatever compiles the
381    /// condition, which is what knows the scope.
382    pub captured_binders: Vec<SmolStr>,
383}
384
385/// A checkpoint of the tables a compile attempt grows as it goes, taken by
386/// [`State::checkpoint`] and undone by [`State::rollback_to`] when the attempt
387/// ends in a diagnostic.
388///
389/// Two attempts use it: a host's [`Program::call`](crate::Program::call),
390/// which compiles a trampoline against a program that must stay callable
391/// after a refused call, and the entry-point pass, which tries an `any` entry
392/// point for a host-called function with a bare parameter and keeps building
393/// when the body does not compile at `any`.
394///
395/// This is a handful of lengths, one per table plus one per function and one
396/// per file, not a copy of the tables, so an attempt that succeeds pays for
397/// little more than reading them.
398pub struct CompileCheckpoint {
399    registers: usize,
400    /// The function table itself grows during a compile: an anonymous function
401    /// literal hoists to a fresh entry the first time it is reached, and
402    /// instantiating a generic type lowers every applicable `impl` method the
403    /// same way. A function the aborted attempt hoisted is left holding a
404    /// bytecode address the instruction stream never reached, so it goes back
405    /// with everything else.
406    functions: usize,
407    /// Per function that was already there: how many specialisations it had,
408    /// how many return types it had cached, and the register a value of it
409    /// starts from. A specialisation compiled during the attempt records a
410    /// bytecode address in code that is dropped; a cached return type or an
411    /// entry register can name a specialisation or a register that goes too.
412    fn_state: Box<[(usize, usize, Option<u16>)]>,
413    /// One entry per recursive call site the attempt compiled, each written
414    /// once, at the end of the body that holds the call site, so the length
415    /// alone undoes the growth.
416    callsite_registers: usize,
417    /// Every file's scope length, not only the entry file's: a body compiled
418    /// from an imported module declares into that module's scope.
419    namespaces: FileNamespacesCheckpoint,
420    /// A generic type is instantiated, and an enum's variants added, the first
421    /// time a call site needs it; `structs` and `enums` cover that growth the
422    /// way `functions` covers a closure or a lowered `impl` method.
423    structs: usize,
424    enums: usize,
425    /// Grows in step with the attempt's local instruction buffer, keyed by
426    /// instruction value rather than position. Left behind, a stale entry can
427    /// mislabel a later instruction identical to one the attempt compiled.
428    instr_src: usize,
429    generics: GenericsCheckpoint,
430}
431
432impl State<'_> {
433    /// Snapshots the tables a compile attempt writes into as it goes, so a
434    /// diagnostic raised partway through can be undone with
435    /// [`State::rollback_to`].
436    ///
437    /// The instruction stream needs no entry here: an attempt compiles into a
438    /// local buffer that only reaches the stream once the whole attempt
439    /// succeeds. Everything captured below is written in place, because a
440    /// nested call can only reuse a specialisation another call site already
441    /// produced.
442    #[must_use]
443    pub fn checkpoint(&self) -> CompileCheckpoint {
444        CompileCheckpoint {
445            registers: self.registers.len(),
446            functions: self.fns.len(),
447            fn_state: self
448                .fns
449                .iter()
450                .map(|f| (f.impls.len(), f.return_type_cache.len(), f.entry_register))
451                .collect(),
452            callsite_registers: self.callsite_registers.len(),
453            namespaces: self.namespaces.checkpoint(),
454            structs: self.structs.len(),
455            enums: self.enums.len(),
456            instr_src: self.instr_src.len(),
457            generics: self.generics.checkpoint(),
458        }
459    }
460
461    /// Undoes everything a failed compile attempt wrote to the tables, back to
462    /// `checkpoint`. A successful attempt leaves the tables as they stand and
463    /// commits its bytecode alongside them.
464    pub fn rollback_to(&mut self, checkpoint: &CompileCheckpoint) {
465        // A `const_registers` or `free_registers` entry naming a register at or
466        // past `registers` was added during the aborted attempt (registers only
467        // grow, and a constant is registered the moment its register is
468        // pushed), so it goes with the registers themselves. An entry
469        // `free_registers` lost, because the attempt reused an already-free
470        // register, is not restored: that register is orphaned rather than
471        // double-allocated, which the next compile cannot observe.
472        self.const_registers
473            .retain(|_, &mut reg| (reg as usize) < checkpoint.registers);
474        self.free_registers
475            .retain(|&reg| (reg as usize) < checkpoint.registers);
476        self.registers.truncate(checkpoint.registers);
477
478        self.fns.truncate(checkpoint.functions);
479        for (func, &(impls, cached, entry_register)) in
480            self.fns.iter_mut().zip(checkpoint.fn_state.iter())
481        {
482            func.impls.truncate(impls);
483            func.return_type_cache.truncate(cached);
484            func.entry_register = entry_register;
485        }
486        self.callsite_registers
487            .truncate(checkpoint.callsite_registers);
488
489        self.namespaces.rollback_to(&checkpoint.namespaces);
490
491        // A generic type instantiated during the attempt is cached in
492        // `generics` by rendered name, pointing at the struct or enum entry the
493        // attempt pushed; both go together.
494        self.structs.truncate(checkpoint.structs);
495        self.enums.truncate(checkpoint.enums);
496        self.generics.rollback_to(&checkpoint.generics);
497
498        self.instr_src.truncate(checkpoint.instr_src);
499    }
500
501    /// The tables a diagnostic needs to name a struct or an enum the program
502    /// declared, rather than printing the bare words `struct` and `enum`.
503    #[must_use]
504    pub fn type_names(&self) -> TypeNames<'_> {
505        TypeNames {
506            structs: self.structs,
507            enums: self.enums,
508        }
509    }
510
511    /// The scope names written in `file_idx` resolve in: that file's own
512    /// declarations, the symbols its bare imports merged in, and the modules it
513    /// bound with `as`.
514    #[must_use]
515    pub fn scope(&self, file_idx: u16) -> &Namespace {
516        self.namespaces.get(file_idx)
517    }
518    /// The same scope, to declare into. A function or struct declared inside a
519    /// block is registered here for as long as the block is being compiled.
520    pub fn scope_mut(&mut self, file_idx: u16) -> &mut Namespace {
521        self.namespaces.get_mut(file_idx)
522    }
523    /// The scope and registries a [`TypeExpr`] resolves against, borrowed from
524    /// this state. Resolving a generic type registers the instantiation, which
525    /// is why it needs more than the namespace.
526    pub fn type_ctx(&mut self, file_idx: u16) -> TypeCtx<'_> {
527        TypeCtx {
528            file_idx,
529            namespaces: self.namespaces,
530            sources: self.sources,
531            structs: self.structs,
532            enums: self.enums,
533            fns: self.fns,
534            generics: self.generics,
535        }
536    }
537    /// Marks a register as free, allowing it to later be reused by `alloc_reg`.
538    /// The register is marked as free iff:
539    /// - the register isn't tied to any variable
540    /// - the register isn't a constant register
541    /// - the register isn't reserved in `reserved_registers`
542    /// - the register isn't already marked as free
543    pub fn free_reg(&mut self, id: u16, v: &[Variable]) {
544        if !v.iter().any(|var| var.register_id == id)
545            && !self.const_registers.values().any(|&reg| reg == id)
546            && !self.reserved_registers.contains(&id)
547            && !self.free_registers.contains(&id)
548        {
549            self.free_registers.push(id);
550        }
551    }
552    /// Allocates a register. It `free_registers` isn't empty, it will reuse the latest one. Else, it will allocate a new one.
553    pub fn alloc_reg(&mut self) -> u16 {
554        if let Some(reg) = self.free_registers.pop() {
555            reg
556        } else {
557            self.registers.push(NULL);
558            (self.registers.len() - 1) as u16
559        }
560    }
561    /// The register a constant integer lives in, allocated on first use.
562    ///
563    /// A constant register is written once by the compiler and read by the
564    /// instructions that name it, so every use of the same constant shares
565    /// one.
566    pub fn const_int_register(&mut self, n: i64) -> u16 {
567        self.const_register(Data::int(n))
568    }
569    /// A new register holding `data`, the template a `Clone*` instruction
570    /// copies from.
571    ///
572    /// Nothing writes a template: every run of the literal reads the value the
573    /// compiler put there. The code holding the literal can run again, through
574    /// another call of its function or another turn of an enclosing loop, so
575    /// the register stays out of the allocator for good.
576    pub fn template_register(&mut self, data: Data) -> u16 {
577        self.registers.push(data);
578        let id = (self.registers.len() - 1) as u16;
579        self.reserve_register(id)
580    }
581    /// The register a constant lives in, allocated on first use and shared by
582    /// every use of the same value.
583    /// The constant register holding a list of these type codes, the one an
584    /// earlier check made when there is one.
585    pub fn type_codes_register(&mut self, codes: &[i64]) -> u16 {
586        let list: Vec<Data> = codes.iter().map(|&code| Data::int(code)).collect();
587        if let Some(&reg) = self.const_registers.iter().find_map(|(data, reg)| {
588            (data.is_array() && self.pools.objs[data.as_array()] == list).then_some(reg)
589        }) {
590            return reg;
591        }
592        self.pools.objs.push(list);
593        let list = Data::array((self.pools.objs.len() - 1) as u32);
594        self.const_register(list)
595    }
596    pub fn const_register(&mut self, data: Data) -> u16 {
597        if let Some(&id) = self.const_registers.get(&data) {
598            return id;
599        }
600        let id = self.registers.len() as u16;
601        self.const_registers.insert(data, id);
602        self.registers.push(data);
603        id
604    }
605    /// Reserves `id` so the allocator never hands it out again, and answers it.
606    fn reserve_register(&mut self, id: u16) -> u16 {
607        self.reserved_registers.insert(id);
608        self.free_registers.retain(|reg| *reg != id);
609        id
610    }
611    /// The register the argument at `idx` of an indirect call travels in.
612    ///
613    /// Reserved on first use, so the allocator never hands it to anything else:
614    /// the caller writes it and the callee reads it as its parameter, and the
615    /// two never meet at compile time.
616    pub fn indirect_arg_register(&mut self, idx: usize) -> u16 {
617        while self.indirect_registers.args.len() <= idx {
618            self.registers.push(NULL);
619            let id = (self.registers.len() - 1) as u16;
620            self.indirect_registers.args.push(id);
621        }
622        self.reserve_register(self.indirect_registers.args[idx])
623    }
624    /// A fresh set of the functions one variable may hold, starting with
625    /// `fn_id`.
626    pub fn new_fn_value_set(&mut self, fn_id: u16) -> u32 {
627        self.indirect_registers.value_sets.push(FnValueSet {
628            candidates: vec![fn_id],
629            used_at: Vec::new(),
630        });
631        (self.indirect_registers.value_sets.len() - 1) as u32
632    }
633    /// The functions a call through `fn_type` may reach.
634    #[must_use]
635    pub fn fn_value_candidates(&self, fn_type: &FnValue) -> Vec<u16> {
636        let mut candidates = fn_type.candidates.to_vec();
637        if let Some(set) = fn_type.set
638            && let Some(set) = self.indirect_registers.value_sets.get(set as usize)
639        {
640            for id in &set.candidates {
641                if !candidates.contains(id) {
642                    candidates.push(*id);
643                }
644            }
645        }
646        candidates
647    }
648    /// The register an indirect call leaves the callee's environment in.
649    pub fn indirect_env_register(&mut self) -> u16 {
650        if let Some(id) = self.indirect_registers.env {
651            return self.reserve_register(id);
652        }
653        self.registers.push(NULL);
654        let id = (self.registers.len() - 1) as u16;
655        self.indirect_registers.env = Some(id);
656        self.reserve_register(id)
657    }
658    /// The register holding where `fn_id`'s body starts, allocated the first
659    /// time the function becomes a value. Every value built for the function
660    /// carries it, and the compiler writes the location into it once the
661    /// function's indirect specialisation is compiled.
662    pub fn fn_entry_register(&mut self, fn_id: usize) -> u16 {
663        if let Some(id) = self.fns[fn_id].entry_register {
664            return self.reserve_register(id);
665        }
666        self.registers.push(NULL);
667        let id = (self.registers.len() - 1) as u16;
668        self.fns[fn_id].entry_register = Some(id);
669        self.reserve_register(id)
670    }
671    /// Allocates a register, reusing `tgt_id` if it holds some register id.
672    /// If `tgt_id == None`, it calls `alloc_reg()`.
673    #[inline(always)]
674    pub fn alloc_reg_tgt(&mut self, tgt_id: Option<u16>) -> u16 {
675        if let Some(id) = tgt_id {
676            id
677        } else {
678            self.alloc_reg()
679        }
680    }
681    /// Frees registers that are written by instructions in scope_instrs.
682    pub fn free_scope_registers(
683        &mut self,
684        regs_before: u16,
685        scope_instrs: &[Instr],
686        v: &[Variable],
687    ) {
688        for id in get_tgt_ids(scope_instrs) {
689            if id >= regs_before {
690                self.free_reg(id, v);
691            }
692        }
693    }
694
695    /// Associates the last instruction in `output` with `span` and adds the `InstrSrc` to `instr_src`.
696    /// This allows runtime errors to be traced back to `span` in the source code.
697    #[inline(always)]
698    pub fn add_to_src(&mut self, ctx: Ctx, output: &[Instr], span: Span) {
699        self.add_instr_to_src(ctx, unsafe { *output.last().unwrap_unchecked() }, span);
700    }
701    /// [`add_to_src`](Self::add_to_src) for an instruction that is written back
702    /// into `output` after later ones were emitted, so it is no longer the last.
703    #[inline(always)]
704    pub fn add_instr_to_src(&mut self, ctx: Ctx, instr: Instr, span: Span) {
705        self.instr_src.push(InstrSrc {
706            instr,
707            span,
708            file_id: ctx.file_idx,
709        });
710    }
711}
712
713#[derive(Debug)]
714pub struct Variable {
715    pub name: SmolStr,
716    pub register_id: u16,
717    pub var_type: DataType,
718    /// Set for a variable a closure captures. Its register holds a cell rather
719    /// than the value, so a read goes through [`Instr::LoadCell`] and a write
720    /// through [`Instr::StoreCell`], and the closure that took it writes the
721    /// slot this scope reads.
722    pub cell: bool,
723    /// The type a `let` annotation declared, `None` for a variable written
724    /// without one. A declared variable keeps this type: every value written
725    /// into it has to fit, and its reads are typed by it.
726    pub declared: Option<DataType>,
727}