Skip to main content

rune/compile/
unit_builder.rs

1//! A single execution unit in the runestick virtual machine.
2//!
3//! A unit consists of a sequence of instructions, and lookaside tables for
4//! metadata like function locations.
5
6use core::fmt;
7
8use crate::alloc::fmt::TryWrite;
9use crate::alloc::prelude::*;
10use crate::alloc::{self, try_format, Box, HashMap, String, Vec};
11use crate::ast::{Span, Spanned};
12use crate::compile::meta;
13use crate::compile::{self, Assembly, AssemblyInst, ErrorKind, Location, Pool, WithSpan};
14use crate::hash;
15use crate::query::QueryInner;
16use crate::runtime::debug::{DebugArgs, DebugGlobal, DebugSignature};
17use crate::runtime::inst;
18use crate::runtime::unit::UnitEncoder;
19use crate::runtime::{
20    Address, Call, ConstValueBuf, DebugInfo, DebugInst, Inst, Label, Protocol, Rtti, RttiKind,
21    StaticString, Unit, UnitFn,
22};
23use crate::sync::Arc;
24use crate::{Context, Diagnostics, Hash, Item, ItemBuf, SourceId};
25
26/// Errors that can be raised when linking units.
27#[derive(Debug)]
28#[allow(missing_docs)]
29#[non_exhaustive]
30pub enum LinkerError {
31    MissingFunction {
32        hash: Hash,
33        spans: Vec<(Span, SourceId)>,
34    },
35}
36
37impl fmt::Display for LinkerError {
38    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39        match self {
40            LinkerError::MissingFunction { hash, .. } => {
41                write!(f, "Missing function with hash {hash}")
42            }
43        }
44    }
45}
46
47impl core::error::Error for LinkerError {}
48
49/// Instructions from a single source file.
50#[derive(Debug, Default)]
51pub(crate) struct UnitBuilder {
52    /// Registered re-exports.
53    reexports: HashMap<Hash, Hash>,
54    /// Where functions are located in the collection of instructions.
55    functions: hash::Map<UnitFn>,
56    /// Function by address.
57    functions_rev: HashMap<usize, Hash>,
58    /// A static string.
59    static_strings: Vec<Arc<StaticString>>,
60    /// Reverse lookup for static strings.
61    static_string_rev: HashMap<Hash, usize>,
62    /// A static byte string.
63    static_bytes: Vec<Vec<u8>>,
64    /// Reverse lookup for static byte strings.
65    static_bytes_rev: HashMap<Hash, usize>,
66    /// Slots used for object keys.
67    ///
68    /// This is used when an object is used in a pattern match, to avoid having
69    /// to send the collection of keys to the virtual machine.
70    ///
71    /// All keys are sorted with the default string sort.
72    static_object_keys: Vec<Box<[String]>>,
73    /// Used to detect duplicates in the collection of static object keys.
74    static_object_keys_rev: HashMap<Hash, usize>,
75    /// A static string.
76    drop_sets: Vec<Arc<[Address]>>,
77    /// Reverse lookup for drop sets.
78    drop_sets_rev: HashMap<Vec<Address>, usize>,
79    /// Runtime type information for types.
80    rtti: hash::Map<Arc<Rtti>>,
81    /// The current label count.
82    label_count: usize,
83    /// A collection of required function hashes.
84    required_functions: HashMap<Hash, Vec<(Span, SourceId)>>,
85    /// Debug info if available for unit.
86    debug: Option<Box<DebugInfo>>,
87    /// Constant values
88    constants: hash::Map<ConstValueBuf>,
89    /// Hash to identifiers.
90    hash_to_ident: HashMap<Hash, Box<str>>,
91    /// Initializers for static items, indexed by the slot they've been
92    /// assigned.
93    globals: Vec<Option<ConstValueBuf>>,
94    /// Reverse lookup from the type hash of a static item to its slot.
95    globals_rev: hash::Map<usize>,
96}
97
98impl UnitBuilder {
99    /// Construct a new drop set.
100    pub(crate) fn drop_set(&mut self) -> DropSet<'_> {
101        DropSet {
102            builder: self,
103            addresses: Vec::new(),
104        }
105    }
106
107    /// Insert an identifier for debug purposes.
108    pub(crate) fn insert_debug_ident(&mut self, ident: &str) -> alloc::Result<()> {
109        self.hash_to_ident
110            .try_insert(Hash::ident(ident), ident.try_into()?)?;
111        Ok(())
112    }
113
114    /// Convert into a runtime unit, shedding our build metadata in the process.
115    ///
116    /// Returns `None` if the builder is still in use.
117    pub(crate) fn build<S>(mut self, span: Span, storage: S) -> compile::Result<Unit<S>> {
118        if let Some(debug) = &mut self.debug {
119            debug.functions_rev = self.functions_rev;
120            debug.hash_to_ident = self.hash_to_ident;
121        }
122
123        for (from, to) in self.reexports {
124            if let Some(info) = self.functions.get(&to) {
125                let info = *info;
126                if self
127                    .functions
128                    .try_insert(from, info)
129                    .with_span(span)?
130                    .is_some()
131                {
132                    return Err(compile::Error::new(
133                        span,
134                        ErrorKind::FunctionConflictHash { hash: from },
135                    ));
136                }
137                continue;
138            }
139
140            if let Some(value) = self.constants.get(&to) {
141                let const_value = value.try_clone()?;
142
143                if self
144                    .constants
145                    .try_insert(from, const_value)
146                    .with_span(span)?
147                    .is_some()
148                {
149                    return Err(compile::Error::new(
150                        span,
151                        ErrorKind::ConstantConflict { hash: from },
152                    ));
153                }
154
155                continue;
156            }
157
158            return Err(compile::Error::new(
159                span,
160                ErrorKind::MissingFunctionHash { hash: to },
161            ));
162        }
163
164        Ok(Unit::new(
165            storage,
166            self.functions,
167            self.static_strings,
168            self.static_bytes,
169            self.static_object_keys,
170            self.drop_sets,
171            self.rtti,
172            self.debug,
173            self.constants,
174            self.globals,
175            self.globals_rev,
176        ))
177    }
178
179    /// Build a runtime unit out of the builder without consuming it.
180    ///
181    /// This is what makes it possible to execute a unit which is still being
182    /// built, which constant evaluation relies on - it compiles the constant
183    /// into an interior unit and has to run it while the outer compilation is
184    /// still in progress.
185    ///
186    /// Debug information is deliberately not included. The caller of a constant
187    /// evaluation already knows the span it is evaluating, and copying the debug
188    /// info of the interior unit for every evaluation is not worth what it adds
189    /// to the diagnostic.
190    pub(crate) fn snapshot<S>(&self, storage: S) -> alloc::Result<Unit<S>> {
191        Ok(Unit::new(
192            storage,
193            self.functions.try_clone()?,
194            self.static_strings.try_clone()?,
195            self.static_bytes.try_clone()?,
196            self.static_object_keys.try_clone()?,
197            self.drop_sets.try_clone()?,
198            self.rtti.try_clone()?,
199            None,
200            self.constants.try_clone()?,
201            self.globals.try_clone()?,
202            self.globals_rev.try_clone()?,
203        ))
204    }
205
206    /// Get the slot assigned to the static item with the given type hash,
207    /// allocating a new one if this is the first time we see it.
208    ///
209    /// Slots are handed out in the order they are requested, so a static which
210    /// is used before it is built still ends up with a single stable slot.
211    pub(crate) fn global_slot(&mut self, hash: Hash) -> alloc::Result<usize> {
212        if let Some(slot) = self.globals_rev.get(&hash).copied() {
213            return Ok(slot);
214        }
215
216        let slot = self.globals.len();
217        self.globals.try_push(None)?;
218        self.globals_rev.try_insert(hash, slot)?;
219        Ok(slot)
220    }
221
222    /// Record the initializer and the debug name for a static item.
223    pub(crate) fn insert_global(
224        &mut self,
225        hash: Hash,
226        path: &Item,
227        init: Option<ConstValueBuf>,
228        debug_info: bool,
229    ) -> alloc::Result<usize> {
230        let slot = self.global_slot(hash)?;
231
232        if let Some(init) = init {
233            self.globals[slot] = Some(init);
234        }
235
236        if debug_info {
237            let debug = self.debug_mut()?;
238
239            while debug.globals.len() <= slot {
240                debug.globals.try_push(DebugGlobal::new(ItemBuf::new()))?;
241            }
242
243            debug.globals[slot] = DebugGlobal::new(path.try_to_owned()?);
244        }
245
246        Ok(slot)
247    }
248
249    /// Insert a static string and return its associated slot that can later be
250    /// looked up through [lookup_string][Unit::lookup_string].
251    ///
252    /// Only uses up space if the static string is unique.
253    pub(crate) fn new_static_string(
254        &mut self,
255        span: &dyn Spanned,
256        current: &str,
257    ) -> compile::Result<usize> {
258        let current = StaticString::new(current)?;
259        let hash = current.hash();
260
261        if let Some(existing_slot) = self.static_string_rev.get(&hash).copied() {
262            let Some(existing) = self.static_strings.get(existing_slot) else {
263                return Err(compile::Error::new(
264                    span,
265                    ErrorKind::StaticStringMissing {
266                        hash,
267                        slot: existing_slot,
268                    },
269                ));
270            };
271
272            if ***existing != *current {
273                return Err(compile::Error::new(
274                    span,
275                    ErrorKind::StaticStringHashConflict {
276                        hash,
277                        current: (*current).try_clone()?,
278                        existing: (***existing).try_clone()?,
279                    },
280                ));
281            }
282
283            return Ok(existing_slot);
284        }
285
286        let new_slot = self.static_strings.len();
287        self.static_strings.try_push(Arc::try_new(current)?)?;
288        self.static_string_rev.try_insert(hash, new_slot)?;
289        Ok(new_slot)
290    }
291
292    /// Insert a static byte string and return its associated slot that can
293    /// later be looked up through [lookup_bytes][Unit::lookup_bytes].
294    ///
295    /// Only uses up space if the static byte string is unique.
296    pub(crate) fn new_static_bytes(
297        &mut self,
298        span: &dyn Spanned,
299        current: &[u8],
300    ) -> compile::Result<usize> {
301        let hash = Hash::static_bytes(current);
302
303        if let Some(existing_slot) = self.static_bytes_rev.get(&hash).copied() {
304            let existing = self.static_bytes.get(existing_slot).ok_or_else(|| {
305                compile::Error::new(
306                    span,
307                    ErrorKind::StaticBytesMissing {
308                        hash,
309                        slot: existing_slot,
310                    },
311                )
312            })?;
313
314            if &**existing != current {
315                return Err(compile::Error::new(
316                    span,
317                    ErrorKind::StaticBytesHashConflict {
318                        hash,
319                        current: current.try_to_owned()?,
320                        existing: existing.try_clone()?,
321                    },
322                ));
323            }
324
325            return Ok(existing_slot);
326        }
327
328        let new_slot = self.static_bytes.len();
329        self.static_bytes.try_push(current.try_to_owned()?)?;
330        self.static_bytes_rev.try_insert(hash, new_slot)?;
331        Ok(new_slot)
332    }
333
334    /// Insert a new collection of static object keys, or return one already
335    /// existing.
336    pub(crate) fn new_static_object_keys_iter(
337        &mut self,
338        span: &dyn Spanned,
339        current: impl IntoIterator<Item: AsRef<str>>,
340    ) -> compile::Result<usize> {
341        let current = current
342            .into_iter()
343            .map(|s| s.as_ref().try_to_owned())
344            .try_collect::<alloc::Result<Box<_>>>()??;
345
346        self.new_static_object_keys(span, current)
347    }
348
349    /// Insert a new collection of static object keys, or return one already
350    /// existing.
351    pub(crate) fn new_static_object_keys(
352        &mut self,
353        span: &dyn Spanned,
354        current: Box<[String]>,
355    ) -> compile::Result<usize> {
356        let hash = Hash::object_keys(&current[..]);
357
358        if let Some(existing_slot) = self.static_object_keys_rev.get(&hash).copied() {
359            let existing = self.static_object_keys.get(existing_slot).ok_or_else(|| {
360                compile::Error::new(
361                    span,
362                    ErrorKind::StaticObjectKeysMissing {
363                        hash,
364                        slot: existing_slot,
365                    },
366                )
367            })?;
368
369            if *existing != current {
370                return Err(compile::Error::new(
371                    span,
372                    ErrorKind::StaticObjectKeysHashConflict {
373                        hash,
374                        current,
375                        existing: existing.try_clone()?,
376                    },
377                ));
378            }
379
380            return Ok(existing_slot);
381        }
382
383        let new_slot = self.static_object_keys.len();
384        self.static_object_keys.try_push(current)?;
385        self.static_object_keys_rev.try_insert(hash, new_slot)?;
386        Ok(new_slot)
387    }
388
389    /// Declare a new struct.
390    pub(crate) fn insert_meta(
391        &mut self,
392        span: &dyn Spanned,
393        meta: &meta::Meta,
394        pool: &Pool,
395        query: &mut QueryInner,
396        debug_info: bool,
397    ) -> compile::Result<()> {
398        debug_assert_eq! {
399            pool.item_type_hash(meta.item_meta.item),
400            meta.hash,
401        };
402
403        match meta.kind {
404            meta::Kind::Type { .. } => {
405                let rtti = Arc::try_new(Rtti {
406                    kind: RttiKind::Empty,
407                    hash: meta.hash,
408                    variant_hash: Hash::EMPTY,
409                    item: pool.item(meta.item_meta.item).try_to_owned()?,
410                    fields: HashMap::default(),
411                })?;
412
413                self.constants
414                    .try_insert(
415                        Hash::associated_function(meta.hash, &Protocol::INTO_TYPE_NAME),
416                        ConstValueBuf::try_from(rtti.item.try_to_string()?)?,
417                    )
418                    .with_span(span)?;
419
420                if self
421                    .rtti
422                    .try_insert(meta.hash, rtti)
423                    .with_span(span)?
424                    .is_some()
425                {
426                    return Err(compile::Error::new(
427                        span,
428                        ErrorKind::TypeRttiConflict { hash: meta.hash },
429                    ));
430                }
431            }
432            meta::Kind::Struct {
433                fields: meta::Fields::Empty,
434                enum_hash: Hash::EMPTY,
435                ..
436            } => {
437                let info = UnitFn::EmptyStruct { hash: meta.hash };
438
439                let signature = DebugSignature::new(
440                    pool.item(meta.item_meta.item).try_to_owned()?,
441                    DebugArgs::EmptyArgs,
442                );
443
444                let rtti = Arc::try_new(Rtti {
445                    kind: RttiKind::Empty,
446                    hash: meta.hash,
447                    variant_hash: Hash::EMPTY,
448                    item: pool.item(meta.item_meta.item).try_to_owned()?,
449                    fields: HashMap::default(),
450                })?;
451
452                if self
453                    .rtti
454                    .try_insert(meta.hash, rtti)
455                    .with_span(span)?
456                    .is_some()
457                {
458                    return Err(compile::Error::new(
459                        span,
460                        ErrorKind::TypeRttiConflict { hash: meta.hash },
461                    ));
462                }
463
464                if self
465                    .functions
466                    .try_insert(meta.hash, info)
467                    .with_span(span)?
468                    .is_some()
469                {
470                    return Err(compile::Error::new(
471                        span,
472                        ErrorKind::FunctionConflict {
473                            existing: signature,
474                        },
475                    ));
476                }
477
478                self.constants
479                    .try_insert(
480                        Hash::associated_function(meta.hash, &Protocol::INTO_TYPE_NAME),
481                        ConstValueBuf::try_from(signature.path.try_to_string()?)?,
482                    )
483                    .with_span(span)?;
484
485                self.debug_mut()?
486                    .functions
487                    .try_insert(meta.hash, signature)?;
488            }
489            meta::Kind::Struct {
490                fields: meta::Fields::Empty,
491                enum_hash,
492                ..
493            } => {
494                let rtti = Arc::try_new(Rtti {
495                    kind: RttiKind::Empty,
496                    hash: enum_hash,
497                    variant_hash: meta.hash,
498                    item: pool.item(meta.item_meta.item).try_to_owned()?,
499                    fields: HashMap::default(),
500                })?;
501
502                if self
503                    .rtti
504                    .try_insert(meta.hash, rtti)
505                    .with_span(span)?
506                    .is_some()
507                {
508                    return Err(compile::Error::new(
509                        span,
510                        ErrorKind::RttiConflict { hash: meta.hash },
511                    ));
512                }
513
514                let info = UnitFn::EmptyStruct { hash: meta.hash };
515
516                let signature = DebugSignature::new(
517                    pool.item(meta.item_meta.item).try_to_owned()?,
518                    DebugArgs::EmptyArgs,
519                );
520
521                if self
522                    .functions
523                    .try_insert(meta.hash, info)
524                    .with_span(span)?
525                    .is_some()
526                {
527                    return Err(compile::Error::new(
528                        span,
529                        ErrorKind::FunctionConflict {
530                            existing: signature,
531                        },
532                    ));
533                }
534
535                self.debug_mut()?
536                    .functions
537                    .try_insert(meta.hash, signature)?;
538            }
539            meta::Kind::Struct {
540                fields: meta::Fields::Unnamed(args),
541                enum_hash: Hash::EMPTY,
542                ..
543            } => {
544                let info = UnitFn::TupleStruct {
545                    hash: meta.hash,
546                    args,
547                };
548
549                let signature = DebugSignature::new(
550                    pool.item(meta.item_meta.item).try_to_owned()?,
551                    DebugArgs::TupleArgs(args),
552                );
553
554                let rtti = Arc::try_new(Rtti {
555                    kind: RttiKind::Tuple,
556                    hash: meta.hash,
557                    variant_hash: Hash::EMPTY,
558                    item: pool.item(meta.item_meta.item).try_to_owned()?,
559                    fields: HashMap::default(),
560                })?;
561
562                if self
563                    .rtti
564                    .try_insert(meta.hash, rtti)
565                    .with_span(span)?
566                    .is_some()
567                {
568                    return Err(compile::Error::new(
569                        span,
570                        ErrorKind::TypeRttiConflict { hash: meta.hash },
571                    ));
572                }
573
574                if self
575                    .functions
576                    .try_insert(meta.hash, info)
577                    .with_span(span)?
578                    .is_some()
579                {
580                    return Err(compile::Error::new(
581                        span,
582                        ErrorKind::FunctionConflict {
583                            existing: signature,
584                        },
585                    ));
586                }
587
588                self.constants
589                    .try_insert(
590                        Hash::associated_function(meta.hash, &Protocol::INTO_TYPE_NAME),
591                        ConstValueBuf::try_from(signature.path.try_to_string()?)?,
592                    )
593                    .with_span(span)?;
594
595                self.debug_mut()?
596                    .functions
597                    .try_insert(meta.hash, signature)?;
598            }
599            meta::Kind::Struct {
600                fields: meta::Fields::Unnamed(args),
601                enum_hash,
602                ..
603            } => {
604                let rtti = Arc::try_new(Rtti {
605                    kind: RttiKind::Tuple,
606                    hash: enum_hash,
607                    variant_hash: meta.hash,
608                    item: pool.item(meta.item_meta.item).try_to_owned()?,
609                    fields: HashMap::default(),
610                })?;
611
612                if self
613                    .rtti
614                    .try_insert(meta.hash, rtti)
615                    .with_span(span)?
616                    .is_some()
617                {
618                    return Err(compile::Error::new(
619                        span,
620                        ErrorKind::RttiConflict { hash: meta.hash },
621                    ));
622                }
623
624                let info = UnitFn::TupleStruct {
625                    hash: meta.hash,
626                    args,
627                };
628
629                let signature = DebugSignature::new(
630                    pool.item(meta.item_meta.item).try_to_owned()?,
631                    DebugArgs::TupleArgs(args),
632                );
633
634                if self
635                    .functions
636                    .try_insert(meta.hash, info)
637                    .with_span(span)?
638                    .is_some()
639                {
640                    return Err(compile::Error::new(
641                        span,
642                        ErrorKind::FunctionConflict {
643                            existing: signature,
644                        },
645                    ));
646                }
647
648                self.debug_mut()?
649                    .functions
650                    .try_insert(meta.hash, signature)?;
651            }
652            meta::Kind::Struct {
653                fields: meta::Fields::Named(ref named),
654                enum_hash: Hash::EMPTY,
655                ..
656            } => {
657                let rtti = Arc::try_new(Rtti {
658                    kind: RttiKind::Struct,
659                    hash: meta.hash,
660                    variant_hash: Hash::EMPTY,
661                    item: pool.item(meta.item_meta.item).try_to_owned()?,
662                    fields: named.to_fields()?,
663                })?;
664
665                self.constants
666                    .try_insert(
667                        Hash::associated_function(meta.hash, &Protocol::INTO_TYPE_NAME),
668                        ConstValueBuf::try_from(rtti.item.try_to_string()?)?,
669                    )
670                    .with_span(span)?;
671
672                if self
673                    .rtti
674                    .try_insert(meta.hash, rtti)
675                    .with_span(span)?
676                    .is_some()
677                {
678                    return Err(compile::Error::new(
679                        span,
680                        ErrorKind::TypeRttiConflict { hash: meta.hash },
681                    ));
682                }
683            }
684            meta::Kind::Struct {
685                fields: meta::Fields::Named(ref named),
686                enum_hash,
687                ..
688            } => {
689                let rtti = Arc::try_new(Rtti {
690                    kind: RttiKind::Struct,
691                    hash: enum_hash,
692                    variant_hash: meta.hash,
693                    item: pool.item(meta.item_meta.item).try_to_owned()?,
694                    fields: named.to_fields()?,
695                })?;
696
697                if self
698                    .rtti
699                    .try_insert(meta.hash, rtti)
700                    .with_span(span)?
701                    .is_some()
702                {
703                    return Err(compile::Error::new(
704                        span,
705                        ErrorKind::RttiConflict { hash: meta.hash },
706                    ));
707                }
708            }
709            meta::Kind::Enum { .. } => {
710                let name = pool
711                    .item(meta.item_meta.item)
712                    .try_to_string()
713                    .with_span(span)?;
714
715                self.constants
716                    .try_insert(
717                        Hash::associated_function(meta.hash, &Protocol::INTO_TYPE_NAME),
718                        ConstValueBuf::try_from(name)?,
719                    )
720                    .with_span(span)?;
721            }
722            meta::Kind::Const => {
723                let Some(const_value) = query.get_const_value(meta.hash) else {
724                    return Err(compile::Error::msg(
725                        span,
726                        try_format!("Missing constant for hash {}", meta.hash),
727                    ));
728                };
729
730                let value = const_value.try_to_owned().with_span(span)?;
731
732                self.constants
733                    .try_insert(meta.hash, value)
734                    .with_span(span)?;
735            }
736            meta::Kind::Static => {
737                let init = match query.get_static_init(meta.hash) {
738                    Some(init) => Some(init.try_to_owned().with_span(span)?),
739                    None => None,
740                };
741
742                self.insert_global(meta.hash, pool.item(meta.item_meta.item), init, debug_info)
743                    .with_span(span)?;
744            }
745            meta::Kind::Macro => (),
746            meta::Kind::AttributeMacro => (),
747            meta::Kind::Function { .. } => (),
748            meta::Kind::Closure { .. } => (),
749            meta::Kind::AsyncBlock { .. } => (),
750            meta::Kind::ConstFn => (),
751            meta::Kind::Import { .. } => (),
752            meta::Kind::Alias { .. } => (),
753            meta::Kind::Module => (),
754            meta::Kind::Trait => (),
755        }
756
757        Ok(())
758    }
759
760    /// Construct a new empty assembly associated with the current unit.
761    pub(crate) fn new_assembly(&self, location: Location) -> Assembly {
762        Assembly::new(location, self.label_count)
763    }
764
765    /// Register a new function re-export.
766    pub(crate) fn new_function_reexport(
767        &mut self,
768        location: Location,
769        item: &Item,
770        target: &Item,
771    ) -> compile::Result<()> {
772        let hash = Hash::type_hash(item);
773        let target = Hash::type_hash(target);
774
775        if self.reexports.try_insert(hash, target)?.is_some() {
776            return Err(compile::Error::new(
777                location.span,
778                ErrorKind::FunctionReExportConflict { hash },
779            ));
780        }
781
782        Ok(())
783    }
784
785    /// Declare a new instance function at the current instruction pointer.
786    pub(crate) fn new_function(
787        &mut self,
788        location: Location,
789        item: &Item,
790        instance: Option<(Hash, &str)>,
791        args: usize,
792        captures: Option<usize>,
793        assembly: Assembly,
794        call: Call,
795        debug_args: Box<[Box<str>]>,
796        unit_storage: &mut dyn UnitEncoder,
797        size: usize,
798    ) -> compile::Result<()> {
799        tracing::trace!("instance fn: {}", item);
800
801        let offset = unit_storage.offset();
802
803        let info = UnitFn::Offset {
804            offset,
805            call,
806            args,
807            captures,
808        };
809        let signature = DebugSignature::new(item.try_to_owned()?, DebugArgs::Named(debug_args));
810
811        if let Some((type_hash, name)) = instance {
812            let instance_fn = Hash::associated_function(type_hash, name);
813
814            if self
815                .functions
816                .try_insert(instance_fn, info)
817                .with_span(location.span)?
818                .is_some()
819            {
820                return Err(compile::Error::new(
821                    location.span,
822                    ErrorKind::FunctionConflict {
823                        existing: signature,
824                    },
825                ));
826            }
827
828            self.debug_mut()?
829                .functions
830                .try_insert(instance_fn, signature.try_clone()?)?;
831        }
832
833        let hash = Hash::type_hash(item);
834
835        if self
836            .functions
837            .try_insert(hash, info)
838            .with_span(location.span)?
839            .is_some()
840        {
841            return Err(compile::Error::new(
842                location.span,
843                ErrorKind::FunctionConflict {
844                    existing: signature,
845                },
846            ));
847        }
848
849        self.constants
850            .try_insert(
851                Hash::associated_function(hash, &Protocol::INTO_TYPE_NAME),
852                ConstValueBuf::try_from(signature.path.try_to_string().with_span(location.span)?)?,
853            )
854            .with_span(location.span)?;
855
856        self.debug_mut()?.functions.try_insert(hash, signature)?;
857        self.functions_rev.try_insert(offset, hash)?;
858        self.add_assembly(location, assembly, unit_storage, size)?;
859        Ok(())
860    }
861
862    /// Declare a function which only exists inside of the unit it is being
863    /// assembled into, addressed by hash rather than by item.
864    ///
865    /// This is used by constant evaluation, which assembles the constants it
866    /// evaluates into an interior unit. Those functions are never looked up by
867    /// name and never outlive the evaluation, so they carry no debug signature
868    /// and no `INTO_TYPE_NAME` constant.
869    pub(crate) fn new_const_function(
870        &mut self,
871        location: Location,
872        hash: Hash,
873        args: usize,
874        assembly: Assembly,
875        unit_storage: &mut dyn UnitEncoder,
876        size: usize,
877    ) -> compile::Result<()> {
878        let offset = unit_storage.offset();
879
880        let info = UnitFn::Offset {
881            offset,
882            call: Call::Immediate,
883            args,
884            captures: None,
885        };
886
887        if self
888            .functions
889            .try_insert(hash, info)
890            .with_span(location.span)?
891            .is_some()
892        {
893            return Err(compile::Error::new(
894                location.span,
895                ErrorKind::FunctionConflictHash { hash },
896            ));
897        }
898
899        self.add_assembly(location, assembly, unit_storage, size)?;
900        Ok(())
901    }
902
903    /// Try to link the unit with the context, checking that all necessary
904    /// functions are provided.
905    ///
906    /// This can prevent a number of runtime errors, like missing functions.
907    pub(crate) fn link(
908        &mut self,
909        context: &Context,
910        diagnostics: &mut Diagnostics,
911    ) -> alloc::Result<()> {
912        for (hash, spans) in &self.required_functions {
913            if self.functions.get(hash).is_none() && context.lookup_function(*hash).is_none() {
914                diagnostics.error(
915                    SourceId::empty(),
916                    LinkerError::MissingFunction {
917                        hash: *hash,
918                        spans: spans.try_clone()?,
919                    },
920                )?;
921            }
922        }
923
924        Ok(())
925    }
926
927    /// Insert and access debug information.
928    fn debug_mut(&mut self) -> alloc::Result<&mut DebugInfo> {
929        if self.debug.is_none() {
930            self.debug = Some(Box::try_new(DebugInfo::default())?);
931        }
932
933        Ok(self.debug.as_mut().unwrap())
934    }
935
936    /// Translate the given assembly into instructions.
937    fn add_assembly(
938        &mut self,
939        location: Location,
940        assembly: Assembly,
941        storage: &mut dyn UnitEncoder,
942        size: usize,
943    ) -> compile::Result<()> {
944        self.label_count = assembly.label_count;
945
946        storage
947            .encode(Inst::new(inst::Kind::Allocate { size }))
948            .with_span(location.span)?;
949
950        let base = storage.extend_offsets(assembly.labels.len())?;
951
952        self.required_functions
953            .try_extend(assembly.required_functions)?;
954
955        for (offset, (_, labels)) in &assembly.labels {
956            for label in labels {
957                if let Some(jump) = label.jump() {
958                    label.set_jump(storage.label_jump(base, *offset, jump));
959                }
960            }
961        }
962
963        for (pos, (inst, span)) in assembly.instructions.into_iter().enumerate() {
964            let mut comment = String::new();
965
966            let at = storage.offset();
967
968            let mut labels = Vec::new();
969
970            for label in assembly
971                .labels
972                .get(&pos)
973                .map(|e| e.1.as_slice())
974                .unwrap_or_default()
975            {
976                if let Some(index) = label.jump() {
977                    storage.mark_offset(index);
978                }
979
980                labels.try_push(label.to_debug_label())?;
981            }
982
983            let build_label = |label: Label| {
984                label
985                    .jump()
986                    .ok_or(ErrorKind::MissingLabelLocation {
987                        name: label.name,
988                        index: label.index,
989                    })
990                    .with_span(span)
991            };
992
993            match inst {
994                AssemblyInst::Jump { label } => {
995                    write!(comment, "label:{label}")?;
996                    let jump = build_label(label)?;
997                    storage
998                        .encode(Inst::new(inst::Kind::Jump { jump }))
999                        .with_span(span)?;
1000                }
1001                AssemblyInst::JumpIf { addr, label } => {
1002                    write!(comment, "label:{label}")?;
1003                    let jump = build_label(label)?;
1004                    storage
1005                        .encode(Inst::new(inst::Kind::JumpIf { cond: addr, jump }))
1006                        .with_span(span)?;
1007                }
1008                AssemblyInst::JumpIfNot { addr, label } => {
1009                    write!(comment, "label:{label}")?;
1010                    let jump = build_label(label)?;
1011                    storage
1012                        .encode(Inst::new(inst::Kind::JumpIfNot { cond: addr, jump }))
1013                        .with_span(span)?;
1014                }
1015                AssemblyInst::IterNext { addr, label, out } => {
1016                    write!(comment, "label:{label}")?;
1017                    let jump = build_label(label)?;
1018                    storage
1019                        .encode(Inst::new(inst::Kind::IterNext { addr, jump, out }))
1020                        .with_span(span)?;
1021                }
1022                AssemblyInst::Raw { raw } => {
1023                    // Optimization to avoid performing lookups for recursive
1024                    // function calls.
1025                    let kind = match raw {
1026                        inst @ inst::Kind::Call {
1027                            hash,
1028                            addr,
1029                            args,
1030                            out,
1031                        } => {
1032                            if let Some(UnitFn::Offset { offset, call, .. }) =
1033                                self.functions.get(&hash)
1034                            {
1035                                inst::Kind::CallOffset {
1036                                    offset: *offset,
1037                                    call: *call,
1038                                    addr,
1039                                    args,
1040                                    out,
1041                                }
1042                            } else {
1043                                inst
1044                            }
1045                        }
1046                        kind => kind,
1047                    };
1048
1049                    storage.encode(Inst::new(kind)).with_span(span)?;
1050                }
1051            }
1052
1053            if let Some(c) = assembly.comments.get(&pos) {
1054                if !comment.is_empty() {
1055                    comment.try_push_str("; ")?;
1056                }
1057
1058                comment.try_push_str(c)?;
1059            }
1060
1061            let comment = if comment.is_empty() {
1062                None
1063            } else {
1064                Some(comment.try_into()?)
1065            };
1066
1067            self.debug_mut()?.instructions.try_insert(
1068                at,
1069                DebugInst::new(location.source_id, span, comment, labels),
1070            )?;
1071        }
1072
1073        Ok(())
1074    }
1075}
1076
1077/// A set of addresses that should be dropped.
1078pub(crate) struct DropSet<'a> {
1079    builder: &'a mut UnitBuilder,
1080    addresses: Vec<Address>,
1081}
1082
1083impl DropSet<'_> {
1084    /// Construct a new drop set.
1085    pub(crate) fn push(&mut self, addr: Address) -> alloc::Result<()> {
1086        self.addresses.try_push(addr)
1087    }
1088
1089    pub(crate) fn finish(self) -> alloc::Result<Option<usize>> {
1090        if self.addresses.is_empty() {
1091            return Ok(None);
1092        }
1093
1094        if let Some(set) = self.builder.drop_sets_rev.get(&self.addresses) {
1095            return Ok(Some(*set));
1096        }
1097
1098        let set = self.builder.drop_sets.len();
1099
1100        self.builder
1101            .drop_sets_rev
1102            .try_insert(self.addresses.try_clone()?, set)?;
1103        self.builder
1104            .drop_sets
1105            .try_push(Arc::copy_from_slice(&self.addresses[..])?)?;
1106        Ok(Some(set))
1107    }
1108}