Remove heaps from core Cranelift, push them into cranelift-wasm (#5386)

* cranelift-wasm: translate Wasm loads into lower-level CLIF operations

Rather than using `heap_{load,store,addr}`.

* cranelift: Remove the `heap_{addr,load,store}` instructions

These are now legalized in the `cranelift-wasm` frontend.

* cranelift: Remove the `ir::Heap` entity from CLIF

* Port basic memory operation tests to .wat filetests

* Remove test for verifying CLIF heaps

* Remove `heap_addr` from replace_branching_instructions_and_cfg_predecessors.clif test

* Remove `heap_addr` from readonly.clif test

* Remove `heap_addr` from `table_addr.clif` test

* Remove `heap_addr` from the simd-fvpromote_low.clif test

* Remove `heap_addr` from simd-fvdemote.clif test

* Remove `heap_addr` from the load-op-store.clif test

* Remove the CLIF heap runtest

* Remove `heap_addr` from the global_value.clif test

* Remove `heap_addr` from fpromote.clif runtests

* Remove `heap_addr` from fdemote.clif runtests

* Remove `heap_addr` from memory.clif parser test

* Remove `heap_addr` from reject_load_readonly.clif test

* Remove `heap_addr` from reject_load_notrap.clif test

* Remove `heap_addr` from load_readonly_notrap.clif test

* Remove `static-heap-without-guard-pages.clif` test

Will be subsumed when we port `make-heap-load-store-tests.sh` to generating
`.wat` tests.

* Remove `static-heap-with-guard-pages.clif` test

Will be subsumed when we port `make-heap-load-store-tests.sh` over to `.wat`
tests.

* Remove more heap tests

These will be subsumed by porting `make-heap-load-store-tests.sh` over to `.wat`
tests.

* Remove `heap_addr` from `simple-alias.clif` test

* Remove `heap_addr` from partial-redundancy.clif test

* Remove `heap_addr` from multiple-blocks.clif test

* Remove `heap_addr` from fence.clif test

* Remove `heap_addr` from extends.clif test

* Remove runtests that rely on heaps

Heaps are not a thing in CLIF or the interpreter anymore

* Add generated load/store `.wat` tests

* Enable memory-related wasm features in `.wat` tests

* Remove CLIF heap from fcmp-mem-bug.clif test

* Add a mode for compiling `.wat` all the way to assembly in filetests

* Also generate WAT to assembly tests in `make-load-store-tests.sh`

* cargo fmt

* Reinstate `f{de,pro}mote.clif` tests without the heap bits

* Remove undefined doc link

* Remove outdated SVG and dot file from docs

* Add docs about `None` returns for base address computation helpers

* Factor out `env.heap_access_spectre_mitigation()` to a local

* Expand docs for `FuncEnvironment::heaps` trait method

* Restore f{de,pro}mote+load clif runtests with stack memory
This commit is contained in:
Nick Fitzgerald
2022-12-14 16:26:45 -08:00
committed by GitHub
parent e03d65cca7
commit c0b587ac5f
198 changed files with 2494 additions and 4232 deletions

View File

@@ -4,12 +4,11 @@ use crate::entity::{self, PrimaryMap, SecondaryMap};
use crate::ir;
use crate::ir::builder::ReplaceBuilder;
use crate::ir::dynamic_type::{DynamicTypeData, DynamicTypes};
use crate::ir::immediates::HeapImmData;
use crate::ir::instructions::{BranchInfo, CallInfo, InstructionData};
use crate::ir::{types, ConstantData, ConstantPool, Immediate};
use crate::ir::{
Block, DynamicType, FuncRef, HeapImm, Inst, SigRef, Signature, Type, Value,
ValueLabelAssignments, ValueList, ValueListPool,
Block, DynamicType, FuncRef, Inst, SigRef, Signature, Type, Value, ValueLabelAssignments,
ValueList, ValueListPool,
};
use crate::ir::{ExtFuncData, RelSourceLoc};
use crate::packed_option::ReservedValue;
@@ -84,9 +83,6 @@ pub struct DataFlowGraph {
/// Stores large immediates that otherwise will not fit on InstructionData
pub immediates: PrimaryMap<Immediate, ConstantData>,
/// Out-of-line heap access immediates that don't fit in `InstructionData`.
pub heap_imms: PrimaryMap<HeapImm, HeapImmData>,
}
impl DataFlowGraph {
@@ -105,7 +101,6 @@ impl DataFlowGraph {
values_labels: None,
constants: ConstantPool::new(),
immediates: PrimaryMap::new(),
heap_imms: PrimaryMap::new(),
}
}

View File

@@ -368,60 +368,6 @@ impl SigRef {
}
}
/// An opaque reference to a [heap](https://en.wikipedia.org/wiki/Memory_management#DYNAMIC).
///
/// Heaps are used to access dynamically allocated memory through
/// [`heap_addr`](super::InstBuilder::heap_addr).
///
/// To create a heap, use [`FunctionBuilder::create_heap`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.create_heap).
///
/// While the order is stable, it is arbitrary.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Heap(u32);
entity_impl!(Heap, "heap");
impl Heap {
/// Create a new heap reference from its number.
///
/// This method is for use by the parser.
pub fn with_number(n: u32) -> Option<Self> {
if n < u32::MAX {
Some(Self(n))
} else {
None
}
}
}
/// An opaque reference to some out-of-line immediates for `heap_{load,store}`
/// instructions.
///
/// These immediates are too large to store in
/// [`InstructionData`](super::instructions::InstructionData) and therefore must
/// be tracked separately in
/// [`DataFlowGraph::heap_imms`](super::dfg::DataFlowGraph). `HeapImm` provides
/// a way to reference values stored there.
///
/// While the order is stable, it is arbitrary.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct HeapImm(u32);
entity_impl!(HeapImm, "heap_imm");
impl HeapImm {
/// Create a new `HeapImm` reference from its number.
///
/// This method is for use by the parser.
pub fn with_number(n: u32) -> Option<Self> {
if n < u32::MAX {
Some(Self(n))
} else {
None
}
}
}
/// An opaque reference to a [WebAssembly
/// table](https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format#WebAssembly_tables).
///
@@ -477,8 +423,6 @@ pub enum AnyEntity {
FuncRef(FuncRef),
/// A function call signature.
SigRef(SigRef),
/// A heap.
Heap(Heap),
/// A table.
Table(Table),
/// A function's stack limit
@@ -500,7 +444,6 @@ impl fmt::Display for AnyEntity {
Self::Constant(r) => r.fmt(f),
Self::FuncRef(r) => r.fmt(f),
Self::SigRef(r) => r.fmt(f),
Self::Heap(r) => r.fmt(f),
Self::Table(r) => r.fmt(f),
Self::StackLimit => write!(f, "stack_limit"),
}
@@ -579,12 +522,6 @@ impl From<SigRef> for AnyEntity {
}
}
impl From<Heap> for AnyEntity {
fn from(r: Heap) -> Self {
Self::Heap(r)
}
}
impl From<Table> for AnyEntity {
fn from(r: Table) -> Self {
Self::Table(r)

View File

@@ -8,8 +8,8 @@ use crate::ir;
use crate::ir::JumpTables;
use crate::ir::{
instructions::BranchInfo, Block, DynamicStackSlot, DynamicStackSlotData, DynamicType,
ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Heap, HeapData, Inst, InstructionData,
JumpTable, JumpTableData, Opcode, SigRef, StackSlot, StackSlotData, Table, TableData, Type,
ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Inst, InstructionData, JumpTable,
JumpTableData, Opcode, SigRef, StackSlot, StackSlotData, Table, TableData, Type,
};
use crate::ir::{DataFlowGraph, Layout, Signature};
use crate::ir::{DynamicStackSlots, SourceLocs, StackSlots};
@@ -170,9 +170,6 @@ pub struct FunctionStencil {
/// Global values referenced.
pub global_values: PrimaryMap<ir::GlobalValue, ir::GlobalValueData>,
/// Heaps referenced.
pub heaps: PrimaryMap<ir::Heap, ir::HeapData>,
/// Tables referenced.
pub tables: PrimaryMap<ir::Table, ir::TableData>,
@@ -205,7 +202,6 @@ impl FunctionStencil {
self.sized_stack_slots.clear();
self.dynamic_stack_slots.clear();
self.global_values.clear();
self.heaps.clear();
self.tables.clear();
self.jump_tables.clear();
self.dfg.clear();
@@ -261,11 +257,6 @@ impl FunctionStencil {
.concrete()
}
/// Declares a heap accessible to the function.
pub fn create_heap(&mut self, data: HeapData) -> Heap {
self.heaps.push(data)
}
/// Declares a table accessible to the function.
pub fn create_table(&mut self, data: TableData) -> Table {
self.tables.push(data)
@@ -447,7 +438,6 @@ impl Function {
sized_stack_slots: StackSlots::new(),
dynamic_stack_slots: DynamicStackSlots::new(),
global_values: PrimaryMap::new(),
heaps: PrimaryMap::new(),
tables: PrimaryMap::new(),
jump_tables: PrimaryMap::new(),
dfg: DataFlowGraph::new(),

View File

@@ -1,67 +0,0 @@
//! Heaps.
use crate::ir::immediates::Uimm64;
use crate::ir::{GlobalValue, Type};
use core::fmt;
#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};
/// Information about a heap declaration.
#[derive(Clone, PartialEq, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct HeapData {
/// The address of the start of the heap's storage.
pub base: GlobalValue,
/// Guaranteed minimum heap size in bytes. Heap accesses before `min_size` don't need bounds
/// checking.
pub min_size: Uimm64,
/// Size in bytes of the offset-guard pages following the heap.
pub offset_guard_size: Uimm64,
/// Heap style, with additional style-specific info.
pub style: HeapStyle,
/// The index type for the heap.
pub index_type: Type,
}
/// Style of heap including style-specific information.
#[derive(Clone, PartialEq, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum HeapStyle {
/// A dynamic heap can be relocated to a different base address when it is grown.
Dynamic {
/// Global value providing the current bound of the heap in bytes.
bound_gv: GlobalValue,
},
/// A static heap has a fixed base address and a number of not-yet-allocated pages before the
/// offset-guard pages.
Static {
/// Heap bound in bytes. The offset-guard pages are allocated after the bound.
bound: Uimm64,
},
}
impl fmt::Display for HeapData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self.style {
HeapStyle::Dynamic { .. } => "dynamic",
HeapStyle::Static { .. } => "static",
})?;
write!(f, " {}, min {}", self.base, self.min_size)?;
match self.style {
HeapStyle::Dynamic { bound_gv } => write!(f, ", bound {}", bound_gv)?,
HeapStyle::Static { bound } => write!(f, ", bound {}", bound)?,
}
write!(
f,
", offset_guard {}, index_type {}",
self.offset_guard_size, self.index_type
)
}
}

View File

@@ -4,7 +4,6 @@
//! Each type here should have a corresponding definition in the
//! `cranelift-codegen/meta/src/shared/immediates` crate in the meta language.
use crate::ir;
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::convert::TryFrom;
@@ -1178,18 +1177,6 @@ impl Not for Ieee64 {
}
}
/// Out-of-line heap access immediates.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct HeapImmData {
/// The memory flags for the heap access.
pub flags: ir::MemFlags,
/// The heap being accessed.
pub heap: ir::Heap,
/// The static offset added to the heap access's index.
pub offset: Uimm32,
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -11,7 +11,6 @@ mod extfunc;
mod extname;
pub mod function;
mod globalvalue;
mod heap;
pub mod immediates;
pub mod instructions;
pub mod jumptable;
@@ -37,8 +36,8 @@ pub use crate::ir::constant::{ConstantData, ConstantPool};
pub use crate::ir::dfg::{DataFlowGraph, ValueDef};
pub use crate::ir::dynamic_type::{dynamic_to_fixed, DynamicTypeData, DynamicTypes};
pub use crate::ir::entities::{
Block, Constant, DynamicStackSlot, DynamicType, FuncRef, GlobalValue, Heap, HeapImm, Immediate,
Inst, JumpTable, SigRef, StackSlot, Table, UserExternalNameRef, Value,
Block, Constant, DynamicStackSlot, DynamicType, FuncRef, GlobalValue, Immediate, Inst,
JumpTable, SigRef, StackSlot, Table, UserExternalNameRef, Value,
};
pub use crate::ir::extfunc::{
AbiParam, ArgumentExtension, ArgumentPurpose, ExtFuncData, Signature,
@@ -46,7 +45,6 @@ pub use crate::ir::extfunc::{
pub use crate::ir::extname::{ExternalName, UserExternalName, UserFuncName};
pub use crate::ir::function::{DisplayFunctionAnnotations, Function};
pub use crate::ir::globalvalue::GlobalValueData;
pub use crate::ir::heap::{HeapData, HeapStyle};
pub use crate::ir::instructions::{
InstructionData, Opcode, ValueList, ValueListPool, VariableArgs,
};

View File

@@ -1,489 +0,0 @@
//! Legalization of heaps.
//!
//! This module exports the `expand_heap_addr` function which transforms a `heap_addr`
//! instruction into code that depends on the kind of heap referenced.
use crate::cursor::{Cursor, FuncCursor};
use crate::flowgraph::ControlFlowGraph;
use crate::ir::condcodes::IntCC;
use crate::ir::immediates::{HeapImmData, Offset32, Uimm32, Uimm8};
use crate::ir::{self, InstBuilder, RelSourceLoc};
use crate::isa::TargetIsa;
use crate::trace;
/// Expand a `heap_load` instruction according to the definition of the heap.
pub fn expand_heap_load(
inst: ir::Inst,
func: &mut ir::Function,
cfg: &mut ControlFlowGraph,
isa: &dyn TargetIsa,
heap_imm: ir::HeapImm,
index: ir::Value,
) {
let HeapImmData {
flags,
heap,
offset,
} = func.dfg.heap_imms[heap_imm];
let result_ty = func.dfg.ctrl_typevar(inst);
let access_size = result_ty.bytes();
let access_size = u8::try_from(access_size).unwrap();
let mut pos = FuncCursor::new(func).at_inst(inst);
pos.use_srcloc(inst);
let addr =
bounds_check_and_compute_addr(&mut pos, cfg, isa, heap, index, offset.into(), access_size);
pos.func
.dfg
.replace(inst)
.load(result_ty, flags, addr, Offset32::new(0));
}
/// Expand a `heap_store` instruction according to the definition of the heap.
pub fn expand_heap_store(
inst: ir::Inst,
func: &mut ir::Function,
cfg: &mut ControlFlowGraph,
isa: &dyn TargetIsa,
heap_imm: ir::HeapImm,
index: ir::Value,
value: ir::Value,
) {
let HeapImmData {
flags,
heap,
offset,
} = func.dfg.heap_imms[heap_imm];
let store_ty = func.dfg.value_type(value);
let access_size = u8::try_from(store_ty.bytes()).unwrap();
let mut pos = FuncCursor::new(func).at_inst(inst);
pos.use_srcloc(inst);
let addr =
bounds_check_and_compute_addr(&mut pos, cfg, isa, heap, index, offset.into(), access_size);
pos.func
.dfg
.replace(inst)
.store(flags, value, addr, Offset32::new(0));
}
/// Expand a `heap_addr` instruction according to the definition of the heap.
pub fn expand_heap_addr(
inst: ir::Inst,
func: &mut ir::Function,
cfg: &mut ControlFlowGraph,
isa: &dyn TargetIsa,
heap: ir::Heap,
index: ir::Value,
offset: Uimm32,
access_size: Uimm8,
) {
let mut pos = FuncCursor::new(func).at_inst(inst);
pos.use_srcloc(inst);
let addr =
bounds_check_and_compute_addr(&mut pos, cfg, isa, heap, index, offset.into(), access_size);
// Replace the `heap_addr` and its result value with the legalized native
// address.
let addr_inst = pos.func.dfg.value_def(addr).unwrap_inst();
pos.func.dfg.replace_with_aliases(inst, addr_inst);
pos.func.layout.remove_inst(inst);
}
/// Helper used to emit bounds checks (as necessary) and compute the native
/// address of a heap access.
///
/// Returns the `ir::Value` holding the native address of the heap access.
fn bounds_check_and_compute_addr(
pos: &mut FuncCursor,
cfg: &mut ControlFlowGraph,
isa: &dyn TargetIsa,
heap: ir::Heap,
// Dynamic operand indexing into the heap.
index: ir::Value,
// Static immediate added to the index.
offset: u32,
// Static size of the heap access.
access_size: u8,
) -> ir::Value {
let pointer_type = isa.pointer_type();
let spectre = isa.flags().enable_heap_access_spectre_mitigation();
let offset_and_size = offset_plus_size(offset, access_size);
let ir::HeapData {
base: _,
min_size,
offset_guard_size: guard_size,
style,
index_type,
} = pos.func.heaps[heap].clone();
let index = cast_index_to_pointer_ty(index, index_type, pointer_type, pos);
// We need to emit code that will trap (or compute an address that will trap
// when accessed) if
//
// index + offset + access_size > bound
//
// or if the `index + offset + access_size` addition overflows.
//
// Note that we ultimately want a 64-bit integer (we only target 64-bit
// architectures at the moment) and that `offset` is a `u32` and
// `access_size` is a `u8`. This means that we can add the latter together
// as `u64`s without fear of overflow, and we only have to be concerned with
// whether adding in `index` will overflow.
//
// Finally, the following right-hand sides of the matches do have a little
// bit of duplicated code across them, but I think writing it this way is
// worth it for readability and seeing very clearly each of our cases for
// different bounds checks and optimizations of those bounds checks. It is
// intentionally written in a straightforward case-matching style that will
// hopefully make it easy to port to ISLE one day.
match style {
// ====== Dynamic Memories ======
//
// 1. First special case for when `offset + access_size == 1`:
//
// index + 1 > bound
// ==> index >= bound
//
// 1.a. When Spectre mitigations are enabled, avoid duplicating
// bounds checks between the mitigations and the regular bounds
// checks.
ir::HeapStyle::Dynamic { bound_gv } if offset_and_size == 1 && spectre => {
let bound = pos.ins().global_value(pointer_type, bound_gv);
compute_addr(
isa,
pos,
heap,
pointer_type,
index,
offset,
Some(SpectreOobComparison {
cc: IntCC::UnsignedGreaterThanOrEqual,
lhs: index,
rhs: bound,
}),
)
}
// 1.b. Emit explicit `index >= bound` bounds checks.
ir::HeapStyle::Dynamic { bound_gv } if offset_and_size == 1 => {
let bound = pos.ins().global_value(pointer_type, bound_gv);
let oob = pos
.ins()
.icmp(IntCC::UnsignedGreaterThanOrEqual, index, bound);
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds);
compute_addr(isa, pos, heap, pointer_type, index, offset, None)
}
// 2. Second special case for when `offset + access_size <= min_size`.
//
// We know that `bound >= min_size`, so we can do the following
// comparison, without fear of the right-hand side wrapping around:
//
// index + offset + access_size > bound
// ==> index > bound - (offset + access_size)
//
// 2.a. Dedupe bounds checks with Spectre mitigations.
ir::HeapStyle::Dynamic { bound_gv } if offset_and_size <= min_size.into() && spectre => {
let bound = pos.ins().global_value(pointer_type, bound_gv);
let adjusted_bound = pos.ins().iadd_imm(bound, -(offset_and_size as i64));
compute_addr(
isa,
pos,
heap,
pointer_type,
index,
offset,
Some(SpectreOobComparison {
cc: IntCC::UnsignedGreaterThan,
lhs: index,
rhs: adjusted_bound,
}),
)
}
// 2.b. Emit explicit `index > bound - (offset + access_size)` bounds
// checks.
ir::HeapStyle::Dynamic { bound_gv } if offset_and_size <= min_size.into() => {
let bound = pos.ins().global_value(pointer_type, bound_gv);
let adjusted_bound = pos.ins().iadd_imm(bound, -(offset_and_size as i64));
let oob = pos
.ins()
.icmp(IntCC::UnsignedGreaterThan, index, adjusted_bound);
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds);
compute_addr(isa, pos, heap, pointer_type, index, offset, None)
}
// 3. General case for dynamic memories:
//
// index + offset + access_size > bound
//
// And we have to handle the overflow case in the left-hand side.
//
// 3.a. Dedupe bounds checks with Spectre mitigations.
ir::HeapStyle::Dynamic { bound_gv } if spectre => {
let access_size_val = pos.ins().iconst(pointer_type, offset_and_size as i64);
let adjusted_index =
pos.ins()
.uadd_overflow_trap(index, access_size_val, ir::TrapCode::HeapOutOfBounds);
let bound = pos.ins().global_value(pointer_type, bound_gv);
compute_addr(
isa,
pos,
heap,
pointer_type,
index,
offset,
Some(SpectreOobComparison {
cc: IntCC::UnsignedGreaterThan,
lhs: adjusted_index,
rhs: bound,
}),
)
}
// 3.b. Emit an explicit `index + offset + access_size > bound`
// check.
ir::HeapStyle::Dynamic { bound_gv } => {
let access_size_val = pos.ins().iconst(pointer_type, offset_and_size as i64);
let adjusted_index =
pos.ins()
.uadd_overflow_trap(index, access_size_val, ir::TrapCode::HeapOutOfBounds);
let bound = pos.ins().global_value(pointer_type, bound_gv);
let oob = pos
.ins()
.icmp(IntCC::UnsignedGreaterThan, adjusted_index, bound);
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds);
compute_addr(isa, pos, heap, pointer_type, index, offset, None)
}
// ====== Static Memories ======
//
// With static memories we know the size of the heap bound at compile
// time.
//
// 1. First special case: trap immediately if `offset + access_size >
// bound`, since we will end up being out-of-bounds regardless of the
// given `index`.
ir::HeapStyle::Static { bound } if offset_and_size > bound.into() => {
pos.ins().trap(ir::TrapCode::HeapOutOfBounds);
// Split the block, as the trap is a terminator instruction.
let curr_block = pos.current_block().expect("Cursor is not in a block");
let new_block = pos.func.dfg.make_block();
pos.insert_block(new_block);
cfg.recompute_block(pos.func, curr_block);
cfg.recompute_block(pos.func, new_block);
let null = pos.ins().iconst(pointer_type, 0);
return null;
}
// 2. Second special case for when we can completely omit explicit
// bounds checks for 32-bit static memories.
//
// First, let's rewrite our comparison to move all of the constants
// to one side:
//
// index + offset + access_size > bound
// ==> index > bound - (offset + access_size)
//
// We know the subtraction on the right-hand side won't wrap because
// we didn't hit the first special case.
//
// Additionally, we add our guard pages (if any) to the right-hand
// side, since we can rely on the virtual memory subsystem at runtime
// to catch out-of-bound accesses within the range `bound .. bound +
// guard_size`. So now we are dealing with
//
// index > bound + guard_size - (offset + access_size)
//
// Note that `bound + guard_size` cannot overflow for
// correctly-configured heaps, as otherwise the heap wouldn't fit in
// a 64-bit memory space.
//
// The complement of our should-this-trap comparison expression is
// the should-this-not-trap comparison expression:
//
// index <= bound + guard_size - (offset + access_size)
//
// If we know the right-hand side is greater than or equal to
// `u32::MAX`, then
//
// index <= u32::MAX <= bound + guard_size - (offset + access_size)
//
// This expression is always true when the heap is indexed with
// 32-bit integers because `index` cannot be larger than
// `u32::MAX`. This means that `index` is always either in bounds or
// within the guard page region, neither of which require emitting an
// explicit bounds check.
ir::HeapStyle::Static { bound }
if index_type == ir::types::I32
&& u64::from(u32::MAX)
<= u64::from(bound) + u64::from(guard_size) - offset_and_size =>
{
compute_addr(isa, pos, heap, pointer_type, index, offset, None)
}
// 3. General case for static memories.
//
// We have to explicitly test whether
//
// index > bound - (offset + access_size)
//
// and trap if so.
//
// Since we have to emit explicit bounds checks, we might as well be
// precise, not rely on the virtual memory subsystem at all, and not
// factor in the guard pages here.
//
// 3.a. Dedupe the Spectre mitigation and the explicit bounds check.
ir::HeapStyle::Static { bound } if spectre => {
// NB: this subtraction cannot wrap because we didn't hit the first
// special case.
let adjusted_bound = u64::from(bound) - offset_and_size;
let adjusted_bound = pos.ins().iconst(pointer_type, adjusted_bound as i64);
compute_addr(
isa,
pos,
heap,
pointer_type,
index,
offset,
Some(SpectreOobComparison {
cc: IntCC::UnsignedGreaterThan,
lhs: index,
rhs: adjusted_bound,
}),
)
}
// 3.b. Emit the explicit `index > bound - (offset + access_size)`
// check.
ir::HeapStyle::Static { bound } => {
// See comment in 3.a. above.
let adjusted_bound = u64::from(bound) - offset_and_size;
let oob = pos
.ins()
.icmp_imm(IntCC::UnsignedGreaterThan, index, adjusted_bound as i64);
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds);
compute_addr(isa, pos, heap, pointer_type, index, offset, None)
}
}
}
fn cast_index_to_pointer_ty(
index: ir::Value,
index_ty: ir::Type,
pointer_ty: ir::Type,
pos: &mut FuncCursor,
) -> ir::Value {
if index_ty == pointer_ty {
return index;
}
// Note that using 64-bit heaps on a 32-bit host is not currently supported,
// would require at least a bounds check here to ensure that the truncation
// from 64-to-32 bits doesn't lose any upper bits. For now though we're
// mostly interested in the 32-bit-heaps-on-64-bit-hosts cast.
assert!(index_ty.bits() < pointer_ty.bits());
// Convert `index` to `addr_ty`.
let extended_index = pos.ins().uextend(pointer_ty, index);
// Add debug value-label alias so that debuginfo can name the extended
// value as the address
let loc = pos.srcloc();
let loc = RelSourceLoc::from_base_offset(pos.func.params.base_srcloc(), loc);
pos.func
.stencil
.dfg
.add_value_label_alias(extended_index, loc, index);
extended_index
}
struct SpectreOobComparison {
cc: IntCC,
lhs: ir::Value,
rhs: ir::Value,
}
/// Emit code for the base address computation of a `heap_addr` instruction,
/// without any bounds checks (other than optional Spectre mitigations).
fn compute_addr(
isa: &dyn TargetIsa,
pos: &mut FuncCursor,
heap: ir::Heap,
addr_ty: ir::Type,
index: ir::Value,
offset: u32,
// If we are performing Spectre mitigation with conditional selects, the
// values to compare and the condition code that indicates an out-of bounds
// condition; on this condition, the conditional move will choose a
// speculatively safe address (a zero / null pointer) instead.
spectre_oob_comparison: Option<SpectreOobComparison>,
) -> ir::Value {
debug_assert_eq!(pos.func.dfg.value_type(index), addr_ty);
// Add the heap base address base
let base = if isa.flags().enable_pinned_reg() && isa.flags().use_pinned_reg_as_heap_base() {
let base = pos.ins().get_pinned_reg(isa.pointer_type());
trace!(" inserting: {}", pos.func.dfg.display_value_inst(base));
base
} else {
let base_gv = pos.func.heaps[heap].base;
let base = pos.ins().global_value(addr_ty, base_gv);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(base));
base
};
if let Some(SpectreOobComparison { cc, lhs, rhs }) = spectre_oob_comparison {
let final_base = pos.ins().iadd(base, index);
// NB: The addition of the offset immediate must happen *before* the
// `select_spectre_guard`. If it happens after, then we potentially are
// letting speculative execution read the whole first 4GiB of memory.
let final_addr = if offset == 0 {
final_base
} else {
let final_addr = pos.ins().iadd_imm(final_base, offset as i64);
trace!(
" inserting: {}",
pos.func.dfg.display_value_inst(final_addr)
);
final_addr
};
let zero = pos.ins().iconst(addr_ty, 0);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(zero));
let cmp = pos.ins().icmp(cc, lhs, rhs);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(cmp));
let value = pos.ins().select_spectre_guard(cmp, zero, final_addr);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(value));
value
} else if offset == 0 {
let addr = pos.ins().iadd(base, index);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(addr));
addr
} else {
let final_base = pos.ins().iadd(base, index);
trace!(
" inserting: {}",
pos.func.dfg.display_value_inst(final_base)
);
let addr = pos.ins().iadd_imm(final_base, offset as i64);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(addr));
addr
}
}
#[inline]
fn offset_plus_size(offset: u32, size: u8) -> u64 {
// Cannot overflow because we are widening to `u64`.
offset as u64 + size as u64
}

View File

@@ -22,11 +22,9 @@ use crate::isa::TargetIsa;
use crate::trace;
mod globalvalue;
mod heap;
mod table;
use self::globalvalue::expand_global_value;
use self::heap::{expand_heap_addr, expand_heap_load, expand_heap_store};
use self::table::expand_table_addr;
fn imm_const(pos: &mut FuncCursor, arg: Value, imm: Imm64, is_signed: bool) -> Value {
@@ -71,23 +69,6 @@ pub fn simple_legalize(func: &mut ir::Function, cfg: &mut ControlFlowGraph, isa:
opcode: ir::Opcode::GlobalValue,
global_value,
} => expand_global_value(inst, &mut pos.func, isa, global_value),
InstructionData::HeapAddr {
opcode: ir::Opcode::HeapAddr,
heap,
arg,
offset,
size,
} => expand_heap_addr(inst, &mut pos.func, cfg, isa, heap, arg, offset, size),
InstructionData::HeapLoad {
opcode: ir::Opcode::HeapLoad,
heap_imm,
arg,
} => expand_heap_load(inst, &mut pos.func, cfg, isa, heap_imm, arg),
InstructionData::HeapStore {
opcode: ir::Opcode::HeapStore,
heap_imm,
args,
} => expand_heap_store(inst, &mut pos.func, cfg, isa, heap_imm, args[0], args[1]),
InstructionData::StackLoad {
opcode: ir::Opcode::StackLoad,
stack_slot,

View File

@@ -8,8 +8,8 @@ pub use crate::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32, Uimm32, Uimm64,
pub use crate::ir::types::*;
pub use crate::ir::{
dynamic_to_fixed, AtomicRmwOp, Block, Constant, DataFlowGraph, DynamicStackSlot, FuncRef,
GlobalValue, Heap, HeapImm, Immediate, InstructionData, JumpTable, MemFlags, Opcode, StackSlot,
Table, TrapCode, Type, Value,
GlobalValue, Immediate, InstructionData, JumpTable, MemFlags, Opcode, StackSlot, Table,
TrapCode, Type, Value,
};
use crate::isle_common_prelude_methods;
use crate::machinst::isle::*;

View File

@@ -518,9 +518,8 @@ mod tests {
fn display_default() {
let b = builder();
let f = Flags::new(b);
assert_eq!(
f.to_string(),
r#"[shared]
let actual = f.to_string();
let expected = r#"[shared]
opt_level = "none"
tls_model = "none"
libcall_call_conv = "isa_default"
@@ -537,7 +536,6 @@ avoid_div_traps = false
enable_float = true
enable_nan_canonicalization = false
enable_pinned_reg = false
use_pinned_reg_as_heap_base = false
enable_simd = false
enable_atomics = true
enable_safepoints = false
@@ -551,8 +549,15 @@ enable_jump_tables = true
enable_heap_access_spectre_mitigation = true
enable_table_access_spectre_mitigation = true
enable_incremental_compilation_cache_checks = false
"#
);
"#;
if actual != expected {
panic!(
"Default settings do not match expectations:\n\n{}",
similar::TextDiff::from_lines(expected, &actual)
.unified_diff()
.header("expected", "actual")
);
}
assert_eq!(f.opt_level(), super::OptLevel::None);
assert_eq!(f.enable_simd(), false);
}

View File

@@ -63,7 +63,6 @@ use crate::entity::SparseSet;
use crate::flowgraph::{BlockPredecessor, ControlFlowGraph};
use crate::ir;
use crate::ir::entities::AnyEntity;
use crate::ir::immediates::HeapImmData;
use crate::ir::instructions::{BranchInfo, CallInfo, InstructionFormat, ResolvedConstraint};
use crate::ir::{
types, ArgumentPurpose, Block, Constant, DynamicStackSlot, FuncRef, Function, GlobalValue,
@@ -404,49 +403,6 @@ impl<'a> Verifier<'a> {
Ok(())
}
fn verify_heaps(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
if let Some(isa) = self.isa {
for (heap, heap_data) in &self.func.heaps {
let base = heap_data.base;
if !self.func.global_values.is_valid(base) {
return errors.nonfatal((heap, format!("invalid base global value {}", base)));
}
let pointer_type = isa.pointer_type();
let base_type = self.func.global_values[base].global_type(isa);
if base_type != pointer_type {
errors.report((
heap,
format!(
"heap base has type {}, which is not the pointer type {}",
base_type, pointer_type
),
));
}
if let ir::HeapStyle::Dynamic { bound_gv, .. } = heap_data.style {
if !self.func.global_values.is_valid(bound_gv) {
return errors
.nonfatal((heap, format!("invalid bound global value {}", bound_gv)));
}
let bound_type = self.func.global_values[bound_gv].global_type(isa);
if pointer_type != bound_type {
errors.report((
heap,
format!(
"heap pointer type {} differs from the type of its bound, {}",
pointer_type, bound_type
),
));
}
}
}
}
Ok(())
}
fn verify_tables(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
if let Some(isa) = self.isa {
for (table, table_data) in &self.func.tables {
@@ -676,13 +632,6 @@ impl<'a> Verifier<'a> {
UnaryGlobalValue { global_value, .. } => {
self.verify_global_value(inst, global_value, errors)?;
}
HeapLoad { heap_imm, .. } | HeapStore { heap_imm, .. } => {
let HeapImmData { heap, .. } = self.func.dfg.heap_imms[heap_imm];
self.verify_heap(inst, heap, errors)?;
}
HeapAddr { heap, .. } => {
self.verify_heap(inst, heap, errors)?;
}
TableAddr { table, .. } => {
self.verify_table(inst, table, errors)?;
}
@@ -878,19 +827,6 @@ impl<'a> Verifier<'a> {
}
}
fn verify_heap(
&self,
inst: Inst,
heap: ir::Heap,
errors: &mut VerifierErrors,
) -> VerifierStepResult<()> {
if !self.func.heaps.is_valid(heap) {
errors.nonfatal((inst, self.context(inst), format!("invalid heap {}", heap)))
} else {
Ok(())
}
}
fn verify_table(
&self,
inst: Inst,
@@ -1557,20 +1493,6 @@ impl<'a> Verifier<'a> {
_ => {}
}
}
ir::InstructionData::HeapAddr { heap, arg, .. } => {
let index_type = self.func.dfg.value_type(arg);
let heap_index_type = self.func.heaps[heap].index_type;
if index_type != heap_index_type {
return errors.nonfatal((
inst,
self.context(inst),
format!(
"index type {} differs from heap index type {}",
index_type, heap_index_type,
),
));
}
}
ir::InstructionData::TableAddr { table, arg, .. } => {
let index_type = self.func.dfg.value_type(arg);
let table_index_type = self.func.tables[table].index_type;
@@ -1775,7 +1697,6 @@ impl<'a> Verifier<'a> {
pub fn run(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
self.verify_global_values(errors)?;
self.verify_heaps(errors)?;
self.verify_tables(errors)?;
self.verify_jump_tables(errors)?;
self.typecheck_entry_block_params(errors)?;

View File

@@ -5,7 +5,6 @@
use crate::entity::SecondaryMap;
use crate::ir::entities::AnyEntity;
use crate::ir::immediates::{HeapImmData, Uimm32};
use crate::ir::{Block, DataFlowGraph, Function, Inst, SigRef, Type, Value, ValueDef};
use crate::packed_option::ReservedValue;
use alloc::string::{String, ToString};
@@ -57,13 +56,6 @@ pub trait FuncWriter {
self.write_entity_definition(w, func, gv.into(), gv_data)?;
}
for (heap, heap_data) in &func.heaps {
if !heap_data.index_type.is_invalid() {
any = true;
self.write_entity_definition(w, func, heap.into(), heap_data)?;
}
}
for (table, table_data) in &func.tables {
if !table_data.index_type.is_invalid() {
any = true;
@@ -478,54 +470,6 @@ pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt
dynamic_stack_slot,
..
} => write!(w, " {}, {}", arg, dynamic_stack_slot),
HeapLoad {
opcode: _,
heap_imm,
arg,
} => {
let HeapImmData {
flags,
heap,
offset,
} = dfg.heap_imms[heap_imm];
write!(
w,
" {heap} {flags} {arg}{optional_offset}",
optional_offset = if offset == Uimm32::from(0) {
"".to_string()
} else {
format!("+{offset}")
}
)
}
HeapStore {
opcode: _,
heap_imm,
args,
} => {
let HeapImmData {
flags,
heap,
offset,
} = dfg.heap_imms[heap_imm];
let [index, value] = args;
write!(
w,
" {heap} {flags} {index}{optional_offset}, {value}",
optional_offset = if offset == Uimm32::from(0) {
"".to_string()
} else {
format!("+{offset}")
}
)
}
HeapAddr {
heap,
arg,
offset,
size,
..
} => write!(w, " {}, {}, {}, {}", heap, arg, offset, size),
TableAddr { table, arg, .. } => write!(w, " {}, {}", table, arg),
Load {
flags, arg, offset, ..