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