1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
mod function;
pub mod intrinsics;
mod module;
mod program;
mod region;

pub use midenc_hir::{
    Local, LocalId, MasmBlock as Block, MasmBlockId as BlockId, MasmImport as Import, MasmOp as Op,
    ModuleImportInfo,
};
use serde::{Deserialize, Serialize};

pub use self::{
    function::{FrozenFunctionList, Function, FunctionList},
    module::{FrozenModuleTree, Module, ModuleTree},
    program::{Library, Program},
    region::Region,
};

/// This represents a descriptor for a pointer translated from the IR into a form suitable for
/// referencing data in Miden's linear memory.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NativePtr {
    /// This is the address of the word containing the first byte of data
    pub waddr: u32,
    /// This is the element index of the word referenced by `waddr` containing the first byte of
    /// data
    ///
    /// Each element is assumed to be a 32-bit value/chunk
    pub index: u8,
    /// This is the byte offset into the 32-bit chunk referenced by `index`
    ///
    /// This offset is where the data referenced by the pointer actually starts.
    pub offset: u8,
    /// This is the assumed address space of the pointer value.
    ///
    /// This address space is unknown by default, but can be specified if known statically.
    /// The address space determines whether the pointer is valid in certain contexts. For
    /// example, attempting to load a pointer with address space 0 is invalid if not operating
    /// in the root context.
    ///
    /// Currently this has no effect, but is here as we expand support for multiple memories.
    pub addrspace: midenc_hir::AddressSpace,
}
impl NativePtr {
    pub fn new(waddr: u32, index: u8, offset: u8) -> Self {
        Self {
            waddr,
            index,
            offset,
            addrspace: Default::default(),
        }
    }

    /// Translates a raw pointer (assumed to be in a byte-addressable address space) to
    /// a native pointer value, in the default [hir::AddressSpace].
    pub fn from_ptr(addr: u32) -> Self {
        // The native word address for `addr` is derived by splitting
        // the byte-addressable space into 32-bit chunks, each chunk
        // belonging to a single field element. Thus, each word of the
        // native address space represents 128 bits of byte-addressable
        // memory.
        //
        // By dividing `addr` by 16, we get the word index (i.e. address)
        // where the data starts.
        let waddr = addr / 16;
        // If our address is not word-aligned, we need to determine what
        // element index contains the 32-bit chunk where the data begins
        let woffset = addr % 16;
        let index = (woffset / 4) as u8;
        // If our address is not element-aligned, we need to determine
        // what byte offset contains the first byte of the data
        let offset = (woffset % 4) as u8;
        Self {
            waddr,
            index,
            offset,
            addrspace: Default::default(),
        }
    }

    /// Returns true if this pointer is aligned to a word boundary
    pub const fn is_word_aligned(&self) -> bool {
        self.index == 0 && self.offset == 0
    }

    /// Returns true if this pointer is aligned to a field element boundary
    pub const fn is_element_aligned(&self) -> bool {
        self.offset == 0
    }

    /// Returns true if this pointer is not word or element aligned
    pub const fn is_unaligned(&self) -> bool {
        self.offset > 0
    }

    /// Returns the byte alignment implied by this pointer value.
    ///
    /// For example, a pointer to the first word in linear memory, i.e. address 0,
    /// with an element index of 1, and offset of 16, is equivalent to an address
    /// in byte-addressable memory of 48, which has an implied alignment of 16 bytes.
    pub const fn alignment(&self) -> u32 {
        2u32.pow(self.as_ptr().trailing_zeros())
    }

    /// Converts this native pointer back to a byte-addressable pointer value
    pub const fn as_ptr(&self) -> u32 {
        (self.waddr * 16) + (self.index as u32 * 4) + self.offset as u32
    }
}