midenc_hir/
globals.rs

1use alloc::{alloc::Layout, collections::BTreeMap, sync::Arc};
2use core::{
3    fmt::{self, Write},
4    hash::{Hash, Hasher},
5};
6
7use cranelift_entity::entity_impl;
8use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink};
9
10use crate::{
11    diagnostics::{miette, Diagnostic, Spanned},
12    *,
13};
14
15/// The policy to apply to a global variable (or function) when linking
16/// together a program during code generation.
17///
18/// Miden doesn't (currently) have a notion of a symbol table for things like global variables.
19/// At runtime, there are not actually symbols at all in any familiar sense, instead functions,
20/// being the only entities with a formal identity in MASM, are either inlined at all their call
21/// sites, or are referenced by the hash of their MAST root, to be unhashed at runtime if the call
22/// is executed.
23///
24/// Because of this, and because we cannot perform linking ourselves (we must emit separate modules,
25/// and leave it up to the VM to link them into the MAST), there are limits to what we can do in
26/// terms of linking function symbols. We essentially just validate that given a set of modules in
27/// a [Program], that there are no invalid references across modules to symbols which either don't
28/// exist, or which exist, but have internal linkage.
29///
30/// However, with global variables, we have a bit more freedom, as it is a concept that we are
31/// completely inventing from whole cloth without explicit support from the VM or Miden Assembly.
32/// In short, when we compile a [Program] to MASM, we first gather together all of the global
33/// variables into a program-wide table, merging and garbage collecting as appropriate, and updating
34/// all references to them in each module. This global variable table is then assumed to be laid out
35/// in memory starting at the base of the linear memory address space in the same order, with
36/// appropriate padding to ensure accesses are aligned. Then, when emitting MASM instructions which
37/// reference global values, we use the layout information to derive the address where that global
38/// value is allocated.
39///
40/// This has some downsides however, the biggest of which is that we can't prevent someone from
41/// loading modules generated from a [Program] with either their own hand-written modules, or
42/// even with modules from another [Program]. In such cases, assumptions about the allocation of
43/// linear memory from different sets of modules will almost certainly lead to undefined behavior.
44/// In the future, we hope to have a better solution to this problem, preferably one involving
45/// native support from the Miden VM itself. For now though, we're working with what we've got.
46#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
47#[cfg_attr(
48    feature = "serde",
49    derive(serde_repr::Deserialize_repr, serde_repr::Serialize_repr)
50)]
51#[repr(u8)]
52pub enum Linkage {
53    /// This symbol is only visible in the containing module.
54    ///
55    /// Internal symbols may be renamed to avoid collisions
56    ///
57    /// Unreferenced internal symbols can be discarded at link time.
58    Internal,
59    /// This symbol will be linked using the "one definition rule", i.e. symbols with
60    /// the same name, type, and linkage will be merged into a single definition.
61    ///
62    /// Unlike `internal` linkage, unreferenced `odr` symbols cannot be discarded.
63    ///
64    /// NOTE: `odr` symbols cannot satisfy external symbol references
65    Odr,
66    /// This symbol is visible externally, and can be used to resolve external symbol references.
67    #[default]
68    External,
69}
70impl fmt::Display for Linkage {
71    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72        match self {
73            Self::Internal => f.write_str("internal"),
74            Self::Odr => f.write_str("odr"),
75            Self::External => f.write_str("external"),
76        }
77    }
78}
79
80intrusive_adapter!(pub GlobalVariableAdapter = UnsafeRef<GlobalVariableData>: GlobalVariableData { link: LinkedListLink });
81
82/// This error is raised when attempting to declare [GlobalVariableData]
83/// with a conflicting symbol name and/or linkage.
84///
85/// For example, two global variables with the same name, but differing
86/// types will result in this error, as there is no way to resolve the
87/// conflict.
88#[derive(Debug, thiserror::Error, Diagnostic)]
89pub enum GlobalVariableError {
90    /// There are multiple conflicting definitions of the given global symbol
91    #[error(
92        "invalid global variable: there are multiple conflicting definitions for symbol '{0}'"
93    )]
94    #[diagnostic()]
95    NameConflict(Ident),
96    /// An attempt was made to set the initializer for a global that already has one
97    #[error("cannot set an initializer for '{0}', it is already initialized")]
98    #[diagnostic()]
99    AlreadyInitialized(Ident),
100    /// The initializer data is invalid for the declared type of the given global, e.g. size
101    /// mismatch.
102    #[error(
103        "invalid global variable initializer for '{0}': the data does not match the declared type"
104    )]
105    #[diagnostic()]
106    InvalidInit(Ident),
107}
108
109/// Describes the way in which global variable conflicts will be handled
110#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
111pub enum ConflictResolutionStrategy {
112    /// Do not attempt to resolve conflicts
113    ///
114    /// NOTE: This does not change the behavior of "one definition rule" linkage,
115    /// when the globals have identical definitions.
116    None,
117    /// Attempt to resolve conflicts by renaming symbols with `internal` linkage.
118    #[default]
119    Rename,
120}
121
122/// This table is used to lay out and link together global variables for a [Program].
123///
124/// See the docs for [Linkage], [GlobalVariableData], and [GlobalVariableTable::declare] for more
125/// details.
126pub struct GlobalVariableTable {
127    layout: LinkedList<GlobalVariableAdapter>,
128    names: BTreeMap<Ident, GlobalVariable>,
129    arena: ArenaMap<GlobalVariable, GlobalVariableData>,
130    data: ConstantPool,
131    next_unique_id: usize,
132    conflict_strategy: ConflictResolutionStrategy,
133}
134impl Default for GlobalVariableTable {
135    fn default() -> Self {
136        Self::new(Default::default())
137    }
138}
139impl GlobalVariableTable {
140    pub fn new(conflict_strategy: ConflictResolutionStrategy) -> Self {
141        Self {
142            layout: Default::default(),
143            names: Default::default(),
144            arena: Default::default(),
145            data: ConstantPool::default(),
146            next_unique_id: 0,
147            conflict_strategy,
148        }
149    }
150
151    /// Returns the number of global variables in this table
152    pub fn len(&self) -> usize {
153        self.layout.iter().count()
154    }
155
156    /// Returns true if the global variable table is empty
157    pub fn is_empty(&self) -> bool {
158        self.layout.is_empty()
159    }
160
161    /// Get a double-ended iterator over the current table layout
162    pub fn iter<'a, 'b: 'a>(
163        &'b self,
164    ) -> intrusive_collections::linked_list::Iter<'a, GlobalVariableAdapter> {
165        self.layout.iter()
166    }
167
168    /// Returns true if a global variable with `name` has been declared
169    pub fn exists(&self, name: Ident) -> bool {
170        self.names.contains_key(&name)
171    }
172
173    /// Looks up a [GlobalVariable] by name.
174    pub fn find(&self, name: Ident) -> Option<GlobalVariable> {
175        self.names.get(&name).copied()
176    }
177
178    /// Gets the data associated with the given [GlobalVariable]
179    pub fn get(&self, id: GlobalVariable) -> &GlobalVariableData {
180        &self.arena[id]
181    }
182
183    /// Checks if the given `id` can be found in this table
184    pub fn contains_key(&self, id: GlobalVariable) -> bool {
185        self.arena.contains(id)
186    }
187
188    /// Removes the global variable associated with `id` from this table
189    ///
190    /// The actual definition remains behind, in order to ensure that `id`
191    /// remains valid should there be any other outstanding references,
192    /// however the data is removed from the layout, and will not be
193    /// seen when traversing the table.
194    pub fn remove(&mut self, id: GlobalVariable) {
195        let mut cursor = self.layout.front_mut();
196        while let Some(gv) = cursor.get() {
197            if gv.id == id {
198                cursor.remove();
199                return;
200            }
201
202            cursor.move_next();
203        }
204    }
205
206    /// Computes the total size in bytes of the table, as it is currently laid out.
207    pub fn size_in_bytes(&self) -> usize {
208        // We mimic the allocation process here, by visiting each
209        // global variable, padding the current heap pointer as necessary
210        // to provide the necessary minimum alignment for the value, and
211        // then bumping it by the size of the value itself.
212        //
213        // At the end, the effective address of the pointer is the total
214        // size in bytes of the allocation
215        let mut size = 0;
216        for gv in self.layout.iter() {
217            let layout = gv.layout();
218            size += layout.size().align_up(layout.align());
219        }
220        size
221    }
222
223    /// Computes the offset, in bytes, of the given [GlobalVariable] from the
224    /// start of the segment in which globals are allocated, assuming that the
225    /// layout of the global variable table up to and including `id` remains
226    /// unchanged.
227    ///
228    /// # Safety
229    ///
230    /// This should only be used once all data segments and global variables have
231    /// been declared, and the layout of the table has been decided. It is technically
232    /// safe to use offsets obtained before all global variables are declared, _IF_ the
233    /// data segments and global variable layout up to and including those global variables
234    /// remains unchanged after that point.
235    ///
236    /// If the offset for a given global variable is obtained, and the heap layout is
237    /// subsequently changed in such a way that the original offset is no longer
238    /// accurate, bad things will happen.
239    pub unsafe fn offset_of(&self, id: GlobalVariable) -> u32 {
240        let mut size = 0usize;
241        for gv in self.layout.iter() {
242            let layout = gv.layout();
243            let align_offset = layout.size().align_offset(layout.align());
244            size += align_offset;
245
246            // If the current variable is the one we're after,
247            // the aligned address is the offset to the start
248            // of the allocation, so we're done
249            if gv.id == id {
250                break;
251            }
252
253            size += layout.size();
254        }
255        size.try_into().expect("data segment table is invalid")
256    }
257
258    /// Get the constant data associated with `id`
259    pub fn get_constant(&self, id: Constant) -> Arc<ConstantData> {
260        self.data.get(id)
261    }
262
263    /// Inserts the given constant data into this table without allocating a global
264    pub fn insert_constant(&mut self, data: ConstantData) -> Constant {
265        self.data.insert(data)
266    }
267
268    /// Inserts the given constant data into this table without allocating a global
269    pub fn insert_refcounted_constant(&mut self, data: Arc<ConstantData>) -> Constant {
270        self.data.insert_arc(data)
271    }
272
273    /// Returns true if the given constant data is in the constant pool
274    pub fn contains_constant(&self, data: &ConstantData) -> bool {
275        self.data.contains(data)
276    }
277
278    /// Traverse all of the constants in the table
279    #[inline]
280    pub fn constants(&self) -> impl Iterator<Item = (Constant, Arc<ConstantData>)> + '_ {
281        self.data.iter()
282    }
283
284    /// Returns true if the table has constant data stored
285    pub fn has_constants(&self) -> bool {
286        !self.data.is_empty()
287    }
288
289    /// Declares a new global variable with the given symbol name, type, linkage, and optional
290    /// initializer.
291    ///
292    /// If successful, `Ok` is returned, with the [GlobalVariable] corresponding to the data for the
293    /// symbol.
294    ///
295    /// Returns an error if the specification of the global is invalid in any way, or the
296    /// declaration conflicts with a previous declaration of the same name.
297    ///
298    /// NOTE: While similar to `try_insert`, a key difference is that `try_declare` does not attempt
299    /// to resolve conflicts. If the given name has been previously declared, and the
300    /// declarations are not identical, then an error will be returned. This is because conflict
301    /// resolution is a process performed when linking together modules. Declaring globals is
302    /// done during the initial construction of a module, where any attempt to rename a global
303    /// variable locally would cause unexpected issues as references to that global are emitted.
304    /// Once a module is constructed, globals it declares with internal linkage can be renamed
305    /// freely, as the name is no longer significant.
306    pub fn declare(
307        &mut self,
308        name: Ident,
309        ty: Type,
310        linkage: Linkage,
311        init: Option<ConstantData>,
312    ) -> Result<GlobalVariable, GlobalVariableError> {
313        assert_ne!(
314            name.as_symbol(),
315            symbols::Empty,
316            "global variable declarations require a non-empty symbol name"
317        );
318
319        // Validate the initializer
320        let init = match init {
321            None => None,
322            Some(init) => {
323                let layout = ty.layout();
324                if init.len() > layout.size() {
325                    return Err(GlobalVariableError::InvalidInit(name));
326                }
327                Some(self.data.insert(init))
328            }
329        };
330
331        let data = GlobalVariableData {
332            link: Default::default(),
333            id: Default::default(),
334            name,
335            ty,
336            linkage,
337            init,
338        };
339
340        // If the symbol is already declared, but the declarations are compatible, then
341        // return the id of the existing declaration. If the declarations are incompatible,
342        // then we raise an error.
343        //
344        // If the symbol is not declared yet, proceed with insertion.
345        if let Some(gv) = self.names.get(&data.name).copied() {
346            if data.is_compatible_with(&self.arena[gv]) {
347                // If the declarations are compatible, and the new declaration has an initializer,
348                // then the previous declaration must either have no initializer, or the same one,
349                // but we want to make sure that the initializer is set if not already.
350                if data.init.is_some() {
351                    self.arena[gv].init = data.init;
352                }
353                Ok(gv)
354            } else {
355                Err(GlobalVariableError::NameConflict(data.name))
356            }
357        } else {
358            Ok(unsafe { self.insert(data) })
359        }
360    }
361
362    /// Attempt to insert the given [GlobalVariableData] into this table.
363    ///
364    /// Returns the id of the global variable in the table, along with a flag indicating whether the
365    /// global symbol was renamed to resolve a conflict with an existing symbol. The caller is
366    /// expected to handle such renames so that any references to the original name that are
367    /// affected can be updated.
368    ///
369    /// If there was an unresolvable conflict, an error will be returned.
370    pub fn try_insert(
371        &mut self,
372        mut data: GlobalVariableData,
373    ) -> Result<(GlobalVariable, bool), GlobalVariableError> {
374        assert_ne!(
375            data.name.as_symbol(),
376            symbols::Empty,
377            "global variable declarations require a non-empty symbol name"
378        );
379
380        if let Some(gv) = self.names.get(&data.name).copied() {
381            // The symbol is already declared, check to see if they are compatible
382            if data.is_compatible_with(&self.arena[gv]) {
383                // If the declarations are compatible, and the new declaration has an initializer,
384                // then the previous declaration must either have no initializer, or the same one,
385                // but we make sure that the initializer is set.
386                if data.init.is_some() {
387                    self.arena[gv].init = data.init;
388                }
389                return Ok((gv, false));
390            }
391
392            // Otherwise, the declarations conflict, but depending on the conflict resolution
393            // strategy, we may yet be able to proceed.
394            let rename_internal_symbols =
395                matches!(self.conflict_strategy, ConflictResolutionStrategy::Rename);
396            match data.linkage {
397                // Conflicting declarations with internal linkage can be resolved by renaming
398                Linkage::Internal if rename_internal_symbols => {
399                    let mut generated = String::from(data.name.as_str());
400                    let original_len = generated.len();
401                    loop {
402                        // Allocate a new unique integer value to mix into the hash
403                        let unique_id = self.next_unique_id;
404                        self.next_unique_id += 1;
405                        // Calculate the hash of the global variable data
406                        let mut hasher = rustc_hash::FxHasher::default();
407                        data.hash(&mut hasher);
408                        unique_id.hash(&mut hasher);
409                        let hash = hasher.finish();
410                        // Append `.<hash>` as a suffix to the original symbol name
411                        write!(&mut generated, ".{:x}", hash)
412                            .expect("failed to write unique suffix to global variable name");
413                        // If by some stroke of bad luck we generate a symbol name that
414                        // is in use, try again with a different unique id until we find
415                        // an unused name
416                        if !self.names.contains_key(generated.as_str()) {
417                            data.name =
418                                Ident::new(Symbol::intern(generated.as_str()), data.name.span());
419                            break;
420                        }
421                        // Strip off the suffix we just added before we try again
422                        generated.truncate(original_len);
423                    }
424
425                    let gv = unsafe { self.insert(data) };
426                    Ok((gv, true))
427                }
428                // In all other cases, a conflicting declaration cannot be resolved
429                Linkage::External | Linkage::Internal | Linkage::Odr => {
430                    Err(GlobalVariableError::NameConflict(data.name))
431                }
432            }
433        } else {
434            let gv = unsafe { self.insert(data) };
435            Ok((gv, false))
436        }
437    }
438
439    /// This sets the initializer for the given [GlobalVariable] to `init`.
440    ///
441    /// This function will return `Err` if any of the following occur:
442    ///
443    /// * The global variable already has an initializer
444    /// * The given data does not match the type of the global variable, i.e. more data than the
445    ///   type supports.
446    ///
447    /// If the data is smaller than the type of the global variable, the data will be zero-extended
448    /// to fill it out.
449    ///
450    /// NOTE: The initializer data is expected to be in little-endian order.
451    pub fn set_initializer(
452        &mut self,
453        gv: GlobalVariable,
454        init: ConstantData,
455    ) -> Result<(), GlobalVariableError> {
456        let global = &mut self.arena[gv];
457        let layout = global.layout();
458        if init.len() > layout.size() {
459            return Err(GlobalVariableError::InvalidInit(global.name));
460        }
461
462        match global.init {
463            // If the global is uninitialized, we're good to go
464            None => {
465                global.init = Some(self.data.insert(init));
466            }
467            // If it is already initialized, but the initializers are the
468            // same, then we consider this a successful, albeit redundant,
469            // operation; otherwise we raise an error.
470            Some(prev_init) => {
471                let prev = self.data.get_by_ref(prev_init);
472                if prev != &init {
473                    return Err(GlobalVariableError::AlreadyInitialized(global.name));
474                }
475            }
476        }
477
478        Ok(())
479    }
480
481    /// This is a low-level operation to insert global variable data directly into the table,
482    /// allocating a fresh unique id, which is then returned.
483    ///
484    /// # SAFETY
485    ///
486    /// It is expected that the caller has already guaranteed that the name of the given global
487    /// variable is not present in the table, and that all validation rules for global variables
488    /// have been enforced.
489    pub(crate) unsafe fn insert(&mut self, mut data: GlobalVariableData) -> GlobalVariable {
490        let name = data.name;
491        // Allocate the data in the arena
492        let gv = if data.id == GlobalVariable::default() {
493            let gv = self.arena.alloc_key();
494            data.id = gv;
495            gv
496        } else {
497            data.id
498        };
499        self.arena.append(gv, data);
500        // Add the symbol name to the symbol map
501        self.names.insert(name, gv);
502
503        // Add the global variable to the layout
504        let unsafe_ref = unsafe {
505            let ptr = self.arena.get_raw(gv).unwrap();
506            UnsafeRef::from_raw(ptr.as_ptr())
507        };
508        self.layout.push_back(unsafe_ref);
509        gv
510    }
511}
512impl fmt::Debug for GlobalVariableTable {
513    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
514        f.debug_list().entries(self.layout.iter()).finish()
515    }
516}
517
518/// A handle to a global variable definition
519#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
520pub struct GlobalVariable(u32);
521entity_impl!(GlobalVariable, "gvar");
522impl Default for GlobalVariable {
523    #[inline]
524    fn default() -> Self {
525        use cranelift_entity::packed_option::ReservedValue;
526
527        Self::reserved_value()
528    }
529}
530
531/// A [GlobalVariable] represents a concrete definition for a symbolic value,
532/// i.e. it corresponds to the actual allocated memory referenced by a [GlobalValueData::Symbol]
533/// value.
534#[derive(Clone)]
535pub struct GlobalVariableData {
536    /// The intrusive link used for storing this global variable in a list
537    link: LinkedListLink,
538    /// The unique identifier associated with this global variable
539    id: GlobalVariable,
540    /// The symbol name for this global variable
541    pub name: Ident,
542    /// The type of the value this variable is allocated for.
543    ///
544    /// Nothing prevents one from accessing the variable as if it is
545    /// another type, but at a minimum this type is used to derive the
546    /// size and alignment requirements for this global variable on
547    /// the heap.
548    pub ty: Type,
549    /// The linkage for this global variable
550    pub linkage: Linkage,
551    /// The initializer for this global variable, if applicable
552    pub init: Option<Constant>,
553}
554impl GlobalVariableData {
555    pub(crate) fn new(
556        id: GlobalVariable,
557        name: Ident,
558        ty: Type,
559        linkage: Linkage,
560        init: Option<Constant>,
561    ) -> Self {
562        Self {
563            link: LinkedListLink::new(),
564            id,
565            name,
566            ty,
567            linkage,
568            init,
569        }
570    }
571
572    /// Get the unique identifier assigned to this global variable
573    #[inline]
574    pub fn id(&self) -> GlobalVariable {
575        self.id
576    }
577
578    /// Return the [Layout] of this global variable in memory
579    pub fn layout(&self) -> Layout {
580        self.ty.layout()
581    }
582
583    /// Return a handle to the initializer for this global variable, if present
584    pub fn initializer(&self) -> Option<Constant> {
585        self.init
586    }
587
588    /// Returns true if `self` is compatible with `other`, meaning that the two declarations are
589    /// identical in terms of type and linkage, and do not have conflicting initializers.
590    ///
591    /// NOTE: The name of the global is not considered here, only the properties of the value
592    /// itself.
593    pub fn is_compatible_with(&self, other: &Self) -> bool {
594        let compatible_init =
595            self.init.is_none() || other.init.is_none() || self.init == other.init;
596        self.ty == other.ty && self.linkage == other.linkage && compatible_init
597    }
598}
599impl Eq for GlobalVariableData {}
600impl PartialEq for GlobalVariableData {
601    fn eq(&self, other: &Self) -> bool {
602        self.linkage == other.linkage
603            && self.ty == other.ty
604            && self.name == other.name
605            && self.init == other.init
606    }
607}
608impl Hash for GlobalVariableData {
609    fn hash<H: Hasher>(&self, state: &mut H) {
610        self.name.hash(state);
611        self.ty.hash(state);
612        self.linkage.hash(state);
613        self.init.hash(state);
614    }
615}
616impl fmt::Debug for GlobalVariableData {
617    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
618        f.debug_struct("GlobalVariableData")
619            .field("id", &self.id)
620            .field("name", &self.name)
621            .field("ty", &self.ty)
622            .field("linkage", &self.linkage)
623            .field("init", &self.init)
624            .finish()
625    }
626}
627impl formatter::PrettyPrint for GlobalVariableData {
628    fn render(&self) -> formatter::Document {
629        use crate::formatter::*;
630
631        let name = if matches!(self.linkage, Linkage::Internal) {
632            display(self.name)
633        } else {
634            const_text("(")
635                + const_text("export")
636                + const_text(" ")
637                + display(self.name)
638                + const_text(")")
639        };
640
641        let doc = const_text("(")
642            + const_text("global")
643            + const_text(" ")
644            + name
645            + const_text(" ")
646            + const_text("(")
647            + const_text("id")
648            + const_text(" ")
649            + display(self.id.as_u32())
650            + const_text(")")
651            + const_text(" ")
652            + const_text("(")
653            + const_text("type")
654            + const_text(" ")
655            + text(format!("{}", &self.ty))
656            + const_text(")");
657
658        if let Some(init) = self.init {
659            doc + const_text(" ")
660                + const_text("(")
661                + const_text("const")
662                + const_text(" ")
663                + display(init.as_u32())
664                + const_text(")")
665                + const_text(")")
666        } else {
667            doc + const_text(")")
668        }
669    }
670}
671
672/// A handle to a global variable definition
673#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
674pub struct GlobalValue(u32);
675entity_impl!(GlobalValue, "gv");
676impl Default for GlobalValue {
677    #[inline]
678    fn default() -> Self {
679        use cranelift_entity::packed_option::ReservedValue;
680
681        Self::reserved_value()
682    }
683}
684
685/// Data associated with a `GlobalValue`.
686///
687/// Globals are allocated statically, and live for the lifetime of the program.
688/// In Miden, we allocate globals at the start of the heap. Since all globals are
689/// known statically, we instructions which manipulate globals are converted to
690/// loads/stores using constant addresses when translated to MASM.
691///
692/// Like other entities, globals may also have a [crate::diagnostics::SourceSpan] associated with
693/// them.
694#[derive(Debug, Clone)]
695pub enum GlobalValueData {
696    /// A symbolic reference to a global variable symbol
697    ///
698    /// The type of a symbolic global value is always a pointer, the address
699    /// of the referenced global variable.
700    Symbol {
701        /// The name of the global variable that is referenced
702        name: Ident,
703        /// A constant offset, in bytes, from the address of the symbol
704        offset: i32,
705    },
706    /// A global whose value is given by reading the value from the address
707    /// derived from another global value and an offset.
708    Load {
709        /// The global value whose value is the base pointer
710        base: GlobalValue,
711        /// A constant offset, in bytes, from the base address
712        offset: i32,
713        /// The type of the value stored at `base + offset`
714        ty: Type,
715    },
716    /// A global whose value is an address computed as the offset from another global
717    ///
718    /// This can be used for `getelementptr`-like situations, such as calculating the
719    /// address of a field in a struct that is stored in a global variable.
720    IAddImm {
721        /// The global value whose value is the base pointer
722        base: GlobalValue,
723        /// A constant offset, in units of `ty`, from the base address
724        offset: i32,
725        /// The unit type of the offset
726        ///
727        /// This can be helpful when computing addresses to elements of an array
728        /// stored in a global variable.
729        ty: Type,
730    },
731}
732impl GlobalValueData {
733    /// Returns true if this global value is a symbolic or computed address
734    /// which can be resolved at compile-time.
735    ///
736    /// Notably, global loads may produce an address, but the value of that
737    /// address is not known until runtime.
738    pub fn is_constant_addr(&self) -> bool {
739        !matches!(self, Self::Load { .. })
740    }
741
742    /// Return the computed offset for this global value (relative to it's position in the global
743    /// table)
744    pub fn offset(&self) -> i32 {
745        match self {
746            Self::Symbol { offset, .. } => *offset,
747            Self::Load { offset, .. } => *offset,
748            Self::IAddImm { ref ty, offset, .. } => {
749                let offset = *offset as usize * ty.size_in_bytes();
750                offset
751                    .try_into()
752                    .expect("invalid iadd expression: expected computed offset to fit in i32 range")
753            }
754        }
755    }
756
757    /// Get the type associated with this value, if applicable
758    pub fn ty(&self) -> Option<&Type> {
759        match self {
760            Self::Symbol { .. } => None,
761            Self::Load { ref ty, .. } => Some(ty),
762            Self::IAddImm { ref ty, .. } => Some(ty),
763        }
764    }
765
766    pub(crate) fn render(&self, dfg: &DataFlowGraph) -> formatter::Document {
767        use crate::formatter::*;
768
769        match self {
770            Self::Symbol { name, offset } => {
771                let offset = *offset;
772                let offset = if offset == 0 {
773                    None
774                } else {
775                    Some(
776                        const_text("(")
777                            + const_text("offset")
778                            + const_text(" ")
779                            + display(offset)
780                            + const_text(")"),
781                    )
782                };
783
784                const_text("(")
785                    + const_text("global.symbol")
786                    + const_text(" ")
787                    + display(*name)
788                    + offset.map(|offset| const_text(" ") + offset).unwrap_or_default()
789                    + const_text(")")
790            }
791            Self::Load { base, offset, ty } => {
792                let offset = *offset;
793                let offset = if offset == 0 {
794                    None
795                } else {
796                    Some(
797                        const_text("(")
798                            + const_text("offset")
799                            + const_text(" ")
800                            + display(offset)
801                            + const_text(")"),
802                    )
803                };
804
805                const_text("(")
806                    + const_text("global.load")
807                    + const_text(" ")
808                    + text(format!("{}", ty))
809                    + offset.map(|offset| const_text(" ") + offset).unwrap_or_default()
810                    + const_text(" ")
811                    + dfg.global_value(*base).render(dfg)
812                    + const_text(")")
813            }
814            Self::IAddImm { base, offset, ty } => {
815                const_text("(")
816                    + const_text("global.iadd")
817                    + const_text(" ")
818                    + const_text("(")
819                    + const_text("offset")
820                    + const_text(" ")
821                    + display(*offset)
822                    + const_text(" ")
823                    + const_text(".")
824                    + const_text(" ")
825                    + text(format!("{}", ty))
826                    + const_text(")")
827                    + const_text(" ")
828                    + dfg.global_value(*base).render(dfg)
829                    + const_text(")")
830            }
831        }
832    }
833}