diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 0c0dfa3b22..5eb871a7a0 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -163,6 +163,27 @@ comptime { @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); } +// Utils continued - SJLJ +// For tests (in particular test_gen), roc_panic is implemented in terms of +// setjmp/longjmp. LLVM is unable to generate code for longjmp on AArch64 (https://github.com/rtfeldman/roc/issues/2965), +// so instead we ask Zig to please provide implementations for us, which is does +// (seemingly via musl). +pub usingnamespace @import("std").c.builtins; +pub extern fn setjmp([*c]c_int) c_int; +pub extern fn longjmp([*c]c_int, c_int) noreturn; +pub extern fn _setjmp([*c]c_int) c_int; +pub extern fn _longjmp([*c]c_int, c_int) noreturn; +pub extern fn sigsetjmp([*c]c_int, c_int) c_int; +pub extern fn siglongjmp([*c]c_int, c_int) noreturn; +pub extern fn longjmperror() void; +// Zig won't expose the externs (and hence link correctly) unless we force them to be used. +pub export fn __roc_force_setjmp(it: [*c]c_int) c_int { + return setjmp(it); +} +pub export fn __roc_force_longjmp(a0: [*c]c_int, a1: c_int) noreturn { + longjmp(a0, a1); +} + // Export helpers - Must be run inside a comptime fn exportBuiltinFn(comptime func: anytype, comptime func_name: []const u8) void { @export(func, .{ .name = "roc_builtins." ++ func_name, .linkage = .Strong }); diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 6299e5cd35..73de5d2452 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -365,6 +365,9 @@ pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed"; pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures"; pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures"; +pub const UTILS_LONGJMP: &str = "longjmp"; +pub const UTILS_SETJMP: &str = "setjmp"; + #[derive(Debug, Default)] pub struct IntToIntrinsicName { pub options: [IntrinsicName; 10], diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 219ca9a4e2..da7d335e8e 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -521,35 +521,11 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic); let i32_type = ctx.i32_type(); let i64_type = ctx.i64_type(); - let void_type = ctx.void_type(); if let Some(func) = module.get_function("__muloti4") { func.set_linkage(Linkage::WeakAny); } - add_intrinsic( - ctx, - module, - LLVM_SETJMP, - i32_type.fn_type(&[i8_ptr_type.into()], false), - ); - - if true { - add_intrinsic( - ctx, - module, - LLVM_LONGJMP, - void_type.fn_type(&[i8_ptr_type.into()], false), - ); - } else { - add_intrinsic( - ctx, - module, - LLVM_LONGJMP, - void_type.fn_type(&[i8_ptr_type.into(), i32_type.into()], false), - ); - } - add_intrinsic( ctx, module, @@ -628,9 +604,6 @@ static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64"; static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress.p0i8"; static LLVM_STACK_SAVE: &str = "llvm.stacksave"; -static LLVM_SETJMP: &str = "llvm.eh.sjlj.setjmp"; -pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp"; - const LLVM_ADD_WITH_OVERFLOW: IntrinsicName = llvm_int_intrinsic!("llvm.sadd.with.overflow", "llvm.uadd.with.overflow"); const LLVM_SUB_WITH_OVERFLOW: IntrinsicName = @@ -3677,10 +3650,15 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( } pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { + // The size of jump_buf is platform-dependent. + // AArch64 needs 3 64-bit words, x86 seemingly needs less than that. Let's just make it 3 + // 64-bit words. + // We can always increase the size, since we hand it off opaquely to both `setjmp` and `longjmp`. + // The unused space won't be touched. let type_ = env .context - .i8_type() - .array_type(5 * env.target_info.ptr_width() as u32); + .i32_type() + .array_type(6 * env.target_info.ptr_width() as u32); let global = match env.module.get_global("roc_sjlj_buffer") { Some(global) => global, @@ -3692,12 +3670,26 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu env.builder .build_bitcast( global.as_pointer_value(), - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i32_type().ptr_type(AddressSpace::Generic), "cast_sjlj_buffer", ) .into_pointer_value() } +/// Pointer to pointer of the panic message. +pub fn get_panic_msg_ptr<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { + let ptr_to_u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let global_name = "roc_panic_msg_ptr"; + let global = env.module.get_global(global_name).unwrap_or_else(|| { + let global = env.module.add_global(ptr_to_u8_ptr, None, global_name); + global.set_initializer(&ptr_to_u8_ptr.const_zero()); + global + }); + + global.as_pointer_value() +} + fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, @@ -3718,51 +3710,7 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( let buffer = get_sjlj_buffer(env); - let cast = env - .builder - .build_bitcast( - buffer, - env.context - .i8_type() - .ptr_type(AddressSpace::Generic) - .array_type(5) - .ptr_type(AddressSpace::Generic), - "to [5 x i8*]", - ) - .into_pointer_value(); - - let zero = env.context.i32_type().const_zero(); - - let index = env.context.i32_type().const_zero(); - let fa = unsafe { - env.builder - .build_in_bounds_gep(cast, &[zero, index], "name") - }; - - let index = env.context.i32_type().const_int(2, false); - let ss = unsafe { - env.builder - .build_in_bounds_gep(cast, &[zero, index], "name") - }; - - let index = env.context.i32_type().const_int(3, false); - let error_msg = unsafe { - env.builder - .build_in_bounds_gep(cast, &[zero, index], "name") - }; - - let frame_address = env.call_intrinsic( - LLVM_FRAME_ADDRESS, - &[env.context.i32_type().const_zero().into()], - ); - - env.builder.build_store(fa, frame_address); - - let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]); - - env.builder.build_store(ss, stack_save); - - let panicked_u32 = env.call_intrinsic(LLVM_SETJMP, &[buffer.into()]); + let panicked_u32 = call_bitcode_fn(env, &[buffer.into()], bitcode::UTILS_SETJMP); let panicked_bool = env.builder.build_int_compare( IntPredicate::NE, panicked_u32.into_int_value(), @@ -3792,17 +3740,10 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( let error_msg = { // u8** - let ptr_int_ptr = builder.build_bitcast( - error_msg, - env.context - .i8_type() - .ptr_type(AddressSpace::Generic) - .ptr_type(AddressSpace::Generic), - "cast", - ); + let ptr_int_ptr = get_panic_msg_ptr(env); // u8* again - let ptr_int = builder.build_load(ptr_int_ptr.into_pointer_value(), "ptr_int"); + let ptr_int = builder.build_load(ptr_int_ptr, "ptr_int"); ptr_int }; diff --git a/compiler/gen_llvm/src/llvm/externs.rs b/compiler/gen_llvm/src/llvm/externs.rs index 055c0f8712..1f1a691199 100644 --- a/compiler/gen_llvm/src/llvm/externs.rs +++ b/compiler/gen_llvm/src/llvm/externs.rs @@ -1,9 +1,11 @@ -use crate::llvm::build::{add_func, C_CALL_CONV}; +use crate::llvm::bitcode::call_void_bitcode_fn; +use crate::llvm::build::{add_func, get_panic_msg_ptr, C_CALL_CONV}; use crate::llvm::build::{CCReturn, Env, FunctionSpec}; use inkwell::module::Linkage; use inkwell::types::BasicType; use inkwell::values::BasicValue; use inkwell::AddressSpace; +use roc_builtins::bitcode; /// Define functions for roc_alloc, roc_realloc, and roc_dealloc /// which use libc implementations (malloc, realloc, and free) @@ -184,7 +186,7 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { // roc_panic { - use crate::llvm::build::LLVM_LONGJMP; + // use crate::llvm::build::LLVM_LONGJMP; // The type of this function (but not the implementation) should have // already been defined by the builtins, which rely on it. @@ -210,29 +212,10 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { let buffer = crate::llvm::build::get_sjlj_buffer(env); // write our error message pointer - let index = env - .ptr_int() - .const_int(3 * env.target_info.ptr_width() as u64, false); - let message_buffer_raw = - unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") }; - let message_buffer = builder.build_bitcast( - message_buffer_raw, - env.context - .i8_type() - .ptr_type(AddressSpace::Generic) - .ptr_type(AddressSpace::Generic), - "to **u8", - ); - - env.builder - .build_store(message_buffer.into_pointer_value(), ptr_arg); + env.builder.build_store(get_panic_msg_ptr(env), ptr_arg); let tag = env.context.i32_type().const_int(1, false); - if true { - let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into()]); - } else { - let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into(), tag.into()]); - } + let _call = call_void_bitcode_fn(env, &[buffer.into(), tag.into()], bitcode::UTILS_LONGJMP); builder.build_unreachable();