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

@@ -109,6 +109,10 @@ pub(crate) struct AArch64MachineDeps;
impl ABIMachineSpec for AArch64MachineDeps { impl ABIMachineSpec for AArch64MachineDeps {
type I = Inst; type I = Inst;
fn word_bits() -> u32 {
64
}
fn compute_arg_locs( fn compute_arg_locs(
call_conv: isa::CallConv, call_conv: isa::CallConv,
params: &[ir::AbiParam], params: &[ir::AbiParam],

View File

@@ -1,108 +1,450 @@
//! ARM ABI implementation. //! Implementation of the 32-bit ARM ABI.
//! This is from the RISC-V target and will need to be updated for ARM32.
use super::registers::{D, GPR, Q, S}; use crate::ir;
use crate::abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion}; use crate::ir::types::*;
use crate::ir::{self, AbiParam, ArgumentExtension, ArgumentLoc, Type}; use crate::ir::SourceLoc;
use crate::isa::RegClass; use crate::isa;
use crate::regalloc::RegisterSet; use crate::isa::arm32::inst::*;
use alloc::borrow::Cow; use crate::machinst::*;
use core::i32; use crate::settings;
use target_lexicon::Triple; use crate::{CodegenError, CodegenResult};
use alloc::boxed::Box;
use alloc::vec::Vec;
use regalloc::{RealReg, Reg, RegClass, Set, Writable};
use smallvec::SmallVec;
struct Args { /// Support for the ARM ABI from the callee side (within a function body).
pointer_bits: u8, pub(crate) type Arm32ABICallee = ABICalleeImpl<Arm32MachineDeps>;
pointer_bytes: u8,
pointer_type: Type, /// Support for the ARM ABI from the caller side (at a callsite).
regs: u32, pub(crate) type Arm32ABICaller = ABICallerImpl<Arm32MachineDeps>;
reg_limit: u32,
offset: u32, /// 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 Args { impl ABIMachineSpec for Arm32MachineDeps {
fn new(bits: u8) -> Self { type I = Inst;
Self {
pointer_bits: bits, fn word_bits() -> u32 {
pointer_bytes: bits / 8, 32
pointer_type: Type::int(u16::from(bits)).unwrap(), }
regs: 0,
reg_limit: 8, 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![];
let max_reg_val = 4; // r0-r3
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;
}
}
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 {
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,
}
}
fn gen_ret() -> Inst {
Inst::Ret
}
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, 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
} }
} }
impl ArgAssigner for Args { fn is_callee_save(r: RealReg) -> bool {
fn assign(&mut self, arg: &AbiParam) -> ArgAction { let enc = r.get_hw_encoding();
fn align(value: u32, to: u32) -> u32 { 4 <= enc && enc <= 10
(value + to - 1) & !(to - 1)
}
let ty = arg.value_type;
// Check for a legal type.
// SIMD instructions are currently no implemented, so break down vectors
if ty.is_vector() {
return ValueConversion::VectorSplit.into();
}
// 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();
}
// 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(),
}
}
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()
} 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()
}
}
} }
/// Legalize `sig`. fn get_callee_saves(regs: &Set<Writable<RealReg>>) -> Vec<Writable<RealReg>> {
pub fn legalize_signature(sig: &mut Cow<ir::Signature>, triple: &Triple, _current: bool) { let mut ret = Vec::new();
let bits = triple.pointer_width().unwrap().bits(); for &reg in regs.iter() {
if is_callee_save(reg.to_reg()) {
let mut args = Args::new(bits); ret.push(reg);
if let Some(new_params) = legalize_args(&sig.params, &mut args) {
sig.to_mut().params = new_params;
}
}
/// 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),
} }
} }
// Sort registers for deterministic code output.
ret.sort_by_key(|r| r.to_reg().get_index());
ret
} }
/// Get the set of allocatable registers for `func`. fn is_caller_save(r: RealReg) -> bool {
pub fn allocatable_registers(_func: &ir::Function) -> RegisterSet { let enc = r.get_hw_encoding();
unimplemented!() enc <= 3
} }

View File

@@ -1,8 +0,0 @@
//! Emitting binary ARM32 machine code.
use crate::binemit::{bad_encoding, CodeSink};
use crate::ir::{Function, Inst};
use crate::isa::TargetIsa;
use crate::regalloc::RegDiversions;
include!(concat!(env!("OUT_DIR"), "/binemit-arm32.rs"));

View File

@@ -1,10 +0,0 @@
//! Encoding tables for ARM32 ISA.
use crate::ir;
use crate::isa;
use crate::isa::constraints::*;
use crate::isa::enc_tables::*;
use crate::isa::encoding::RecipeSizing;
include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs"));
include!(concat!(env!("OUT_DIR"), "/legalize-arm32.rs"));

View File

@@ -0,0 +1,335 @@
//! 32-bit ARM ISA definitions: instruction arguments.
use crate::isa::arm32::inst::*;
use regalloc::{RealRegUniverse, Reg};
use std::string::String;
/// A shift operator for a register or immediate.
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum ShiftOp {
LSL = 0b00,
LSR = 0b01,
ASR = 0b10,
ROR = 0b11,
}
impl ShiftOp {
/// Get the encoding of this shift op.
pub fn bits(self) -> u8 {
self as u8
}
}
/// A shift operator amount.
#[derive(Clone, Copy, Debug)]
pub struct ShiftOpShiftImm(u8);
impl ShiftOpShiftImm {
/// Maximum shift for shifted-register operands.
pub const MAX_SHIFT: u32 = 31;
/// Create a new shiftop shift amount, if possible.
pub fn maybe_from_shift(shift: u32) -> Option<ShiftOpShiftImm> {
if shift <= Self::MAX_SHIFT {
Some(ShiftOpShiftImm(shift as u8))
} else {
None
}
}
/// Return the shift amount.
pub fn value(self) -> u8 {
self.0
}
}
/// A shift operator with an amount, guaranteed to be within range.
#[derive(Clone, Debug)]
pub struct ShiftOpAndAmt {
op: ShiftOp,
shift: ShiftOpShiftImm,
}
impl ShiftOpAndAmt {
pub fn new(op: ShiftOp, shift: ShiftOpShiftImm) -> ShiftOpAndAmt {
ShiftOpAndAmt { op, shift }
}
/// Get the shift op.
pub fn op(&self) -> ShiftOp {
self.op
}
/// Get the shift amount.
pub fn amt(&self) -> ShiftOpShiftImm {
self.shift
}
}
// An unsigned 8-bit immediate.
#[derive(Clone, Copy, Debug)]
pub struct UImm8 {
/// The value.
value: u8,
}
impl UImm8 {
pub fn maybe_from_i64(value: i64) -> Option<UImm8> {
if 0 <= value && value < (1 << 8) {
Some(UImm8 { value: value as u8 })
} else {
None
}
}
/// Bits for encoding.
pub fn bits(&self) -> u32 {
u32::from(self.value)
}
}
/// An unsigned 12-bit immediate.
#[derive(Clone, Copy, Debug)]
pub struct UImm12 {
/// The value.
value: u16,
}
impl UImm12 {
pub fn maybe_from_i64(value: i64) -> Option<UImm12> {
if 0 <= value && value < (1 << 12) {
Some(UImm12 {
value: value as u16,
})
} else {
None
}
}
/// Bits for encoding.
pub fn bits(&self) -> u32 {
u32::from(self.value)
}
}
/// An addressing mode specified for a load/store operation.
#[derive(Clone, Debug)]
pub enum AMode {
// Real addressing modes
/// Register plus register offset, which can be shifted left by imm2.
RegReg(Reg, Reg, u8),
/// Unsigned 12-bit immediate offset from reg.
RegOffset12(Reg, UImm12),
/// Immediate offset from program counter aligned to 4.
/// Cannot be used by store instructions.
PCRel(i32),
// Virtual addressing modes that are lowered at emission time:
/// Immediate offset from reg.
RegOffset(Reg, i64),
/// Signed immediate offset from stack pointer.
SPOffset(i64, Type),
/// Offset from the frame pointer.
FPOffset(i64, Type),
/// Signed immediate offset from "nominal stack pointer".
NominalSPOffset(i64, Type),
}
impl AMode {
/// Memory reference using the sum of two registers as an address.
pub fn reg_plus_reg(reg1: Reg, reg2: Reg, shift_amt: u8) -> AMode {
assert!(shift_amt <= 3);
AMode::RegReg(reg1, reg2, shift_amt)
}
/// Memory reference using the sum of a register and an immediate offset
/// as an address.
pub fn reg_plus_imm(reg: Reg, offset: i64) -> AMode {
AMode::RegOffset(reg, offset)
}
}
/// Condition for conditional branches.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum Cond {
Eq = 0,
Ne = 1,
Hs = 2,
Lo = 3,
Mi = 4,
Pl = 5,
Vs = 6,
Vc = 7,
Hi = 8,
Ls = 9,
Ge = 10,
Lt = 11,
Gt = 12,
Le = 13,
Al = 14,
}
impl Cond {
/// Return the inverted condition.
pub fn invert(self) -> Cond {
match self {
Cond::Eq => Cond::Ne,
Cond::Ne => Cond::Eq,
Cond::Hs => Cond::Lo,
Cond::Lo => Cond::Hs,
Cond::Mi => Cond::Pl,
Cond::Pl => Cond::Mi,
Cond::Vs => Cond::Vc,
Cond::Vc => Cond::Vs,
Cond::Hi => Cond::Ls,
Cond::Ls => Cond::Hi,
Cond::Ge => Cond::Lt,
Cond::Lt => Cond::Ge,
Cond::Gt => Cond::Le,
Cond::Le => Cond::Gt,
Cond::Al => panic!("Cannot inverse {:?} condition", self),
}
}
/// Return the machine encoding of this condition.
pub fn bits(self) -> u16 {
self as u16
}
}
/// A branch target. Either unresolved (basic-block index) or resolved (offset
/// from end of current instruction).
#[derive(Clone, Copy, Debug)]
pub enum BranchTarget {
/// An unresolved reference to a Label.
Label(MachLabel),
/// A fixed PC offset.
ResolvedOffset(i32),
}
impl BranchTarget {
/// Return the target's label, if it is a label-based target.
pub fn as_label(self) -> Option<MachLabel> {
match self {
BranchTarget::Label(l) => Some(l),
_ => None,
}
}
// Ready for embedding in instruction.
fn as_offset(self, inst_16_bit: bool) -> i32 {
match self {
BranchTarget::ResolvedOffset(off) => {
if inst_16_bit {
// pc is equal to end of the current inst + 2.
(off - 2) >> 1
} else {
// pc points to end of the current inst.
off >> 1
}
}
_ => 0,
}
}
// For 32-bit unconditional jump.
pub fn as_off24(self) -> u32 {
let off = self.as_offset(false);
assert!(off < (1 << 24));
assert!(off >= -(1 << 24));
(off as u32) & ((1 << 24) - 1)
}
// For 32-bit conditional jump.
pub fn as_off20(self) -> u32 {
let off = self.as_offset(false);
assert!(off < (1 << 20));
assert!(off >= -(1 << 20));
(off as u32) & ((1 << 20) - 1)
}
}
impl ShowWithRRU for ShiftOpAndAmt {
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
let op = match self.op() {
ShiftOp::LSL => "lsl",
ShiftOp::LSR => "lsr",
ShiftOp::ASR => "asr",
ShiftOp::ROR => "ror",
};
format!("{} #{}", op, self.amt().value())
}
}
impl ShowWithRRU for UImm8 {
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
format!("#{}", self.value)
}
}
impl ShowWithRRU for UImm12 {
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
format!("#{}", self.value)
}
}
impl ShowWithRRU for AMode {
fn show_rru(&self, mb_rru: Option<&RealRegUniverse>) -> String {
match self {
&AMode::RegReg(rn, rm, imm2) => {
let shift = if imm2 != 0 {
format!(", lsl #{}", imm2)
} else {
"".to_string()
};
format!(
"[{}, {}{}]",
rn.show_rru(mb_rru),
rm.show_rru(mb_rru),
shift
)
}
&AMode::RegOffset12(rn, off) => {
format!("[{}, {}]", rn.show_rru(mb_rru), off.show_rru(mb_rru))
}
&AMode::PCRel(off) => format!("[pc, #{}]", off),
&AMode::RegOffset(..)
| &AMode::SPOffset(..)
| &AMode::FPOffset(..)
| &AMode::NominalSPOffset(..) => panic!("unexpected mem mode"),
}
}
}
impl ShowWithRRU for Cond {
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
let mut s = format!("{:?}", self);
s.make_ascii_lowercase();
s
}
}
impl ShowWithRRU for BranchTarget {
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
match self {
&BranchTarget::Label(label) => format!("label{:?}", label.get()),
&BranchTarget::ResolvedOffset(off) => format!("{}", off),
}
}
}

View File

@@ -0,0 +1,801 @@
//! 32-bit ARM ISA: binary code emission.
use crate::binemit::{Reloc, StackMap};
use crate::isa::arm32::inst::*;
use core::convert::TryFrom;
use log::debug;
/// Memory addressing mode finalization: convert "special" modes (e.g.,
/// nominal stack offset) into real addressing modes, possibly by
/// emitting some helper instructions that come immediately before the use
/// of this amode.
pub fn mem_finalize(mem: &AMode, state: &EmitState) -> (SmallVec<[Inst; 4]>, AMode) {
match mem {
&AMode::RegOffset(_, off)
| &AMode::SPOffset(off, _)
| &AMode::FPOffset(off, _)
| &AMode::NominalSPOffset(off, _) => {
let basereg = match mem {
&AMode::RegOffset(reg, _) => reg,
&AMode::SPOffset(..) | &AMode::NominalSPOffset(..) => sp_reg(),
&AMode::FPOffset(..) => fp_reg(),
_ => unreachable!(),
};
let adj = match mem {
&AMode::NominalSPOffset(..) => {
debug!(
"mem_finalize: nominal SP offset {} + adj {} -> {}",
off,
state.virtual_sp_offset,
off + state.virtual_sp_offset
);
state.virtual_sp_offset
}
_ => 0,
};
let off = off + adj;
assert!(-(1 << 31) <= off && off <= (1 << 32));
if let Some(off) = UImm12::maybe_from_i64(off) {
let mem = AMode::RegOffset12(basereg, off);
(smallvec![], mem)
} else {
let tmp = writable_ip_reg();
let const_insts = Inst::load_constant(tmp, off as u32);
let mem = AMode::reg_plus_reg(basereg, tmp.to_reg(), 0);
(const_insts, mem)
}
}
// Just assert immediate is valid here.
_ => (smallvec![], mem.clone()),
}
}
//=============================================================================
// Instructions and subcomponents: emission
fn machreg_to_gpr(m: Reg) -> u16 {
assert_eq!(m.get_class(), RegClass::I32);
u16::try_from(m.to_real_reg().get_hw_encoding()).unwrap()
}
fn machreg_to_gpr_lo(m: Reg) -> u16 {
let gpr_lo = machreg_to_gpr(m);
assert!(gpr_lo < 8);
gpr_lo
}
fn machreg_is_lo(m: Reg) -> bool {
machreg_to_gpr(m) < 8
}
fn enc_16_rr(bits_15_6: u16, rd: Reg, rm: Reg) -> u16 {
(bits_15_6 << 6) | machreg_to_gpr_lo(rd) | (machreg_to_gpr_lo(rm) << 3)
}
fn enc_16_rr_any(bits_15_8: u16, rd: Reg, rm: Reg) -> u16 {
let rd = machreg_to_gpr(rd);
(bits_15_8 << 8) | (rd & 0x7) | ((rd >> 3) << 7) | (machreg_to_gpr(rm) << 3)
}
fn enc_16_mov(rd: Writable<Reg>, rm: Reg) -> u16 {
enc_16_rr_any(0b01000110, rd.to_reg(), rm)
}
fn enc_16_it(cond: Cond, insts: &Vec<CondInst>) -> u16 {
let cond = cond.bits();
let mut mask: u16 = 0;
for inst in insts.iter().skip(1) {
if inst.then {
mask |= cond & 0x1;
} else {
mask |= (cond & 0x1) ^ 0x1;
}
mask <<= 1;
}
mask |= 0x1;
mask <<= 4 - insts.len();
0b1011_1111_0000_0000 | (cond << 4) | mask
}
fn enc_32_regs(
mut inst: u32,
reg_0: Option<Reg>,
reg_8: Option<Reg>,
reg_12: Option<Reg>,
reg_16: Option<Reg>,
) -> u32 {
if let Some(reg_0) = reg_0 {
inst |= u32::from(machreg_to_gpr(reg_0));
}
if let Some(reg_8) = reg_8 {
inst |= u32::from(machreg_to_gpr(reg_8)) << 8;
}
if let Some(reg_12) = reg_12 {
inst |= u32::from(machreg_to_gpr(reg_12)) << 12;
}
if let Some(reg_16) = reg_16 {
inst |= u32::from(machreg_to_gpr(reg_16)) << 16;
}
inst
}
fn enc_32_reg_shift(inst: u32, shift: &Option<ShiftOpAndAmt>) -> u32 {
match shift {
Some(shift) => {
let op = u32::from(shift.op().bits());
let amt = u32::from(shift.amt().value());
let imm2 = amt & 0x3;
let imm3 = (amt >> 2) & 0x7;
inst | (op << 4) | (imm2 << 6) | (imm3 << 12)
}
None => inst,
}
}
fn enc_32_r_imm16(bits_31_20: u32, rd: Reg, imm16: u16) -> u32 {
let imm16 = u32::from(imm16);
let imm8 = imm16 & 0xff;
let imm3 = (imm16 >> 8) & 0x7;
let i = (imm16 >> 11) & 0x1;
let imm4 = (imm16 >> 12) & 0xf;
let inst = ((bits_31_20 << 20) & !(1 << 26)) | imm8 | (imm3 << 12) | (imm4 << 16) | (i << 26);
enc_32_regs(inst, None, Some(rd), None, None)
}
fn enc_32_rrr(bits_31_20: u32, bits_15_12: u32, bits_7_4: u32, rd: Reg, rm: Reg, rn: Reg) -> u32 {
let inst = (bits_31_20 << 20) | (bits_15_12 << 12) | (bits_7_4 << 4);
enc_32_regs(inst, Some(rm), Some(rd), None, Some(rn))
}
fn enc_32_imm12(inst: u32, imm12: UImm12) -> u32 {
let imm12 = imm12.bits();
let imm8 = imm12 & 0xff;
let imm3 = (imm12 >> 8) & 0x7;
let i = (imm12 >> 11) & 0x1;
inst | imm8 | (imm3 << 12) | (i << 26)
}
fn enc_32_mem_r(bits_24_20: u32, rt: Reg, rn: Reg, rm: Reg, imm2: u8) -> u32 {
let imm2 = u32::from(imm2);
let inst = (imm2 << 4) | (bits_24_20 << 20) | (0b11111 << 27);
enc_32_regs(inst, Some(rm), None, Some(rt), Some(rn))
}
fn enc_32_mem_off12(bits_24_20: u32, rt: Reg, rn: Reg, off12: UImm12) -> u32 {
let off12 = off12.bits();
let inst = off12 | (bits_24_20 << 20) | (0b11111 << 27);
enc_32_regs(inst, None, None, Some(rt), Some(rn))
}
fn enc_32_jump(target: BranchTarget) -> u32 {
let off24 = target.as_off24();
let imm11 = off24 & 0x7ff;
let imm10 = (off24 >> 11) & 0x3ff;
let i2 = (off24 >> 21) & 0x1;
let i1 = (off24 >> 22) & 0x1;
let s = (off24 >> 23) & 0x1;
let j1 = (i1 ^ s) ^ 1;
let j2 = (i2 ^ s) ^ 1;
0b11110_0_0000000000_10_0_1_0_00000000000
| imm11
| (j2 << 11)
| (j1 << 13)
| (imm10 << 16)
| (s << 26)
}
fn enc_32_cond_branch(cond: Cond, target: BranchTarget) -> u32 {
let cond = u32::from(cond.bits());
let off20 = target.as_off20();
let imm11 = off20 & 0x7ff;
let imm6 = (off20 >> 11) & 0x3f;
let j1 = (off20 >> 17) & 0x1;
let j2 = (off20 >> 18) & 0x1;
let s = (off20 >> 19) & 0x1;
0b11110_0_0000_000000_10_0_0_0_00000000000
| imm11
| (j2 << 11)
| (j1 << 13)
| (imm6 << 16)
| (cond << 22)
| (s << 26)
}
fn u32_swap_halfwords(x: u32) -> u32 {
(x >> 16) | (x << 16)
}
fn emit_32(inst: u32, sink: &mut MachBuffer<Inst>) {
let inst_hi = (inst >> 16) as u16;
let inst_lo = (inst & 0xffff) as u16;
sink.put2(inst_hi);
sink.put2(inst_lo);
}
/// State carried between emissions of a sequence of instructions.
#[derive(Default, Clone, Debug)]
pub struct EmitState {
/// Addend to convert nominal-SP offsets to real-SP offsets at the current
/// program point.
pub(crate) virtual_sp_offset: i64,
/// Offset of FP from nominal-SP.
pub(crate) nominal_sp_to_fp: i64,
/// Safepoint stack map for upcoming instruction, as provided to `pre_safepoint()`.
stack_map: Option<StackMap>,
}
impl MachInstEmitState<Inst> for EmitState {
fn new(abi: &dyn ABICallee<I = Inst>) -> Self {
EmitState {
virtual_sp_offset: 0,
nominal_sp_to_fp: abi.frame_size() as i64,
stack_map: None,
}
}
fn pre_safepoint(&mut self, stack_map: StackMap) {
self.stack_map = Some(stack_map);
}
}
impl EmitState {
fn take_stack_map(&mut self) -> Option<StackMap> {
self.stack_map.take()
}
fn clear_post_insn(&mut self) {
self.stack_map = None;
}
}
impl MachInstEmit for Inst {
type State = EmitState;
fn emit(&self, sink: &mut MachBuffer<Inst>, flags: &settings::Flags, state: &mut EmitState) {
let start_off = sink.cur_offset();
match self {
&Inst::Nop0 | &Inst::EpiloguePlaceholder => {}
&Inst::Nop2 => {
sink.put2(0b1011_1111_0000_0000);
}
&Inst::AluRRR { alu_op, rd, rn, rm } => {
let (bits_31_20, bits_15_12, bits_7_4) = match alu_op {
ALUOp::Lsl => (0b111110100000, 0b1111, 0b0000),
ALUOp::Lsr => (0b111110100010, 0b1111, 0b0000),
ALUOp::Asr => (0b111110100100, 0b1111, 0b0000),
ALUOp::Ror => (0b111110100110, 0b1111, 0b0000),
ALUOp::Qadd => (0b111110101000, 0b1111, 0b1000),
ALUOp::Qsub => (0b111110101000, 0b1111, 0b1010),
ALUOp::Mul => (0b111110110000, 0b1111, 0b0000),
ALUOp::Udiv => (0b111110111011, 0b1111, 0b1111),
ALUOp::Sdiv => (0b111110111001, 0b1111, 0b1111),
_ => panic!("Invalid ALUOp {:?} in RRR form!", alu_op),
};
emit_32(
enc_32_rrr(bits_31_20, bits_15_12, bits_7_4, rd.to_reg(), rm, rn),
sink,
);
}
&Inst::AluRRRShift {
alu_op,
rd,
rn,
rm,
ref shift,
} => {
let bits_31_24 = 0b111_0101;
let bits_24_20 = match alu_op {
ALUOp::And => 0b00000,
ALUOp::Bic => 0b00010,
ALUOp::Orr => 0b00100,
ALUOp::Orn => 0b00110,
ALUOp::Eor => 0b01000,
ALUOp::Add => 0b10000,
ALUOp::Adds => 0b10001,
ALUOp::Adc => 0b10100,
ALUOp::Adcs => 0b10101,
ALUOp::Sbc => 0b10110,
ALUOp::Sbcs => 0b10111,
ALUOp::Sub => 0b11010,
ALUOp::Subs => 0b11011,
ALUOp::Rsb => 0b11100,
_ => panic!("Invalid ALUOp {:?} in RRRShift form!", alu_op),
};
let bits_31_20 = (bits_31_24 << 5) | bits_24_20;
let inst = enc_32_rrr(bits_31_20, 0, 0, rd.to_reg(), rm, rn);
let inst = enc_32_reg_shift(inst, shift);
emit_32(inst, sink);
}
&Inst::AluRRShift {
alu_op,
rd,
rm,
ref shift,
} => {
let bits_24_21 = match alu_op {
ALUOp1::Mvn => 0b0011,
ALUOp1::Mov => 0b0010,
};
let inst = 0b1110101_0000_0_1111_0_000_0000_00_00_0000 | (bits_24_21 << 21);
let inst = enc_32_regs(inst, Some(rm), Some(rd.to_reg()), None, None);
let inst = enc_32_reg_shift(inst, shift);
emit_32(inst, sink);
}
&Inst::AluRRRR {
alu_op,
rd_hi,
rd_lo,
rn,
rm,
} => {
let (bits_22_20, bits_7_4) = match alu_op {
ALUOp::Smull => (0b000, 0b0000),
ALUOp::Umull => (0b010, 0b0000),
_ => panic!("Invalid ALUOp {:?} in RRRR form!", alu_op),
};
let inst = (0b111110111 << 23) | (bits_22_20 << 20) | (bits_7_4 << 4);
let inst = enc_32_regs(
inst,
Some(rm),
Some(rd_hi.to_reg()),
Some(rd_lo.to_reg()),
Some(rn),
);
emit_32(inst, sink);
}
&Inst::AluRRImm12 {
alu_op,
rd,
rn,
imm12,
} => {
let bits_24_20 = match alu_op {
ALUOp::Add => 0b00000,
ALUOp::Sub => 0b01010,
_ => panic!("Invalid ALUOp {:?} in RRImm12 form!", alu_op),
};
let inst = (0b11110_0_1 << 25) | (bits_24_20 << 20);
let inst = enc_32_regs(inst, None, Some(rd.to_reg()), None, Some(rn));
let inst = enc_32_imm12(inst, imm12);
emit_32(inst, sink);
}
&Inst::AluRRImm8 {
alu_op,
rd,
rn,
imm8,
} => {
let bits_24_20 = match alu_op {
ALUOp::And => 0b00000,
ALUOp::Bic => 0b00010,
ALUOp::Orr => 0b00100,
ALUOp::Orn => 0b00110,
ALUOp::Eor => 0b01000,
ALUOp::Add => 0b10000,
ALUOp::Adds => 0b10001,
ALUOp::Adc => 0b10100,
ALUOp::Adcs => 0b10101,
ALUOp::Sbc => 0b10110,
ALUOp::Sbcs => 0b10111,
ALUOp::Sub => 0b11010,
ALUOp::Subs => 0b11011,
ALUOp::Rsb => 0b11100,
_ => panic!("Invalid ALUOp {:?} in RRImm8 form!", alu_op),
};
let imm8 = imm8.bits();
let inst = 0b11110_0_0_00000_0000_0_000_0000_00000000 | imm8 | (bits_24_20 << 20);
let inst = enc_32_regs(inst, None, Some(rd.to_reg()), None, Some(rn));
emit_32(inst, sink);
}
&Inst::AluRImm8 { alu_op, rd, imm8 } => {
let bits_24_20 = match alu_op {
ALUOp1::Mvn => 0b00110,
ALUOp1::Mov => 0b00100,
};
let imm8 = imm8.bits();
let inst = 0b11110_0_0_00000_1111_0_000_0000_00000000 | imm8 | (bits_24_20 << 20);
let inst = enc_32_regs(inst, None, Some(rd.to_reg()), None, None);
emit_32(inst, sink);
}
&Inst::BitOpRR { bit_op, rd, rm } => {
let (bits_22_20, bits_7_4) = match bit_op {
BitOp::Rbit => (0b001, 0b1010),
BitOp::Rev => (0b001, 0b1000),
BitOp::Clz => (0b011, 0b1000),
};
let inst =
0b111110101_000_0000_1111_0000_0000_0000 | (bits_22_20 << 20) | (bits_7_4 << 4);
let inst = enc_32_regs(inst, Some(rm), Some(rd.to_reg()), None, Some(rm));
emit_32(inst, sink);
}
&Inst::Mov { rd, rm } => {
sink.put2(enc_16_mov(rd, rm));
}
&Inst::MovImm16 { rd, imm16 } => {
emit_32(enc_32_r_imm16(0b11110_0_100100, rd.to_reg(), imm16), sink);
}
&Inst::Movt { rd, imm16 } => {
emit_32(enc_32_r_imm16(0b11110_0_101100, rd.to_reg(), imm16), sink);
}
&Inst::Cmp { rn, rm } => {
// Check which 16-bit encoding is allowed.
if machreg_is_lo(rn) && machreg_is_lo(rm) {
sink.put2(enc_16_rr(0b0100001010, rn, rm));
} else {
sink.put2(enc_16_rr_any(0b01000101, rn, rm));
}
}
&Inst::CmpImm8 { rn, imm8 } => {
let inst = 0b11110_0_011011_0000_0_000_1111_00000000 | u32::from(imm8);
let inst = enc_32_regs(inst, None, None, None, Some(rn));
emit_32(inst, sink);
}
&Inst::Store {
rt,
ref mem,
srcloc,
bits,
} => {
let (mem_insts, mem) = mem_finalize(mem, state);
for inst in mem_insts.into_iter() {
inst.emit(sink, flags, state);
}
if let Some(srcloc) = srcloc {
// Register the offset at which the store instruction starts.
sink.add_trap(srcloc, TrapCode::HeapOutOfBounds);
}
match mem {
AMode::RegReg(rn, rm, imm2) => {
let bits_24_20 = match bits {
32 => 0b00100,
16 => 0b00010,
8 => 0b00000,
_ => panic!("Unsupported store case {:?}", self),
};
emit_32(enc_32_mem_r(bits_24_20, rt, rn, rm, imm2), sink);
}
AMode::RegOffset12(rn, off12) => {
let bits_24_20 = match bits {
32 => 0b01100,
16 => 0b01010,
8 => 0b01000,
_ => panic!("Unsupported store case {:?}", self),
};
emit_32(enc_32_mem_off12(bits_24_20, rt, rn, off12), sink);
}
AMode::PCRel(_) => panic!("Unsupported store case {:?}", self),
_ => unreachable!(),
}
}
&Inst::Load {
rt,
ref mem,
srcloc,
bits,
sign_extend,
} => {
let (mem_insts, mem) = mem_finalize(mem, state);
for inst in mem_insts.into_iter() {
inst.emit(sink, flags, state);
}
if let Some(srcloc) = srcloc {
// Register the offset at which the load instruction starts.
sink.add_trap(srcloc, TrapCode::HeapOutOfBounds);
}
match mem {
AMode::RegReg(rn, rm, imm2) => {
let bits_24_20 = match (bits, sign_extend) {
(32, _) => 0b00101,
(16, true) => 0b10011,
(16, false) => 0b00011,
(8, true) => 0b10001,
(8, false) => 0b00001,
_ => panic!("Unsupported load case {:?}", self),
};
emit_32(enc_32_mem_r(bits_24_20, rt.to_reg(), rn, rm, imm2), sink);
}
AMode::RegOffset12(rn, off12) => {
let bits_24_20 = match (bits, sign_extend) {
(32, _) => 0b01101,
(16, true) => 0b11011,
(16, false) => 0b01011,
(8, true) => 0b11001,
(8, false) => 0b01001,
_ => panic!("Unsupported load case {:?}", self),
};
emit_32(enc_32_mem_off12(bits_24_20, rt.to_reg(), rn, off12), sink);
}
AMode::PCRel(off12) => {
let mut bits_24_20 = match (bits, sign_extend) {
(32, _) => 0b00101,
(16, true) => 0b10011,
(16, false) => 0b00011,
(8, true) => 0b10001,
(8, false) => 0b00001,
_ => panic!("Unsupported load case {:?}", self),
};
let (u, off12) = if off12 > 0 { (1, off12) } else { (0, -off12) };
let off12 = UImm12::maybe_from_i64(i64::from(off12)).unwrap();
bits_24_20 |= u << 3;
emit_32(
enc_32_mem_off12(bits_24_20, rt.to_reg(), pc_reg(), off12),
sink,
);
}
_ => unreachable!(),
}
}
&Inst::LoadAddr { rd, ref mem } => {
let (mem_insts, mem) = mem_finalize(mem, state);
for inst in mem_insts.into_iter() {
inst.emit(sink, flags, state);
}
let inst = match mem {
AMode::RegReg(reg1, reg2, shift) => {
let shift = u32::from(shift);
let shift_amt = ShiftOpShiftImm::maybe_from_shift(shift).unwrap();
let shift = ShiftOpAndAmt::new(ShiftOp::LSL, shift_amt);
Inst::AluRRRShift {
alu_op: ALUOp::Add,
rd,
rn: reg1,
rm: reg2,
shift: Some(shift),
}
}
AMode::RegOffset12(reg, imm12) => Inst::AluRRImm12 {
alu_op: ALUOp::Add,
rd,
rn: reg,
imm12,
},
AMode::PCRel(off12) => {
let (off12, alu_op) = if off12 > 0 {
(off12, ALUOp::Add)
} else {
(-off12, ALUOp::Sub)
};
let imm12 = UImm12::maybe_from_i64(i64::from(off12)).unwrap();
Inst::AluRRImm12 {
alu_op,
rd,
rn: pc_reg(),
imm12,
}
}
_ => unreachable!(),
};
inst.emit(sink, flags, state);
}
&Inst::Extend {
rd,
rm,
from_bits,
signed,
} if from_bits >= 8 => {
let rd = rd.to_reg();
if machreg_is_lo(rd) && machreg_is_lo(rm) {
let bits_15_9 = match (from_bits, signed) {
(16, true) => 0b1011001000,
(16, false) => 0b1011001010,
(8, true) => 0b1011001001,
(8, false) => 0b1011001011,
_ => panic!("Unsupported Extend case: {:?}", self),
};
sink.put2(enc_16_rr(bits_15_9, rd, rm));
} else {
let bits_22_20 = match (from_bits, signed) {
(16, true) => 0b000,
(16, false) => 0b001,
(8, true) => 0b100,
(8, false) => 0b101,
_ => panic!("Unsupported Extend case: {:?}", self),
};
let inst = 0b111110100_000_11111111_0000_1000_0000 | (bits_22_20 << 20);
let inst = enc_32_regs(inst, Some(rm), Some(rd), None, None);
emit_32(inst, sink);
}
}
&Inst::Extend {
rd,
rm,
from_bits,
signed,
} if from_bits == 1 => {
let inst = Inst::AluRRImm8 {
alu_op: ALUOp::And,
rd,
rn: rm,
imm8: UImm8::maybe_from_i64(1).unwrap(),
};
inst.emit(sink, flags, state);
if signed {
let inst = Inst::AluRRImm8 {
alu_op: ALUOp::Rsb,
rd,
rn: rd.to_reg(),
imm8: UImm8::maybe_from_i64(1).unwrap(),
};
inst.emit(sink, flags, state);
}
}
&Inst::Extend { .. } => {
panic!("Unsupported extend variant");
}
&Inst::It { cond, ref insts } => {
assert!(1 <= insts.len() && insts.len() <= 4);
assert!(insts[0].then);
sink.put2(enc_16_it(cond, insts));
for inst in insts.iter() {
inst.inst.emit(sink, flags, state);
}
}
&Inst::Push { ref reg_list } => match reg_list.len() {
0 => panic!("Unsupported Push case: {:?}", self),
1 => {
let reg = u32::from(machreg_to_gpr(reg_list[0]));
let inst: u32 = 0b1111100001001101_0000_110100000100 | (reg << 12);
emit_32(inst, sink);
}
_ => {
let mut inst: u32 = 0b1110100100101101 << 16;
for reg in reg_list {
inst |= 1 << machreg_to_gpr(*reg);
}
if inst & ((1 << 13) | (1 << 15)) != 0 {
panic!("Unsupported Push case: {:?}", self);
}
emit_32(inst, sink);
}
},
&Inst::Pop { ref reg_list } => match reg_list.len() {
0 => panic!("Unsupported Pop case: {:?}", self),
1 => {
let reg = u32::from(machreg_to_gpr(reg_list[0].to_reg()));
let inst: u32 = 0b1111100001011101_0000_101100000100 | (reg << 12);
emit_32(inst, sink);
}
_ => {
let mut inst: u32 = 0b1110100010111101 << 16;
for reg in reg_list {
inst |= 1 << machreg_to_gpr(reg.to_reg());
}
if (inst & (1 << 14) != 0) && (inst & (1 << 15) != 0) {
panic!("Unsupported Pop case: {:?}", self);
}
emit_32(inst, sink);
}
},
&Inst::Call { ref info } => {
sink.add_reloc(info.loc, Reloc::Arm32Call, &info.dest, 0);
emit_32(0b11110_0_0000000000_11_0_1_0_00000000000, sink);
if info.opcode.is_call() {
sink.add_call_site(info.loc, info.opcode);
}
}
&Inst::CallInd { ref info } => {
sink.put2(0b01000111_1_0000_000 | (machreg_to_gpr(info.rm) << 3));
if info.opcode.is_call() {
sink.add_call_site(info.loc, info.opcode);
}
}
&Inst::LoadExtName {
rt,
ref name,
offset,
srcloc,
} => {
// maybe nop2 (0|2) bytes (pc is now 4-aligned)
// ldr rt, [pc, #4] 4 bytes
// b continue 4 bytes
// addr 4 bytes
// continue:
//
if start_off & 0x3 != 0 {
Inst::Nop2.emit(sink, flags, state);
}
assert_eq!(sink.cur_offset() & 0x3, 0);
let mem = AMode::PCRel(4);
let inst = Inst::Load {
rt,
mem,
srcloc: Some(srcloc),
bits: 32,
sign_extend: false,
};
inst.emit(sink, flags, state);
let inst = Inst::Jump {
dest: BranchTarget::ResolvedOffset(4),
};
inst.emit(sink, flags, state);
sink.add_reloc(srcloc, Reloc::Abs4, name, offset.into());
sink.put4(0);
}
&Inst::Ret => {
sink.put2(0b010001110_1110_000); // bx lr
}
&Inst::Jump { dest } => {
let off = sink.cur_offset();
// Indicate that the jump uses a label, if so, so that a fixup can occur later.
if let Some(l) = dest.as_label() {
sink.use_label_at_offset(off, l, LabelUse::Branch24);
sink.add_uncond_branch(off, off + 4, l);
}
emit_32(enc_32_jump(dest), sink);
}
&Inst::CondBr {
taken,
not_taken,
cond,
} => {
// Conditional part first.
let cond_off = sink.cur_offset();
if let Some(l) = taken.as_label() {
let label_use = LabelUse::Branch20;
sink.use_label_at_offset(cond_off, l, label_use);
let inverted = enc_32_cond_branch(cond.invert(), taken);
let inverted = u32_swap_halfwords(inverted).to_le_bytes();
sink.add_cond_branch(cond_off, cond_off + 4, l, &inverted[..]);
}
emit_32(enc_32_cond_branch(cond, taken), sink);
// Unconditional part.
let uncond_off = sink.cur_offset();
if let Some(l) = not_taken.as_label() {
sink.use_label_at_offset(uncond_off, l, LabelUse::Branch24);
sink.add_uncond_branch(uncond_off, uncond_off + 4, l);
}
emit_32(enc_32_jump(not_taken), sink);
}
&Inst::IndirectBr { rm, .. } => {
let inst = 0b010001110_0000_000 | (machreg_to_gpr(rm) << 3);
sink.put2(inst);
}
&Inst::Udf { trap_info } => {
let (srcloc, code) = trap_info;
sink.add_trap(srcloc, code);
sink.put2(0b11011110_00000000);
}
&Inst::Bkpt => {
sink.put2(0b10111110_00000000);
}
&Inst::TrapIf { cond, trap_info } => {
let cond = cond.invert();
let dest = BranchTarget::ResolvedOffset(2);
emit_32(enc_32_cond_branch(cond, dest), sink);
let trap = Inst::Udf { trap_info };
trap.emit(sink, flags, state);
}
&Inst::VirtualSPOffsetAdj { offset } => {
debug!(
"virtual sp offset adjusted by {} -> {}",
offset,
state.virtual_sp_offset + offset,
);
state.virtual_sp_offset += offset;
}
}
let end_off = sink.cur_offset();
debug_assert!((end_off - start_off) <= Inst::worst_case_size());
}
fn pretty_print(&self, mb_rru: Option<&RealRegUniverse>, state: &mut EmitState) -> String {
self.print_with_state(mb_rru, state)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,128 @@
//! 32-bit ARM ISA definitions: registers.
use regalloc::{RealRegUniverse, Reg, RegClass, RegClassInfo, Writable, NUM_REG_CLASSES};
use std::string::ToString;
/// Get a reference to a GPR.
pub fn rreg(num: u8) -> Reg {
assert!(num < 16);
Reg::new_real(RegClass::I32, num, num)
}
/// Get a writable reference to a GPR.
pub fn writable_rreg(num: u8) -> Writable<Reg> {
Writable::from_reg(rreg(num))
}
/// Get a reference to the program counter (r15).
pub fn pc_reg() -> Reg {
rreg(15)
}
/// Get a writable reference to the program counter.
pub fn writable_pc_reg() -> Writable<Reg> {
Writable::from_reg(pc_reg())
}
/// Get a reference to the link register (r14).
pub fn lr_reg() -> Reg {
rreg(14)
}
/// Get a writable reference to the link register.
pub fn writable_lr_reg() -> Writable<Reg> {
Writable::from_reg(lr_reg())
}
/// Get a reference to the stack pointer (r13).
pub fn sp_reg() -> Reg {
rreg(13)
}
/// Get a writable reference to the stack pointer.
pub fn writable_sp_reg() -> Writable<Reg> {
Writable::from_reg(sp_reg())
}
/// Get a reference to the intra-procedure-call scratch register (r12),
/// which is used as a temporary register.
pub fn ip_reg() -> Reg {
rreg(12)
}
/// Get a writable reference to the Intra-Procedure-call scratch register.
pub fn writable_ip_reg() -> Writable<Reg> {
Writable::from_reg(ip_reg())
}
/// Get a reference to the frame pointer register (r11).
pub fn fp_reg() -> Reg {
rreg(11)
}
/// Get a writable reference to the frame-pointer register.
pub fn writable_fp_reg() -> Writable<Reg> {
Writable::from_reg(fp_reg())
}
/// Get a reference to the second temp register. We need this in some edge cases
/// where we need both the ip and another temporary.
///
/// We use r10 for this role.
pub fn tmp2_reg() -> Reg {
rreg(10)
}
/// Get a writable reference to the tmp2 reg.
pub fn writable_tmp2_reg() -> Writable<Reg> {
Writable::from_reg(tmp2_reg())
}
/// Create the register universe.
/// Use only GPR for now.
pub fn create_reg_universe() -> RealRegUniverse {
let mut regs = vec![];
let mut allocable_by_class = [None; NUM_REG_CLASSES];
let r_reg_base = 0u8;
let r_reg_count = 10; // to exclude r10, fp, ip, sp, lr and pc.
for i in 0..r_reg_count {
let reg = Reg::new_real(
RegClass::I32,
/* enc = */ i,
/* index = */ r_reg_base + i,
)
.to_real_reg();
let name = format!("r{}", i);
regs.push((reg, name));
}
let r_reg_last = r_reg_base + r_reg_count - 1;
allocable_by_class[RegClass::I32.rc_to_usize()] = Some(RegClassInfo {
first: r_reg_base as usize,
last: r_reg_last as usize,
suggested_scratch: None,
});
// Other regs, not available to the allocator.
let allocable = regs.len();
regs.push((tmp2_reg().to_real_reg(), "r10".to_string()));
regs.push((fp_reg().to_real_reg(), "fp".to_string()));
regs.push((ip_reg().to_real_reg(), "ip".to_string()));
regs.push((sp_reg().to_real_reg(), "sp".to_string()));
regs.push((lr_reg().to_real_reg(), "lr".to_string()));
regs.push((pc_reg().to_real_reg(), "pc".to_string()));
// The indices in the register structs must match their
// actual indices in the array.
for (i, reg) in regs.iter().enumerate() {
assert_eq!(i, reg.0.get_index());
}
RealRegUniverse {
regs,
allocable,
allocable_by_class,
}
}

View File

@@ -0,0 +1,240 @@
//! Lowering rules for 32-bit ARM.
use crate::ir::condcodes::IntCC;
use crate::ir::types::*;
use crate::ir::Inst as IRInst;
use crate::ir::{InstructionData, Opcode, TrapCode};
use crate::machinst::lower::*;
use crate::machinst::*;
use crate::CodegenResult;
use crate::isa::arm32::inst::*;
use crate::isa::arm32::Arm32Backend;
use super::lower_inst;
use regalloc::{Reg, RegClass, Writable};
//============================================================================
// Lowering: convert instruction outputs to result types.
/// Lower an instruction output to a 32-bit constant, if possible.
pub(crate) fn output_to_const<C: LowerCtx<I = Inst>>(ctx: &mut C, out: InsnOutput) -> Option<u64> {
if out.output > 0 {
None
} else {
let inst_data = ctx.data(out.insn);
if inst_data.opcode() == Opcode::Null {
Some(0)
} else {
match inst_data {
&InstructionData::UnaryImm { opcode: _, imm } => {
// Only has Into for i64; we use u64 elsewhere, so we cast.
let imm: i64 = imm.into();
Some(imm as u64)
}
&InstructionData::UnaryBool { opcode: _, imm } => Some(u64::from(imm)),
&InstructionData::UnaryIeee32 { .. } | &InstructionData::UnaryIeee64 { .. } => {
unimplemented!()
}
_ => None,
}
}
}
}
/// How to handle narrow values loaded into registers.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum NarrowValueMode {
None,
/// Zero-extend to 32 bits if original is < 32 bits.
ZeroExtend,
/// Sign-extend to 32 bits if original is < 32 bits.
SignExtend,
}
/// Lower an instruction output to a reg.
pub(crate) fn output_to_reg<C: LowerCtx<I = Inst>>(ctx: &mut C, out: InsnOutput) -> Writable<Reg> {
ctx.get_output(out.insn, out.output)
}
/// Lower an instruction input to a reg.
///
/// The given register will be extended appropriately, according to `narrow_mode`.
pub(crate) fn input_to_reg<C: LowerCtx<I = Inst>>(
ctx: &mut C,
input: InsnInput,
narrow_mode: NarrowValueMode,
) -> Reg {
let ty = ctx.input_ty(input.insn, input.input);
let from_bits = ty.bits() as u8;
let inputs = ctx.get_input(input.insn, input.input);
let in_reg = if let Some(c) = inputs.constant {
let to_reg = ctx.alloc_tmp(Inst::rc_for_type(ty).unwrap(), ty);
for inst in Inst::gen_constant(to_reg, c, ty, |reg_class, ty| ctx.alloc_tmp(reg_class, ty))
.into_iter()
{
ctx.emit(inst);
}
to_reg.to_reg()
} else {
ctx.use_input_reg(inputs);
inputs.reg
};
match (narrow_mode, from_bits) {
(NarrowValueMode::None, _) => in_reg,
(NarrowValueMode::ZeroExtend, 1) => {
let tmp = ctx.alloc_tmp(RegClass::I32, I32);
ctx.emit(Inst::AluRRImm8 {
alu_op: ALUOp::And,
rd: tmp,
rn: in_reg,
imm8: UImm8::maybe_from_i64(0x1).unwrap(),
});
tmp.to_reg()
}
(NarrowValueMode::ZeroExtend, n) if n < 32 => {
let tmp = ctx.alloc_tmp(RegClass::I32, I32);
ctx.emit(Inst::Extend {
rd: tmp,
rm: in_reg,
signed: false,
from_bits: n,
});
tmp.to_reg()
}
(NarrowValueMode::SignExtend, n) if n < 32 => {
let tmp = ctx.alloc_tmp(RegClass::I32, I32);
ctx.emit(Inst::Extend {
rd: tmp,
rm: in_reg,
signed: true,
from_bits: n,
});
tmp.to_reg()
}
(NarrowValueMode::ZeroExtend, 32) | (NarrowValueMode::SignExtend, 32) => in_reg,
_ => panic!(
"Unsupported input width: input ty {} bits {} mode {:?}",
ty, from_bits, narrow_mode
),
}
}
pub(crate) fn lower_constant<C: LowerCtx<I = Inst>>(ctx: &mut C, rd: Writable<Reg>, value: u64) {
// We allow sign bits for high word.
assert!((value >> 32) == 0x0 || (value >> 32) == (1 << 32) - 1);
for inst in Inst::load_constant(rd, (value & ((1 << 32) - 1)) as u32) {
ctx.emit(inst);
}
}
pub(crate) fn emit_cmp<C: LowerCtx<I = Inst>>(ctx: &mut C, insn: IRInst) {
let inputs = [InsnInput { insn, input: 0 }, InsnInput { insn, input: 1 }];
let rn = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
let rm = input_to_reg(ctx, inputs[1], NarrowValueMode::None);
ctx.emit(Inst::Cmp { rn, rm });
}
pub(crate) fn lower_condcode(cc: IntCC) -> Cond {
match cc {
IntCC::Equal => Cond::Eq,
IntCC::NotEqual => Cond::Ne,
IntCC::SignedGreaterThanOrEqual => Cond::Ge,
IntCC::SignedGreaterThan => Cond::Gt,
IntCC::SignedLessThanOrEqual => Cond::Le,
IntCC::SignedLessThan => Cond::Lt,
IntCC::UnsignedGreaterThanOrEqual => Cond::Hs,
IntCC::UnsignedGreaterThan => Cond::Hi,
IntCC::UnsignedLessThanOrEqual => Cond::Ls,
IntCC::UnsignedLessThan => Cond::Lo,
IntCC::Overflow => Cond::Vs,
IntCC::NotOverflow => Cond::Vc,
}
}
/// Determines whether this condcode interprets inputs as signed or unsigned.
pub(crate) fn condcode_is_signed(cc: IntCC) -> bool {
match cc {
IntCC::Equal => false,
IntCC::NotEqual => false,
IntCC::SignedGreaterThanOrEqual => true,
IntCC::SignedGreaterThan => true,
IntCC::SignedLessThanOrEqual => true,
IntCC::SignedLessThan => true,
IntCC::UnsignedGreaterThanOrEqual => false,
IntCC::UnsignedGreaterThan => false,
IntCC::UnsignedLessThanOrEqual => false,
IntCC::UnsignedLessThan => false,
IntCC::Overflow => true,
IntCC::NotOverflow => true,
}
}
//=============================================================================
// Helpers for instruction lowering.
pub(crate) fn ldst_offset(data: &InstructionData) -> Option<i32> {
match data {
&InstructionData::Load { offset, .. }
| &InstructionData::StackLoad { offset, .. }
| &InstructionData::LoadComplex { offset, .. }
| &InstructionData::Store { offset, .. }
| &InstructionData::StackStore { offset, .. }
| &InstructionData::StoreComplex { offset, .. } => Some(offset.into()),
_ => None,
}
}
pub(crate) fn inst_condcode(data: &InstructionData) -> Option<IntCC> {
match data {
&InstructionData::IntCond { cond, .. }
| &InstructionData::BranchIcmp { cond, .. }
| &InstructionData::IntCompare { cond, .. }
| &InstructionData::IntCondTrap { cond, .. }
| &InstructionData::BranchInt { cond, .. }
| &InstructionData::IntSelect { cond, .. }
| &InstructionData::IntCompareImm { cond, .. } => Some(cond),
_ => None,
}
}
pub(crate) fn inst_trapcode(data: &InstructionData) -> Option<TrapCode> {
match data {
&InstructionData::Trap { code, .. }
| &InstructionData::CondTrap { code, .. }
| &InstructionData::IntCondTrap { code, .. } => Some(code),
&InstructionData::FloatCondTrap { code, .. } => {
panic!("Unexpected float cond trap {:?}", code)
}
_ => None,
}
}
//=============================================================================
// Lowering-backend trait implementation.
impl LowerBackend for Arm32Backend {
type MInst = Inst;
fn lower<C: LowerCtx<I = Inst>>(&self, ctx: &mut C, ir_inst: IRInst) -> CodegenResult<()> {
lower_inst::lower_insn_to_regs(ctx, ir_inst)
}
fn lower_branch_group<C: LowerCtx<I = Inst>>(
&self,
ctx: &mut C,
branches: &[IRInst],
targets: &[MachLabel],
fallthrough: Option<MachLabel>,
) -> CodegenResult<()> {
lower_inst::lower_branch(ctx, branches, targets, fallthrough)
}
fn maybe_pinned_reg(&self) -> Option<Reg> {
None
}
}

View File

@@ -0,0 +1,623 @@
//! Lower a single Cranelift instruction into vcode.
use crate::ir::types::*;
use crate::ir::Inst as IRInst;
use crate::ir::Opcode;
use crate::machinst::lower::*;
use crate::machinst::*;
use crate::CodegenResult;
use crate::isa::arm32::abi::*;
use crate::isa::arm32::inst::*;
use regalloc::RegClass;
use smallvec::SmallVec;
use super::lower::*;
/// Actually codegen an instruction's results into registers.
pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
ctx: &mut C,
insn: IRInst,
) -> CodegenResult<()> {
let op = ctx.data(insn).opcode();
let inputs: SmallVec<[InsnInput; 4]> = (0..ctx.num_inputs(insn))
.map(|i| InsnInput { insn, input: i })
.collect();
let outputs: SmallVec<[InsnOutput; 2]> = (0..ctx.num_outputs(insn))
.map(|i| InsnOutput { insn, output: i })
.collect();
let ty = if outputs.len() > 0 {
let ty = ctx.output_ty(insn, 0);
if ty.bits() > 32 || ty.is_float() {
panic!("Cannot lower inst with type {}!", ty);
}
Some(ty)
} else {
None
};
match op {
Opcode::Iconst | Opcode::Bconst | Opcode::Null => {
let value = output_to_const(ctx, outputs[0]).unwrap();
let rd = output_to_reg(ctx, outputs[0]);
lower_constant(ctx, rd, value);
}
Opcode::Iadd
| Opcode::IaddIfcin
| Opcode::IaddIfcout
| Opcode::IaddIfcarry
| Opcode::Isub
| Opcode::IsubIfbin
| Opcode::IsubIfbout
| Opcode::IsubIfborrow
| Opcode::Band
| Opcode::Bor
| Opcode::Bxor
| Opcode::BandNot
| Opcode::BorNot => {
let rd = output_to_reg(ctx, outputs[0]);
let rn = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
let rm = input_to_reg(ctx, inputs[1], NarrowValueMode::None);
let alu_op = match op {
Opcode::Iadd => ALUOp::Add,
Opcode::IaddIfcin => ALUOp::Adc,
Opcode::IaddIfcout => ALUOp::Adds,
Opcode::IaddIfcarry => ALUOp::Adcs,
Opcode::Isub => ALUOp::Sub,
Opcode::IsubIfbin => ALUOp::Sbc,
Opcode::IsubIfbout => ALUOp::Subs,
Opcode::IsubIfborrow => ALUOp::Sbcs,
Opcode::Band => ALUOp::And,
Opcode::Bor => ALUOp::Orr,
Opcode::Bxor => ALUOp::Eor,
Opcode::BandNot => ALUOp::Bic,
Opcode::BorNot => ALUOp::Orn,
_ => unreachable!(),
};
ctx.emit(Inst::AluRRRShift {
alu_op,
rd,
rn,
rm,
shift: None,
});
}
Opcode::SaddSat | Opcode::SsubSat | Opcode::Imul | Opcode::Udiv | Opcode::Sdiv => {
let rd = output_to_reg(ctx, outputs[0]);
let rn = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
let rm = input_to_reg(ctx, inputs[1], NarrowValueMode::None);
let alu_op = match op {
Opcode::SaddSat => ALUOp::Qadd,
Opcode::SsubSat => ALUOp::Qsub,
Opcode::Imul => ALUOp::Mul,
Opcode::Udiv => ALUOp::Udiv,
Opcode::Sdiv => ALUOp::Sdiv,
_ => unreachable!(),
};
ctx.emit(Inst::AluRRR { alu_op, rd, rn, rm });
}
Opcode::Ineg => {
let rd = output_to_reg(ctx, outputs[0]);
let rn = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
ctx.emit(Inst::AluRRImm8 {
alu_op: ALUOp::Rsb,
rd,
rn,
imm8: UImm8::maybe_from_i64(0).unwrap(),
});
}
Opcode::Ishl | Opcode::Ushr | Opcode::Sshr => {
let (alu_op, ext) = match op {
Opcode::Ishl => (ALUOp::Lsl, NarrowValueMode::None),
Opcode::Ushr => (ALUOp::Lsr, NarrowValueMode::ZeroExtend),
Opcode::Sshr => (ALUOp::Asr, NarrowValueMode::SignExtend),
_ => unreachable!(),
};
let rd = output_to_reg(ctx, outputs[0]);
let rn = input_to_reg(ctx, inputs[0], ext);
let rm = input_to_reg(ctx, inputs[1], NarrowValueMode::ZeroExtend);
ctx.emit(Inst::AluRRR { alu_op, rd, rn, rm });
}
Opcode::Rotr => {
if ty.unwrap().bits() != 32 {
unimplemented!()
}
let rd = output_to_reg(ctx, outputs[0]);
let rn = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
let rm = input_to_reg(ctx, inputs[1], NarrowValueMode::None);
ctx.emit(Inst::AluRRR {
alu_op: ALUOp::Ror,
rd,
rn,
rm,
});
}
Opcode::Rotl => {
if ty.unwrap().bits() != 32 {
unimplemented!()
}
let rd = output_to_reg(ctx, outputs[0]);
let rn = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
let rm = input_to_reg(ctx, inputs[1], NarrowValueMode::None);
let tmp = ctx.alloc_tmp(RegClass::I32, I32);
// ror rd, rn, 32 - (rm & 31)
ctx.emit(Inst::AluRRImm8 {
alu_op: ALUOp::And,
rd: tmp,
rn: rm,
imm8: UImm8::maybe_from_i64(31).unwrap(),
});
ctx.emit(Inst::AluRRImm8 {
alu_op: ALUOp::Rsb,
rd: tmp,
rn: tmp.to_reg(),
imm8: UImm8::maybe_from_i64(32).unwrap(),
});
ctx.emit(Inst::AluRRR {
alu_op: ALUOp::Ror,
rd,
rn,
rm: tmp.to_reg(),
});
}
Opcode::Smulhi | Opcode::Umulhi => {
let ty = ty.unwrap();
let is_signed = op == Opcode::Smulhi;
match ty {
I32 => {
let rd_hi = output_to_reg(ctx, outputs[0]);
let rd_lo = ctx.alloc_tmp(RegClass::I32, ty);
let rn = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
let rm = input_to_reg(ctx, inputs[1], NarrowValueMode::None);
let alu_op = if is_signed {
ALUOp::Smull
} else {
ALUOp::Umull
};
ctx.emit(Inst::AluRRRR {
alu_op,
rd_hi,
rd_lo,
rn,
rm,
});
}
I16 | I8 => {
let narrow_mode = if is_signed {
NarrowValueMode::SignExtend
} else {
NarrowValueMode::ZeroExtend
};
let rd = output_to_reg(ctx, outputs[0]);
let rn = input_to_reg(ctx, inputs[0], narrow_mode);
let rm = input_to_reg(ctx, inputs[1], narrow_mode);
ctx.emit(Inst::AluRRR {
alu_op: ALUOp::Mul,
rd,
rn,
rm,
});
let shift_amt = if ty == I16 { 16 } else { 8 };
let imm8 = UImm8::maybe_from_i64(shift_amt).unwrap();
let alu_op = if is_signed { ALUOp::Asr } else { ALUOp::Lsr };
ctx.emit(Inst::AluRRImm8 {
alu_op,
rd,
rn: rd.to_reg(),
imm8,
});
}
_ => panic!("Unexpected type {} in lower {}!", ty, op),
}
}
Opcode::Bnot => {
let rd = output_to_reg(ctx, outputs[0]);
let rm = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
ctx.emit(Inst::AluRRShift {
alu_op: ALUOp1::Mvn,
rd,
rm,
shift: None,
});
}
Opcode::Clz | Opcode::Ctz => {
let rd = output_to_reg(ctx, outputs[0]);
let rm = input_to_reg(ctx, inputs[0], NarrowValueMode::ZeroExtend);
let ty = ctx.output_ty(insn, 0);
let in_reg = if op == Opcode::Ctz {
ctx.emit(Inst::BitOpRR {
bit_op: BitOp::Rbit,
rd,
rm,
});
rd.to_reg()
} else {
rm
};
ctx.emit(Inst::BitOpRR {
bit_op: BitOp::Clz,
rd,
rm: in_reg,
});
if ty.bits() < 32 {
let imm12 = UImm12::maybe_from_i64(32 - ty.bits() as i64).unwrap();
ctx.emit(Inst::AluRRImm12 {
alu_op: ALUOp::Sub,
rd,
rn: rd.to_reg(),
imm12,
});
}
}
Opcode::Bitrev => {
let rd = output_to_reg(ctx, outputs[0]);
let rm = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
let ty = ctx.output_ty(insn, 0);
let bit_op = BitOp::Rbit;
match ty.bits() {
32 => ctx.emit(Inst::BitOpRR { bit_op, rd, rm }),
n if n < 32 => {
let shift = ShiftOpAndAmt::new(
ShiftOp::LSL,
ShiftOpShiftImm::maybe_from_shift(32 - n as u32).unwrap(),
);
ctx.emit(Inst::AluRRShift {
alu_op: ALUOp1::Mov,
rd,
rm,
shift: Some(shift),
});
ctx.emit(Inst::BitOpRR {
bit_op,
rd,
rm: rd.to_reg(),
});
}
_ => panic!("Unexpected output type {}", ty),
}
}
Opcode::Icmp | Opcode::Ifcmp => {
let condcode = inst_condcode(ctx.data(insn)).unwrap();
let cond = lower_condcode(condcode);
let is_signed = condcode_is_signed(condcode);
let narrow_mode = if is_signed {
NarrowValueMode::SignExtend
} else {
NarrowValueMode::ZeroExtend
};
let rd = output_to_reg(ctx, outputs[0]);
let rn = input_to_reg(ctx, inputs[0], narrow_mode);
let rm = input_to_reg(ctx, inputs[1], narrow_mode);
ctx.emit(Inst::Cmp { rn, rm });
if op == Opcode::Icmp {
let mut it_insts = vec![];
it_insts.push(CondInst::new(Inst::MovImm16 { rd, imm16: 1 }, true));
it_insts.push(CondInst::new(Inst::MovImm16 { rd, imm16: 0 }, false));
ctx.emit(Inst::It {
cond,
insts: it_insts,
});
}
}
Opcode::Trueif => {
let cmp_insn = ctx
.get_input(inputs[0].insn, inputs[0].input)
.inst
.unwrap()
.0;
debug_assert_eq!(ctx.data(cmp_insn).opcode(), Opcode::Ifcmp);
emit_cmp(ctx, cmp_insn);
let condcode = inst_condcode(ctx.data(insn)).unwrap();
let cond = lower_condcode(condcode);
let rd = output_to_reg(ctx, outputs[0]);
let mut it_insts = vec![];
it_insts.push(CondInst::new(Inst::MovImm16 { rd, imm16: 1 }, true));
it_insts.push(CondInst::new(Inst::MovImm16 { rd, imm16: 0 }, false));
ctx.emit(Inst::It {
cond,
insts: it_insts,
});
}
Opcode::Select | Opcode::Selectif => {
let cond = if op == Opcode::Select {
let rn = input_to_reg(ctx, inputs[0], NarrowValueMode::ZeroExtend);
ctx.emit(Inst::CmpImm8 { rn, imm8: 0 });
Cond::Ne
} else {
// Verification ensures that the input is always a single-def ifcmp.
let cmp_insn = ctx
.get_input(inputs[0].insn, inputs[0].input)
.inst
.unwrap()
.0;
debug_assert_eq!(ctx.data(cmp_insn).opcode(), Opcode::Ifcmp);
emit_cmp(ctx, cmp_insn);
let condcode = inst_condcode(ctx.data(insn)).unwrap();
lower_condcode(condcode)
};
let r1 = input_to_reg(ctx, inputs[1], NarrowValueMode::None);
let r2 = input_to_reg(ctx, inputs[2], NarrowValueMode::None);
let out_reg = output_to_reg(ctx, outputs[0]);
let mut it_insts = vec![];
it_insts.push(CondInst::new(Inst::mov(out_reg, r1), true));
it_insts.push(CondInst::new(Inst::mov(out_reg, r2), false));
ctx.emit(Inst::It {
cond,
insts: it_insts,
});
}
Opcode::Store | Opcode::Istore8 | Opcode::Istore16 | Opcode::Istore32 => {
let off = ldst_offset(ctx.data(insn)).unwrap();
let elem_ty = match op {
Opcode::Istore8 => I8,
Opcode::Istore16 => I16,
Opcode::Istore32 => I32,
Opcode::Store => ctx.input_ty(insn, 0),
_ => unreachable!(),
};
if elem_ty.bits() > 32 {
unimplemented!()
}
let bits = elem_ty.bits() as u8;
assert_eq!(inputs.len(), 2, "only one input for store memory operands");
let rt = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
let base = input_to_reg(ctx, inputs[1], NarrowValueMode::None);
let mem = AMode::RegOffset(base, i64::from(off));
let memflags = ctx.memflags(insn).expect("memory flags");
let srcloc = if !memflags.notrap() {
Some(ctx.srcloc(insn))
} else {
None
};
ctx.emit(Inst::Store {
rt,
mem,
srcloc,
bits,
});
}
Opcode::Load
| Opcode::Uload8
| Opcode::Sload8
| Opcode::Uload16
| Opcode::Sload16
| Opcode::Uload32
| Opcode::Sload32 => {
let off = ldst_offset(ctx.data(insn)).unwrap();
let elem_ty = match op {
Opcode::Sload8 | Opcode::Uload8 => I8,
Opcode::Sload16 | Opcode::Uload16 => I16,
Opcode::Sload32 | Opcode::Uload32 => I32,
Opcode::Load => ctx.output_ty(insn, 0),
_ => unreachable!(),
};
if elem_ty.bits() > 32 {
unimplemented!()
}
let bits = elem_ty.bits() as u8;
let sign_extend = match op {
Opcode::Sload8 | Opcode::Sload16 | Opcode::Sload32 => true,
_ => false,
};
let out_reg = output_to_reg(ctx, outputs[0]);
assert_eq!(inputs.len(), 2, "only one input for store memory operands");
let base = input_to_reg(ctx, inputs[1], NarrowValueMode::None);
let mem = AMode::RegOffset(base, i64::from(off));
let memflags = ctx.memflags(insn).expect("memory flags");
let srcloc = if !memflags.notrap() {
Some(ctx.srcloc(insn))
} else {
None
};
ctx.emit(Inst::Load {
rt: out_reg,
mem,
srcloc,
bits,
sign_extend,
});
}
Opcode::Uextend | Opcode::Sextend => {
let output_ty = ty.unwrap();
let input_ty = ctx.input_ty(insn, 0);
let from_bits = input_ty.bits() as u8;
let to_bits = 32;
let signed = op == Opcode::Sextend;
let rm = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
let rd = output_to_reg(ctx, outputs[0]);
if output_ty.bits() > 32 {
panic!("Unexpected output type {}", output_ty);
}
if from_bits < to_bits {
ctx.emit(Inst::Extend {
rd,
rm,
from_bits,
signed,
});
}
}
Opcode::Bint | Opcode::Breduce | Opcode::Bextend | Opcode::Ireduce => {
let rn = input_to_reg(ctx, inputs[0], NarrowValueMode::ZeroExtend);
let rd = output_to_reg(ctx, outputs[0]);
let ty = ctx.input_ty(insn, 0);
ctx.emit(Inst::gen_move(rd, rn, ty));
}
Opcode::Copy => {
let rd = output_to_reg(ctx, outputs[0]);
let rn = input_to_reg(ctx, inputs[0], NarrowValueMode::None);
let ty = ctx.input_ty(insn, 0);
ctx.emit(Inst::gen_move(rd, rn, ty));
}
Opcode::Debugtrap => {
ctx.emit(Inst::Bkpt);
}
Opcode::Trap => {
let trap_info = (ctx.srcloc(insn), inst_trapcode(ctx.data(insn)).unwrap());
ctx.emit(Inst::Udf { trap_info })
}
Opcode::Trapif => {
let cmp_insn = ctx
.get_input(inputs[0].insn, inputs[0].input)
.inst
.unwrap()
.0;
debug_assert_eq!(ctx.data(cmp_insn).opcode(), Opcode::Ifcmp);
emit_cmp(ctx, cmp_insn);
let trap_info = (ctx.srcloc(insn), inst_trapcode(ctx.data(insn)).unwrap());
let condcode = inst_condcode(ctx.data(insn)).unwrap();
let cond = lower_condcode(condcode);
ctx.emit(Inst::TrapIf { cond, trap_info });
}
Opcode::FallthroughReturn | Opcode::Return => {
for (i, input) in inputs.iter().enumerate() {
let reg = input_to_reg(ctx, *input, NarrowValueMode::None);
let retval_reg = ctx.retval(i);
let ty = ctx.input_ty(insn, i);
ctx.emit(Inst::gen_move(retval_reg, reg, ty));
}
}
Opcode::Call | Opcode::CallIndirect => {
let loc = ctx.srcloc(insn);
let (mut abi, inputs) = match op {
Opcode::Call => {
let (extname, dist) = ctx.call_target(insn).unwrap();
let extname = extname.clone();
let sig = ctx.call_sig(insn).unwrap();
assert_eq!(inputs.len(), sig.params.len());
assert_eq!(outputs.len(), sig.returns.len());
(
Arm32ABICaller::from_func(sig, &extname, dist, loc)?,
&inputs[..],
)
}
Opcode::CallIndirect => {
let ptr = input_to_reg(ctx, inputs[0], NarrowValueMode::ZeroExtend);
let sig = ctx.call_sig(insn).unwrap();
assert_eq!(inputs.len() - 1, sig.params.len());
assert_eq!(outputs.len(), sig.returns.len());
(Arm32ABICaller::from_ptr(sig, ptr, loc, op)?, &inputs[1..])
}
_ => unreachable!(),
};
assert_eq!(inputs.len(), abi.num_args());
for (i, input) in inputs.iter().enumerate().filter(|(i, _)| *i <= 3) {
let arg_reg = input_to_reg(ctx, *input, NarrowValueMode::None);
abi.emit_copy_reg_to_arg(ctx, i, arg_reg);
}
abi.emit_call(ctx);
for (i, output) in outputs.iter().enumerate() {
let retval_reg = output_to_reg(ctx, *output);
abi.emit_copy_retval_to_reg(ctx, i, retval_reg);
}
}
_ => panic!("lowering {} unimplemented!", op),
}
Ok(())
}
pub(crate) fn lower_branch<C: LowerCtx<I = Inst>>(
ctx: &mut C,
branches: &[IRInst],
targets: &[MachLabel],
fallthrough: Option<MachLabel>,
) -> CodegenResult<()> {
// A block should end with at most two branches. The first may be a
// conditional branch; a conditional branch can be followed only by an
// unconditional branch or fallthrough. Otherwise, if only one branch,
// it may be an unconditional branch, a fallthrough, a return, or a
// trap. These conditions are verified by `is_ebb_basic()` during the
// verifier pass.
assert!(branches.len() <= 2);
if branches.len() == 2 {
// Must be a conditional branch followed by an unconditional branch.
let op0 = ctx.data(branches[0]).opcode();
let op1 = ctx.data(branches[1]).opcode();
assert!(op1 == Opcode::Jump || op1 == Opcode::Fallthrough);
let taken = BranchTarget::Label(targets[0]);
let not_taken = match op1 {
Opcode::Jump => BranchTarget::Label(targets[1]),
Opcode::Fallthrough => BranchTarget::Label(fallthrough.unwrap()),
_ => unreachable!(), // assert above.
};
match op0 {
Opcode::Brz | Opcode::Brnz => {
let rn = input_to_reg(
ctx,
InsnInput {
insn: branches[0],
input: 0,
},
NarrowValueMode::ZeroExtend,
);
let cond = if op0 == Opcode::Brz {
Cond::Eq
} else {
Cond::Ne
};
ctx.emit(Inst::CmpImm8 { rn, imm8: 0 });
ctx.emit(Inst::CondBr {
taken,
not_taken,
cond,
});
}
_ => unimplemented!(),
}
} else {
// Must be an unconditional branch or an indirect branch.
let op = ctx.data(branches[0]).opcode();
match op {
Opcode::Jump | Opcode::Fallthrough => {
assert_eq!(branches.len(), 1);
// In the Fallthrough case, the machine-independent driver
// fills in `targets[0]` with our fallthrough block, so this
// is valid for both Jump and Fallthrough.
ctx.emit(Inst::Jump {
dest: BranchTarget::Label(targets[0]),
});
}
_ => unimplemented!(),
}
}
Ok(())
}

View File

@@ -1,149 +1,123 @@
//! ARM 32-bit Instruction Set Architecture. //! 32-bit ARM Instruction Set Architecture.
mod abi; use crate::ir::condcodes::IntCC;
mod binemit; use crate::ir::Function;
mod enc_tables;
mod registers;
pub mod settings;
use super::super::settings as shared_settings;
#[cfg(feature = "testing_hooks")]
use crate::binemit::CodeSink;
use crate::binemit::{emit_function, MemoryCodeSink};
use crate::ir;
use crate::isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings};
use crate::isa::Builder as IsaBuilder; use crate::isa::Builder as IsaBuilder;
use crate::isa::{EncInfo, RegClass, RegInfo, TargetIsa}; use crate::machinst::{
use crate::regalloc; compile, MachBackend, MachCompileResult, ShowWithRRU, TargetIsaAdapter, VCode,
use alloc::borrow::Cow; };
use crate::result::CodegenResult;
use crate::settings;
use alloc::boxed::Box; use alloc::boxed::Box;
use core::any::Any; use regalloc::RealRegUniverse;
use core::fmt; use target_lexicon::{Architecture, ArmArchitecture, Triple};
use target_lexicon::{Architecture, Triple};
#[allow(dead_code)] // New backend:
struct Isa { mod abi;
mod inst;
mod lower;
mod lower_inst;
use inst::create_reg_universe;
/// An ARM32 backend.
pub struct Arm32Backend {
triple: Triple, triple: Triple,
shared_flags: shared_settings::Flags, flags: settings::Flags,
isa_flags: settings::Flags, reg_universe: RealRegUniverse,
cpumode: &'static [shared_enc_tables::Level1Entry<u16>],
} }
/// Get an ISA builder for creating ARM32 targets. impl Arm32Backend {
pub fn isa_builder(triple: Triple) -> IsaBuilder { /// Create a new ARM32 backend with the given (shared) flags.
IsaBuilder { pub fn new_with_flags(triple: Triple, flags: settings::Flags) -> Arm32Backend {
let reg_universe = create_reg_universe();
Arm32Backend {
triple, triple,
setup: settings::builder(), flags,
constructor: isa_constructor, reg_universe,
}
}
fn compile_vcode(
&self,
func: &Function,
flags: settings::Flags,
) -> CodegenResult<VCode<inst::Inst>> {
// This performs lowering to VCode, register-allocates the code, computes
// block layout and finalizes branches. The result is ready for binary emission.
let abi = Box::new(abi::Arm32ABICallee::new(func, flags)?);
compile::compile::<Arm32Backend>(func, self, abi)
} }
} }
fn isa_constructor( impl MachBackend for Arm32Backend {
triple: Triple, fn compile_function(
shared_flags: shared_settings::Flags, &self,
builder: shared_settings::Builder, func: &Function,
) -> Box<dyn TargetIsa> { want_disasm: bool,
let level1 = match triple.architecture { ) -> CodegenResult<MachCompileResult> {
Architecture::Arm(arm) => { let flags = self.flags();
if arm.is_thumb() { let vcode = self.compile_vcode(func, flags.clone())?;
&enc_tables::LEVEL1_T32[..] let buffer = vcode.emit();
let frame_size = vcode.frame_size();
let disasm = if want_disasm {
Some(vcode.show_rru(Some(&create_reg_universe())))
} else { } else {
&enc_tables::LEVEL1_A32[..] None
}
}
_ => panic!(),
}; };
Box::new(Isa {
triple,
isa_flags: settings::Flags::new(&shared_flags, builder),
shared_flags,
cpumode: level1,
})
}
impl TargetIsa for Isa { let buffer = buffer.finish();
Ok(MachCompileResult {
buffer,
frame_size,
disasm,
})
}
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"arm32" "arm32"
} }
fn triple(&self) -> &Triple { fn triple(&self) -> Triple {
&self.triple self.triple.clone()
} }
fn flags(&self) -> &shared_settings::Flags { fn flags(&self) -> &settings::Flags {
&self.shared_flags &self.flags
} }
fn register_info(&self) -> RegInfo { fn reg_universe(&self) -> &RealRegUniverse {
registers::INFO.clone() &self.reg_universe
} }
fn encoding_info(&self) -> EncInfo { fn unsigned_add_overflow_condition(&self) -> IntCC {
enc_tables::INFO.clone() // Carry flag set.
IntCC::UnsignedGreaterThanOrEqual
} }
fn legal_encodings<'a>( fn unsigned_sub_overflow_condition(&self) -> IntCC {
&'a self, // Carry flag clear.
func: &'a ir::Function, IntCC::UnsignedLessThan
inst: &'a ir::InstructionData,
ctrl_typevar: ir::Type,
) -> Encodings<'a> {
lookup_enclist(
ctrl_typevar,
inst,
func,
self.cpumode,
&enc_tables::LEVEL2[..],
&enc_tables::ENCLISTS[..],
&enc_tables::LEGALIZE_ACTIONS[..],
&enc_tables::RECIPE_PREDICATES[..],
&enc_tables::INST_PREDICATES[..],
self.isa_flags.predicate_view(),
)
}
fn legalize_signature(&self, sig: &mut Cow<ir::Signature>, current: bool) {
abi::legalize_signature(sig, &self.triple, current)
}
fn regclass_for_abi_type(&self, ty: ir::Type) -> RegClass {
abi::regclass_for_abi_type(ty)
}
fn allocatable_registers(&self, func: &ir::Function) -> regalloc::RegisterSet {
abi::allocatable_registers(func)
}
#[cfg(feature = "testing_hooks")]
fn emit_inst(
&self,
func: &ir::Function,
inst: ir::Inst,
divert: &mut regalloc::RegDiversions,
sink: &mut dyn CodeSink,
) {
binemit::emit_inst(func, inst, divert, sink, self)
}
fn emit_function_to_memory(&self, func: &ir::Function, sink: &mut MemoryCodeSink) {
emit_function(func, binemit::emit_inst, sink, self)
}
fn unsigned_add_overflow_condition(&self) -> ir::condcodes::IntCC {
ir::condcodes::IntCC::UnsignedLessThan
}
fn unsigned_sub_overflow_condition(&self) -> ir::condcodes::IntCC {
ir::condcodes::IntCC::UnsignedGreaterThanOrEqual
}
fn as_any(&self) -> &dyn Any {
self as &dyn Any
} }
} }
impl fmt::Display for Isa { /// Create a new `isa::Builder`.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { pub fn isa_builder(triple: Triple) -> IsaBuilder {
write!(f, "{}\n{}", self.shared_flags, self.isa_flags) assert!(match triple.architecture {
Architecture::Arm(ArmArchitecture::Arm)
| Architecture::Arm(ArmArchitecture::Armv7)
| Architecture::Arm(ArmArchitecture::Armv6) => true,
_ => false,
});
IsaBuilder {
triple,
setup: settings::builder(),
constructor: |triple, shared_flags, _| {
let backend = Arm32Backend::new_with_flags(triple, shared_flags);
Box::new(TargetIsaAdapter::new(backend))
},
} }
} }

View File

@@ -1,68 +0,0 @@
//! ARM32 register descriptions.
use crate::isa::registers::{RegBank, RegClass, RegClassData, RegInfo, RegUnit};
include!(concat!(env!("OUT_DIR"), "/registers-arm32.rs"));
#[cfg(test)]
mod tests {
use super::{D, GPR, INFO, S};
use crate::isa::RegUnit;
use alloc::string::{String, ToString};
#[test]
fn unit_encodings() {
assert_eq!(INFO.parse_regunit("s0"), Some(0));
assert_eq!(INFO.parse_regunit("s31"), Some(31));
assert_eq!(INFO.parse_regunit("s32"), Some(32));
assert_eq!(INFO.parse_regunit("r0"), Some(64));
assert_eq!(INFO.parse_regunit("r15"), Some(79));
}
#[test]
fn unit_names() {
fn uname(ru: RegUnit) -> String {
INFO.display_regunit(ru).to_string()
}
assert_eq!(uname(0), "%s0");
assert_eq!(uname(1), "%s1");
assert_eq!(uname(31), "%s31");
assert_eq!(uname(64), "%r0");
}
#[test]
fn overlaps() {
// arm32 has the most interesting register geometries, so test `regs_overlap()` here.
use crate::isa::regs_overlap;
let r0 = GPR.unit(0);
let r1 = GPR.unit(1);
let r2 = GPR.unit(2);
assert!(regs_overlap(GPR, r0, GPR, r0));
assert!(regs_overlap(GPR, r2, GPR, r2));
assert!(!regs_overlap(GPR, r0, GPR, r1));
assert!(!regs_overlap(GPR, r1, GPR, r0));
assert!(!regs_overlap(GPR, r2, GPR, r1));
assert!(!regs_overlap(GPR, r1, GPR, r2));
let s0 = S.unit(0);
let s1 = S.unit(1);
let s2 = S.unit(2);
let s3 = S.unit(3);
let d0 = D.unit(0);
let d1 = D.unit(1);
assert!(regs_overlap(S, s0, D, d0));
assert!(regs_overlap(S, s1, D, d0));
assert!(!regs_overlap(S, s0, D, d1));
assert!(!regs_overlap(S, s1, D, d1));
assert!(regs_overlap(S, s2, D, d1));
assert!(regs_overlap(S, s3, D, d1));
assert!(!regs_overlap(D, d1, S, s1));
assert!(regs_overlap(D, d1, S, s2));
assert!(!regs_overlap(D, d0, D, d1));
assert!(regs_overlap(D, d1, D, d1));
}
}

View File

@@ -1,9 +0,0 @@
//! ARM32 Settings.
use crate::settings::{self, detail, Builder};
use core::fmt;
// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs`. This file contains a
// public `Flags` struct with an impl for all of the settings defined in
// `cranelift-codegen/meta/src/isa/arm32/mod.rs`.
include!(concat!(env!("OUT_DIR"), "/settings-arm32.rs"));

View File

@@ -59,6 +59,10 @@ pub(crate) struct X64ABIMachineSpec;
impl ABIMachineSpec for X64ABIMachineSpec { impl ABIMachineSpec for X64ABIMachineSpec {
type I = Inst; type I = Inst;
fn word_bits() -> u32 {
64
}
fn compute_arg_locs( fn compute_arg_locs(
call_conv: isa::CallConv, call_conv: isa::CallConv,
params: &[ir::AbiParam], params: &[ir::AbiParam],

View File

@@ -175,6 +175,32 @@ pub trait ABIMachineSpec {
/// The instruction type. /// The instruction type.
type I: VCodeInst; type I: VCodeInst;
/// Returns the number of bits in a word, that is 32/64 for 32/64-bit architecture.
fn word_bits() -> u32;
/// Returns the number of bytes in a word.
fn word_bytes() -> u32 {
return Self::word_bits() / 8;
}
/// Returns word-size integer type.
fn word_type() -> Type {
match Self::word_bits() {
32 => I32,
64 => I64,
_ => unreachable!(),
}
}
/// Returns word register class.
fn word_reg_class() -> RegClass {
match Self::word_bits() {
32 => RegClass::I32,
64 => RegClass::I64,
_ => unreachable!(),
}
}
/// Process a list of parameters or return values and allocate them to registers /// Process a list of parameters or return values and allocate them to registers
/// and stack slots. /// and stack slots.
/// ///
@@ -453,7 +479,8 @@ impl<M: ABIMachineSpec> ABICalleeImpl<M> {
for (stackslot, data) in f.stack_slots.iter() { for (stackslot, data) in f.stack_slots.iter() {
let off = stack_offset; let off = stack_offset;
stack_offset += data.size; stack_offset += data.size;
stack_offset = (stack_offset + 7) & !7; let mask = M::word_bytes() - 1;
stack_offset = (stack_offset + mask) & !mask;
debug_assert_eq!(stackslot.as_u32() as usize, stackslots.len()); debug_assert_eq!(stackslot.as_u32() as usize, stackslots.len());
stackslots.push(off); stackslots.push(off);
} }
@@ -587,7 +614,12 @@ fn generate_gv<M: ABIMachineSpec>(
} => { } => {
let base = generate_gv::<M>(f, abi, base, insts); let base = generate_gv::<M>(f, abi, base, insts);
let into_reg = Writable::from_reg(M::get_stacklimit_reg()); let into_reg = Writable::from_reg(M::get_stacklimit_reg());
insts.push(M::gen_load_base_offset(into_reg, base, offset.into(), I64)); insts.push(M::gen_load_base_offset(
into_reg,
base,
offset.into(),
M::word_type(),
));
return into_reg.to_reg(); return into_reg.to_reg();
} }
ref other => panic!("global value for stack limit not supported: {}", other), ref other => panic!("global value for stack limit not supported: {}", other),
@@ -603,13 +635,13 @@ fn generate_gv<M: ABIMachineSpec>(
/// associated vreg when needed to satisfy a safepoint (which requires all /// associated vreg when needed to satisfy a safepoint (which requires all
/// ref-typed values, even those in real registers in the original vcode, to be /// ref-typed values, even those in real registers in the original vcode, to be
/// in spillslots). /// in spillslots).
fn ty_from_ty_hint_or_reg_class(r: Reg, ty: Option<Type>) -> Type { fn ty_from_ty_hint_or_reg_class<M: ABIMachineSpec>(r: Reg, ty: Option<Type>) -> Type {
match (ty, r.get_class()) { match (ty, r.get_class()) {
// If the type is provided // If the type is provided
(Some(t), _) => t, (Some(t), _) => t,
// If no type is provided, this should be a register spill for a // If no type is provided, this should be a register spill for a
// safepoint, so we only expect I64 (integer) registers. // safepoint, so we only expect I32/I64 (integer) registers.
(None, RegClass::I64) => I64, (None, rc) if rc == M::word_reg_class() => M::word_type(),
_ => panic!("Unexpected register class!"), _ => panic!("Unexpected register class!"),
} }
} }
@@ -679,19 +711,22 @@ impl<M: ABIMachineSpec> ABICallee for ABICalleeImpl<M> {
fn gen_copy_reg_to_retval(&self, idx: usize, from_reg: Writable<Reg>) -> Vec<Self::I> { fn gen_copy_reg_to_retval(&self, idx: usize, from_reg: Writable<Reg>) -> Vec<Self::I> {
let mut ret = Vec::new(); let mut ret = Vec::new();
let word_bits = M::word_bits() as u8;
match &self.sig.rets[idx] { match &self.sig.rets[idx] {
&ABIArg::Reg(r, ty, ext) => { &ABIArg::Reg(r, ty, ext) => {
let from_bits = ty_bits(ty) as u8; let from_bits = ty_bits(ty) as u8;
let dest_reg = Writable::from_reg(r.to_reg()); let dest_reg = Writable::from_reg(r.to_reg());
match (ext, from_bits) { match (ext, from_bits) {
(ArgumentExtension::Uext, n) | (ArgumentExtension::Sext, n) if n < 64 => { (ArgumentExtension::Uext, n) | (ArgumentExtension::Sext, n)
if n < word_bits =>
{
let signed = ext == ArgumentExtension::Sext; let signed = ext == ArgumentExtension::Sext;
ret.push(M::gen_extend( ret.push(M::gen_extend(
dest_reg, dest_reg,
from_reg.to_reg(), from_reg.to_reg(),
signed, signed,
from_bits, from_bits,
/* to_bits = */ 64, /* to_bits = */ word_bits,
)); ));
} }
_ => ret.push(M::gen_move(dest_reg, from_reg.to_reg(), ty)), _ => ret.push(M::gen_move(dest_reg, from_reg.to_reg(), ty)),
@@ -706,18 +741,20 @@ impl<M: ABIMachineSpec> ABICallee for ABICalleeImpl<M> {
.expect("Argument stack offset greater than 2GB; should hit impl limit first"); .expect("Argument stack offset greater than 2GB; should hit impl limit first");
// Trash the from_reg; it should be its last use. // Trash the from_reg; it should be its last use.
match (ext, from_bits) { match (ext, from_bits) {
(ArgumentExtension::Uext, n) | (ArgumentExtension::Sext, n) if n < 64 => { (ArgumentExtension::Uext, n) | (ArgumentExtension::Sext, n)
assert_eq!(RegClass::I64, from_reg.to_reg().get_class()); if n < word_bits =>
{
assert_eq!(M::word_reg_class(), from_reg.to_reg().get_class());
let signed = ext == ArgumentExtension::Sext; let signed = ext == ArgumentExtension::Sext;
ret.push(M::gen_extend( ret.push(M::gen_extend(
from_reg, from_reg,
from_reg.to_reg(), from_reg.to_reg(),
signed, signed,
from_bits, from_bits,
/* to_bits = */ 64, /* to_bits = */ word_bits,
)); ));
// Store the extended version. // Store the extended version.
ty = I64; ty = M::word_type();
} }
_ => {} _ => {}
}; };
@@ -802,7 +839,7 @@ impl<M: ABIMachineSpec> ABICallee for ABICalleeImpl<M> {
fn load_spillslot(&self, slot: SpillSlot, ty: Type, into_reg: Writable<Reg>) -> Self::I { fn load_spillslot(&self, slot: SpillSlot, ty: Type, into_reg: Writable<Reg>) -> Self::I {
// Offset from beginning of spillslot area, which is at nominal SP + stackslots_size. // Offset from beginning of spillslot area, which is at nominal SP + stackslots_size.
let islot = slot.get() as i64; let islot = slot.get() as i64;
let spill_off = islot * 8; // FIXME: 64-bit machine assumed. let spill_off = islot * M::word_bytes() as i64;
let sp_off = self.stackslots_size as i64 + spill_off; let sp_off = self.stackslots_size as i64 + spill_off;
trace!("load_spillslot: slot {:?} -> sp_off {}", slot, sp_off); trace!("load_spillslot: slot {:?} -> sp_off {}", slot, sp_off);
M::gen_load_stack(StackAMode::NominalSPOffset(sp_off, ty), into_reg, ty) M::gen_load_stack(StackAMode::NominalSPOffset(sp_off, ty), into_reg, ty)
@@ -812,7 +849,7 @@ impl<M: ABIMachineSpec> ABICallee for ABICalleeImpl<M> {
fn store_spillslot(&self, slot: SpillSlot, ty: Type, from_reg: Reg) -> Self::I { fn store_spillslot(&self, slot: SpillSlot, ty: Type, from_reg: Reg) -> Self::I {
// Offset from beginning of spillslot area, which is at nominal SP + stackslots_size. // Offset from beginning of spillslot area, which is at nominal SP + stackslots_size.
let islot = slot.get() as i64; let islot = slot.get() as i64;
let spill_off = islot * 8; // FIXME: 64-bit machine assumed. let spill_off = islot * M::word_bytes() as i64;
let sp_off = self.stackslots_size as i64 + spill_off; let sp_off = self.stackslots_size as i64 + spill_off;
trace!("store_spillslot: slot {:?} -> sp_off {}", slot, sp_off); trace!("store_spillslot: slot {:?} -> sp_off {}", slot, sp_off);
M::gen_store_stack(StackAMode::NominalSPOffset(sp_off, ty), from_reg, ty) M::gen_store_stack(StackAMode::NominalSPOffset(sp_off, ty), from_reg, ty)
@@ -832,12 +869,14 @@ impl<M: ABIMachineSpec> ABICallee for ABICalleeImpl<M> {
state state
); );
let map_size = (virtual_sp_offset + nominal_sp_to_fp) as u32; let map_size = (virtual_sp_offset + nominal_sp_to_fp) as u32;
let map_words = (map_size + 7) / 8; // FIXME: 64-bit machine assumed. let bytes = M::word_bytes();
let map_words = (map_size + bytes - 1) / bytes;
let mut bits = std::iter::repeat(false) let mut bits = std::iter::repeat(false)
.take(map_words as usize) .take(map_words as usize)
.collect::<Vec<bool>>(); .collect::<Vec<bool>>();
let first_spillslot_word = ((self.stackslots_size + virtual_sp_offset as u32) / 8) as usize; let first_spillslot_word =
((self.stackslots_size + virtual_sp_offset as u32) / bytes) as usize;
for &slot in slots { for &slot in slots {
let slot = slot.get() as usize; let slot = slot.get() as usize;
bits[first_spillslot_word + slot] = true; bits[first_spillslot_word + slot] = true;
@@ -853,16 +892,17 @@ impl<M: ABIMachineSpec> ABICallee for ABICalleeImpl<M> {
insts.extend(M::gen_prologue_frame_setup().into_iter()); insts.extend(M::gen_prologue_frame_setup().into_iter());
} }
let mut total_stacksize = self.stackslots_size + 8 * self.spillslots.unwrap() as u32; let bytes = M::word_bytes();
let mut total_stacksize = self.stackslots_size + bytes * self.spillslots.unwrap() as u32;
if self.call_conv.extends_baldrdash() { if self.call_conv.extends_baldrdash() {
debug_assert!( debug_assert!(
!self.flags.enable_probestack(), !self.flags.enable_probestack(),
"baldrdash does not expect cranelift to emit stack probes" "baldrdash does not expect cranelift to emit stack probes"
); );
// FIXME: 64-bit machine assumed. total_stacksize += self.flags.baldrdash_prologue_words() as u32 * bytes;
total_stacksize += self.flags.baldrdash_prologue_words() as u32 * 8;
} }
let total_stacksize = (total_stacksize + 15) & !15; // 16-align the stack. let mask = 2 * bytes - 1;
let total_stacksize = (total_stacksize + mask) & !mask; // 16-align the stack.
let mut total_sp_adjust = 0; let mut total_sp_adjust = 0;
@@ -943,7 +983,7 @@ impl<M: ABIMachineSpec> ABICallee for ABICalleeImpl<M> {
} }
fn gen_spill(&self, to_slot: SpillSlot, from_reg: RealReg, ty: Option<Type>) -> Self::I { fn gen_spill(&self, to_slot: SpillSlot, from_reg: RealReg, ty: Option<Type>) -> Self::I {
let ty = ty_from_ty_hint_or_reg_class(from_reg.to_reg(), ty); let ty = ty_from_ty_hint_or_reg_class::<M>(from_reg.to_reg(), ty);
self.store_spillslot(to_slot, ty, from_reg.to_reg()) self.store_spillslot(to_slot, ty, from_reg.to_reg())
} }
@@ -953,7 +993,7 @@ impl<M: ABIMachineSpec> ABICallee for ABICalleeImpl<M> {
from_slot: SpillSlot, from_slot: SpillSlot,
ty: Option<Type>, ty: Option<Type>,
) -> Self::I { ) -> Self::I {
let ty = ty_from_ty_hint_or_reg_class(to_reg.to_reg().to_reg(), ty); let ty = ty_from_ty_hint_or_reg_class::<M>(to_reg.to_reg().to_reg(), ty);
self.load_spillslot(from_slot, ty, to_reg.map(|r| r.to_reg())) self.load_spillslot(from_slot, ty, to_reg.map(|r| r.to_reg()))
} }
} }
@@ -1092,11 +1132,13 @@ impl<M: ABIMachineSpec> ABICaller for ABICallerImpl<M> {
idx: usize, idx: usize,
from_reg: Reg, from_reg: Reg,
) { ) {
let word_rc = M::word_reg_class();
let word_bits = M::word_bits() as usize;
match &self.sig.args[idx] { match &self.sig.args[idx] {
&ABIArg::Reg(reg, ty, ext) &ABIArg::Reg(reg, ty, ext)
if ext != ir::ArgumentExtension::None && ty_bits(ty) < 64 => if ext != ir::ArgumentExtension::None && ty_bits(ty) < word_bits =>
{ {
assert_eq!(RegClass::I64, reg.get_class()); assert_eq!(word_rc, reg.get_class());
let signed = match ext { let signed = match ext {
ir::ArgumentExtension::Uext => false, ir::ArgumentExtension::Uext => false,
ir::ArgumentExtension::Sext => true, ir::ArgumentExtension::Sext => true,
@@ -1107,15 +1149,15 @@ impl<M: ABIMachineSpec> ABICaller for ABICallerImpl<M> {
from_reg, from_reg,
signed, signed,
ty_bits(ty) as u8, ty_bits(ty) as u8,
64, word_bits as u8,
)); ));
} }
&ABIArg::Reg(reg, ty, _) => { &ABIArg::Reg(reg, ty, _) => {
ctx.emit(M::gen_move(Writable::from_reg(reg.to_reg()), from_reg, ty)); ctx.emit(M::gen_move(Writable::from_reg(reg.to_reg()), from_reg, ty));
} }
&ABIArg::Stack(off, mut ty, ext) => { &ABIArg::Stack(off, mut ty, ext) => {
if ext != ir::ArgumentExtension::None && ty_bits(ty) < 64 { if ext != ir::ArgumentExtension::None && ty_bits(ty) < word_bits {
assert_eq!(RegClass::I64, from_reg.get_class()); assert_eq!(word_rc, from_reg.get_class());
let signed = match ext { let signed = match ext {
ir::ArgumentExtension::Uext => false, ir::ArgumentExtension::Uext => false,
ir::ArgumentExtension::Sext => true, ir::ArgumentExtension::Sext => true,
@@ -1129,10 +1171,10 @@ impl<M: ABIMachineSpec> ABICaller for ABICallerImpl<M> {
from_reg, from_reg,
signed, signed,
ty_bits(ty) as u8, ty_bits(ty) as u8,
64, word_bits as u8,
)); ));
// Store the extended version. // Store the extended version.
ty = I64; ty = M::word_type();
} }
ctx.emit(M::gen_store_stack( ctx.emit(M::gen_store_stack(
StackAMode::SPOffset(off, ty), StackAMode::SPOffset(off, ty),
@@ -1169,8 +1211,10 @@ impl<M: ABIMachineSpec> ABICaller for ABICallerImpl<M> {
mem::replace(&mut self.uses, Default::default()), mem::replace(&mut self.uses, Default::default()),
mem::replace(&mut self.defs, Default::default()), mem::replace(&mut self.defs, Default::default()),
); );
let word_rc = M::word_reg_class();
let word_type = M::word_type();
if let Some(i) = self.sig.stack_ret_arg { if let Some(i) = self.sig.stack_ret_arg {
let rd = ctx.alloc_tmp(RegClass::I64, I64); let rd = ctx.alloc_tmp(word_rc, word_type);
let ret_area_base = self.sig.stack_arg_space; let ret_area_base = self.sig.stack_arg_space;
ctx.emit(M::gen_get_stack_addr( ctx.emit(M::gen_get_stack_addr(
StackAMode::SPOffset(ret_area_base, I8), StackAMode::SPOffset(ret_area_base, I8),
@@ -1179,7 +1223,7 @@ impl<M: ABIMachineSpec> ABICaller for ABICallerImpl<M> {
)); ));
self.emit_copy_reg_to_arg(ctx, i, rd.to_reg()); self.emit_copy_reg_to_arg(ctx, i, rd.to_reg());
} }
let tmp = ctx.alloc_tmp(RegClass::I64, I64); let tmp = ctx.alloc_tmp(word_rc, word_type);
for (is_safepoint, inst) in for (is_safepoint, inst) in
M::gen_call(&self.dest, uses, defs, self.loc, self.opcode, tmp).into_iter() M::gen_call(&self.dest, uses, defs, self.loc, self.opcode, tmp).into_iter()
{ {

View File

@@ -274,29 +274,18 @@ impl fmt::Display for Pressure {
#[cfg(feature = "arm32")] #[cfg(feature = "arm32")]
mod tests { mod tests {
use super::Pressure; use super::Pressure;
use crate::isa::{RegClass, TargetIsa}; use crate::isa::registers::{RegBank, RegClassData};
use crate::isa::{RegClass, RegInfo, RegUnit};
use crate::regalloc::RegisterSet; use crate::regalloc::RegisterSet;
use alloc::boxed::Box;
use core::borrow::Borrow; use core::borrow::Borrow;
use core::str::FromStr;
use target_lexicon::triple;
// Make an arm32 `TargetIsa`, if possible. // Arm32 `TargetIsa` is now `TargetIsaAdapter`, which does not hold any info
fn arm32() -> Option<Box<dyn TargetIsa>> { // about registers, so we directly access `INFO` from registers-arm32.rs.
use crate::isa; include!(concat!(env!("OUT_DIR"), "/registers-arm32.rs"));
use crate::settings;
let shared_builder = settings::builder();
let shared_flags = settings::Flags::new(shared_builder);
isa::lookup(triple!("arm"))
.ok()
.map(|b| b.finish(shared_flags))
}
// Get a register class by name. // Get a register class by name.
fn rc_by_name(isa: &dyn TargetIsa, name: &str) -> RegClass { fn rc_by_name(reginfo: &RegInfo, name: &str) -> RegClass {
isa.register_info() reginfo
.classes .classes
.iter() .iter()
.find(|rc| rc.name == name) .find(|rc| rc.name == name)
@@ -305,11 +294,10 @@ mod tests {
#[test] #[test]
fn basic_counting() { fn basic_counting() {
let isa = arm32().expect("This test requires arm32 support"); let reginfo = INFO.borrow();
let isa = isa.borrow(); let gpr = rc_by_name(&reginfo, "GPR");
let gpr = rc_by_name(isa, "GPR"); let s = rc_by_name(&reginfo, "S");
let s = rc_by_name(isa, "S");
let reginfo = isa.register_info();
let regs = RegisterSet::new(); let regs = RegisterSet::new();
let mut pressure = Pressure::new(&reginfo, &regs); let mut pressure = Pressure::new(&reginfo, &regs);
@@ -333,12 +321,10 @@ mod tests {
#[test] #[test]
fn arm_float_bank() { fn arm_float_bank() {
let isa = arm32().expect("This test requires arm32 support"); let reginfo = INFO.borrow();
let isa = isa.borrow(); let s = rc_by_name(&reginfo, "S");
let s = rc_by_name(isa, "S"); let d = rc_by_name(&reginfo, "D");
let d = rc_by_name(isa, "D"); let q = rc_by_name(&reginfo, "Q");
let q = rc_by_name(isa, "Q");
let reginfo = isa.register_info();
let regs = RegisterSet::new(); let regs = RegisterSet::new();
let mut pressure = Pressure::new(&reginfo, &regs); let mut pressure = Pressure::new(&reginfo, &regs);

View File

@@ -1149,24 +1149,14 @@ mod tests {
use super::{Move, Solver}; use super::{Move, Solver};
use crate::entity::EntityRef; use crate::entity::EntityRef;
use crate::ir::Value; use crate::ir::Value;
use crate::isa::{RegClass, RegInfo, RegUnit, TargetIsa}; use crate::isa::registers::{RegBank, RegClassData};
use crate::isa::{RegClass, RegInfo, RegUnit};
use crate::regalloc::RegisterSet; use crate::regalloc::RegisterSet;
use alloc::boxed::Box; use core::borrow::Borrow;
use core::str::FromStr;
use target_lexicon::triple;
// Make an arm32 `TargetIsa`, if possible. // Arm32 `TargetIsa` is now `TargetIsaAdapter`, which does not hold any info
fn arm32() -> Option<Box<dyn TargetIsa>> { // about registers, so we directly access `INFO` from registers-arm32.rs.
use crate::isa; include!(concat!(env!("OUT_DIR"), "/registers-arm32.rs"));
use crate::settings;
let shared_builder = settings::builder();
let shared_flags = settings::Flags::new(shared_builder);
isa::lookup(triple!("arm"))
.ok()
.map(|b| b.finish(shared_flags))
}
// Get a register class by name. // Get a register class by name.
fn rc_by_name(reginfo: &RegInfo, name: &str) -> RegClass { fn rc_by_name(reginfo: &RegInfo, name: &str) -> RegClass {
@@ -1207,8 +1197,7 @@ mod tests {
#[test] #[test]
fn simple_moves() { fn simple_moves() {
let isa = arm32().expect("This test requires arm32 support"); let reginfo = INFO.borrow();
let reginfo = isa.register_info();
let gpr = rc_by_name(&reginfo, "GPR"); let gpr = rc_by_name(&reginfo, "GPR");
let r0 = gpr.unit(0); let r0 = gpr.unit(0);
let r1 = gpr.unit(1); let r1 = gpr.unit(1);
@@ -1260,8 +1249,7 @@ mod tests {
#[test] #[test]
fn harder_move_cycles() { fn harder_move_cycles() {
let isa = arm32().expect("This test requires arm32 support"); let reginfo = INFO.borrow();
let reginfo = isa.register_info();
let s = rc_by_name(&reginfo, "S"); let s = rc_by_name(&reginfo, "S");
let d = rc_by_name(&reginfo, "D"); let d = rc_by_name(&reginfo, "D");
let d0 = d.unit(0); let d0 = d.unit(0);
@@ -1322,8 +1310,7 @@ mod tests {
#[test] #[test]
fn emergency_spill() { fn emergency_spill() {
let isa = arm32().expect("This test requires arm32 support"); let reginfo = INFO.borrow();
let reginfo = isa.register_info();
let gpr = rc_by_name(&reginfo, "GPR"); let gpr = rc_by_name(&reginfo, "GPR");
let r0 = gpr.unit(0); let r0 = gpr.unit(0);
let r1 = gpr.unit(1); let r1 = gpr.unit(1);

View File

@@ -0,0 +1,269 @@
test compile
target arm
function %iadd(i8, i8) -> i8 {
block0(v0: i8, v1: i8):
v2 = iadd v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: add r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %iadd(i16, i16) -> i16 {
block0(v0: i16, v1: i16):
v2 = iadd v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: add r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %iadd(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = iadd v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: add r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %sadd_sat(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = sadd_sat v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: qadd r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %isub(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = isub v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: sub r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ssub_sat(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = ssub_sat v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: qsub r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ineg(i32) -> i32 {
block0(v0: i32):
v1 = ineg v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: rsb r0, r0, #0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %imul(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = imul v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mul r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %umulhi(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = umulhi v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: umull r1, r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %smulhi(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = smulhi v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: smull r1, r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %udiv(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = udiv v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: udiv r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %sdiv(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = sdiv v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: sdiv r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %iadd_flags(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2, v3 = iadd_ifcout v0, v1
v4, v5 = iadd_ifcarry v1, v2, v3
v6 = iadd_ifcin v1, v4, v5
return v6
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: adds r0, r0, r1
; nextln: adcs r0, r1, r0
; nextln: adc r0, r1, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %isub_flags(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2, v3 = isub_ifbout v0, v1
v4, v5 = isub_ifborrow v1, v2, v3
v6 = isub_ifbin v1, v4, v5
return v6
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: subs r0, r0, r1
; nextln: sbcs r0, r1, r0
; nextln: sbc r0, r1, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %band(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = band v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: and r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %bor(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = bor v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: orr r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %bxor(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = bxor v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: eor r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %bnot(i32) -> i32 {
block0(v0: i32):
v1 = bnot v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mvn r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %band_not(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = band_not v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: bic r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %bor_not(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = bor_not v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: orn r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr

View File

@@ -0,0 +1,132 @@
test compile
target arm
function %bitrev_i8(i8) -> i8 {
block0(v0: i8):
v1 = bitrev v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, r0, lsl #24
; nextln: rbit r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %bitrev_i16(i16) -> i16 {
block0(v0: i16):
v1 = bitrev v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, r0, lsl #16
; nextln: rbit r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %bitrev_i32(i32) -> i32 {
block0(v0: i32):
v1 = bitrev v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: rbit r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %clz_i8(i8) -> i8 {
block0(v0: i8):
v1 = clz v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxtb r0, r0
; nextln: clz r0, r0
; nextln: sub r0, r0, #24
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %clz_i16(i16) -> i16 {
block0(v0: i16):
v1 = clz v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxth r0, r0
; nextln: clz r0, r0
; nextln: sub r0, r0, #16
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %clz_i32(i32) -> i32 {
block0(v0: i32):
v1 = clz v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: clz r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ctz_i8(i8) -> i8 {
block0(v0: i8):
v1 = ctz v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxtb r0, r0
; nextln: rbit r0, r0
; nextln: clz r0, r0
; nextln: sub r0, r0, #24
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ctz_i16(i16) -> i16 {
block0(v0: i16):
v1 = ctz v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxth r0, r0
; nextln: rbit r0, r0
; nextln: clz r0, r0
; nextln: sub r0, r0, #16
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ctz_i32(i32) -> i32 {
block0(v0: i32):
v1 = ctz v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: rbit r0, r0
; nextln: clz r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr

View File

@@ -0,0 +1,60 @@
test compile
target arm
function %icmp(i32, i32) -> b1 {
block0(v0: i32, v1: i32):
v2 = icmp eq v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: cmp r0, r1
; nextln: ite eq ; mov r0, #1 ; mov r0, #0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ifcmp_trueif(i32, i32) -> b1 {
block0(v0: i32, v1: i32):
v2 = ifcmp v0, v1
v3 = trueif eq v2
return v3
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: cmp r0, r1
; nextln: ite eq ; mov r0, #1 ; mov r0, #0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %select(i32, i32, i32) -> i32 {
block0(v0: i32, v1: i32, v2: i32):
v3 = select v0, v1, v2
return v3
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: cmp r0, #0
; nextln: ite ne ; mov r0, r1 ; mov r0, r2
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %selectif(i32, i32, i32, i32) -> i32 {
block0(v0: i32, v1: i32, v2: i32, v3: i32):
v4 = ifcmp v0, v1
v5 = selectif.i32 eq v4, v2, v3
return v5
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: cmp r0, r1
; nextln: ite eq ; mov r0, r2 ; mov r0, r3
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr

View File

@@ -0,0 +1,107 @@
test compile
target arm
function %b1() -> b1 {
block0:
v0 = bconst.b1 true
return v0
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, #1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %b8() -> b8 {
block0:
v0 = bconst.b8 false
return v0
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, #0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %b16() -> b16 {
block0:
v0 = bconst.b16 true
return v0
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, #1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %b32() -> b32 {
block0:
v0 = bconst.b32 false
return v0
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, #0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %i8() -> i8 {
block0:
v0 = iconst.i8 0xff
return v0
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, #255
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %i8() -> i16 {
block0:
v0 = iconst.i16 0xffff
return v0
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, #65535
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %f() -> i32 {
block0:
v0 = iconst.i32 0xffff
return v0
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, #65535
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %f() -> i32 {
block0:
v0 = iconst.i32 0xffffffff
return v0
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, #65535
; nextln: movt r0, #65535
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr

View File

@@ -0,0 +1,131 @@
test compile
target arm
function %brnz(b1) -> i32 {
block0(v0: b1):
brnz v0, block1
jump block2
block1:
v1 = iconst.i32 1
return v1
block2:
v2 = iconst.i32 2
return v2
}
; check: Block 0:
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: and r0, r0, #1
; nextln: cmp r0, #0
; nextln: bne label1 ; b label2
; check: Block 1:
; check: mov r0, #1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
; check: Block 2:
; check: mov r0, #2
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %brz(b1) -> i32 {
block0(v0: b1):
brz v0, block1
jump block2
block1:
v1 = iconst.i32 1
return v1
block2:
v2 = iconst.i32 2
return v2
}
; check: Block 0:
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: and r0, r0, #1
; nextln: cmp r0, #0
; nextln: beq label1 ; b label2
; check: Block 1:
; check: mov r0, #1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
; check: Block 2:
; check: mov r0, #2
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %trap() {
block0:
trap user0
}
; check: udf #0
function %trapif(i32, i32) {
block0(v0: i32, v1: i32):
v2 = ifcmp v0, v1
trapif eq v2, user0
return
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: cmp r0, r1
; nextln: bne 2 ; udf #0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %debugtrap() {
block0:
debugtrap
return
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: bkpt #0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %call(i32) -> i32 {
fn0 = %f(i32) -> i32
block0(v0: i32):
v1 = call fn0(v0)
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: ldr r1, [pc, #4] ; b 4 ; data
; nextln: blx r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %call_indirect(i32, i32) -> i32 {
sig0 = (i32) -> i32
block0(v0: i32, v1: i32):
v2 = call_indirect.i32 sig0, v1(v0)
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: blx r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr

View File

@@ -0,0 +1,80 @@
test compile
target arm
function %uextend_i8_i32(i8) -> i32 {
block0(v0: i8):
v1 = uextend.i32 v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxtb r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %uextend_i8_i16(i8) -> i16 {
block0(v0: i8):
v1 = uextend.i16 v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxtb r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %uextend_i16_i32(i16) -> i32 {
block0(v0: i16):
v1 = uextend.i32 v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxth r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %sextend_i8_i32(i8) -> i32 {
block0(v0: i8):
v1 = sextend.i32 v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: sxtb r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %sextend_i8_i16(i8) -> i16 {
block0(v0: i8):
v1 = sextend.i16 v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: sxtb r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %sextend_i16_i32(i16) -> i32 {
block0(v0: i16):
v1 = sextend.i32 v0
return v1
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: sxth r0, r0
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr

View File

@@ -0,0 +1,47 @@
test compile
target arm
function %args(i32) -> i32 {
sig0 = (i32, i32, i32, i32) -> i32
block0(v0: i32):
v1 = iconst.i32 1
v2 = iconst.i32 2
v3 = iconst.i32 3
v4 = iconst.i32 4
v5 = call_indirect.i32 sig0, v0(v1, v2, v3, v4)
return v5
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: push {r4, ip}
; nextln: virtual_sp_offset_adjust 8
; nextln: mov r4, r0
; nextln: mov r0, #1
; nextln: mov r1, #2
; nextln: mov r2, #3
; nextln: mov r3, #4
; nextln: blx r4
; nextln: pop {r4, ip}
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %multi_return() -> i32, i32, i32, i32 {
block0:
v0 = iconst.i32 1
v1 = iconst.i32 2
v2 = iconst.i32 3
v3 = iconst.i32 4
return v0, v1, v2, v3
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: mov r0, #1
; nextln: mov r1, #2
; nextln: mov r2, #3
; nextln: mov r3, #4
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr

View File

@@ -0,0 +1,157 @@
test compile
target arm
function %ishl_i8(i8, i8) -> i8 {
block0(v0: i8, v1: i8):
v2 = ishl v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxtb r1, r1
; nextln: lsl r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ishl_i16(i16, i16) -> i16 {
block0(v0: i16, v1: i16):
v2 = ishl v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxth r1, r1
; nextln: lsl r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ishl_i32(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = ishl v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: lsl r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ushr_i8(i8, i8) -> i8 {
block0(v0: i8, v1: i8):
v2 = ushr v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxtb r0, r0
; nextln: uxtb r1, r1
; nextln: lsr r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ushr_i16(i16, i16) -> i16 {
block0(v0: i16, v1: i16):
v2 = ushr v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: uxth r0, r0
; nextln: uxth r1, r1
; nextln: lsr r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ushr_i32(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = ushr v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: lsr r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %sshr_i8(i8, i8) -> i8 {
block0(v0: i8, v1: i8):
v2 = sshr v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: sxtb r0, r0
; nextln: uxtb r1, r1
; nextln: asr r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %sshr_i16(i16, i16) -> i16 {
block0(v0: i16, v1: i16):
v2 = sshr v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: sxth r0, r0
; nextln: uxth r1, r1
; nextln: asr r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %sshr_i32(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = sshr v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: asr r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %ror_i32(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = rotr v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: ror r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr
function %rotl_i32(i32, i32) -> i32 {
block0(v0: i32, v1: i32):
v2 = rotl v0, v1
return v2
}
; check: push {fp, lr}
; nextln: mov fp, sp
; nextln: and r1, r1, #31
; nextln: rsb r1, r1, #32
; nextln: ror r0, r0, r1
; nextln: mov sp, fp
; nextln: pop {fp, lr}
; nextln: bx lr