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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user