midenc_codegen_masm/lower/
native_ptr.rs

1use serde::{Deserialize, Serialize};
2
3/// This represents a descriptor for a pointer translated from the IR into a form suitable for
4/// referencing data in Miden's linear memory.
5#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub struct NativePtr {
7    /// This is the address of the element containing the first byte of data
8    ///
9    /// Each element is assumed to be a 32-bit value/chunk
10    pub addr: u32,
11    /// This is the byte offset into the 32-bit chunk referenced by `index`
12    ///
13    /// This offset is where the data referenced by the pointer actually starts.
14    pub offset: u8,
15    /// This is the assumed address space of the pointer value.
16    ///
17    /// This address space is unknown by default, but can be specified if known statically.
18    /// The address space determines whether the pointer is valid in certain contexts. For
19    /// example, attempting to load a pointer with address space 0 is invalid if not operating
20    /// in the root context.
21    ///
22    /// Currently this has no effect, but is here as we expand support for multiple memories.
23    pub addrspace: midenc_hir::AddressSpace,
24}
25impl NativePtr {
26    pub fn new(addr: u32, offset: u8) -> Self {
27        Self {
28            addr,
29            offset,
30            addrspace: Default::default(),
31        }
32    }
33
34    /// Translates a raw pointer (assumed to be in a byte-addressable address space) to a native
35    /// pointer value, in the default [hir::AddressSpace].
36    pub fn from_ptr(addr: u32) -> Self {
37        // The native word address for `addr` is derived by splitting the byte-addressable space
38        // into 32-bit chunks, each chunk belonging to a single field element, i.e. each element
39        // of the native address space represents 32 bits of byte-addressable memory.
40        //
41        // By dividing `addr` by 4, we get the element address where the data starts.
42        let eaddr = addr / 4;
43        // If our address is not element-aligned, we need to determine what byte offset contains
44        // the first byte of the data.
45        let offset = (addr % 4) as u8;
46        Self {
47            addr: eaddr,
48            offset,
49            addrspace: Default::default(),
50        }
51    }
52
53    /// Returns true if this pointer is aligned to a word boundary
54    pub const fn is_word_aligned(&self) -> bool {
55        self.offset == 0 && self.addr.is_multiple_of(4)
56    }
57
58    /// Returns true if this pointer is aligned to a field element boundary
59    pub const fn is_element_aligned(&self) -> bool {
60        self.offset == 0
61    }
62
63    /// Returns true if this pointer is not element aligned
64    pub const fn is_unaligned(&self) -> bool {
65        self.offset > 0
66    }
67
68    /// Returns the byte alignment implied by this pointer value.
69    ///
70    /// For example, a pointer to the first word in linear memory, i.e. address 1, with an offset
71    /// of 2, is equivalent to an address in byte-addressable memory of 6, which has an implied
72    /// alignment of 2 bytes.
73    pub const fn alignment(&self) -> u32 {
74        2u32.pow(self.as_ptr().trailing_zeros())
75    }
76
77    /// Converts this native pointer back to a byte-addressable pointer value
78    pub const fn as_ptr(&self) -> u32 {
79        (self.addr * 4) + self.offset as u32
80    }
81}