1362 lines
41 KiB
Rust
1362 lines
41 KiB
Rust
//! This module defines 32-bit ARM specific machine instruction types.
|
|
|
|
#![allow(dead_code)]
|
|
|
|
use crate::binemit::{Addend, CodeOffset, Reloc};
|
|
use crate::ir::types::{B1, B16, B32, B8, I16, I32, I8, IFLAGS};
|
|
use crate::ir::{ExternalName, Opcode, TrapCode, Type};
|
|
use crate::machinst::*;
|
|
use crate::{settings, CodegenError, CodegenResult};
|
|
|
|
use regalloc::{PrettyPrint, 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<Reg>,
|
|
pub defs: Vec<Writable<Reg>>,
|
|
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<Reg>,
|
|
pub defs: Vec<Writable<Reg>>,
|
|
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<Reg>,
|
|
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<Reg>,
|
|
rn: Reg,
|
|
rm: Reg,
|
|
shift: Option<ShiftOpAndAmt>,
|
|
},
|
|
|
|
/// An ALU operation with one register source, which can be optionally shifted
|
|
/// and one register destination.
|
|
AluRRShift {
|
|
alu_op: ALUOp1,
|
|
rd: Writable<Reg>,
|
|
rm: Reg,
|
|
shift: Option<ShiftOpAndAmt>,
|
|
},
|
|
|
|
/// An ALU operation with two register sources and two register destinations.
|
|
AluRRRR {
|
|
alu_op: ALUOp,
|
|
rd_hi: Writable<Reg>,
|
|
rd_lo: Writable<Reg>,
|
|
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<Reg>,
|
|
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<Reg>,
|
|
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<Reg>,
|
|
imm8: UImm8,
|
|
},
|
|
|
|
/// A bit operation with a register source and a register destination.
|
|
BitOpRR {
|
|
bit_op: BitOp,
|
|
rd: Writable<Reg>,
|
|
rm: Reg,
|
|
},
|
|
|
|
/// A mov instruction with a GPR source and a GPR destination.
|
|
Mov {
|
|
rd: Writable<Reg>,
|
|
rm: Reg,
|
|
},
|
|
|
|
/// A move instruction with a 16-bit immediate source and a register destination.
|
|
MovImm16 {
|
|
rd: Writable<Reg>,
|
|
imm16: u16,
|
|
},
|
|
|
|
/// A move top instruction, which writes 16-bit immediate to the top
|
|
/// halfword of the destination register.
|
|
Movt {
|
|
rd: Writable<Reg>,
|
|
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,
|
|
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<Reg>,
|
|
mem: AMode,
|
|
bits: u8,
|
|
sign_extend: bool,
|
|
},
|
|
|
|
/// Load address referenced by `mem` into `rd`.
|
|
LoadAddr {
|
|
rd: Writable<Reg>,
|
|
mem: AMode,
|
|
},
|
|
|
|
/// A sign- or zero-extend operation.
|
|
Extend {
|
|
rd: Writable<Reg>,
|
|
rm: Reg,
|
|
from_bits: u8,
|
|
signed: bool,
|
|
},
|
|
|
|
// An If-Then instruction, which makes up to four instructions conditinal.
|
|
It {
|
|
cond: Cond,
|
|
insts: Vec<CondInst>,
|
|
},
|
|
|
|
/// A push instuction, which stores registers to the stack and updates sp.
|
|
Push {
|
|
reg_list: Vec<Reg>,
|
|
},
|
|
|
|
/// A pop instuction, which load registers from the stack and updates sp.
|
|
Pop {
|
|
reg_list: Vec<Writable<Reg>>,
|
|
},
|
|
|
|
/// A machine call instruction.
|
|
Call {
|
|
info: Box<CallInfo>,
|
|
},
|
|
|
|
/// A machine indirect-call instruction.
|
|
CallInd {
|
|
info: Box<CallIndInfo>,
|
|
},
|
|
|
|
/// Load an inline symbol reference.
|
|
LoadExtName {
|
|
rt: Writable<Reg>,
|
|
name: Box<ExternalName>,
|
|
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<MachLabel>,
|
|
},
|
|
|
|
/// 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: TrapCode,
|
|
},
|
|
|
|
/// An instruction guaranteed to always be undefined and to trigger an illegal instruction at
|
|
/// runtime.
|
|
Udf {
|
|
trap_info: 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<Reg>, 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<Reg>, 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<Reg>, 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,
|
|
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,
|
|
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<RUM: RegUsageMapper>(inst: &mut Inst, mapper: &RUM) {
|
|
fn map_use<RUM: RegUsageMapper>(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<RUM: RegUsageMapper>(m: &RUM, r: &mut Writable<Reg>) {
|
|
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<RUM: RegUsageMapper>(m: &RUM, r: &mut Writable<Reg>) {
|
|
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<RUM: RegUsageMapper>(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<RUM: RegUsageMapper>(&mut self, mapper: &RUM) {
|
|
arm32_map_regs(self, mapper);
|
|
}
|
|
|
|
fn is_move(&self) -> Option<(Writable<Reg>, 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<Reg>, 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<F: FnMut(Type) -> Writable<Reg>>(
|
|
to_regs: ValueRegs<Writable<Reg>>,
|
|
value: u128,
|
|
ty: Type,
|
|
_alloc_tmp: F,
|
|
) -> SmallVec<[Inst; 4]> {
|
|
let to_reg = to_regs
|
|
.only_reg()
|
|
.expect("multi-reg values not supported yet");
|
|
let value = value as u64;
|
|
|
|
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_nop(preferred_size: usize) -> Inst {
|
|
if preferred_size == 0 {
|
|
return Inst::Nop0;
|
|
}
|
|
assert!(preferred_size >= 2);
|
|
Inst::Nop2
|
|
}
|
|
|
|
fn maybe_direct_reload(&self, _reg: VirtualReg, _slot: SpillSlot) -> Option<Inst> {
|
|
None
|
|
}
|
|
|
|
fn rc_for_type(ty: Type) -> CodegenResult<(&'static [RegClass], &'static [Type])> {
|
|
match ty {
|
|
I8 | I16 | I32 | B1 | B8 | B16 | B32 => Ok((&[RegClass::I32], &[I32])),
|
|
IFLAGS => Ok((&[RegClass::I32], &[I32])),
|
|
_ => Err(CodegenError::Unsupported(format!(
|
|
"Unexpected SSA-value type: {}",
|
|
ty
|
|
))),
|
|
}
|
|
}
|
|
|
|
fn gen_jump(target: MachLabel) -> Inst {
|
|
Inst::Jump {
|
|
dest: BranchTarget::Label(target),
|
|
}
|
|
}
|
|
|
|
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::<Vec<_>>()
|
|
.join(" ; ");
|
|
if !mem_str.is_empty() {
|
|
mem_str += " ; ";
|
|
}
|
|
|
|
(mem_str, mem)
|
|
}
|
|
|
|
impl PrettyPrint 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<ShiftOpAndAmt>,
|
|
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,
|
|
} => {
|
|
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.")
|
|
}
|
|
|
|
fn from_reloc(_reloc: Reloc, _addend: Addend) -> Option<LabelUse> {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
}
|