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