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 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 == 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(®) = 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}