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}