Commit 9b82225d authored by Yi Lin's avatar Yi Lin

Merge branch 'debug-exceptions' into 'develop'

Debug exceptions

See merge request !42
parents 7c9c215b 81b6a24d
Pipeline #1204 failed with stages
in 11 minutes and 5 seconds
......@@ -340,6 +340,80 @@ impl Instruction {
}
}
/// can this instruction throw exception?
/// (whether or not it containjs a ctach for it)
pub fn is_potentially_throwing(&self) -> bool {
use inst::Instruction_::*;
match self.v {
// Note: commented out ones are ones where we haven't implemented exceptions yet
Watchpoint { .. } |
Call { .. } |
CCall { .. } |
SwapStackExc { .. } |
SwapStackExpr { .. } |
ExnInstruction { .. } |
ExprCall { .. } |
ExprCCall { .. } |
//Load { .. } |
//Store { .. } |
//CmpXchg { .. } |
//AtomicRMW { .. } |
//New(_) |
//NewHybrid(_, _) |
Throw(_) => true,
// BinOp(op, _, _) |
// BinOpWithStatus(op, _, _, _) if op.may_throw() => true,
BinOp(_, _, _) |
BinOpWithStatus(_, _, _, _) |
CmpOp(_, _, _) |
ConvOp { .. } |
AllocA(_) |
AllocAHybrid(_, _) |
NewStack(_) |
NewThread { .. } |
NewFrameCursor(_) |
GetIRef(_) |
GetFieldIRef { .. } |
GetElementIRef { .. } |
ShiftIRef { .. } |
GetVarPartIRef { .. } |
Fence(_) |
Return(_) |
ThreadExit |
TailCall(_) |
Branch1(_) |
Branch2 { .. } |
Select { .. } |
WPBranch { .. } |
Switch { .. } |
CommonInst_GetThreadLocal |
CommonInst_SetThreadLocal(_) |
CommonInst_Pin(_) |
CommonInst_Unpin(_) |
CommonInst_GetAddr(_) |
CommonInst_Tr64IsFp(_) |
CommonInst_Tr64IsInt(_) |
CommonInst_Tr64IsRef(_) |
CommonInst_Tr64FromFp(_) |
CommonInst_Tr64FromInt(_) |
CommonInst_Tr64FromRef(_, _) |
CommonInst_Tr64ToFp(_) |
CommonInst_Tr64ToInt(_) |
CommonInst_Tr64ToRef(_) |
CommonInst_Tr64ToTag(_) |
Move(_) |
PrintHex(_) |
SetRetval(_) |
KillStack(_) |
CurrentStack |
SwapStackKill { .. } => false,
_ => false
}
}
fn debug_str(&self, ops: &Vec<P<TreeNode>>) -> String {
self.v.debug_str(ops)
}
......
......@@ -304,8 +304,8 @@ impl MuFunctionVersion {
}
/// gets call outedges in this function
/// returns Map(CallSiteID -> FuncID)
pub fn get_static_call_edges(&self) -> LinkedHashMap<MuID, MuID> {
/// returns Map(CallSiteID -> (FuncID, has exception clause))
pub fn get_static_call_edges(&self) -> LinkedHashMap<MuID, (MuID, bool)> {
let mut ret = LinkedHashMap::new();
let f_content = self.content.as_ref().unwrap();
......@@ -317,7 +317,6 @@ impl MuFunctionVersion {
match inst.v {
TreeNode_::Instruction(ref inst) => {
let ref ops = inst.ops;
match inst.v {
Instruction_::ExprCall { ref data, .. } |
Instruction_::ExprCCall { ref data, .. } |
......@@ -330,7 +329,10 @@ impl MuFunctionVersion {
TreeNode_::Value(ref pv) => {
match pv.v {
Value_::Constant(Constant::FuncRef(id)) => {
ret.insert(inst.id(), id);
ret.insert(
inst.id(),
(id, inst.has_exception_clause())
);
}
_ => {}
}
......@@ -352,7 +354,7 @@ impl MuFunctionVersion {
// TODO: It may be more efficient to compute this when the instructions
// are added to the function version and store the result in a field
pub fn has_throw(&self) -> bool {
pub fn could_throw(&self) -> bool {
let f_content = self.content.as_ref().unwrap();
for (_, block) in f_content.blocks.iter() {
......@@ -361,13 +363,11 @@ impl MuFunctionVersion {
for inst in block_content.body.iter() {
match inst.v {
TreeNode_::Instruction(ref inst) => {
match inst.v {
Instruction_::Throw(_) => {
return true;
}
_ => {
// do nothing
}
if inst.is_potentially_throwing() {
// TODO: Do some smarter checking
// (e.g. if this is a CALL to a function where !could_throw,
// or a division where the divisor can't possibly be zero..)
return true;
}
}
_ => unreachable!()
......
......@@ -69,10 +69,11 @@ impl Inlining {
let mut inline_something = false;
// check each call from this function
for func_id in func.get_static_call_edges().values() {
for (_, (func_id, has_exc)) in func.get_static_call_edges() {
// check a single callsite, whether it should be inlined
// the result is returned as boolean, and also written into 'should_inline'
let should_inline_this = self.check_should_inline_func(*func_id, func.func_id, vm);
let should_inline_this =
self.check_should_inline_func(has_exc, func_id, func.func_id, vm);
inline_something = inline_something || should_inline_this;
}
......@@ -80,7 +81,13 @@ impl Inlining {
}
/// checks whether we should inline the caller into the callee
fn check_should_inline_func(&mut self, callee: MuID, caller: MuID, vm: &VM) -> bool {
fn check_should_inline_func(
&mut self,
has_exc: bool,
callee: MuID,
caller: MuID,
vm: &VM
) -> bool {
// recursive call, do not inline
if callee == caller {
return false;
......@@ -123,19 +130,21 @@ impl Inlining {
// some heuristics here to decide if we should inline the function
let n_insts = estimate_insts(&fv);
let out_calls = fv.get_static_call_edges();
let has_throw = fv.has_throw();
let could_throw = fv.could_throw();
let has_tailcall = fv.has_tailcall();
// simple heuristic here:
// * estimated machine insts are fewer than 10 insts
// * leaf in call graph (no out calls)
// * no throw (otherwise we will need to rearrange catch)
let should_inline = n_insts <= 25 && out_calls.len() == 0 && !has_throw && !has_tailcall;
let should_inline = n_insts <= 25 && !(has_exc && could_throw) && !has_tailcall;
trace!("func {} has {} insts (estimated)", callee, n_insts);
trace!(" has {} out calls", out_calls.len());
trace!(" has throws? {}", has_throw);
trace!(
" has exception clause? {}, could throw? {}",
has_exc,
could_throw
);
trace!("SO func should be inlined? {}", should_inline);
self.should_inline.insert(callee, should_inline);
......@@ -166,7 +175,7 @@ impl Inlining {
trace!("check inst: {}", inst);
let inst_id = inst.id();
if call_edges.contains_key(&inst_id) {
let call_target = call_edges.get(&inst_id).unwrap();
let call_target = &call_edges.get(&inst_id).unwrap().0;
if self.should_inline.contains_key(call_target) &&
*self.should_inline.get(call_target).unwrap()
{
......@@ -178,7 +187,7 @@ impl Inlining {
// inline expansion starts here
// getting the function being inlined
let inlined_func = *call_edges.get(&inst.id()).unwrap();
let inlined_func = call_edges.get(&inst.id()).unwrap().0;
trace!("function being inlined is {}", inlined_func);
let inlined_fvid = match vm.get_cur_version_for_func(inlined_func) {
Some(fvid) => fvid,
......
......@@ -19,6 +19,7 @@ use std::collections::HashMap;
use std::ops::Deref;
use compiler::machine_code::CompiledCallsite;
use runtime::*;
use log;
/// runtime function to deal with exception (unwind stack, find catch block, and restore)
/// This function is called by muentry_throw_exception() which gets emitted for THROW instruction
......@@ -39,7 +40,7 @@ use runtime::*;
/// real frame pointers or the frame cursor)
#[no_mangle]
pub extern "C" fn throw_exception_internal(exception_obj: Address, frame_cursor: Address) -> ! {
trace!("throwing exception: {}", exception_obj);
debug!("throwing exception: {}", exception_obj);
if cfg!(debug_assertions) {
trace!("Initial Frame: ");
......@@ -65,7 +66,9 @@ pub extern "C" fn throw_exception_internal(exception_obj: Address, frame_cursor:
// acquire lock for exception table
let compiled_callsite_table = vm.compiled_callsite_table().read().unwrap();
print_backtrace(frame_cursor, compiled_callsite_table.deref());
loop {
// Lookup the table for the callsite
trace!("Callsite: 0x{:x}", callsite);
trace!("\tprevious_frame_pointer: 0x{:x}", previous_frame_pointer);
......@@ -82,8 +85,6 @@ pub extern "C" fn throw_exception_internal(exception_obj: Address, frame_cursor:
either there isn't a catch block to catch the exception or \
your catch block is above a native function call"
);
// This function may segfault
print_backtrace(frame_cursor, compiled_callsite_table.deref());
panic!("Uncaught Mu Exception");
}
table_entry.unwrap()
......@@ -92,7 +93,11 @@ pub extern "C" fn throw_exception_internal(exception_obj: Address, frame_cursor:
// Check for a catch block at this callsite
if callsite_info.exceptional_destination.is_some() {
catch_address = callsite_info.exceptional_destination.unwrap();
trace!("Found catch block: 0x{:x}", catch_address);
debug!(
"Found catch block: 0x{:x} - {}",
catch_address,
get_symbol_name(catch_address)
);
sp = get_previous_stack_pointer(
current_frame_pointer,
callsite_info.stack_args_size
......@@ -155,8 +160,11 @@ fn print_frame(cursor: Address) {
/// This function may segfault or panic when it reaches the bottom of the stack
// TODO: Determine where the bottom is without segfaulting
fn print_backtrace(base: Address, compiled_callsite_table: &HashMap<Address, CompiledCallsite>) {
error!("BACKTRACE: ");
if log::max_log_level() < log::LogLevelFilter::Debug {
return;
}
debug!("BACKTRACE: ");
let cur_thread = thread::MuThread::current();
let ref vm = cur_thread.vm;
// compiled_funcs: RwLock<HashMap<MuID, RwLock<CompiledFunction>>>;
......@@ -166,6 +174,10 @@ fn print_backtrace(base: Address, compiled_callsite_table: &HashMap<Address, Com
loop {
let callsite = get_return_address(frame_pointer);
frame_pointer = get_previous_frame_pointer(frame_pointer);
if frame_pointer.is_zero() {
return;
}
if compiled_callsite_table.contains_key(&callsite) {
let function_version = compiled_callsite_table
......@@ -178,30 +190,29 @@ fn print_backtrace(base: Address, compiled_callsite_table: &HashMap<Address, Com
.read()
.unwrap();
error!(
"\tframe {:2}: 0x{:x} - {} (fid: #{}, fvid: #{}) at 0x{:x}",
debug!(
"\tframe {:2}: 0x{:x} - {} (fid: #{}, fvid: #{}) at 0x{:x} - {}",
frame_count,
compiled_func.start.to_address(),
vm.get_name_for_func(compiled_func.func_id),
compiled_func.func_id,
compiled_func.func_ver_id,
callsite
callsite,
get_symbol_name(callsite)
);
} else {
let (func_name, func_start) = get_function_info(callsite);
error!(
debug!(
"\tframe {:2}: 0x{:x} - {} at 0x{:x}",
frame_count,
func_start,
func_name,
callsite
);
debug!("\tother native frames...");
break;
}
frame_pointer = get_previous_frame_pointer(frame_pointer);
if frame_pointer.is_zero() {
return;
}
frame_count += 1;
}
}
......@@ -45,6 +45,14 @@ pub mod exception;
lazy_static!{
static ref UNKNOWN_FUNCTION_NAME : CName = Arc::new("UNKOWN".to_string());
}
/// returns the name for a symbol address (inverse of resolve_symbol)
/// WARNING: Only use this for Mu symbols
pub fn get_symbol_name(symbol: Address) -> CName {
let (name, start) = get_function_info(symbol);
assert!(start == symbol);
return demangle_name((*name).clone());
}
/// returns name for a function address
// FIXME: this actually returns the name and address of the nearest symbol (of any type)
// that starts before function_addr (instead we want the nearest function symbol)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment