arm32 codegen

This commit adds arm32 code generation for some IR insts.
Floating-point instructions are not supported, because regalloc
does not allow to represent overlapping register classes,
which are needed by VFP/Neon.

There is also no support for big-endianness, I64 and I128 types.
This commit is contained in:
Jakub Krauz
2020-09-16 12:11:29 +02:00
parent c29f4599ac
commit f6a140a662
26 changed files with 7103 additions and 381 deletions

View File

@@ -1,108 +1,450 @@
//! ARM ABI implementation.
//! This is from the RISC-V target and will need to be updated for ARM32.
//! Implementation of the 32-bit ARM ABI.
use super::registers::{D, GPR, Q, S};
use crate::abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion};
use crate::ir::{self, AbiParam, ArgumentExtension, ArgumentLoc, Type};
use crate::isa::RegClass;
use crate::regalloc::RegisterSet;
use alloc::borrow::Cow;
use core::i32;
use target_lexicon::Triple;
use crate::ir;
use crate::ir::types::*;
use crate::ir::SourceLoc;
use crate::isa;
use crate::isa::arm32::inst::*;
use crate::machinst::*;
use crate::settings;
use crate::{CodegenError, CodegenResult};
use alloc::boxed::Box;
use alloc::vec::Vec;
use regalloc::{RealReg, Reg, RegClass, Set, Writable};
use smallvec::SmallVec;
struct Args {
pointer_bits: u8,
pointer_bytes: u8,
pointer_type: Type,
regs: u32,
reg_limit: u32,
offset: u32,
}
/// Support for the ARM ABI from the callee side (within a function body).
pub(crate) type Arm32ABICallee = ABICalleeImpl<Arm32MachineDeps>;
impl Args {
fn new(bits: u8) -> Self {
Self {
pointer_bits: bits,
pointer_bytes: bits / 8,
pointer_type: Type::int(u16::from(bits)).unwrap(),
regs: 0,
reg_limit: 8,
offset: 0,
/// Support for the ARM ABI from the caller side (at a callsite).
pub(crate) type Arm32ABICaller = ABICallerImpl<Arm32MachineDeps>;
/// This is the limit for the size of argument and return-value areas on the
/// stack. We place a reasonable limit here to avoid integer overflow issues
/// with 32-bit arithmetic: for now, 128 MB.
static STACK_ARG_RET_SIZE_LIMIT: u64 = 128 * 1024 * 1024;
/// ARM-specific ABI behavior. This struct just serves as an implementation
/// point for the trait; it is never actually instantiated.
pub(crate) struct Arm32MachineDeps;
impl Into<AMode> for StackAMode {
fn into(self) -> AMode {
match self {
StackAMode::FPOffset(off, ty) => AMode::FPOffset(off, ty),
StackAMode::NominalSPOffset(off, ty) => AMode::NominalSPOffset(off, ty),
StackAMode::SPOffset(off, ty) => AMode::SPOffset(off, ty),
}
}
}
impl ArgAssigner for Args {
fn assign(&mut self, arg: &AbiParam) -> ArgAction {
fn align(value: u32, to: u32) -> u32 {
(value + to - 1) & !(to - 1)
}
impl ABIMachineSpec for Arm32MachineDeps {
type I = Inst;
let ty = arg.value_type;
fn word_bits() -> u32 {
32
}
// Check for a legal type.
// SIMD instructions are currently no implemented, so break down vectors
if ty.is_vector() {
return ValueConversion::VectorSplit.into();
}
fn compute_arg_locs(
_call_conv: isa::CallConv,
params: &[ir::AbiParam],
args_or_rets: ArgsOrRets,
add_ret_area_ptr: bool,
) -> CodegenResult<(Vec<ABIArg>, i64, Option<usize>)> {
let mut next_rreg = 0;
let mut next_stack: u64 = 0;
let mut ret = vec![];
let mut stack_args = vec![];
// Large integers and booleans are broken down to fit in a register.
if !ty.is_float() && ty.bits() > u16::from(self.pointer_bits) {
// Align registers and stack to a multiple of two pointers.
self.regs = align(self.regs, 2);
self.offset = align(self.offset, 2 * u32::from(self.pointer_bytes));
return ValueConversion::IntSplit.into();
}
let max_reg_val = 4; // r0-r3
// Small integers are extended to the size of a pointer register.
if ty.is_int() && ty.bits() < u16::from(self.pointer_bits) {
match arg.extension {
ArgumentExtension::None => {}
ArgumentExtension::Uext => return ValueConversion::Uext(self.pointer_type).into(),
ArgumentExtension::Sext => return ValueConversion::Sext(self.pointer_type).into(),
for i in 0..params.len() {
let param = params[i];
// Validate "purpose".
match &param.purpose {
&ir::ArgumentPurpose::VMContext
| &ir::ArgumentPurpose::Normal
| &ir::ArgumentPurpose::StackLimit
| &ir::ArgumentPurpose::SignatureId => {}
_ => panic!(
"Unsupported argument purpose {:?} in signature: {:?}",
param.purpose, params
),
}
assert!(param.value_type.bits() <= 32);
if next_rreg < max_reg_val {
let reg = rreg(next_rreg);
ret.push(ABIArg::Reg(
reg.to_real_reg(),
param.value_type,
param.extension,
));
next_rreg += 1;
} else {
// Arguments are stored on stack in reversed order.
// https://static.docs.arm.com/ihi0042/g/aapcs32.pdf
// Stack offset is not known yet. Store param info for later.
stack_args.push((param.value_type, param.extension));
next_stack += 4;
}
}
if self.regs < self.reg_limit {
// Assign to a register.
let reg = GPR.unit(10 + self.regs as usize);
self.regs += 1;
ArgumentLoc::Reg(reg).into()
let extra_arg = if add_ret_area_ptr {
debug_assert!(args_or_rets == ArgsOrRets::Args);
if next_rreg < max_reg_val {
ret.push(ABIArg::Reg(
rreg(next_rreg).to_real_reg(),
I32,
ir::ArgumentExtension::None,
));
} else {
stack_args.push((I32, ir::ArgumentExtension::None));
next_stack += 4;
}
Some(ret.len() - 1)
} else {
// Assign a stack location.
let loc = ArgumentLoc::Stack(self.offset as i32);
self.offset += u32::from(self.pointer_bytes);
debug_assert!(self.offset <= i32::MAX as u32);
loc.into()
None
};
// Now we can assign proper stack offsets to params.
let max_stack = next_stack;
for (ty, ext) in stack_args.into_iter().rev() {
next_stack -= 4;
ret.push(ABIArg::Stack((max_stack - next_stack) as i64, ty, ext));
}
assert_eq!(next_stack, 0);
next_stack = (next_stack + 7) & !7;
// To avoid overflow issues, limit the arg/return size to something
// reasonable -- here, 128 MB.
if next_stack > STACK_ARG_RET_SIZE_LIMIT {
return Err(CodegenError::ImplLimitExceeded);
}
Ok((ret, next_stack as i64, extra_arg))
}
fn fp_to_arg_offset(_call_conv: isa::CallConv, _flags: &settings::Flags) -> i64 {
8 // frame pointer and link register
}
fn gen_load_stack(mem: StackAMode, into_reg: Writable<Reg>, ty: Type) -> Inst {
Inst::gen_load(into_reg, mem.into(), ty)
}
fn gen_store_stack(mem: StackAMode, from_reg: Reg, ty: Type) -> Inst {
Inst::gen_store(from_reg, mem.into(), ty)
}
fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Inst {
Inst::gen_move(to_reg, from_reg, ty)
}
fn gen_extend(
to_reg: Writable<Reg>,
from_reg: Reg,
is_signed: bool,
from_bits: u8,
to_bits: u8,
) -> Inst {
assert!(to_bits == 32);
assert!(from_bits < 32);
Inst::Extend {
rd: to_reg,
rm: from_reg,
signed: is_signed,
from_bits,
}
}
}
/// Legalize `sig`.
pub fn legalize_signature(sig: &mut Cow<ir::Signature>, triple: &Triple, _current: bool) {
let bits = triple.pointer_width().unwrap().bits();
let mut args = Args::new(bits);
if let Some(new_params) = legalize_args(&sig.params, &mut args) {
sig.to_mut().params = new_params;
fn gen_ret() -> Inst {
Inst::Ret
}
}
/// Get register class for a type appearing in a legalized signature.
pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass {
if ty.is_int() {
GPR
} else {
match ty.bits() {
32 => S,
64 => D,
128 => Q,
_ => panic!("Unexpected {} ABI type for arm32", ty),
fn gen_epilogue_placeholder() -> Inst {
Inst::EpiloguePlaceholder
}
fn gen_add_imm(into_reg: Writable<Reg>, from_reg: Reg, imm: u32) -> SmallVec<[Inst; 4]> {
let mut insts = SmallVec::new();
if let Some(imm12) = UImm12::maybe_from_i64(imm as i64) {
insts.push(Inst::AluRRImm12 {
alu_op: ALUOp::Add,
rd: into_reg,
rn: from_reg,
imm12,
});
} else {
let scratch2 = writable_tmp2_reg();
insts.extend(Inst::load_constant(scratch2, imm));
insts.push(Inst::AluRRRShift {
alu_op: ALUOp::Add,
rd: into_reg,
rn: from_reg,
rm: scratch2.to_reg(),
shift: None,
});
}
insts
}
fn gen_stack_lower_bound_trap(limit_reg: Reg) -> SmallVec<[Inst; 2]> {
let mut insts = SmallVec::new();
insts.push(Inst::Cmp {
rn: sp_reg(),
rm: limit_reg,
});
insts.push(Inst::TrapIf {
trap_info: (ir::SourceLoc::default(), ir::TrapCode::StackOverflow),
// Here `Lo` == "less than" when interpreting the two
// operands as unsigned integers.
cond: Cond::Lo,
});
insts
}
fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>, _ty: Type) -> Inst {
let mem = mem.into();
Inst::LoadAddr { rd: into_reg, mem }
}
fn get_stacklimit_reg() -> Reg {
ip_reg()
}
fn gen_load_base_offset(into_reg: Writable<Reg>, base: Reg, offset: i32, ty: Type) -> Inst {
let mem = AMode::RegOffset(base, offset as i64);
Inst::gen_load(into_reg, mem, ty)
}
fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Inst {
let mem = AMode::RegOffset(base, offset as i64);
Inst::gen_store(from_reg, mem, ty)
}
fn gen_sp_reg_adjust(amount: i32) -> SmallVec<[Inst; 2]> {
let mut ret = SmallVec::new();
if amount == 0 {
return ret;
}
let (amount, is_sub) = if amount > 0 {
(amount, false)
} else {
(-amount, true)
};
let alu_op = if is_sub { ALUOp::Sub } else { ALUOp::Add };
if let Some(imm12) = UImm12::maybe_from_i64(amount as i64) {
ret.push(Inst::AluRRImm12 {
alu_op,
rd: writable_sp_reg(),
rn: sp_reg(),
imm12,
});
} else {
let tmp = writable_ip_reg();
ret.extend(Inst::load_constant(tmp, amount as u32));
ret.push(Inst::AluRRRShift {
alu_op,
rd: writable_sp_reg(),
rn: sp_reg(),
rm: tmp.to_reg(),
shift: None,
});
}
ret
}
fn gen_nominal_sp_adj(offset: i32) -> Inst {
let offset = i64::from(offset);
Inst::VirtualSPOffsetAdj { offset }
}
fn gen_prologue_frame_setup() -> SmallVec<[Inst; 2]> {
let mut ret = SmallVec::new();
let reg_list = vec![fp_reg(), lr_reg()];
ret.push(Inst::Push { reg_list });
ret.push(Inst::Mov {
rd: writable_fp_reg(),
rm: sp_reg(),
});
ret
}
fn gen_epilogue_frame_restore() -> SmallVec<[Inst; 2]> {
let mut ret = SmallVec::new();
ret.push(Inst::Mov {
rd: writable_sp_reg(),
rm: fp_reg(),
});
let reg_list = vec![writable_fp_reg(), writable_lr_reg()];
ret.push(Inst::Pop { reg_list });
ret
}
/// Returns stack bytes used as well as instructions. Does not adjust
/// nominal SP offset; caller will do that.
fn gen_clobber_save(
_call_conv: isa::CallConv,
clobbers: &Set<Writable<RealReg>>,
) -> (u64, SmallVec<[Inst; 16]>) {
let mut insts = SmallVec::new();
let clobbered_vec = get_callee_saves(clobbers);
let mut clobbered_vec: Vec<_> = clobbered_vec
.into_iter()
.map(|r| r.to_reg().to_reg())
.collect();
if clobbered_vec.len() % 2 == 1 {
// For alignment purposes.
clobbered_vec.push(ip_reg());
}
let stack_used = clobbered_vec.len() * 4;
if !clobbered_vec.is_empty() {
insts.push(Inst::Push {
reg_list: clobbered_vec,
});
}
(stack_used as u64, insts)
}
fn gen_clobber_restore(
_call_conv: isa::CallConv,
clobbers: &Set<Writable<RealReg>>,
) -> SmallVec<[Inst; 16]> {
let mut insts = SmallVec::new();
let clobbered_vec = get_callee_saves(clobbers);
let mut clobbered_vec: Vec<_> = clobbered_vec
.into_iter()
.map(|r| Writable::from_reg(r.to_reg().to_reg()))
.collect();
if clobbered_vec.len() % 2 == 1 {
clobbered_vec.push(writable_ip_reg());
}
if !clobbered_vec.is_empty() {
insts.push(Inst::Pop {
reg_list: clobbered_vec,
});
}
insts
}
fn gen_call(
dest: &CallDest,
uses: Vec<Reg>,
defs: Vec<Writable<Reg>>,
loc: SourceLoc,
opcode: ir::Opcode,
tmp: Writable<Reg>,
) -> SmallVec<[(InstIsSafepoint, Inst); 2]> {
let mut insts = SmallVec::new();
match &dest {
&CallDest::ExtName(ref name, RelocDistance::Near) => insts.push((
InstIsSafepoint::Yes,
Inst::Call {
info: Box::new(CallInfo {
dest: name.clone(),
uses,
defs,
loc,
opcode,
}),
},
)),
&CallDest::ExtName(ref name, RelocDistance::Far) => {
insts.push((
InstIsSafepoint::No,
Inst::LoadExtName {
rt: tmp,
name: Box::new(name.clone()),
offset: 0,
srcloc: loc,
},
));
insts.push((
InstIsSafepoint::Yes,
Inst::CallInd {
info: Box::new(CallIndInfo {
rm: tmp.to_reg(),
uses,
defs,
loc,
opcode,
}),
},
));
}
&CallDest::Reg(reg) => insts.push((
InstIsSafepoint::Yes,
Inst::CallInd {
info: Box::new(CallIndInfo {
rm: *reg,
uses,
defs,
loc,
opcode,
}),
},
)),
}
insts
}
fn get_number_of_spillslots_for_value(rc: RegClass, _ty: Type) -> u32 {
match rc {
RegClass::I32 => 1,
_ => panic!("Unexpected register class!"),
}
}
fn get_virtual_sp_offset_from_state(s: &EmitState) -> i64 {
s.virtual_sp_offset
}
fn get_nominal_sp_to_fp(s: &EmitState) -> i64 {
s.nominal_sp_to_fp
}
fn get_caller_saves(_call_conv: isa::CallConv) -> Vec<Writable<Reg>> {
let mut caller_saved = Vec::new();
for i in 0..15 {
let r = writable_rreg(i);
if is_caller_save(r.to_reg().to_real_reg()) {
caller_saved.push(r);
}
}
caller_saved
}
}
/// Get the set of allocatable registers for `func`.
pub fn allocatable_registers(_func: &ir::Function) -> RegisterSet {
unimplemented!()
fn is_callee_save(r: RealReg) -> bool {
let enc = r.get_hw_encoding();
4 <= enc && enc <= 10
}
fn get_callee_saves(regs: &Set<Writable<RealReg>>) -> Vec<Writable<RealReg>> {
let mut ret = Vec::new();
for &reg in regs.iter() {
if is_callee_save(reg.to_reg()) {
ret.push(reg);
}
}
// Sort registers for deterministic code output.
ret.sort_by_key(|r| r.to_reg().get_index());
ret
}
fn is_caller_save(r: RealReg) -> bool {
let enc = r.get_hw_encoding();
enc <= 3
}