cranelift_codegen/ir/
memflags.rs

1//! Memory operation flags.
2
3use super::TrapCode;
4use core::fmt;
5use core::num::NonZeroU8;
6use core::str::FromStr;
7
8#[cfg(feature = "enable-serde")]
9use serde_derive::{Deserialize, Serialize};
10
11/// Endianness of a memory access.
12#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
13pub enum Endianness {
14    /// Little-endian
15    Little,
16    /// Big-endian
17    Big,
18}
19
20/// Which disjoint region of aliasing memory is accessed in this memory
21/// operation.
22#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
23#[repr(u8)]
24#[allow(missing_docs)]
25#[rustfmt::skip]
26pub enum AliasRegion {
27    // None = 0b00;
28    Heap    = 0b01,
29    Table   = 0b10,
30    Vmctx   = 0b11,
31}
32
33impl AliasRegion {
34    const fn from_bits(bits: u8) -> Option<Self> {
35        match bits {
36            0b00 => None,
37            0b01 => Some(Self::Heap),
38            0b10 => Some(Self::Table),
39            0b11 => Some(Self::Vmctx),
40            _ => panic!("invalid alias region bits"),
41        }
42    }
43
44    const fn to_bits(region: Option<Self>) -> u8 {
45        match region {
46            None => 0b00,
47            Some(r) => r as u8,
48        }
49    }
50}
51
52/// Flags for memory operations like load/store.
53///
54/// Each of these flags introduce a limited form of undefined behavior. The flags each enable
55/// certain optimizations that need to make additional assumptions. Generally, the semantics of a
56/// program does not change when a flag is removed, but adding a flag will.
57///
58/// In addition, the flags determine the endianness of the memory access.  By default,
59/// any memory access uses the native endianness determined by the target ISA.  This can
60/// be overridden for individual accesses by explicitly specifying little- or big-endian
61/// semantics via the flags.
62#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
63#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
64pub struct MemFlags {
65    // Initialized to all zeros to have all flags have their default value.
66    // This is interpreted through various methods below. Currently the bits of
67    // this are defined as:
68    //
69    // * 0 - aligned flag
70    // * 1 - readonly flag
71    // * 2 - little endian flag
72    // * 3 - big endian flag
73    // * 4 - checked flag
74    // * 5/6 - alias region
75    // * 7/8/9/10/11/12/13/14 - trap code
76    // * 15 - unallocated
77    //
78    // Current properties upheld are:
79    //
80    // * only one of little/big endian is set
81    // * only one alias region can be set - once set it cannot be changed
82    bits: u16,
83}
84
85/// Guaranteed to use "natural alignment" for the given type. This
86/// may enable better instruction selection.
87const BIT_ALIGNED: u16 = 1 << 0;
88
89/// A load that reads data in memory that does not change for the
90/// duration of the function's execution. This may enable
91/// additional optimizations to be performed.
92const BIT_READONLY: u16 = 1 << 1;
93
94/// Load multi-byte values from memory in a little-endian format.
95const BIT_LITTLE_ENDIAN: u16 = 1 << 2;
96
97/// Load multi-byte values from memory in a big-endian format.
98const BIT_BIG_ENDIAN: u16 = 1 << 3;
99
100/// Check this load or store for safety when using the
101/// proof-carrying-code framework. The address must have a
102/// `PointsTo` fact attached with a sufficiently large valid range
103/// for the accessed size.
104const BIT_CHECKED: u16 = 1 << 4;
105
106/// Used for alias analysis, indicates which disjoint part of the abstract state
107/// is being accessed.
108const MASK_ALIAS_REGION: u16 = 0b11 << ALIAS_REGION_OFFSET;
109const ALIAS_REGION_OFFSET: u16 = 5;
110
111/// Trap code, if any, for this memory operation.
112const MASK_TRAP_CODE: u16 = 0b1111_1111 << TRAP_CODE_OFFSET;
113const TRAP_CODE_OFFSET: u16 = 7;
114
115impl MemFlags {
116    /// Create a new empty set of flags.
117    pub const fn new() -> Self {
118        Self { bits: 0 }.with_trap_code(Some(TrapCode::HEAP_OUT_OF_BOUNDS))
119    }
120
121    /// Create a set of flags representing an access from a "trusted" address, meaning it's
122    /// known to be aligned and non-trapping.
123    pub const fn trusted() -> Self {
124        Self::new().with_notrap().with_aligned()
125    }
126
127    /// Read a flag bit.
128    const fn read_bit(self, bit: u16) -> bool {
129        self.bits & bit != 0
130    }
131
132    /// Return a new `MemFlags` with this flag bit set.
133    const fn with_bit(mut self, bit: u16) -> Self {
134        self.bits |= bit;
135        self
136    }
137
138    /// Reads the alias region that this memory operation works with.
139    pub const fn alias_region(self) -> Option<AliasRegion> {
140        AliasRegion::from_bits(((self.bits & MASK_ALIAS_REGION) >> ALIAS_REGION_OFFSET) as u8)
141    }
142
143    /// Sets the alias region that this works on to the specified `region`.
144    pub const fn with_alias_region(mut self, region: Option<AliasRegion>) -> Self {
145        let bits = AliasRegion::to_bits(region);
146        self.bits &= !MASK_ALIAS_REGION;
147        self.bits |= (bits as u16) << ALIAS_REGION_OFFSET;
148        self
149    }
150
151    /// Sets the alias region that this works on to the specified `region`.
152    pub fn set_alias_region(&mut self, region: Option<AliasRegion>) {
153        *self = self.with_alias_region(region);
154    }
155
156    /// Set a flag bit by name.
157    ///
158    /// Returns true if the flag was found and set, false for an unknown flag
159    /// name.
160    ///
161    /// # Errors
162    ///
163    /// Returns an error message if the `name` is known but couldn't be applied
164    /// due to it being a semantic error.
165    pub fn set_by_name(&mut self, name: &str) -> Result<bool, &'static str> {
166        *self = match name {
167            "notrap" => self.with_trap_code(None),
168            "aligned" => self.with_aligned(),
169            "readonly" => self.with_readonly(),
170            "little" => {
171                if self.read_bit(BIT_BIG_ENDIAN) {
172                    return Err("cannot set both big and little endian bits");
173                }
174                self.with_endianness(Endianness::Little)
175            }
176            "big" => {
177                if self.read_bit(BIT_LITTLE_ENDIAN) {
178                    return Err("cannot set both big and little endian bits");
179                }
180                self.with_endianness(Endianness::Big)
181            }
182            "heap" => {
183                if self.alias_region().is_some() {
184                    return Err("cannot set more than one alias region");
185                }
186                self.with_alias_region(Some(AliasRegion::Heap))
187            }
188            "table" => {
189                if self.alias_region().is_some() {
190                    return Err("cannot set more than one alias region");
191                }
192                self.with_alias_region(Some(AliasRegion::Table))
193            }
194            "vmctx" => {
195                if self.alias_region().is_some() {
196                    return Err("cannot set more than one alias region");
197                }
198                self.with_alias_region(Some(AliasRegion::Vmctx))
199            }
200            "checked" => self.with_checked(),
201
202            other => match TrapCode::from_str(other) {
203                Ok(code) => self.with_trap_code(Some(code)),
204                Err(()) => return Ok(false),
205            },
206        };
207        Ok(true)
208    }
209
210    /// Return endianness of the memory access.  This will return the endianness
211    /// explicitly specified by the flags if any, and will default to the native
212    /// endianness otherwise.  The native endianness has to be provided by the
213    /// caller since it is not explicitly encoded in CLIF IR -- this allows a
214    /// front end to create IR without having to know the target endianness.
215    pub const fn endianness(self, native_endianness: Endianness) -> Endianness {
216        if self.read_bit(BIT_LITTLE_ENDIAN) {
217            Endianness::Little
218        } else if self.read_bit(BIT_BIG_ENDIAN) {
219            Endianness::Big
220        } else {
221            native_endianness
222        }
223    }
224
225    /// Return endianness of the memory access, if explicitly specified.
226    ///
227    /// If the endianness is not explicitly specified, this will return `None`,
228    /// which means "native endianness".
229    pub const fn explicit_endianness(self) -> Option<Endianness> {
230        if self.read_bit(BIT_LITTLE_ENDIAN) {
231            Some(Endianness::Little)
232        } else if self.read_bit(BIT_BIG_ENDIAN) {
233            Some(Endianness::Big)
234        } else {
235            None
236        }
237    }
238
239    /// Set endianness of the memory access.
240    pub fn set_endianness(&mut self, endianness: Endianness) {
241        *self = self.with_endianness(endianness);
242    }
243
244    /// Set endianness of the memory access, returning new flags.
245    pub const fn with_endianness(self, endianness: Endianness) -> Self {
246        let res = match endianness {
247            Endianness::Little => self.with_bit(BIT_LITTLE_ENDIAN),
248            Endianness::Big => self.with_bit(BIT_BIG_ENDIAN),
249        };
250        assert!(!(res.read_bit(BIT_LITTLE_ENDIAN) && res.read_bit(BIT_BIG_ENDIAN)));
251        res
252    }
253
254    /// Test if this memory operation cannot trap.
255    ///
256    /// By default `MemFlags` will assume that any load/store can trap and is
257    /// associated with a `TrapCode::HeapOutOfBounds` code. If the trap code is
258    /// configured to `None` though then this method will return `true` and
259    /// indicates that the memory operation will not trap.
260    ///
261    /// If this returns `true` then the memory is *accessible*, which means
262    /// that accesses will not trap. This makes it possible to delete an unused
263    /// load or a dead store instruction.
264    pub const fn notrap(self) -> bool {
265        self.trap_code().is_none()
266    }
267
268    /// Sets the trap code for this `MemFlags` to `None`.
269    pub fn set_notrap(&mut self) {
270        *self = self.with_notrap();
271    }
272
273    /// Sets the trap code for this `MemFlags` to `None`, returning the new
274    /// flags.
275    pub const fn with_notrap(self) -> Self {
276        self.with_trap_code(None)
277    }
278
279    /// Test if the `aligned` flag is set.
280    ///
281    /// By default, Cranelift memory instructions work with any unaligned effective address. If the
282    /// `aligned` flag is set, the instruction is permitted to trap or return a wrong result if the
283    /// effective address is misaligned.
284    pub const fn aligned(self) -> bool {
285        self.read_bit(BIT_ALIGNED)
286    }
287
288    /// Set the `aligned` flag.
289    pub fn set_aligned(&mut self) {
290        *self = self.with_aligned();
291    }
292
293    /// Set the `aligned` flag, returning new flags.
294    pub const fn with_aligned(self) -> Self {
295        self.with_bit(BIT_ALIGNED)
296    }
297
298    /// Test if the `readonly` flag is set.
299    ///
300    /// Loads with this flag have no memory dependencies.
301    /// This results in undefined behavior if the dereferenced memory is mutated at any time
302    /// between when the function is called and when it is exited.
303    pub const fn readonly(self) -> bool {
304        self.read_bit(BIT_READONLY)
305    }
306
307    /// Set the `readonly` flag.
308    pub fn set_readonly(&mut self) {
309        *self = self.with_readonly();
310    }
311
312    /// Set the `readonly` flag, returning new flags.
313    pub const fn with_readonly(self) -> Self {
314        self.with_bit(BIT_READONLY)
315    }
316
317    /// Test if the `checked` bit is set.
318    ///
319    /// Loads and stores with this flag are verified to access
320    /// pointers only with a validated `PointsTo` fact attached, and
321    /// with that fact validated, when using the proof-carrying-code
322    /// framework. If initial facts on program inputs are correct
323    /// (i.e., correctly denote the shape and types of data structures
324    /// in memory), and if PCC validates the compiled output, then all
325    /// `checked`-marked memory accesses are guaranteed (up to the
326    /// checker's correctness) to access valid memory. This can be
327    /// used to ensure memory safety and sandboxing.
328    pub const fn checked(self) -> bool {
329        self.read_bit(BIT_CHECKED)
330    }
331
332    /// Set the `checked` bit.
333    pub fn set_checked(&mut self) {
334        *self = self.with_checked();
335    }
336
337    /// Set the `checked` bit, returning new flags.
338    pub const fn with_checked(self) -> Self {
339        self.with_bit(BIT_CHECKED)
340    }
341
342    /// Get the trap code to report if this memory access traps.
343    ///
344    /// A `None` trap code indicates that this memory access does not trap.
345    pub const fn trap_code(self) -> Option<TrapCode> {
346        let byte = ((self.bits & MASK_TRAP_CODE) >> TRAP_CODE_OFFSET) as u8;
347        match NonZeroU8::new(byte) {
348            Some(code) => Some(TrapCode::from_raw(code)),
349            None => None,
350        }
351    }
352
353    /// Configures these flags with the specified trap code `code`.
354    ///
355    /// A trap code indicates that this memory operation cannot be optimized
356    /// away and it must "stay where it is" in the programs. Traps are
357    /// considered side effects, for example, and have meaning through the trap
358    /// code that is communicated and which instruction trapped.
359    pub const fn with_trap_code(mut self, code: Option<TrapCode>) -> Self {
360        let bits = match code {
361            Some(code) => code.as_raw().get() as u16,
362            None => 0,
363        };
364        self.bits &= !MASK_TRAP_CODE;
365        self.bits |= bits << TRAP_CODE_OFFSET;
366        self
367    }
368}
369
370impl fmt::Display for MemFlags {
371    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
372        match self.trap_code() {
373            None => write!(f, " notrap")?,
374            // This is the default trap code, so don't print anything extra
375            // for this.
376            Some(TrapCode::HEAP_OUT_OF_BOUNDS) => {}
377            Some(t) => write!(f, " {t}")?,
378        }
379        if self.aligned() {
380            write!(f, " aligned")?;
381        }
382        if self.readonly() {
383            write!(f, " readonly")?;
384        }
385        if self.read_bit(BIT_BIG_ENDIAN) {
386            write!(f, " big")?;
387        }
388        if self.read_bit(BIT_LITTLE_ENDIAN) {
389            write!(f, " little")?;
390        }
391        if self.checked() {
392            write!(f, " checked")?;
393        }
394        match self.alias_region() {
395            None => {}
396            Some(AliasRegion::Heap) => write!(f, " heap")?,
397            Some(AliasRegion::Table) => write!(f, " table")?,
398            Some(AliasRegion::Vmctx) => write!(f, " vmctx")?,
399        }
400        Ok(())
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407
408    #[test]
409    fn roundtrip_traps() {
410        for trap in TrapCode::non_user_traps().iter().copied() {
411            let flags = MemFlags::new().with_trap_code(Some(trap));
412            assert_eq!(flags.trap_code(), Some(trap));
413        }
414        let flags = MemFlags::new().with_trap_code(None);
415        assert_eq!(flags.trap_code(), None);
416    }
417
418    #[test]
419    fn cannot_set_big_and_little() {
420        let mut big = MemFlags::new().with_endianness(Endianness::Big);
421        assert!(big.set_by_name("little").is_err());
422
423        let mut little = MemFlags::new().with_endianness(Endianness::Little);
424        assert!(little.set_by_name("big").is_err());
425    }
426
427    #[test]
428    fn only_one_region() {
429        let mut big = MemFlags::new().with_alias_region(Some(AliasRegion::Heap));
430        assert!(big.set_by_name("table").is_err());
431        assert!(big.set_by_name("vmctx").is_err());
432
433        let mut big = MemFlags::new().with_alias_region(Some(AliasRegion::Table));
434        assert!(big.set_by_name("heap").is_err());
435        assert!(big.set_by_name("vmctx").is_err());
436
437        let mut big = MemFlags::new().with_alias_region(Some(AliasRegion::Vmctx));
438        assert!(big.set_by_name("heap").is_err());
439        assert!(big.set_by_name("table").is_err());
440    }
441}