mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-20 15:27:45 +03:00
Merge pull request #5699 from roc-lang/dev-backend-roc-panic
dev backend: roc panic
This commit is contained in:
commit
ed9ece0f99
6
.github/workflows/windows_tests.yml
vendored
6
.github/workflows/windows_tests.yml
vendored
@ -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
|
||||
|
||||
|
@ -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)]
|
||||
|
@ -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| {
|
||||
|
@ -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
@ -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>);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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: ")]
|
||||
|
@ -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 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(success);
|
||||
assert_eq!(&given, &expected);
|
||||
};
|
||||
run_jit_function_raw!(lib, main_fn_name, $ty, transform, errors)
|
||||
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}""#),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user