use std::ffi::CStr; use std::mem::MaybeUninit; use std::os::raw::c_char; /// This must have the same size as the repr() of RocCallResult! pub const ROC_CALL_RESULT_DISCRIMINANT_SIZE: usize = std::mem::size_of::(); #[repr(C)] pub struct RocCallResult { tag: u64, error_msg: *mut c_char, value: MaybeUninit, } impl RocCallResult { pub fn new(value: T) -> Self { Self { tag: 0, error_msg: std::ptr::null_mut(), value: MaybeUninit::new(value), } } } impl Default for RocCallResult { fn default() -> Self { Self { tag: 0, error_msg: std::ptr::null_mut(), value: MaybeUninit::new(Default::default()), } } } impl From> for Result { fn from(call_result: RocCallResult) -> Self { match call_result.tag { 0 => Ok(unsafe { call_result.value.assume_init() }), _ => Err({ let raw = unsafe { CStr::from_ptr(call_result.error_msg) }; raw.to_str().unwrap().to_owned() }), } } } #[macro_export] macro_rules! run_roc_dylib { ($lib:expr, $main_fn_name:expr, $argument_type:ty, $return_type:ty) => {{ use inkwell::context::Context; use roc_builtins::bitcode; use roc_gen_llvm::run_roc::RocCallResult; use std::mem::MaybeUninit; // NOTE: return value is not first argument currently (may/should change in the future) type Main = unsafe extern "C" fn($argument_type, *mut RocCallResult<$return_type>); unsafe { let main: libloading::Symbol
= $lib .get($main_fn_name.as_bytes()) .ok() .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .expect("errored"); main } }}; } #[macro_export] macro_rules! try_run_jit_function { ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{ let v: String = String::new(); try_run_jit_function!($lib, $main_fn_name, $ty, $transform, v) }}; ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{ try_run_jit_function!($lib, $main_fn_name, $ty, $transform, &[]) }}; ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $expect_failures:expr) => {{ use inkwell::context::Context; use roc_builtins::bitcode; use roc_gen_llvm::run_roc::RocCallResult; use std::mem::MaybeUninit; unsafe { let main: libloading::Symbol)> = $lib .get($main_fn_name.as_bytes()) .ok() .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .expect("errored"); let mut main_result = MaybeUninit::uninit(); main(main_result.as_mut_ptr()); main_result.assume_init().into() } }}; } #[macro_export] macro_rules! run_jit_function { ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{ let v: String = String::new(); run_jit_function!($lib, $main_fn_name, $ty, $transform, v) }}; ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{ run_jit_function!($lib, $main_fn_name, $ty, $transform, $errors, &[]) }}; ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr, $expect_failures:expr) => {{ let result = $crate::try_run_jit_function!($lib, $main_fn_name, $ty, $transform, $expect_failures); match result { Ok(success) => { // only if there are no exceptions thrown, check for errors assert!($errors.is_empty(), "Encountered errors:\n{}", $errors); $transform(success) } Err(error_msg) => panic!("Roc failed with message: {}", error_msg), } }}; } /// In the repl, we don't know the type that is returned; it it's large enough to not fit in 2 /// registers (i.e. size bigger than 16 bytes on 64-bit systems), then we use this macro. /// It explicitly allocates a buffer that the roc main function can write its result into. #[macro_export] macro_rules! run_jit_function_dynamic_type { ($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{ let v: String = String::new(); run_jit_function_dynamic_type!($lib, $main_fn_name, $bytes, $transform, v) }}; ($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr, $errors:expr) => {{ use inkwell::context::Context; use roc_gen_llvm::run_roc::RocCallResult; unsafe { let main: libloading::Symbol = $lib .get($main_fn_name.as_bytes()) .ok() .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .expect("errored"); let size = std::mem::size_of::>() + $bytes; let layout = std::alloc::Layout::array::(size).unwrap(); let result = std::alloc::alloc(layout); main(result); let flag = *result; if flag == 0 { $transform(result.add(std::mem::size_of::>()) as usize) } else { use std::ffi::CString; use std::os::raw::c_char; // first field is a char pointer (to the error message) // read value, and transmute to a pointer let ptr_as_int = *(result as *const u64).offset(1); let ptr = std::mem::transmute::(ptr_as_int); // make CString (null-terminated) let raw = CString::from_raw(ptr); let result = format!("{:?}", raw); // make sure rust doesn't try to free the Roc constant string std::mem::forget(raw); eprintln!("{}", result); panic!("Roc hit an error"); } } }}; }