diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 224a8b6639..a79ba7ba84 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -1,7 +1,8 @@ use bumpalo::collections::Vec; use bumpalo::Bump; -use inkwell::execution_engine::{ExecutionEngine, JitFunction}; +use inkwell::execution_engine::ExecutionEngine; use roc_collections::all::MutMap; +use roc_gen::{run_jit_function, run_jit_function_dynamic_type}; use roc_module::ident::{Lowercase, TagName}; use roc_module::operator::CalledVia; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -50,20 +51,6 @@ pub unsafe fn jit_to_ast<'a>( jit_to_ast_help(&env, execution_engine, main_fn_name, layout, content) } -macro_rules! jit_map { - ($execution_engine: expr, $main_fn_name: expr, $ty: ty, $transform: expr) => {{ - unsafe { - let main: JitFunction $ty> = $execution_engine - .get_function($main_fn_name) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) - .expect("errored"); - - $transform(main.call()) - } - }}; -} - fn jit_to_ast_help<'a>( env: &Env<'a, '_>, execution_engine: ExecutionEngine, @@ -72,32 +59,30 @@ fn jit_to_ast_help<'a>( content: &Content, ) -> Expr<'a> { match layout { - Layout::Builtin(Builtin::Int64) => { - jit_map!(execution_engine, main_fn_name, i64, |num| num_to_ast( - env, - i64_to_ast(env.arena, num), - content - )) - } - Layout::Builtin(Builtin::Float64) => { - jit_map!(execution_engine, main_fn_name, f64, |num| num_to_ast( - env, - f64_to_ast(env.arena, num), - content - )) - } - Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => jit_map!( + Layout::Builtin(Builtin::Int64) => run_jit_function!( + execution_engine, + main_fn_name, + i64, + |num| num_to_ast(env, i64_to_ast(env.arena, num), content) + ), + Layout::Builtin(Builtin::Float64) => run_jit_function!( + execution_engine, + main_fn_name, + f64, + |num| num_to_ast(env, f64_to_ast(env.arena, num), content) + ), + Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => run_jit_function!( execution_engine, main_fn_name, &'static str, |string: &'static str| { str_slice_to_ast(env.arena, env.arena.alloc(string)) } ), Layout::Builtin(Builtin::EmptyList) => { - jit_map!(execution_engine, main_fn_name, &'static str, |_| { + run_jit_function!(execution_engine, main_fn_name, &'static str, |_| { Expr::List(Vec::new_in(env.arena)) }) } - Layout::Builtin(Builtin::List(_, elem_layout)) => jit_map!( + Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!( execution_engine, main_fn_name, (*const libc::c_void, usize), @@ -118,18 +103,26 @@ fn jit_to_ast_help<'a>( } }; + let fields = [Layout::Builtin(Builtin::Int64), layout.clone()]; + let layout = Layout::Struct(&fields); + match env.ptr_bytes { // 64-bit target (8-byte pointers, 16-byte structs) 8 => match layout.stack_size(env.ptr_bytes) { 8 => { // just one eightbyte, returned as-is - jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| { - ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) - }) + run_jit_function!( + execution_engine, + main_fn_name, + [u8; 8], + |bytes: [u8; 8]| { + ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) + } + ) } 16 => { // two eightbytes, returned as-is - jit_map!( + run_jit_function!( execution_engine, main_fn_name, [u8; 16], @@ -138,14 +131,17 @@ fn jit_to_ast_help<'a>( } ) } - _ => { + larger_size => { // anything more than 2 eightbytes // the return "value" is a pointer to the result - jit_map!( + run_jit_function_dynamic_type!( execution_engine, main_fn_name, - *const u8, - |bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) } + larger_size as usize, + |bytes: *const u8| { + dbg!(bytes); + ptr_to_ast(bytes as *const libc::c_void) + } ) } }, @@ -157,24 +153,37 @@ fn jit_to_ast_help<'a>( match layout.stack_size(env.ptr_bytes) { 4 => { // just one fourbyte, returned as-is - jit_map!(execution_engine, main_fn_name, [u8; 4], |bytes: [u8; 4]| { - ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) - }) + run_jit_function!( + execution_engine, + main_fn_name, + [u8; 4], + |bytes: [u8; 4]| { + ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) + } + ) } 8 => { // just one fourbyte, returned as-is - jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| { - ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) - }) - } - _ => { - // anything more than 2 fourbytes - // the return "value" is a pointer to the result - jit_map!( + run_jit_function!( execution_engine, main_fn_name, - *const u8, - |bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) } + [u8; 8], + |bytes: [u8; 8]| { + ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) + } + ) + } + larger_size => { + // anything more than 2 fourbytes + // the return "value" is a pointer to the result + run_jit_function_dynamic_type!( + execution_engine, + main_fn_name, + larger_size as usize, + |bytes: *const u8| { + dbg!(bytes); + ptr_to_ast(bytes as *const libc::c_void) + } ) } } @@ -288,6 +297,7 @@ fn struct_to_ast<'a>( field_layouts: &[Layout<'a>], fields: &MutMap>, ) -> Expr<'a> { + dbg!(&ptr); let arena = env.arena; let subs = env.subs; let mut output = Vec::with_capacity_in(field_layouts.len(), &arena); diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index f867800665..f6ea7d787c 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -15,6 +15,14 @@ mod repl_eval { assert!(out.status.success()); } + fn expect_failure(input: &str, expected: &str) { + let out = helpers::repl_eval(input); + + assert_eq!(&out.stderr, ""); + assert_eq!(&out.stdout, expected); + assert!(out.status.success()); + } + #[test] fn literal_0() { expect_success("0", "0 : Num *"); @@ -241,6 +249,15 @@ mod repl_eval { ); } + #[test] + fn four_element_record() { + // if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help + expect_success( + "{ a: 1, b: 2, c: 3, d: 4 }", + "{ a: 1, b: 2, c: 3, d: 4 } : { a : Num *, b : Num *, c : Num *, d : Num * }", + ); + } + // #[test] // fn multiline_string() { // // If a string contains newlines, format it as a multiline string in the output diff --git a/compiler/gen/src/lib.rs b/compiler/gen/src/lib.rs index f053e8771d..b7a0724fd7 100644 --- a/compiler/gen/src/lib.rs +++ b/compiler/gen/src/lib.rs @@ -13,3 +13,5 @@ pub mod layout_id; pub mod llvm; + +pub mod run_roc; diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 57c348d2b8..6b9f0508ff 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -238,7 +238,10 @@ enum PassVia { impl PassVia { fn from_layout(ptr_bytes: u32, layout: &Layout<'_>) -> Self { - if layout.stack_size(ptr_bytes) > 16 { + let stack_size = layout.stack_size(ptr_bytes); + let eightbyte = 8; + + if stack_size > 2 * eightbyte { PassVia::Memory } else { PassVia::Register @@ -246,43 +249,20 @@ impl PassVia { } } -pub fn make_main_function<'a, 'ctx, 'env>( +/// entry point to roc code; uses the fastcc calling convention +pub fn build_roc_main<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, layout: &Layout<'a>, main_body: &roc_mono::ir::Stmt<'a>, -) -> (&'static str, &'a FunctionValue<'ctx>) { +) -> &'a FunctionValue<'ctx> { use inkwell::types::BasicType; - use PassVia::*; - let module = env.module; let context = env.context; let builder = env.builder; let arena = env.arena; let ptr_bytes = env.ptr_bytes; - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); - - let personality_func = module.add_function( - "__gxx_personality_v0", - context.i64_type().fn_type(&[], false), - Some(Linkage::External), - ); - - let cxa_begin_catch = module.add_function( - "__cxa_begin_catch", - u8_ptr.fn_type(&[u8_ptr.into()], false), - Some(Linkage::External), - ); - cxa_begin_catch.set_call_conventions(C_CALL_CONV); - - let cxa_end_catch = module.add_function( - "__cxa_end_catch", - context.void_type().fn_type(&[], false), - Some(Linkage::External), - ); - cxa_end_catch.set_call_conventions(C_CALL_CONV); - let return_type = basic_type_from_layout(&arena, context, &layout, ptr_bytes); let roc_main_fn_name = "$Test.roc_main"; @@ -294,9 +274,7 @@ pub fn make_main_function<'a, 'ctx, 'env>( .module .add_function(roc_main_fn_name, roc_main_fn_type, None); - // roc_main_fn.set_personality_function(personality_func); - - // our exposed main function adheres to the C calling convention + // internal function, use fast calling convention roc_main_fn.set_call_conventions(FAST_CALL_CONV); // Add main's body @@ -313,6 +291,26 @@ pub fn make_main_function<'a, 'ctx, 'env>( main_body, ); + env.arena.alloc(roc_main_fn) +} + +pub fn make_main_function<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + layout: &Layout<'a>, + main_body: &roc_mono::ir::Stmt<'a>, +) -> (&'static str, &'a FunctionValue<'ctx>) { + use inkwell::types::BasicType; + use PassVia::*; + + let context = env.context; + let builder = env.builder; + + let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + + // internal main function + let roc_main_fn = *build_roc_main(env, layout_ids, layout, main_body); + // build the C calling convention wrapper let main_fn_name = "$Test.main"; @@ -332,11 +330,7 @@ pub fn make_main_function<'a, 'ctx, 'env>( }; // Add main to the module. - - // Add main to the module. - let main_fn = env.module.add_function("$Test.main", main_fn_type, None); - - // main_fn.set_personality_function(personality_func); + let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); // our exposed main function adheres to the C calling convention main_fn.set_call_conventions(C_CALL_CONV); @@ -352,144 +346,146 @@ pub fn make_main_function<'a, 'ctx, 'env>( let result_alloca = builder.build_alloca(main_return_type, "result"); // invoke instead of call, so that we can catch any exeptions thrown in Roc code - let call = builder.build_invoke(roc_main_fn, &[], then_block, catch_block, "call_roc_main"); - - call.set_call_convention(FAST_CALL_CONV); - - let call_result = call.try_as_basic_value().left().unwrap(); + let call_result = { + let call = builder.build_invoke(roc_main_fn, &[], then_block, catch_block, "call_roc_main"); + call.set_call_convention(FAST_CALL_CONV); + call.try_as_basic_value().left().unwrap() + }; // exception handling - builder.position_at_end(catch_block); + { + builder.position_at_end(catch_block); - let landing_pad_type = context.struct_type( - &[ - context.i8_type().ptr_type(AddressSpace::Generic).into(), - context.i32_type().into(), - ], - false, - ); + let landing_pad_type = { + let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into(); + let selector_value = context.i32_type().into(); - let info = builder - .build_landing_pad( - &landing_pad_type, - &BasicValueEnum::IntValue(context.i8_type().const_zero()), - context - .i8_type() - .ptr_type(AddressSpace::Generic) - .const_zero(), - "main_landing_pad", - ) - .into_struct_value(); + context.struct_type(&[exception_ptr, selector_value], false) + }; - let exception_ptr = builder - .build_extract_value(info, 0, "exception_ptr") - .unwrap(); + let info = builder + .build_catch_all_landing_pad( + &landing_pad_type, + &BasicValueEnum::IntValue(context.i8_type().const_zero()), + context.i8_type().ptr_type(AddressSpace::Generic), + "main_landing_pad", + ) + .into_struct_value(); - let puts = module.add_function( - "puts", - context.i32_type().fn_type(&[u8_ptr.into()], false), - Some(Linkage::External), - ); - - /* - let v = module.get_global("error_msg").unwrap().as_pointer_value(); - let vv = builder.build_bitcast(v, u8_ptr, "name"); - */ - let call = builder.build_call(cxa_begin_catch, &[exception_ptr], "call"); - let thrown = call.try_as_basic_value().left().unwrap(); - - let v42 = context.i64_type().const_int(42, false); - - let exception_type = u8_ptr; - let ptr = builder.build_bitcast( - thrown, - exception_type.ptr_type(AddressSpace::Generic), - "cast", - ); - let val = builder.build_load(ptr.into_pointer_value(), "name"); - // builder.build_call(puts, &[val], "call"); - - let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false); - - let return_value = { - let v1 = return_type.const_zero(); - - let v2 = builder - .build_insert_value(v1, context.i64_type().const_int(1, false), 0, "set_error") - .unwrap(); - let v3 = builder - .build_insert_value(v2, val, 1, "set_exception") + let exception_ptr = builder + .build_extract_value(info, 0, "exception_ptr") .unwrap(); - v3 - }; + let thrown = cxa_begin_catch(env, exception_ptr); - let ptr = builder.build_bitcast( - result_alloca, - return_type.ptr_type(AddressSpace::Generic), - "name", - ); - builder.build_store(ptr.into_pointer_value(), return_value); + let error_msg = { + let exception_type = u8_ptr; + let ptr = builder.build_bitcast( + thrown, + exception_type.ptr_type(AddressSpace::Generic), + "cast", + ); - builder.build_call(cxa_end_catch, &[], "call"); + builder.build_load(ptr.into_pointer_value(), "error_msg") + }; - builder.build_unconditional_branch(cont_block); + let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false); - builder.position_at_end(then_block); + let return_value = { + let v1 = return_type.const_zero(); - let actual_return_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes); - let return_type = context.struct_type(&[context.i64_type().into(), actual_return_type], false); + // flag is non-zero, indicating failure + let flag = context.i64_type().const_int(1, false); - let return_value = { - let v1 = return_type.const_zero(); + let v2 = builder + .build_insert_value(v1, flag, 0, "set_error") + .unwrap(); - let v2 = builder - .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") - .unwrap(); - let v3 = builder - .build_insert_value(v2, call_result, 1, "set_call_result") - .unwrap(); + let v3 = builder + .build_insert_value(v2, error_msg, 1, "set_exception") + .unwrap(); - v3 - }; + v3 + }; - let ptr = builder.build_bitcast( - result_alloca, - return_type.ptr_type(AddressSpace::Generic), - "name", - ); - builder.build_store(ptr.into_pointer_value(), return_value); + // bitcast result alloca so we can store our concrete type { flag, error_msg } in there + let result_alloca_bitcast = builder + .build_bitcast( + result_alloca, + return_type.ptr_type(AddressSpace::Generic), + "result_alloca_bitcast", + ) + .into_pointer_value(); - builder.build_unconditional_branch(cont_block); + // store our return value + builder.build_store(result_alloca_bitcast, return_value); - builder.position_at_end(cont_block); + cxa_end_catch(env); - let result = builder.build_load(result_alloca, "result"); + builder.build_unconditional_branch(cont_block); + } - match register_or_memory { - Memory => { - // write the result into the supplied pointer - // this is a void function, therefore return None + { + builder.position_at_end(then_block); - let ptr_return_type = main_return_type.ptr_type(AddressSpace::Generic); + let actual_return_type = + basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes); + let return_type = + context.struct_type(&[context.i64_type().into(), actual_return_type], false); - let ptr_as_int = main_fn.get_first_param().unwrap(); + let return_value = { + let v1 = return_type.const_zero(); - let ptr = builder.build_bitcast(ptr_as_int, ptr_return_type, "caller_ptr"); + let v2 = builder + .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") + .unwrap(); + let v3 = builder + .build_insert_value(v2, call_result, 1, "set_call_result") + .unwrap(); - builder.build_store(ptr.into_pointer_value(), result); + v3 + }; - builder.build_return(None); - } - Register => { - // construct a normal return - // values are passed to the caller via registers - builder.build_return(Some(&result)); + let ptr = builder.build_bitcast( + result_alloca, + return_type.ptr_type(AddressSpace::Generic), + "name", + ); + builder.build_store(ptr.into_pointer_value(), return_value); + + builder.build_unconditional_branch(cont_block); + } + + { + builder.position_at_end(cont_block); + + let result = builder.build_load(result_alloca, "result"); + + match register_or_memory { + Memory => { + // write the result into the supplied pointer + let ptr_return_type = main_return_type.ptr_type(AddressSpace::Generic); + + let ptr_as_int = main_fn.get_first_param().unwrap(); + + let ptr = builder.build_bitcast(ptr_as_int, ptr_return_type, "caller_ptr"); + + builder.build_store(ptr.into_pointer_value(), result); + + // this is a void function, therefore return None + builder.build_return(None); + } + Register => { + // construct a normal return + // values are passed to the caller via registers + builder.build_return(Some(&result)); + } } } - // MUST set the personality at the bottom; doing it earlier can cause the personality to be - // ignored + // MUST set the personality at the very end; + // doing it earlier can cause the personality to be ignored + let personality_func = get_gxx_personality_v0(env); main_fn.set_personality_function(personality_func); (main_fn_name, env.arena.alloc(main_fn)) @@ -1372,100 +1368,12 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( } RuntimeError(error_msg) => { - let context = env.context; - let module = env.module; - let builder = env.builder; + throw_exception(env, error_msg); - // define the error message as a global constant - let error_msg_global = module.add_global( - context.i8_type().array_type(error_msg.len() as u32), - None, - "error_msg", - ); - - let i8_type = context.i8_type(); - let error_msg_bytes = error_msg - .chars() - .map(|c| i8_type.const_int(c as u64, false)) - .collect::>(); - let const_array = i8_type.const_array(&error_msg_bytes); - error_msg_global.set_initializer(&const_array); - - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); - let void_ptr = u8_ptr.clone(); - - // void *__cxa_allocate_exception(size_t thrown_size); - let cxa_allocate_exception = module.add_function( - "__cxa_allocate_exception", - void_ptr.fn_type(&[context.i64_type().into()], false), - Some(Linkage::External), - ); - - // void __cxa_throw (void *thrown_exception, std::type_info *tinfo, void (*dest) (void *) ); - let cxa_throw = module.add_function( - "__cxa_throw", - context - .void_type() - .fn_type(&[u8_ptr.into(), u8_ptr.into(), u8_ptr.into()], false), - Some(Linkage::External), - ); - - let cxa_rethrow = module.add_function( - "__cxa_rethrow", - context.void_type().fn_type(&[], false), - Some(Linkage::External), - ); - cxa_rethrow.set_call_conventions(C_CALL_CONV); - - let ztii = module.add_global(u8_ptr, Some(AddressSpace::Generic), "_ZTIi"); - ztii.set_linkage(Linkage::External); - - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); - - let zero = context.i64_type().const_int(8, false); - let call = - builder.build_call(cxa_allocate_exception, &[zero.into()], "allocate_exception"); - call.set_call_convention(C_CALL_CONV); - - let info = { - let initial = call.try_as_basic_value().left().unwrap(); - - let error_msg_ptr = error_msg_global.as_pointer_value(); - let error_msg_ptr2 = builder.build_bitcast(error_msg_ptr, u8_ptr, "cast_u8ptr"); - - let exception_type = u8_ptr; - let exception_value = error_msg_ptr2; - - let temp = builder - .build_bitcast( - initial, - exception_type.ptr_type(AddressSpace::Generic), - "name", - ) - .into_pointer_value(); - - builder.build_store(temp, exception_value); - - initial - }; - - let arg1 = builder.build_bitcast(ztii.as_pointer_value(), u8_ptr, "cast"); - let arg2 = context - .i8_type() - .ptr_type(AddressSpace::Generic) - .const_zero(); - - let call = builder.build_call(cxa_throw, &[info, arg1, arg2.into()], "throw"); - call.set_call_convention(C_CALL_CONV); - - builder.build_unreachable(); - - let zero = context.i64_type().const_zero(); - - // builder.build_return(Some(&BasicValueEnum::IntValue(zero))); + // unused value (must return a BasicValue) + let zero = env.context.i64_type().const_zero(); zero.into() } - _ => todo!("unsupported expr {:?}", stmt), } } @@ -2684,3 +2592,272 @@ fn build_float_unary_op<'a, 'ctx, 'env>( } } } + +fn define_global_str<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + message: &str, +) -> inkwell::values::GlobalValue<'ctx> { + let context = env.context; + let module = env.module; + + // hash the name so we don't re-define existing messages + let name = { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut hasher = DefaultHasher::new(); + message.hash(&mut hasher); + let hash = hasher.finish(); + + format!("message_{}", hash) + }; + + match module.get_global(&name) { + Some(current) => current, + None => { + let i8_type = context.i8_type(); + + // define the error message as a global constant + let message_global = + module.add_global(i8_type.array_type(message.len() as u32), None, &name); + + let mut message_bytes = Vec::with_capacity_in(message.len(), env.arena); + + for c in message.chars() { + message_bytes.push(i8_type.const_int(c as u64, false)); + } + + let const_array = i8_type.const_array(&message_bytes); + message_global.set_initializer(&const_array); + + message_global + } + } +} + +fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) { + let context = env.context; + let builder = env.builder; + + let info = { + // we represend both void and char pointers with `u8*` + let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + + // allocate an exception (that can hold a pointer to a string) + let str_ptr_size = env + .context + .i64_type() + .const_int(env.ptr_bytes as u64, false); + let initial = cxa_allocate_exception(env, str_ptr_size); + + // define the error message as a global + // (a hash is used such that the same value is not defined repeatedly) + let error_msg_global = define_global_str(env, message); + + // cast this to a void pointer + let error_msg_ptr = + builder.build_bitcast(error_msg_global.as_pointer_value(), u8_ptr, "unused"); + + // store this void pointer in the exception + let exception_type = u8_ptr; + let exception_value = error_msg_ptr; + + let temp = builder + .build_bitcast( + initial, + exception_type.ptr_type(AddressSpace::Generic), + "exception_object_str_ptr_ptr", + ) + .into_pointer_value(); + + builder.build_store(temp, exception_value); + + initial + }; + + cxa_throw_exception(env, info); + + builder.build_unreachable(); +} + +fn cxa_allocate_exception<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + exception_size: IntValue<'ctx>, +) -> BasicValueEnum<'ctx> { + let name = "__cxa_allocate_exception"; + + let module = env.module; + let context = env.context; + let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + + let function = match module.get_function(&name) { + Some(gvalue) => gvalue, + None => { + // void *__cxa_allocate_exception(size_t thrown_size); + let cxa_allocate_exception = module.add_function( + name, + u8_ptr.fn_type(&[context.i64_type().into()], false), + Some(Linkage::External), + ); + cxa_allocate_exception.set_call_conventions(C_CALL_CONV); + + cxa_allocate_exception + } + }; + let call = env.builder.build_call( + function, + &[exception_size.into()], + "exception_object_void_ptr", + ); + + call.set_call_convention(C_CALL_CONV); + call.try_as_basic_value().left().unwrap() +} + +fn cxa_throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, info: BasicValueEnum<'ctx>) { + let name = "__cxa_throw"; + + let module = env.module; + let context = env.context; + let builder = env.builder; + + let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + + let function = match module.get_function(&name) { + Some(value) => value, + None => { + // void __cxa_throw (void *thrown_exception, std::type_info *tinfo, void (*dest) (void *) ); + let cxa_throw = module.add_function( + name, + context + .void_type() + .fn_type(&[u8_ptr.into(), u8_ptr.into(), u8_ptr.into()], false), + Some(Linkage::External), + ); + cxa_throw.set_call_conventions(C_CALL_CONV); + + cxa_throw + } + }; + + // global storing the type info of a c++ int (equivalent to `i32` in llvm) + // we just need any valid such value, and arbitrarily use this one + let ztii = match module.get_global("_ZTIi") { + Some(gvalue) => gvalue.as_pointer_value(), + None => { + let ztii = module.add_global(u8_ptr, Some(AddressSpace::Generic), "_ZTIi"); + ztii.set_linkage(Linkage::External); + + ztii.as_pointer_value() + } + }; + + let type_info = builder.build_bitcast(ztii, u8_ptr, "cast"); + let null: BasicValueEnum = u8_ptr.const_zero().into(); + + let call = builder.build_call(function, &[info, type_info, null], "throw"); + call.set_call_convention(C_CALL_CONV); +} + +#[allow(dead_code)] +fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> { + let name = "__cxa_rethrow"; + + let module = env.module; + let context = env.context; + + let function = match module.get_function(&name) { + Some(gvalue) => gvalue, + None => { + let cxa_rethrow = module.add_function( + name, + context.void_type().fn_type(&[], false), + Some(Linkage::External), + ); + cxa_rethrow.set_call_conventions(C_CALL_CONV); + + cxa_rethrow + } + }; + let call = env.builder.build_call(function, &[], "never_used"); + + call.set_call_convention(C_CALL_CONV); + call.try_as_basic_value().left().unwrap() +} + +fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> { + let name = "__cxa_rethrow"; + + let module = env.module; + let context = env.context; + + match module.get_function(&name) { + Some(gvalue) => gvalue, + None => { + let personality_func = module.add_function( + "__gxx_personality_v0", + context.i64_type().fn_type(&[], false), + Some(Linkage::External), + ); + personality_func.set_call_conventions(C_CALL_CONV); + + personality_func + } + } +} + +fn cxa_end_catch<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) { + let name = "__cxa_end_catch"; + + let module = env.module; + let context = env.context; + + let function = match module.get_function(&name) { + Some(gvalue) => gvalue, + None => { + let cxa_end_catch = module.add_function( + name, + context.void_type().fn_type(&[], false), + Some(Linkage::External), + ); + cxa_end_catch.set_call_conventions(C_CALL_CONV); + + cxa_end_catch + } + }; + let call = env.builder.build_call(function, &[], "never_used"); + + call.set_call_convention(C_CALL_CONV); +} + +fn cxa_begin_catch<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + exception_ptr: BasicValueEnum<'ctx>, +) -> BasicValueEnum<'ctx> { + let name = "__cxa_begin_catch"; + + let module = env.module; + let context = env.context; + + let function = match module.get_function(&name) { + Some(gvalue) => gvalue, + None => { + let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + + let cxa_begin_catch = module.add_function( + "__cxa_begin_catch", + u8_ptr.fn_type(&[u8_ptr.into()], false), + Some(Linkage::External), + ); + cxa_begin_catch.set_call_conventions(C_CALL_CONV); + + cxa_begin_catch + } + }; + let call = env + .builder + .build_call(function, &[exception_ptr], "exception_payload_ptr"); + + call.set_call_convention(C_CALL_CONV); + call.try_as_basic_value().left().unwrap() +} diff --git a/compiler/gen/tests/gen_primitives.rs b/compiler/gen/tests/gen_primitives.rs index a0a8a79b84..82b0052d55 100644 --- a/compiler/gen/tests/gen_primitives.rs +++ b/compiler/gen/tests/gen_primitives.rs @@ -820,7 +820,10 @@ mod gen_primitives { assert_evals_to!( indoc!( r#" - x + z + if True then + x + z + else + y + z "# ), 3, diff --git a/compiler/gen/tests/gen_records.rs b/compiler/gen/tests/gen_records.rs index 0319e09f17..ffcb65cca3 100644 --- a/compiler/gen/tests/gen_records.rs +++ b/compiler/gen/tests/gen_records.rs @@ -664,6 +664,19 @@ mod gen_records { ); } + #[test] + fn return_nested_record() { + assert_evals_to!( + indoc!( + r#" + { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } } + "# + ), + (0x0, (6.28, 3.14, 0.1)), + (i64, (f64, f64, f64)) + ); + } + #[test] fn just_to_be_sure() { assert_evals_to!( diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index 1aba6a0551..91d68557b3 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -1,7 +1,6 @@ use roc_collections::all::MutSet; use roc_types::subs::Subs; -use std::ffi::{CStr, CString}; -use std::fmt; +use std::ffi::CString; use std::os::raw::c_char; #[repr(C)] @@ -39,7 +38,11 @@ pub fn helper_without_uniqueness<'a>( src: &str, leak: bool, context: &'a inkwell::context::Context, -) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) { +) -> ( + &'static str, + Vec, + inkwell::execution_engine::ExecutionEngine<'a>, +) { use crate::helpers::{can_expr, infer_expr, CanExprOut}; use inkwell::OptimizationLevel; use roc_gen::llvm::build::{build_proc, build_proc_header}; @@ -57,6 +60,8 @@ pub fn helper_without_uniqueness<'a>( problems, .. } = can_expr(src); + + // don't panic based on the errors here, so we can test that RuntimeError generates the correct code let errors = problems .into_iter() .filter(|problem| { @@ -70,8 +75,6 @@ pub fn helper_without_uniqueness<'a>( }) .collect::>(); - // assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors); - let subs = Subs::new(var_store.into()); let mut unify_problems = Vec::new(); let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); @@ -211,7 +214,7 @@ pub fn helper_without_uniqueness<'a>( // Uncomment this to see the module's optimized LLVM instruction output: // env.module.print_to_stderr(); - (main_fn_name, execution_engine.clone()) + (main_fn_name, errors, execution_engine.clone()) } pub fn helper_with_uniqueness<'a>( @@ -434,7 +437,7 @@ macro_rules! assert_llvm_evals_to { let context = Context::create(); - let (main_fn_name, execution_engine) = + let (main_fn_name, errors, execution_engine) = $crate::helpers::eval::helper_without_uniqueness(&arena, $src, $leak, &context); unsafe { @@ -445,7 +448,12 @@ macro_rules! assert_llvm_evals_to { .expect("errored"); match main.call().into() { - Ok(success) => assert_eq!($transform(success), $expected), + Ok(success) => { + // only if there are no exceptions thrown, check for errors + assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors); + + assert_eq!($transform(success), $expected); + } Err(error_msg) => panic!("Roc failed with message: {}", error_msg), } }