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