Mark loads from globals aligned and notrap.

Mark loads from globals generated by cton_wasm or by legalization as
`aligned` and `notrap`, since memory for these globals should be
allocated by the runtime environment for that purpose. This reduces
the number of potentially trapping instructions, which can reduce
the amount of metadata required by embedding environments.
This commit is contained in:
Dan Gohman
2018-03-23 13:34:40 -07:00
parent bcd3309c15
commit 685cde98a4
9 changed files with 47 additions and 15 deletions

View File

@@ -654,6 +654,9 @@ trap when accessed.
address space reserved for the heap, not including the guard pages. address space reserved for the heap, not including the guard pages.
:arg GuardBytes: Size of the guard pages in bytes. :arg GuardBytes: Size of the guard pages in bytes.
When the base is a global variable, it must be :term:`accessible` and naturally
aligned for a pointer value.
The ``reserved_reg`` option is not yet implemented. The ``reserved_reg`` option is not yet implemented.
Dynamic heaps Dynamic heaps
@@ -674,6 +677,9 @@ is resized. The bound of a dynamic heap is stored in a global variable.
:arg BoundGV: Global variable containing the current heap bound in bytes. :arg BoundGV: Global variable containing the current heap bound in bytes.
:arg GuardBytes: Size of the guard pages in bytes. :arg GuardBytes: Size of the guard pages in bytes.
When the base is a global variable, it must be :term:`accessible` and naturally
aligned for a pointer value.
The ``reserved_reg`` option is not yet implemented. The ``reserved_reg`` option is not yet implemented.
Heap examples Heap examples

View File

@@ -23,7 +23,7 @@ function %deref(i64 vmctx) -> i64 {
ebb1(v1: i64): ebb1(v1: i64):
v2 = global_addr.i64 gv2 v2 = global_addr.i64 gv2
; check: $(a1=$V) = iadd_imm v1, -16 ; check: $(a1=$V) = iadd_imm v1, -16
; check: $(p1=$V) = load.i64 $a1 ; check: $(p1=$V) = load.i64 notrap aligned $a1
; check: v2 = iadd_imm $p1, 32 ; check: v2 = iadd_imm $p1, 32
return v2 return v2
; check: return v2 ; check: return v2
@@ -55,7 +55,7 @@ ebb0(v0: i32, v999: i64):
; Checks here are assuming that no pipehole opts fold the load offsets. ; Checks here are assuming that no pipehole opts fold the load offsets.
; nextln: $(xoff=$V) = uextend.i64 v0 ; nextln: $(xoff=$V) = uextend.i64 v0
; nextln: $(haddr=$V) = iadd_imm v999, 64 ; nextln: $(haddr=$V) = iadd_imm v999, 64
; nextln: $(hbase=$V) = load.i64 $haddr ; nextln: $(hbase=$V) = load.i64 notrap aligned $haddr
; nextln: v1 = iadd $hbase, $xoff ; nextln: v1 = iadd $hbase, $xoff
v2 = load.f32 v1+16 v2 = load.f32 v1+16
; nextln: v2 = load.f32 v1+16 ; nextln: v2 = load.f32 v1+16
@@ -103,7 +103,7 @@ ebb0(v0: i32, v999: i64):
; Checks here are assuming that no pipehole opts fold the load offsets. ; Checks here are assuming that no pipehole opts fold the load offsets.
; nextln: $(xoff=$V) = uextend.i64 v0 ; nextln: $(xoff=$V) = uextend.i64 v0
; nextln: $(haddr=$V) = iadd_imm.i64 v999, 64 ; nextln: $(haddr=$V) = iadd_imm.i64 v999, 64
; nextln: $(hbase=$V) = load.i64 $haddr ; nextln: $(hbase=$V) = load.i64 notrap aligned $haddr
; nextln: v1 = iadd $hbase, $xoff ; nextln: v1 = iadd $hbase, $xoff
v2 = load.f32 v1+0x7fff_ffff v2 = load.f32 v1+0x7fff_ffff
; nextln: v2 = load.f32 v1+0x7fff_ffff ; nextln: v2 = load.f32 v1+0x7fff_ffff

View File

@@ -588,6 +588,9 @@ stack_check = Instruction(
Read the stack limit from ``GV`` and compare it to the stack pointer. If Read the stack limit from ``GV`` and compare it to the stack pointer. If
the stack pointer has reached or exceeded the limit, generate a trap with a the stack pointer has reached or exceeded the limit, generate a trap with a
``stk_ovf`` code. ``stk_ovf`` code.
The global variable must be accessible and naturally aligned for a
pointer-sized value.
""", """,
ins=GV, can_trap=True) ins=GV, can_trap=True)

View File

@@ -17,7 +17,8 @@ pub enum GlobalVarData {
/// Variable is part of a struct pointed to by another global variable. /// Variable is part of a struct pointed to by another global variable.
/// ///
/// The `base` global variable is assumed to contain a pointer to a struct. This global /// The `base` global variable is assumed to contain a pointer to a struct. This global
/// variable lives at an offset into the struct. /// variable lives at an offset into the struct. The memory must be accessible, and
/// naturally aligned to hold a pointer value.
Deref { Deref {
/// The base pointer global variable. /// The base pointer global variable.
base: GlobalVar, base: GlobalVar,

View File

@@ -29,7 +29,8 @@ pub enum HeapBase {
/// This feature is not yet implemented. /// This feature is not yet implemented.
ReservedReg, ReservedReg,
/// The heap base is in a global variable. /// The heap base is in a global variable. The variable must be accessible and naturally
/// aligned for a pointer.
GlobalVar(GlobalVar), GlobalVar(GlobalVar),
} }
@@ -38,7 +39,8 @@ pub enum HeapBase {
pub enum HeapStyle { pub enum HeapStyle {
/// A dynamic heap can be relocated to a different base address when it is grown. /// A dynamic heap can be relocated to a different base address when it is grown.
Dynamic { Dynamic {
/// Global variable holding the current bound of the heap in bytes. /// Global variable holding the current bound of the heap in bytes. It is
/// required to be accessible and naturally aligned for a pointer-sized integer.
bound_gv: GlobalVar, bound_gv: GlobalVar,
}, },

View File

@@ -52,8 +52,11 @@ fn deref_addr(inst: ir::Inst, func: &mut ir::Function, base: ir::GlobalVar, offs
pos.use_srcloc(inst); pos.use_srcloc(inst);
let base_addr = pos.ins().global_addr(ptr_ty, base); let base_addr = pos.ins().global_addr(ptr_ty, base);
// TODO: We could probably set both `notrap` and `aligned` on this load instruction. let mut mflags = ir::MemFlags::new();
let base_ptr = pos.ins().load(ptr_ty, ir::MemFlags::new(), base_addr, 0); // Deref globals are required to be accessible and aligned.
mflags.set_notrap();
mflags.set_aligned();
let base_ptr = pos.ins().load(ptr_ty, mflags, base_addr, 0);
pos.func.dfg.replace(inst).iadd_imm(base_ptr, offset); pos.func.dfg.replace(inst).iadd_imm(base_ptr, offset);
} }

View File

@@ -58,7 +58,11 @@ fn dynamic_addr(
// Start with the bounds check. Trap if `offset + size > bound`. // Start with the bounds check. Trap if `offset + size > bound`.
let bound_addr = pos.ins().global_addr(addr_ty, bound_gv); let bound_addr = pos.ins().global_addr(addr_ty, bound_gv);
let bound = pos.ins().load(offset_ty, MemFlags::new(), bound_addr, 0); let mut mflags = MemFlags::new();
// The bound variable is requied to be accessible and aligned.
mflags.set_notrap();
mflags.set_aligned();
let bound = pos.ins().load(offset_ty, mflags, bound_addr, 0);
let oob; let oob;
if size == 1 { if size == 1 {
@@ -175,7 +179,11 @@ fn offset_addr(
ir::HeapBase::ReservedReg => unimplemented!(), ir::HeapBase::ReservedReg => unimplemented!(),
ir::HeapBase::GlobalVar(base_gv) => { ir::HeapBase::GlobalVar(base_gv) => {
let base_addr = pos.ins().global_addr(addr_ty, base_gv); let base_addr = pos.ins().global_addr(addr_ty, base_gv);
let base = pos.ins().load(addr_ty, MemFlags::new(), base_addr, 0); let mut mflags = MemFlags::new();
// The base address variable is requied to be accessible and aligned.
mflags.set_notrap();
mflags.set_aligned();
let base = pos.ins().load(addr_ty, mflags, base_addr, 0);
pos.func.dfg.replace(inst).iadd(base, offset); pos.func.dfg.replace(inst).iadd(base, offset);
} }
} }

View File

@@ -75,8 +75,9 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
GlobalValue::Const(val) => val, GlobalValue::Const(val) => val,
GlobalValue::Memory { gv, ty } => { GlobalValue::Memory { gv, ty } => {
let addr = builder.ins().global_addr(environ.native_pointer(), gv); let addr = builder.ins().global_addr(environ.native_pointer(), gv);
// TODO: It is likely safe to set `aligned notrap` flags on a global load. let mut flags = ir::MemFlags::new();
let flags = ir::MemFlags::new(); flags.set_notrap();
flags.set_aligned();
builder.ins().load(ty, flags, addr, 0) builder.ins().load(ty, flags, addr, 0)
} }
}; };
@@ -87,8 +88,9 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
GlobalValue::Const(_) => panic!("global #{} is a constant", global_index), GlobalValue::Const(_) => panic!("global #{} is a constant", global_index),
GlobalValue::Memory { gv, .. } => { GlobalValue::Memory { gv, .. } => {
let addr = builder.ins().global_addr(environ.native_pointer(), gv); let addr = builder.ins().global_addr(environ.native_pointer(), gv);
// TODO: It is likely safe to set `aligned notrap` flags on a global store. let mut flags = ir::MemFlags::new();
let flags = ir::MemFlags::new(); flags.set_notrap();
flags.set_aligned();
let val = state.pop1(); let val = state.pop1();
builder.ins().store(flags, val, addr, 0); builder.ins().store(flags, val, addr, 0);
} }
@@ -992,6 +994,9 @@ fn translate_load<FE: FuncEnvironment + ?Sized>(
// We don't yet support multiple linear memories. // We don't yet support multiple linear memories.
let heap = state.get_heap(builder.func, 0, environ); let heap = state.get_heap(builder.func, 0, environ);
let (base, offset) = get_heap_addr(heap, addr32, offset, environ.native_pointer(), builder); let (base, offset) = get_heap_addr(heap, addr32, offset, environ.native_pointer(), builder);
// Note that we don't set `is_aligned` here, even if the load instruction's
// alignment immediate says it's aligned, because WebAssembly's immediate
// field is just a hint, while Cretonne's aligned flag needs a guarantee.
let flags = MemFlags::new(); let flags = MemFlags::new();
let (load, dfg) = builder.ins().Load( let (load, dfg) = builder.ins().Load(
opcode, opcode,
@@ -1017,6 +1022,7 @@ fn translate_store<FE: FuncEnvironment + ?Sized>(
// We don't yet support multiple linear memories. // We don't yet support multiple linear memories.
let heap = state.get_heap(builder.func, 0, environ); let heap = state.get_heap(builder.func, 0, environ);
let (base, offset) = get_heap_addr(heap, addr32, offset, environ.native_pointer(), builder); let (base, offset) = get_heap_addr(heap, addr32, offset, environ.native_pointer(), builder);
// See the comments in `translate_load` about the flags.
let flags = MemFlags::new(); let flags = MemFlags::new();
builder.ins().Store( builder.ins().Store(
opcode, opcode,

View File

@@ -209,7 +209,10 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
let ext = pos.ins().uextend(I64, callee); let ext = pos.ins().uextend(I64, callee);
pos.ins().imul_imm(ext, 4) pos.ins().imul_imm(ext, 4)
}; };
let func_ptr = pos.ins().load(ptr, ir::MemFlags::new(), callee_offset, 0); let mut mflags = ir::MemFlags::new();
mflags.set_notrap();
mflags.set_aligned();
let func_ptr = pos.ins().load(ptr, mflags, callee_offset, 0);
// Build a value list for the indirect call instruction containing the callee, call_args, // Build a value list for the indirect call instruction containing the callee, call_args,
// and the vmctx parameter. // and the vmctx parameter.