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:
@@ -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],
|
||||||
|
|||||||
@@ -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,
|
|
||||||
regs: u32,
|
|
||||||
reg_limit: u32,
|
|
||||||
offset: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Args {
|
/// Support for the ARM ABI from the caller side (at a callsite).
|
||||||
fn new(bits: u8) -> Self {
|
pub(crate) type Arm32ABICaller = ABICallerImpl<Arm32MachineDeps>;
|
||||||
Self {
|
|
||||||
pointer_bits: bits,
|
/// This is the limit for the size of argument and return-value areas on the
|
||||||
pointer_bytes: bits / 8,
|
/// stack. We place a reasonable limit here to avoid integer overflow issues
|
||||||
pointer_type: Type::int(u16::from(bits)).unwrap(),
|
/// with 32-bit arithmetic: for now, 128 MB.
|
||||||
regs: 0,
|
static STACK_ARG_RET_SIZE_LIMIT: u64 = 128 * 1024 * 1024;
|
||||||
reg_limit: 8,
|
|
||||||
offset: 0,
|
/// ARM-specific ABI behavior. This struct just serves as an implementation
|
||||||
|
/// point for the trait; it is never actually instantiated.
|
||||||
|
pub(crate) struct Arm32MachineDeps;
|
||||||
|
|
||||||
|
impl Into<AMode> for StackAMode {
|
||||||
|
fn into(self) -> AMode {
|
||||||
|
match self {
|
||||||
|
StackAMode::FPOffset(off, ty) => AMode::FPOffset(off, ty),
|
||||||
|
StackAMode::NominalSPOffset(off, ty) => AMode::NominalSPOffset(off, ty),
|
||||||
|
StackAMode::SPOffset(off, ty) => AMode::SPOffset(off, ty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArgAssigner for Args {
|
impl ABIMachineSpec for Arm32MachineDeps {
|
||||||
fn assign(&mut self, arg: &AbiParam) -> ArgAction {
|
type I = Inst;
|
||||||
fn align(value: u32, to: u32) -> u32 {
|
|
||||||
(value + to - 1) & !(to - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
let ty = arg.value_type;
|
fn word_bits() -> u32 {
|
||||||
|
32
|
||||||
|
}
|
||||||
|
|
||||||
// Check for a legal type.
|
fn compute_arg_locs(
|
||||||
// SIMD instructions are currently no implemented, so break down vectors
|
_call_conv: isa::CallConv,
|
||||||
if ty.is_vector() {
|
params: &[ir::AbiParam],
|
||||||
return ValueConversion::VectorSplit.into();
|
args_or_rets: ArgsOrRets,
|
||||||
}
|
add_ret_area_ptr: bool,
|
||||||
|
) -> CodegenResult<(Vec<ABIArg>, i64, Option<usize>)> {
|
||||||
|
let mut next_rreg = 0;
|
||||||
|
let mut next_stack: u64 = 0;
|
||||||
|
let mut ret = vec![];
|
||||||
|
let mut stack_args = vec![];
|
||||||
|
|
||||||
// Large integers and booleans are broken down to fit in a register.
|
let max_reg_val = 4; // r0-r3
|
||||||
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.
|
for i in 0..params.len() {
|
||||||
if ty.is_int() && ty.bits() < u16::from(self.pointer_bits) {
|
let param = params[i];
|
||||||
match arg.extension {
|
|
||||||
ArgumentExtension::None => {}
|
// Validate "purpose".
|
||||||
ArgumentExtension::Uext => return ValueConversion::Uext(self.pointer_type).into(),
|
match ¶m.purpose {
|
||||||
ArgumentExtension::Sext => return ValueConversion::Sext(self.pointer_type).into(),
|
&ir::ArgumentPurpose::VMContext
|
||||||
|
| &ir::ArgumentPurpose::Normal
|
||||||
|
| &ir::ArgumentPurpose::StackLimit
|
||||||
|
| &ir::ArgumentPurpose::SignatureId => {}
|
||||||
|
_ => panic!(
|
||||||
|
"Unsupported argument purpose {:?} in signature: {:?}",
|
||||||
|
param.purpose, params
|
||||||
|
),
|
||||||
|
}
|
||||||
|
assert!(param.value_type.bits() <= 32);
|
||||||
|
|
||||||
|
if next_rreg < max_reg_val {
|
||||||
|
let reg = rreg(next_rreg);
|
||||||
|
|
||||||
|
ret.push(ABIArg::Reg(
|
||||||
|
reg.to_real_reg(),
|
||||||
|
param.value_type,
|
||||||
|
param.extension,
|
||||||
|
));
|
||||||
|
next_rreg += 1;
|
||||||
|
} else {
|
||||||
|
// Arguments are stored on stack in reversed order.
|
||||||
|
// https://static.docs.arm.com/ihi0042/g/aapcs32.pdf
|
||||||
|
|
||||||
|
// Stack offset is not known yet. Store param info for later.
|
||||||
|
stack_args.push((param.value_type, param.extension));
|
||||||
|
next_stack += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.regs < self.reg_limit {
|
let extra_arg = if add_ret_area_ptr {
|
||||||
// Assign to a register.
|
debug_assert!(args_or_rets == ArgsOrRets::Args);
|
||||||
let reg = GPR.unit(10 + self.regs as usize);
|
if next_rreg < max_reg_val {
|
||||||
self.regs += 1;
|
ret.push(ABIArg::Reg(
|
||||||
ArgumentLoc::Reg(reg).into()
|
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 {
|
} else {
|
||||||
// Assign a stack location.
|
None
|
||||||
let loc = ArgumentLoc::Stack(self.offset as i32);
|
};
|
||||||
self.offset += u32::from(self.pointer_bytes);
|
|
||||||
debug_assert!(self.offset <= i32::MAX as u32);
|
// Now we can assign proper stack offsets to params.
|
||||||
loc.into()
|
let max_stack = next_stack;
|
||||||
|
for (ty, ext) in stack_args.into_iter().rev() {
|
||||||
|
next_stack -= 4;
|
||||||
|
ret.push(ABIArg::Stack((max_stack - next_stack) as i64, ty, ext));
|
||||||
|
}
|
||||||
|
assert_eq!(next_stack, 0);
|
||||||
|
|
||||||
|
next_stack = (next_stack + 7) & !7;
|
||||||
|
|
||||||
|
// To avoid overflow issues, limit the arg/return size to something
|
||||||
|
// reasonable -- here, 128 MB.
|
||||||
|
if next_stack > STACK_ARG_RET_SIZE_LIMIT {
|
||||||
|
return Err(CodegenError::ImplLimitExceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((ret, next_stack as i64, extra_arg))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fp_to_arg_offset(_call_conv: isa::CallConv, _flags: &settings::Flags) -> i64 {
|
||||||
|
8 // frame pointer and link register
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_load_stack(mem: StackAMode, into_reg: Writable<Reg>, ty: Type) -> Inst {
|
||||||
|
Inst::gen_load(into_reg, mem.into(), ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_store_stack(mem: StackAMode, from_reg: Reg, ty: Type) -> Inst {
|
||||||
|
Inst::gen_store(from_reg, mem.into(), ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Inst {
|
||||||
|
Inst::gen_move(to_reg, from_reg, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_extend(
|
||||||
|
to_reg: Writable<Reg>,
|
||||||
|
from_reg: Reg,
|
||||||
|
is_signed: bool,
|
||||||
|
from_bits: u8,
|
||||||
|
to_bits: u8,
|
||||||
|
) -> Inst {
|
||||||
|
assert!(to_bits == 32);
|
||||||
|
assert!(from_bits < 32);
|
||||||
|
Inst::Extend {
|
||||||
|
rd: to_reg,
|
||||||
|
rm: from_reg,
|
||||||
|
signed: is_signed,
|
||||||
|
from_bits,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Legalize `sig`.
|
fn gen_ret() -> Inst {
|
||||||
pub fn legalize_signature(sig: &mut Cow<ir::Signature>, triple: &Triple, _current: bool) {
|
Inst::Ret
|
||||||
let bits = triple.pointer_width().unwrap().bits();
|
|
||||||
|
|
||||||
let mut args = Args::new(bits);
|
|
||||||
if let Some(new_params) = legalize_args(&sig.params, &mut args) {
|
|
||||||
sig.to_mut().params = new_params;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Get register class for a type appearing in a legalized signature.
|
fn gen_epilogue_placeholder() -> Inst {
|
||||||
pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass {
|
Inst::EpiloguePlaceholder
|
||||||
if ty.is_int() {
|
}
|
||||||
GPR
|
|
||||||
} else {
|
fn gen_add_imm(into_reg: Writable<Reg>, from_reg: Reg, imm: u32) -> SmallVec<[Inst; 4]> {
|
||||||
match ty.bits() {
|
let mut insts = SmallVec::new();
|
||||||
32 => S,
|
|
||||||
64 => D,
|
if let Some(imm12) = UImm12::maybe_from_i64(imm as i64) {
|
||||||
128 => Q,
|
insts.push(Inst::AluRRImm12 {
|
||||||
_ => panic!("Unexpected {} ABI type for arm32", ty),
|
alu_op: ALUOp::Add,
|
||||||
|
rd: into_reg,
|
||||||
|
rn: from_reg,
|
||||||
|
imm12,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let scratch2 = writable_tmp2_reg();
|
||||||
|
insts.extend(Inst::load_constant(scratch2, imm));
|
||||||
|
insts.push(Inst::AluRRRShift {
|
||||||
|
alu_op: ALUOp::Add,
|
||||||
|
rd: into_reg,
|
||||||
|
rn: from_reg,
|
||||||
|
rm: scratch2.to_reg(),
|
||||||
|
shift: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
insts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_stack_lower_bound_trap(limit_reg: Reg) -> SmallVec<[Inst; 2]> {
|
||||||
|
let mut insts = SmallVec::new();
|
||||||
|
insts.push(Inst::Cmp {
|
||||||
|
rn: sp_reg(),
|
||||||
|
rm: limit_reg,
|
||||||
|
});
|
||||||
|
insts.push(Inst::TrapIf {
|
||||||
|
trap_info: (ir::SourceLoc::default(), ir::TrapCode::StackOverflow),
|
||||||
|
// Here `Lo` == "less than" when interpreting the two
|
||||||
|
// operands as unsigned integers.
|
||||||
|
cond: Cond::Lo,
|
||||||
|
});
|
||||||
|
insts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>, _ty: Type) -> Inst {
|
||||||
|
let mem = mem.into();
|
||||||
|
Inst::LoadAddr { rd: into_reg, mem }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_stacklimit_reg() -> Reg {
|
||||||
|
ip_reg()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_load_base_offset(into_reg: Writable<Reg>, base: Reg, offset: i32, ty: Type) -> Inst {
|
||||||
|
let mem = AMode::RegOffset(base, offset as i64);
|
||||||
|
Inst::gen_load(into_reg, mem, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Inst {
|
||||||
|
let mem = AMode::RegOffset(base, offset as i64);
|
||||||
|
Inst::gen_store(from_reg, mem, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_sp_reg_adjust(amount: i32) -> SmallVec<[Inst; 2]> {
|
||||||
|
let mut ret = SmallVec::new();
|
||||||
|
|
||||||
|
if amount == 0 {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
let (amount, is_sub) = if amount > 0 {
|
||||||
|
(amount, false)
|
||||||
|
} else {
|
||||||
|
(-amount, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
let alu_op = if is_sub { ALUOp::Sub } else { ALUOp::Add };
|
||||||
|
|
||||||
|
if let Some(imm12) = UImm12::maybe_from_i64(amount as i64) {
|
||||||
|
ret.push(Inst::AluRRImm12 {
|
||||||
|
alu_op,
|
||||||
|
rd: writable_sp_reg(),
|
||||||
|
rn: sp_reg(),
|
||||||
|
imm12,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let tmp = writable_ip_reg();
|
||||||
|
ret.extend(Inst::load_constant(tmp, amount as u32));
|
||||||
|
ret.push(Inst::AluRRRShift {
|
||||||
|
alu_op,
|
||||||
|
rd: writable_sp_reg(),
|
||||||
|
rn: sp_reg(),
|
||||||
|
rm: tmp.to_reg(),
|
||||||
|
shift: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_nominal_sp_adj(offset: i32) -> Inst {
|
||||||
|
let offset = i64::from(offset);
|
||||||
|
Inst::VirtualSPOffsetAdj { offset }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_prologue_frame_setup() -> SmallVec<[Inst; 2]> {
|
||||||
|
let mut ret = SmallVec::new();
|
||||||
|
let reg_list = vec![fp_reg(), lr_reg()];
|
||||||
|
ret.push(Inst::Push { reg_list });
|
||||||
|
ret.push(Inst::Mov {
|
||||||
|
rd: writable_fp_reg(),
|
||||||
|
rm: sp_reg(),
|
||||||
|
});
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_epilogue_frame_restore() -> SmallVec<[Inst; 2]> {
|
||||||
|
let mut ret = SmallVec::new();
|
||||||
|
ret.push(Inst::Mov {
|
||||||
|
rd: writable_sp_reg(),
|
||||||
|
rm: fp_reg(),
|
||||||
|
});
|
||||||
|
let reg_list = vec![writable_fp_reg(), writable_lr_reg()];
|
||||||
|
ret.push(Inst::Pop { reg_list });
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns stack bytes used as well as instructions. Does not adjust
|
||||||
|
/// nominal SP offset; caller will do that.
|
||||||
|
fn gen_clobber_save(
|
||||||
|
_call_conv: isa::CallConv,
|
||||||
|
clobbers: &Set<Writable<RealReg>>,
|
||||||
|
) -> (u64, SmallVec<[Inst; 16]>) {
|
||||||
|
let mut insts = SmallVec::new();
|
||||||
|
let clobbered_vec = get_callee_saves(clobbers);
|
||||||
|
let mut clobbered_vec: Vec<_> = clobbered_vec
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| r.to_reg().to_reg())
|
||||||
|
.collect();
|
||||||
|
if clobbered_vec.len() % 2 == 1 {
|
||||||
|
// For alignment purposes.
|
||||||
|
clobbered_vec.push(ip_reg());
|
||||||
|
}
|
||||||
|
let stack_used = clobbered_vec.len() * 4;
|
||||||
|
if !clobbered_vec.is_empty() {
|
||||||
|
insts.push(Inst::Push {
|
||||||
|
reg_list: clobbered_vec,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(stack_used as u64, insts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_clobber_restore(
|
||||||
|
_call_conv: isa::CallConv,
|
||||||
|
clobbers: &Set<Writable<RealReg>>,
|
||||||
|
) -> SmallVec<[Inst; 16]> {
|
||||||
|
let mut insts = SmallVec::new();
|
||||||
|
let clobbered_vec = get_callee_saves(clobbers);
|
||||||
|
let mut clobbered_vec: Vec<_> = clobbered_vec
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| Writable::from_reg(r.to_reg().to_reg()))
|
||||||
|
.collect();
|
||||||
|
if clobbered_vec.len() % 2 == 1 {
|
||||||
|
clobbered_vec.push(writable_ip_reg());
|
||||||
|
}
|
||||||
|
if !clobbered_vec.is_empty() {
|
||||||
|
insts.push(Inst::Pop {
|
||||||
|
reg_list: clobbered_vec,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
insts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_call(
|
||||||
|
dest: &CallDest,
|
||||||
|
uses: Vec<Reg>,
|
||||||
|
defs: Vec<Writable<Reg>>,
|
||||||
|
loc: SourceLoc,
|
||||||
|
opcode: ir::Opcode,
|
||||||
|
tmp: Writable<Reg>,
|
||||||
|
) -> SmallVec<[(InstIsSafepoint, Inst); 2]> {
|
||||||
|
let mut insts = SmallVec::new();
|
||||||
|
match &dest {
|
||||||
|
&CallDest::ExtName(ref name, RelocDistance::Near) => insts.push((
|
||||||
|
InstIsSafepoint::Yes,
|
||||||
|
Inst::Call {
|
||||||
|
info: Box::new(CallInfo {
|
||||||
|
dest: name.clone(),
|
||||||
|
uses,
|
||||||
|
defs,
|
||||||
|
loc,
|
||||||
|
opcode,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
&CallDest::ExtName(ref name, RelocDistance::Far) => {
|
||||||
|
insts.push((
|
||||||
|
InstIsSafepoint::No,
|
||||||
|
Inst::LoadExtName {
|
||||||
|
rt: tmp,
|
||||||
|
name: Box::new(name.clone()),
|
||||||
|
offset: 0,
|
||||||
|
srcloc: loc,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
insts.push((
|
||||||
|
InstIsSafepoint::Yes,
|
||||||
|
Inst::CallInd {
|
||||||
|
info: Box::new(CallIndInfo {
|
||||||
|
rm: tmp.to_reg(),
|
||||||
|
uses,
|
||||||
|
defs,
|
||||||
|
loc,
|
||||||
|
opcode,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
&CallDest::Reg(reg) => insts.push((
|
||||||
|
InstIsSafepoint::Yes,
|
||||||
|
Inst::CallInd {
|
||||||
|
info: Box::new(CallIndInfo {
|
||||||
|
rm: *reg,
|
||||||
|
uses,
|
||||||
|
defs,
|
||||||
|
loc,
|
||||||
|
opcode,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
|
||||||
|
insts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_number_of_spillslots_for_value(rc: RegClass, _ty: Type) -> u32 {
|
||||||
|
match rc {
|
||||||
|
RegClass::I32 => 1,
|
||||||
|
_ => panic!("Unexpected register class!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_virtual_sp_offset_from_state(s: &EmitState) -> i64 {
|
||||||
|
s.virtual_sp_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_nominal_sp_to_fp(s: &EmitState) -> i64 {
|
||||||
|
s.nominal_sp_to_fp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_caller_saves(_call_conv: isa::CallConv) -> Vec<Writable<Reg>> {
|
||||||
|
let mut caller_saved = Vec::new();
|
||||||
|
for i in 0..15 {
|
||||||
|
let r = writable_rreg(i);
|
||||||
|
if is_caller_save(r.to_reg().to_real_reg()) {
|
||||||
|
caller_saved.push(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
caller_saved
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the set of allocatable registers for `func`.
|
fn is_callee_save(r: RealReg) -> bool {
|
||||||
pub fn allocatable_registers(_func: &ir::Function) -> RegisterSet {
|
let enc = r.get_hw_encoding();
|
||||||
unimplemented!()
|
4 <= enc && enc <= 10
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_callee_saves(regs: &Set<Writable<RealReg>>) -> Vec<Writable<RealReg>> {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
for ® in regs.iter() {
|
||||||
|
if is_callee_save(reg.to_reg()) {
|
||||||
|
ret.push(reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort registers for deterministic code output.
|
||||||
|
ret.sort_by_key(|r| r.to_reg().get_index());
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_caller_save(r: RealReg) -> bool {
|
||||||
|
let enc = r.get_hw_encoding();
|
||||||
|
enc <= 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"));
|
|
||||||
@@ -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"));
|
|
||||||
335
cranelift/codegen/src/isa/arm32/inst/args.rs
Normal file
335
cranelift/codegen/src/isa/arm32/inst/args.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
801
cranelift/codegen/src/isa/arm32/inst/emit.rs
Normal file
801
cranelift/codegen/src/isa/arm32/inst/emit.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
2001
cranelift/codegen/src/isa/arm32/inst/emit_tests.rs
Normal file
2001
cranelift/codegen/src/isa/arm32/inst/emit_tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
1365
cranelift/codegen/src/isa/arm32/inst/mod.rs
Normal file
1365
cranelift/codegen/src/isa/arm32/inst/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
128
cranelift/codegen/src/isa/arm32/inst/regs.rs
Normal file
128
cranelift/codegen/src/isa/arm32/inst/regs.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
240
cranelift/codegen/src/isa/arm32/lower.rs
Normal file
240
cranelift/codegen/src/isa/arm32/lower.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
623
cranelift/codegen/src/isa/arm32/lower_inst.rs
Normal file
623
cranelift/codegen/src/isa/arm32/lower_inst.rs
Normal 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(())
|
||||||
|
}
|
||||||
@@ -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 alloc::boxed::Box;
|
use crate::result::CodegenResult;
|
||||||
use core::any::Any;
|
use crate::settings;
|
||||||
use core::fmt;
|
|
||||||
use target_lexicon::{Architecture, Triple};
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
use alloc::boxed::Box;
|
||||||
struct Isa {
|
use regalloc::RealRegUniverse;
|
||||||
|
use target_lexicon::{Architecture, ArmArchitecture, Triple};
|
||||||
|
|
||||||
|
// New backend:
|
||||||
|
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 {
|
||||||
triple,
|
let reg_universe = create_reg_universe();
|
||||||
setup: settings::builder(),
|
Arm32Backend {
|
||||||
constructor: isa_constructor,
|
triple,
|
||||||
|
flags,
|
||||||
|
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();
|
||||||
} else {
|
let frame_size = vcode.frame_size();
|
||||||
&enc_tables::LEVEL1_A32[..]
|
|
||||||
}
|
let disasm = if want_disasm {
|
||||||
}
|
Some(vcode.show_rru(Some(&create_reg_universe())))
|
||||||
_ => panic!(),
|
} else {
|
||||||
};
|
None
|
||||||
Box::new(Isa {
|
};
|
||||||
triple,
|
|
||||||
isa_flags: settings::Flags::new(&shared_flags, builder),
|
let buffer = buffer.finish();
|
||||||
shared_flags,
|
|
||||||
cpumode: level1,
|
Ok(MachCompileResult {
|
||||||
})
|
buffer,
|
||||||
}
|
frame_size,
|
||||||
|
disasm,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl TargetIsa for Isa {
|
|
||||||
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))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"));
|
|
||||||
@@ -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],
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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(®info, "GPR");
|
||||||
let gpr = rc_by_name(isa, "GPR");
|
let s = rc_by_name(®info, "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(®info, ®s);
|
let mut pressure = Pressure::new(®info, ®s);
|
||||||
@@ -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(®info, "S");
|
||||||
let s = rc_by_name(isa, "S");
|
let d = rc_by_name(®info, "D");
|
||||||
let d = rc_by_name(isa, "D");
|
let q = rc_by_name(®info, "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(®info, ®s);
|
let mut pressure = Pressure::new(®info, ®s);
|
||||||
|
|||||||
@@ -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(®info, "GPR");
|
let gpr = rc_by_name(®info, "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(®info, "S");
|
let s = rc_by_name(®info, "S");
|
||||||
let d = rc_by_name(®info, "D");
|
let d = rc_by_name(®info, "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(®info, "GPR");
|
let gpr = rc_by_name(®info, "GPR");
|
||||||
let r0 = gpr.unit(0);
|
let r0 = gpr.unit(0);
|
||||||
let r1 = gpr.unit(1);
|
let r1 = gpr.unit(1);
|
||||||
|
|||||||
269
cranelift/filetests/filetests/vcode/arm32/aluops.clif
Normal file
269
cranelift/filetests/filetests/vcode/arm32/aluops.clif
Normal 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
|
||||||
132
cranelift/filetests/filetests/vcode/arm32/bitops.clif
Normal file
132
cranelift/filetests/filetests/vcode/arm32/bitops.clif
Normal 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
|
||||||
60
cranelift/filetests/filetests/vcode/arm32/cond.clif
Normal file
60
cranelift/filetests/filetests/vcode/arm32/cond.clif
Normal 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
|
||||||
107
cranelift/filetests/filetests/vcode/arm32/constants.clif
Normal file
107
cranelift/filetests/filetests/vcode/arm32/constants.clif
Normal 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
|
||||||
131
cranelift/filetests/filetests/vcode/arm32/control-flow.clif
Normal file
131
cranelift/filetests/filetests/vcode/arm32/control-flow.clif
Normal 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
|
||||||
|
|
||||||
80
cranelift/filetests/filetests/vcode/arm32/extend.clif
Normal file
80
cranelift/filetests/filetests/vcode/arm32/extend.clif
Normal 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
|
||||||
47
cranelift/filetests/filetests/vcode/arm32/params.clif
Normal file
47
cranelift/filetests/filetests/vcode/arm32/params.clif
Normal 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
|
||||||
157
cranelift/filetests/filetests/vcode/arm32/shift-rotate.clif
Normal file
157
cranelift/filetests/filetests/vcode/arm32/shift-rotate.clif
Normal 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
|
||||||
Reference in New Issue
Block a user