1 //! Memory operation flags. 2 3 use core::fmt; 4 5 #[cfg(feature = "enable-serde")] 6 use serde::{Deserialize, Serialize}; 7 8 enum FlagBit { 9 Notrap, 10 Aligned, 11 Readonly, 12 LittleEndian, 13 BigEndian, 14 } 15 16 const NAMES: [&str; 5] = ["notrap", "aligned", "readonly", "little", "big"]; 17 18 /// Endianness of a memory access. 19 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] 20 pub enum Endianness { 21 /// Little-endian 22 Little, 23 /// Big-endian 24 Big, 25 } 26 27 /// Flags for memory operations like load/store. 28 /// 29 /// Each of these flags introduce a limited form of undefined behavior. The flags each enable 30 /// certain optimizations that need to make additional assumptions. Generally, the semantics of a 31 /// program does not change when a flag is removed, but adding a flag will. 32 /// 33 /// In addition, the flags determine the endianness of the memory access. By default, 34 /// any memory access uses the native endianness determined by the target ISA. This can 35 /// be overridden for individual accesses by explicitly specifying little- or big-endian 36 /// semantics via the flags. 37 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] 38 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] 39 pub struct MemFlags { 40 bits: u8, 41 } 42 43 impl MemFlags { 44 /// Create a new empty set of flags. new() -> Self45 pub fn new() -> Self { 46 Self { bits: 0 } 47 } 48 49 /// Create a set of flags representing an access from a "trusted" address, meaning it's 50 /// known to be aligned and non-trapping. trusted() -> Self51 pub fn trusted() -> Self { 52 let mut result = Self::new(); 53 result.set_notrap(); 54 result.set_aligned(); 55 result 56 } 57 58 /// Read a flag bit. read(self, bit: FlagBit) -> bool59 fn read(self, bit: FlagBit) -> bool { 60 self.bits & (1 << bit as usize) != 0 61 } 62 63 /// Set a flag bit. set(&mut self, bit: FlagBit)64 fn set(&mut self, bit: FlagBit) { 65 self.bits |= 1 << bit as usize 66 } 67 68 /// Set a flag bit by name. 69 /// 70 /// Returns true if the flag was found and set, false for an unknown flag name. 71 /// Will also return false when trying to set inconsistent endianness flags. set_by_name(&mut self, name: &str) -> bool72 pub fn set_by_name(&mut self, name: &str) -> bool { 73 match NAMES.iter().position(|&s| s == name) { 74 Some(bit) => { 75 let bits = self.bits | 1 << bit; 76 if (bits & (1 << FlagBit::LittleEndian as usize)) != 0 77 && (bits & (1 << FlagBit::BigEndian as usize)) != 0 78 { 79 false 80 } else { 81 self.bits = bits; 82 true 83 } 84 } 85 None => false, 86 } 87 } 88 89 /// Return endianness of the memory access. This will return the endianness 90 /// explicitly specified by the flags if any, and will default to the native 91 /// endianness otherwise. The native endianness has to be provided by the 92 /// caller since it is not explicitly encoded in CLIF IR -- this allows a 93 /// front end to create IR without having to know the target endianness. endianness(self, native_endianness: Endianness) -> Endianness94 pub fn endianness(self, native_endianness: Endianness) -> Endianness { 95 if self.read(FlagBit::LittleEndian) { 96 Endianness::Little 97 } else if self.read(FlagBit::BigEndian) { 98 Endianness::Big 99 } else { 100 native_endianness 101 } 102 } 103 104 /// Set endianness of the memory access. set_endianness(&mut self, endianness: Endianness)105 pub fn set_endianness(&mut self, endianness: Endianness) { 106 match endianness { 107 Endianness::Little => self.set(FlagBit::LittleEndian), 108 Endianness::Big => self.set(FlagBit::BigEndian), 109 }; 110 assert!(!(self.read(FlagBit::LittleEndian) && self.read(FlagBit::BigEndian))); 111 } 112 113 /// Test if the `notrap` flag is set. 114 /// 115 /// Normally, trapping is part of the semantics of a load/store operation. If the platform 116 /// would cause a trap when accessing the effective address, the Cranelift memory operation is 117 /// also required to trap. 118 /// 119 /// The `notrap` flag tells Cranelift that the memory is *accessible*, which means that 120 /// accesses will not trap. This makes it possible to delete an unused load or a dead store 121 /// instruction. notrap(self) -> bool122 pub fn notrap(self) -> bool { 123 self.read(FlagBit::Notrap) 124 } 125 126 /// Set the `notrap` flag. set_notrap(&mut self)127 pub fn set_notrap(&mut self) { 128 self.set(FlagBit::Notrap) 129 } 130 131 /// Test if the `aligned` flag is set. 132 /// 133 /// By default, Cranelift memory instructions work with any unaligned effective address. If the 134 /// `aligned` flag is set, the instruction is permitted to trap or return a wrong result if the 135 /// effective address is misaligned. aligned(self) -> bool136 pub fn aligned(self) -> bool { 137 self.read(FlagBit::Aligned) 138 } 139 140 /// Set the `aligned` flag. set_aligned(&mut self)141 pub fn set_aligned(&mut self) { 142 self.set(FlagBit::Aligned) 143 } 144 145 /// Test if the `readonly` flag is set. 146 /// 147 /// Loads with this flag have no memory dependencies. 148 /// This results in undefined behavior if the dereferenced memory is mutated at any time 149 /// between when the function is called and when it is exited. readonly(self) -> bool150 pub fn readonly(self) -> bool { 151 self.read(FlagBit::Readonly) 152 } 153 154 /// Set the `readonly` flag. set_readonly(&mut self)155 pub fn set_readonly(&mut self) { 156 self.set(FlagBit::Readonly) 157 } 158 } 159 160 impl fmt::Display for MemFlags { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result161 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 162 for (i, n) in NAMES.iter().enumerate() { 163 if self.bits & (1 << i) != 0 { 164 write!(f, " {}", n)?; 165 } 166 } 167 Ok(()) 168 } 169 } 170