use super::ControlStackFrame;
use crate::{
abi::{ABIResult, ABI},
frame::Frame,
masm::{MacroAssembler, OperandSize, RegImm},
reg::Reg,
regalloc::RegAlloc,
stack::{Stack, Val},
};
use std::ops::RangeBounds;
pub(crate) struct CodeGenContext<'a> {
pub regalloc: RegAlloc,
pub stack: Stack,
pub frame: &'a Frame,
pub reachable: bool,
}
impl<'a> CodeGenContext<'a> {
pub fn new(regalloc: RegAlloc, stack: Stack, frame: &'a Frame) -> Self {
Self {
regalloc,
stack,
frame,
reachable: true,
}
}
pub fn gpr<M: MacroAssembler>(&mut self, named: Reg, masm: &mut M) -> Reg {
self.regalloc.gpr(named, &mut |regalloc| {
Self::spill_impl(&mut self.stack, regalloc, &self.frame, masm)
})
}
pub fn any_gpr<M: MacroAssembler>(&mut self, masm: &mut M) -> Reg {
self.regalloc
.any_gpr(&mut |regalloc| Self::spill_impl(&mut self.stack, regalloc, &self.frame, masm))
}
pub fn free_gpr(&mut self, reg: Reg) {
self.regalloc.free_gpr(reg);
}
pub fn pop_to_reg<M: MacroAssembler>(
&mut self,
masm: &mut M,
named: Option<Reg>,
size: OperandSize,
) -> Reg {
let (in_stack, dst) = if let Some(dst) = named {
self.stack
.pop_named_reg(dst)
.map(|reg| (true, reg))
.unwrap_or_else(|| (false, self.gpr(dst, masm)))
} else {
self.stack
.pop_reg()
.map(|reg| (true, reg))
.unwrap_or_else(|| (false, self.any_gpr(masm)))
};
if in_stack {
return dst;
}
let val = self.stack.pop().expect("a value at stack top");
if val.is_mem() {
masm.pop(dst);
} else {
self.move_val_to_reg(&val, dst, masm, size);
if val.is_reg() {
self.regalloc.free_gpr(val.get_reg());
}
}
dst
}
pub fn move_val_to_reg<M: MacroAssembler>(
&self,
src: &Val,
dst: Reg,
masm: &mut M,
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 = self
.frame
.get_local(*index)
.unwrap_or_else(|| panic!("valid local at index = {}", index));
let addr = masm.local_address(&slot);
masm.load(addr, dst, slot.ty.into());
}
Val::Memory(offset) => {
let addr = masm.address_from_sp(*offset);
masm.load(addr, dst, size);
}
}
}
pub fn unop<F, M>(&mut self, masm: &mut M, size: OperandSize, emit: &mut F)
where
F: FnMut(&mut M, Reg, OperandSize),
M: MacroAssembler,
{
let reg = self.pop_to_reg(masm, None, size);
emit(masm, reg, size);
self.stack.push(Val::reg(reg));
}
pub fn i32_binop<F, M>(&mut self, masm: &mut M, mut emit: F)
where
F: FnMut(&mut M, RegImm, RegImm, OperandSize),
M: MacroAssembler,
{
let top = self.stack.peek().expect("value at stack top");
if top.is_i32_const() {
let val = self
.stack
.pop_i32_const()
.expect("i32 const value at stack top");
let reg = self.pop_to_reg(masm, None, OperandSize::S32);
emit(
masm,
RegImm::reg(reg),
RegImm::imm(val as i64),
OperandSize::S32,
);
self.stack.push(Val::reg(reg));
} else {
let src = self.pop_to_reg(masm, None, OperandSize::S32);
let dst = self.pop_to_reg(masm, None, OperandSize::S32);
emit(masm, dst.into(), src.into(), OperandSize::S32);
self.regalloc.free_gpr(src);
self.stack.push(Val::reg(dst));
}
}
pub fn i64_binop<F, M>(&mut self, masm: &mut M, mut emit: F)
where
F: FnMut(&mut M, RegImm, RegImm, OperandSize),
M: MacroAssembler,
{
let top = self.stack.peek().expect("value at stack top");
if top.is_i64_const() {
let val = self
.stack
.pop_i64_const()
.expect("i64 const value at stack top");
let reg = self.pop_to_reg(masm, None, OperandSize::S64);
emit(masm, RegImm::reg(reg), RegImm::imm(val), OperandSize::S64);
self.stack.push(Val::reg(reg));
} else {
let src = self.pop_to_reg(masm, None, OperandSize::S64);
let dst = self.pop_to_reg(masm, None, OperandSize::S64);
emit(masm, dst.into(), src.into(), OperandSize::S64);
self.regalloc.free_gpr(src);
self.stack.push(Val::reg(dst));
}
}
pub fn spill_regs_and_count_memory_in<M, R>(&mut self, masm: &mut M, range: R) -> (u32, u32)
where
R: RangeBounds<usize>,
M: MacroAssembler,
{
let mut spilled: u32 = 0;
let mut memory_values = 0;
for i in self.stack.inner_mut().range_mut(range) {
if i.is_reg() {
let reg = i.get_reg();
let offset = masm.push(reg);
self.regalloc.free_gpr(reg);
*i = Val::Memory(offset);
spilled += 1;
} else if i.is_mem() {
memory_values += 1;
}
}
(spilled, memory_values)
}
pub fn drop_last(&mut self, last: usize) {
let len = self.stack.len();
assert!(last <= len);
let truncate = self.stack.len() - last;
self.stack.inner_mut().range(truncate..).for_each(|v| {
if v.is_reg() {
self.regalloc.free_gpr(v.get_reg());
}
});
self.stack.inner_mut().truncate(truncate);
}
pub fn pop_sp_for_branch<M: MacroAssembler>(
&mut self,
destination: &ControlStackFrame,
masm: &mut M,
) {
let (_, original_sp_offset) = destination.original_stack_len_and_sp_offset();
let current_sp_offset = masm.sp_offset();
assert!(
current_sp_offset >= original_sp_offset
|| (current_sp_offset + <M::ABI as ABI>::word_bytes()) == original_sp_offset
);
if current_sp_offset > original_sp_offset {
masm.free_stack(current_sp_offset - original_sp_offset);
}
}
pub fn spill<M: MacroAssembler>(&mut self, masm: &mut M) {
Self::spill_impl(&mut self.stack, &mut self.regalloc, &mut self.frame, masm);
}
pub fn pop_abi_results<M: MacroAssembler>(&mut self, result: &ABIResult, masm: &mut M) {
if result.is_void() {
return;
}
let reg = self.pop_to_reg(masm, Some(result.result_reg()), OperandSize::S64);
self.regalloc.free_gpr(reg);
}
pub fn push_abi_results<M: MacroAssembler>(&mut self, result: &ABIResult, masm: &mut M) {
if result.is_void() {
return;
}
match result {
ABIResult::Reg { reg, .. } => {
assert!(self.regalloc.gpr_available(*reg));
let result_reg = Val::reg(self.gpr(*reg, masm));
self.stack.push(result_reg);
}
}
}
pub fn set_local<M: MacroAssembler>(&mut self, masm: &mut M, index: u32) -> Reg {
let slot = self
.frame
.get_local(index)
.unwrap_or_else(|| panic!("valid local at slot = {}", index));
let size: OperandSize = slot.ty.into();
let src = self.pop_to_reg(masm, None, size);
let addr = masm.local_address(&slot);
masm.store(RegImm::reg(src), addr, size);
src
}
fn spill_impl<M: MacroAssembler>(
stack: &mut Stack,
regalloc: &mut RegAlloc,
frame: &Frame,
masm: &mut M,
) {
stack.inner_mut().iter_mut().for_each(|v| match v {
Val::Reg(r) => {
let offset = masm.push(*r);
regalloc.free_gpr(*r);
*v = Val::Memory(offset);
}
Val::Local(index) => {
let slot = frame.get_local(*index).expect("valid local at slot");
let addr = masm.local_address(&slot);
masm.load(addr, regalloc.scratch, slot.ty.into());
let offset = masm.push(regalloc.scratch);
*v = Val::Memory(offset);
}
_ => {}
});
}
}