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;
use utils::Address;
use utils::POINTER_SIZE;
use compiler::backend::aarch64::*;
use runtime::mm::*;
use compiler::backend::{Reg, Mem};
use compiler::machine_code::MachineCode;
......@@ -37,6 +38,7 @@ use std::usize;
use std::ops;
use std::collections::HashSet;
use std::sync::RwLock;
use std::io::Write;
macro_rules! trace_emit {
($arg1:tt $($arg:tt)*) => {
......@@ -3662,30 +3664,30 @@ pub fn emit_context_with_reloc(
// data
writeln!(file, ".data").unwrap();
// persist heap - we traverse the heap from globals
{
use runtime::mm;
// persist globals
let global_locs_lock = vm.global_locations().read().unwrap();
let global_lock = vm.globals().read().unwrap();
// a map from address to ID
let global_addr_id_map = {
let mut map: LinkedHashMap<Address, MuID> = LinkedHashMap::new();
for (id, global_loc) in global_locs_lock.iter() {
map.insert(global_loc.to_address(), *id);
}
map
};
// dump heap from globals
// get address of all globals so we can traverse heap from them
let global_addrs: Vec<Address> =
global_locs_lock.values().map(|x| x.to_address()).collect();
debug!("going to dump these globals: {:?}", global_addrs);
// heap dump
let mut global_dump = mm::persist_heap(global_addrs);
debug!("Heap Dump from GC: {:?}", global_dump);
let ref objects = global_dump.objects;
let ref mut relocatable_refs = global_dump.relocatable_refs;
......@@ -3694,15 +3696,18 @@ pub fn emit_context_with_reloc(
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() {
// write object metadata
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_data_bytes(&mut file, obj_dump.mem_start, obj_dump.reference_addr);
if global_addr_id_map.contains_key(&obj_dump.reference_addr) {
let global_id = global_addr_id_map.get(&obj_dump.reference_addr).unwrap();
// write alignment for the object
write_align(&mut file, obj_dump.align);
// 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();
// .globl global_cell_name
......@@ -3712,6 +3717,7 @@ pub fn emit_context_with_reloc(
writeln!(file, "\t{}", directive_globl(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) {
let demangled_name = (*demangled_name).clone();
writeln!(file, "\t{}", directive_globl(demangled_name.clone())).unwrap();
......@@ -3723,51 +3729,56 @@ pub fn emit_context_with_reloc(
}
}
// dump_label:
let dump_label = relocatable_refs
.get(&obj_dump.reference_addr)
.unwrap()
.clone();
writeln!(file, "{}:", dump_label).unwrap();
// put dump_label for this object (so it can be referred to from other dumped objects)
let dump_label = relocatable_refs.get(&obj_dump.addr).unwrap().clone();
file.write_fmt(format_args!("{}:\n", dump_label)).unwrap();
let base = obj_dump.reference_addr;
let end = obj_dump.mem_start + obj_dump.mem_size;
// get ready to go through from the object start (not mem_start) to the end
let base = obj_dump.addr;
let end = obj_dump.addr + obj_dump.size;
assert!(base.is_aligned_to(POINTER_SIZE));
// offset as cursor
let mut offset = 0;
while offset < obj_dump.mem_size {
while offset < obj_dump.size {
let cur_addr = base + 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>() };
if load_ref.is_zero() {
// write 0
writeln!(file, ".xword 0").unwrap();
// null reference, write 0
file.write("\t.xword 0\n".as_bytes()).unwrap();
} else {
// get the relocatable label
let label = match relocatable_refs.get(&load_ref) {
Some(label) => label,
None => {
panic!(
"cannot find label for address {}, \
it is not dumped by GC (why GC didn't trace to it)",
"cannot find label for address {}, it is not dumped by GC \
(why GC didn't trace to it?)",
load_ref
)
}
};
writeln!(file, ".xword {}", label.clone()).unwrap();
file.write_fmt(format_args!("\t.xword {}\n", label.clone()))
.unwrap();
}
} 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();
writeln!(file, ".xword {}", mangle_name(label.clone())).unwrap();
file.write_fmt(format_args!("\t.xword {}\n", mangle_name(label.clone())))
.unwrap();
} else {
// otherwise this offset is plain data
// write plain word (as bytes)
let next_word_addr = cur_addr + POINTER_SIZE;
if next_word_addr <= end {
write_data_bytes(&mut file, cur_addr, next_word_addr);
} else {
......@@ -3809,6 +3820,15 @@ pub fn emit_context_with_reloc(
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) {
emit_context_with_reloc(vm, hashmap!{}, hashmap!{});
}
......
......@@ -14,6 +14,7 @@
#![warn(unused_imports)]
#![warn(unreachable_code)]
#![warn(dead_code)]
use ast::ir::*;
use ast::ptr::*;
......@@ -21,19 +22,17 @@ use ast::inst::*;
use ast::op;
use ast::op::*;
use ast::types::*;
use utils::math::align_up;
use utils::POINTER_SIZE;
use utils::WORD_SIZE;
use vm::VM;
use runtime::mm;
use runtime::mm::OBJECT_HEADER_SIZE;
use runtime::ValueLocation;
use runtime::thread;
use runtime::entrypoints;
use runtime::entrypoints::RuntimeEntrypoint;
use compiler::CompilerPass;
use compiler::backend::BackendType;
use compiler::PROLOGUE_BLOCK_NAME;
use compiler::backend::aarch64::*;
......@@ -47,8 +46,6 @@ use std::mem;
use std::any::Any;
use num::integer::lcm;
const INLINE_FASTPATH: bool = false;
pub struct InstructionSelection {
name: &'static str,
backend: Box<CodeGenerator>,
......@@ -1503,6 +1500,12 @@ impl<'a> InstructionSelection {
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 => {
trace!("instsel on GETTHREADLOCAL");
// get thread local
......@@ -1579,121 +1582,94 @@ impl<'a> InstructionSelection {
}
Instruction_::New(ref ty) => {
trace!("instsel on NEW");
if cfg!(debug_assertions) {
match ty.v {
MuType_::Hybrid(_) => {
panic!("cannot use NEW for hybrid, use NEWHYBRID instead")
}
_ => {}
}
}
trace!("instsel on NEW: {}", ty.print_details());
assert!(!ty.is_hybrid());
let ty_info = vm.get_backend_type_info(ty.id());
let size = ty_info.size;
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_res = self.emit_alloc_sequence(
tmp_allocator.clone(),
const_size,
// allocate and init
self.emit_alloc_const_size(
&tmp_allocator,
size,
ty_align,
node,
f_context,
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),
&ty_info,
f_context,
vm
);
}
Instruction_::NewHybrid(ref ty, var_len) => {
trace!("instsel on NEWHYBRID");
if cfg!(debug_assertions) {
match ty.v {
MuType_::Hybrid(_) => {}
_ => {
panic!(
"NEWHYBRID is only for allocating hybrid types, \
use NEW for others"
)
}
}
}
trace!("instsel on NEWHYBRID: {}", ty.print_details());
assert!(ty.is_hybrid());
let ty_info = vm.get_backend_type_info(ty.id());
let ty_align = ty_info.alignment;
let fix_part_size = ty_info.size;
let var_ty_size = ty_info.elem_size.unwrap();
// actual size = fix_part_size + var_ty_size * len
let (actual_size, length) = {
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 var_ty_size = match ty_info.elem_size {
Some(sz) => sz,
None => {
panic!("expect HYBRID type here with elem_size, found {}", ty_info)
}
};
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_res = self.emit_alloc_sequence(
tmp_allocator.clone(),
actual_size,
// size is known at compile time
if match_node_int_imm(op_var_len) {
let const_var_len = op_var_len.as_value().extract_int_const().unwrap();
let const_size = mm::check_hybrid_size(
fix_part_size + var_ty_size * (const_var_len as usize)
);
self.emit_alloc_const_size(
&tmp_allocator,
const_size,
ty_align,
node,
&ty_info,
f_context,
vm
);
} else {
debug_assert!(self.match_ireg(op_var_len));
let tmp_var_len = op_var_len.as_value().clone();
// 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);
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::INIT_HYBRID,
vec![tmp_allocator.clone(), tmp_res.clone(), encode, length],
None,
&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) => {
trace!("instsel on ALLOCA");
......@@ -3598,90 +3574,122 @@ impl<'a> InstructionSelection {
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,
tmp_allocator: P<Value>,
size: P<Value>,
align: usize,
tmp_allocator: &P<Value>,
size: ByteSize,
align: ByteSize,
node: &TreeNode,
backend_ty: &BackendType,
f_context: &mut FunctionContext,
vm: &VM
) -> P<Value> {
if size.is_int_const() {
// size known at compile time, we can choose to emit alloc_small or large now
let size_i = size.extract_int_const().unwrap();
let size = align_up(mm::check_size(size), POINTER_SIZE);
let encode = mm::gen_object_encode(backend_ty, size, vm);
if size_i + OBJECT_HEADER_SIZE as u64 > mm::LARGE_OBJECT_THRESHOLD as u64 {
self.emit_alloc_sequence_large(tmp_allocator, size, align, node, f_context, vm)
} else {
self.emit_alloc_sequence_small(tmp_allocator, size, align, node, f_context, vm)
}
} else {
// size is unknown at compile time
// we need to emit both alloc small and alloc large,
// and it is decided at runtime
let tmp_res = self.get_result_value(node, 0);
let tmp_align = make_value_int_const(align as u64, vm);
let tmp_size = make_value_int_const(size as u64, vm);
// emit: CMP size, THRESHOLD
// emit: B.GT ALLOC_LARGE
// emit: >> small object alloc
// emit: B ALLOC_LARGE_END
// emit: ALLOC_LARGE:
// emit: >> large object alloc
// emit: ALLOC_LARGE_END:
let blk_alloc_large = make_block_name(&node.name(), "alloc_large");
let blk_alloc_large_end = make_block_name(&node.name(), "alloc_large_end");
if size <= mm::MAX_TINY_OBJECT {
// alloc tiny
self.emit_runtime_entry(
&entrypoints::ALLOC_TINY,
vec![tmp_allocator.clone(), tmp_size.clone(), tmp_align],
Some(vec![tmp_res.clone()]),
Some(node),
f_context,
vm
);
// init object
let tmp_encode = cast_value(
&make_value_int_const(encode.tiny().as_u64(), vm),
&UINT8_TYPE
);
self.emit_runtime_entry(
&entrypoints::INIT_TINY,
vec![tmp_allocator.clone(), tmp_res.clone(), tmp_encode],
None,
Some(node),
f_context,
vm
);
} else if size <= mm::MAX_MEDIUM_OBJECT {
// this could be either a small object or a medium object
if OBJECT_HEADER_SIZE != 0 {
let size_with_hdr = make_temporary(f_context, UINT64_TYPE.clone(), vm);
emit_add_u64(
self.backend.as_mut(),
&size_with_hdr,
&size,
OBJECT_HEADER_SIZE as u64
// alloc normal
self.emit_runtime_entry(
&entrypoints::ALLOC_NORMAL,
vec![tmp_allocator.clone(), tmp_size.clone(), tmp_align],
Some(vec![tmp_res.clone()]),
Some(node),
f_context,
vm
);
emit_cmp_u64(
self.backend.as_mut(),
&size_with_hdr,
// init object
if size <= mm::MAX_SMALL_OBJECT {
let tmp_encode = cast_value(
&make_value_int_const(encode.small().as_u64(), vm),
&UINT16_TYPE
);
self.emit_runtime_entry(
&entrypoints::INIT_SMALL,
vec![tmp_allocator.clone(), tmp_res.clone(), tmp_encode],
None,
Some(node),
f_context,
vm,
mm::LARGE_OBJECT_THRESHOLD as u64
vm
);
} else {
emit_cmp_u64(
self.backend.as_mut(),
&size,
let tmp_encode = cast_value(
&make_value_int_const(encode.medium().as_u64(), vm),
&UINT32_TYPE
);
self.emit_runtime_entry(
&entrypoints::INIT_MEDIUM,
vec![tmp_allocator.clone(), tmp_res.clone(), tmp_encode],
None,
Some(node),
f_context,
vm,
mm::LARGE_OBJECT_THRESHOLD as u64
vm
);
}
self.backend.emit_b_cond("GT", blk_alloc_large.clone());
self.finish_block();
let block_name = make_block_name(&node.name(), "allocsmall");
self.start_block(block_name);
self.emit_alloc_sequence_small(
};
} else {
// large allocation
self.emit_runtime_entry(
&entrypoints::ALLOC_LARGE,
vec![tmp_allocator.clone(), tmp_size.clone(), tmp_align],
Some(vec![tmp_res.clone()]),
Some(node),
f_context,
vm
);
// init object
let encode = encode.large();
let tmp_encode1 = make_value_int_const(encode.size() as u64, vm);
let tmp_encode2 = make_value_int_const(encode.type_id() as u64, vm);
self.emit_runtime_entry(
&entrypoints::INIT_LARGE,
vec![
tmp_allocator.clone(),
size.clone(),
align,
node,
tmp_res.clone(),
tmp_encode1,
tmp_encode2,
],
None,
Some(node),
f_context,
vm
);
self.backend.emit_b(blk_alloc_large_end.clone());
self.finish_block();
// alloc_large:
self.start_block(blk_alloc_large.clone());
self.emit_alloc_sequence_large(tmp_allocator.clone(), size, align, node, f_context, vm);
self.finish_block();
// alloc_large_end:
self.start_block(blk_alloc_large_end.clone());
self.get_result_value(node, 0)
}
tmp_res
}
fn emit_get_allocator(&mut self, f_context: &mut FunctionContext, vm: &VM) -> P<Value> {
......@@ -3700,61 +3708,6 @@ impl<'a> InstructionSelection {
tmp_allocator
}
fn emit_alloc_sequence_large(
&mut self,
tmp_allocator: P<Value>,
size: P<Value>,
align: usize,
node: &TreeNode