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, ConstValue, 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<ConstValue>,
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<ConstValue>>,
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    /// Get the slot assigned to the static item with the given type hash,
180    /// allocating a new one if this is the first time we see it.
181    ///
182    /// Slots are handed out in the order they are requested, so a static which
183    /// is used before it is built still ends up with a single stable slot.
184    pub(crate) fn global_slot(&mut self, hash: Hash) -> alloc::Result<usize> {
185        if let Some(slot) = self.globals_rev.get(&hash).copied() {
186            return Ok(slot);
187        }
188
189        let slot = self.globals.len();
190        self.globals.try_push(None)?;
191        self.globals_rev.try_insert(hash, slot)?;
192        Ok(slot)
193    }
194
195    /// Record the initializer and the debug name for a static item.
196    pub(crate) fn insert_global(
197        &mut self,
198        hash: Hash,
199        path: &Item,
200        init: Option<ConstValue>,
201        debug_info: bool,
202    ) -> alloc::Result<usize> {
203        let slot = self.global_slot(hash)?;
204
205        if let Some(init) = init {
206            self.globals[slot] = Some(init);
207        }
208
209        if debug_info {
210            let debug = self.debug_mut()?;
211
212            while debug.globals.len() <= slot {
213                debug.globals.try_push(DebugGlobal::new(ItemBuf::new()))?;
214            }
215
216            debug.globals[slot] = DebugGlobal::new(path.try_to_owned()?);
217        }
218
219        Ok(slot)
220    }
221
222    /// Insert a static string and return its associated slot that can later be
223    /// looked up through [lookup_string][Unit::lookup_string].
224    ///
225    /// Only uses up space if the static string is unique.
226    pub(crate) fn new_static_string(
227        &mut self,
228        span: &dyn Spanned,
229        current: &str,
230    ) -> compile::Result<usize> {
231        let current = StaticString::new(current)?;
232        let hash = current.hash();
233
234        if let Some(existing_slot) = self.static_string_rev.get(&hash).copied() {
235            let Some(existing) = self.static_strings.get(existing_slot) else {
236                return Err(compile::Error::new(
237                    span,
238                    ErrorKind::StaticStringMissing {
239                        hash,
240                        slot: existing_slot,
241                    },
242                ));
243            };
244
245            if ***existing != *current {
246                return Err(compile::Error::new(
247                    span,
248                    ErrorKind::StaticStringHashConflict {
249                        hash,
250                        current: (*current).try_clone()?,
251                        existing: (***existing).try_clone()?,
252                    },
253                ));
254            }
255
256            return Ok(existing_slot);
257        }
258
259        let new_slot = self.static_strings.len();
260        self.static_strings.try_push(Arc::try_new(current)?)?;
261        self.static_string_rev.try_insert(hash, new_slot)?;
262        Ok(new_slot)
263    }
264
265    /// Insert a static byte string and return its associated slot that can
266    /// later be looked up through [lookup_bytes][Unit::lookup_bytes].
267    ///
268    /// Only uses up space if the static byte string is unique.
269    pub(crate) fn new_static_bytes(
270        &mut self,
271        span: &dyn Spanned,
272        current: &[u8],
273    ) -> compile::Result<usize> {
274        let hash = Hash::static_bytes(current);
275
276        if let Some(existing_slot) = self.static_bytes_rev.get(&hash).copied() {
277            let existing = self.static_bytes.get(existing_slot).ok_or_else(|| {
278                compile::Error::new(
279                    span,
280                    ErrorKind::StaticBytesMissing {
281                        hash,
282                        slot: existing_slot,
283                    },
284                )
285            })?;
286
287            if &**existing != current {
288                return Err(compile::Error::new(
289                    span,
290                    ErrorKind::StaticBytesHashConflict {
291                        hash,
292                        current: current.try_to_owned()?,
293                        existing: existing.try_clone()?,
294                    },
295                ));
296            }
297
298            return Ok(existing_slot);
299        }
300
301        let new_slot = self.static_bytes.len();
302        self.static_bytes.try_push(current.try_to_owned()?)?;
303        self.static_bytes_rev.try_insert(hash, new_slot)?;
304        Ok(new_slot)
305    }
306
307    /// Insert a new collection of static object keys, or return one already
308    /// existing.
309    pub(crate) fn new_static_object_keys_iter(
310        &mut self,
311        span: &dyn Spanned,
312        current: impl IntoIterator<Item: AsRef<str>>,
313    ) -> compile::Result<usize> {
314        let current = current
315            .into_iter()
316            .map(|s| s.as_ref().try_to_owned())
317            .try_collect::<alloc::Result<Box<_>>>()??;
318
319        self.new_static_object_keys(span, current)
320    }
321
322    /// Insert a new collection of static object keys, or return one already
323    /// existing.
324    pub(crate) fn new_static_object_keys(
325        &mut self,
326        span: &dyn Spanned,
327        current: Box<[String]>,
328    ) -> compile::Result<usize> {
329        let hash = Hash::object_keys(&current[..]);
330
331        if let Some(existing_slot) = self.static_object_keys_rev.get(&hash).copied() {
332            let existing = self.static_object_keys.get(existing_slot).ok_or_else(|| {
333                compile::Error::new(
334                    span,
335                    ErrorKind::StaticObjectKeysMissing {
336                        hash,
337                        slot: existing_slot,
338                    },
339                )
340            })?;
341
342            if *existing != current {
343                return Err(compile::Error::new(
344                    span,
345                    ErrorKind::StaticObjectKeysHashConflict {
346                        hash,
347                        current,
348                        existing: existing.try_clone()?,
349                    },
350                ));
351            }
352
353            return Ok(existing_slot);
354        }
355
356        let new_slot = self.static_object_keys.len();
357        self.static_object_keys.try_push(current)?;
358        self.static_object_keys_rev.try_insert(hash, new_slot)?;
359        Ok(new_slot)
360    }
361
362    /// Declare a new struct.
363    pub(crate) fn insert_meta(
364        &mut self,
365        span: &dyn Spanned,
366        meta: &meta::Meta,
367        pool: &Pool,
368        query: &mut QueryInner,
369        debug_info: bool,
370    ) -> compile::Result<()> {
371        debug_assert_eq! {
372            pool.item_type_hash(meta.item_meta.item),
373            meta.hash,
374        };
375
376        match meta.kind {
377            meta::Kind::Type { .. } => {
378                let rtti = Arc::try_new(Rtti {
379                    kind: RttiKind::Empty,
380                    hash: meta.hash,
381                    variant_hash: Hash::EMPTY,
382                    item: pool.item(meta.item_meta.item).try_to_owned()?,
383                    fields: HashMap::default(),
384                })?;
385
386                self.constants
387                    .try_insert(
388                        Hash::associated_function(meta.hash, &Protocol::INTO_TYPE_NAME),
389                        ConstValue::try_from(rtti.item.try_to_string()?)?,
390                    )
391                    .with_span(span)?;
392
393                if self
394                    .rtti
395                    .try_insert(meta.hash, rtti)
396                    .with_span(span)?
397                    .is_some()
398                {
399                    return Err(compile::Error::new(
400                        span,
401                        ErrorKind::TypeRttiConflict { hash: meta.hash },
402                    ));
403                }
404            }
405            meta::Kind::Struct {
406                fields: meta::Fields::Empty,
407                enum_hash: Hash::EMPTY,
408                ..
409            } => {
410                let info = UnitFn::EmptyStruct { hash: meta.hash };
411
412                let signature = DebugSignature::new(
413                    pool.item(meta.item_meta.item).try_to_owned()?,
414                    DebugArgs::EmptyArgs,
415                );
416
417                let rtti = Arc::try_new(Rtti {
418                    kind: RttiKind::Empty,
419                    hash: meta.hash,
420                    variant_hash: Hash::EMPTY,
421                    item: pool.item(meta.item_meta.item).try_to_owned()?,
422                    fields: HashMap::default(),
423                })?;
424
425                if self
426                    .rtti
427                    .try_insert(meta.hash, rtti)
428                    .with_span(span)?
429                    .is_some()
430                {
431                    return Err(compile::Error::new(
432                        span,
433                        ErrorKind::TypeRttiConflict { hash: meta.hash },
434                    ));
435                }
436
437                if self
438                    .functions
439                    .try_insert(meta.hash, info)
440                    .with_span(span)?
441                    .is_some()
442                {
443                    return Err(compile::Error::new(
444                        span,
445                        ErrorKind::FunctionConflict {
446                            existing: signature,
447                        },
448                    ));
449                }
450
451                self.constants
452                    .try_insert(
453                        Hash::associated_function(meta.hash, &Protocol::INTO_TYPE_NAME),
454                        ConstValue::try_from(signature.path.try_to_string()?)?,
455                    )
456                    .with_span(span)?;
457
458                self.debug_mut()?
459                    .functions
460                    .try_insert(meta.hash, signature)?;
461            }
462            meta::Kind::Struct {
463                fields: meta::Fields::Empty,
464                enum_hash,
465                ..
466            } => {
467                let rtti = Arc::try_new(Rtti {
468                    kind: RttiKind::Empty,
469                    hash: enum_hash,
470                    variant_hash: meta.hash,
471                    item: pool.item(meta.item_meta.item).try_to_owned()?,
472                    fields: HashMap::default(),
473                })?;
474
475                if self
476                    .rtti
477                    .try_insert(meta.hash, rtti)
478                    .with_span(span)?
479                    .is_some()
480                {
481                    return Err(compile::Error::new(
482                        span,
483                        ErrorKind::RttiConflict { hash: meta.hash },
484                    ));
485                }
486
487                let info = UnitFn::EmptyStruct { hash: meta.hash };
488
489                let signature = DebugSignature::new(
490                    pool.item(meta.item_meta.item).try_to_owned()?,
491                    DebugArgs::EmptyArgs,
492                );
493
494                if self
495                    .functions
496                    .try_insert(meta.hash, info)
497                    .with_span(span)?
498                    .is_some()
499                {
500                    return Err(compile::Error::new(
501                        span,
502                        ErrorKind::FunctionConflict {
503                            existing: signature,
504                        },
505                    ));
506                }
507
508                self.debug_mut()?
509                    .functions
510                    .try_insert(meta.hash, signature)?;
511            }
512            meta::Kind::Struct {
513                fields: meta::Fields::Unnamed(args),
514                enum_hash: Hash::EMPTY,
515                ..
516            } => {
517                let info = UnitFn::TupleStruct {
518                    hash: meta.hash,
519                    args,
520                };
521
522                let signature = DebugSignature::new(
523                    pool.item(meta.item_meta.item).try_to_owned()?,
524                    DebugArgs::TupleArgs(args),
525                );
526
527                let rtti = Arc::try_new(Rtti {
528                    kind: RttiKind::Tuple,
529                    hash: meta.hash,
530                    variant_hash: Hash::EMPTY,
531                    item: pool.item(meta.item_meta.item).try_to_owned()?,
532                    fields: HashMap::default(),
533                })?;
534
535                if self
536                    .rtti
537                    .try_insert(meta.hash, rtti)
538                    .with_span(span)?
539                    .is_some()
540                {
541                    return Err(compile::Error::new(
542                        span,
543                        ErrorKind::TypeRttiConflict { hash: meta.hash },
544                    ));
545                }
546
547                if self
548                    .functions
549                    .try_insert(meta.hash, info)
550                    .with_span(span)?
551                    .is_some()
552                {
553                    return Err(compile::Error::new(
554                        span,
555                        ErrorKind::FunctionConflict {
556                            existing: signature,
557                        },
558                    ));
559                }
560
561                self.constants
562                    .try_insert(
563                        Hash::associated_function(meta.hash, &Protocol::INTO_TYPE_NAME),
564                        ConstValue::try_from(signature.path.try_to_string()?)?,
565                    )
566                    .with_span(span)?;
567
568                self.debug_mut()?
569                    .functions
570                    .try_insert(meta.hash, signature)?;
571            }
572            meta::Kind::Struct {
573                fields: meta::Fields::Unnamed(args),
574                enum_hash,
575                ..
576            } => {
577                let rtti = Arc::try_new(Rtti {
578                    kind: RttiKind::Tuple,
579                    hash: enum_hash,
580                    variant_hash: meta.hash,
581                    item: pool.item(meta.item_meta.item).try_to_owned()?,
582                    fields: HashMap::default(),
583                })?;
584
585                if self
586                    .rtti
587                    .try_insert(meta.hash, rtti)
588                    .with_span(span)?
589                    .is_some()
590                {
591                    return Err(compile::Error::new(
592                        span,
593                        ErrorKind::RttiConflict { hash: meta.hash },
594                    ));
595                }
596
597                let info = UnitFn::TupleStruct {
598                    hash: meta.hash,
599                    args,
600                };
601
602                let signature = DebugSignature::new(
603                    pool.item(meta.item_meta.item).try_to_owned()?,
604                    DebugArgs::TupleArgs(args),
605                );
606
607                if self
608                    .functions
609                    .try_insert(meta.hash, info)
610                    .with_span(span)?
611                    .is_some()
612                {
613                    return Err(compile::Error::new(
614                        span,
615                        ErrorKind::FunctionConflict {
616                            existing: signature,
617                        },
618                    ));
619                }
620
621                self.debug_mut()?
622                    .functions
623                    .try_insert(meta.hash, signature)?;
624            }
625            meta::Kind::Struct {
626                fields: meta::Fields::Named(ref named),
627                enum_hash: Hash::EMPTY,
628                ..
629            } => {
630                let rtti = Arc::try_new(Rtti {
631                    kind: RttiKind::Struct,
632                    hash: meta.hash,
633                    variant_hash: Hash::EMPTY,
634                    item: pool.item(meta.item_meta.item).try_to_owned()?,
635                    fields: named.to_fields()?,
636                })?;
637
638                self.constants
639                    .try_insert(
640                        Hash::associated_function(meta.hash, &Protocol::INTO_TYPE_NAME),
641                        ConstValue::try_from(rtti.item.try_to_string()?)?,
642                    )
643                    .with_span(span)?;
644
645                if self
646                    .rtti
647                    .try_insert(meta.hash, rtti)
648                    .with_span(span)?
649                    .is_some()
650                {
651                    return Err(compile::Error::new(
652                        span,
653                        ErrorKind::TypeRttiConflict { hash: meta.hash },
654                    ));
655                }
656            }
657            meta::Kind::Struct {
658                fields: meta::Fields::Named(ref named),
659                enum_hash,
660                ..
661            } => {
662                let rtti = Arc::try_new(Rtti {
663                    kind: RttiKind::Struct,
664                    hash: enum_hash,
665                    variant_hash: meta.hash,
666                    item: pool.item(meta.item_meta.item).try_to_owned()?,
667                    fields: named.to_fields()?,
668                })?;
669
670                if self
671                    .rtti
672                    .try_insert(meta.hash, rtti)
673                    .with_span(span)?
674                    .is_some()
675                {
676                    return Err(compile::Error::new(
677                        span,
678                        ErrorKind::RttiConflict { hash: meta.hash },
679                    ));
680                }
681            }
682            meta::Kind::Enum { .. } => {
683                let name = pool
684                    .item(meta.item_meta.item)
685                    .try_to_string()
686                    .with_span(span)?;
687
688                self.constants
689                    .try_insert(
690                        Hash::associated_function(meta.hash, &Protocol::INTO_TYPE_NAME),
691                        ConstValue::try_from(name)?,
692                    )
693                    .with_span(span)?;
694            }
695            meta::Kind::Const => {
696                let Some(const_value) = query.get_const_value(meta.hash) else {
697                    return Err(compile::Error::msg(
698                        span,
699                        try_format!("Missing constant for hash {}", meta.hash),
700                    ));
701                };
702
703                let value = const_value.try_clone().with_span(span)?;
704
705                self.constants
706                    .try_insert(meta.hash, value)
707                    .with_span(span)?;
708            }
709            meta::Kind::Static => {
710                let init = match query.get_static_init(meta.hash) {
711                    Some(init) => Some(init.try_clone().with_span(span)?),
712                    None => None,
713                };
714
715                self.insert_global(meta.hash, pool.item(meta.item_meta.item), init, debug_info)
716                    .with_span(span)?;
717            }
718            meta::Kind::Macro => (),
719            meta::Kind::AttributeMacro => (),
720            meta::Kind::Function { .. } => (),
721            meta::Kind::Closure { .. } => (),
722            meta::Kind::AsyncBlock { .. } => (),
723            meta::Kind::ConstFn => (),
724            meta::Kind::Import { .. } => (),
725            meta::Kind::Alias { .. } => (),
726            meta::Kind::Module => (),
727            meta::Kind::Trait => (),
728        }
729
730        Ok(())
731    }
732
733    /// Construct a new empty assembly associated with the current unit.
734    pub(crate) fn new_assembly(&self, location: Location) -> Assembly {
735        Assembly::new(location, self.label_count)
736    }
737
738    /// Register a new function re-export.
739    pub(crate) fn new_function_reexport(
740        &mut self,
741        location: Location,
742        item: &Item,
743        target: &Item,
744    ) -> compile::Result<()> {
745        let hash = Hash::type_hash(item);
746        let target = Hash::type_hash(target);
747
748        if self.reexports.try_insert(hash, target)?.is_some() {
749            return Err(compile::Error::new(
750                location.span,
751                ErrorKind::FunctionReExportConflict { hash },
752            ));
753        }
754
755        Ok(())
756    }
757
758    /// Declare a new instance function at the current instruction pointer.
759    pub(crate) fn new_function(
760        &mut self,
761        location: Location,
762        item: &Item,
763        instance: Option<(Hash, &str)>,
764        args: usize,
765        captures: Option<usize>,
766        assembly: Assembly,
767        call: Call,
768        debug_args: Box<[Box<str>]>,
769        unit_storage: &mut dyn UnitEncoder,
770        size: usize,
771    ) -> compile::Result<()> {
772        tracing::trace!("instance fn: {}", item);
773
774        let offset = unit_storage.offset();
775
776        let info = UnitFn::Offset {
777            offset,
778            call,
779            args,
780            captures,
781        };
782        let signature = DebugSignature::new(item.try_to_owned()?, DebugArgs::Named(debug_args));
783
784        if let Some((type_hash, name)) = instance {
785            let instance_fn = Hash::associated_function(type_hash, name);
786
787            if self
788                .functions
789                .try_insert(instance_fn, info)
790                .with_span(location.span)?
791                .is_some()
792            {
793                return Err(compile::Error::new(
794                    location.span,
795                    ErrorKind::FunctionConflict {
796                        existing: signature,
797                    },
798                ));
799            }
800
801            self.debug_mut()?
802                .functions
803                .try_insert(instance_fn, signature.try_clone()?)?;
804        }
805
806        let hash = Hash::type_hash(item);
807
808        if self
809            .functions
810            .try_insert(hash, info)
811            .with_span(location.span)?
812            .is_some()
813        {
814            return Err(compile::Error::new(
815                location.span,
816                ErrorKind::FunctionConflict {
817                    existing: signature,
818                },
819            ));
820        }
821
822        self.constants
823            .try_insert(
824                Hash::associated_function(hash, &Protocol::INTO_TYPE_NAME),
825                ConstValue::try_from(signature.path.try_to_string().with_span(location.span)?)?,
826            )
827            .with_span(location.span)?;
828
829        self.debug_mut()?.functions.try_insert(hash, signature)?;
830        self.functions_rev.try_insert(offset, hash)?;
831        self.add_assembly(location, assembly, unit_storage, size)?;
832        Ok(())
833    }
834
835    /// Try to link the unit with the context, checking that all necessary
836    /// functions are provided.
837    ///
838    /// This can prevent a number of runtime errors, like missing functions.
839    pub(crate) fn link(
840        &mut self,
841        context: &Context,
842        diagnostics: &mut Diagnostics,
843    ) -> alloc::Result<()> {
844        for (hash, spans) in &self.required_functions {
845            if self.functions.get(hash).is_none() && context.lookup_function(*hash).is_none() {
846                diagnostics.error(
847                    SourceId::empty(),
848                    LinkerError::MissingFunction {
849                        hash: *hash,
850                        spans: spans.try_clone()?,
851                    },
852                )?;
853            }
854        }
855
856        Ok(())
857    }
858
859    /// Insert and access debug information.
860    fn debug_mut(&mut self) -> alloc::Result<&mut DebugInfo> {
861        if self.debug.is_none() {
862            self.debug = Some(Box::try_new(DebugInfo::default())?);
863        }
864
865        Ok(self.debug.as_mut().unwrap())
866    }
867
868    /// Translate the given assembly into instructions.
869    fn add_assembly(
870        &mut self,
871        location: Location,
872        assembly: Assembly,
873        storage: &mut dyn UnitEncoder,
874        size: usize,
875    ) -> compile::Result<()> {
876        self.label_count = assembly.label_count;
877
878        storage
879            .encode(Inst::new(inst::Kind::Allocate { size }))
880            .with_span(location.span)?;
881
882        let base = storage.extend_offsets(assembly.labels.len())?;
883
884        self.required_functions
885            .try_extend(assembly.required_functions)?;
886
887        for (offset, (_, labels)) in &assembly.labels {
888            for label in labels {
889                if let Some(jump) = label.jump() {
890                    label.set_jump(storage.label_jump(base, *offset, jump));
891                }
892            }
893        }
894
895        for (pos, (inst, span)) in assembly.instructions.into_iter().enumerate() {
896            let mut comment = String::new();
897
898            let at = storage.offset();
899
900            let mut labels = Vec::new();
901
902            for label in assembly
903                .labels
904                .get(&pos)
905                .map(|e| e.1.as_slice())
906                .unwrap_or_default()
907            {
908                if let Some(index) = label.jump() {
909                    storage.mark_offset(index);
910                }
911
912                labels.try_push(label.to_debug_label())?;
913            }
914
915            let build_label = |label: Label| {
916                label
917                    .jump()
918                    .ok_or(ErrorKind::MissingLabelLocation {
919                        name: label.name,
920                        index: label.index,
921                    })
922                    .with_span(span)
923            };
924
925            match inst {
926                AssemblyInst::Jump { label } => {
927                    write!(comment, "label:{label}")?;
928                    let jump = build_label(label)?;
929                    storage
930                        .encode(Inst::new(inst::Kind::Jump { jump }))
931                        .with_span(span)?;
932                }
933                AssemblyInst::JumpIf { addr, label } => {
934                    write!(comment, "label:{label}")?;
935                    let jump = build_label(label)?;
936                    storage
937                        .encode(Inst::new(inst::Kind::JumpIf { cond: addr, jump }))
938                        .with_span(span)?;
939                }
940                AssemblyInst::JumpIfNot { addr, label } => {
941                    write!(comment, "label:{label}")?;
942                    let jump = build_label(label)?;
943                    storage
944                        .encode(Inst::new(inst::Kind::JumpIfNot { cond: addr, jump }))
945                        .with_span(span)?;
946                }
947                AssemblyInst::IterNext { addr, label, out } => {
948                    write!(comment, "label:{label}")?;
949                    let jump = build_label(label)?;
950                    storage
951                        .encode(Inst::new(inst::Kind::IterNext { addr, jump, out }))
952                        .with_span(span)?;
953                }
954                AssemblyInst::Raw { raw } => {
955                    // Optimization to avoid performing lookups for recursive
956                    // function calls.
957                    let kind = match raw {
958                        inst @ inst::Kind::Call {
959                            hash,
960                            addr,
961                            args,
962                            out,
963                        } => {
964                            if let Some(UnitFn::Offset { offset, call, .. }) =
965                                self.functions.get(&hash)
966                            {
967                                inst::Kind::CallOffset {
968                                    offset: *offset,
969                                    call: *call,
970                                    addr,
971                                    args,
972                                    out,
973                                }
974                            } else {
975                                inst
976                            }
977                        }
978                        kind => kind,
979                    };
980
981                    storage.encode(Inst::new(kind)).with_span(span)?;
982                }
983            }
984
985            if let Some(c) = assembly.comments.get(&pos) {
986                if !comment.is_empty() {
987                    comment.try_push_str("; ")?;
988                }
989
990                comment.try_push_str(c)?;
991            }
992
993            let comment = if comment.is_empty() {
994                None
995            } else {
996                Some(comment.try_into()?)
997            };
998
999            self.debug_mut()?.instructions.try_insert(
1000                at,
1001                DebugInst::new(location.source_id, span, comment, labels),
1002            )?;
1003        }
1004
1005        Ok(())
1006    }
1007}
1008
1009/// A set of addresses that should be dropped.
1010pub(crate) struct DropSet<'a> {
1011    builder: &'a mut UnitBuilder,
1012    addresses: Vec<Address>,
1013}
1014
1015impl DropSet<'_> {
1016    /// Construct a new drop set.
1017    pub(crate) fn push(&mut self, addr: Address) -> alloc::Result<()> {
1018        self.addresses.try_push(addr)
1019    }
1020
1021    pub(crate) fn finish(self) -> alloc::Result<Option<usize>> {
1022        if self.addresses.is_empty() {
1023            return Ok(None);
1024        }
1025
1026        if let Some(set) = self.builder.drop_sets_rev.get(&self.addresses) {
1027            return Ok(Some(*set));
1028        }
1029
1030        let set = self.builder.drop_sets.len();
1031
1032        self.builder
1033            .drop_sets_rev
1034            .try_insert(self.addresses.try_clone()?, set)?;
1035        self.builder
1036            .drop_sets
1037            .try_push(Arc::copy_from_slice(&self.addresses[..])?)?;
1038        Ok(Some(set))
1039    }
1040}