Merge pull request #5699 from roc-lang/dev-backend-roc-panic

dev backend: roc panic
This commit is contained in:
Folkert de Vries 2023-08-03 21:17:48 +02:00 committed by GitHub
commit ed9ece0f99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1387 additions and 201 deletions

View File

@ -50,7 +50,11 @@ jobs:
# Why are these tests not build with previous command? => fingerprint error. Use `CARGO_LOG=cargo::core::compiler::fingerprint=info` to investigate
- name: Build specific tests without running. Twice for zig lld-link error.
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli || cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli -p test_gen || cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli -p test_gen
- name: Test setjmp/longjmp logic
run: cargo test-gen-dev --locked --release nat_alias && cargo test-gen-dev --locked --release a_crash
- name: Actually run the tests.
run: cargo test --locked --release -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli
run: cargo test --locked --release -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli

View File

@ -455,6 +455,18 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64C
) {
todo!("Loading returned complex symbols for AArch64");
}
fn setjmp(_buf: &mut Vec<'_, u8>) {
todo!()
}
fn longjmp(_buf: &mut Vec<'_, u8>) {
todo!()
}
fn roc_panic(_buf: &mut Vec<'_, u8>, _relocs: &mut Vec<'_, Relocation>) {
todo!()
}
}
impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
@ -529,7 +541,17 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_fn_name: String,
_dst: AArch64GeneralReg,
) {
todo!("calling functions literal for AArch64");
todo!("function pointer for AArch64");
}
#[inline(always)]
fn data_pointer(
_buf: &mut Vec<'_, u8>,
_relocs: &mut Vec<'_, Relocation>,
_fn_name: String,
_dst: AArch64GeneralReg,
) {
todo!("data pointer for AArch64");
}
#[inline(always)]

View File

@ -134,6 +134,10 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
sym: &Symbol,
layout: &InLayout<'a>,
);
fn setjmp(buf: &mut Vec<'_, u8>);
fn longjmp(buf: &mut Vec<'_, u8>);
fn roc_panic(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>);
}
pub enum CompareOperation {
@ -238,6 +242,13 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
dst: GeneralReg,
);
fn data_pointer(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
fn_name: String,
dst: GeneralReg,
);
/// Jumps by an offset of offset bytes unconditionally.
/// It should always generate the same number of bytes to enable replacement if offset changes.
/// It returns the base offset to calculate the jump from (generally the instruction after the jump).
@ -714,6 +725,9 @@ impl<
fn interner(&self) -> &STLayoutInterner<'a> {
self.layout_interner
}
fn relocations_mut(&mut self) -> &mut Vec<'a, Relocation> {
&mut self.relocs
}
fn module_interns_helpers_mut(
&mut self,
) -> (
@ -887,12 +901,47 @@ impl<
(out.into_bump_slice(), offset)
}
fn build_roc_setjmp(&mut self) -> &'a [u8] {
let mut out = bumpalo::vec![in self.env.arena];
CC::setjmp(&mut out);
out.into_bump_slice()
}
fn build_roc_longjmp(&mut self) -> &'a [u8] {
let mut out = bumpalo::vec![in self.env.arena];
CC::longjmp(&mut out);
out.into_bump_slice()
}
fn build_roc_panic(&mut self) -> (&'a [u8], Vec<'a, Relocation>) {
let mut out = bumpalo::vec![in self.env.arena];
let mut relocs = bumpalo::vec![in self.env.arena];
CC::roc_panic(&mut out, &mut relocs);
(out.into_bump_slice(), relocs)
}
fn build_fn_pointer(&mut self, dst: &Symbol, fn_name: String) {
let reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
ASM::function_pointer(&mut self.buf, &mut self.relocs, fn_name, reg)
}
fn build_data_pointer(&mut self, dst: &Symbol, data_name: String) {
let reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
// now, this gives a pointer to the value
ASM::data_pointer(&mut self.buf, &mut self.relocs, data_name, reg);
// dereference
ASM::mov_reg64_mem64_offset32(&mut self.buf, reg, reg, 0);
}
fn build_fn_call(
&mut self,
dst: &Symbol,
@ -4215,7 +4264,18 @@ impl<
Builtin::Int(int_width) => match int_width {
IntWidth::I128 | IntWidth::U128 => {
// can we treat this as 2 u64's?
todo!()
storage_manager.with_tmp_general_reg(
buf,
|storage_manager, buf, tmp_reg| {
let base_offset = storage_manager.claim_stack_area(&dst, 16);
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset);
ASM::mov_base32_reg64(buf, base_offset, tmp_reg);
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset + 8);
ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg);
},
);
}
IntWidth::I64 | IntWidth::U64 => {
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
@ -4253,6 +4313,15 @@ impl<
}
Builtin::Decimal => {
// same as 128-bit integer
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| {
let base_offset = storage_manager.claim_stack_area(&dst, 16);
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset);
ASM::mov_base32_reg64(buf, base_offset, tmp_reg);
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset + 8);
ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg);
});
}
Builtin::Str | Builtin::List(_) => {
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| {

View File

@ -807,7 +807,13 @@ impl<
}
}
}
Builtin::Decimal => todo!(),
Builtin::Decimal => {
let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert_eq!(from_offset % 8, 0);
debug_assert_eq!(size % 8, 0);
debug_assert_eq!(size, layout_interner.stack_size(*layout));
self.copy_to_stack_offset(buf, size, from_offset, to_offset)
}
Builtin::Str | Builtin::List(_) => {
let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert_eq!(size, layout_interner.stack_size(*layout));

File diff suppressed because it is too large Load Diff

View File

@ -291,6 +291,7 @@ trait Backend<'a> {
fn interns(&self) -> &Interns;
fn interns_mut(&mut self) -> &mut Interns;
fn interner(&self) -> &STLayoutInterner<'a>;
fn relocations_mut(&mut self) -> &mut Vec<'a, Relocation>;
fn interner_mut(&mut self) -> &mut STLayoutInterner<'a> {
self.module_interns_helpers_mut().1
@ -463,6 +464,11 @@ trait Backend<'a> {
/// Used for generating wrappers for malloc/realloc/free
fn build_wrapped_jmp(&mut self) -> (&'a [u8], u64);
// use for roc_panic
fn build_roc_setjmp(&mut self) -> &'a [u8];
fn build_roc_longjmp(&mut self) -> &'a [u8];
fn build_roc_panic(&mut self) -> (&'a [u8], Vec<'a, Relocation>);
/// build_proc creates a procedure and outputs it to the wrapped object writer.
/// Returns the procedure bytes, its relocations, and the names of the refcounting functions it references.
fn build_proc(
@ -1661,6 +1667,23 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
LowLevel::SetJmp => self.build_fn_call(
sym,
String::from("roc_setjmp"),
args,
arg_layouts,
ret_layout,
),
LowLevel::LongJmp => self.build_fn_call(
sym,
String::from("roc_longjmp"),
args,
arg_layouts,
ret_layout,
),
LowLevel::SetLongJmpBuffer => {
self.build_data_pointer(sym, String::from("setlongjmp_buffer"));
}
LowLevel::DictPseudoSeed => self.build_fn_call(
sym,
bitcode::UTILS_DICT_PSEUDO_SEED.to_string(),
@ -1963,6 +1986,7 @@ trait Backend<'a> {
);
fn build_fn_pointer(&mut self, dst: &Symbol, fn_name: String);
fn build_data_pointer(&mut self, dst: &Symbol, data_name: String);
/// Move a returned value into `dst`
fn move_return_value(&mut self, dst: &Symbol, ret_layout: &InLayout<'a>);

View File

@ -74,6 +74,23 @@ pub fn build_module<'a, 'r>(
),
)
}
Triple {
architecture: TargetArch::X86_64,
binary_format: TargetBF::Coff,
..
} if cfg!(feature = "target-x86_64") => {
let backend = new_backend_64bit::<
x86_64::X86_64GeneralReg,
x86_64::X86_64FloatReg,
x86_64::X86_64Assembler,
x86_64::X86_64WindowsFastcall,
>(env, TargetInfo::default_x86_64(), interns, layout_interner);
build_object(
procedures,
backend,
Object::new(BinaryFormat::Coff, Architecture::X86_64, Endianness::Little),
)
}
Triple {
architecture: TargetArch::Aarch64(_),
binary_format: TargetBF::Elf,
@ -118,6 +135,111 @@ pub fn build_module<'a, 'r>(
}
}
fn define_setlongjmp_buffer(output: &mut Object) -> SymbolId {
let bss_section = output.section_id(StandardSection::Data);
// 8 registers + 3 words for a RocStr
// TODO 50 is the wrong size here, look at implementation and put correct value in here
const SIZE: usize = (8 + 50) * core::mem::size_of::<u64>();
let symbol = Symbol {
name: b"setlongjmp_buffer".to_vec(),
value: 0,
size: SIZE as u64,
kind: SymbolKind::Data,
scope: SymbolScope::Dynamic,
weak: false,
section: SymbolSection::Section(bss_section),
flags: SymbolFlags::None,
};
let symbol_id = output.add_symbol(symbol);
output.add_symbol_data(symbol_id, bss_section, &[0x00; SIZE], 8);
symbol_id
}
fn generate_setjmp<'a, B: Backend<'a>>(backend: &mut B, output: &mut Object) {
let text_section = output.section_id(StandardSection::Text);
let proc_symbol = Symbol {
name: b"roc_setjmp".to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Dynamic,
weak: false,
section: SymbolSection::Section(text_section),
flags: SymbolFlags::None,
};
let proc_id = output.add_symbol(proc_symbol);
let proc_data = backend.build_roc_setjmp();
output.add_symbol_data(proc_id, text_section, proc_data, 16);
}
fn generate_longjmp<'a, B: Backend<'a>>(backend: &mut B, output: &mut Object) {
let text_section = output.section_id(StandardSection::Text);
let proc_symbol = Symbol {
name: b"roc_longjmp".to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Dynamic,
weak: false,
section: SymbolSection::Section(text_section),
flags: SymbolFlags::None,
};
let proc_id = output.add_symbol(proc_symbol);
let proc_data = backend.build_roc_longjmp();
output.add_symbol_data(proc_id, text_section, proc_data, 16);
}
// a roc_panic to be used in tests; relies on setjmp/longjmp
fn generate_roc_panic<'a, B: Backend<'a>>(backend: &mut B, output: &mut Object) {
let text_section = output.section_id(StandardSection::Text);
let proc_symbol = Symbol {
name: b"roc_panic".to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Dynamic,
weak: false,
section: SymbolSection::Section(text_section),
flags: SymbolFlags::None,
};
let proc_id = output.add_symbol(proc_symbol);
let (proc_data, relocs) = backend.build_roc_panic();
let proc_offset = output.add_symbol_data(proc_id, text_section, proc_data, 16);
for r in relocs {
let relocation = match r {
Relocation::LinkedData { offset, name } => {
if let Some(sym_id) = output.symbol_id(name.as_bytes()) {
write::Relocation {
offset: offset + proc_offset,
size: 32,
kind: RelocationKind::GotRelative,
encoding: RelocationEncoding::Generic,
symbol: sym_id,
addend: -4,
}
} else {
internal_error!("failed to find data symbol for {:?}", name);
}
}
Relocation::LocalData { .. }
| Relocation::LinkedFunction { .. }
| Relocation::JmpToReturn { .. } => {
unreachable!("not currently created by build_roc_panic")
}
};
output.add_relocation(text_section, relocation).unwrap();
}
}
fn generate_wrapper<'a, B: Backend<'a>>(
backend: &mut B,
output: &mut Object,
@ -190,6 +312,12 @@ fn build_object<'a, B: Backend<'a>>(
);
*/
define_setlongjmp_buffer(&mut output);
generate_roc_panic(&mut backend, &mut output);
generate_setjmp(&mut backend, &mut output);
generate_longjmp(&mut backend, &mut output);
if backend.env().mode.generate_allocators() {
generate_wrapper(
&mut backend,
@ -209,12 +337,7 @@ fn build_object<'a, B: Backend<'a>>(
"roc_dealloc".into(),
"free".into(),
);
generate_wrapper(
&mut backend,
&mut output,
"roc_panic".into(),
"roc_builtins.utils.test_panic".into(),
);
// Extra symbols only required on unix systems.
if matches!(output.format(), BinaryFormat::Elf | BinaryFormat::MachO) {
generate_wrapper(
@ -230,6 +353,15 @@ fn build_object<'a, B: Backend<'a>>(
"roc_shm_open".into(),
"shm_open".into(),
);
} else if matches!(output.format(), BinaryFormat::Coff) {
// TODO figure out why this symbol is required, it should not be required
// Without this it does not build on Windows
generate_wrapper(
&mut backend,
&mut output,
"roc_getppid".into(),
"malloc".into(),
);
}
}
@ -245,6 +377,18 @@ fn build_object<'a, B: Backend<'a>>(
let exposed_proc = build_exposed_proc(&mut backend, &proc);
let exposed_generic_proc = build_exposed_generic_proc(&mut backend, &proc);
let (module_id, layout_interner, interns, code_gen_help, _) =
backend.module_interns_helpers_mut();
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
let test_helper = roc_mono::code_gen_help::test_helper(
code_gen_help,
ident_ids,
layout_interner,
&proc,
);
#[cfg(debug_assertions)]
{
let module_id = exposed_generic_proc.name.name().module_id();
@ -256,6 +400,18 @@ fn build_object<'a, B: Backend<'a>>(
module_id.register_debug_idents(ident_ids);
}
// println!("{}", test_helper.to_pretty(backend.interner(), 200, true));
build_proc_symbol(
&mut output,
&mut layout_ids,
&mut procs,
&mut backend,
layout,
test_helper,
Exposed::TestMain,
);
build_proc_symbol(
&mut output,
&mut layout_ids,
@ -535,6 +691,7 @@ enum Exposed {
ExposedGeneric,
Exposed,
NotExposed,
TestMain,
}
fn build_proc_symbol<'a, B: Backend<'a>>(
@ -567,6 +724,7 @@ fn build_proc_symbol<'a, B: Backend<'a>>(
None,
layout.result,
),
Exposed::TestMain => String::from("test_main"),
};
let proc_symbol = Symbol {
@ -577,7 +735,7 @@ fn build_proc_symbol<'a, B: Backend<'a>>(
// TODO: Depending on whether we are building a static or dynamic lib, this should change.
// We should use Dynamic -> anyone, Linkage -> static link, Compilation -> this module only.
scope: match exposed {
Exposed::ExposedGeneric | Exposed::Exposed => SymbolScope::Dynamic,
Exposed::ExposedGeneric | Exposed::Exposed | Exposed::TestMain => SymbolScope::Dynamic,
Exposed::NotExposed => SymbolScope::Linkage,
},
weak: false,

View File

@ -6,8 +6,8 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_target::TargetInfo;
use crate::ir::{
Call, CallSpecId, CallType, Expr, HostExposedLayouts, JoinPointId, ModifyRc, PassedFunction,
Proc, ProcLayout, SelfRecursive, Stmt, UpdateModeId,
BranchInfo, Call, CallSpecId, CallType, Expr, HostExposedLayouts, JoinPointId, Literal,
ModifyRc, PassedFunction, Proc, ProcLayout, SelfRecursive, Stmt, UpdateModeId,
};
use crate::layout::{
Builtin, InLayout, LambdaName, Layout, LayoutInterner, LayoutRepr, LayoutWrapper, Niche,
@ -848,3 +848,224 @@ fn layout_needs_helper_proc<'a>(
LayoutRepr::Erased(_) => true,
}
}
pub fn test_helper<'a>(
env: &CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
layout_interner: &mut STLayoutInterner<'a>,
main_proc: &Proc<'a>,
) -> Proc<'a> {
let name = LambdaName::no_niche(env.create_symbol(ident_ids, "test_main"));
let it = (0..main_proc.args.len()).map(|i| env.create_symbol(ident_ids, &format!("arg_{i}")));
let arguments = Vec::from_iter_in(it, env.arena).into_bump_slice();
let it = arguments
.iter()
.zip(main_proc.args.iter())
.map(|(s, (l, _))| (*l, *s));
let args = Vec::from_iter_in(it, env.arena).into_bump_slice();
// tag: u64,
// error_msg: *mut RocStr,
// value: MaybeUninit<T>,
let fields = [Layout::U64, Layout::U64, main_proc.ret_layout];
let repr = LayoutRepr::Struct(env.arena.alloc(fields));
let output_layout = layout_interner.insert_direct_no_semantic(repr);
let body = test_helper_body(
env,
ident_ids,
layout_interner,
main_proc,
arguments,
output_layout,
);
Proc {
name,
args,
body,
closure_data_layout: None,
ret_layout: output_layout,
is_self_recursive: main_proc.is_self_recursive,
host_exposed_layouts: HostExposedLayouts::HostExposed {
rigids: Default::default(),
aliases: Default::default(),
},
is_erased: false,
}
}
fn test_helper_body<'a>(
env: &CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
layout_interner: &mut STLayoutInterner<'a>,
main_proc: &Proc<'a>,
arguments: &'a [Symbol],
output_layout: InLayout<'a>,
) -> Stmt<'a> {
// let buffer = SetLongJmpBuffer
let buffer_symbol = env.create_symbol(ident_ids, "buffer");
let buffer_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::SetLongJmpBuffer,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: &[],
});
let buffer_stmt = |next| Stmt::Let(buffer_symbol, buffer_expr, Layout::U64, next);
let field_layouts = env.arena.alloc([Layout::U64, Layout::U64]);
let ret_layout = layout_interner.insert_direct_no_semantic(LayoutRepr::Struct(field_layouts));
let setjmp_symbol = env.create_symbol(ident_ids, "setjmp");
let setjmp_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::SetJmp,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: env.arena.alloc([buffer_symbol]),
});
let setjmp_stmt = |next| Stmt::Let(setjmp_symbol, setjmp_expr, ret_layout, next);
let is_longjmp_symbol = env.create_symbol(ident_ids, "is_longjmp");
let is_longjmp_expr = Expr::StructAtIndex {
index: 0,
field_layouts,
structure: setjmp_symbol,
};
let is_longjmp_stmt = |next| Stmt::Let(is_longjmp_symbol, is_longjmp_expr, Layout::U64, next);
let tag_symbol = env.create_symbol(ident_ids, "tag");
let tag_expr = Expr::StructAtIndex {
index: 1,
field_layouts,
structure: setjmp_symbol,
};
let tag_stmt = |next| Stmt::Let(tag_symbol, tag_expr, Layout::U64, next);
// normal path, no panics
let if_zero_stmt = {
let it = main_proc.args.iter().map(|(a, _)| *a);
let arg_layouts = Vec::from_iter_in(it, env.arena).into_bump_slice();
let result_symbol = env.create_symbol(ident_ids, "result");
let result_expr = Expr::Call(Call {
call_type: CallType::ByName {
name: main_proc.name,
ret_layout: main_proc.ret_layout,
arg_layouts,
specialization_id: CallSpecId::BACKEND_DUMMY,
},
arguments,
});
let result = |next| Stmt::Let(result_symbol, result_expr, main_proc.ret_layout, next);
let ok_tag_symbol = env.create_symbol(ident_ids, "ok_tag");
let ok_tag_expr = Expr::Literal(Literal::Int((0i128).to_ne_bytes()));
let ok_tag = |next| Stmt::Let(ok_tag_symbol, ok_tag_expr, Layout::U64, next);
let msg_ptr_symbol = env.create_symbol(ident_ids, "msg_ptr");
let msg_ptr_expr = Expr::Literal(Literal::Int((0i128).to_ne_bytes()));
let msg_ptr = |next| Stmt::Let(msg_ptr_symbol, msg_ptr_expr, Layout::U64, next);
// construct the record
let output_symbol = env.create_symbol(ident_ids, "output_ok");
let fields = [ok_tag_symbol, msg_ptr_symbol, result_symbol];
let output_expr = Expr::Struct(env.arena.alloc(fields));
let output = |next| Stmt::Let(output_symbol, output_expr, output_layout, next);
let arena = env.arena;
result(arena.alloc(
//
ok_tag(arena.alloc(
//
msg_ptr(arena.alloc(
//
output(arena.alloc(
//
Stmt::Ret(output_symbol),
)),
)),
)),
))
};
// a longjmp/panic occurred
let if_nonzero_stmt = {
let alloca_symbol = env.create_symbol(ident_ids, "alloca");
let alloca_expr = Expr::Alloca {
element_layout: main_proc.ret_layout,
initializer: None,
};
let alloca = |next| Stmt::Let(alloca_symbol, alloca_expr, Layout::U64, next);
let load_symbol = env.create_symbol(ident_ids, "load");
let load_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::PtrLoad,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: env.arena.alloc([alloca_symbol]),
});
let load = |next| Stmt::Let(load_symbol, load_expr, main_proc.ret_layout, next);
// construct the record
let output_symbol = env.create_symbol(ident_ids, "output_err");
// is_longjmp_symbol is a pointer to the error message
let fields = [tag_symbol, is_longjmp_symbol, load_symbol];
let output_expr = Expr::Struct(env.arena.alloc(fields));
let output = |next| Stmt::Let(output_symbol, output_expr, output_layout, next);
let arena = env.arena;
arena.alloc(alloca(arena.alloc(
//
load(arena.alloc(
//
output(arena.alloc(
//
Stmt::Ret(output_symbol),
)),
)),
)))
};
buffer_stmt(env.arena.alloc(
//
setjmp_stmt(env.arena.alloc(
//
is_longjmp_stmt(env.arena.alloc(
//
tag_stmt(env.arena.alloc(
//
switch_if_zero_else(
env.arena,
is_longjmp_symbol,
output_layout,
if_zero_stmt,
if_nonzero_stmt,
),
)),
)),
)),
))
}
fn switch_if_zero_else<'a>(
arena: &'a Bump,
condition_symbol: Symbol,
return_layout: InLayout<'a>,
then_branch_stmt: Stmt<'a>,
else_branch_stmt: &'a Stmt<'a>,
) -> Stmt<'a> {
let then_branch = (0u64, BranchInfo::None, then_branch_stmt);
let else_branch = (BranchInfo::None, else_branch_stmt);
Stmt::Switch {
cond_symbol: condition_symbol,
cond_layout: Layout::U64,
branches: &*arena.alloc([then_branch]),
default_branch: else_branch,
ret_layout: return_layout,
}
}

View File

@ -992,6 +992,24 @@ fn undefined_variable() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
#[should_panic(expected = "User crash with message: \"a crash\"")]
fn a_crash() {
assert_evals_to!(
indoc!(
r#"
if Bool.true then
crash "a crash"
else
0u64
"#
),
3,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "Roc failed with message: ")]

View File

@ -2,10 +2,13 @@ use libloading::Library;
use roc_build::link::{link, LinkType};
use roc_builtins::bitcode;
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
use roc_mono::ir::CrashTag;
use roc_mono::ir::SingleEntryPoint;
use roc_packaging::cache::RocCacheDir;
use roc_region::all::LineInfo;
use roc_solve::FunctionKind;
use roc_std::RocStr;
use std::mem::MaybeUninit;
use tempfile::tempdir;
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
@ -212,8 +215,10 @@ pub fn helper(
let builtins_host_tempfile =
roc_bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
// TODO make this an envrionment variable
if false {
std::fs::copy(&app_o_file, "/tmp/app.o").unwrap();
let file_path = std::env::temp_dir().join("app.o");
std::fs::copy(&app_o_file, file_path).unwrap();
}
let (mut child, dylib_path) = link(
@ -245,6 +250,81 @@ pub fn helper(
(main_fn_name, delayed_errors, lib)
}
#[derive(Debug)]
#[repr(C)]
pub struct RocCallResult<T> {
pub tag: u64,
pub error_msg: *mut RocStr,
pub value: MaybeUninit<T>,
}
impl<T> RocCallResult<T> {
pub fn new(value: T) -> Self {
Self {
tag: 0,
error_msg: std::ptr::null_mut(),
value: MaybeUninit::new(value),
}
}
}
impl<T: Default> Default for RocCallResult<T> {
fn default() -> Self {
Self {
tag: 0,
error_msg: std::ptr::null_mut(),
value: MaybeUninit::new(Default::default()),
}
}
}
impl<T> RocCallResult<T> {
pub fn into_result(self) -> Result<T, (String, CrashTag)> {
match self.tag {
0 => Ok(unsafe { self.value.assume_init() }),
n => Err({
let mut msg = RocStr::default();
unsafe { std::ptr::swap(&mut msg, self.error_msg) };
let tag = (n - 1) as u32;
let tag = tag
.try_into()
.unwrap_or_else(|_| panic!("received illegal tag: {tag} {msg}"));
(msg.as_str().to_owned(), tag)
}),
}
}
}
fn get_test_main_fn<T>(
lib: &libloading::Library,
) -> libloading::Symbol<unsafe extern "C" fn() -> RocCallResult<T>> {
let main_fn_name = "test_main";
unsafe {
lib.get(main_fn_name.as_bytes())
.ok()
.ok_or(format!("Unable to JIT compile `{main_fn_name}`"))
.expect("errored")
}
}
pub(crate) fn run_test_main<T>(lib: &libloading::Library) -> Result<T, (String, CrashTag)> {
let main = get_test_main_fn::<T>(lib);
let result = unsafe { main() };
result.into_result()
}
impl<T: Sized> From<RocCallResult<T>> for Result<T, (String, CrashTag)> {
fn from(call_result: RocCallResult<T>) -> Self {
call_result.into_result()
}
}
#[allow(unused_macros)]
macro_rules! assert_evals_to {
($src:expr, $expected:expr, $ty:ty) => {{
@ -267,19 +347,40 @@ macro_rules! assert_evals_to {
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr, $lazy_literals:expr) => {
use bumpalo::Bump;
use roc_gen_dev::run_jit_function_raw;
let arena = Bump::new();
let (main_fn_name, errors, lib) =
let (_main_fn_name, errors, lib) =
$crate::helpers::dev::helper(&arena, $src, $leak, $lazy_literals);
let transform = |success| {
let expected = $expected;
#[allow(clippy::redundant_closure_call)]
let given = $transform(success);
assert_eq!(&given, &expected);
};
run_jit_function_raw!(lib, main_fn_name, $ty, transform, errors)
let result = $crate::helpers::dev::run_test_main::<$ty>(&lib);
if !errors.is_empty() {
dbg!(&errors);
assert_eq!(
errors,
std::vec::Vec::new(),
"Encountered errors: {:?}",
errors
);
}
match result {
Ok(value) => {
let expected = $expected;
#[allow(clippy::redundant_closure_call)]
let given = $transform(value);
assert_eq!(&given, &expected, "output is different");
}
Err((msg, tag)) => {
use roc_mono::ir::CrashTag;
match tag {
CrashTag::Roc => panic!(r#"Roc failed with message: "{msg}""#),
CrashTag::User => panic!(r#"User crash with message: "{msg}""#),
}
}
}
};
}