Path: blob/main/cranelift/codegen/src/ir/memflags.rs
1693 views
//! Memory operation flags.12use super::TrapCode;3use core::fmt;4use core::num::NonZeroU8;5use core::str::FromStr;67#[cfg(feature = "enable-serde")]8use serde_derive::{Deserialize, Serialize};910/// Endianness of a memory access.11#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]12pub enum Endianness {13/// Little-endian14Little,15/// Big-endian16Big,17}1819/// Which disjoint region of aliasing memory is accessed in this memory20/// operation.21#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]22#[repr(u8)]23#[expect(missing_docs, reason = "self-describing variants")]24#[rustfmt::skip]25pub enum AliasRegion {26// None = 0b00;27Heap = 0b01,28Table = 0b10,29Vmctx = 0b11,30}3132impl AliasRegion {33const fn from_bits(bits: u8) -> Option<Self> {34match bits {350b00 => None,360b01 => Some(Self::Heap),370b10 => Some(Self::Table),380b11 => Some(Self::Vmctx),39_ => panic!("invalid alias region bits"),40}41}4243const fn to_bits(region: Option<Self>) -> u8 {44match region {45None => 0b00,46Some(r) => r as u8,47}48}49}5051/// Flags for memory operations like load/store.52///53/// Each of these flags introduce a limited form of undefined behavior. The flags each enable54/// certain optimizations that need to make additional assumptions. Generally, the semantics of a55/// program does not change when a flag is removed, but adding a flag will.56///57/// In addition, the flags determine the endianness of the memory access. By default,58/// any memory access uses the native endianness determined by the target ISA. This can59/// be overridden for individual accesses by explicitly specifying little- or big-endian60/// semantics via the flags.61#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]62#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]63pub struct MemFlags {64// Initialized to all zeros to have all flags have their default value.65// This is interpreted through various methods below. Currently the bits of66// this are defined as:67//68// * 0 - aligned flag69// * 1 - readonly flag70// * 2 - little endian flag71// * 3 - big endian flag72// * 4 - checked flag73// * 5/6 - alias region74// * 7/8/9/10/11/12/13/14 - trap code75// * 15 - can_move flag76//77// Current properties upheld are:78//79// * only one of little/big endian is set80// * only one alias region can be set - once set it cannot be changed81bits: u16,82}8384/// Guaranteed to use "natural alignment" for the given type. This85/// may enable better instruction selection.86const BIT_ALIGNED: u16 = 1 << 0;8788/// A load that reads data in memory that does not change for the89/// duration of the function's execution. This may enable90/// additional optimizations to be performed.91const BIT_READONLY: u16 = 1 << 1;9293/// Load multi-byte values from memory in a little-endian format.94const BIT_LITTLE_ENDIAN: u16 = 1 << 2;9596/// Load multi-byte values from memory in a big-endian format.97const BIT_BIG_ENDIAN: u16 = 1 << 3;9899/// Check this load or store for safety when using the100/// proof-carrying-code framework. The address must have a101/// `PointsTo` fact attached with a sufficiently large valid range102/// for the accessed size.103const BIT_CHECKED: u16 = 1 << 4;104105/// Used for alias analysis, indicates which disjoint part of the abstract state106/// is being accessed.107const MASK_ALIAS_REGION: u16 = 0b11 << ALIAS_REGION_OFFSET;108const ALIAS_REGION_OFFSET: u16 = 5;109110/// Trap code, if any, for this memory operation.111const MASK_TRAP_CODE: u16 = 0b1111_1111 << TRAP_CODE_OFFSET;112const TRAP_CODE_OFFSET: u16 = 7;113114/// Whether this memory operation may be freely moved by the optimizer so long115/// as its data dependencies are satisfied. That is, by setting this flag, the116/// producer is guaranteeing that this memory operation's safety is not guarded117/// by outside-the-data-flow-graph properties, like implicit bounds-checking118/// control dependencies.119const BIT_CAN_MOVE: u16 = 1 << 15;120121impl MemFlags {122/// Create a new empty set of flags.123pub const fn new() -> Self {124Self { bits: 0 }.with_trap_code(Some(TrapCode::HEAP_OUT_OF_BOUNDS))125}126127/// Create a set of flags representing an access from a "trusted" address, meaning it's128/// known to be aligned and non-trapping.129pub const fn trusted() -> Self {130Self::new().with_notrap().with_aligned()131}132133/// Read a flag bit.134const fn read_bit(self, bit: u16) -> bool {135self.bits & bit != 0136}137138/// Return a new `MemFlags` with this flag bit set.139const fn with_bit(mut self, bit: u16) -> Self {140self.bits |= bit;141self142}143144/// Reads the alias region that this memory operation works with.145pub const fn alias_region(self) -> Option<AliasRegion> {146AliasRegion::from_bits(((self.bits & MASK_ALIAS_REGION) >> ALIAS_REGION_OFFSET) as u8)147}148149/// Sets the alias region that this works on to the specified `region`.150pub const fn with_alias_region(mut self, region: Option<AliasRegion>) -> Self {151let bits = AliasRegion::to_bits(region);152self.bits &= !MASK_ALIAS_REGION;153self.bits |= (bits as u16) << ALIAS_REGION_OFFSET;154self155}156157/// Sets the alias region that this works on to the specified `region`.158pub fn set_alias_region(&mut self, region: Option<AliasRegion>) {159*self = self.with_alias_region(region);160}161162/// Set a flag bit by name.163///164/// Returns true if the flag was found and set, false for an unknown flag165/// name.166///167/// # Errors168///169/// Returns an error message if the `name` is known but couldn't be applied170/// due to it being a semantic error.171pub fn set_by_name(&mut self, name: &str) -> Result<bool, &'static str> {172*self = match name {173"notrap" => self.with_trap_code(None),174"aligned" => self.with_aligned(),175"readonly" => self.with_readonly(),176"little" => {177if self.read_bit(BIT_BIG_ENDIAN) {178return Err("cannot set both big and little endian bits");179}180self.with_endianness(Endianness::Little)181}182"big" => {183if self.read_bit(BIT_LITTLE_ENDIAN) {184return Err("cannot set both big and little endian bits");185}186self.with_endianness(Endianness::Big)187}188"heap" => {189if self.alias_region().is_some() {190return Err("cannot set more than one alias region");191}192self.with_alias_region(Some(AliasRegion::Heap))193}194"table" => {195if self.alias_region().is_some() {196return Err("cannot set more than one alias region");197}198self.with_alias_region(Some(AliasRegion::Table))199}200"vmctx" => {201if self.alias_region().is_some() {202return Err("cannot set more than one alias region");203}204self.with_alias_region(Some(AliasRegion::Vmctx))205}206"checked" => self.with_checked(),207"can_move" => self.with_can_move(),208209other => match TrapCode::from_str(other) {210Ok(code) => self.with_trap_code(Some(code)),211Err(()) => return Ok(false),212},213};214Ok(true)215}216217/// Return endianness of the memory access. This will return the endianness218/// explicitly specified by the flags if any, and will default to the native219/// endianness otherwise. The native endianness has to be provided by the220/// caller since it is not explicitly encoded in CLIF IR -- this allows a221/// front end to create IR without having to know the target endianness.222pub const fn endianness(self, native_endianness: Endianness) -> Endianness {223if self.read_bit(BIT_LITTLE_ENDIAN) {224Endianness::Little225} else if self.read_bit(BIT_BIG_ENDIAN) {226Endianness::Big227} else {228native_endianness229}230}231232/// Return endianness of the memory access, if explicitly specified.233///234/// If the endianness is not explicitly specified, this will return `None`,235/// which means "native endianness".236pub const fn explicit_endianness(self) -> Option<Endianness> {237if self.read_bit(BIT_LITTLE_ENDIAN) {238Some(Endianness::Little)239} else if self.read_bit(BIT_BIG_ENDIAN) {240Some(Endianness::Big)241} else {242None243}244}245246/// Set endianness of the memory access.247pub fn set_endianness(&mut self, endianness: Endianness) {248*self = self.with_endianness(endianness);249}250251/// Set endianness of the memory access, returning new flags.252pub const fn with_endianness(self, endianness: Endianness) -> Self {253let res = match endianness {254Endianness::Little => self.with_bit(BIT_LITTLE_ENDIAN),255Endianness::Big => self.with_bit(BIT_BIG_ENDIAN),256};257assert!(!(res.read_bit(BIT_LITTLE_ENDIAN) && res.read_bit(BIT_BIG_ENDIAN)));258res259}260261/// Test if this memory operation cannot trap.262///263/// By default `MemFlags` will assume that any load/store can trap and is264/// associated with a `TrapCode::HeapOutOfBounds` code. If the trap code is265/// configured to `None` though then this method will return `true` and266/// indicates that the memory operation will not trap.267///268/// If this returns `true` then the memory is *accessible*, which means269/// that accesses will not trap. This makes it possible to delete an unused270/// load or a dead store instruction.271///272/// This flag does *not* mean that the associated instruction can be273/// code-motioned to arbitrary places in the function so long as its data274/// dependencies are met. This only means that, given its current location275/// in the function, it will never trap. See the `can_move` method for more276/// details.277pub const fn notrap(self) -> bool {278self.trap_code().is_none()279}280281/// Sets the trap code for this `MemFlags` to `None`.282pub fn set_notrap(&mut self) {283*self = self.with_notrap();284}285286/// Sets the trap code for this `MemFlags` to `None`, returning the new287/// flags.288pub const fn with_notrap(self) -> Self {289self.with_trap_code(None)290}291292/// Is this memory operation safe to move so long as its data dependencies293/// remain satisfied?294///295/// If this is `true`, then it is okay to code motion this instruction to296/// arbitrary locations, in the function, including across blocks and297/// conditional branches, so long as data dependencies (and trap ordering,298/// if any) are upheld.299///300/// If this is `false`, then this memory operation's safety potentially301/// relies upon invariants that are not reflected in its data dependencies,302/// and therefore it is not safe to code motion this operation. For example,303/// this operation could be in a block that is dominated by a control-flow304/// bounds check, which is not reflected in its operands, and it would be305/// unsafe to code motion it above the bounds check, even if its data306/// dependencies would still be satisfied.307pub const fn can_move(self) -> bool {308self.read_bit(BIT_CAN_MOVE)309}310311/// Set the `can_move` flag.312pub const fn set_can_move(&mut self) {313*self = self.with_can_move();314}315316/// Set the `can_move` flag, returning new flags.317pub const fn with_can_move(self) -> Self {318self.with_bit(BIT_CAN_MOVE)319}320321/// Test if the `aligned` flag is set.322///323/// By default, Cranelift memory instructions work with any unaligned effective address. If the324/// `aligned` flag is set, the instruction is permitted to trap or return a wrong result if the325/// effective address is misaligned.326pub const fn aligned(self) -> bool {327self.read_bit(BIT_ALIGNED)328}329330/// Set the `aligned` flag.331pub fn set_aligned(&mut self) {332*self = self.with_aligned();333}334335/// Set the `aligned` flag, returning new flags.336pub const fn with_aligned(self) -> Self {337self.with_bit(BIT_ALIGNED)338}339340/// Test if the `readonly` flag is set.341///342/// Loads with this flag have no memory dependencies.343/// This results in undefined behavior if the dereferenced memory is mutated at any time344/// between when the function is called and when it is exited.345pub const fn readonly(self) -> bool {346self.read_bit(BIT_READONLY)347}348349/// Set the `readonly` flag.350pub fn set_readonly(&mut self) {351*self = self.with_readonly();352}353354/// Set the `readonly` flag, returning new flags.355pub const fn with_readonly(self) -> Self {356self.with_bit(BIT_READONLY)357}358359/// Test if the `checked` bit is set.360///361/// Loads and stores with this flag are verified to access362/// pointers only with a validated `PointsTo` fact attached, and363/// with that fact validated, when using the proof-carrying-code364/// framework. If initial facts on program inputs are correct365/// (i.e., correctly denote the shape and types of data structures366/// in memory), and if PCC validates the compiled output, then all367/// `checked`-marked memory accesses are guaranteed (up to the368/// checker's correctness) to access valid memory. This can be369/// used to ensure memory safety and sandboxing.370pub const fn checked(self) -> bool {371self.read_bit(BIT_CHECKED)372}373374/// Set the `checked` bit.375pub fn set_checked(&mut self) {376*self = self.with_checked();377}378379/// Set the `checked` bit, returning new flags.380pub const fn with_checked(self) -> Self {381self.with_bit(BIT_CHECKED)382}383384/// Get the trap code to report if this memory access traps.385///386/// A `None` trap code indicates that this memory access does not trap.387pub const fn trap_code(self) -> Option<TrapCode> {388let byte = ((self.bits & MASK_TRAP_CODE) >> TRAP_CODE_OFFSET) as u8;389match NonZeroU8::new(byte) {390Some(code) => Some(TrapCode::from_raw(code)),391None => None,392}393}394395/// Configures these flags with the specified trap code `code`.396///397/// A trap code indicates that this memory operation cannot be optimized398/// away and it must "stay where it is" in the programs. Traps are399/// considered side effects, for example, and have meaning through the trap400/// code that is communicated and which instruction trapped.401pub const fn with_trap_code(mut self, code: Option<TrapCode>) -> Self {402let bits = match code {403Some(code) => code.as_raw().get() as u16,404None => 0,405};406self.bits &= !MASK_TRAP_CODE;407self.bits |= bits << TRAP_CODE_OFFSET;408self409}410}411412impl fmt::Display for MemFlags {413fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {414match self.trap_code() {415None => write!(f, " notrap")?,416// This is the default trap code, so don't print anything extra417// for this.418Some(TrapCode::HEAP_OUT_OF_BOUNDS) => {}419Some(t) => write!(f, " {t}")?,420}421if self.aligned() {422write!(f, " aligned")?;423}424if self.readonly() {425write!(f, " readonly")?;426}427if self.can_move() {428write!(f, " can_move")?;429}430if self.read_bit(BIT_BIG_ENDIAN) {431write!(f, " big")?;432}433if self.read_bit(BIT_LITTLE_ENDIAN) {434write!(f, " little")?;435}436if self.checked() {437write!(f, " checked")?;438}439match self.alias_region() {440None => {}441Some(AliasRegion::Heap) => write!(f, " heap")?,442Some(AliasRegion::Table) => write!(f, " table")?,443Some(AliasRegion::Vmctx) => write!(f, " vmctx")?,444}445Ok(())446}447}448449#[cfg(test)]450mod tests {451use super::*;452453#[test]454fn roundtrip_traps() {455for trap in TrapCode::non_user_traps().iter().copied() {456let flags = MemFlags::new().with_trap_code(Some(trap));457assert_eq!(flags.trap_code(), Some(trap));458}459let flags = MemFlags::new().with_trap_code(None);460assert_eq!(flags.trap_code(), None);461}462463#[test]464fn cannot_set_big_and_little() {465let mut big = MemFlags::new().with_endianness(Endianness::Big);466assert!(big.set_by_name("little").is_err());467468let mut little = MemFlags::new().with_endianness(Endianness::Little);469assert!(little.set_by_name("big").is_err());470}471472#[test]473fn only_one_region() {474let mut big = MemFlags::new().with_alias_region(Some(AliasRegion::Heap));475assert!(big.set_by_name("table").is_err());476assert!(big.set_by_name("vmctx").is_err());477478let mut big = MemFlags::new().with_alias_region(Some(AliasRegion::Table));479assert!(big.set_by_name("heap").is_err());480assert!(big.set_by_name("vmctx").is_err());481482let mut big = MemFlags::new().with_alias_region(Some(AliasRegion::Vmctx));483assert!(big.set_by_name("heap").is_err());484assert!(big.set_by_name("table").is_err());485}486}487488489