use crate::arm::arm_asm_printer::ArmAsmPrinter;
use crate::arm::arm_calling_convention::ArmCallingConvention;
use crate::arm::arm_frame_lowering::ArmFrameLowering;
use crate::arm::arm_isel::ArmInstructionSelector;
use crate::arm::arm_register_info::{ArmRegisterInfo, AARCH64_REG_COUNT, ARM32_REG_COUNT};
use crate::codegen::{MachineFunction, RegisterAllocator};
use crate::module::Module;
use crate::opcode::Opcode;
use crate::data_layout::DataLayout;
use crate::value::ValueRef;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum OptimizationLevel {
O0 = 0,
O1 = 1,
O2 = 2,
O3 = 3,
Os = 4,
Oz = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelocModel {
Static,
PIC,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodeModel {
Small,
Large,
Tiny,
}
pub const DEFAULT_AARCH64_DATA_LAYOUT: &str = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
pub const DEFAULT_ARM32_DATA_LAYOUT: &str = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64";
#[derive(Debug, Clone)]
pub struct ArmTargetMachine {
pub triple: String,
pub cpu: String,
pub features: String,
pub is_64bit: bool,
pub data_layout: String,
pub opt_level: OptimizationLevel,
pub reloc_model: RelocModel,
pub code_model: CodeModel,
}
impl ArmTargetMachine {
pub fn new(triple: &str, cpu: &str, features: &str) -> Self {
let is_64bit = Self::detect_64bit_from_triple(triple);
let data_layout = if is_64bit {
DEFAULT_AARCH64_DATA_LAYOUT.to_string()
} else {
DEFAULT_ARM32_DATA_LAYOUT.to_string()
};
let code_model = CodeModel::Small;
ArmTargetMachine {
triple: triple.to_string(),
cpu: cpu.to_string(),
features: features.to_string(),
is_64bit,
data_layout,
opt_level: OptimizationLevel::O0,
reloc_model: RelocModel::Static,
code_model,
}
}
fn detect_64bit_from_triple(triple: &str) -> bool {
let lower = triple.to_lowercase();
lower.starts_with("aarch64") || lower.starts_with("arm64")
}
pub fn with_opt_level(mut self, level: OptimizationLevel) -> Self {
self.opt_level = level;
self
}
pub fn with_reloc_model(mut self, model: RelocModel) -> Self {
self.reloc_model = model;
self
}
pub fn with_code_model(mut self, model: CodeModel) -> Self {
self.code_model = model;
self
}
pub fn get_data_layout(&self) -> &str {
&self.data_layout
}
pub fn get_target_triple(&self) -> &str {
&self.triple
}
pub fn get_cpu(&self) -> &str {
&self.cpu
}
pub fn get_features(&self) -> &str {
&self.features
}
pub fn is_64bit(&self) -> bool {
self.is_64bit
}
pub fn get_opt_level(&self) -> OptimizationLevel {
self.opt_level
}
pub fn get_reloc_model(&self) -> RelocModel {
self.reloc_model
}
pub fn get_code_model(&self) -> CodeModel {
self.code_model
}
pub fn is_pic(&self) -> bool {
matches!(self.reloc_model, RelocModel::PIC)
}
pub fn add_analysis_passes(&self) -> Vec<String> {
let mut passes = vec![
"target-library-info".to_string(),
"target-transform-info".to_string(),
"assumption-cache-tracker".to_string(),
];
if self.opt_level >= OptimizationLevel::O1 {
passes.push("loop-info".to_string());
passes.push("scalar-evolution".to_string());
passes.push("basic-aa".to_string());
passes.push("domtree".to_string());
}
if self.opt_level >= OptimizationLevel::O2 {
passes.push("branch-prob".to_string());
passes.push("block-freq".to_string());
passes.push("globals-aa".to_string());
}
passes
}
pub fn add_ir_passes(&self) -> Vec<String> {
let mut passes = Vec::new();
match self.opt_level {
OptimizationLevel::O0 => {
passes.push("mem2reg".to_string());
}
OptimizationLevel::O1 => {
passes.push("mem2reg".to_string());
passes.push("instcombine".to_string());
passes.push("simplifycfg".to_string());
passes.push("reassociate".to_string());
passes.push("early-cse".to_string());
passes.push("inline".to_string());
}
OptimizationLevel::O2 => {
passes.push("mem2reg".to_string());
passes.push("instcombine".to_string());
passes.push("simplifycfg".to_string());
passes.push("reassociate".to_string());
passes.push("early-cse".to_string());
passes.push("inline".to_string());
passes.push("gvn".to_string());
passes.push("licm".to_string());
passes.push("loop-rotate".to_string());
passes.push("indvars".to_string());
passes.push("loop-unroll".to_string());
passes.push("slp-vectorizer".to_string());
}
OptimizationLevel::O3 => {
passes.push("mem2reg".to_string());
passes.push("instcombine".to_string());
passes.push("simplifycfg".to_string());
passes.push("reassociate".to_string());
passes.push("early-cse".to_string());
passes.push("inline".to_string());
passes.push("gvn".to_string());
passes.push("licm".to_string());
passes.push("loop-rotate".to_string());
passes.push("indvars".to_string());
passes.push("loop-unroll".to_string());
passes.push("slp-vectorizer".to_string());
passes.push("loop-vectorize".to_string());
passes.push("aggressive-instcombine".to_string());
}
OptimizationLevel::Os | OptimizationLevel::Oz => {
passes.push("mem2reg".to_string());
passes.push("instcombine".to_string());
passes.push("simplifycfg".to_string());
passes.push("inline".to_string());
passes.push("gvn".to_string());
passes.push("loop-rotate".to_string());
passes.push("indvars".to_string());
passes.push("mergefunc".to_string());
}
}
passes
}
pub fn add_isel_passes(&self) -> Vec<String> {
vec![
"arm-isel".to_string(),
"arm-legalize-dag".to_string(),
"arm-dag-combine".to_string(),
]
}
pub fn add_machine_passes(&self) -> Vec<String> {
let mut passes = vec![
"arm-frame-lowering".to_string(),
"prolog-epilog-insertion".to_string(),
"register-allocation".to_string(),
"virtual-register-rewriter".to_string(),
"stack-slot-coloring".to_string(),
];
if self.opt_level >= OptimizationLevel::O1 {
passes.push("arm-peephole-opt".to_string());
passes.push("machine-cse".to_string());
passes.push("branch-folding".to_string());
}
if self.opt_level >= OptimizationLevel::O2 {
passes.push("machine-licm".to_string());
passes.push("machine-block-placement".to_string());
passes.push("arm-csel-optimization".to_string());
}
if self.opt_level >= OptimizationLevel::O3 {
passes.push("machine-scheduler".to_string());
passes.push("arm-fixup-cortex-a53-835769".to_string());
}
passes
}
pub fn emit_assembly(&self, module: &Module) -> String {
let mut asm_output = String::new();
asm_output.push_str("\t.arch ");
if self.is_64bit {
asm_output.push_str("armv8-a\n");
} else {
asm_output.push_str("armv7-a\n");
}
asm_output.push_str(&format!("\t.file\t\"{}\"\n", module.source_filename));
asm_output.push_str("\t.text\n");
for func in &module.functions {
let f = func.borrow();
if f.subclass != crate::value::SubclassKind::Function {
continue;
}
let mut mf = MachineFunction::new(&f.name);
let mut isel = ArmInstructionSelector::new(self.is_64bit);
isel.select(&mut mf, func);
let mut ra = ArmRegisterAllocator::new(self.is_64bit);
ra.allocate(&mut mf);
let conv = if self.is_64bit {
ArmCallingConvention::AAPCS64
} else {
ArmCallingConvention::AAPCS
};
let frame = ArmFrameLowering::new(conv);
let frame_info = frame.build_frame_info(&mf);
let prologue = frame.emit_prologue(&frame_info);
let epilogue = frame.emit_epilogue(&frame_info);
if !mf.blocks.is_empty() {
let first = &mut mf.blocks[0];
let mut new_instructions = prologue;
new_instructions.append(&mut first.instructions);
first.instructions = new_instructions;
let last_idx = mf.blocks.len() - 1;
let last = &mut mf.blocks[last_idx];
last.instructions.append(&mut epilogue.clone());
}
let mut printer = ArmAsmPrinter::new(self.is_64bit);
printer.print_function(&mf);
asm_output.push_str(&printer.output);
asm_output.push('\n');
}
for g in &module.globals {
let g_ref = g.borrow();
asm_output.push_str(&format!(
"\t.globl\t{}\n\t.type\t{},%object\n",
g_ref.name, g_ref.name
));
}
asm_output
}
pub fn emit_object(&self, module: &Module) -> Vec<u8> {
let _ = module; self.build_minimal_elf_object()
}
fn build_minimal_elf_object(&self) -> Vec<u8> {
let mut buf = Vec::new();
if self.is_64bit {
buf.extend_from_slice(&[
0x7f, b'E', b'L', b'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]);
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&183u16.to_le_bytes()); buf.extend_from_slice(&1u32.to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes()); buf.extend_from_slice(&0u64.to_le_bytes()); buf.extend_from_slice(&0u64.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
let e_ehsize: u16 = 64;
buf.extend_from_slice(&e_ehsize.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&64u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes());
let aarch64_ret: &[u8] = &[0xc0, 0x03, 0x5f, 0xd6];
buf.extend_from_slice(aarch64_ret);
} else {
buf.extend_from_slice(&[
0x7f, b'E', b'L', b'F', 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]);
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&40u16.to_le_bytes()); buf.extend_from_slice(&1u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0x5000000u32.to_le_bytes());
let e_ehsize: u16 = 52;
buf.extend_from_slice(&e_ehsize.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&40u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes());
let arm32_bx_lr: &[u8] = &[0x1e, 0xff, 0x2f, 0xe1];
buf.extend_from_slice(arm32_bx_lr);
}
buf
}
pub fn describe(&self) -> String {
format!(
"ArmTargetMachine {{ triple: \"{}\", cpu: \"{}\", features: \"{}\", \
is_64bit: {}, opt_level: {:?}, reloc_model: {:?}, code_model: {:?} }}",
self.triple,
self.cpu,
self.features,
self.is_64bit,
self.opt_level,
self.reloc_model,
self.code_model,
)
}
}
pub struct ArmRegisterAllocator {
pub assignments: HashMap<crate::codegen::VirtReg, crate::codegen::PhysReg>,
available: Vec<crate::codegen::PhysReg>,
}
impl ArmRegisterAllocator {
pub fn new(is_64bit: bool) -> Self {
let available = if is_64bit {
vec![
X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15,
]
.into_iter()
.map(|r| r as crate::codegen::PhysReg)
.collect()
} else {
vec![R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10]
.into_iter()
.map(|r| r as crate::codegen::PhysReg)
.collect()
};
Self {
assignments: HashMap::new(),
available,
}
}
pub fn allocate(&mut self, mf: &mut MachineFunction) {
let mut next_phys = 0usize;
for bb in &mut mf.blocks {
for mi in &mut bb.instructions {
if let Some(vreg) = mi.def {
if !self.assignments.contains_key(&vreg) {
if next_phys < self.available.len() {
self.assignments.insert(vreg, self.available[next_phys]);
next_phys += 1;
}
}
}
}
}
}
}
use crate::arm::arm_register_info::{
R0, R1, R10, R2, R3, R4, R5, R6, R7, R8, R9, X0, X1, X10, X11, X12, X13, X14, X15, X2, X3, X4,
X5, X6, X7, X8, X9,
};
#[cfg(test)]
mod tests {
use super::*;
use crate::arm::arm_calling_convention::ArmCallingConvention;
use crate::arm::arm_frame_lowering::ArmFrameLowering;
use crate::arm::arm_isel::ArmInstructionSelector;
use crate::arm::arm_register_info::{ArmRegisterInfo, AARCH64_REG_COUNT, ARM32_REG_COUNT};
#[test]
fn test_construct_aarch64_target_machine() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "+neon");
assert_eq!(tm.get_target_triple(), "aarch64-unknown-linux-gnu");
assert_eq!(tm.get_cpu(), "generic");
assert_eq!(tm.get_features(), "+neon");
assert!(tm.is_64bit());
assert_eq!(tm.get_opt_level(), OptimizationLevel::O0);
assert_eq!(tm.get_reloc_model(), RelocModel::Static);
}
#[test]
fn test_construct_arm32_target_machine() {
let tm = ArmTargetMachine::new("armv7-unknown-linux-gnueabi", "cortex-a9", "");
assert!(!tm.is_64bit());
assert_eq!(tm.get_code_model(), CodeModel::Small);
}
#[test]
fn test_construct_arm64_alias() {
let tm = ArmTargetMachine::new("arm64-apple-darwin", "apple-a13", "");
assert!(tm.is_64bit());
assert!(tm.get_target_triple().contains("arm64"));
}
#[test]
fn test_with_opt_level() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "")
.with_opt_level(OptimizationLevel::O3);
assert_eq!(tm.get_opt_level(), OptimizationLevel::O3);
}
#[test]
fn test_with_reloc_model() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "")
.with_reloc_model(RelocModel::PIC);
assert!(tm.is_pic());
}
#[test]
fn test_data_layout_aarch64() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "");
let dl = tm.get_data_layout();
assert!(dl.starts_with("e-m:e-i8:8:32"));
assert!(dl.contains("S128"));
assert!(dl.contains("i64:64"));
}
#[test]
fn test_data_layout_arm32() {
let tm = ArmTargetMachine::new("arm-unknown-linux-gnueabihf", "generic", "");
let dl = tm.get_data_layout();
assert!(dl.starts_with("e-m:e-p:32:32"));
assert!(dl.contains("S64"));
}
#[test]
fn test_analysis_passes_o0() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "");
let passes = tm.add_analysis_passes();
assert!(!passes.is_empty());
assert!(passes.contains(&"target-library-info".to_string()));
}
#[test]
fn test_analysis_passes_o2() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "")
.with_opt_level(OptimizationLevel::O2);
let passes = tm.add_analysis_passes();
assert!(passes.contains(&"branch-prob".to_string()));
assert!(passes.contains(&"globals-aa".to_string()));
}
#[test]
fn test_ir_passes_o0_minimal() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "");
let passes = tm.add_ir_passes();
assert_eq!(passes, vec!["mem2reg"]);
}
#[test]
fn test_ir_passes_o2_has_gvn() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "")
.with_opt_level(OptimizationLevel::O2);
let passes = tm.add_ir_passes();
assert!(passes.contains(&"gvn".to_string()));
assert!(passes.contains(&"licm".to_string()));
assert!(passes.contains(&"loop-unroll".to_string()));
}
#[test]
fn test_ir_passes_o3_has_vectorize() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "")
.with_opt_level(OptimizationLevel::O3);
let passes = tm.add_ir_passes();
assert!(passes.contains(&"loop-vectorize".to_string()));
}
#[test]
fn test_ir_passes_os_favors_size() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "")
.with_opt_level(OptimizationLevel::Os);
let passes = tm.add_ir_passes();
assert!(passes.contains(&"mergefunc".to_string()));
assert!(!passes.contains(&"loop-unroll".to_string()));
}
#[test]
fn test_isel_passes() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "");
let passes = tm.add_isel_passes();
assert!(passes.contains(&"arm-isel".to_string()));
}
#[test]
fn test_machine_passes_o0() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "");
let passes = tm.add_machine_passes();
assert!(passes.contains(&"arm-frame-lowering".to_string()));
assert!(passes.contains(&"register-allocation".to_string()));
}
#[test]
fn test_machine_passes_o3() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "")
.with_opt_level(OptimizationLevel::O3);
let passes = tm.add_machine_passes();
assert!(passes.contains(&"machine-scheduler".to_string()));
}
#[test]
fn test_emit_object_aarch64() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "generic", "");
let module = Module::new("test");
let obj = tm.emit_object(&module);
assert_eq!(&obj[0..4], &[0x7f, b'E', b'L', b'F']);
assert_eq!(obj[4], 2);
}
#[test]
fn test_emit_object_arm32() {
let tm = ArmTargetMachine::new("arm-unknown-linux-gnueabi", "generic", "");
let module = Module::new("test");
let obj = tm.emit_object(&module);
assert_eq!(&obj[0..4], &[0x7f, b'E', b'L', b'F']);
assert_eq!(obj[4], 1);
}
#[test]
fn test_describe() {
let tm = ArmTargetMachine::new("aarch64-unknown-linux-gnu", "cortex-a72", "+neon");
let desc = tm.describe();
assert!(desc.contains("aarch64"));
assert!(desc.contains("cortex-a72"));
assert!(desc.contains("+neon"));
assert!(desc.contains("is_64bit: true"));
}
#[test]
fn test_register_allocator_aarch64() {
let mut ra = ArmRegisterAllocator::new(true);
assert_eq!(ra.assignments.len(), 0);
assert_eq!(ra.available.len(), 16);
let mut mf = MachineFunction::new("test");
let vreg = mf.new_vreg();
let mut mi = crate::codegen::MachineInstr::new(0).with_def(vreg);
mf.blocks.push(crate::codegen::MachineBasicBlock {
name: "entry".to_string(),
instructions: vec![mi],
successors: vec![],
});
ra.allocate(&mut mf);
assert_eq!(ra.assignments.len(), 1);
}
#[test]
fn test_register_allocator_arm32() {
let mut ra = ArmRegisterAllocator::new(false);
assert_eq!(ra.available.len(), 11); }
}