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
use serde::{Deserialize, Serialize};
/// 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 element containing the first byte of data
///
/// Each element is assumed to be a 32-bit value/chunk
pub addr: u32,
/// 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(addr: u32, offset: u8) -> Self {
Self {
addr,
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, i.e. each element
// of the native address space represents 32 bits of byte-addressable memory.
//
// By dividing `addr` by 4, we get the element address where the data starts.
let eaddr = addr / 4;
// If our address is not element-aligned, we need to determine what byte offset contains
// the first byte of the data.
let offset = (addr % 4) as u8;
Self {
addr: eaddr,
offset,
addrspace: Default::default(),
}
}
/// Returns true if this pointer is aligned to a word boundary
pub const fn is_word_aligned(&self) -> bool {
self.offset == 0 && self.addr.is_multiple_of(4)
}
/// 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 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 1, with an offset
/// of 2, is equivalent to an address in byte-addressable memory of 6, which has an implied
/// alignment of 2 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.addr * 4) + self.offset as u32
}
}