use super::riscv_instr_info::RiscVOpcode;
use super::riscv_register_info::*;
use crate::codegen::*;
const STACK_ALIGNMENT: i64 = 16;
pub const RA_SAVE_OFFSET_FROM_SP: i64 = -8;
#[derive(Debug, Clone)]
pub struct RiscVFrameInfo {
pub frame_size: i64,
pub saved_regs: Vec<u16>,
pub has_frame_pointer: bool,
pub has_calls: bool,
pub local_area_offset: i64,
pub callee_saved_size: i64,
pub saved_ra_offset: i64,
pub saved_fp_offset: i64,
pub fixed_frame_size: i64,
pub is_64bit: bool,
}
impl RiscVFrameInfo {
pub fn new(is_64bit: bool) -> Self {
RiscVFrameInfo {
frame_size: 0,
saved_regs: Vec::new(),
has_frame_pointer: false,
has_calls: false,
local_area_offset: 0,
callee_saved_size: 0,
saved_ra_offset: 0,
saved_fp_offset: 0,
fixed_frame_size: 0,
is_64bit,
}
}
pub fn total_frame_size(&self) -> i64 {
self.frame_size + self.fixed_frame_size
}
pub fn local_start_offset(&self) -> i64 {
self.local_area_offset
}
pub fn reg_width(&self) -> i64 {
if self.is_64bit {
8
} else {
4
}
}
}
impl Default for RiscVFrameInfo {
fn default() -> Self {
Self::new(true)
}
}
pub struct RiscVFrameLowering {
pub is_64bit: bool,
}
impl RiscVFrameLowering {
pub fn new(is_64bit: bool) -> Self {
RiscVFrameLowering { is_64bit }
}
pub fn emit_prologue(&self, info: &RiscVFrameInfo) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let frame_size = info.frame_size;
if frame_size == 0 && info.saved_regs.is_empty() && !info.has_calls {
return instrs;
}
if frame_size > 0 {
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(SP as u32));
addi.operands.push(MachineOperand::PhysReg(SP as u32));
addi.push_imm(-frame_size);
instrs.push(addi);
}
let ra_offset = info.saved_ra_offset;
if info.has_calls {
if self.is_64bit {
let mut sd = MachineInstr::new(RiscVOpcode::SD as u32);
sd.operands.push(MachineOperand::PhysReg(RA as u32));
sd.operands.push(MachineOperand::PhysReg(SP as u32));
sd.push_imm(ra_offset);
instrs.push(sd);
} else {
let mut sw = MachineInstr::new(RiscVOpcode::SW as u32);
sw.operands.push(MachineOperand::PhysReg(RA as u32));
sw.operands.push(MachineOperand::PhysReg(SP as u32));
sw.push_imm(ra_offset);
instrs.push(sw);
}
}
if info.has_frame_pointer {
let fp_offset = info.saved_fp_offset;
if self.is_64bit {
let mut sd = MachineInstr::new(RiscVOpcode::SD as u32);
sd.operands.push(MachineOperand::PhysReg(S0 as u32));
sd.operands.push(MachineOperand::PhysReg(SP as u32));
sd.push_imm(fp_offset);
instrs.push(sd);
} else {
let mut sw = MachineInstr::new(RiscVOpcode::SW as u32);
sw.operands.push(MachineOperand::PhysReg(S0 as u32));
sw.operands.push(MachineOperand::PhysReg(SP as u32));
sw.push_imm(fp_offset);
instrs.push(sw);
}
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(S0 as u32));
addi.operands.push(MachineOperand::PhysReg(SP as u32));
addi.push_imm(frame_size);
instrs.push(addi);
}
for &(reg, offset) in &self.assign_callee_saved_spill_slots(info) {
if self.is_64bit {
let mut sd = MachineInstr::new(RiscVOpcode::SD as u32);
sd.operands.push(MachineOperand::PhysReg(reg as u32));
sd.operands.push(MachineOperand::PhysReg(SP as u32));
sd.push_imm(offset);
instrs.push(sd);
} else {
let mut sw = MachineInstr::new(RiscVOpcode::SW as u32);
sw.operands.push(MachineOperand::PhysReg(reg as u32));
sw.operands.push(MachineOperand::PhysReg(SP as u32));
sw.push_imm(offset);
instrs.push(sw);
}
}
instrs
}
pub fn emit_epilogue(&self, info: &RiscVFrameInfo) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let frame_size = info.frame_size;
if frame_size == 0 && info.saved_regs.is_empty() && !info.has_calls {
instrs.push(MachineInstr::new(RiscVOpcode::RET as u32));
return instrs;
}
let spill_slots = self.assign_callee_saved_spill_slots(info);
for &(reg, offset) in spill_slots.iter().rev() {
if self.is_64bit {
let mut ld = MachineInstr::new(RiscVOpcode::LD as u32);
ld.operands.push(MachineOperand::PhysReg(reg as u32));
ld.operands.push(MachineOperand::PhysReg(SP as u32));
ld.push_imm(offset);
instrs.push(ld);
} else {
let mut lw = MachineInstr::new(RiscVOpcode::LW as u32);
lw.operands.push(MachineOperand::PhysReg(reg as u32));
lw.operands.push(MachineOperand::PhysReg(SP as u32));
lw.push_imm(offset);
instrs.push(lw);
}
}
if info.has_frame_pointer {
let fp_offset = info.saved_fp_offset;
if self.is_64bit {
let mut ld = MachineInstr::new(RiscVOpcode::LD as u32);
ld.operands.push(MachineOperand::PhysReg(S0 as u32));
ld.operands.push(MachineOperand::PhysReg(SP as u32));
ld.push_imm(fp_offset);
instrs.push(ld);
} else {
let mut lw = MachineInstr::new(RiscVOpcode::LW as u32);
lw.operands.push(MachineOperand::PhysReg(S0 as u32));
lw.operands.push(MachineOperand::PhysReg(SP as u32));
lw.push_imm(fp_offset);
instrs.push(lw);
}
}
if info.has_calls {
let ra_offset = info.saved_ra_offset;
if self.is_64bit {
let mut ld = MachineInstr::new(RiscVOpcode::LD as u32);
ld.operands.push(MachineOperand::PhysReg(RA as u32));
ld.operands.push(MachineOperand::PhysReg(SP as u32));
ld.push_imm(ra_offset);
instrs.push(ld);
} else {
let mut lw = MachineInstr::new(RiscVOpcode::LW as u32);
lw.operands.push(MachineOperand::PhysReg(RA as u32));
lw.operands.push(MachineOperand::PhysReg(SP as u32));
lw.push_imm(ra_offset);
instrs.push(lw);
}
}
if frame_size > 0 {
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(SP as u32));
addi.operands.push(MachineOperand::PhysReg(SP as u32));
addi.push_imm(frame_size);
instrs.push(addi);
}
instrs.push(MachineInstr::new(RiscVOpcode::RET as u32));
instrs
}
pub fn build_frame_info(&self, mf: &MachineFunction) -> RiscVFrameInfo {
let mut info = RiscVFrameInfo::new(self.is_64bit);
let used_regs = self.collect_used_regs(mf);
let callee_saved_candidates = if self.is_64bit {
vec![S2, S3, S4, S5, S6, S7, S8, S9, S10, S11]
} else {
vec![S2, S3, S4, S5, S6, S7, S8, S9, S10, S11]
};
for ® in &callee_saved_candidates {
if used_regs.contains(&(reg as u32)) {
info.saved_regs.push(reg);
}
}
info.has_calls = mf.blocks.iter().any(|bb| {
bb.instructions.iter().any(|mi| {
mi.opcode == RiscVOpcode::JAL as u32
|| mi.opcode == RiscVOpcode::JALR as u32
|| mi.opcode == RiscVOpcode::CALL as u32
})
});
info.has_frame_pointer = self.needs_frame_pointer(&info);
let reg_width: i64 = if self.is_64bit { 8 } else { 4 };
let mut offset = 0i64;
info.local_area_offset = offset;
let local_size = info.frame_size - info.callee_saved_size;
let num_saved = info.saved_regs.len() as i64;
info.callee_saved_size = num_saved * reg_width;
let mut total = local_size + info.callee_saved_size;
if info.has_frame_pointer {
total += reg_width;
info.saved_fp_offset = total - reg_width;
} else {
info.saved_fp_offset = 0;
}
total += reg_width;
info.saved_ra_offset = total - reg_width;
info.frame_size = total;
info.frame_size = Self::align_frame_size(info.frame_size);
if info.has_frame_pointer {
info.saved_fp_offset = info.frame_size - 2 * reg_width;
}
info.saved_ra_offset = info.frame_size - reg_width;
info
}
fn collect_used_regs(&self, mf: &MachineFunction) -> Vec<u32> {
let mut used = Vec::new();
for bb in &mf.blocks {
for mi in &bb.instructions {
for op in &mi.operands {
if let MachineOperand::PhysReg(r) = op {
if !used.contains(r) {
used.push(*r);
}
}
}
}
}
used
}
pub fn needs_frame_pointer(&self, info: &RiscVFrameInfo) -> bool {
if info.frame_size >= 2048 {
return true;
}
if info.saved_regs.len() >= 8 {
return true;
}
if info.has_calls && info.saved_regs.len() >= 4 {
return true;
}
false
}
pub fn calculate_frame_size(&self, info: &RiscVFrameInfo) -> i64 {
let mut size = info.fixed_frame_size;
let reg_width: i64 = if self.is_64bit { 8 } else { 4 };
size += info.saved_regs.len() as i64 * reg_width;
size += reg_width;
if info.has_frame_pointer {
size += reg_width;
}
size
}
fn align_frame_size(size: i64) -> i64 {
if size <= 0 {
return 0;
}
((size + (STACK_ALIGNMENT - 1)) / STACK_ALIGNMENT) * STACK_ALIGNMENT
}
pub fn assign_callee_saved_spill_slots(&self, info: &RiscVFrameInfo) -> Vec<(u16, i64)> {
let mut slots = Vec::new();
let reg_width: i64 = if self.is_64bit { 8 } else { 4 };
let base_offset = info.local_area_offset;
let mut current_offset = base_offset;
for ® in &info.saved_regs {
slots.push((reg, current_offset));
current_offset += reg_width;
}
slots
}
pub fn get_frame_index_reference(&self, info: &RiscVFrameInfo, object_offset: i64) -> i64 {
if info.has_frame_pointer {
object_offset - info.frame_size
} else {
object_offset
}
}
pub fn is_reg_used_in_function(&self, mf: &MachineFunction, reg: u32) -> bool {
for bb in &mf.blocks {
for mi in &bb.instructions {
for op in &mi.operands {
if let MachineOperand::PhysReg(r) = op {
if *r == reg {
return true;
}
}
}
}
}
false
}
pub fn build_frame_info_rv32e(&self, mf: &MachineFunction) -> RiscVFrameInfo {
let mut info = RiscVFrameInfo::new(false);
info.is_64bit = false;
let callee_saved_candidates: Vec<u16> = vec![S0, S1];
let used_regs = self.collect_used_regs(mf);
for ® in &callee_saved_candidates {
if used_regs.contains(&(reg as u32)) {
info.saved_regs.push(reg);
}
}
info.has_calls = mf.blocks.iter().any(|bb| {
bb.instructions.iter().any(|mi| {
mi.opcode == RiscVOpcode::JAL as u32
|| mi.opcode == RiscVOpcode::JALR as u32
|| mi.opcode == RiscVOpcode::CALL as u32
})
});
info.has_frame_pointer = self.needs_frame_pointer(&info);
let reg_width: i64 = 4; let num_saved = info.saved_regs.len() as i64;
info.callee_saved_size = num_saved * reg_width;
let mut total = info.fixed_frame_size + info.callee_saved_size;
if info.has_frame_pointer {
total += reg_width;
info.saved_fp_offset = total - reg_width;
}
total += reg_width;
info.saved_ra_offset = total - reg_width;
info.frame_size = Self::align_frame_size(total);
if info.has_frame_pointer {
info.saved_fp_offset = info.frame_size - 2 * reg_width;
}
info.saved_ra_offset = info.frame_size - reg_width;
info
}
pub fn emit_prologue_rv32e(&self, info: &RiscVFrameInfo) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let frame_size = info.frame_size;
if frame_size == 0 && info.saved_regs.is_empty() && !info.has_calls {
return instrs;
}
if frame_size > 0 {
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(SP as u32));
addi.operands.push(MachineOperand::PhysReg(SP as u32));
addi.push_imm(-frame_size);
instrs.push(addi);
}
if info.has_calls {
let mut sw = MachineInstr::new(RiscVOpcode::SW as u32);
sw.operands.push(MachineOperand::PhysReg(RA as u32));
sw.operands.push(MachineOperand::PhysReg(SP as u32));
sw.push_imm(info.saved_ra_offset);
instrs.push(sw);
}
if info.has_frame_pointer {
let mut sw = MachineInstr::new(RiscVOpcode::SW as u32);
sw.operands.push(MachineOperand::PhysReg(S0 as u32));
sw.operands.push(MachineOperand::PhysReg(SP as u32));
sw.push_imm(info.saved_fp_offset);
instrs.push(sw);
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(S0 as u32));
addi.operands.push(MachineOperand::PhysReg(SP as u32));
addi.push_imm(frame_size);
instrs.push(addi);
}
for &(reg, offset) in &self.assign_callee_saved_spill_slots(info) {
let mut sw = MachineInstr::new(RiscVOpcode::SW as u32);
sw.operands.push(MachineOperand::PhysReg(reg as u32));
sw.operands.push(MachineOperand::PhysReg(SP as u32));
sw.push_imm(offset);
instrs.push(sw);
}
instrs
}
pub fn emit_epilogue_rv32e(&self, info: &RiscVFrameInfo) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let frame_size = info.frame_size;
if frame_size == 0 && info.saved_regs.is_empty() && !info.has_calls {
instrs.push(MachineInstr::new(RiscVOpcode::RET as u32));
return instrs;
}
let spill_slots = self.assign_callee_saved_spill_slots(info);
for &(reg, offset) in spill_slots.iter().rev() {
let mut lw = MachineInstr::new(RiscVOpcode::LW as u32);
lw.operands.push(MachineOperand::PhysReg(reg as u32));
lw.operands.push(MachineOperand::PhysReg(SP as u32));
lw.push_imm(offset);
instrs.push(lw);
}
if info.has_frame_pointer {
let mut lw = MachineInstr::new(RiscVOpcode::LW as u32);
lw.operands.push(MachineOperand::PhysReg(S0 as u32));
lw.operands.push(MachineOperand::PhysReg(SP as u32));
lw.push_imm(info.saved_fp_offset);
instrs.push(lw);
}
if info.has_calls {
let mut lw = MachineInstr::new(RiscVOpcode::LW as u32);
lw.operands.push(MachineOperand::PhysReg(RA as u32));
lw.operands.push(MachineOperand::PhysReg(SP as u32));
lw.push_imm(info.saved_ra_offset);
instrs.push(lw);
}
if frame_size > 0 {
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(SP as u32));
addi.operands.push(MachineOperand::PhysReg(SP as u32));
addi.push_imm(frame_size);
instrs.push(addi);
}
instrs.push(MachineInstr::new(RiscVOpcode::RET as u32));
instrs
}
pub fn emit_prologue_large_frame(&self, info: &RiscVFrameInfo) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let frame_size = info.frame_size;
if frame_size <= 2047 {
return self.emit_prologue(info);
}
let tmp = T1 as u32;
let upper = ((frame_size + 0x800) as u64 >> 12) as i64;
let lower = frame_size - (upper << 12);
if upper > 0 {
let mut lui = MachineInstr::new(RiscVOpcode::LUI as u32);
lui.operands.push(MachineOperand::PhysReg(tmp));
lui.push_imm(upper);
instrs.push(lui);
}
if lower != 0 {
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(tmp));
addi.push_reg(tmp);
addi.push_imm(lower);
instrs.push(addi);
}
let mut sub = MachineInstr::new(RiscVOpcode::SUB as u32);
sub.operands.push(MachineOperand::PhysReg(SP as u32));
sub.operands.push(MachineOperand::PhysReg(SP as u32));
sub.push_reg(tmp);
instrs.push(sub);
if info.has_calls {
let op = if self.is_64bit {
RiscVOpcode::SD as u32
} else {
RiscVOpcode::SW as u32
};
let mut store = MachineInstr::new(op);
store.operands.push(MachineOperand::PhysReg(RA as u32));
store.operands.push(MachineOperand::PhysReg(SP as u32));
store.push_imm(info.saved_ra_offset);
instrs.push(store);
}
if info.has_frame_pointer {
let op = if self.is_64bit {
RiscVOpcode::SD as u32
} else {
RiscVOpcode::SW as u32
};
let mut store = MachineInstr::new(op);
store.operands.push(MachineOperand::PhysReg(S0 as u32));
store.operands.push(MachineOperand::PhysReg(SP as u32));
store.push_imm(info.saved_fp_offset);
instrs.push(store);
let fp_upper = ((frame_size + 0x800) as u64 >> 12) as i64;
let fp_lower = frame_size - (fp_upper << 12);
let tmp2 = T2 as u32;
if fp_upper > 0 {
let mut lui = MachineInstr::new(RiscVOpcode::LUI as u32);
lui.operands.push(MachineOperand::PhysReg(tmp2));
lui.push_imm(fp_upper);
instrs.push(lui);
}
if fp_lower != 0 || fp_upper == 0 {
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(tmp2));
if fp_upper > 0 {
addi.push_reg(tmp2);
} else {
addi.operands.push(MachineOperand::PhysReg(ZERO as u32));
}
addi.push_imm(fp_lower);
instrs.push(addi);
}
let mut add = MachineInstr::new(RiscVOpcode::ADD as u32);
add.operands.push(MachineOperand::PhysReg(S0 as u32));
add.operands.push(MachineOperand::PhysReg(SP as u32));
add.push_reg(tmp2);
instrs.push(add);
}
for &(reg, offset) in &self.assign_callee_saved_spill_slots(info) {
let op = if self.is_64bit {
RiscVOpcode::SD as u32
} else {
RiscVOpcode::SW as u32
};
let mut store = MachineInstr::new(op);
store.operands.push(MachineOperand::PhysReg(reg as u32));
store.operands.push(MachineOperand::PhysReg(SP as u32));
store.push_imm(offset);
instrs.push(store);
}
instrs
}
pub fn emit_epilogue_large_frame(&self, info: &RiscVFrameInfo) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let frame_size = info.frame_size;
if frame_size <= 2047 {
return self.emit_epilogue(info);
}
let spill_slots = self.assign_callee_saved_spill_slots(info);
for &(reg, offset) in spill_slots.iter().rev() {
let op = if self.is_64bit {
RiscVOpcode::LD as u32
} else {
RiscVOpcode::LW as u32
};
let mut load = MachineInstr::new(op);
load.operands.push(MachineOperand::PhysReg(reg as u32));
load.operands.push(MachineOperand::PhysReg(SP as u32));
load.push_imm(offset);
instrs.push(load);
}
if info.has_frame_pointer {
let op = if self.is_64bit {
RiscVOpcode::LD as u32
} else {
RiscVOpcode::LW as u32
};
let mut load = MachineInstr::new(op);
load.operands.push(MachineOperand::PhysReg(S0 as u32));
load.operands.push(MachineOperand::PhysReg(SP as u32));
load.push_imm(info.saved_fp_offset);
instrs.push(load);
}
if info.has_calls {
let op = if self.is_64bit {
RiscVOpcode::LD as u32
} else {
RiscVOpcode::LW as u32
};
let mut load = MachineInstr::new(op);
load.operands.push(MachineOperand::PhysReg(RA as u32));
load.operands.push(MachineOperand::PhysReg(SP as u32));
load.push_imm(info.saved_ra_offset);
instrs.push(load);
}
let tmp = T1 as u32;
let upper = ((frame_size + 0x800) as u64 >> 12) as i64;
let lower = frame_size - (upper << 12);
if upper > 0 {
let mut lui = MachineInstr::new(RiscVOpcode::LUI as u32);
lui.operands.push(MachineOperand::PhysReg(tmp));
lui.push_imm(upper);
instrs.push(lui);
}
if lower != 0 {
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(tmp));
addi.push_reg(tmp);
addi.push_imm(lower);
instrs.push(addi);
}
let mut add = MachineInstr::new(RiscVOpcode::ADD as u32);
add.operands.push(MachineOperand::PhysReg(SP as u32));
add.operands.push(MachineOperand::PhysReg(SP as u32));
add.push_reg(tmp);
instrs.push(add);
instrs.push(MachineInstr::new(RiscVOpcode::RET as u32));
instrs
}
pub fn has_variable_sized_objects(&self, mf: &MachineFunction) -> bool {
for bb in &mf.blocks {
for mi in &bb.instructions {
if mi.opcode == RiscVOpcode::ADDI as u32 {
if let Some(MachineOperand::PhysReg(r)) = mi.operands.first() {
if *r == SP as u32 {
if mi.operands.len() >= 3 {
if let Some(MachineOperand::Reg(_)) = mi.operands.get(1) {
if let Some(MachineOperand::Reg(_)) = mi.operands.get(2) {
return true;
}
}
}
}
}
}
}
}
false
}
pub fn build_frame_info_with_vla(&self, mf: &MachineFunction, vla_size: i64) -> RiscVFrameInfo {
let mut info = self.build_frame_info(mf);
info.has_frame_pointer = true;
info.fixed_frame_size += vla_size;
let reg_width: i64 = if self.is_64bit { 8 } else { 4 };
let mut total = info.fixed_frame_size + info.callee_saved_size;
if info.has_frame_pointer {
total += reg_width;
info.saved_fp_offset = total - reg_width;
}
total += reg_width;
info.saved_ra_offset = total - reg_width;
info.frame_size = Self::align_frame_size(total);
if info.has_frame_pointer {
info.saved_fp_offset = info.frame_size - 2 * reg_width;
}
info.saved_ra_offset = info.frame_size - reg_width;
info
}
pub fn emit_vla_adjustment(&self, vla_size_reg: u32) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let mut sub = MachineInstr::new(RiscVOpcode::SUB as u32);
sub.operands.push(MachineOperand::PhysReg(SP as u32));
sub.operands.push(MachineOperand::PhysReg(S0 as u32));
sub.push_reg(vla_size_reg);
instrs.push(sub);
instrs
}
pub fn calculate_outgoing_arg_area(&self, max_call_args: usize, has_fp_args: bool) -> i64 {
let int_reg_args = 8usize;
let fp_reg_args = if has_fp_args { 8usize } else { 0usize };
let overflow_int = if max_call_args > int_reg_args {
max_call_args - int_reg_args
} else {
0
};
let reg_width: i64 = if self.is_64bit { 8 } else { 4 };
let raw_size = overflow_int as i64 * reg_width;
Self::align_frame_size(raw_size)
}
pub fn build_frame_info_with_outgoing_args(
&self,
mf: &MachineFunction,
max_call_args: usize,
has_fp_args: bool,
) -> RiscVFrameInfo {
let mut info = self.build_frame_info(mf);
let outgoing_area = self.calculate_outgoing_arg_area(max_call_args, has_fp_args);
info.fixed_frame_size += outgoing_area;
let reg_width: i64 = if self.is_64bit { 8 } else { 4 };
let mut total = info.fixed_frame_size + info.callee_saved_size;
if info.has_frame_pointer {
total += reg_width;
info.saved_fp_offset = total - reg_width;
}
total += reg_width;
info.saved_ra_offset = total - reg_width;
info.frame_size = Self::align_frame_size(total);
if info.has_frame_pointer {
info.saved_fp_offset = info.frame_size - 2 * reg_width;
}
info.saved_ra_offset = info.frame_size - reg_width;
info
}
pub fn estimate_max_call_args(&self, mf: &MachineFunction) -> usize {
let mut max_args = 0usize;
for bb in &mf.blocks {
for mi in &bb.instructions {
if mi.opcode == RiscVOpcode::JAL as u32 || mi.opcode == RiscVOpcode::CALL as u32 {
let arg_count = mi
.operands
.iter()
.filter(|op| {
if let MachineOperand::PhysReg(r) = op {
let id = *r as u16;
id >= A0 && id <= A7
} else {
false
}
})
.count();
max_args = max_args.max(arg_count);
}
}
}
max_args
}
pub fn needs_stack_realignment(&self, local_alignment: u32) -> bool {
local_alignment > STACK_ALIGNMENT as u32
}
pub fn calculate_realignment_offset(&self, current_offset: i64, alignment: i64) -> i64 {
if alignment == 0 {
return 0;
}
let misalignment = current_offset % alignment;
if misalignment == 0 {
0
} else {
alignment - misalignment
}
}
pub fn emit_stack_realignment(&self, alignment: i64) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let tmp = T1 as u32;
let neg_align = -alignment;
if neg_align >= -2048 && neg_align <= 2047 {
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(tmp));
addi.operands.push(MachineOperand::PhysReg(ZERO as u32));
addi.push_imm(neg_align);
instrs.push(addi);
} else {
let upper = ((neg_align as u64 + 0x800) >> 12) as i64;
let mut lui = MachineInstr::new(RiscVOpcode::LUI as u32);
lui.operands.push(MachineOperand::PhysReg(tmp));
lui.push_imm(upper);
instrs.push(lui);
let lower = neg_align - (upper << 12);
if lower != 0 {
let mut addi = MachineInstr::new(RiscVOpcode::ADDI as u32);
addi.operands.push(MachineOperand::PhysReg(tmp));
addi.push_reg(tmp);
addi.push_imm(lower);
instrs.push(addi);
}
}
let mut and_mi = MachineInstr::new(RiscVOpcode::AND as u32);
and_mi.operands.push(MachineOperand::PhysReg(SP as u32));
and_mi.operands.push(MachineOperand::PhysReg(SP as u32));
and_mi.push_reg(tmp);
instrs.push(and_mi);
instrs
}
pub fn should_use_save_restore_libcall(&self, num_saved: usize) -> bool {
num_saved >= 5
}
pub fn emit_save_libcall(&self, num_regs: usize) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let symbol = format!("__riscv_save_{}", num_regs);
let mut auipc = MachineInstr::new(RiscVOpcode::AUIPC as u32);
auipc.operands.push(MachineOperand::PhysReg(RA as u32));
auipc
.operands
.push(MachineOperand::Global(format!("%pcrel_hi({})", symbol)));
instrs.push(auipc);
let mut jalr = MachineInstr::new(RiscVOpcode::JALR as u32);
jalr.operands.push(MachineOperand::PhysReg(RA as u32));
jalr.operands.push(MachineOperand::PhysReg(RA as u32));
jalr.operands
.push(MachineOperand::Global(format!("%pcrel_lo({})", symbol)));
instrs.push(jalr);
instrs
}
pub fn emit_restore_libcall(&self, num_regs: usize) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let symbol = format!("__riscv_restore_{}", num_regs);
let mut auipc = MachineInstr::new(RiscVOpcode::AUIPC as u32);
auipc.operands.push(MachineOperand::PhysReg(T1 as u32));
auipc
.operands
.push(MachineOperand::Global(format!("%pcrel_hi({})", symbol)));
instrs.push(auipc);
let mut jalr = MachineInstr::new(RiscVOpcode::JALR as u32);
jalr.operands.push(MachineOperand::PhysReg(ZERO as u32));
jalr.operands.push(MachineOperand::PhysReg(T1 as u32));
jalr.operands
.push(MachineOperand::Global(format!("%pcrel_lo({})", symbol)));
instrs.push(jalr);
instrs
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_simple_mf(name: &str) -> MachineFunction {
MachineFunction::new(name)
}
fn make_mf_with_call(name: &str) -> MachineFunction {
let mut mf = MachineFunction::new(name);
let mut bb = MachineBasicBlock {
name: "entry".to_string(),
instructions: Vec::new(),
successors: Vec::new(),
};
let mut call = MachineInstr::new(RiscVOpcode::CALL as u32);
call.operands
.push(MachineOperand::Global("helper".to_string()));
bb.instructions.push(call);
mf.blocks.push(bb);
mf
}
#[test]
fn test_new_rv64() {
let fl = RiscVFrameLowering::new(true);
assert!(fl.is_64bit);
}
#[test]
fn test_new_rv32() {
let fl = RiscVFrameLowering::new(false);
assert!(!fl.is_64bit);
}
#[test]
fn test_frame_info_default() {
let info = RiscVFrameInfo::default();
assert!(info.is_64bit);
assert_eq!(info.frame_size, 0);
assert!(!info.has_frame_pointer);
assert_eq!(info.reg_width(), 8);
}
#[test]
fn test_frame_info_rv32_reg_width() {
let info = RiscVFrameInfo::new(false);
assert_eq!(info.reg_width(), 4);
}
#[test]
fn test_prologue_rv64_empty() {
let fl = RiscVFrameLowering::new(true);
let info = RiscVFrameInfo::new(true);
let instrs = fl.emit_prologue(&info);
assert!(instrs.is_empty());
}
#[test]
fn test_prologue_rv64_with_frame() {
let fl = RiscVFrameLowering::new(true);
let mut info = RiscVFrameInfo::new(true);
info.frame_size = 32;
info.has_calls = true;
info.has_frame_pointer = true;
info.saved_ra_offset = 24;
info.saved_fp_offset = 16;
info.saved_regs = vec![S1, S2];
let instrs = fl.emit_prologue(&info);
assert!(!instrs.is_empty(), "Prologue should contain instructions");
let first = &instrs[0];
assert_eq!(first.opcode, RiscVOpcode::ADDI as u32);
let has_sd_ra = instrs.iter().any(|mi| {
mi.opcode == RiscVOpcode::SD as u32
&& mi.operands.len() >= 2
&& matches!(&mi.operands[0], MachineOperand::PhysReg(r) if *r == RA as u32)
});
assert!(has_sd_ra);
}
#[test]
fn test_prologue_rv32_with_frame() {
let fl = RiscVFrameLowering::new(false);
let mut info = RiscVFrameInfo::new(false);
info.frame_size = 16;
info.has_calls = true;
info.saved_ra_offset = 12;
info.saved_regs = vec![S1];
let instrs = fl.emit_prologue(&info);
assert!(!instrs.is_empty());
let has_sw = instrs.iter().any(|mi| mi.opcode == RiscVOpcode::SW as u32);
assert!(has_sw);
}
#[test]
fn test_epilogue_rv64_empty() {
let fl = RiscVFrameLowering::new(true);
let info = RiscVFrameInfo::new(true);
let instrs = fl.emit_epilogue(&info);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, RiscVOpcode::RET as u32);
}
#[test]
fn test_epilogue_rv64_with_frame() {
let fl = RiscVFrameLowering::new(true);
let mut info = RiscVFrameInfo::new(true);
info.frame_size = 32;
info.has_calls = true;
info.has_frame_pointer = true;
info.saved_ra_offset = 24;
info.saved_fp_offset = 16;
info.saved_regs = vec![S1];
let instrs = fl.emit_epilogue(&info);
let last = instrs.last().unwrap();
assert_eq!(last.opcode, RiscVOpcode::RET as u32);
let has_ld_ra = instrs.iter().any(|mi| {
mi.opcode == RiscVOpcode::LD as u32
&& mi.operands.len() >= 2
&& matches!(&mi.operands[0], MachineOperand::PhysReg(r) if *r == RA as u32)
});
assert!(has_ld_ra);
}
#[test]
fn test_build_frame_info_leaf() {
let fl = RiscVFrameLowering::new(true);
let mf = make_simple_mf("leaf");
let info = fl.build_frame_info(&mf);
assert!(!info.has_calls);
assert!(!info.has_frame_pointer);
}
#[test]
fn test_build_frame_info_with_call() {
let fl = RiscVFrameLowering::new(true);
let mf = make_mf_with_call("caller");
let info = fl.build_frame_info(&mf);
assert!(info.has_calls);
}
#[test]
fn test_needs_frame_pointer_small_frame() {
let fl = RiscVFrameLowering::new(true);
let info = RiscVFrameInfo::new(true);
assert!(!fl.needs_frame_pointer(&info));
}
#[test]
fn test_needs_frame_pointer_large_frame() {
let fl = RiscVFrameLowering::new(true);
let mut info = RiscVFrameInfo::new(true);
info.frame_size = 4096; assert!(fl.needs_frame_pointer(&info));
}
#[test]
fn test_needs_frame_pointer_many_saved_regs() {
let fl = RiscVFrameLowering::new(true);
let mut info = RiscVFrameInfo::new(true);
info.saved_regs = (0..8).map(|i| (S2 + i as u16)).collect();
assert!(fl.needs_frame_pointer(&info));
}
#[test]
fn test_align_frame_size() {
assert_eq!(RiscVFrameLowering::align_frame_size(0), 0);
assert_eq!(RiscVFrameLowering::align_frame_size(1), 16);
assert_eq!(RiscVFrameLowering::align_frame_size(15), 16);
assert_eq!(RiscVFrameLowering::align_frame_size(16), 16);
assert_eq!(RiscVFrameLowering::align_frame_size(17), 32);
assert_eq!(RiscVFrameLowering::align_frame_size(32), 32);
assert_eq!(RiscVFrameLowering::align_frame_size(-5), 0);
}
#[test]
fn test_calculate_frame_size() {
let fl = RiscVFrameLowering::new(true);
let mut info = RiscVFrameInfo::new(true);
info.saved_regs = vec![S1, S2];
assert_eq!(fl.calculate_frame_size(&info), 24);
info.has_frame_pointer = true;
assert_eq!(fl.calculate_frame_size(&info), 32);
}
#[test]
fn test_assign_callee_saved_spill_slots() {
let fl = RiscVFrameLowering::new(true);
let mut info = RiscVFrameInfo::new(true);
info.saved_regs = vec![S1, S2, S3];
info.local_area_offset = 0;
let slots = fl.assign_callee_saved_spill_slots(&info);
assert_eq!(slots.len(), 3);
assert_eq!(slots[0], (S1, 0));
assert_eq!(slots[1], (S2, 8));
assert_eq!(slots[2], (S3, 16));
}
#[test]
fn test_assign_callee_saved_spill_slots_rv32() {
let fl = RiscVFrameLowering::new(false);
let mut info = RiscVFrameInfo::new(false);
info.saved_regs = vec![S1, S2];
info.local_area_offset = 0;
let slots = fl.assign_callee_saved_spill_slots(&info);
assert_eq!(slots.len(), 2);
assert_eq!(slots[0], (S1, 0));
assert_eq!(slots[1], (S2, 4)); }
#[test]
fn test_get_frame_index_reference_with_fp() {
let fl = RiscVFrameLowering::new(true);
let mut info = RiscVFrameInfo::new(true);
info.has_frame_pointer = true;
info.frame_size = 48;
let ref_offset = fl.get_frame_index_reference(&info, 0);
assert_eq!(ref_offset, -48);
}
#[test]
fn test_get_frame_index_reference_without_fp() {
let fl = RiscVFrameLowering::new(true);
let info = RiscVFrameInfo::new(true);
let ref_offset = fl.get_frame_index_reference(&info, 8);
assert_eq!(ref_offset, 8);
}
#[test]
fn test_is_reg_used_in_function() {
let fl = RiscVFrameLowering::new(true);
let mut mf = MachineFunction::new("test");
let mut bb = MachineBasicBlock {
name: "entry".to_string(),
instructions: Vec::new(),
successors: Vec::new(),
};
let mut mi = MachineInstr::new(RiscVOpcode::ADD as u32);
mi.operands.push(MachineOperand::PhysReg(S1 as u32));
mi.operands.push(MachineOperand::PhysReg(A0 as u32));
bb.instructions.push(mi);
mf.blocks.push(bb);
assert!(fl.is_reg_used_in_function(&mf, S1 as u32));
assert!(fl.is_reg_used_in_function(&mf, A0 as u32));
assert!(!fl.is_reg_used_in_function(&mf, S3 as u32));
}
#[test]
fn test_prologue_contains_frame_setup() {
let fl = RiscVFrameLowering::new(true);
let mut info = RiscVFrameInfo::new(true);
info.frame_size = 64;
info.has_calls = true;
info.has_frame_pointer = true;
info.saved_ra_offset = 56;
info.saved_fp_offset = 48;
info.saved_regs = vec![S1];
let instrs = fl.emit_prologue(&info);
let has_fp_setup = instrs.iter().any(|mi| {
mi.opcode == RiscVOpcode::ADDI as u32
&& mi.operands.len() >= 3
&& matches!(&mi.operands[0], MachineOperand::PhysReg(r) if *r == S0 as u32)
&& matches!(&mi.operands[1], MachineOperand::PhysReg(r) if *r == SP as u32)
});
assert!(has_fp_setup);
}
#[test]
fn test_epilogue_restores_in_reverse_order() {
let fl = RiscVFrameLowering::new(true);
let mut info = RiscVFrameInfo::new(true);
info.frame_size = 32;
info.has_calls = true;
info.saved_ra_offset = 24;
info.saved_regs = vec![S1, S2];
let instrs = fl.emit_epilogue(&info);
let lds: Vec<_> = instrs
.iter()
.filter(|mi| mi.opcode == RiscVOpcode::LD as u32)
.collect();
assert!(lds.len() >= 2);
}
#[test]
fn test_total_frame_size() {
let mut info = RiscVFrameInfo::new(true);
info.frame_size = 32;
info.fixed_frame_size = 16;
assert_eq!(info.total_frame_size(), 48);
}
#[test]
fn test_rv32e_frame_info() {
let fl = RiscVFrameLowering::new(false);
let mf = make_mf_with_call("test_rv32e");
let info = fl.build_frame_info_rv32e(&mf);
assert_eq!(info.reg_width(), 4);
assert!(!info.has_frame_pointer || info.frame_size >= 0);
}
#[test]
fn test_rv32e_prologue_epilogue() {
let fl = RiscVFrameLowering::new(false);
let info = RiscVFrameInfo {
frame_size: 16,
saved_regs: vec![S1],
has_frame_pointer: false,
has_calls: true,
local_area_offset: 0,
callee_saved_size: 4,
saved_ra_offset: 8,
saved_fp_offset: 0,
fixed_frame_size: 0,
is_64bit: false,
};
let prologue = fl.emit_prologue_rv32e(&info);
assert!(!prologue.is_empty());
assert!(prologue
.iter()
.any(|mi| mi.opcode == RiscVOpcode::SW as u32));
let epilogue = fl.emit_epilogue_rv32e(&info);
assert!(!epilogue.is_empty());
assert_eq!(epilogue.last().unwrap().opcode, RiscVOpcode::RET as u32);
}
#[test]
fn test_large_frame_prologue() {
let fl = RiscVFrameLowering::new(true);
let info = RiscVFrameInfo {
frame_size: 4096,
saved_regs: vec![S2, S3],
has_frame_pointer: true,
has_calls: true,
local_area_offset: 0,
callee_saved_size: 16,
saved_ra_offset: 4080,
saved_fp_offset: 4072,
fixed_frame_size: 0,
is_64bit: true,
};
let prologue = fl.emit_prologue_large_frame(&info);
assert!(!prologue.is_empty());
assert!(prologue
.iter()
.any(|mi| mi.opcode == RiscVOpcode::LUI as u32));
}
#[test]
fn test_large_frame_epilogue() {
let fl = RiscVFrameLowering::new(true);
let info = RiscVFrameInfo {
frame_size: 4096,
saved_regs: vec![],
has_frame_pointer: false,
has_calls: false,
local_area_offset: 0,
callee_saved_size: 0,
saved_ra_offset: 4088,
saved_fp_offset: 0,
fixed_frame_size: 0,
is_64bit: true,
};
let epilogue = fl.emit_epilogue_large_frame(&info);
assert!(!epilogue.is_empty());
assert_eq!(epilogue.last().unwrap().opcode, RiscVOpcode::RET as u32);
}
#[test]
fn test_has_variable_sized_objects() {
let fl = RiscVFrameLowering::new(true);
let mf = make_simple_mf("test_vla");
assert!(!fl.has_variable_sized_objects(&mf));
}
#[test]
fn test_calculate_outgoing_arg_area() {
let fl = RiscVFrameLowering::new(true);
let area = fl.calculate_outgoing_arg_area(10, false);
assert_eq!(area, 16);
let area2 = fl.calculate_outgoing_arg_area(5, false);
assert_eq!(area2, 0);
let area3 = fl.calculate_outgoing_arg_area(0, false);
assert_eq!(area3, 0);
}
#[test]
fn test_needs_stack_realignment() {
let fl = RiscVFrameLowering::new(true);
assert!(!fl.needs_stack_realignment(8));
assert!(!fl.needs_stack_realignment(16));
assert!(fl.needs_stack_realignment(32));
assert!(fl.needs_stack_realignment(64));
}
#[test]
fn test_calculate_realignment_offset() {
let fl = RiscVFrameLowering::new(true);
assert_eq!(fl.calculate_realignment_offset(0, 32), 0);
assert_eq!(fl.calculate_realignment_offset(32, 32), 0);
assert_eq!(fl.calculate_realignment_offset(40, 32), 24);
}
#[test]
fn test_save_restore_libcall() {
let fl = RiscVFrameLowering::new(true);
assert!(!fl.should_use_save_restore_libcall(2));
assert!(!fl.should_use_save_restore_libcall(4));
assert!(fl.should_use_save_restore_libcall(5));
assert!(fl.should_use_save_restore_libcall(10));
}
#[test]
fn test_emit_save_libcall() {
let fl = RiscVFrameLowering::new(true);
let instrs = fl.emit_save_libcall(5);
assert!(!instrs.is_empty());
assert!(instrs
.iter()
.any(|mi| mi.opcode == RiscVOpcode::AUIPC as u32));
assert!(instrs
.iter()
.any(|mi| mi.opcode == RiscVOpcode::JALR as u32));
}
#[test]
fn test_emit_restore_libcall() {
let fl = RiscVFrameLowering::new(true);
let instrs = fl.emit_restore_libcall(5);
assert!(!instrs.is_empty());
assert_eq!(instrs.last().unwrap().opcode, RiscVOpcode::JALR as u32);
}
#[test]
fn test_estimate_max_call_args() {
let fl = RiscVFrameLowering::new(true);
let mf = make_mf_with_call("test_args");
let args = fl.estimate_max_call_args(&mf);
assert!(args <= 8); }
}