WARNING! Access to this system is limited to authorised users only.
Unauthorised users may be subject to prosecution.
Unauthorised access to this system is a criminal offence under Australian law (Federal Crimes Act 1914 Part VIA)
It is a criminal offence to:
(1) Obtain access to data without authority. -Penalty 2 years imprisonment.
(2) Damage, delete, alter or insert data without authority. -Penalty 10 years imprisonment.
User activity is monitored and recorded. Anyone using this system expressly consents to such monitoring and recording.

Commit 99de258f authored by Isaac Oscar Gariano's avatar Isaac Oscar Gariano
Browse files

Implemented GC allocator for AArch64

parent e71b64fa
...@@ -18,6 +18,7 @@ use utils::ByteSize; ...@@ -18,6 +18,7 @@ use utils::ByteSize;
use utils::Address; use utils::Address;
use utils::POINTER_SIZE; use utils::POINTER_SIZE;
use compiler::backend::aarch64::*; use compiler::backend::aarch64::*;
use runtime::mm::*;
use compiler::backend::{Reg, Mem}; use compiler::backend::{Reg, Mem};
use compiler::machine_code::MachineCode; use compiler::machine_code::MachineCode;
...@@ -37,6 +38,7 @@ use std::usize; ...@@ -37,6 +38,7 @@ use std::usize;
use std::ops; use std::ops;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::RwLock; use std::sync::RwLock;
use std::io::Write;
macro_rules! trace_emit { macro_rules! trace_emit {
($arg1:tt $($arg:tt)*) => { ($arg1:tt $($arg:tt)*) => {
...@@ -3662,30 +3664,30 @@ pub fn emit_context_with_reloc( ...@@ -3662,30 +3664,30 @@ pub fn emit_context_with_reloc(
// data // data
writeln!(file, ".data").unwrap(); writeln!(file, ".data").unwrap();
// persist heap - we traverse the heap from globals
{ {
use runtime::mm; use runtime::mm;
// persist globals
let global_locs_lock = vm.global_locations().read().unwrap(); let global_locs_lock = vm.global_locations().read().unwrap();
let global_lock = vm.globals().read().unwrap(); let global_lock = vm.globals().read().unwrap();
// a map from address to ID
let global_addr_id_map = { let global_addr_id_map = {
let mut map: LinkedHashMap<Address, MuID> = LinkedHashMap::new(); let mut map: LinkedHashMap<Address, MuID> = LinkedHashMap::new();
for (id, global_loc) in global_locs_lock.iter() { for (id, global_loc) in global_locs_lock.iter() {
map.insert(global_loc.to_address(), *id); map.insert(global_loc.to_address(), *id);
} }
map map
}; };
// dump heap from globals // get address of all globals so we can traverse heap from them
let global_addrs: Vec<Address> = let global_addrs: Vec<Address> =
global_locs_lock.values().map(|x| x.to_address()).collect(); global_locs_lock.values().map(|x| x.to_address()).collect();
debug!("going to dump these globals: {:?}", global_addrs); debug!("going to dump these globals: {:?}", global_addrs);
// heap dump
let mut global_dump = mm::persist_heap(global_addrs); let mut global_dump = mm::persist_heap(global_addrs);
debug!("Heap Dump from GC: {:?}", global_dump); debug!("Heap Dump from GC: {:?}", global_dump);
let ref objects = global_dump.objects; let ref objects = global_dump.objects;
let ref mut relocatable_refs = global_dump.relocatable_refs; let ref mut relocatable_refs = global_dump.relocatable_refs;
...@@ -3694,15 +3696,18 @@ pub fn emit_context_with_reloc( ...@@ -3694,15 +3696,18 @@ pub fn emit_context_with_reloc(
relocatable_refs.insert(addr, mangle_name(str)); relocatable_refs.insert(addr, mangle_name(str));
} }
// for all the reachable object, we write them to the boot image
for obj_dump in objects.values() { for obj_dump in objects.values() {
// write object metadata
write_align(&mut file, 8); write_align(&mut file, 8);
write_obj_header(&mut file, &obj_dump.encode);
// .bytes xx,xx,xx,xx (between mem_start to reference_addr) // write alignment for the object
write_data_bytes(&mut file, obj_dump.mem_start, obj_dump.reference_addr); write_align(&mut file, obj_dump.align);
if global_addr_id_map.contains_key(&obj_dump.reference_addr) {
let global_id = global_addr_id_map.get(&obj_dump.reference_addr).unwrap();
// if this object is a global cell, we add labels so it can be accessed
if global_addr_id_map.contains_key(&obj_dump.addr) {
let global_id = global_addr_id_map.get(&obj_dump.addr).unwrap();
let global_value = global_lock.get(global_id).unwrap(); let global_value = global_lock.get(global_id).unwrap();
// .globl global_cell_name // .globl global_cell_name
...@@ -3712,6 +3717,7 @@ pub fn emit_context_with_reloc( ...@@ -3712,6 +3717,7 @@ pub fn emit_context_with_reloc(
writeln!(file, "\t{}", directive_globl(global_cell_name.clone())).unwrap(); writeln!(file, "\t{}", directive_globl(global_cell_name.clone())).unwrap();
writeln!(file, "{}:", global_cell_name.clone()).unwrap(); writeln!(file, "{}:", global_cell_name.clone()).unwrap();
// .equiv global_cell_name_if_its_valid_c_ident
if is_valid_c_identifier(&demangled_name) { if is_valid_c_identifier(&demangled_name) {
let demangled_name = (*demangled_name).clone(); let demangled_name = (*demangled_name).clone();
writeln!(file, "\t{}", directive_globl(demangled_name.clone())).unwrap(); writeln!(file, "\t{}", directive_globl(demangled_name.clone())).unwrap();
...@@ -3723,51 +3729,56 @@ pub fn emit_context_with_reloc( ...@@ -3723,51 +3729,56 @@ pub fn emit_context_with_reloc(
} }
} }
// dump_label: // put dump_label for this object (so it can be referred to from other dumped objects)
let dump_label = relocatable_refs let dump_label = relocatable_refs.get(&obj_dump.addr).unwrap().clone();
.get(&obj_dump.reference_addr) file.write_fmt(format_args!("{}:\n", dump_label)).unwrap();
.unwrap()
.clone();
writeln!(file, "{}:", dump_label).unwrap();
let base = obj_dump.reference_addr; // get ready to go through from the object start (not mem_start) to the end
let end = obj_dump.mem_start + obj_dump.mem_size; let base = obj_dump.addr;
let end = obj_dump.addr + obj_dump.size;
assert!(base.is_aligned_to(POINTER_SIZE)); assert!(base.is_aligned_to(POINTER_SIZE));
// offset as cursor
let mut offset = 0; let mut offset = 0;
while offset < obj_dump.size {
while offset < obj_dump.mem_size {
let cur_addr = base + offset; let cur_addr = base + offset;
if obj_dump.reference_offsets.contains(&offset) { if obj_dump.reference_offsets.contains(&offset) {
// write ref with label // if this offset is a reference field, we put a relocatable label
// generated by the GC instead of address value
let load_ref = unsafe { cur_addr.load::<Address>() }; let load_ref = unsafe { cur_addr.load::<Address>() };
if load_ref.is_zero() { if load_ref.is_zero() {
// write 0 // null reference, write 0
writeln!(file, ".xword 0").unwrap(); file.write("\t.xword 0\n".as_bytes()).unwrap();
} else { } else {
// get the relocatable label
let label = match relocatable_refs.get(&load_ref) { let label = match relocatable_refs.get(&load_ref) {
Some(label) => label, Some(label) => label,
None => { None => {
panic!( panic!(
"cannot find label for address {}, \ "cannot find label for address {}, it is not dumped by GC \
it is not dumped by GC (why GC didn't trace to it)", (why GC didn't trace to it?)",
load_ref load_ref
) )
} }
}; };
file.write_fmt(format_args!("\t.xword {}\n", label.clone()))
writeln!(file, ".xword {}", label.clone()).unwrap(); .unwrap();
} }
} else if fields.contains_key(&cur_addr) { } else if fields.contains_key(&cur_addr) {
// write uptr (or other relocatable value) with label // if this offset is a field named by the client to relocatable,
// we put the relocatable label given by the client
let label = fields.get(&cur_addr).unwrap(); let label = fields.get(&cur_addr).unwrap();
writeln!(file, ".xword {}", mangle_name(label.clone())).unwrap(); file.write_fmt(format_args!("\t.xword {}\n", mangle_name(label.clone())))
.unwrap();
} else { } else {
// otherwise this offset is plain data
// write plain word (as bytes) // write plain word (as bytes)
let next_word_addr = cur_addr + POINTER_SIZE; let next_word_addr = cur_addr + POINTER_SIZE;
if next_word_addr <= end { if next_word_addr <= end {
write_data_bytes(&mut file, cur_addr, next_word_addr); write_data_bytes(&mut file, cur_addr, next_word_addr);
} else { } else {
...@@ -3809,6 +3820,15 @@ pub fn emit_context_with_reloc( ...@@ -3809,6 +3820,15 @@ pub fn emit_context_with_reloc(
debug!("---finish---"); debug!("---finish---");
} }
fn write_obj_header(f: &mut File, obj: &ObjectEncode) {
// header is 8 bytes aligned, and takes 24 bytes
write_align(f, 8);
let hdr = obj.as_raw();
f.write_fmt(format_args!("\t.xword {}\n", hdr[0])).unwrap();
f.write_fmt(format_args!("\t.xword {}\n", hdr[1])).unwrap();
f.write_fmt(format_args!("\t.xword {}\n", hdr[2])).unwrap();
}
pub fn emit_context(vm: &VM) { pub fn emit_context(vm: &VM) {
emit_context_with_reloc(vm, hashmap!{}, hashmap!{}); emit_context_with_reloc(vm, hashmap!{}, hashmap!{});
} }
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#![warn(unused_imports)] #![warn(unused_imports)]
#![warn(unreachable_code)] #![warn(unreachable_code)]
#![warn(dead_code)]
use ast::ir::*; use ast::ir::*;
use ast::ptr::*; use ast::ptr::*;
...@@ -21,19 +22,17 @@ use ast::inst::*; ...@@ -21,19 +22,17 @@ use ast::inst::*;
use ast::op; use ast::op;
use ast::op::*; use ast::op::*;
use ast::types::*; use ast::types::*;
use utils::math::align_up;
use utils::POINTER_SIZE; use utils::POINTER_SIZE;
use utils::WORD_SIZE; use utils::WORD_SIZE;
use vm::VM; use vm::VM;
use runtime::mm; use runtime::mm;
use runtime::mm::OBJECT_HEADER_SIZE;
use runtime::ValueLocation; use runtime::ValueLocation;
use runtime::thread; use runtime::thread;
use runtime::entrypoints; use runtime::entrypoints;
use runtime::entrypoints::RuntimeEntrypoint; use runtime::entrypoints::RuntimeEntrypoint;
use compiler::CompilerPass; use compiler::CompilerPass;
use compiler::backend::BackendType;
use compiler::PROLOGUE_BLOCK_NAME; use compiler::PROLOGUE_BLOCK_NAME;
use compiler::backend::aarch64::*; use compiler::backend::aarch64::*;
...@@ -47,8 +46,6 @@ use std::mem; ...@@ -47,8 +46,6 @@ use std::mem;
use std::any::Any; use std::any::Any;
use num::integer::lcm; use num::integer::lcm;
const INLINE_FASTPATH: bool = false;
pub struct InstructionSelection { pub struct InstructionSelection {
name: &'static str, name: &'static str,
backend: Box<CodeGenerator>, backend: Box<CodeGenerator>,
...@@ -1503,6 +1500,12 @@ impl<'a> InstructionSelection { ...@@ -1503,6 +1500,12 @@ impl<'a> InstructionSelection {
vm vm
); );
} }
Instruction_::GetVMThreadLocal => {
trace!("instsel on GETVMTHREADLOCAL");
let tl = self.emit_get_threadlocal(f_context, vm);
let tmp_res = self.get_result_value(node, 0);
self.backend.emit_mov(&tmp_res, &tl);
}
Instruction_::CommonInst_GetThreadLocal => { Instruction_::CommonInst_GetThreadLocal => {
trace!("instsel on GETTHREADLOCAL"); trace!("instsel on GETTHREADLOCAL");
// get thread local // get thread local
...@@ -1579,120 +1582,93 @@ impl<'a> InstructionSelection { ...@@ -1579,120 +1582,93 @@ impl<'a> InstructionSelection {
} }
Instruction_::New(ref ty) => { Instruction_::New(ref ty) => {
trace!("instsel on NEW"); trace!("instsel on NEW: {}", ty.print_details());
if cfg!(debug_assertions) { assert!(!ty.is_hybrid());
match ty.v {
MuType_::Hybrid(_) => {
panic!("cannot use NEW for hybrid, use NEWHYBRID instead")
}
_ => {}
}
}
let ty_info = vm.get_backend_type_info(ty.id()); let ty_info = vm.get_backend_type_info(ty.id());
let size = ty_info.size; let size = ty_info.size;
let ty_align = ty_info.alignment; let ty_align = ty_info.alignment;
let const_size = make_value_int_const(size as u64, vm); // get allocator
let tmp_allocator = self.emit_get_allocator(f_context, vm); let tmp_allocator = self.emit_get_allocator(f_context, vm);
let tmp_res = self.emit_alloc_sequence( // allocate and init
tmp_allocator.clone(), self.emit_alloc_const_size(
const_size, &tmp_allocator,
size,
ty_align, ty_align,
node, node,
f_context, &ty_info,
vm
);
// ASM: call muentry_init_object(%allocator, %tmp_res, %encode)
let encode =
make_value_int_const(mm::get_gc_type_encode(ty_info.gc_type.id), vm);
self.emit_runtime_entry(
&entrypoints::INIT_OBJ,
vec![tmp_allocator.clone(), tmp_res.clone(), encode],
None,
Some(node),
f_context, f_context,
vm vm
); );
} }
Instruction_::NewHybrid(ref ty, var_len) => { Instruction_::NewHybrid(ref ty, var_len) => {
trace!("instsel on NEWHYBRID"); trace!("instsel on NEWHYBRID: {}", ty.print_details());
if cfg!(debug_assertions) { assert!(ty.is_hybrid());
match ty.v {
MuType_::Hybrid(_) => {}
_ => {
panic!(
"NEWHYBRID is only for allocating hybrid types, \
use NEW for others"
)
}
}
}
let ty_info = vm.get_backend_type_info(ty.id()); let ty_info = vm.get_backend_type_info(ty.id());
let ty_align = ty_info.alignment; let ty_align = ty_info.alignment;
let fix_part_size = ty_info.size; let fix_part_size = ty_info.size;
let var_ty_size = ty_info.elem_size.unwrap(); let var_ty_size = match ty_info.elem_size {
Some(sz) => sz,
// actual size = fix_part_size + var_ty_size * len None => {
let (actual_size, length) = { panic!("expect HYBRID type here with elem_size, found {}", ty_info)
let ref ops = inst.ops;
let ref var_len = ops[var_len];
if match_node_int_imm(var_len) {
let var_len = node_imm_to_u64(var_len);
let actual_size = fix_part_size + var_ty_size * (var_len as usize);
(
make_value_int_const(actual_size as u64, vm),
make_value_int_const(var_len as u64, vm)
)
} else {
let tmp_actual_size =
make_temporary(f_context, UINT64_TYPE.clone(), vm);
let tmp_var_len = self.emit_ireg(var_len, f_content, f_context, vm);
// tmp_actual_size = tmp_var_len*var_ty_size
emit_mul_u64(
self.backend.as_mut(),
&tmp_actual_size,
&tmp_var_len,
var_ty_size as u64
);
// tmp_actual_size = tmp_var_len*var_ty_size + fix_part_size
emit_add_u64(
self.backend.as_mut(),
&tmp_actual_size,
&tmp_actual_size,
fix_part_size as u64
);
(tmp_actual_size, tmp_var_len)
} }
}; };
let ref ops = inst.ops;
let ref op_var_len = ops[var_len];
let tmp_allocator = self.emit_get_allocator(f_context, vm); let tmp_allocator = self.emit_get_allocator(f_context, vm);
let tmp_res = self.emit_alloc_sequence( // size is known at compile time
tmp_allocator.clone(), if match_node_int_imm(op_var_len) {
actual_size, let const_var_len = op_var_len.as_value().extract_int_const().unwrap();
ty_align, let const_size = mm::check_hybrid_size(
node, fix_part_size + var_ty_size * (const_var_len as usize)
f_context, );
vm self.emit_alloc_const_size(
); &tmp_allocator,
const_size,
// ASM: call muentry_init_object(%allocator, %tmp_res, %encode) ty_align,
let encode = node,
make_value_int_const(mm::get_gc_type_encode(ty_info.gc_type.id), vm); &ty_info,
self.emit_runtime_entry( f_context,
&entrypoints::INIT_HYBRID, vm
vec![tmp_allocator.clone(), tmp_res.clone(), encode, length], );
None, } else {
Some(node), debug_assert!(self.match_ireg(op_var_len));
f_context, let tmp_var_len = op_var_len.as_value().clone();
vm
); let tmp_fix_size = make_value_int_const(fix_part_size as u64, vm);
let tmp_var_size = make_value_int_const(var_ty_size as u64, vm);
let tmp_align = make_value_int_const(ty_align as u64, vm);
let tmp_tyid = {
let enc = ty_info.gc_type.as_ref().unwrap();
let id = vm.get_gc_type_id(enc);
make_value_int_const(id as u64, vm)
};
let tmp_full_tyid = {
let enc = ty_info.gc_type_hybrid_full.as_ref().unwrap();
let id = vm.get_gc_type_id(enc);
make_value_int_const(id as u64, vm)
};
let tmp_res = self.get_result_value(node, 0);
self.emit_runtime_entry(
&entrypoints::ALLOC_VAR_SIZE,
vec![
tmp_fix_size,
tmp_var_size,
tmp_var_len,
tmp_align,
tmp_tyid,
tmp_full_tyid,
],
Some(vec![tmp_res].clone()),
Some(node),
f_context,
vm
);
}
} }
Instruction_::AllocA(ref ty) => { Instruction_::AllocA(ref ty) => {
...@@ -3598,90 +3574,122 @@ impl<'a> InstructionSelection { ...@@ -3598,90 +3574,122 @@ impl<'a> InstructionSelection {
self.backend.emit_mov(&SP, &res); self.backend.emit_mov(&SP, &res);
}; };
} }
fn emit_alloc_sequence(
/// emits the allocation sequence
/// * if the size is known at compile time, either emits allocation for small objects or
/// large objects
/// * if the size is not known at compile time, emits a branch to check the size at runtime,
/// and call corresponding allocation
fn emit_alloc_const_size(
&mut self, &mut self,
tmp_allocator: P<Value>, tmp_allocator: &P<Value>,
size: P<Value>, size: ByteSize,
align: usize, align: ByteSize,
node: &TreeNode, node: &TreeNode,
backend_ty: &BackendType,
f_context: &mut FunctionContext, f_context: &mut FunctionContext,
vm: &VM vm: &VM
) -> P<Value> { ) -> P<Value> {
if size.is_int_const() { let size = align_up(mm::check_size(size), POINTER_SIZE);
// size known at compile time, we can choose to emit alloc_small or large now let encode = mm::gen_object_encode(backend_ty, size, vm);
let size_i = size.extract_int_const().unwrap();
if size_i + OBJECT_HEADER_SIZE as u64 > mm::LARGE_OBJECT_THRESHOLD as u64 { let tmp_res = self.get_result_value(node, 0);
self.emit_alloc_sequence_large(tmp_allocator, size, align, node, f_context, vm) let tmp_align = make_value_int_const(align as u64, vm);
} else { let tmp_size = make_value_int_const(size as u64, vm);
self.emit_alloc_sequence_small(tmp_allocator, size, align, node, f_context, vm)
} if size <= mm::MAX_TINY_OBJECT {
} else { // alloc tiny
// size is unknown at compile time self.emit_runtime_entry(
// we need to emit both alloc small and alloc large, &entrypoints::ALLOC_TINY,
// and it is decided at runtime vec![tmp_allocator.clone(), tmp_size.clone(), tmp_align],
Some(vec![tmp_res.clone()]),
// emit: CMP size, THRESHOLD Some(node),
// emit: B.GT ALLOC_LARGE f_context,
// emit: >> small object alloc vm
// emit: B ALLOC_LARGE_END );
// emit: ALLOC_LARGE: // init object
// emit: >> large object alloc let tmp_encode = cast_value(
// emit: ALLOC_LARGE_END: &make_value_int_const(encode.tiny().as_u64(), vm),
let blk_alloc_large = make_block_name(&node.name(), "alloc_large");