winch: Add support for the <i32|i64>.div_* instructions (#5807)
* Refactor the structure and responsibilities of `CodeGenContext` This commit refactors how the `CodeGenContext` is used throughout the code generation process, making it easier to pass it around when more flexibility is desired in the MacroAssembler to perform the lowering of certain instructions. As of this change, the responsibility of the `CodeGenContext` is to provide an interface for operations that require an orchestration between the register allocator, the value stack and function's frame. The MacroAssembler is removed from the CodeGenContext as is passed as a dependency where needed, effectly using it as an independent code generation interface only. By giving more responsibilities to the `CodeGenContext` we can clearly separate the concerns of the register allocator, which previously did more than it should (e.g. popping values and spilling). This change ultimately allows passing in the `CodeGenContext` to the `MacroAssembler` when a given instruction cannot be generically described through a common interface. Allowing each implementation to decide the best way to lower a particular instruction. * winch: Add support for the WebAssembly `<i32|i64>.div_*` instructions Given that some architectures have very specific requirements on how to handle division, this change uses `CodeGenContext` as a dependency to the `div` MacroAssembler instruction to ensure that each implementation can decide on how to lower the division. This approach also allows -- in architectures where division can be expressed as an ordinary binary operation -- to rely on the `CodeGenContext::i32_binop` or `CodeGenContext::i64_binop` helpers.
This commit is contained in:
@@ -1,11 +1,4 @@
|
||||
use crate::{
|
||||
codegen::CodeGenContext,
|
||||
frame::Frame,
|
||||
isa::reg::Reg,
|
||||
masm::{MacroAssembler, OperandSize, RegImm},
|
||||
regset::RegSet,
|
||||
stack::Val,
|
||||
};
|
||||
use crate::{isa::reg::Reg, regset::RegSet};
|
||||
|
||||
/// The register allocator.
|
||||
///
|
||||
@@ -30,79 +23,26 @@ impl RegAlloc {
|
||||
Self { regset, scratch }
|
||||
}
|
||||
|
||||
/// Loads the stack top value into a register, if it isn't already one;
|
||||
/// spilling if there are no registers available.
|
||||
pub fn pop_to_reg<M: MacroAssembler>(
|
||||
&mut self,
|
||||
context: &mut CodeGenContext<M>,
|
||||
size: OperandSize,
|
||||
) -> Reg {
|
||||
if let Some(reg) = context.stack.pop_reg() {
|
||||
return reg;
|
||||
}
|
||||
|
||||
let dst = self.any_gpr(context);
|
||||
let val = context.stack.pop().expect("a value at stack top");
|
||||
Self::move_val_to_reg(val, dst, context.masm, context.frame, size);
|
||||
dst
|
||||
}
|
||||
|
||||
/// Checks if the stack top contains the given register. The register
|
||||
/// gets allocated otherwise, potentially causing a spill.
|
||||
/// Once the requested register is allocated, the value at the top of the stack
|
||||
/// gets loaded into the register.
|
||||
pub fn pop_to_named_reg<M: MacroAssembler>(
|
||||
&mut self,
|
||||
context: &mut CodeGenContext<M>,
|
||||
named: Reg,
|
||||
size: OperandSize,
|
||||
) -> Reg {
|
||||
if let Some(reg) = context.stack.pop_named_reg(named) {
|
||||
return reg;
|
||||
}
|
||||
|
||||
let dst = self.gpr(context, named);
|
||||
let val = context.stack.pop().expect("a value at stack top");
|
||||
Self::move_val_to_reg(val, dst, context.masm, context.frame, size);
|
||||
dst
|
||||
}
|
||||
|
||||
fn move_val_to_reg<M: MacroAssembler>(
|
||||
src: Val,
|
||||
dst: Reg,
|
||||
masm: &mut M,
|
||||
frame: &Frame,
|
||||
size: OperandSize,
|
||||
) {
|
||||
match src {
|
||||
Val::Reg(src) => masm.mov(RegImm::reg(src), RegImm::reg(dst), size),
|
||||
Val::I32(imm) => masm.mov(RegImm::imm(imm.into()), RegImm::reg(dst), size),
|
||||
Val::I64(imm) => masm.mov(RegImm::imm(imm), RegImm::reg(dst), size),
|
||||
Val::Local(index) => {
|
||||
let slot = frame
|
||||
.get_local(index)
|
||||
.expect(&format!("valid locat at index = {}", index));
|
||||
let addr = masm.local_address(&slot);
|
||||
masm.load(addr, dst, slot.ty.into());
|
||||
}
|
||||
v => panic!("Unsupported value {:?}", v),
|
||||
};
|
||||
}
|
||||
|
||||
/// Allocate the next available general purpose register,
|
||||
/// spilling if none available.
|
||||
pub fn any_gpr<M: MacroAssembler>(&mut self, context: &mut CodeGenContext<M>) -> Reg {
|
||||
pub fn any_gpr<F>(&mut self, spill: &mut F) -> Reg
|
||||
where
|
||||
F: FnMut(&mut RegAlloc),
|
||||
{
|
||||
self.regset.any_gpr().unwrap_or_else(|| {
|
||||
self.spill(context);
|
||||
spill(self);
|
||||
self.regset.any_gpr().expect("any gpr to be available")
|
||||
})
|
||||
}
|
||||
|
||||
/// Request a specific general purpose register,
|
||||
/// spilling if not available.
|
||||
pub fn gpr<M: MacroAssembler>(&mut self, context: &mut CodeGenContext<M>, named: Reg) -> Reg {
|
||||
pub fn gpr<F>(&mut self, named: Reg, spill: &mut F) -> Reg
|
||||
where
|
||||
F: FnMut(&mut RegAlloc),
|
||||
{
|
||||
self.regset.gpr(named).unwrap_or_else(|| {
|
||||
self.spill(context);
|
||||
spill(self);
|
||||
self.regset
|
||||
.gpr(named)
|
||||
.expect(&format!("gpr {:?} to be available", named))
|
||||
@@ -113,33 +53,4 @@ impl RegAlloc {
|
||||
pub fn free_gpr(&mut self, reg: Reg) {
|
||||
self.regset.free_gpr(reg);
|
||||
}
|
||||
|
||||
/// Spill locals and registers to memory.
|
||||
// TODO optimize the spill range;
|
||||
//
|
||||
// At any point in the program, the stack
|
||||
// might already contain Memory entries;
|
||||
// we could effectively ignore that range;
|
||||
// only focusing on the range that contains
|
||||
// spillable values.
|
||||
fn spill<M: MacroAssembler>(&mut self, context: &mut CodeGenContext<M>) {
|
||||
context.stack.inner_mut().iter_mut().for_each(|v| match v {
|
||||
Val::Reg(r) => {
|
||||
let offset = context.masm.push(*r);
|
||||
self.free_gpr(*r);
|
||||
*v = Val::Memory(offset);
|
||||
}
|
||||
Val::Local(index) => {
|
||||
let slot = context
|
||||
.frame
|
||||
.get_local(*index)
|
||||
.expect("valid local at slot");
|
||||
let addr = context.masm.local_address(&slot);
|
||||
context.masm.load(addr, self.scratch, slot.ty.into());
|
||||
let offset = context.masm.push(self.scratch);
|
||||
*v = Val::Memory(offset);
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user