GitLab will be upgraded to the 12.10.14-ce.0 on 28 Sept 2020 at 2.00pm (AEDT) to 2.30pm (AEDT). During the update, GitLab and Mattermost services will not be available. If you have any concerns with this, please talk to us at N110 (b) CSIT building.

Commit fbfe7dba authored by Yi Lin's avatar Yi Lin

Merge branch 'gc-rewrite' into 'master'

GC rewrite

See merge request !55
parents 0f303eca 71aefd55
......@@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
[package]
name = "mu"
version = "0.0.1"
......@@ -61,19 +61,19 @@ num-traits = "*"
#built = "0.1"
[target.aarch64-unknown-linux-gnu.dependencies]
mu_gc = { path = "src/gc", default-features = false}
mu_gc = { path = "src/gc"}
built = "0.1"
[target.x86_64-unknown-linux-gnu.dependencies]
mu_gc = { path = "src/gc", default-features = false}
mu_gc = { path = "src/gc"}
built = "0.1"
[target.x86_64-apple-darwin.dependencies]
mu_gc = { path = "src/gc", default-features = false}
mu_gc = { path = "src/gc"}
built = "0.1"
[target.x86_64-rumprun-netbsd.dependencies]
mu_gc = { path = "src/gc", default-features = false, features = ["sel4-rumprun-target-side"], target = "x86_64-rumprun-netbsd"}
mu_gc = { path = "src/gc", features = ["sel4-rumprun-target-side"], target = "x86_64-rumprun-netbsd"}
[target.aarch64-unknown-linux-gnu.build-dependencies]
built = "0.1"
......
......@@ -29,7 +29,7 @@ are not compliant to Mu spec.
## Building
You will need:
* rust version 1.19 (0ade33941 2017-07-17)
* rust version 1.20 (f3d6973f4 2017-08-27)
* clang 4.0+
* cmake 3.8+ (we do not depend on cmake, but some Rust crates use it)
* internet connection (as Rust will download dependencies)
......
......@@ -108,6 +108,7 @@ impl Instruction {
Move(_) |
PrintHex(_) |
SetRetval(_) |
GetVMThreadLocal |
KillStack(_) |
CurrentStack |
SwapStackExpr { .. } => false
......@@ -185,7 +186,8 @@ impl Instruction {
CommonInst_Tr64ToRef(_) |
CommonInst_Tr64ToTag(_) |
Move(_) |
CurrentStack => false
CurrentStack |
GetVMThreadLocal => false
}
}
......@@ -250,6 +252,7 @@ impl Instruction {
Move(_) |
PrintHex(_) |
SetRetval(_) |
GetVMThreadLocal |
KillStack(_) |
CurrentStack |
SwapStackExpr { .. } |
......@@ -322,6 +325,7 @@ impl Instruction {
Move(_) |
PrintHex(_) |
SetRetval(_) |
GetVMThreadLocal |
KillStack(_) |
CurrentStack |
SwapStackExpr { .. } |
......@@ -395,6 +399,7 @@ impl Instruction {
Move(_) |
PrintHex(_) |
SetRetval(_) |
GetVMThreadLocal |
KillStack(_) |
CurrentStack |
SwapStackKill { .. } => false,
......@@ -820,7 +825,9 @@ impl Instruction {
// print hex
&Instruction_::PrintHex(i) => format!("PRINTHEX<{}> {}", ops[i].ty(), ops[i]),
// set retval
&Instruction_::SetRetval(val) => format!("SETRETVAL {}", ops[val])
&Instruction_::SetRetval(val) => format!("SETRETVAL {}", ops[val]),
// get vm thread local
&Instruction_::GetVMThreadLocal => format!("GETVMTHREADLOCAL")
}
}
}
......@@ -1118,7 +1125,9 @@ pub enum Instruction_ {
/// internal use: print op as hex value
PrintHex(OpIndex),
/// internal use: set return value for main
SetRetval(OpIndex)
SetRetval(OpIndex),
/// internal use: get zebu thread local
GetVMThreadLocal
}
fn format_value_types(value: &Option<Vec<P<Value>>>) -> String {
......
......@@ -566,6 +566,14 @@ impl Block {
}
}
pub fn clear_insts(&mut self) {
self.content.as_mut().unwrap().body.clear();
}
pub fn append_inst(&mut self, inst: P<TreeNode>) {
self.content.as_mut().unwrap().body.push(inst);
}
/// does this block have an exception arguments?
pub fn is_receiving_exception_arg(&self) -> bool {
return self.content.as_ref().unwrap().exn_arg.is_some();
......@@ -859,6 +867,30 @@ impl TreeNode {
})
}
/// is instruction
pub fn is_inst(&self) -> bool {
match self.v {
TreeNode_::Instruction(_) => true,
_ => false
}
}
/// is value
pub fn is_value(&self) -> bool {
match self.v {
TreeNode_::Value(_) => true,
_ => false
}
}
/// is constant value
pub fn is_const_value(&self) -> bool {
match self.v {
TreeNode_::Value(ref val) => val.is_const(),
_ => false
}
}
/// extracts the MuID of an SSA TreeNode
/// if the node is not an SSA, returns None
pub fn extract_ssa_id(&self) -> Option<MuID> {
......@@ -975,10 +1007,14 @@ rodal_struct!(Value { hdr, ty, v });
impl Value {
/// creates an int constant value
pub fn make_int_const(id: MuID, val: u64) -> P<Value> {
pub fn make_int32_const(id: MuID, val: u64) -> P<Value> {
Value::make_int_const_ty(id, UINT32_TYPE.clone(), val)
}
pub fn make_int64_const(id: MuID, val: u64) -> P<Value> {
Value::make_int_const_ty(id, UINT64_TYPE.clone(), val)
}
pub fn make_int_const_ty(id: MuID, ty: P<MuType>, val: u64) -> P<Value> {
P(Value {
hdr: MuEntityHeader::unnamed(id),
......
......@@ -75,6 +75,14 @@ lazy_static! {
MuType::new(new_internal_id(), MuType_::iref(VOID_TYPE.clone()))
);
pub static ref UPTR_U8_TYPE: P<MuType> = P(
MuType::new(new_internal_id(), MuType_::uptr(UINT8_TYPE.clone()))
);
pub static ref UPTR_U64_TYPE: P<MuType> = P(
MuType::new(new_internal_id(), MuType_::uptr(UINT64_TYPE.clone()))
);
pub static ref STACKREF_TYPE : P<MuType> = P(
MuType::new(new_internal_id(), MuType_::StackRef)
);
......@@ -99,6 +107,8 @@ lazy_static! {
IREF_VOID_TYPE.clone(),
STACKREF_TYPE.clone(),
THREADREF_TYPE.clone(),
UPTR_U8_TYPE.clone(),
UPTR_U64_TYPE.clone()
];
}
......@@ -477,6 +487,29 @@ impl MuType {
_ => None
}
}
/// prints a struct type
pub fn print_details(&self) -> String {
match self.v {
MuType_::Struct(ref tag) => {
let lock = STRUCT_TAG_MAP.read().unwrap();
format!("{} = {}", tag, lock.get(tag).unwrap())
}
MuType_::Hybrid(ref tag) => {
let lock = HYBRID_TAG_MAP.read().unwrap();
format!("{} = {}", tag, lock.get(tag).unwrap())
}
_ => format!("{}", self)
}
}
/// prints a struct type
pub fn print_hybrid(&self) -> String {
match self.v {
_ => panic!()
}
}
}
pub type StructTag = MuName;
......
......@@ -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)*) => {
......@@ -3684,9 +3686,10 @@ pub fn emit_context_with_reloc(
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;
......@@ -3695,15 +3698,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
......@@ -3713,6 +3719,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();
......@@ -3724,51 +3731,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 {
......@@ -3815,6 +3827,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!{}, None);
}
......
......@@ -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,120 +1582,93 @@ 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,
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_HYBRID,
vec![tmp_allocator.clone(), tmp_res.clone(), encode, length],
None,
Some(node),
f_context,
vm
);
// 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();
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) => {
......@@ -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
// 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 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
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);
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),