1 //! Memory operation flags. 2 3 use super::TrapCode; 4 use core::fmt; 5 use core::num::NonZeroU8; 6 use core::str::FromStr; 7 8 #[cfg(feature = "enable-serde")] 9 use serde_derive::{Deserialize, Serialize}; 10 11 /// Endianness of a memory access. 12 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] 13 pub 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] 26 pub enum AliasRegion { 27 // None = 0b00; 28 Heap = 0b01, 29 Table = 0b10, 30 Vmctx = 0b11, 31 } 32 33 impl 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))] 64 pub 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. 87 const 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. 92 const BIT_READONLY: u16 = 1 << 1; 93 94 /// Load multi-byte values from memory in a little-endian format. 95 const BIT_LITTLE_ENDIAN: u16 = 1 << 2; 96 97 /// Load multi-byte values from memory in a big-endian format. 98 const 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. 104 const BIT_CHECKED: u16 = 1 << 4; 105 106 /// Used for alias analysis, indicates which disjoint part of the abstract state 107 /// is being accessed. 108 const MASK_ALIAS_REGION: u16 = 0b11 << ALIAS_REGION_OFFSET; 109 const ALIAS_REGION_OFFSET: u16 = 5; 110 111 /// Trap code, if any, for this memory operation. 112 const MASK_TRAP_CODE: u16 = 0b1111_1111 << TRAP_CODE_OFFSET; 113 const TRAP_CODE_OFFSET: u16 = 7; 114 115 impl 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 370 impl 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)] 405 mod 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 } 442