#[cfg(any(target_arch="x86_64", target_arch="x86"))]
mod x86;
#[cfg(any(target_os="linux", target_os="macos", target_os="freebsd", target_os="openbsd"))]
mod unix;
mod signal_stack;
mod cfi;
mod range_vec;
use std::cell::{LazyCell, RefCell};
use std::fmt::{Display, Formatter};
use std::hash::DefaultHasher;
use std::ops::Index;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use cranelift_codegen::ir::TrapCode;
use log::{error, warn};
#[cfg(feature="serde")]
use serde::{Deserialize, Serialize};
use edlc_core::lexer::SrcPos;
use edlc_core::prelude::{AmorphusDataCopy, HirPhase, MirPhase, ModuleSrc, TrapInfo, TypeArgument, TypeArguments};
use edlc_core::prelude::mir_backend::Backend;
use edlc_core::prelude::mir_funcs::{MirFuncId, MirFuncRegistry};
use edlc_core::prelude::mir_type::MirTypeId;
#[cfg(any(target_os="linux", target_os="macos", target_os="freebsd", target_os="openbsd"))]
pub use unix::{TrapHandler, jit_panic, cause_jit_async_panic, jit_sync_panic};
pub use range_vec::{RangeVec, RangeVecIter};
use crate::compiler::{UnwindInfo, JIT};
#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub enum PrintableBacktraceElement {
Jit {
pos: SrcPos,
src: ModuleSrc,
func: MirFuncId,
},
JitUnknown {
func: MirFuncId,
offset: u32,
},
Host {
func: usize,
offset: u32,
},
}
#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
#[derive(Default, Debug)]
pub struct PrintableBacktrace {
pub trace: Vec<PrintableBacktraceElement>,
}
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
struct BacktraceFlags(u32);
const FLAG_JIT_FRAME: u32 = 0b1;
impl BacktraceFlags {
pub fn set_jit_frame(mut self) -> Self {
self.0 |= FLAG_JIT_FRAME;
self
}
pub fn jit_frame(&self) -> bool {
self.0 & FLAG_JIT_FRAME != 0
}
}
#[derive(Default, Clone, Copy, Debug)]
pub struct BacktraceEntry {
func: usize,
loc: u32,
flags: BacktraceFlags,
}
pub struct Backtrace {
trace: Box<[BacktraceEntry]>,
len: usize,
}
const BACKTRACE_SIZE_MAX: usize = 32;
impl Backtrace {
fn new() -> Self {
let trace = vec![const { BacktraceEntry {
loc: 0,
func: 0,
flags: BacktraceFlags(0),
} }; BACKTRACE_SIZE_MAX];
Backtrace {
trace: trace.into_boxed_slice(),
len: 0,
}
}
fn push(&mut self, entry: BacktraceEntry) {
if self.len >= BACKTRACE_SIZE_MAX {
return;
}
self.trace[self.len] = entry;
self.len += 1;
}
fn slice(&self) -> &[BacktraceEntry] {
&self.trace[..self.len]
}
fn clear(&mut self) {
self.len = 0;
}
fn len(&self) -> usize {
self.len
}
fn is_empty(&self) -> bool {
self.len == 0
}
fn synchronous(&self) -> bool {
if let Some(first) = self.slice().first() {
first.func as *const u8 == jit_sync_panic as *const u8
} else {
false
}
}
pub fn is_recoverable(&self) -> bool {
let mut iter = self.slice().iter();
let Some(first) = iter.next() else {
return true;
};
if first.flags.jit_frame() {
return true;
}
if first.func as *const u8 != jit_sync_panic as *const u8 {
error!("[EDL-JIT] non-synchronous panic originating in host frames");
return false; }
let Some(next) = iter.next() else {
return true;
};
if next.flags.jit_frame() {
return true; }
let Some(next) = iter.next() else {
return true;
};
if !next.flags.jit_frame() {
error!("[EDL-JIT] jit_sync_panic called from a host function that is nested more than \
one function call away from JIT frame");
return false; }
true
}
fn get_panic_type(&self, debug_frames: &UnwindInfo) -> Option<PanicType> {
let Some(first) = self.slice().first() else {
return None;
};
if let Some(id) = debug_frames.find_id(&first.func) {
if let Some(source_frame) = debug_frames.get_source(&id) {
if let Some(trap_code) = source_frame.trap_code(first.loc) {
match *trap_code {
TrapCode::BAD_CONVERSION_TO_INTEGER => Some(PanicType::BadConversionToInteger),
TrapCode::HEAP_OUT_OF_BOUNDS => Some(PanicType::HeapOutOfBounds),
TrapCode::INTEGER_DIVISION_BY_ZERO => Some(PanicType::DivideByZero),
TrapCode::INTEGER_OVERFLOW => Some(PanicType::IntegerOverflow),
TrapCode::STACK_OVERFLOW => Some(PanicType::StackOverflow),
_ => {
if let Some(trap_info) = source_frame.trap_info(first.loc) {
match trap_info {
TrapInfo::DivideByZero => Some(PanicType::DivideByZero),
TrapInfo::ArrayIndex => Some(PanicType::ArrayIndexOutOfBounds),
TrapInfo::SliceIndex => Some(PanicType::SliceIndexOutOfBounds),
TrapInfo::ArrayRange => Some(PanicType::ArrayRangeOutOfBounds),
TrapInfo::SliceRange => Some(PanicType::SliceRangeOutOfBounds),
TrapInfo::ExplicitPanic => Some(PanicType::Explicit),
TrapInfo::AssertionFailed => Some(PanicType::Assertion),
TrapInfo::Other(reason) => Some(PanicType::Other(reason)),
}
} else {
Some(PanicType::Unknown)
}
},
}
} else {
Some(PanicType::Unknown)
}
} else {
None
}
} else {
None
}
}
fn printable(
&self,
debug_frames: &UnwindInfo,
) -> PrintableBacktrace {
let mut out = PrintableBacktrace::default();
for trace in self.slice().iter() {
let el = if let Some(id) = debug_frames.find_id(&trace.func) {
if let Some(source_frame) = debug_frames.get_source(&id) {
if let Some(loc) = source_frame.source_location(trace.loc) {
PrintableBacktraceElement::Jit {
func: id,
src: loc.src.clone(),
pos: loc.pos,
}
} else {
PrintableBacktraceElement::JitUnknown {
func: id,
offset: trace.loc,
}
}
} else {
PrintableBacktraceElement::JitUnknown {
func: id,
offset: trace.loc,
}
}
} else {
PrintableBacktraceElement::Host {
func: trace.func,
offset: trace.loc,
}
};
out.trace.push(el);
}
out
}
fn print<B: Backend>(
&self,
phase: &HirPhase,
funcs: &MirFuncRegistry<B>,
debug_frames: &UnwindInfo,
) {
let Some(first) = self.slice().first() else {
return;
};
if let Some(id) = debug_frames.find_id(&first.func) {
if let Some(source_frame) = debug_frames.get_source(&id) {
if let Some(trap_code) = source_frame.trap_code(first.loc) {
match *trap_code {
TrapCode::BAD_CONVERSION_TO_INTEGER => {
error!("bad conversion to integer");
},
TrapCode::HEAP_OUT_OF_BOUNDS => {
error!("heap out of bounds");
},
TrapCode::INTEGER_DIVISION_BY_ZERO => {
error!("integer division by zero")
},
TrapCode::INTEGER_OVERFLOW => {
error!("integer overflow")
},
TrapCode::STACK_OVERFLOW => {
error!("stack overflow")
},
_ => {
if let Some(trap_info) = source_frame.trap_info(first.loc) {
match trap_info {
TrapInfo::DivideByZero => {
error!("division by zero");
}
TrapInfo::ArrayIndex => {
error!("array index out of bounds");
}
TrapInfo::SliceIndex => {
error!("slice index out of bounds");
}
TrapInfo::ArrayRange => {
error!("array range out of bounds");
}
TrapInfo::SliceRange => {
error!("slice range out of bounds");
}
TrapInfo::ExplicitPanic => {
error!("explicit panic");
}
TrapInfo::AssertionFailed => {
error!("assertion failed");
}
TrapInfo::Other(reason) => {
error!("{}", reason);
}
}
} else {
error!("<unknown error>");
}
},
}
} else {
error!("<unknown error>");
}
}
}
for trace in self.slice().iter() {
if let Some(id) = debug_frames.find_id(&trace.func) {
let edl_func = funcs.get_edl_id(id)
.expect("failed to get EDL function id from MIR function id");
let sig = phase.types.get_fn_signature(edl_func)
.expect("failed to get function signature");
if let Some((pos, src)) = funcs.get_source_information(id) {
error!(" {} at {}", TypeArguments::<'_, DefaultHasher>::new(&[
TypeArgument::new_edl(sig)
]).printable(&phase.types, &phase.vars), src.format_pos(pos));
} else {
error!(" {}", TypeArguments::<'_, DefaultHasher>::new(&[
TypeArgument::new_edl(sig),
]).printable(&phase.types, &phase.vars));
}
if let Some(source_frame) = debug_frames.get_source(&id) {
if let Some(loc) = source_frame.source_location(trace.loc) {
error!(" at {}", loc.src.format_pos(loc.pos));
} else {
error!(" at <unknown> {:p}", trace.loc as *const ());
}
} else {
error!(" at <unknown> {:p}", trace.loc as *const ());
}
} else if trace.func as *const u8 == jit_sync_panic as *const u8 {
error!(" jit_sync_panic (EDL runtime synchronous panic unwind hook)")
} else {
error!(" <unknown> {:p}", trace.func as *const ());
error!(" at <unknown> {:p}", trace.loc as *const ());
}
}
}
}
impl Index<usize> for Backtrace {
type Output = BacktraceEntry;
fn index(&self, index: usize) -> &Self::Output {
self.trace.get(index).unwrap()
}
}
struct PanicPayload {
backtrace: Backtrace,
reached_host: bool,
}
impl PanicPayload {
fn clear(&mut self) {
self.backtrace.clear();
self.reached_host = false;
}
}
pub struct PanicData {
panic: AtomicBool,
cleaning_up: AtomicBool,
payload: Mutex<PanicPayload>,
}
thread_local! {
static PANIC: LazyCell<PanicData> = LazyCell::new(PanicData::new);
}
impl PanicData {
fn new() -> PanicData {
PanicData {
panic: AtomicBool::new(false),
cleaning_up: AtomicBool::new(false),
payload: Mutex::new(PanicPayload {
backtrace: Backtrace::new(),
reached_host: false,
}),
}
}
fn set<R, F: FnOnce(&mut PanicPayload) -> R>(with: F, default: R) -> R {
PANIC.with(|panic| {
panic.panic.store(true, Ordering::Relaxed);
if let Ok(mut backtrace) = panic.payload.lock() {
backtrace.clear();
with(&mut backtrace)
} else {
default
}
})
}
pub fn fetch<Runtime: 'static>(jit: &JIT<Runtime>, hir_phase: &HirPhase) -> Result<(), PanicError> {
PANIC.with(|panic| if panic
.panic
.fetch_and(false, Ordering::Relaxed) {
let payload = panic.payload.lock().unwrap();
error!("encountered panic while executing JIT code in thread: {}",
std::thread::current().name().unwrap_or("unknown"));
error!("stack trace:");
let t = payload.backtrace.get_panic_type(&jit.unwind_info)
.unwrap_or(PanicType::Unknown);
payload.backtrace.print(hir_phase, &*jit.func_reg.borrow(), &jit.unwind_info);
error!("end of stack trace.");
let recoverable = payload.reached_host && payload.backtrace.is_recoverable();
if recoverable {
error!("host function reached – attempting to recover through graceful panic protocol");
} else {
error!("host function was not reached during unwinding – graceful panic not possible");
panic!("unrecoverable panic during JIT code execution");
}
let backtrace = payload.backtrace.printable(&jit.unwind_info);
let msg = PanicMessage::take().map(|msg| msg.data);
Err(PanicError {
msg,
graceful: recoverable,
panic_type: t,
backtrace,
})
} else {
Ok(())
})
}
}
#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PanicType {
BadConversionToInteger,
HeapOutOfBounds,
IntegerOverflow,
StackOverflow,
DivideByZero,
ArrayIndexOutOfBounds,
SliceIndexOutOfBounds,
ArrayRangeOutOfBounds,
SliceRangeOutOfBounds,
Assertion,
Explicit,
Segfault,
Other(&'static str),
Unknown,
}
impl Display for PanicType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PanicType::BadConversionToInteger => write!(f, "bad conversion to integer"),
PanicType::HeapOutOfBounds => write!(f, "heap out of bounds"),
PanicType::IntegerOverflow => write!(f, "integer overflow"),
PanicType::StackOverflow => write!(f, "stack overflow"),
PanicType::DivideByZero => write!(f, "divide by zero"),
PanicType::ArrayIndexOutOfBounds => write!(f, "array index out of bounds"),
PanicType::SliceIndexOutOfBounds => write!(f, "slice index out of bounds"),
PanicType::ArrayRangeOutOfBounds => write!(f, "array range out of bounds"),
PanicType::SliceRangeOutOfBounds => write!(f, "slice range out of bounds"),
PanicType::Assertion => write!(f, "assertion failed"),
PanicType::Explicit => write!(f, "explicit panic"),
PanicType::Segfault => write!(f, "segfault panic"),
PanicType::Other(s) => write!(f, "{s}"),
PanicType::Unknown => write!(f, "unknown"),
}
}
}
#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct PanicError {
pub msg: Option<String>,
pub graceful: bool,
pub panic_type: PanicType,
pub backtrace: PrintableBacktrace,
}
impl Display for PanicError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.panic_type)?;
if let Some(msg) = self.msg.as_ref() {
write!(f, ": {msg}")?;
}
if !self.graceful {
write!(f, " !non recoverable - aborting!")?;
}
Ok(())
}
}
pub struct PanicMessage {
pub data: String,
}
thread_local! {
static PANIC_MSG: RefCell<Option<PanicMessage>> = const { RefCell::new(None) };
}
impl PanicMessage {
pub fn set(msg: PanicMessage) {
PANIC_MSG.set(Some(msg));
}
fn take() -> Option<PanicMessage> {
PANIC_MSG.take()
}
}