Commit f5a44010 authored by Isaac Oscar Gariano's avatar Isaac Oscar Gariano

Implemented Aarch64 compiler backend

parent 0bf63ab8
......@@ -8,4 +8,25 @@ fn main() {
gcc::Config::new().flag("-O3").flag("-c")
.file("src/runtime/swap_stack_x64_sysv.S")
.compile("libswap_stack.a");
}
#[cfg(target_os = "linux")]
#[cfg(target_arch = "aarch64")]
fn main() {
gcc::compile_library("libruntime.a", &["src/runtime/runtime_aarch64_sysv.c"]);
gcc::Config::new().flag("-O3").flag("-c")
.file("src/runtime/swap_stack_aarch64_sysv.S")
.compile("libswap_stack.a");
}
// This is here to enable cross compiling from windows/x86_64 to linux/aarch64
#[cfg(target_os = "windows")]
#[cfg(target_arch = "x86_64")]
fn main() {
gcc::compile_library("libruntime.a", &["src/runtime/runtime_aarch64_sysv.c"]);
gcc::Config::new().flag("-O3").flag("-c")
.file("src/runtime/swap_stack_aarch64_sysv.S")
.compile("libswap_stack.a");
}
\ No newline at end of file
This diff is collapsed.
......@@ -186,21 +186,81 @@ pub enum CmpOp {
}
impl CmpOp {
// Returns the CmpOp c, such that (a self b) is equivelent to (b c a)
pub fn swap_operands(self) -> CmpOp {
use op::CmpOp::*;
match self {
EQ => EQ,
NE => NE,
SGE => SLE,
SLE => SGE,
SGT => SLT,
SLT => SGT,
UGE => ULE,
ULE => UGE,
UGT => ULT,
ULT => UGT,
FOGE => FOLE,
FOLE => FOGE,
FOGT => FOLT,
FOLT => FOGT,
FUGE => FULE,
FULE => FUGE,
FUGT => FULT,
FULT => FUGT,
_ => self, // all other comparisons are reflexive
}
}
pub fn invert(self) -> CmpOp {
use op::CmpOp::*;
match self {
EQ => NE,
NE => EQ,
FOEQ => FUNE,
FUNE => FOEQ,
FUGE => FOLT,
FOLT => FUGE,
FUNO => FORD,
FORD => FUNO,
UGT => ULE,
ULE => UGT,
FUGT => FOLE,
FOLE => FUGT,
SGE => SLT,
SLT => SGE,
FOGE => FULT,
FULT => FOGE,
SGT => SLE,
SLE => SGT,
SLT => SGE,
FOGT => FULE,
FULE => FOGT,
UGE => ULT,
UGT => ULE,
ULE => UGT,
ULT => UGE,
_ => unimplemented!()
FUEQ => FONE,
FONE => FUEQ,
FFALSE => FTRUE,
FTRUE => FFALSE,
}
}
pub fn is_signed(self) -> bool {
use op::CmpOp::*;
match self {
SGE | SLT | SGT | SLE => true,
_ => false
}
}
}
......
......@@ -32,7 +32,15 @@ lazy_static! {
pub static ref UINT64_TYPE : P<MuType> = P(
MuType::new(new_internal_id(), MuType_::int(64))
);
pub static ref UINT128_TYPE : P<MuType> = P(
MuType::new(new_internal_id(), MuType_::int(128))
);
pub static ref FLOAT_TYPE : P<MuType> = P(
MuType::new(new_internal_id(), MuType_::float())
);
pub static ref DOUBLE_TYPE : P<MuType> = P(
MuType::new(new_internal_id(), MuType_::double())
);
......@@ -48,6 +56,8 @@ lazy_static! {
UINT16_TYPE.clone(),
UINT32_TYPE.clone(),
UINT64_TYPE.clone(),
UINT128_TYPE.clone(),
FLOAT_TYPE.clone(),
DOUBLE_TYPE.clone(),
VOID_TYPE.clone()
];
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
use ast::ir::*;
use ast::inst::Instruction_::*;
use vm::context::VM;
use compiler::CompilerPass;
pub struct InstructionSelection {
name: &'static str
}
impl InstructionSelection {
pub fn new() -> InstructionSelection {
InstructionSelection{name: "Instruction Selection (ARM)"}
}
}
impl CompilerPass for InstructionSelection {
fn name(&self) -> &'static str {
self.name
}
#[allow(unused_variables)]
fn start_function(&mut self, vm: &VM, func: &mut MuFunctionVersion) {
debug!("{}", self.name());
}
}
pub use inst_sel;
pub const GPR_COUNT : usize = 16;
pub const FPR_COUNT : usize = 16;
#![allow(unused_variables)]
/*#![allow(unused_variables)]
use compiler::backend::AOT_EMIT_CONTEXT_FILE;
use compiler::backend::RegGroup;
......@@ -655,7 +655,7 @@ impl MachineCode for ASMCode {
// self.code.insert(index, ASMInst::nop());
}
fn remove_unnecessary_callee_saved(&mut self, used_callee_saved: Vec<MuID>) -> Vec<MuID> {
fn remove_unnecessary_callee_saved(&mut self, used_callee_saved: Vec<MuID>) -> (Vec<MuID>, usize) {
// we always save rbp
let rbp = x86_64::RBP.extract_ssa_id().unwrap();
// every push/pop will use/define rsp
......@@ -678,6 +678,7 @@ impl MachineCode for ASMCode {
let mut inst_to_remove = vec![];
let mut regs_to_remove = vec![];
let mut kept_callee_saved = 0;
for i in 0..self.number_of_insts() {
let ref inst = self.code[i];
......@@ -687,10 +688,14 @@ impl MachineCode for ASMCode {
Some(op) => {
// if this push/pop instruction is about a callee saved register
// and the register is not used, we set the instruction as nop
if x86_64::is_callee_saved(op) && !used_callee_saved.contains(&op) {
trace!("removing instruction {:?} for save/restore unnecessary callee saved regs", inst);
regs_to_remove.push(op);
inst_to_remove.push(i);
if x86_64::is_callee_saved(op) {
if used_callee_saved.contains(&op) {
kept_callee_saved += 1;
} else {
trace!("removing instruction {:?} for save/restore unnecessary callee saved regs", inst);
regs_to_remove.push(op);
inst_to_remove.push(i);
}
}
}
None => {}
......@@ -702,7 +707,7 @@ impl MachineCode for ASMCode {
self.set_inst_nop(i);
}
regs_to_remove
(regs_to_remove, kept_callee_saved)
}
#[allow(unused_variables)]
......@@ -3639,3 +3644,4 @@ pub fn spill_rewrite(
spilled_scratch_temps
}
*/
\ No newline at end of file
......@@ -267,7 +267,7 @@ fn emit_muir_dot_inner(file: &mut File,
}
}
file.write("}".as_bytes()).unwrap();
file.write("}\n".as_bytes()).unwrap();
}
fn emit_mc_dot(func: &MuFunctionVersion, vm: &VM) {
......@@ -338,7 +338,7 @@ fn emit_mc_dot(func: &MuFunctionVersion, vm: &VM) {
}
}
file.write("}".as_bytes()).unwrap();
file.write("}\n".as_bytes()).unwrap();
}
impl CompilerPass for CodeEmission {
......
#[cfg(target_arch = "x86_64")]
pub use compiler::backend::x86_64::inst_sel::*;
#[cfg(target_arch = "arm")]
pub use compiler::backend::arm::inst_sel::*;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::inst_sel::*;
......@@ -55,11 +55,40 @@ pub use compiler::backend::x86_64::emit_context_with_reloc;
#[cfg(target_arch = "x86_64")]
pub use compiler::backend::x86_64::spill_rewrite;
// ARM
// aarch64
#[cfg(target_arch = "arm")]
#[path = "arch/arm/mod.rs"]
mod arm;
#[cfg(target_arch = "aarch64")]
#[path = "arch/aarch64/mod.rs"]
pub mod aarch64;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::estimate_insts_for_ir;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::init_machine_regs_for_func;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::is_aliased;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::get_color_for_precolored;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::number_of_regs_in_group;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::number_of_all_regs;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::all_regs;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::all_usable_regs;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::pick_group_for_reg;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::is_callee_saved;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::emit_code;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::emit_context;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::emit_context_with_reloc;
#[cfg(target_arch = "aarch64")]
pub use compiler::backend::aarch64::spill_rewrite;
// common data structure with target specific info
......
......@@ -70,7 +70,7 @@ impl RegisterAllocation {
let used_callee_saved: Vec<MuID> = used_callee_saved.into_iter().collect();
let n_used_callee_saved = used_callee_saved.len();
let removed_callee_saved = coloring.cf.mc_mut().remove_unnecessary_callee_saved(used_callee_saved);
let (removed_callee_saved, n_kept_callee_saved) = coloring.cf.mc_mut().remove_unnecessary_callee_saved(used_callee_saved);
for reg in removed_callee_saved {
coloring.cf.frame.remove_record_for_callee_saved_reg(reg);
}
......@@ -78,7 +78,7 @@ impl RegisterAllocation {
// patch frame size
// size for callee saved regs
let size_for_callee_saved_regs = n_used_callee_saved * POINTER_SIZE;
let size_for_callee_saved_regs = n_kept_callee_saved * POINTER_SIZE;
trace!("callee saved registers used {} bytes", size_for_callee_saved_regs);
let total_frame_size = coloring.cf.frame.cur_size();
......
......@@ -377,4 +377,29 @@ fn add_machine_specific_regs_at_func_start(alive: &mut AliveEntries) {
alive.new_alive_reg(x86_64::R13.id());
alive.new_alive_reg(x86_64::R14.id());
alive.new_alive_reg(x86_64::R15.id());
}
#[cfg(target_arch = "aarch64")]
fn add_machine_specific_regs_at_func_start(alive: &mut AliveEntries) {
use compiler::backend::aarch64;
// the instruction pointer, stack pointer, link register and frame pointer, always have valid values
alive.new_alive_reg(aarch64::SP.id());
alive.new_alive_reg(aarch64::LR.id());
alive.new_alive_reg(aarch64::FP.id());
// callee saved regs are alive
alive.new_alive_reg(aarch64::X28.id());
alive.new_alive_reg(aarch64::X27.id());
alive.new_alive_reg(aarch64::X26.id());
alive.new_alive_reg(aarch64::X25.id());
alive.new_alive_reg(aarch64::X24.id());
alive.new_alive_reg(aarch64::X23.id());
alive.new_alive_reg(aarch64::X22.id());
alive.new_alive_reg(aarch64::X21.id());
alive.new_alive_reg(aarch64::X20.id());
alive.new_alive_reg(aarch64::X19.id());
// platform register, reserved (never use it)
alive.new_alive_reg(aarch64::PR.id());
}
\ No newline at end of file
use ast::ir::*;
use ast::ptr::*;
use ast::types::*;
use runtime::ValueLocation;
use std::fmt;
use std::collections::HashMap;
use utils::POINTER_SIZE;
use vm::VM;
// | previous frame ...
// |---------------
// | return address
// | old RBP <- RBP
// | callee saved
// | spilled
// |---------------
// | alloca area
#[derive(RustcEncodable, RustcDecodable, Clone)]
pub struct Frame {
func_ver_id: MuID,
cur_offset: isize, // offset to frame base pointer
pub argument_by_reg: HashMap<MuID, P<Value>>,
pub argument_by_stack: HashMap<MuID, P<Value>>,
pub allocated: HashMap<MuID, FrameSlot>,
// (callsite, destination address)
exception_callsites: Vec<(ValueLocation, ValueLocation)>
}
impl fmt::Display for Frame {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "\nFrame for FuncVer {} {{", self.func_ver_id).unwrap();
writeln!(f, " allocated slots:").unwrap();
for slot in self.allocated.values() {
writeln!(f, " {}", slot).unwrap();
}
writeln!(f, " exception callsites:").unwrap();
for &(ref callsite, ref dest) in self.exception_callsites.iter() {
writeln!(f, " callsite: {} -> {}", callsite, dest).unwrap()
}
writeln!(f, " cur offset: {}", self.cur_offset).unwrap();
writeln!(f, "}}")
}
}
impl Frame {
pub fn new(func_ver_id: MuID) -> Frame {
Frame {
func_ver_id: func_ver_id,
cur_offset: - (POINTER_SIZE as isize * 1), // reserve for old RBP
argument_by_reg: HashMap::new(),
argument_by_stack: HashMap::new(),
allocated: HashMap::new(),
exception_callsites: vec![]
}
}
#[cfg(target_arch = "x86_64")]
pub fn cur_size(&self) -> usize {
// frame size is a multiple of 16 bytes
let size = self.cur_offset.abs() as usize;
// align size to a multiple of 16 bytes
let size = (size + 16 - 1) & !(16 - 1);
debug_assert!(size % 16 == 0);
size
}
pub fn add_argument_by_reg(&mut self, temp: MuID, reg: P<Value>) {
self.argument_by_reg.insert(temp, reg);
}
pub fn add_argument_by_stack(&mut self, temp: MuID, stack_slot: P<Value>) {
self.argument_by_stack.insert(temp, stack_slot);
}
pub fn alloc_slot_for_callee_saved_reg(&mut self, reg: P<Value>, vm: &VM) -> P<Value> {
let slot = self.alloc_slot(&reg, vm);
slot.make_memory_op(reg.ty.clone(), vm)
}
pub fn remove_record_for_callee_saved_reg(&mut self, reg: MuID) {
self.allocated.remove(&reg);
}
pub fn alloc_slot_for_spilling(&mut self, reg: P<Value>, vm: &VM) -> P<Value> {
let slot = self.alloc_slot(&reg, vm);
slot.make_memory_op(reg.ty.clone(), vm)
}
pub fn get_exception_callsites(&self) -> &Vec<(ValueLocation, ValueLocation)> {
&self.exception_callsites
}
pub fn add_exception_callsite(&mut self, callsite: ValueLocation, dest: ValueLocation) {
trace!("add exception callsite: {} to dest {}", callsite, dest);
self.exception_callsites.push((callsite, dest));
}
fn alloc_slot(&mut self, val: &P<Value>, vm: &VM) -> &FrameSlot {
let id = val.id();
let ret = FrameSlot {
offset: self.cur_offset,
value: val.clone()
};
self.cur_offset -= vm.get_type_size(val.ty.id()) as isize;
self.allocated.insert(id, ret);
self.allocated.get(&id).unwrap()
}
}
#[derive(RustcEncodable, RustcDecodable, Clone)]
pub struct FrameSlot {
pub offset: isize,
pub value: P<Value>
}
impl fmt::Display for FrameSlot {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}(RBP): {}", self.offset, self.value)
}
}
impl FrameSlot {
#[cfg(target_arch = "x86_64")]
pub fn make_memory_op(&self, ty: P<MuType>, vm: &VM) -> P<Value> {
use compiler::backend::x86_64;
P(Value{
hdr: MuEntityHeader::unnamed(vm.next_id()),
ty: ty.clone(),
v: Value_::Memory(
MemoryLocation::Address{
base: x86_64::RBP.clone(),
offset: Some(Value::make_int_const(vm.next_id(), self.offset as u64)),
index: None,
scale: None
}
)
})
}
}
use ast::ir::*;
use ast::ptr::*;
use ast::types::*;
use runtime::ValueLocation;
use std::fmt;
use std::collections::HashMap;
use utils::POINTER_SIZE;
use vm::VM;
// | previous frame ...
// |---------------
// | return address
// | old RBP <- RBP
// | callee saved
// | spilled
// |---------------
// | alloca area
#[derive(RustcEncodable, RustcDecodable, Clone)]
pub struct Frame {
func_ver_id: MuID,
cur_offset: isize, // offset to frame base pointer
pub argument_by_reg: HashMap<MuID, P<Value>>,
pub argument_by_stack: HashMap<MuID, P<Value>>,
pub allocated: HashMap<MuID, FrameSlot>,
// (callsite, destination address)
exception_callsites: Vec<(ValueLocation, ValueLocation)>
}
impl fmt::Display for Frame {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "\nFrame for FuncVer {} {{", self.func_ver_id).unwrap();
writeln!(f, " allocated slots:").unwrap();
for slot in self.allocated.values() {
writeln!(f, " {}", slot).unwrap();
}
writeln!(f, " exception callsites:").unwrap();
for &(ref callsite, ref dest) in self.exception_callsites.iter() {
writeln!(f, " callsite: {} -> {}", callsite, dest).unwrap()
}
writeln!(f, " cur offset: {}", self.cur_offset).unwrap();
writeln!(f, "}}")
}
}
impl Frame {
pub fn new(func_ver_id: MuID) -> Frame {
Frame {
func_ver_id: func_ver_id,
cur_offset: - (POINTER_SIZE as isize * 1), // reserve for old RBP
argument_by_reg: HashMap::new(),
argument_by_stack: HashMap::new(),
allocated: HashMap::new(),
exception_callsites: vec![]
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub fn cur_size(&self) -> usize {
// frame size is a multiple of 16 bytes
let size = self.cur_offset.abs() as usize;
// align size to a multiple of 16 bytes
let size = (size + 16 - 1) & !(16 - 1);
debug_assert!(size % 16 == 0);
size
}
pub fn add_argument_by_reg(&mut self, temp: MuID, reg: P<Value>) {
self.argument_by_reg.insert(temp, reg);
}
pub fn add_argument_by_stack(&mut self, temp: MuID, stack_slot: P<Value>) {
self.argument_by_stack.insert(temp, stack_slot);
}
pub fn alloc_slot_for_callee_saved_reg(&mut self, reg: P<Value>, vm: &VM) -> P<Value> {
let slot = self.alloc_slot(&reg, vm);
slot.make_memory_op(reg.ty.clone(), vm)
}
pub fn remove_record_for_callee_saved_reg(&mut self, reg: MuID) {
self.allocated.remove(&reg);
}
pub fn alloc_slot_for_spilling(&mut self, reg: P<Value>, vm: &VM) -> P<Value> {
let slot = self.alloc_slot(&reg, vm);
slot.make_memory_op(reg.ty.clone(), vm)
}
pub fn get_exception_callsites(&self) -> &Vec<(ValueLocation, ValueLocation)> {
&self.exception_callsites
}
pub fn add_exception_callsite(&mut self, callsite: ValueLocation, dest: ValueLocation) {
trace!("add exception callsite: {} to dest {}", callsite, dest);
self.exception_callsites.push((callsite, dest));
}
fn alloc_slot(&mut self, val: &P<Value>, vm: &VM) -> &FrameSlot {
let id = val.id();
let ret = FrameSlot {
offset: self.cur_offset,
value: val.clone()
};
self.cur_offset -= vm.get_type_size(val.ty.id()) as isize;
self.allocated.insert(id, ret);
self.allocated.get(&id).unwrap()
}
}
#[derive(RustcEncodable, RustcDecodable, Clone)]
pub struct FrameSlot {
pub offset: isize,
pub value: P<Value>
}
impl fmt::Display for FrameSlot {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}(RBP): {}", self.offset, self.value)
}
}
impl FrameSlot {
#[cfg(target_arch = "x86_64")]
pub fn make_memory_op(&self, ty: P<MuType>, vm: &VM) -> P<Value> {
use compiler::backend::x86_64;
P(Value{
hdr: MuEntityHeader::unnamed(vm.next_id()),
ty: ty.clone(),
v: Value_::Memory(
MemoryLocation::Address{
base: x86_64::RBP.clone(),
offset: Some(Value::make_int_const(vm.next_id(), self.offset as u64)),
index: None,
scale: None
}
)
})
}
#[cfg(target_arch = "aarch64")]
pub fn make_memory_op(&self, ty: P<MuType>, vm: &VM) -> P<Value> {
use compiler::backend::aarch64;
P(Value{
hdr: MuEntityHeader::unnamed(vm.next_id()),
ty: ty.clone(),
v: Value_::Memory(
MemoryLocation::Address{
base: aarch64::FP.clone(),
offset: Some(Value::make_int_const(vm.next_id(), self.offset as u64)),
shift: 0,
signed: false
}
)
})
}
}
......@@ -189,8 +189,9 @@ pub trait MachineCode {
/// set an instruction as nop
fn set_inst_nop(&mut self, index: usize);
/// remove unnecessary push/pop if the callee saved register is not used
/// returns what registers push/pop have been deleted
fn remove_unnecessary_callee_saved(&mut self, used_callee_saved: Vec<MuID>) -> Vec<MuID>;
/// returns what registers push/pop have been deleted, and the number of callee saved registers
/// that weren't deleted
fn remove_unnecessary_callee_saved(&mut self, used_callee_saved: Vec<MuID>) -> (Vec<MuID>, usize);
/// patch frame size
fn patch_frame_size(&mut self, size: usize, size_used: usize);
......
......@@ -5,3 +5,16 @@ extern crate gcc;
fn main() {
gcc::compile_library("libgc_clib_x64.a", &["src/heap/gc/clib_x64.c"]);
}
#[cfg(target_os = "linux")]
#[cfg(target_arch = "aarch64")]
fn main() {
gcc::compile_library("libgc_clib_aarch64.a", &["src/heap/gc/clib_aarch64.c"]);
}
// This is here to enable cross compiling from windows/x86_64 to linux/aarch64
#[cfg(target_os = "windows")]
#[cfg(target_arch = "x86_64")]
fn main() {
gcc::compile_library("libgc_clib_aarch64.a", &["src/heap/gc/clib_aarch64.c"]);
}
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
void* malloc_zero(size_t size) {
void* ret = malloc(size);
memset(ret, 0, size);