Add a readonly flag for loads (#562)

* Add readonly MemFlag

* Add readonly flag verifier check

* Make global loads readonly

* Fix gvn to consider readonly loads
This commit is contained in:
Lachlan Sneff
2018-10-23 00:50:09 -04:00
committed by Dan Gohman
parent 3ec21459c5
commit 586a8835e9
9 changed files with 120 additions and 16 deletions

View File

@@ -507,6 +507,7 @@ Flag Description
======= =========================================== ======= ===========================================
notrap Memory is assumed to be :term:`accessible`. notrap Memory is assumed to be :term:`accessible`.
aligned Trapping allowed for misaligned accesses. aligned Trapping allowed for misaligned accesses.
readonly The data at the specified address will not modified between when this function is called and exited.
======= =========================================== ======= ===========================================
When the ``accessible`` flag is set, the behavior is undefined if the memory When the ``accessible`` flag is set, the behavior is undefined if the memory

View File

@@ -0,0 +1,25 @@
test simple-gvn
target x86_64
function %eliminate_redundant_global_loads(i32, i64 vmctx) {
gv0 = vmctx
gv1 = load.i64 notrap aligned readonly gv0
heap0 = static gv1, min 0x1_0000, bound 0x1_0000_0000, guard 0x8000_0000, index_type i32
ebb0(v0: i32, v1: i64):
v2 = heap_addr.i64 heap0, v0, 1
v3 = heap_addr.i64 heap0, v0, 1
v4 = iconst.i32 0
store.i32 notrap aligned v4, v2
store.i32 notrap aligned v4, v3
return
}
; check: v2 = heap_addr.i64 heap0, v0, 1
; check: v3 -> v2
; check: v4 = iconst.i32 0
; check: store notrap aligned v4, v2
; check: store notrap aligned v4, v2
; check: return

View File

@@ -15,7 +15,8 @@ pub enum GlobalValueData {
/// ///
/// The `base` global value is assumed to contain a pointer. This global value is computed /// The `base` global value is assumed to contain a pointer. This global value is computed
/// by loading from memory at that pointer value. The memory must be accessible, and /// by loading from memory at that pointer value. The memory must be accessible, and
/// naturally aligned to hold a value of the type. /// naturally aligned to hold a value of the type. The data at this address is assumed
/// to never change while the current function is executing.
Load { Load {
/// The base pointer global value. /// The base pointer global value.
base: GlobalValue, base: GlobalValue,
@@ -25,6 +26,9 @@ pub enum GlobalValueData {
/// Type of the loaded value. /// Type of the loaded value.
global_type: Type, global_type: Type,
/// Specifies whether the memory that this refers to is readonly, allowing for the elimination of redundant loads.
readonly: bool,
}, },
/// Value is an offset from another global value. /// Value is an offset from another global value.
@@ -90,7 +94,15 @@ impl fmt::Display for GlobalValueData {
base, base,
offset, offset,
global_type, global_type,
} => write!(f, "load.{} notrap aligned {}{}", global_type, base, offset), readonly,
} => write!(
f,
"load.{} notrap aligned {}{}{}",
global_type,
if readonly { "readonly " } else { "" },
base,
offset
),
GlobalValueData::IAddImm { GlobalValueData::IAddImm {
global_type, global_type,
base, base,

View File

@@ -5,9 +5,10 @@ use std::fmt;
enum FlagBit { enum FlagBit {
Notrap, Notrap,
Aligned, Aligned,
Readonly,
} }
const NAMES: [&str; 2] = ["notrap", "aligned"]; const NAMES: [&str; 3] = ["notrap", "aligned", "readonly"];
/// Flags for memory operations like load/store. /// Flags for memory operations like load/store.
/// ///
@@ -79,6 +80,20 @@ impl MemFlags {
pub fn set_aligned(&mut self) { pub fn set_aligned(&mut self) {
self.set(FlagBit::Aligned) self.set(FlagBit::Aligned)
} }
/// Test if the `readonly` flag is set.
///
/// Loads with this flag have no memory dependendies.
/// This results in indefined behavior if the dereferenced memory is mutated at any time
/// between when the function is called and when it is exited.
pub fn readonly(self) -> bool {
self.read(FlagBit::Readonly)
}
/// Set the `readonly` flag.
pub fn set_readonly(&mut self) {
self.set(FlagBit::Readonly)
}
} }
impl fmt::Display for MemFlags { impl fmt::Display for MemFlags {

View File

@@ -38,7 +38,8 @@ pub fn expand_global_value(
base, base,
offset, offset,
global_type, global_type,
} => load_addr(inst, func, base, offset, global_type, isa), readonly,
} => load_addr(inst, func, base, offset, global_type, readonly, isa),
ir::GlobalValueData::Symbol { .. } => symbol(inst, func, gv, isa), ir::GlobalValueData::Symbol { .. } => symbol(inst, func, gv, isa),
} }
} }
@@ -88,6 +89,7 @@ fn load_addr(
base: ir::GlobalValue, base: ir::GlobalValue,
offset: ir::immediates::Offset32, offset: ir::immediates::Offset32,
global_type: ir::Type, global_type: ir::Type,
readonly: bool,
isa: &TargetIsa, isa: &TargetIsa,
) { ) {
// We need to load a pointer from the `base` global value, so insert a new `global_value` // We need to load a pointer from the `base` global value, so insert a new `global_value`
@@ -107,10 +109,13 @@ fn load_addr(
pos.ins().global_value(ptr_ty, base) pos.ins().global_value(ptr_ty, base)
}; };
// Global-value loads are always notrap and aligned. // Global-value loads are always notrap and aligned. They may be readonly.
let mut mflags = ir::MemFlags::new(); let mut mflags = ir::MemFlags::new();
mflags.set_notrap(); mflags.set_notrap();
mflags.set_aligned(); mflags.set_aligned();
if readonly {
mflags.set_readonly();
}
// Perform the load. // Perform the load.
pos.func pos.func

View File

@@ -18,10 +18,19 @@ fn trivially_unsafe_for_gvn(opcode: Opcode) -> bool {
|| opcode.can_trap() || opcode.can_trap()
|| opcode.other_side_effects() || opcode.other_side_effects()
|| opcode.can_store() || opcode.can_store()
|| opcode.can_load()
|| opcode.writes_cpu_flags() || opcode.writes_cpu_flags()
} }
/// Test that, if the specified instruction is a load, it doesn't have the `readonly` memflag.
fn is_load_and_not_readonly(inst_data: &InstructionData) -> bool {
match *inst_data {
InstructionData::Load { flags, .. } | InstructionData::LoadComplex { flags, .. } => {
!flags.readonly()
}
_ => inst_data.opcode().can_load(),
}
}
/// Wrapper around `InstructionData` which implements `Eq` and `Hash` /// Wrapper around `InstructionData` which implements `Eq` and `Hash`
#[derive(Clone)] #[derive(Clone)]
struct HashKey<'a, 'f: 'a> { struct HashKey<'a, 'f: 'a> {
@@ -91,14 +100,23 @@ pub fn do_simple_gvn(func: &mut Function, domtree: &mut DominatorTree) {
let func = Ref::map(pos.borrow(), |pos| &pos.func); let func = Ref::map(pos.borrow(), |pos| &pos.func);
let opcode = func.dfg[inst].opcode(); let opcode = func.dfg[inst].opcode();
if opcode.is_branch() && !opcode.is_terminator() { if opcode.is_branch() && !opcode.is_terminator() {
scope_stack.push(func.layout.next_inst(inst).unwrap()); scope_stack.push(func.layout.next_inst(inst).unwrap());
visible_values.increment_depth(); visible_values.increment_depth();
} }
if trivially_unsafe_for_gvn(opcode) { if trivially_unsafe_for_gvn(opcode) {
continue; continue;
} }
let inst_data = func.dfg[inst].clone();
// These are split up to separate concerns.
if is_load_and_not_readonly(&inst_data) {
continue;
}
let ctrl_typevar = func.dfg.ctrl_typevar(inst); let ctrl_typevar = func.dfg.ctrl_typevar(inst);
let key = HashKey { let key = HashKey {
inst: func.dfg[inst].clone(), inst: func.dfg[inst].clone(),

View File

@@ -1651,6 +1651,31 @@ impl<'a> Verifier<'a> {
Ok(()) Ok(())
} }
fn immediate_constraints(
&self,
inst: Inst,
errors: &mut VerifierErrors,
) -> VerifierStepResult<()> {
let inst_data = &self.func.dfg[inst];
// If this is some sort of a store instruction, get the memflags, else, just return.
let memflags = match *inst_data {
ir::InstructionData::Store { flags, .. }
| ir::InstructionData::StoreComplex { flags, .. } => flags,
_ => return Ok(()),
};
if memflags.readonly() {
fatal!(
errors,
inst,
"A store instruction cannot have the `readonly` MemFlag"
)
} else {
Ok(())
}
}
pub fn run(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> { pub fn run(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
self.verify_global_values(errors)?; self.verify_global_values(errors)?;
self.verify_heaps(errors)?; self.verify_heaps(errors)?;
@@ -1663,6 +1688,7 @@ impl<'a> Verifier<'a> {
self.instruction_integrity(inst, errors)?; self.instruction_integrity(inst, errors)?;
self.typecheck(inst, errors)?; self.typecheck(inst, errors)?;
self.verify_encoding(inst, errors)?; self.verify_encoding(inst, errors)?;
self.immediate_constraints(inst, errors)?;
} }
} }

View File

@@ -1226,16 +1226,15 @@ impl<'a> Parser<'a> {
let flags = self.optional_memflags(); let flags = self.optional_memflags();
let base = self.match_gv("expected global value: gv«n»")?; let base = self.match_gv("expected global value: gv«n»")?;
let offset = self.optional_offset32()?; let offset = self.optional_offset32()?;
let mut expected_flags = MemFlags::new();
expected_flags.set_notrap(); if !(flags.notrap() && flags.aligned()) {
expected_flags.set_aligned();
if flags != expected_flags {
return err!(self.loc, "global-value load must be notrap and aligned"); return err!(self.loc, "global-value load must be notrap and aligned");
} }
GlobalValueData::Load { GlobalValueData::Load {
base, base,
offset, offset,
global_type, global_type,
readonly: flags.readonly(),
} }
} }
"iadd_imm" => { "iadd_imm" => {

View File

@@ -199,6 +199,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
base: addr, base: addr,
offset: Offset32::new(0), offset: Offset32::new(0),
global_type: self.pointer_type(), global_type: self.pointer_type(),
readonly: true,
}); });
func.create_heap(ir::HeapData { func.create_heap(ir::HeapData {
@@ -219,11 +220,13 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
base: vmctx, base: vmctx,
offset: Offset32::new(0), offset: Offset32::new(0),
global_type: self.pointer_type(), global_type: self.pointer_type(),
readonly: true, // when tables in wasm become "growable", revisit whether this can be readonly or not.
}); });
let bound_gv = func.create_global_value(ir::GlobalValueData::Load { let bound_gv = func.create_global_value(ir::GlobalValueData::Load {
base: vmctx, base: vmctx,
offset: Offset32::new(0), offset: Offset32::new(0),
global_type: I32, global_type: I32,
readonly: true,
}); });
func.create_table(ir::TableData { func.create_table(ir::TableData {