Extend X86 ABI to cover stack overflow checking on X86-32.
In stark contrast with every reasonable architecture, X86-32 does not pass any parameters in registers. Because of that we have to resort to reading arguments from stack without being able to use the stack slot machinery. (This wouldn't have been avoidable even by pinning a register because there is a trampoline in wasmtime with the C ABI that Cranelift needs to be able to call.)
This commit is contained in:
@@ -649,6 +649,14 @@ fn system_v_prologue_epilogue(func: &mut ir::Function, isa: &dyn TargetIsa) -> C
|
|||||||
|
|
||||||
// Add CSRs to function signature
|
// Add CSRs to function signature
|
||||||
let reg_type = ir::Type::int(u16::from(pointer_width.bits())).unwrap();
|
let reg_type = ir::Type::int(u16::from(pointer_width.bits())).unwrap();
|
||||||
|
if isa.pointer_bits() == 32 {
|
||||||
|
let sp_arg = ir::AbiParam::special_reg(
|
||||||
|
reg_type,
|
||||||
|
ir::ArgumentPurpose::CalleeSaved,
|
||||||
|
RU::rsp as RegUnit,
|
||||||
|
);
|
||||||
|
func.signature.params.push(sp_arg);
|
||||||
|
}
|
||||||
let fp_arg = ir::AbiParam::special_reg(
|
let fp_arg = ir::AbiParam::special_reg(
|
||||||
reg_type,
|
reg_type,
|
||||||
ir::ArgumentPurpose::FramePointer,
|
ir::ArgumentPurpose::FramePointer,
|
||||||
@@ -685,6 +693,17 @@ fn insert_common_prologue(
|
|||||||
fpr_slot: Option<&StackSlot>,
|
fpr_slot: Option<&StackSlot>,
|
||||||
isa: &dyn TargetIsa,
|
isa: &dyn TargetIsa,
|
||||||
) {
|
) {
|
||||||
|
// On X86-32 all parameters, including vmctx, are passed on stack, and we need
|
||||||
|
// to extract vmctx from the stack before we can save the frame pointer.
|
||||||
|
let sp = if isa.pointer_bits() == 32 {
|
||||||
|
let block = pos.current_block().expect("missing block under cursor");
|
||||||
|
let sp = pos.func.dfg.append_block_param(block, reg_type);
|
||||||
|
pos.func.locations[sp] = ir::ValueLoc::Reg(RU::rsp as RegUnit);
|
||||||
|
Some(sp)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// If this is a leaf function with zero stack, then there's no need to
|
// If this is a leaf function with zero stack, then there's no need to
|
||||||
// insert a stack check since it can't overflow anything and
|
// insert a stack check since it can't overflow anything and
|
||||||
// forward-progress is guarantee so long as loop are handled anyway.
|
// forward-progress is guarantee so long as loop are handled anyway.
|
||||||
@@ -707,7 +726,7 @@ fn insert_common_prologue(
|
|||||||
None => pos
|
None => pos
|
||||||
.func
|
.func
|
||||||
.stack_limit
|
.stack_limit
|
||||||
.map(|gv| interpret_gv(pos, gv, scratch)),
|
.map(|gv| interpret_gv(pos, gv, sp, scratch)),
|
||||||
};
|
};
|
||||||
if let Some(stack_limit_arg) = stack_limit_arg {
|
if let Some(stack_limit_arg) = stack_limit_arg {
|
||||||
insert_stack_check(pos, stack_size, stack_limit_arg);
|
insert_stack_check(pos, stack_size, stack_limit_arg);
|
||||||
@@ -834,19 +853,55 @@ fn insert_common_prologue(
|
|||||||
/// compared to the stack pointer, but currently it serves enough functionality
|
/// compared to the stack pointer, but currently it serves enough functionality
|
||||||
/// to get this implemented in `wasmtime` itself. This'll likely get expanded a
|
/// to get this implemented in `wasmtime` itself. This'll likely get expanded a
|
||||||
/// bit over time!
|
/// bit over time!
|
||||||
fn interpret_gv(pos: &mut EncCursor, gv: ir::GlobalValue, scratch: ir::ValueLoc) -> ir::Value {
|
fn interpret_gv(
|
||||||
|
pos: &mut EncCursor,
|
||||||
|
gv: ir::GlobalValue,
|
||||||
|
sp: Option<ir::Value>,
|
||||||
|
scratch: ir::ValueLoc,
|
||||||
|
) -> ir::Value {
|
||||||
match pos.func.global_values[gv] {
|
match pos.func.global_values[gv] {
|
||||||
ir::GlobalValueData::VMContext => pos
|
ir::GlobalValueData::VMContext => {
|
||||||
.func
|
let vmctx_index = pos
|
||||||
.special_param(ir::ArgumentPurpose::VMContext)
|
.func
|
||||||
.expect("no vmcontext parameter found"),
|
.signature
|
||||||
|
.special_param_index(ir::ArgumentPurpose::VMContext)
|
||||||
|
.expect("no vmcontext parameter found");
|
||||||
|
match pos.func.signature.params[vmctx_index] {
|
||||||
|
AbiParam {
|
||||||
|
location: ArgumentLoc::Reg(_),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let entry = pos.func.layout.entry_block().unwrap();
|
||||||
|
pos.func.dfg.block_params(entry)[vmctx_index]
|
||||||
|
}
|
||||||
|
AbiParam {
|
||||||
|
location: ArgumentLoc::Stack(offset),
|
||||||
|
value_type,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let offset =
|
||||||
|
offset + i32::from(pos.isa.pointer_bytes() * (1 + vmctx_index as u8));
|
||||||
|
// The following access can be marked `trusted` because it is a load of an argument. We
|
||||||
|
// know it is safe because it was safe to write it in preparing this function call.
|
||||||
|
let ret =
|
||||||
|
pos.ins()
|
||||||
|
.load(value_type, ir::MemFlags::trusted(), sp.unwrap(), offset);
|
||||||
|
pos.func.locations[ret] = scratch;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
AbiParam {
|
||||||
|
location: ArgumentLoc::Unassigned,
|
||||||
|
..
|
||||||
|
} => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
ir::GlobalValueData::Load {
|
ir::GlobalValueData::Load {
|
||||||
base,
|
base,
|
||||||
offset,
|
offset,
|
||||||
global_type,
|
global_type,
|
||||||
readonly: _,
|
readonly: _,
|
||||||
} => {
|
} => {
|
||||||
let base = interpret_gv(pos, base, scratch);
|
let base = interpret_gv(pos, base, sp, scratch);
|
||||||
let ret = pos
|
let ret = pos
|
||||||
.ins()
|
.ins()
|
||||||
.load(global_type, ir::MemFlags::trusted(), base, offset);
|
.load(global_type, ir::MemFlags::trusted(), base, offset);
|
||||||
|
|||||||
Reference in New Issue
Block a user