diff --git a/cranelift/codegen/src/isa/aarch64/abi.rs b/cranelift/codegen/src/isa/aarch64/abi.rs index a5b6942ace..bae50904da 100644 --- a/cranelift/codegen/src/isa/aarch64/abi.rs +++ b/cranelift/codegen/src/isa/aarch64/abi.rs @@ -109,6 +109,10 @@ pub(crate) struct AArch64MachineDeps; impl ABIMachineSpec for AArch64MachineDeps { type I = Inst; + fn word_bits() -> u32 { + 64 + } + fn compute_arg_locs( call_conv: isa::CallConv, params: &[ir::AbiParam], diff --git a/cranelift/codegen/src/isa/arm32/abi.rs b/cranelift/codegen/src/isa/arm32/abi.rs index 85dc8d8f43..d50eafb719 100644 --- a/cranelift/codegen/src/isa/arm32/abi.rs +++ b/cranelift/codegen/src/isa/arm32/abi.rs @@ -1,108 +1,450 @@ -//! ARM ABI implementation. -//! This is from the RISC-V target and will need to be updated for ARM32. +//! Implementation of the 32-bit ARM ABI. -use super::registers::{D, GPR, Q, S}; -use crate::abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion}; -use crate::ir::{self, AbiParam, ArgumentExtension, ArgumentLoc, Type}; -use crate::isa::RegClass; -use crate::regalloc::RegisterSet; -use alloc::borrow::Cow; -use core::i32; -use target_lexicon::Triple; +use crate::ir; +use crate::ir::types::*; +use crate::ir::SourceLoc; +use crate::isa; +use crate::isa::arm32::inst::*; +use crate::machinst::*; +use crate::settings; +use crate::{CodegenError, CodegenResult}; +use alloc::boxed::Box; +use alloc::vec::Vec; +use regalloc::{RealReg, Reg, RegClass, Set, Writable}; +use smallvec::SmallVec; -struct Args { - pointer_bits: u8, - pointer_bytes: u8, - pointer_type: Type, - regs: u32, - reg_limit: u32, - offset: u32, -} +/// Support for the ARM ABI from the callee side (within a function body). +pub(crate) type Arm32ABICallee = ABICalleeImpl; -impl Args { - fn new(bits: u8) -> Self { - Self { - pointer_bits: bits, - pointer_bytes: bits / 8, - pointer_type: Type::int(u16::from(bits)).unwrap(), - regs: 0, - reg_limit: 8, - offset: 0, +/// Support for the ARM ABI from the caller side (at a callsite). +pub(crate) type Arm32ABICaller = ABICallerImpl; + +/// This is the limit for the size of argument and return-value areas on the +/// stack. We place a reasonable limit here to avoid integer overflow issues +/// with 32-bit arithmetic: for now, 128 MB. +static STACK_ARG_RET_SIZE_LIMIT: u64 = 128 * 1024 * 1024; + +/// ARM-specific ABI behavior. This struct just serves as an implementation +/// point for the trait; it is never actually instantiated. +pub(crate) struct Arm32MachineDeps; + +impl Into for StackAMode { + fn into(self) -> AMode { + match self { + StackAMode::FPOffset(off, ty) => AMode::FPOffset(off, ty), + StackAMode::NominalSPOffset(off, ty) => AMode::NominalSPOffset(off, ty), + StackAMode::SPOffset(off, ty) => AMode::SPOffset(off, ty), } } } -impl ArgAssigner for Args { - fn assign(&mut self, arg: &AbiParam) -> ArgAction { - fn align(value: u32, to: u32) -> u32 { - (value + to - 1) & !(to - 1) - } +impl ABIMachineSpec for Arm32MachineDeps { + type I = Inst; - let ty = arg.value_type; + fn word_bits() -> u32 { + 32 + } - // Check for a legal type. - // SIMD instructions are currently no implemented, so break down vectors - if ty.is_vector() { - return ValueConversion::VectorSplit.into(); - } + fn compute_arg_locs( + _call_conv: isa::CallConv, + params: &[ir::AbiParam], + args_or_rets: ArgsOrRets, + add_ret_area_ptr: bool, + ) -> CodegenResult<(Vec, i64, Option)> { + let mut next_rreg = 0; + let mut next_stack: u64 = 0; + let mut ret = vec![]; + let mut stack_args = vec![]; - // Large integers and booleans are broken down to fit in a register. - if !ty.is_float() && ty.bits() > u16::from(self.pointer_bits) { - // Align registers and stack to a multiple of two pointers. - self.regs = align(self.regs, 2); - self.offset = align(self.offset, 2 * u32::from(self.pointer_bytes)); - return ValueConversion::IntSplit.into(); - } + let max_reg_val = 4; // r0-r3 - // Small integers are extended to the size of a pointer register. - if ty.is_int() && ty.bits() < u16::from(self.pointer_bits) { - match arg.extension { - ArgumentExtension::None => {} - ArgumentExtension::Uext => return ValueConversion::Uext(self.pointer_type).into(), - ArgumentExtension::Sext => return ValueConversion::Sext(self.pointer_type).into(), + for i in 0..params.len() { + let param = params[i]; + + // Validate "purpose". + match ¶m.purpose { + &ir::ArgumentPurpose::VMContext + | &ir::ArgumentPurpose::Normal + | &ir::ArgumentPurpose::StackLimit + | &ir::ArgumentPurpose::SignatureId => {} + _ => panic!( + "Unsupported argument purpose {:?} in signature: {:?}", + param.purpose, params + ), + } + assert!(param.value_type.bits() <= 32); + + if next_rreg < max_reg_val { + let reg = rreg(next_rreg); + + ret.push(ABIArg::Reg( + reg.to_real_reg(), + param.value_type, + param.extension, + )); + next_rreg += 1; + } else { + // Arguments are stored on stack in reversed order. + // https://static.docs.arm.com/ihi0042/g/aapcs32.pdf + + // Stack offset is not known yet. Store param info for later. + stack_args.push((param.value_type, param.extension)); + next_stack += 4; } } - if self.regs < self.reg_limit { - // Assign to a register. - let reg = GPR.unit(10 + self.regs as usize); - self.regs += 1; - ArgumentLoc::Reg(reg).into() + let extra_arg = if add_ret_area_ptr { + debug_assert!(args_or_rets == ArgsOrRets::Args); + if next_rreg < max_reg_val { + ret.push(ABIArg::Reg( + rreg(next_rreg).to_real_reg(), + I32, + ir::ArgumentExtension::None, + )); + } else { + stack_args.push((I32, ir::ArgumentExtension::None)); + next_stack += 4; + } + Some(ret.len() - 1) } else { - // Assign a stack location. - let loc = ArgumentLoc::Stack(self.offset as i32); - self.offset += u32::from(self.pointer_bytes); - debug_assert!(self.offset <= i32::MAX as u32); - loc.into() + None + }; + + // Now we can assign proper stack offsets to params. + let max_stack = next_stack; + for (ty, ext) in stack_args.into_iter().rev() { + next_stack -= 4; + ret.push(ABIArg::Stack((max_stack - next_stack) as i64, ty, ext)); + } + assert_eq!(next_stack, 0); + + next_stack = (next_stack + 7) & !7; + + // To avoid overflow issues, limit the arg/return size to something + // reasonable -- here, 128 MB. + if next_stack > STACK_ARG_RET_SIZE_LIMIT { + return Err(CodegenError::ImplLimitExceeded); + } + + Ok((ret, next_stack as i64, extra_arg)) + } + + fn fp_to_arg_offset(_call_conv: isa::CallConv, _flags: &settings::Flags) -> i64 { + 8 // frame pointer and link register + } + + fn gen_load_stack(mem: StackAMode, into_reg: Writable, 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, from_reg: Reg, ty: Type) -> Inst { + Inst::gen_move(to_reg, from_reg, ty) + } + + fn gen_extend( + to_reg: Writable, + from_reg: Reg, + is_signed: bool, + from_bits: u8, + to_bits: u8, + ) -> Inst { + assert!(to_bits == 32); + assert!(from_bits < 32); + Inst::Extend { + rd: to_reg, + rm: from_reg, + signed: is_signed, + from_bits, } } -} -/// Legalize `sig`. -pub fn legalize_signature(sig: &mut Cow, triple: &Triple, _current: bool) { - let bits = triple.pointer_width().unwrap().bits(); - - let mut args = Args::new(bits); - if let Some(new_params) = legalize_args(&sig.params, &mut args) { - sig.to_mut().params = new_params; + fn gen_ret() -> Inst { + Inst::Ret } -} -/// Get register class for a type appearing in a legalized signature. -pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass { - if ty.is_int() { - GPR - } else { - match ty.bits() { - 32 => S, - 64 => D, - 128 => Q, - _ => panic!("Unexpected {} ABI type for arm32", ty), + fn gen_epilogue_placeholder() -> Inst { + Inst::EpiloguePlaceholder + } + + fn gen_add_imm(into_reg: Writable, from_reg: Reg, imm: u32) -> SmallVec<[Inst; 4]> { + let mut insts = SmallVec::new(); + + if let Some(imm12) = UImm12::maybe_from_i64(imm as i64) { + insts.push(Inst::AluRRImm12 { + alu_op: ALUOp::Add, + rd: into_reg, + rn: from_reg, + imm12, + }); + } else { + let scratch2 = writable_tmp2_reg(); + insts.extend(Inst::load_constant(scratch2, imm)); + insts.push(Inst::AluRRRShift { + alu_op: ALUOp::Add, + rd: into_reg, + rn: from_reg, + rm: scratch2.to_reg(), + shift: None, + }); + } + insts + } + + fn gen_stack_lower_bound_trap(limit_reg: Reg) -> SmallVec<[Inst; 2]> { + let mut insts = SmallVec::new(); + insts.push(Inst::Cmp { + rn: sp_reg(), + rm: limit_reg, + }); + insts.push(Inst::TrapIf { + trap_info: (ir::SourceLoc::default(), ir::TrapCode::StackOverflow), + // Here `Lo` == "less than" when interpreting the two + // operands as unsigned integers. + cond: Cond::Lo, + }); + insts + } + + fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable, _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, 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>, + ) -> (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>, + ) -> 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, + defs: Vec>, + loc: SourceLoc, + opcode: ir::Opcode, + tmp: Writable, + ) -> 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> { + let mut caller_saved = Vec::new(); + for i in 0..15 { + let r = writable_rreg(i); + if is_caller_save(r.to_reg().to_real_reg()) { + caller_saved.push(r); + } + } + caller_saved + } } -/// Get the set of allocatable registers for `func`. -pub fn allocatable_registers(_func: &ir::Function) -> RegisterSet { - unimplemented!() +fn is_callee_save(r: RealReg) -> bool { + let enc = r.get_hw_encoding(); + 4 <= enc && enc <= 10 +} + +fn get_callee_saves(regs: &Set>) -> Vec> { + 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 } diff --git a/cranelift/codegen/src/isa/arm32/binemit.rs b/cranelift/codegen/src/isa/arm32/binemit.rs deleted file mode 100644 index d74ee0911a..0000000000 --- a/cranelift/codegen/src/isa/arm32/binemit.rs +++ /dev/null @@ -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")); diff --git a/cranelift/codegen/src/isa/arm32/enc_tables.rs b/cranelift/codegen/src/isa/arm32/enc_tables.rs deleted file mode 100644 index 5cb4e5c4c0..0000000000 --- a/cranelift/codegen/src/isa/arm32/enc_tables.rs +++ /dev/null @@ -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")); diff --git a/cranelift/codegen/src/isa/arm32/inst/args.rs b/cranelift/codegen/src/isa/arm32/inst/args.rs new file mode 100644 index 0000000000..252581a2a1 --- /dev/null +++ b/cranelift/codegen/src/isa/arm32/inst/args.rs @@ -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 { + 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 { + 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 { + 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 { + 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), + } + } +} diff --git a/cranelift/codegen/src/isa/arm32/inst/emit.rs b/cranelift/codegen/src/isa/arm32/inst/emit.rs new file mode 100644 index 0000000000..e2fbe6679d --- /dev/null +++ b/cranelift/codegen/src/isa/arm32/inst/emit.rs @@ -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, rm: Reg) -> u16 { + enc_16_rr_any(0b01000110, rd.to_reg(), rm) +} + +fn enc_16_it(cond: Cond, insts: &Vec) -> 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_8: Option, + reg_12: Option, + reg_16: Option, +) -> 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) -> 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) { + 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, +} + +impl MachInstEmitState for EmitState { + fn new(abi: &dyn ABICallee) -> 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 { + 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, 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) + } +} diff --git a/cranelift/codegen/src/isa/arm32/inst/emit_tests.rs b/cranelift/codegen/src/isa/arm32/inst/emit_tests.rs new file mode 100644 index 0000000000..e14a58fee0 --- /dev/null +++ b/cranelift/codegen/src/isa/arm32/inst/emit_tests.rs @@ -0,0 +1,2001 @@ +use crate::isa::arm32::inst::*; +use crate::isa::test_utils; +use crate::settings; + +use alloc::vec::Vec; + +#[test] +fn test_arm32_emit() { + let flags = settings::Flags::new(settings::builder()); + let mut insns = Vec::<(Inst, &str, &str)>::new(); + + // litle endian order + insns.push((Inst::Nop0, "", "nop-zero-len")); + insns.push((Inst::Nop2, "00BF", "nop")); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Lsl, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + }, + "01FA02F0", + "lsl r0, r1, r2", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Lsl, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + }, + "09FA0AF8", + "lsl r8, r9, r10", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Lsr, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + }, + "21FA02F0", + "lsr r0, r1, r2", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Lsr, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + }, + "29FA0AF8", + "lsr r8, r9, r10", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Asr, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + }, + "41FA02F0", + "asr r0, r1, r2", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Asr, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + }, + "49FA0AF8", + "asr r8, r9, r10", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Ror, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + }, + "61FA02F0", + "ror r0, r1, r2", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Ror, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + }, + "69FA0AF8", + "ror r8, r9, r10", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Qadd, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + }, + "81FA82F0", + "qadd r0, r1, r2", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Qadd, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + }, + "89FA8AF8", + "qadd r8, r9, r10", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Qsub, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + }, + "81FAA2F0", + "qsub r0, r1, r2", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Qsub, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + }, + "89FAAAF8", + "qsub r8, r9, r10", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Mul, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + }, + "01FB02F0", + "mul r0, r1, r2", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Mul, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + }, + "09FB0AF8", + "mul r8, r9, r10", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Udiv, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + }, + "B1FBF2F0", + "udiv r0, r1, r2", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Udiv, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + }, + "B9FBFAF8", + "udiv r8, r9, r10", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Sdiv, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + }, + "91FBF2F0", + "sdiv r0, r1, r2", + )); + insns.push(( + Inst::AluRRR { + alu_op: ALUOp::Sdiv, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + }, + "99FBFAF8", + "sdiv r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::And, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "01EAC250", + "and r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::And, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "09EA0A08", + "and r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Bic, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "21EAC250", + "bic r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Bic, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "29EA0A08", + "bic r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Orr, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "41EAC250", + "orr r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Orr, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "49EA0A08", + "orr r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Orn, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "61EAC250", + "orn r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Orn, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "69EA0A08", + "orn r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Eor, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "81EAC250", + "eor r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Eor, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "89EA0A08", + "eor r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Add, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "01EBC250", + "add r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Add, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "09EB0A08", + "add r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Adds, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "11EBC250", + "adds r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Adds, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "19EB0A08", + "adds r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Adc, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "41EBC250", + "adc r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Adc, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "49EB0A08", + "adc r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Adcs, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "51EBC250", + "adcs r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Adcs, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "59EB0A08", + "adcs r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Sbc, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "61EBC250", + "sbc r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Sbc, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "69EB0A08", + "sbc r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Sbcs, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "71EBC250", + "sbcs r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Sbcs, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "79EB0A08", + "sbcs r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Sub, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "A1EBC250", + "sub r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Sub, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "A9EB0A08", + "sub r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Subs, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "B1EBC250", + "subs r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Subs, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "B9EB0A08", + "subs r8, r9, r10", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Rsb, + rd: writable_rreg(0), + rn: rreg(1), + rm: rreg(2), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(23).unwrap(), + )), + }, + "C1EBC250", + "rsb r0, r1, r2, lsl #23", + )); + insns.push(( + Inst::AluRRRShift { + alu_op: ALUOp::Rsb, + rd: writable_rreg(8), + rn: rreg(9), + rm: rreg(10), + shift: None, + }, + "C9EB0A08", + "rsb r8, r9, r10", + )); + insns.push(( + Inst::AluRRShift { + alu_op: ALUOp1::Mvn, + rd: writable_rreg(0), + rm: rreg(1), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(11).unwrap(), + )), + }, + "6FEAC120", + "mvn r0, r1, lsl #11", + )); + insns.push(( + Inst::AluRRShift { + alu_op: ALUOp1::Mvn, + rd: writable_rreg(8), + rm: rreg(9), + shift: None, + }, + "6FEA0908", + "mvn r8, r9", + )); + insns.push(( + Inst::AluRRShift { + alu_op: ALUOp1::Mov, + rd: writable_rreg(0), + rm: rreg(1), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSL, + ShiftOpShiftImm::maybe_from_shift(11).unwrap(), + )), + }, + "4FEAC120", + "mov r0, r1, lsl #11", + )); + insns.push(( + Inst::AluRRShift { + alu_op: ALUOp1::Mov, + rd: writable_rreg(2), + rm: rreg(8), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::LSR, + ShiftOpShiftImm::maybe_from_shift(27).unwrap(), + )), + }, + "4FEAD862", + "mov r2, r8, lsr #27", + )); + insns.push(( + Inst::AluRRShift { + alu_op: ALUOp1::Mov, + rd: writable_rreg(9), + rm: rreg(3), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::ASR, + ShiftOpShiftImm::maybe_from_shift(3).unwrap(), + )), + }, + "4FEAE309", + "mov r9, r3, asr #3", + )); + insns.push(( + Inst::AluRRShift { + alu_op: ALUOp1::Mov, + rd: writable_rreg(10), + rm: rreg(11), + shift: Some(ShiftOpAndAmt::new( + ShiftOp::ROR, + ShiftOpShiftImm::maybe_from_shift(7).unwrap(), + )), + }, + "4FEAFB1A", + "mov r10, fp, ror #7", + )); + insns.push(( + Inst::AluRRRR { + alu_op: ALUOp::Smull, + rd_lo: writable_rreg(0), + rd_hi: writable_rreg(1), + rn: rreg(2), + rm: rreg(3), + }, + "82FB0301", + "smull r0, r1, r2, r3", + )); + insns.push(( + Inst::AluRRRR { + alu_op: ALUOp::Smull, + rd_lo: writable_rreg(8), + rd_hi: writable_rreg(9), + rn: rreg(10), + rm: rreg(11), + }, + "8AFB0B89", + "smull r8, r9, r10, fp", + )); + insns.push(( + Inst::AluRRRR { + alu_op: ALUOp::Umull, + rd_lo: writable_rreg(0), + rd_hi: writable_rreg(1), + rn: rreg(2), + rm: rreg(3), + }, + "A2FB0301", + "umull r0, r1, r2, r3", + )); + insns.push(( + Inst::AluRRRR { + alu_op: ALUOp::Umull, + rd_lo: writable_rreg(8), + rd_hi: writable_rreg(9), + rn: rreg(10), + rm: rreg(11), + }, + "AAFB0B89", + "umull r8, r9, r10, fp", + )); + insns.push(( + Inst::AluRRImm12 { + alu_op: ALUOp::Add, + rd: writable_rreg(0), + rn: rreg(1), + imm12: UImm12::maybe_from_i64(4095).unwrap(), + }, + "01F6FF70", + "add r0, r1, #4095", + )); + insns.push(( + Inst::AluRRImm12 { + alu_op: ALUOp::Add, + rd: writable_rreg(8), + rn: rreg(9), + imm12: UImm12::maybe_from_i64(0).unwrap(), + }, + "09F20008", + "add r8, r9, #0", + )); + insns.push(( + Inst::AluRRImm12 { + alu_op: ALUOp::Sub, + rd: writable_rreg(0), + rn: rreg(1), + imm12: UImm12::maybe_from_i64(1999).unwrap(), + }, + "A1F2CF70", + "sub r0, r1, #1999", + )); + insns.push(( + Inst::AluRRImm12 { + alu_op: ALUOp::Sub, + rd: writable_rreg(8), + rn: rreg(9), + imm12: UImm12::maybe_from_i64(101).unwrap(), + }, + "A9F26508", + "sub r8, r9, #101", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::And, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "01F0FF00", + "and r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::And, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "09F00108", + "and r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Bic, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "21F0FF00", + "bic r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Bic, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "29F00108", + "bic r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Orr, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "41F0FF00", + "orr r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Orr, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "49F00108", + "orr r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Orn, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "61F0FF00", + "orn r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Orn, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "69F00108", + "orn r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Eor, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "81F0FF00", + "eor r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Eor, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "89F00108", + "eor r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Add, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "01F1FF00", + "add r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Add, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "09F10108", + "add r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Adds, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "11F1FF00", + "adds r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Adds, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "19F10108", + "adds r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Adc, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "41F1FF00", + "adc r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Adc, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "49F10108", + "adc r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Adcs, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "51F1FF00", + "adcs r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Adcs, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "59F10108", + "adcs r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Sbc, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "61F1FF00", + "sbc r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Sbc, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "69F10108", + "sbc r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Sbcs, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "71F1FF00", + "sbcs r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Sbcs, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "79F10108", + "sbcs r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Sub, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "A1F1FF00", + "sub r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Sub, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "A9F10108", + "sub r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Subs, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "B1F1FF00", + "subs r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Subs, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "B9F10108", + "subs r8, r9, #1", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Rsb, + rd: writable_rreg(0), + rn: rreg(1), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "C1F1FF00", + "rsb r0, r1, #255", + )); + insns.push(( + Inst::AluRRImm8 { + alu_op: ALUOp::Rsb, + rd: writable_rreg(8), + rn: rreg(9), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "C9F10108", + "rsb r8, r9, #1", + )); + insns.push(( + Inst::AluRImm8 { + alu_op: ALUOp1::Mvn, + rd: writable_rreg(0), + imm8: UImm8::maybe_from_i64(255).unwrap(), + }, + "6FF0FF00", + "mvn r0, #255", + )); + insns.push(( + Inst::AluRImm8 { + alu_op: ALUOp1::Mvn, + rd: writable_rreg(8), + imm8: UImm8::maybe_from_i64(1).unwrap(), + }, + "6FF00108", + "mvn r8, #1", + )); + insns.push(( + Inst::AluRImm8 { + alu_op: ALUOp1::Mov, + rd: writable_rreg(0), + imm8: UImm8::maybe_from_i64(0).unwrap(), + }, + "4FF00000", + "mov r0, #0", + )); + insns.push(( + Inst::AluRImm8 { + alu_op: ALUOp1::Mov, + rd: writable_rreg(8), + imm8: UImm8::maybe_from_i64(176).unwrap(), + }, + "4FF0B008", + "mov r8, #176", + )); + insns.push(( + Inst::BitOpRR { + bit_op: BitOp::Rbit, + rd: writable_rreg(0), + rm: rreg(1), + }, + "91FAA1F0", + "rbit r0, r1", + )); + insns.push(( + Inst::BitOpRR { + bit_op: BitOp::Rbit, + rd: writable_rreg(8), + rm: rreg(9), + }, + "99FAA9F8", + "rbit r8, r9", + )); + insns.push(( + Inst::BitOpRR { + bit_op: BitOp::Rev, + rd: writable_rreg(0), + rm: rreg(1), + }, + "91FA81F0", + "rev r0, r1", + )); + insns.push(( + Inst::BitOpRR { + bit_op: BitOp::Rev, + rd: writable_rreg(8), + rm: rreg(9), + }, + "99FA89F8", + "rev r8, r9", + )); + insns.push(( + Inst::BitOpRR { + bit_op: BitOp::Clz, + rd: writable_rreg(0), + rm: rreg(1), + }, + "B1FA81F0", + "clz r0, r1", + )); + insns.push(( + Inst::BitOpRR { + bit_op: BitOp::Clz, + rd: writable_rreg(8), + rm: rreg(9), + }, + "B9FA89F8", + "clz r8, r9", + )); + insns.push(( + Inst::Mov { + rd: writable_rreg(0), + rm: rreg(1), + }, + "0846", + "mov r0, r1", + )); + insns.push(( + Inst::Mov { + rd: writable_rreg(2), + rm: rreg(8), + }, + "4246", + "mov r2, r8", + )); + insns.push(( + Inst::Mov { + rd: writable_rreg(9), + rm: rreg(3), + }, + "9946", + "mov r9, r3", + )); + insns.push(( + Inst::Mov { + rd: writable_rreg(10), + rm: rreg(11), + }, + "DA46", + "mov r10, fp", + )); + insns.push(( + Inst::MovImm16 { + rd: writable_rreg(0), + imm16: 0, + }, + "40F20000", + "mov r0, #0", + )); + insns.push(( + Inst::MovImm16 { + rd: writable_rreg(1), + imm16: 15, + }, + "40F20F01", + "mov r1, #15", + )); + insns.push(( + Inst::MovImm16 { + rd: writable_rreg(2), + imm16: 255, + }, + "40F2FF02", + "mov r2, #255", + )); + insns.push(( + Inst::MovImm16 { + rd: writable_rreg(8), + imm16: 4095, + }, + "40F6FF78", + "mov r8, #4095", + )); + insns.push(( + Inst::MovImm16 { + rd: writable_rreg(9), + imm16: 65535, + }, + "4FF6FF79", + "mov r9, #65535", + )); + insns.push(( + Inst::Movt { + rd: writable_rreg(0), + imm16: 0, + }, + "C0F20000", + "movt r0, #0", + )); + insns.push(( + Inst::Movt { + rd: writable_rreg(1), + imm16: 15, + }, + "C0F20F01", + "movt r1, #15", + )); + insns.push(( + Inst::Movt { + rd: writable_rreg(2), + imm16: 255, + }, + "C0F2FF02", + "movt r2, #255", + )); + insns.push(( + Inst::Movt { + rd: writable_rreg(8), + imm16: 4095, + }, + "C0F6FF78", + "movt r8, #4095", + )); + insns.push(( + Inst::Movt { + rd: writable_rreg(9), + imm16: 65535, + }, + "CFF6FF79", + "movt r9, #65535", + )); + insns.push(( + Inst::Cmp { + rn: rreg(0), + rm: rreg(1), + }, + "8842", + "cmp r0, r1", + )); + insns.push(( + Inst::Cmp { + rn: rreg(2), + rm: rreg(8), + }, + "4245", + "cmp r2, r8", + )); + insns.push(( + Inst::Cmp { + rn: rreg(9), + rm: rreg(3), + }, + "9945", + "cmp r9, r3", + )); + insns.push(( + Inst::Cmp { + rn: rreg(10), + rm: rreg(11), + }, + "DA45", + "cmp r10, fp", + )); + insns.push(( + Inst::CmpImm8 { + rn: rreg(0), + imm8: 255, + }, + "B0F1FF0F", + "cmp r0, #255", + )); + insns.push(( + Inst::CmpImm8 { + rn: rreg(1), + imm8: 0, + }, + "B1F1000F", + "cmp r1, #0", + )); + insns.push(( + Inst::CmpImm8 { + rn: rreg(8), + imm8: 1, + }, + "B8F1010F", + "cmp r8, #1", + )); + + insns.push(( + Inst::Store { + rt: rreg(0), + mem: AMode::reg_plus_reg(rreg(1), rreg(2), 0), + srcloc: None, + bits: 32, + }, + "41F80200", + "str r0, [r1, r2]", + )); + insns.push(( + Inst::Store { + rt: rreg(8), + mem: AMode::reg_plus_reg(rreg(9), rreg(10), 3), + srcloc: None, + bits: 32, + }, + "49F83A80", + "str r8, [r9, r10, lsl #3]", + )); + insns.push(( + Inst::Store { + rt: rreg(0), + mem: AMode::RegOffset(rreg(1), 4095), + srcloc: None, + bits: 32, + }, + "C1F8FF0F", + "str r0, [r1, #4095]", + )); + insns.push(( + Inst::Store { + rt: rreg(8), + mem: AMode::RegOffset(rreg(9), 0), + srcloc: None, + bits: 32, + }, + "C9F80080", + "str r8, [r9, #0]", + )); + insns.push(( + Inst::Store { + rt: rreg(7), + mem: AMode::RegOffset(rreg(11), 65535), + srcloc: None, + bits: 32, + }, + "4FF6FF7C4BF80C70", + "mov ip, #65535 ; str r7, [fp, ip]", + )); + insns.push(( + Inst::Store { + rt: rreg(10), + mem: AMode::RegOffset(rreg(4), 16777215), + srcloc: None, + bits: 32, + }, + "4FF6FF7CC0F2FF0C44F80CA0", + "mov ip, #65535 ; movt ip, #255 ; str r10, [r4, ip]", + )); + insns.push(( + Inst::Store { + rt: rreg(0), + mem: AMode::reg_plus_reg(rreg(1), rreg(2), 0), + srcloc: None, + bits: 16, + }, + "21F80200", + "strh r0, [r1, r2]", + )); + insns.push(( + Inst::Store { + rt: rreg(8), + mem: AMode::reg_plus_reg(rreg(9), rreg(10), 2), + srcloc: None, + bits: 16, + }, + "29F82A80", + "strh r8, [r9, r10, lsl #2]", + )); + insns.push(( + Inst::Store { + rt: rreg(0), + mem: AMode::RegOffset(rreg(1), 3210), + srcloc: None, + bits: 16, + }, + "A1F88A0C", + "strh r0, [r1, #3210]", + )); + insns.push(( + Inst::Store { + rt: rreg(8), + mem: AMode::RegOffset(rreg(9), 1), + srcloc: None, + bits: 16, + }, + "A9F80180", + "strh r8, [r9, #1]", + )); + insns.push(( + Inst::Store { + rt: rreg(7), + mem: AMode::RegOffset(rreg(11), 65535), + srcloc: None, + bits: 16, + }, + "4FF6FF7C2BF80C70", + "mov ip, #65535 ; strh r7, [fp, ip]", + )); + insns.push(( + Inst::Store { + rt: rreg(10), + mem: AMode::RegOffset(rreg(4), 16777215), + srcloc: None, + bits: 16, + }, + "4FF6FF7CC0F2FF0C24F80CA0", + "mov ip, #65535 ; movt ip, #255 ; strh r10, [r4, ip]", + )); + insns.push(( + Inst::Store { + rt: rreg(0), + mem: AMode::reg_plus_reg(rreg(1), rreg(2), 0), + srcloc: None, + bits: 8, + }, + "01F80200", + "strb r0, [r1, r2]", + )); + insns.push(( + Inst::Store { + rt: rreg(8), + mem: AMode::reg_plus_reg(rreg(9), rreg(10), 1), + srcloc: None, + bits: 8, + }, + "09F81A80", + "strb r8, [r9, r10, lsl #1]", + )); + insns.push(( + Inst::Store { + rt: rreg(0), + mem: AMode::RegOffset(rreg(1), 4), + srcloc: None, + bits: 8, + }, + "81F80400", + "strb r0, [r1, #4]", + )); + insns.push(( + Inst::Store { + rt: rreg(8), + mem: AMode::RegOffset(rreg(9), 777), + srcloc: None, + bits: 8, + }, + "89F80983", + "strb r8, [r9, #777]", + )); + insns.push(( + Inst::Store { + rt: rreg(7), + mem: AMode::RegOffset(rreg(11), 65535), + srcloc: None, + bits: 8, + }, + "4FF6FF7C0BF80C70", + "mov ip, #65535 ; strb r7, [fp, ip]", + )); + insns.push(( + Inst::Store { + rt: rreg(10), + mem: AMode::RegOffset(rreg(4), 16777215), + srcloc: None, + bits: 8, + }, + "4FF6FF7CC0F2FF0C04F80CA0", + "mov ip, #65535 ; movt ip, #255 ; strb r10, [r4, ip]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(0), + mem: AMode::reg_plus_reg(rreg(1), rreg(2), 0), + srcloc: None, + bits: 32, + sign_extend: false, + }, + "51F80200", + "ldr r0, [r1, r2]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(8), + mem: AMode::reg_plus_reg(rreg(9), rreg(10), 1), + srcloc: None, + bits: 32, + sign_extend: false, + }, + "59F81A80", + "ldr r8, [r9, r10, lsl #1]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(0), + mem: AMode::RegOffset(rreg(1), 55), + srcloc: None, + bits: 32, + sign_extend: false, + }, + "D1F83700", + "ldr r0, [r1, #55]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(8), + mem: AMode::RegOffset(rreg(9), 1234), + srcloc: None, + bits: 32, + sign_extend: false, + }, + "D9F8D284", + "ldr r8, [r9, #1234]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(7), + mem: AMode::RegOffset(rreg(11), 9876), + srcloc: None, + bits: 32, + sign_extend: false, + }, + "42F2946C5BF80C70", + "mov ip, #9876 ; ldr r7, [fp, ip]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(10), + mem: AMode::RegOffset(rreg(4), 252645135), + srcloc: None, + bits: 32, + sign_extend: false, + }, + "40F60F7CC0F60F7C54F80CA0", + "mov ip, #3855 ; movt ip, #3855 ; ldr r10, [r4, ip]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(0), + mem: AMode::PCRel(-56), + srcloc: None, + bits: 32, + sign_extend: false, + }, + "5FF83800", + "ldr r0, [pc, #-56]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(8), + mem: AMode::PCRel(1024), + srcloc: None, + bits: 32, + sign_extend: false, + }, + "DFF80084", + "ldr r8, [pc, #1024]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(0), + mem: AMode::reg_plus_reg(rreg(1), rreg(2), 0), + srcloc: None, + bits: 16, + sign_extend: true, + }, + "31F90200", + "ldrsh r0, [r1, r2]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(8), + mem: AMode::reg_plus_reg(rreg(9), rreg(10), 2), + srcloc: None, + bits: 16, + sign_extend: false, + }, + "39F82A80", + "ldrh r8, [r9, r10, lsl #2]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(0), + mem: AMode::RegOffset(rreg(1), 55), + srcloc: None, + bits: 16, + sign_extend: false, + }, + "B1F83700", + "ldrh r0, [r1, #55]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(8), + mem: AMode::RegOffset(rreg(9), 1234), + srcloc: None, + bits: 16, + sign_extend: true, + }, + "B9F9D284", + "ldrsh r8, [r9, #1234]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(7), + mem: AMode::RegOffset(rreg(11), 9876), + srcloc: None, + bits: 16, + sign_extend: true, + }, + "42F2946C3BF90C70", + "mov ip, #9876 ; ldrsh r7, [fp, ip]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(10), + mem: AMode::RegOffset(rreg(4), 252645135), + srcloc: None, + bits: 16, + sign_extend: false, + }, + "40F60F7CC0F60F7C34F80CA0", + "mov ip, #3855 ; movt ip, #3855 ; ldrh r10, [r4, ip]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(0), + mem: AMode::PCRel(56), + srcloc: None, + bits: 16, + sign_extend: false, + }, + "BFF83800", + "ldrh r0, [pc, #56]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(8), + mem: AMode::PCRel(-1000), + srcloc: None, + bits: 16, + sign_extend: true, + }, + "3FF9E883", + "ldrsh r8, [pc, #-1000]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(0), + mem: AMode::reg_plus_reg(rreg(1), rreg(2), 0), + srcloc: None, + bits: 8, + sign_extend: true, + }, + "11F90200", + "ldrsb r0, [r1, r2]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(8), + mem: AMode::reg_plus_reg(rreg(9), rreg(10), 3), + srcloc: None, + bits: 8, + sign_extend: false, + }, + "19F83A80", + "ldrb r8, [r9, r10, lsl #3]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(0), + mem: AMode::RegOffset(rreg(1), 55), + srcloc: None, + bits: 8, + sign_extend: false, + }, + "91F83700", + "ldrb r0, [r1, #55]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(8), + mem: AMode::RegOffset(rreg(9), 1234), + srcloc: None, + bits: 8, + sign_extend: true, + }, + "99F9D284", + "ldrsb r8, [r9, #1234]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(7), + mem: AMode::RegOffset(rreg(11), 9876), + srcloc: None, + bits: 8, + sign_extend: true, + }, + "42F2946C1BF90C70", + "mov ip, #9876 ; ldrsb r7, [fp, ip]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(10), + mem: AMode::RegOffset(rreg(4), 252645135), + srcloc: None, + bits: 8, + sign_extend: false, + }, + "40F60F7CC0F60F7C14F80CA0", + "mov ip, #3855 ; movt ip, #3855 ; ldrb r10, [r4, ip]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(0), + mem: AMode::PCRel(72), + srcloc: None, + bits: 8, + sign_extend: false, + }, + "9FF84800", + "ldrb r0, [pc, #72]", + )); + insns.push(( + Inst::Load { + rt: writable_rreg(8), + mem: AMode::PCRel(-1234), + srcloc: None, + bits: 8, + sign_extend: true, + }, + "1FF9D284", + "ldrsb r8, [pc, #-1234]", + )); + insns.push(( + Inst::Extend { + rd: writable_rreg(0), + rm: rreg(1), + from_bits: 16, + signed: false, + }, + "88B2", + "uxth r0, r1", + )); + insns.push(( + Inst::Extend { + rd: writable_rreg(8), + rm: rreg(9), + from_bits: 16, + signed: false, + }, + "1FFA89F8", + "uxth r8, r9", + )); + insns.push(( + Inst::Extend { + rd: writable_rreg(0), + rm: rreg(1), + from_bits: 8, + signed: false, + }, + "C8B2", + "uxtb r0, r1", + )); + insns.push(( + Inst::Extend { + rd: writable_rreg(8), + rm: rreg(9), + from_bits: 8, + signed: false, + }, + "5FFA89F8", + "uxtb r8, r9", + )); + insns.push(( + Inst::Extend { + rd: writable_rreg(0), + rm: rreg(1), + from_bits: 16, + signed: true, + }, + "08B2", + "sxth r0, r1", + )); + insns.push(( + Inst::Extend { + rd: writable_rreg(8), + rm: rreg(9), + from_bits: 16, + signed: true, + }, + "0FFA89F8", + "sxth r8, r9", + )); + insns.push(( + Inst::Extend { + rd: writable_rreg(0), + rm: rreg(1), + from_bits: 8, + signed: true, + }, + "48B2", + "sxtb r0, r1", + )); + insns.push(( + Inst::Extend { + rd: writable_rreg(8), + rm: rreg(9), + from_bits: 8, + signed: true, + }, + "4FFA89F8", + "sxtb r8, r9", + )); + insns.push(( + Inst::It { + cond: Cond::Eq, + insts: vec![CondInst::new(Inst::mov(writable_rreg(0), rreg(0)), true)], + }, + "08BF0046", + "it eq ; mov r0, r0", + )); + insns.push(( + Inst::It { + cond: Cond::Ne, + insts: vec![ + CondInst::new(Inst::mov(writable_rreg(0), rreg(0)), true), + CondInst::new(Inst::mov(writable_rreg(0), rreg(0)), false), + ], + }, + "14BF00460046", + "ite ne ; mov r0, r0 ; mov r0, r0", + )); + insns.push(( + Inst::It { + cond: Cond::Lt, + insts: vec![ + CondInst::new(Inst::mov(writable_rreg(0), rreg(0)), true), + CondInst::new(Inst::mov(writable_rreg(0), rreg(0)), false), + CondInst::new(Inst::mov(writable_rreg(0), rreg(0)), true), + ], + }, + "B6BF004600460046", + "itet lt ; mov r0, r0 ; mov r0, r0 ; mov r0, r0", + )); + insns.push(( + Inst::It { + cond: Cond::Hs, + insts: vec![ + CondInst::new(Inst::mov(writable_rreg(0), rreg(0)), true), + CondInst::new(Inst::mov(writable_rreg(0), rreg(0)), true), + CondInst::new(Inst::mov(writable_rreg(0), rreg(0)), false), + CondInst::new(Inst::mov(writable_rreg(0), rreg(0)), false), + ], + }, + "27BF0046004600460046", + "ittee hs ; mov r0, r0 ; mov r0, r0 ; mov r0, r0 ; mov r0, r0", + )); + insns.push(( + Inst::Push { + reg_list: vec![rreg(0)], + }, + "4DF8040D", + "push {r0}", + )); + insns.push(( + Inst::Push { + reg_list: vec![rreg(8)], + }, + "4DF8048D", + "push {r8}", + )); + insns.push(( + Inst::Push { + reg_list: vec![rreg(0), rreg(1), rreg(2), rreg(6), rreg(8)], + }, + "2DE94701", + "push {r0, r1, r2, r6, r8}", + )); + insns.push(( + Inst::Push { + reg_list: vec![rreg(8), rreg(9), rreg(10)], + }, + "2DE90007", + "push {r8, r9, r10}", + )); + insns.push(( + Inst::Pop { + reg_list: vec![writable_rreg(0)], + }, + "5DF8040B", + "pop {r0}", + )); + insns.push(( + Inst::Pop { + reg_list: vec![writable_rreg(8)], + }, + "5DF8048B", + "pop {r8}", + )); + insns.push(( + Inst::Pop { + reg_list: vec![ + writable_rreg(0), + writable_rreg(1), + writable_rreg(2), + writable_rreg(6), + writable_rreg(8), + ], + }, + "BDE84701", + "pop {r0, r1, r2, r6, r8}", + )); + insns.push(( + Inst::Pop { + reg_list: vec![writable_rreg(8), writable_rreg(9), writable_rreg(10)], + }, + "BDE80007", + "pop {r8, r9, r10}", + )); + insns.push(( + Inst::Call { + info: Box::new(CallInfo { + dest: ExternalName::testcase("test0"), + uses: Vec::new(), + defs: Vec::new(), + loc: SourceLoc::default(), + opcode: Opcode::Call, + }), + }, + "00F000D0", + "bl 0", + )); + insns.push(( + Inst::CallInd { + info: Box::new(CallIndInfo { + rm: rreg(0), + uses: Vec::new(), + defs: Vec::new(), + loc: SourceLoc::default(), + opcode: Opcode::CallIndirect, + }), + }, + "8047", + "blx r0", + )); + insns.push(( + Inst::CallInd { + info: Box::new(CallIndInfo { + rm: rreg(8), + uses: Vec::new(), + defs: Vec::new(), + loc: SourceLoc::default(), + opcode: Opcode::CallIndirect, + }), + }, + "C047", + "blx r8", + )); + insns.push((Inst::Ret, "7047", "bx lr")); + insns.push(( + Inst::Jump { + dest: BranchTarget::ResolvedOffset(32), + }, + "00F010B8", + "b 32", + )); + insns.push(( + Inst::Jump { + dest: BranchTarget::ResolvedOffset(0xfffff4), + }, + "FFF3FA97", + "b 16777204", + )); + insns.push(( + Inst::CondBr { + taken: BranchTarget::ResolvedOffset(20), + not_taken: BranchTarget::ResolvedOffset(68), + cond: Cond::Eq, + }, + "00F00A8000F022B8", + "beq 20 ; b 68", + )); + insns.push(( + Inst::CondBr { + taken: BranchTarget::ResolvedOffset(6), + not_taken: BranchTarget::ResolvedOffset(100), + cond: Cond::Gt, + }, + "00F3038000F032B8", + "bgt 6 ; b 100", + )); + insns.push(( + Inst::IndirectBr { + rm: rreg(0), + targets: vec![], + }, + "0047", + "bx r0", + )); + insns.push(( + Inst::IndirectBr { + rm: rreg(8), + targets: vec![], + }, + "4047", + "bx r8", + )); + insns.push(( + Inst::TrapIf { + cond: Cond::Eq, + trap_info: (SourceLoc::default(), TrapCode::Interrupt), + }, + "40F0018000DE", + "bne 2 ; udf #0", + )); + insns.push(( + Inst::TrapIf { + cond: Cond::Hs, + trap_info: (SourceLoc::default(), TrapCode::Interrupt), + }, + "C0F0018000DE", + "blo 2 ; udf #0", + )); + insns.push(( + Inst::Udf { + trap_info: (SourceLoc::default(), TrapCode::Interrupt), + }, + "00DE", + "udf #0", + )); + insns.push((Inst::Bkpt, "00BE", "bkpt #0")); + + // ======================================================== + // Run the tests + let rru = regs::create_reg_universe(); + for (insn, expected_encoding, expected_printing) in insns { + // Check the printed text is as expected. + let actual_printing = insn.show_rru(Some(&rru)); + assert_eq!(expected_printing, actual_printing); + let mut sink = test_utils::TestCodeSink::new(); + let mut buffer = MachBuffer::new(); + insn.emit(&mut buffer, &flags, &mut Default::default()); + let buffer = buffer.finish(); + buffer.emit(&mut sink); + let actual_encoding = &sink.stringify(); + assert_eq!(expected_encoding, actual_encoding, "{}", expected_printing); + } +} diff --git a/cranelift/codegen/src/isa/arm32/inst/mod.rs b/cranelift/codegen/src/isa/arm32/inst/mod.rs new file mode 100644 index 0000000000..dfd4906f05 --- /dev/null +++ b/cranelift/codegen/src/isa/arm32/inst/mod.rs @@ -0,0 +1,1365 @@ +//! This module defines 32-bit ARM specific machine instruction types. + +#![allow(dead_code)] + +use crate::binemit::CodeOffset; +use crate::ir::types::{B1, B16, B32, B8, I16, I32, I8, IFLAGS}; +use crate::ir::{ExternalName, Opcode, SourceLoc, TrapCode, Type}; +use crate::machinst::*; +use crate::{settings, CodegenError, CodegenResult}; + +use regalloc::{RealRegUniverse, Reg, RegClass, SpillSlot, VirtualReg, Writable}; +use regalloc::{RegUsageCollector, RegUsageMapper}; + +use alloc::boxed::Box; +use alloc::vec::Vec; +use smallvec::{smallvec, SmallVec}; +use std::string::{String, ToString}; + +mod args; +pub use self::args::*; +mod emit; +pub use self::emit::*; +mod regs; +pub use self::regs::*; + +#[cfg(test)] +mod emit_tests; + +//============================================================================= +// Instructions (top level): definition + +/// An ALU operation. This can be paired with several instruction formats +/// below (see `Inst`) in any combination. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ALUOp { + Add, + Adds, + Adc, + Adcs, + Qadd, + Sub, + Subs, + Sbc, + Sbcs, + Rsb, + Qsub, + Mul, + Smull, + Umull, + Udiv, + Sdiv, + And, + Orr, + Orn, + Eor, + Bic, + Lsl, + Lsr, + Asr, + Ror, +} + +/// An ALU operation with one argument. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ALUOp1 { + Mvn, + Mov, +} + +/// An operation on the bits of a register. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum BitOp { + Rbit, + Rev, + Clz, +} + +/// Additional information for (direct) Call instructions, left out of line to lower the size of +/// the Inst enum. +#[derive(Clone, Debug)] +pub struct CallInfo { + pub dest: ExternalName, + pub uses: Vec, + pub defs: Vec>, + pub loc: SourceLoc, + pub opcode: Opcode, +} + +/// Additional information for CallInd instructions, left out of line to lower the size of the Inst +/// enum. +#[derive(Clone, Debug)] +pub struct CallIndInfo { + pub rm: Reg, + pub uses: Vec, + pub defs: Vec>, + pub loc: SourceLoc, + pub opcode: Opcode, +} + +/// Instruction formats. +#[derive(Clone, Debug)] +pub enum Inst { + /// A no-op of zero size. + Nop0, + + /// A no-op that is two bytes large. + Nop2, + + /// An ALU operation with two register sources and one register destination. + AluRRR { + alu_op: ALUOp, + rd: Writable, + rn: Reg, + rm: Reg, + }, + + /// An ALU operation with two register sources, one of which can be optionally shifted + /// and one register destination. + AluRRRShift { + alu_op: ALUOp, + rd: Writable, + rn: Reg, + rm: Reg, + shift: Option, + }, + + /// An ALU operation with one register source, which can be optionally shifted + /// and one register destination. + AluRRShift { + alu_op: ALUOp1, + rd: Writable, + rm: Reg, + shift: Option, + }, + + /// An ALU operation with two register sources and two register destinations. + AluRRRR { + alu_op: ALUOp, + rd_hi: Writable, + rd_lo: Writable, + rn: Reg, + rm: Reg, + }, + + /// An ALU operation with a register source and a 12-bit immediate source, + /// and a register destination. + AluRRImm12 { + alu_op: ALUOp, + rd: Writable, + rn: Reg, + imm12: UImm12, + }, + + /// An ALU operation with a register source and a 8-bit immediate source, + /// and a register destination. + /// + /// In fact these instructions take a `modified immediate constant` operand, + /// which is encoded as a 12-bit immediate. The only case used here + /// is when high 4 bits of that 12-immediate are zeros. + /// In this case operand is simple 8-bit immediate. + /// For all possible operands see + /// https://static.docs.arm.com/ddi0406/c/DDI0406C_C_arm_architecture_reference_manual.pdf#G10.4954509 + AluRRImm8 { + alu_op: ALUOp, + rd: Writable, + rn: Reg, + imm8: UImm8, + }, + + /// An ALU operation with a 8-bit immediate and a register destination. + /// See `AluRRImm8` description above. + AluRImm8 { + alu_op: ALUOp1, + rd: Writable, + imm8: UImm8, + }, + + /// A bit operation with a register source and a register destination. + BitOpRR { + bit_op: BitOp, + rd: Writable, + rm: Reg, + }, + + /// A mov instruction with a GPR source and a GPR destination. + Mov { + rd: Writable, + rm: Reg, + }, + + /// A move instruction with a 16-bit immediate source and a register destination. + MovImm16 { + rd: Writable, + imm16: u16, + }, + + /// A move top instruction, which writes 16-bit immediate to the top + /// halfword of the destination register. + Movt { + rd: Writable, + imm16: u16, + }, + + /// A compare instruction with two register arguments. + Cmp { + rn: Reg, + rm: Reg, + }, + + /// A compare instruction with a register operand and a 8-bit immediate operand. + CmpImm8 { + rn: Reg, + imm8: u8, + }, + + /// A store instruction, which stores to memory 8, 16 or 32-bit operand. + Store { + rt: Reg, + mem: AMode, + srcloc: Option, + bits: u8, + }, + + /// A load instruction, which loads from memory 8, 16 or 32-bit operand, + /// which can be sign- or zero-extended. + Load { + rt: Writable, + mem: AMode, + srcloc: Option, + bits: u8, + sign_extend: bool, + }, + + /// Load address referenced by `mem` into `rd`. + LoadAddr { + rd: Writable, + mem: AMode, + }, + + /// A sign- or zero-extend operation. + Extend { + rd: Writable, + rm: Reg, + from_bits: u8, + signed: bool, + }, + + // An If-Then instruction, which makes up to four instructions conditinal. + It { + cond: Cond, + insts: Vec, + }, + + /// A push instuction, which stores registers to the stack and updates sp. + Push { + reg_list: Vec, + }, + + /// A pop instuction, which load registers from the stack and updates sp. + Pop { + reg_list: Vec>, + }, + + /// A machine call instruction. + Call { + info: Box, + }, + + /// A machine indirect-call instruction. + CallInd { + info: Box, + }, + + /// Load an inline symbol reference. + LoadExtName { + rt: Writable, + name: Box, + srcloc: SourceLoc, + offset: i32, + }, + + /// A return instruction, which is encoded as `bx lr`. + Ret, + + /// An unconditional branch. + Jump { + dest: BranchTarget, + }, + + /// A conditional branch. + CondBr { + taken: BranchTarget, + not_taken: BranchTarget, + cond: Cond, + }, + + /// An indirect branch through a register, augmented with set of all + /// possible successors. + IndirectBr { + rm: Reg, + targets: Vec, + }, + + /// A conditional trap: execute a `udf` if the condition is true. This is + /// one VCode instruction because it uses embedded control flow; it is + /// logically a single-in, single-out region, but needs to appear as one + /// unit to the register allocator. + TrapIf { + cond: Cond, + trap_info: (SourceLoc, TrapCode), + }, + + /// An instruction guaranteed to always be undefined and to trigger an illegal instruction at + /// runtime. + Udf { + trap_info: (SourceLoc, TrapCode), + }, + + /// A "breakpoint" instruction, used for e.g. traps and debug breakpoints. + Bkpt, + + /// Marker, no-op in generated code: SP "virtual offset" is adjusted. + VirtualSPOffsetAdj { + offset: i64, + }, + + /// A placeholder instruction, generating no code, meaning that a function epilogue must be + /// inserted there. + EpiloguePlaceholder, +} + +/// An instruction inside an it block. +#[derive(Clone, Debug)] +pub struct CondInst { + inst: Inst, + // In which case execute the instruction: + // true => when it condition is met + // false => otherwise. + then: bool, +} + +impl CondInst { + pub fn new(inst: Inst, then: bool) -> Self { + match inst { + Inst::It { .. } + | Inst::Ret { .. } + | Inst::Jump { .. } + | Inst::CondBr { .. } + | Inst::TrapIf { .. } + | Inst::EpiloguePlaceholder { .. } + | Inst::LoadExtName { .. } => panic!("Instruction {:?} cannot occur in it block", inst), + _ => Self { inst, then }, + } + } +} + +impl Inst { + /// Create a move instruction. + pub fn mov(to_reg: Writable, from_reg: Reg) -> Inst { + Inst::Mov { + rd: to_reg, + rm: from_reg, + } + } + + /// Create an instruction that loads a constant. + pub fn load_constant(rd: Writable, value: u32) -> SmallVec<[Inst; 4]> { + let mut insts = smallvec![]; + let imm_lo = (value & 0xffff) as u16; + let imm_hi = (value >> 16) as u16; + + if imm_lo != 0 || imm_hi == 0 { + // imm_lo == 0 && imm_hi == 0 => we have to overwrite reg value with 0 + insts.push(Inst::MovImm16 { rd, imm16: imm_lo }); + } + if imm_hi != 0 { + insts.push(Inst::Movt { rd, imm16: imm_hi }); + } + + insts + } + + /// Generic constructor for a load (zero-extending where appropriate). + pub fn gen_load(into_reg: Writable, mem: AMode, ty: Type) -> Inst { + assert!(ty.bits() <= 32); + // Load 8 bits for B1. + let bits = std::cmp::max(ty.bits(), 8) as u8; + + Inst::Load { + rt: into_reg, + mem, + srcloc: None, + bits, + sign_extend: false, + } + } + + /// Generic constructor for a store. + pub fn gen_store(from_reg: Reg, mem: AMode, ty: Type) -> Inst { + assert!(ty.bits() <= 32); + // Store 8 bits for B1. + let bits = std::cmp::max(ty.bits(), 8) as u8; + + Inst::Store { + rt: from_reg, + mem, + srcloc: None, + bits, + } + } +} + +//============================================================================= +// Instructions: get_regs + +fn memarg_regs(memarg: &AMode, collector: &mut RegUsageCollector) { + match memarg { + &AMode::RegReg(rn, rm, ..) => { + collector.add_use(rn); + collector.add_use(rm); + } + &AMode::RegOffset12(rn, ..) | &AMode::RegOffset(rn, _) => { + collector.add_use(rn); + } + &AMode::SPOffset(..) | &AMode::NominalSPOffset(..) => { + collector.add_use(sp_reg()); + } + &AMode::FPOffset(..) => { + collector.add_use(fp_reg()); + } + &AMode::PCRel(_) => {} + } +} + +fn arm32_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { + match inst { + &Inst::Nop0 + | &Inst::Nop2 + | &Inst::Ret + | &Inst::VirtualSPOffsetAdj { .. } + | &Inst::EpiloguePlaceholder + | &Inst::Jump { .. } + | &Inst::CondBr { .. } + | &Inst::Bkpt + | &Inst::Udf { .. } + | &Inst::TrapIf { .. } => {} + &Inst::AluRRR { rd, rn, rm, .. } => { + collector.add_def(rd); + collector.add_use(rn); + collector.add_use(rm); + } + &Inst::AluRRRShift { rd, rn, rm, .. } => { + collector.add_def(rd); + collector.add_use(rn); + collector.add_use(rm); + } + &Inst::AluRRShift { rd, rm, .. } => { + collector.add_def(rd); + collector.add_use(rm); + } + &Inst::AluRRRR { + rd_hi, + rd_lo, + rn, + rm, + .. + } => { + collector.add_def(rd_hi); + collector.add_def(rd_lo); + collector.add_use(rn); + collector.add_use(rm); + } + &Inst::AluRRImm12 { rd, rn, .. } => { + collector.add_def(rd); + collector.add_use(rn); + } + &Inst::AluRRImm8 { rd, rn, .. } => { + collector.add_def(rd); + collector.add_use(rn); + } + &Inst::AluRImm8 { rd, .. } => { + collector.add_def(rd); + } + &Inst::BitOpRR { rd, rm, .. } => { + collector.add_def(rd); + collector.add_use(rm); + } + &Inst::Mov { rd, rm, .. } => { + collector.add_def(rd); + collector.add_use(rm); + } + &Inst::MovImm16 { rd, .. } => { + collector.add_def(rd); + } + &Inst::Movt { rd, .. } => { + collector.add_def(rd); + } + &Inst::Cmp { rn, rm } => { + collector.add_use(rn); + collector.add_use(rm); + } + &Inst::CmpImm8 { rn, .. } => { + collector.add_use(rn); + } + &Inst::Store { rt, ref mem, .. } => { + collector.add_use(rt); + memarg_regs(mem, collector); + } + &Inst::Load { rt, ref mem, .. } => { + collector.add_def(rt); + memarg_regs(mem, collector); + } + &Inst::LoadAddr { rd, mem: _ } => { + collector.add_def(rd); + } + &Inst::Extend { rd, rm, .. } => { + collector.add_def(rd); + collector.add_use(rm); + } + &Inst::It { ref insts, .. } => { + for inst in insts.iter() { + arm32_get_regs(&inst.inst, collector); + } + } + &Inst::Push { ref reg_list } => { + for reg in reg_list { + collector.add_use(*reg); + } + } + &Inst::Pop { ref reg_list } => { + for reg in reg_list { + collector.add_def(*reg); + } + } + &Inst::Call { ref info, .. } => { + collector.add_uses(&*info.uses); + collector.add_defs(&*info.defs); + } + &Inst::CallInd { ref info, .. } => { + collector.add_uses(&*info.uses); + collector.add_defs(&*info.defs); + collector.add_use(info.rm); + } + &Inst::LoadExtName { rt, .. } => { + collector.add_def(rt); + } + &Inst::IndirectBr { rm, .. } => { + collector.add_use(rm); + } + } +} + +//============================================================================= +// Instructions: map_regs + +fn arm32_map_regs(inst: &mut Inst, mapper: &RUM) { + fn map_use(m: &RUM, r: &mut Reg) { + if r.is_virtual() { + let new = m.get_use(r.to_virtual_reg()).unwrap().to_reg(); + *r = new; + } + } + + fn map_def(m: &RUM, r: &mut Writable) { + if r.to_reg().is_virtual() { + let new = m.get_def(r.to_reg().to_virtual_reg()).unwrap().to_reg(); + *r = Writable::from_reg(new); + } + } + + fn map_mod(m: &RUM, r: &mut Writable) { + if r.to_reg().is_virtual() { + let new = m.get_mod(r.to_reg().to_virtual_reg()).unwrap().to_reg(); + *r = Writable::from_reg(new); + } + } + + fn map_mem(m: &RUM, mem: &mut AMode) { + match mem { + &mut AMode::RegReg(ref mut rn, ref mut rm, ..) => { + map_use(m, rn); + map_use(m, rm); + } + &mut AMode::RegOffset12(ref mut rn, ..) | &mut AMode::RegOffset(ref mut rn, ..) => { + map_use(m, rn) + } + &mut AMode::SPOffset(..) + | &mut AMode::FPOffset(..) + | &mut AMode::NominalSPOffset(..) + | &mut AMode::PCRel(_) => {} + }; + } + + match inst { + &mut Inst::Nop0 + | &mut Inst::Nop2 + | &mut Inst::Ret + | &mut Inst::VirtualSPOffsetAdj { .. } + | &mut Inst::EpiloguePlaceholder + | &mut Inst::Jump { .. } + | &mut Inst::CondBr { .. } + | &mut Inst::Bkpt + | &mut Inst::Udf { .. } + | &mut Inst::TrapIf { .. } => {} + &mut Inst::AluRRR { + ref mut rd, + ref mut rn, + ref mut rm, + .. + } => { + map_def(mapper, rd); + map_use(mapper, rn); + map_use(mapper, rm); + } + &mut Inst::AluRRRShift { + ref mut rd, + ref mut rn, + ref mut rm, + .. + } => { + map_def(mapper, rd); + map_use(mapper, rn); + map_use(mapper, rm); + } + &mut Inst::AluRRShift { + ref mut rd, + ref mut rm, + .. + } => { + map_def(mapper, rd); + map_use(mapper, rm); + } + &mut Inst::AluRRRR { + ref mut rd_hi, + ref mut rd_lo, + ref mut rn, + ref mut rm, + .. + } => { + map_def(mapper, rd_hi); + map_def(mapper, rd_lo); + map_use(mapper, rn); + map_use(mapper, rm); + } + &mut Inst::AluRRImm12 { + ref mut rd, + ref mut rn, + .. + } => { + map_def(mapper, rd); + map_use(mapper, rn); + } + &mut Inst::AluRRImm8 { + ref mut rd, + ref mut rn, + .. + } => { + map_def(mapper, rd); + map_use(mapper, rn); + } + &mut Inst::AluRImm8 { ref mut rd, .. } => { + map_def(mapper, rd); + } + &mut Inst::BitOpRR { + ref mut rd, + ref mut rm, + .. + } => { + map_def(mapper, rd); + map_use(mapper, rm); + } + &mut Inst::Mov { + ref mut rd, + ref mut rm, + .. + } => { + map_def(mapper, rd); + map_use(mapper, rm); + } + &mut Inst::MovImm16 { ref mut rd, .. } => { + map_def(mapper, rd); + } + &mut Inst::Movt { ref mut rd, .. } => { + map_def(mapper, rd); + } + &mut Inst::Cmp { + ref mut rn, + ref mut rm, + } => { + map_use(mapper, rn); + map_use(mapper, rm); + } + &mut Inst::CmpImm8 { ref mut rn, .. } => { + map_use(mapper, rn); + } + &mut Inst::Store { + ref mut rt, + ref mut mem, + .. + } => { + map_use(mapper, rt); + map_mem(mapper, mem); + } + &mut Inst::Load { + ref mut rt, + ref mut mem, + .. + } => { + map_def(mapper, rt); + map_mem(mapper, mem); + } + &mut Inst::LoadAddr { + ref mut rd, + ref mut mem, + } => { + map_def(mapper, rd); + map_mem(mapper, mem); + } + &mut Inst::Extend { + ref mut rd, + ref mut rm, + .. + } => { + map_def(mapper, rd); + map_use(mapper, rm); + } + &mut Inst::It { ref mut insts, .. } => { + for inst in insts.iter_mut() { + arm32_map_regs(&mut inst.inst, mapper); + } + } + &mut Inst::Push { ref mut reg_list } => { + for reg in reg_list { + map_use(mapper, reg); + } + } + &mut Inst::Pop { ref mut reg_list } => { + for reg in reg_list { + map_def(mapper, reg); + } + } + &mut Inst::Call { ref mut info } => { + for r in info.uses.iter_mut() { + map_use(mapper, r); + } + for r in info.defs.iter_mut() { + map_def(mapper, r); + } + } + &mut Inst::CallInd { ref mut info, .. } => { + for r in info.uses.iter_mut() { + map_use(mapper, r); + } + for r in info.defs.iter_mut() { + map_def(mapper, r); + } + map_use(mapper, &mut info.rm); + } + &mut Inst::LoadExtName { ref mut rt, .. } => { + map_def(mapper, rt); + } + &mut Inst::IndirectBr { ref mut rm, .. } => { + map_use(mapper, rm); + } + } +} + +//============================================================================= +// Instructions: misc functions and external interface + +impl MachInst for Inst { + type LabelUse = LabelUse; + + fn get_regs(&self, collector: &mut RegUsageCollector) { + arm32_get_regs(self, collector) + } + + fn map_regs(&mut self, mapper: &RUM) { + arm32_map_regs(self, mapper); + } + + fn is_move(&self) -> Option<(Writable, Reg)> { + match self { + &Inst::Mov { rd, rm } => Some((rd, rm)), + _ => None, + } + } + + fn is_epilogue_placeholder(&self) -> bool { + if let Inst::EpiloguePlaceholder = self { + true + } else { + false + } + } + + fn is_term<'a>(&'a self) -> MachTerminator<'a> { + match self { + &Inst::Ret | &Inst::EpiloguePlaceholder => MachTerminator::Ret, + &Inst::Jump { dest } => MachTerminator::Uncond(dest.as_label().unwrap()), + &Inst::CondBr { + taken, not_taken, .. + } => MachTerminator::Cond(taken.as_label().unwrap(), not_taken.as_label().unwrap()), + &Inst::IndirectBr { ref targets, .. } => MachTerminator::Indirect(&targets[..]), + _ => MachTerminator::None, + } + } + + fn gen_move(to_reg: Writable, from_reg: Reg, _ty: Type) -> Inst { + assert_eq!(from_reg.get_class(), RegClass::I32); + assert_eq!(to_reg.to_reg().get_class(), from_reg.get_class()); + + Inst::mov(to_reg, from_reg) + } + + fn gen_constant Writable>( + to_reg: Writable, + value: u64, + ty: Type, + _alloc_tmp: F, + ) -> SmallVec<[Inst; 4]> { + match ty { + B1 | I8 | B8 | I16 | B16 | I32 | B32 => { + let v: i64 = value as i64; + + if v >= (1 << 32) || v < -(1 << 32) { + panic!("Cannot load constant value {}", value) + } + Inst::load_constant(to_reg, value as u32) + } + _ => unimplemented!(), + } + } + + fn gen_zero_len_nop() -> Inst { + Inst::Nop0 + } + + fn gen_nop(preferred_size: usize) -> Inst { + assert!(preferred_size >= 2); + Inst::Nop2 + } + + fn maybe_direct_reload(&self, _reg: VirtualReg, _slot: SpillSlot) -> Option { + None + } + + fn rc_for_type(ty: Type) -> CodegenResult { + match ty { + I8 | I16 | I32 | B1 | B8 | B16 | B32 => Ok(RegClass::I32), + IFLAGS => Ok(RegClass::I32), + _ => Err(CodegenError::Unsupported(format!( + "Unexpected SSA-value type: {}", + ty + ))), + } + } + + fn gen_jump(target: MachLabel) -> Inst { + Inst::Jump { + dest: BranchTarget::Label(target), + } + } + + fn reg_universe(_flags: &settings::Flags) -> RealRegUniverse { + create_reg_universe() + } + + fn worst_case_size() -> CodeOffset { + // It inst with four 32-bit instructions + 2 + 4 * 4 + } + + fn ref_type_regclass(_: &settings::Flags) -> RegClass { + RegClass::I32 + } +} + +//============================================================================= +// Pretty-printing of instructions. + +fn mem_finalize_for_show( + mem: &AMode, + mb_rru: Option<&RealRegUniverse>, + state: &EmitState, +) -> (String, AMode) { + let (mem_insts, mem) = mem_finalize(mem, state); + let mut mem_str = mem_insts + .into_iter() + .map(|inst| inst.show_rru(mb_rru)) + .collect::>() + .join(" ; "); + if !mem_str.is_empty() { + mem_str += " ; "; + } + + (mem_str, mem) +} + +impl ShowWithRRU for Inst { + fn show_rru(&self, mb_rru: Option<&RealRegUniverse>) -> String { + self.pretty_print(mb_rru, &mut EmitState::default()) + } +} + +impl Inst { + fn print_with_state(&self, mb_rru: Option<&RealRegUniverse>, state: &mut EmitState) -> String { + fn op_name(alu_op: ALUOp) -> &'static str { + match alu_op { + ALUOp::Add => "add", + ALUOp::Adds => "adds", + ALUOp::Adc => "adc", + ALUOp::Adcs => "adcs", + ALUOp::Qadd => "qadd", + ALUOp::Sub => "sub", + ALUOp::Subs => "subs", + ALUOp::Sbc => "sbc", + ALUOp::Sbcs => "sbcs", + ALUOp::Rsb => "rsb", + ALUOp::Qsub => "qsub", + ALUOp::Mul => "mul", + ALUOp::Smull => "smull", + ALUOp::Umull => "umull", + ALUOp::Udiv => "udiv", + ALUOp::Sdiv => "sdiv", + ALUOp::And => "and", + ALUOp::Orr => "orr", + ALUOp::Orn => "orn", + ALUOp::Eor => "eor", + ALUOp::Bic => "bic", + ALUOp::Lsl => "lsl", + ALUOp::Lsr => "lsr", + ALUOp::Asr => "asr", + ALUOp::Ror => "ror", + } + } + + fn reg_shift_str( + shift: &Option, + mb_rru: Option<&RealRegUniverse>, + ) -> String { + if let Some(ref shift) = shift { + format!(", {}", shift.show_rru(mb_rru)) + } else { + "".to_string() + } + } + + match self { + &Inst::Nop0 => "nop-zero-len".to_string(), + &Inst::Nop2 => "nop".to_string(), + &Inst::AluRRR { alu_op, rd, rn, rm } => { + let op = op_name(alu_op); + let rd = rd.show_rru(mb_rru); + let rn = rn.show_rru(mb_rru); + let rm = rm.show_rru(mb_rru); + format!("{} {}, {}, {}", op, rd, rn, rm) + } + &Inst::AluRRRShift { + alu_op, + rd, + rn, + rm, + ref shift, + } => { + let op = op_name(alu_op); + let rd = rd.show_rru(mb_rru); + let rn = rn.show_rru(mb_rru); + let rm = rm.show_rru(mb_rru); + let shift = reg_shift_str(shift, mb_rru); + format!("{} {}, {}, {}{}", op, rd, rn, rm, shift) + } + &Inst::AluRRShift { + alu_op, + rd, + rm, + ref shift, + } => { + let op = match alu_op { + ALUOp1::Mvn => "mvn", + ALUOp1::Mov => "mov", + }; + let rd = rd.show_rru(mb_rru); + let rm = rm.show_rru(mb_rru); + let shift = reg_shift_str(shift, mb_rru); + format!("{} {}, {}{}", op, rd, rm, shift) + } + &Inst::AluRRRR { + alu_op, + rd_hi, + rd_lo, + rn, + rm, + } => { + let op = op_name(alu_op); + let rd_hi = rd_hi.show_rru(mb_rru); + let rd_lo = rd_lo.show_rru(mb_rru); + let rn = rn.show_rru(mb_rru); + let rm = rm.show_rru(mb_rru); + format!("{} {}, {}, {}, {}", op, rd_lo, rd_hi, rn, rm) + } + &Inst::AluRRImm12 { + alu_op, + rd, + rn, + imm12, + } => { + let op = op_name(alu_op); + let rd = rd.show_rru(mb_rru); + let rn = rn.show_rru(mb_rru); + let imm = imm12.show_rru(mb_rru); + format!("{} {}, {}, {}", op, rd, rn, imm) + } + &Inst::AluRRImm8 { + alu_op, + rd, + rn, + imm8, + } => { + let op = op_name(alu_op); + let rd = rd.show_rru(mb_rru); + let rn = rn.show_rru(mb_rru); + let imm = imm8.show_rru(mb_rru); + format!("{} {}, {}, {}", op, rd, rn, imm) + } + &Inst::AluRImm8 { alu_op, rd, imm8 } => { + let op = match alu_op { + ALUOp1::Mvn => "mvn", + ALUOp1::Mov => "mov", + }; + let rd = rd.show_rru(mb_rru); + let imm = imm8.show_rru(mb_rru); + format!("{} {}, {}", op, rd, imm) + } + &Inst::BitOpRR { bit_op, rd, rm } => { + let op = match bit_op { + BitOp::Rbit => "rbit", + BitOp::Rev => "rev", + BitOp::Clz => "clz", + }; + let rd = rd.show_rru(mb_rru); + let rm = rm.show_rru(mb_rru); + format!("{} {}, {}", op, rd, rm) + } + &Inst::Mov { rd, rm } => { + let rd = rd.show_rru(mb_rru); + let rm = rm.show_rru(mb_rru); + format!("mov {}, {}", rd, rm) + } + &Inst::MovImm16 { rd, imm16 } => { + let rd = rd.show_rru(mb_rru); + format!("mov {}, #{}", rd, imm16) + } + &Inst::Movt { rd, imm16 } => { + let rd = rd.show_rru(mb_rru); + format!("movt {}, #{}", rd, imm16) + } + &Inst::Cmp { rn, rm } => { + let rn = rn.show_rru(mb_rru); + let rm = rm.show_rru(mb_rru); + format!("cmp {}, {}", rn, rm) + } + &Inst::CmpImm8 { rn, imm8 } => { + let rn = rn.show_rru(mb_rru); + format!("cmp {}, #{}", rn, imm8) + } + &Inst::Store { + rt, ref mem, bits, .. + } => { + let op = match bits { + 32 => "str", + 16 => "strh", + 8 => "strb", + _ => panic!("Invalid bit amount {}", bits), + }; + let rt = rt.show_rru(mb_rru); + let (mem_str, mem) = mem_finalize_for_show(mem, mb_rru, state); + let mem = mem.show_rru(mb_rru); + format!("{}{} {}, {}", mem_str, op, rt, mem) + } + &Inst::Load { + rt, + ref mem, + bits, + sign_extend, + .. + } => { + let op = match (bits, sign_extend) { + (32, _) => "ldr", + (16, true) => "ldrsh", + (16, false) => "ldrh", + (8, true) => "ldrsb", + (8, false) => "ldrb", + (_, _) => panic!("Invalid bit amount {}", bits), + }; + let rt = rt.show_rru(mb_rru); + let (mem_str, mem) = mem_finalize_for_show(mem, mb_rru, state); + let mem = mem.show_rru(mb_rru); + format!("{}{} {}, {}", mem_str, op, rt, mem) + } + &Inst::LoadAddr { rd, ref mem } => { + let mut ret = String::new(); + let (mem_insts, mem) = mem_finalize(mem, state); + for inst in mem_insts.into_iter() { + ret.push_str(&inst.show_rru(mb_rru)); + } + let inst = match mem { + AMode::RegReg(rn, rm, 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, + rm, + shift: Some(shift), + } + } + AMode::RegOffset12(reg, imm12) => Inst::AluRRImm12 { + alu_op: ALUOp::Add, + rd, + rn: reg, + imm12, + }, + _ => unreachable!(), + }; + ret.push_str(&inst.show_rru(mb_rru)); + ret + } + &Inst::Extend { + rd, + rm, + from_bits, + signed, + } => { + let op = match (from_bits, signed) { + (16, true) => "sxth", + (16, false) => "uxth", + (8, true) => "sxtb", + (8, false) => "uxtb", + _ => panic!("Unsupported extend case: {:?}", self), + }; + let rd = rd.show_rru(mb_rru); + let rm = rm.show_rru(mb_rru); + format!("{} {}, {}", op, rd, rm) + } + &Inst::It { cond, ref insts } => { + let te: String = insts + .iter() + .skip(1) + .map(|i| if i.then { "t" } else { "e" }) + .collect(); + let cond = cond.show_rru(mb_rru); + let mut ret = format!("it{} {}", te, cond); + for inst in insts.into_iter() { + ret.push_str(" ; "); + ret.push_str(&inst.inst.show_rru(mb_rru)); + } + ret + } + &Inst::Push { ref reg_list } => { + assert!(!reg_list.is_empty()); + let first_reg = reg_list[0].show_rru(mb_rru); + let regs: String = reg_list + .iter() + .skip(1) + .map(|r| [",", &r.show_rru(mb_rru)].join(" ")) + .collect(); + format!("push {{{}{}}}", first_reg, regs) + } + &Inst::Pop { ref reg_list } => { + assert!(!reg_list.is_empty()); + let first_reg = reg_list[0].show_rru(mb_rru); + let regs: String = reg_list + .iter() + .skip(1) + .map(|r| [",", &r.show_rru(mb_rru)].join(" ")) + .collect(); + format!("pop {{{}{}}}", first_reg, regs) + } + &Inst::Call { .. } => format!("bl 0"), + &Inst::CallInd { ref info, .. } => { + let rm = info.rm.show_rru(mb_rru); + format!("blx {}", rm) + } + &Inst::LoadExtName { + rt, + ref name, + offset, + srcloc: _srcloc, + } => { + let rt = rt.show_rru(mb_rru); + format!("ldr {}, [pc, #4] ; b 4 ; data {:?} + {}", rt, name, offset) + } + &Inst::Ret => "bx lr".to_string(), + &Inst::VirtualSPOffsetAdj { offset } => format!("virtual_sp_offset_adjust {}", offset), + &Inst::EpiloguePlaceholder => "epilogue placeholder".to_string(), + &Inst::Jump { ref dest } => { + let dest = dest.show_rru(mb_rru); + format!("b {}", dest) + } + &Inst::CondBr { + ref taken, + ref not_taken, + ref cond, + } => { + let taken = taken.show_rru(mb_rru); + let not_taken = not_taken.show_rru(mb_rru); + let c = cond.show_rru(mb_rru); + format!("b{} {} ; b {}", c, taken, not_taken) + } + &Inst::IndirectBr { rm, .. } => { + let rm = rm.show_rru(mb_rru); + format!("bx {}", rm) + } + &Inst::Udf { .. } => "udf #0".to_string(), + &Inst::Bkpt => "bkpt #0".to_string(), + &Inst::TrapIf { cond, .. } => { + let c = cond.invert().show_rru(mb_rru); + format!("b{} 2 ; udf #0", c) + } + } + } +} + +//============================================================================= +// Label fixups and jump veneers. + +/// Different forms of label references for different instruction formats. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum LabelUse { + /// 20-bit branch offset used by 32-bit conditional jumps. + Branch20, + + /// 24-bit branch offset used by 32-bit uncoditional jump instruction. + Branch24, +} + +impl MachInstLabelUse for LabelUse { + /// Alignment for veneer code. Every instruction must be 4-byte-aligned. + const ALIGN: CodeOffset = 2; + + // Branches range: + // 20-bit sign-extended immediate gives us range [-(2^19), 2^19 - 1]. + // Left-shifted by 1 => [-(2^20), 2^20 - 2]. + // PC is start of this instruction + 4 bytes => [-(2^20) + 4, 2^20 + 2]. + // Likewise for Branch24. + + /// Maximum PC-relative range (positive), inclusive. + fn max_pos_range(self) -> CodeOffset { + match self { + LabelUse::Branch20 => (1 << 20) + 2, + LabelUse::Branch24 => (1 << 24) + 2, + } + } + + /// Maximum PC-relative range (negative). + fn max_neg_range(self) -> CodeOffset { + match self { + LabelUse::Branch20 => (1 << 20) - 4, + LabelUse::Branch24 => (1 << 24) - 4, + } + } + + /// Size of window into code needed to do the patch. + fn patch_size(self) -> CodeOffset { + 4 + } + + /// Perform the patch. + fn patch(self, buffer: &mut [u8], use_offset: CodeOffset, label_offset: CodeOffset) { + let off = (label_offset as i64) - (use_offset as i64); + debug_assert!(off <= self.max_pos_range() as i64); + debug_assert!(off >= -(self.max_neg_range() as i64)); + let off = off - 4; + match self { + LabelUse::Branch20 => { + let off = off as u32 >> 1; + let imm11 = (off & 0x7ff) as u16; + let imm6 = ((off >> 11) & 0x3f) as u16; + let j1 = ((off >> 17) & 0x1) as u16; + let j2 = ((off >> 18) & 0x1) as u16; + let s = ((off >> 19) & 0x1) as u16; + let insn_fst = u16::from_le_bytes([buffer[0], buffer[1]]); + let insn_fst = (insn_fst & !0x43f) | imm6 | (s << 10); + let insn_snd = u16::from_le_bytes([buffer[2], buffer[3]]); + let insn_snd = (insn_snd & !0x2fff) | imm11 | (j2 << 11) | (j1 << 13); + buffer[0..2].clone_from_slice(&u16::to_le_bytes(insn_fst)); + buffer[2..4].clone_from_slice(&u16::to_le_bytes(insn_snd)); + } + LabelUse::Branch24 => { + let off = off as u32 >> 1; + let imm11 = (off & 0x7ff) as u16; + let imm10 = ((off >> 11) & 0x3ff) as u16; + let s = ((off >> 23) & 0x1) as u16; + let j1 = (((off >> 22) & 0x1) as u16 ^ s) ^ 0x1; + let j2 = (((off >> 21) & 0x1) as u16 ^ s) ^ 0x1; + let insn_fst = u16::from_le_bytes([buffer[0], buffer[1]]); + let insn_fst = (insn_fst & !0x07ff) | imm10 | (s << 10); + let insn_snd = u16::from_le_bytes([buffer[2], buffer[3]]); + let insn_snd = (insn_snd & !0x2fff) | imm11 | (j2 << 11) | (j1 << 13); + buffer[0..2].clone_from_slice(&u16::to_le_bytes(insn_fst)); + buffer[2..4].clone_from_slice(&u16::to_le_bytes(insn_snd)); + } + } + } + + fn supports_veneer(self) -> bool { + false + } + + fn veneer_size(self) -> CodeOffset { + 0 + } + + fn generate_veneer( + self, + _buffer: &mut [u8], + _veneer_offset: CodeOffset, + ) -> (CodeOffset, LabelUse) { + panic!("Veneer not supported yet.") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn patch_branch20() { + let label_use = LabelUse::Branch20; + let mut buffer = 0x8000_f000_u32.to_le_bytes(); // beq + let use_offset: CodeOffset = 0; + let label_offset: CodeOffset = label_use.max_pos_range(); + label_use.patch(&mut buffer, use_offset, label_offset); + assert_eq!(u16::from_le_bytes([buffer[0], buffer[1]]), 0xf03f); + assert_eq!(u16::from_le_bytes([buffer[2], buffer[3]]), 0xafff); + + let mut buffer = 0x8000_f000_u32.to_le_bytes(); // beq + let use_offset = label_use.max_neg_range(); + let label_offset: CodeOffset = 0; + label_use.patch(&mut buffer, use_offset, label_offset); + assert_eq!(u16::from_le_bytes([buffer[0], buffer[1]]), 0xf400); + assert_eq!(u16::from_le_bytes([buffer[2], buffer[3]]), 0x8000); + } + + #[test] + fn patch_branch24() { + let label_use = LabelUse::Branch24; + let mut buffer = 0x9000_f000_u32.to_le_bytes(); // b + let use_offset: CodeOffset = 0; + let label_offset: CodeOffset = label_use.max_pos_range(); + label_use.patch(&mut buffer, use_offset, label_offset); + assert_eq!(u16::from_le_bytes([buffer[0], buffer[1]]), 0xf3ff); + assert_eq!(u16::from_le_bytes([buffer[2], buffer[3]]), 0x97ff); + + let mut buffer = 0x9000_f000_u32.to_le_bytes(); // b + let use_offset = label_use.max_neg_range(); + let label_offset: CodeOffset = 0; + label_use.patch(&mut buffer, use_offset, label_offset); + assert_eq!(u16::from_le_bytes([buffer[0], buffer[1]]), 0xf400); + assert_eq!(u16::from_le_bytes([buffer[2], buffer[3]]), 0x9000); + } +} diff --git a/cranelift/codegen/src/isa/arm32/inst/regs.rs b/cranelift/codegen/src/isa/arm32/inst/regs.rs new file mode 100644 index 0000000000..55df5c8db3 --- /dev/null +++ b/cranelift/codegen/src/isa/arm32/inst/regs.rs @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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, + } +} diff --git a/cranelift/codegen/src/isa/arm32/lower.rs b/cranelift/codegen/src/isa/arm32/lower.rs new file mode 100644 index 0000000000..7c11ae95ba --- /dev/null +++ b/cranelift/codegen/src/isa/arm32/lower.rs @@ -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>(ctx: &mut C, out: InsnOutput) -> Option { + 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>(ctx: &mut C, out: InsnOutput) -> Writable { + 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>( + 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>(ctx: &mut C, rd: Writable, 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>(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 { + 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 { + 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 { + 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>(&self, ctx: &mut C, ir_inst: IRInst) -> CodegenResult<()> { + lower_inst::lower_insn_to_regs(ctx, ir_inst) + } + + fn lower_branch_group>( + &self, + ctx: &mut C, + branches: &[IRInst], + targets: &[MachLabel], + fallthrough: Option, + ) -> CodegenResult<()> { + lower_inst::lower_branch(ctx, branches, targets, fallthrough) + } + + fn maybe_pinned_reg(&self) -> Option { + None + } +} diff --git a/cranelift/codegen/src/isa/arm32/lower_inst.rs b/cranelift/codegen/src/isa/arm32/lower_inst.rs new file mode 100644 index 0000000000..7a584fe103 --- /dev/null +++ b/cranelift/codegen/src/isa/arm32/lower_inst.rs @@ -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>( + 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>( + ctx: &mut C, + branches: &[IRInst], + targets: &[MachLabel], + fallthrough: Option, +) -> 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(()) +} diff --git a/cranelift/codegen/src/isa/arm32/mod.rs b/cranelift/codegen/src/isa/arm32/mod.rs index c10d51607d..c61d3f27ad 100644 --- a/cranelift/codegen/src/isa/arm32/mod.rs +++ b/cranelift/codegen/src/isa/arm32/mod.rs @@ -1,149 +1,123 @@ -//! ARM 32-bit Instruction Set Architecture. +//! 32-bit ARM Instruction Set Architecture. -mod abi; -mod binemit; -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::ir::condcodes::IntCC; +use crate::ir::Function; use crate::isa::Builder as IsaBuilder; -use crate::isa::{EncInfo, RegClass, RegInfo, TargetIsa}; -use crate::regalloc; -use alloc::borrow::Cow; -use alloc::boxed::Box; -use core::any::Any; -use core::fmt; -use target_lexicon::{Architecture, Triple}; +use crate::machinst::{ + compile, MachBackend, MachCompileResult, ShowWithRRU, TargetIsaAdapter, VCode, +}; +use crate::result::CodegenResult; +use crate::settings; -#[allow(dead_code)] -struct Isa { +use alloc::boxed::Box; +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, - shared_flags: shared_settings::Flags, - isa_flags: settings::Flags, - cpumode: &'static [shared_enc_tables::Level1Entry], + flags: settings::Flags, + reg_universe: RealRegUniverse, } -/// Get an ISA builder for creating ARM32 targets. -pub fn isa_builder(triple: Triple) -> IsaBuilder { - IsaBuilder { - triple, - setup: settings::builder(), - constructor: isa_constructor, +impl Arm32Backend { + /// Create a new ARM32 backend with the given (shared) flags. + pub fn new_with_flags(triple: Triple, flags: settings::Flags) -> Arm32Backend { + let reg_universe = create_reg_universe(); + Arm32Backend { + triple, + flags, + reg_universe, + } + } + + fn compile_vcode( + &self, + func: &Function, + flags: settings::Flags, + ) -> CodegenResult> { + // 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::(func, self, abi) } } -fn isa_constructor( - triple: Triple, - shared_flags: shared_settings::Flags, - builder: shared_settings::Builder, -) -> Box { - let level1 = match triple.architecture { - Architecture::Arm(arm) => { - if arm.is_thumb() { - &enc_tables::LEVEL1_T32[..] - } else { - &enc_tables::LEVEL1_A32[..] - } - } - _ => panic!(), - }; - Box::new(Isa { - triple, - isa_flags: settings::Flags::new(&shared_flags, builder), - shared_flags, - cpumode: level1, - }) -} +impl MachBackend for Arm32Backend { + fn compile_function( + &self, + func: &Function, + want_disasm: bool, + ) -> CodegenResult { + let flags = self.flags(); + let vcode = self.compile_vcode(func, flags.clone())?; + let buffer = vcode.emit(); + let frame_size = vcode.frame_size(); + + let disasm = if want_disasm { + Some(vcode.show_rru(Some(&create_reg_universe()))) + } else { + None + }; + + let buffer = buffer.finish(); + + Ok(MachCompileResult { + buffer, + frame_size, + disasm, + }) + } -impl TargetIsa for Isa { fn name(&self) -> &'static str { "arm32" } - fn triple(&self) -> &Triple { - &self.triple + fn triple(&self) -> Triple { + self.triple.clone() } - fn flags(&self) -> &shared_settings::Flags { - &self.shared_flags + fn flags(&self) -> &settings::Flags { + &self.flags } - fn register_info(&self) -> RegInfo { - registers::INFO.clone() + fn reg_universe(&self) -> &RealRegUniverse { + &self.reg_universe } - fn encoding_info(&self) -> EncInfo { - enc_tables::INFO.clone() + fn unsigned_add_overflow_condition(&self) -> IntCC { + // Carry flag set. + IntCC::UnsignedGreaterThanOrEqual } - fn legal_encodings<'a>( - &'a self, - func: &'a ir::Function, - 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, 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 + fn unsigned_sub_overflow_condition(&self) -> IntCC { + // Carry flag clear. + IntCC::UnsignedLessThan } } -impl fmt::Display for Isa { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}\n{}", self.shared_flags, self.isa_flags) +/// Create a new `isa::Builder`. +pub fn isa_builder(triple: Triple) -> IsaBuilder { + 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)) + }, } } diff --git a/cranelift/codegen/src/isa/arm32/registers.rs b/cranelift/codegen/src/isa/arm32/registers.rs deleted file mode 100644 index df555b4043..0000000000 --- a/cranelift/codegen/src/isa/arm32/registers.rs +++ /dev/null @@ -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)); - } -} diff --git a/cranelift/codegen/src/isa/arm32/settings.rs b/cranelift/codegen/src/isa/arm32/settings.rs deleted file mode 100644 index bef631b2bb..0000000000 --- a/cranelift/codegen/src/isa/arm32/settings.rs +++ /dev/null @@ -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")); diff --git a/cranelift/codegen/src/isa/x64/abi.rs b/cranelift/codegen/src/isa/x64/abi.rs index 004fbcc459..15c7837a3b 100644 --- a/cranelift/codegen/src/isa/x64/abi.rs +++ b/cranelift/codegen/src/isa/x64/abi.rs @@ -59,6 +59,10 @@ pub(crate) struct X64ABIMachineSpec; impl ABIMachineSpec for X64ABIMachineSpec { type I = Inst; + fn word_bits() -> u32 { + 64 + } + fn compute_arg_locs( call_conv: isa::CallConv, params: &[ir::AbiParam], diff --git a/cranelift/codegen/src/machinst/abi_impl.rs b/cranelift/codegen/src/machinst/abi_impl.rs index c332bfb1c8..6b3b0de6db 100644 --- a/cranelift/codegen/src/machinst/abi_impl.rs +++ b/cranelift/codegen/src/machinst/abi_impl.rs @@ -175,6 +175,32 @@ pub trait ABIMachineSpec { /// The instruction type. 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 /// and stack slots. /// @@ -453,7 +479,8 @@ impl ABICalleeImpl { for (stackslot, data) in f.stack_slots.iter() { let off = stack_offset; 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()); stackslots.push(off); } @@ -587,7 +614,12 @@ fn generate_gv( } => { let base = generate_gv::(f, abi, base, insts); 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(); } ref other => panic!("global value for stack limit not supported: {}", other), @@ -603,13 +635,13 @@ fn generate_gv( /// 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 /// in spillslots). -fn ty_from_ty_hint_or_reg_class(r: Reg, ty: Option) -> Type { +fn ty_from_ty_hint_or_reg_class(r: Reg, ty: Option) -> Type { match (ty, r.get_class()) { // If the type is provided (Some(t), _) => t, // If no type is provided, this should be a register spill for a - // safepoint, so we only expect I64 (integer) registers. - (None, RegClass::I64) => I64, + // safepoint, so we only expect I32/I64 (integer) registers. + (None, rc) if rc == M::word_reg_class() => M::word_type(), _ => panic!("Unexpected register class!"), } } @@ -679,19 +711,22 @@ impl ABICallee for ABICalleeImpl { fn gen_copy_reg_to_retval(&self, idx: usize, from_reg: Writable) -> Vec { let mut ret = Vec::new(); + let word_bits = M::word_bits() as u8; match &self.sig.rets[idx] { &ABIArg::Reg(r, ty, ext) => { let from_bits = ty_bits(ty) as u8; let dest_reg = Writable::from_reg(r.to_reg()); 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; ret.push(M::gen_extend( dest_reg, from_reg.to_reg(), signed, from_bits, - /* to_bits = */ 64, + /* to_bits = */ word_bits, )); } _ => ret.push(M::gen_move(dest_reg, from_reg.to_reg(), ty)), @@ -706,18 +741,20 @@ impl ABICallee for ABICalleeImpl { .expect("Argument stack offset greater than 2GB; should hit impl limit first"); // Trash the from_reg; it should be its last use. match (ext, from_bits) { - (ArgumentExtension::Uext, n) | (ArgumentExtension::Sext, n) if n < 64 => { - assert_eq!(RegClass::I64, from_reg.to_reg().get_class()); + (ArgumentExtension::Uext, n) | (ArgumentExtension::Sext, n) + if n < word_bits => + { + assert_eq!(M::word_reg_class(), from_reg.to_reg().get_class()); let signed = ext == ArgumentExtension::Sext; ret.push(M::gen_extend( from_reg, from_reg.to_reg(), signed, from_bits, - /* to_bits = */ 64, + /* to_bits = */ word_bits, )); // Store the extended version. - ty = I64; + ty = M::word_type(); } _ => {} }; @@ -802,7 +839,7 @@ impl ABICallee for ABICalleeImpl { fn load_spillslot(&self, slot: SpillSlot, ty: Type, into_reg: Writable) -> Self::I { // Offset from beginning of spillslot area, which is at nominal SP + stackslots_size. 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; trace!("load_spillslot: slot {:?} -> sp_off {}", slot, sp_off); M::gen_load_stack(StackAMode::NominalSPOffset(sp_off, ty), into_reg, ty) @@ -812,7 +849,7 @@ impl ABICallee for ABICalleeImpl { 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. 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; trace!("store_spillslot: slot {:?} -> sp_off {}", slot, sp_off); M::gen_store_stack(StackAMode::NominalSPOffset(sp_off, ty), from_reg, ty) @@ -832,12 +869,14 @@ impl ABICallee for ABICalleeImpl { state ); 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) .take(map_words as usize) .collect::>(); - 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 { let slot = slot.get() as usize; bits[first_spillslot_word + slot] = true; @@ -853,16 +892,17 @@ impl ABICallee for ABICalleeImpl { 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() { debug_assert!( !self.flags.enable_probestack(), "baldrdash does not expect cranelift to emit stack probes" ); - // FIXME: 64-bit machine assumed. - total_stacksize += self.flags.baldrdash_prologue_words() as u32 * 8; + total_stacksize += self.flags.baldrdash_prologue_words() as u32 * bytes; } - 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; @@ -943,7 +983,7 @@ impl ABICallee for ABICalleeImpl { } fn gen_spill(&self, to_slot: SpillSlot, from_reg: RealReg, ty: Option) -> 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::(from_reg.to_reg(), ty); self.store_spillslot(to_slot, ty, from_reg.to_reg()) } @@ -953,7 +993,7 @@ impl ABICallee for ABICalleeImpl { from_slot: SpillSlot, ty: Option, ) -> 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::(to_reg.to_reg().to_reg(), ty); self.load_spillslot(from_slot, ty, to_reg.map(|r| r.to_reg())) } } @@ -1092,11 +1132,13 @@ impl ABICaller for ABICallerImpl { idx: usize, from_reg: Reg, ) { + let word_rc = M::word_reg_class(); + let word_bits = M::word_bits() as usize; match &self.sig.args[idx] { &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 { ir::ArgumentExtension::Uext => false, ir::ArgumentExtension::Sext => true, @@ -1107,15 +1149,15 @@ impl ABICaller for ABICallerImpl { from_reg, signed, ty_bits(ty) as u8, - 64, + word_bits as u8, )); } &ABIArg::Reg(reg, ty, _) => { ctx.emit(M::gen_move(Writable::from_reg(reg.to_reg()), from_reg, ty)); } &ABIArg::Stack(off, mut ty, ext) => { - if ext != ir::ArgumentExtension::None && ty_bits(ty) < 64 { - assert_eq!(RegClass::I64, from_reg.get_class()); + if ext != ir::ArgumentExtension::None && ty_bits(ty) < word_bits { + assert_eq!(word_rc, from_reg.get_class()); let signed = match ext { ir::ArgumentExtension::Uext => false, ir::ArgumentExtension::Sext => true, @@ -1129,10 +1171,10 @@ impl ABICaller for ABICallerImpl { from_reg, signed, ty_bits(ty) as u8, - 64, + word_bits as u8, )); // Store the extended version. - ty = I64; + ty = M::word_type(); } ctx.emit(M::gen_store_stack( StackAMode::SPOffset(off, ty), @@ -1169,8 +1211,10 @@ impl ABICaller for ABICallerImpl { mem::replace(&mut self.uses, 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 { - 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; ctx.emit(M::gen_get_stack_addr( StackAMode::SPOffset(ret_area_base, I8), @@ -1179,7 +1223,7 @@ impl ABICaller for ABICallerImpl { )); 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 M::gen_call(&self.dest, uses, defs, self.loc, self.opcode, tmp).into_iter() { diff --git a/cranelift/codegen/src/regalloc/pressure.rs b/cranelift/codegen/src/regalloc/pressure.rs index 3783a78e28..aa83037041 100644 --- a/cranelift/codegen/src/regalloc/pressure.rs +++ b/cranelift/codegen/src/regalloc/pressure.rs @@ -274,29 +274,18 @@ impl fmt::Display for Pressure { #[cfg(feature = "arm32")] mod tests { 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 alloc::boxed::Box; use core::borrow::Borrow; - use core::str::FromStr; - use target_lexicon::triple; - // Make an arm32 `TargetIsa`, if possible. - fn arm32() -> Option> { - use crate::isa; - 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)) - } + // Arm32 `TargetIsa` is now `TargetIsaAdapter`, which does not hold any info + // about registers, so we directly access `INFO` from registers-arm32.rs. + include!(concat!(env!("OUT_DIR"), "/registers-arm32.rs")); // Get a register class by name. - fn rc_by_name(isa: &dyn TargetIsa, name: &str) -> RegClass { - isa.register_info() + fn rc_by_name(reginfo: &RegInfo, name: &str) -> RegClass { + reginfo .classes .iter() .find(|rc| rc.name == name) @@ -305,11 +294,10 @@ mod tests { #[test] fn basic_counting() { - let isa = arm32().expect("This test requires arm32 support"); - let isa = isa.borrow(); - let gpr = rc_by_name(isa, "GPR"); - let s = rc_by_name(isa, "S"); - let reginfo = isa.register_info(); + let reginfo = INFO.borrow(); + let gpr = rc_by_name(®info, "GPR"); + let s = rc_by_name(®info, "S"); + let regs = RegisterSet::new(); let mut pressure = Pressure::new(®info, ®s); @@ -333,12 +321,10 @@ mod tests { #[test] fn arm_float_bank() { - let isa = arm32().expect("This test requires arm32 support"); - let isa = isa.borrow(); - let s = rc_by_name(isa, "S"); - let d = rc_by_name(isa, "D"); - let q = rc_by_name(isa, "Q"); - let reginfo = isa.register_info(); + let reginfo = INFO.borrow(); + let s = rc_by_name(®info, "S"); + let d = rc_by_name(®info, "D"); + let q = rc_by_name(®info, "Q"); let regs = RegisterSet::new(); let mut pressure = Pressure::new(®info, ®s); diff --git a/cranelift/codegen/src/regalloc/solver.rs b/cranelift/codegen/src/regalloc/solver.rs index 7416ec9bc7..07b3aba9b9 100644 --- a/cranelift/codegen/src/regalloc/solver.rs +++ b/cranelift/codegen/src/regalloc/solver.rs @@ -1149,24 +1149,14 @@ mod tests { use super::{Move, Solver}; use crate::entity::EntityRef; 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 alloc::boxed::Box; - use core::str::FromStr; - use target_lexicon::triple; + use core::borrow::Borrow; - // Make an arm32 `TargetIsa`, if possible. - fn arm32() -> Option> { - use crate::isa; - 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)) - } + // Arm32 `TargetIsa` is now `TargetIsaAdapter`, which does not hold any info + // about registers, so we directly access `INFO` from registers-arm32.rs. + include!(concat!(env!("OUT_DIR"), "/registers-arm32.rs")); // Get a register class by name. fn rc_by_name(reginfo: &RegInfo, name: &str) -> RegClass { @@ -1207,8 +1197,7 @@ mod tests { #[test] fn simple_moves() { - let isa = arm32().expect("This test requires arm32 support"); - let reginfo = isa.register_info(); + let reginfo = INFO.borrow(); let gpr = rc_by_name(®info, "GPR"); let r0 = gpr.unit(0); let r1 = gpr.unit(1); @@ -1260,8 +1249,7 @@ mod tests { #[test] fn harder_move_cycles() { - let isa = arm32().expect("This test requires arm32 support"); - let reginfo = isa.register_info(); + let reginfo = INFO.borrow(); let s = rc_by_name(®info, "S"); let d = rc_by_name(®info, "D"); let d0 = d.unit(0); @@ -1322,8 +1310,7 @@ mod tests { #[test] fn emergency_spill() { - let isa = arm32().expect("This test requires arm32 support"); - let reginfo = isa.register_info(); + let reginfo = INFO.borrow(); let gpr = rc_by_name(®info, "GPR"); let r0 = gpr.unit(0); let r1 = gpr.unit(1); diff --git a/cranelift/filetests/filetests/vcode/arm32/aluops.clif b/cranelift/filetests/filetests/vcode/arm32/aluops.clif new file mode 100644 index 0000000000..3dcd47cdba --- /dev/null +++ b/cranelift/filetests/filetests/vcode/arm32/aluops.clif @@ -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 diff --git a/cranelift/filetests/filetests/vcode/arm32/bitops.clif b/cranelift/filetests/filetests/vcode/arm32/bitops.clif new file mode 100644 index 0000000000..9ec808c290 --- /dev/null +++ b/cranelift/filetests/filetests/vcode/arm32/bitops.clif @@ -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 diff --git a/cranelift/filetests/filetests/vcode/arm32/cond.clif b/cranelift/filetests/filetests/vcode/arm32/cond.clif new file mode 100644 index 0000000000..2121c8903f --- /dev/null +++ b/cranelift/filetests/filetests/vcode/arm32/cond.clif @@ -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 diff --git a/cranelift/filetests/filetests/vcode/arm32/constants.clif b/cranelift/filetests/filetests/vcode/arm32/constants.clif new file mode 100644 index 0000000000..19a416bcee --- /dev/null +++ b/cranelift/filetests/filetests/vcode/arm32/constants.clif @@ -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 diff --git a/cranelift/filetests/filetests/vcode/arm32/control-flow.clif b/cranelift/filetests/filetests/vcode/arm32/control-flow.clif new file mode 100644 index 0000000000..0822806501 --- /dev/null +++ b/cranelift/filetests/filetests/vcode/arm32/control-flow.clif @@ -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 + diff --git a/cranelift/filetests/filetests/vcode/arm32/extend.clif b/cranelift/filetests/filetests/vcode/arm32/extend.clif new file mode 100644 index 0000000000..0939ecdf2b --- /dev/null +++ b/cranelift/filetests/filetests/vcode/arm32/extend.clif @@ -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 diff --git a/cranelift/filetests/filetests/vcode/arm32/params.clif b/cranelift/filetests/filetests/vcode/arm32/params.clif new file mode 100644 index 0000000000..6215a78022 --- /dev/null +++ b/cranelift/filetests/filetests/vcode/arm32/params.clif @@ -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 diff --git a/cranelift/filetests/filetests/vcode/arm32/shift-rotate.clif b/cranelift/filetests/filetests/vcode/arm32/shift-rotate.clif new file mode 100644 index 0000000000..1acd7a30e0 --- /dev/null +++ b/cranelift/filetests/filetests/vcode/arm32/shift-rotate.clif @@ -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