diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 718b480a0c..efafca3cec 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -1,7 +1,6 @@ use bumpalo::Bump; use inkwell::context::Context; use inkwell::execution_engine::ExecutionEngine; -use inkwell::types::BasicType; use inkwell::OptimizationLevel; use roc_builtins::unique::uniq_stdlib; use roc_can::constraint::Constraint; @@ -15,7 +14,6 @@ use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Imp use roc_fmt::annotation::{Formattable, Newlines, Parens}; use roc_gen::layout_id::LayoutIds; use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel}; -use roc_gen::llvm::convert::basic_type_from_layout; use roc_module::ident::Ident; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_mono::ir::Procs; @@ -223,10 +221,6 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result Result( } }; - // Functions can return structs of either 8 or 16 bytes, depending - // on whether we're compiling for a 64-bit or 32-bit target. match env.ptr_bytes { // 64-bit target (8-byte pointers, 16-byte structs) - 8 => jit_map!( - execution_engine, - main_fn_name, - [u8; 16], - |bytes: [u8; 16]| { ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) } - ), + 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) + }) + } + 16 => { + // two eightbytes, returned as-is + jit_map!( + execution_engine, + main_fn_name, + [u8; 16], + |bytes: [u8; 16]| { + ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) + } + ) + } + _ => { + // anything more than 2 eightbytes + // the return "value" is a pointer to the result + jit_map!( + execution_engine, + main_fn_name, + *const u8, + |bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) } + ) + } + }, // 32-bit target (4-byte pointers, 8-byte structs) - 4 => jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| { - ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) - }), + 4 => { + // TODO what are valid return sizes here? + // this is just extrapolated from the 64-bit case above + // and not (yet) actually tested on a 32-bit system + 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) + }) + } + 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!( + execution_engine, + main_fn_name, + *const u8, + |bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) } + ) + } + } + } other => { panic!("Unsupported target: Roc cannot currently compile to systems where pointers are {} bytes in length.", other); } @@ -280,7 +327,7 @@ fn struct_to_ast<'a>( output.push(loc_field); // Advance the field pointer to the next field. - field_ptr = unsafe { ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) }; + field_ptr = unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) }; } Expr::Record { diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 360eb9d2e2..f867800665 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -232,6 +232,15 @@ mod repl_eval { ); } + #[test] + fn three_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 }", + "{ a: 1, b: 2, c: 3 } : { a : Num *, b : Num *, c : 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/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 3c31c7b3e3..4c02ea9878 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -219,6 +219,120 @@ pub fn construct_optimization_passes<'a>( (mpm, fpm) } +/// For communication with C (tests and platforms) we need to abide by the C calling convention +/// +/// While small values are just returned like with the fast CC, larger structures need to +/// be written into a pointer (into the callers stack) +enum PassVia { + Register, + Memory, +} + +impl PassVia { + fn from_layout(ptr_bytes: u32, layout: &Layout<'_>) -> Self { + if layout.stack_size(ptr_bytes) > 16 { + PassVia::Memory + } else { + PassVia::Register + } + } +} + +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 arena = env.arena; + let ptr_bytes = env.ptr_bytes; + + let return_type = basic_type_from_layout(&arena, context, &layout, ptr_bytes); + let roc_main_fn_name = "$Test.roc_main"; + + // make the roc main function + let roc_main_fn_type = return_type.fn_type(&[], false); + + // Add main to the module. + let roc_main_fn = env + .module + .add_function(roc_main_fn_name, roc_main_fn_type, None); + + // our exposed main function adheres to the C calling convention + roc_main_fn.set_call_conventions(FAST_CALL_CONV); + + // Add main's body + let basic_block = context.append_basic_block(roc_main_fn, "entry"); + + builder.position_at_end(basic_block); + + // builds the function body (return statement included) + build_exp_stmt( + env, + layout_ids, + &mut Scope::default(), + roc_main_fn, + main_body, + ); + + // build the C calling convention wrapper + + let main_fn_name = "$Test.main"; + let register_or_memory = PassVia::from_layout(env.ptr_bytes, layout); + + let main_fn_type = match register_or_memory { + Memory => { + let return_value_ptr = context.i64_type().ptr_type(AddressSpace::Generic).into(); + context.void_type().fn_type(&[return_value_ptr], false) + } + Register => return_type.fn_type(&[], false), + }; + + // Add main to the module. + 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); + + // Add main's body + let basic_block = context.append_basic_block(main_fn, "entry"); + + builder.position_at_end(basic_block); + + let call = builder.build_call(roc_main_fn, &[], "call_roc_main"); + call.set_call_convention(FAST_CALL_CONV); + + let call_result = call.try_as_basic_value().left().unwrap(); + + match register_or_memory { + Memory => { + // write the result into the supplied pointer + // this is a void function, therefore return None + let ptr_return_type = 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(), call_result); + + builder.build_return(None); + } + Register => { + // construct a normal return + // values are passed to the caller via registers + builder.build_return(Some(&call_result)); + } + } + + (main_fn_name, env.arena.alloc(main_fn)) +} + fn get_inplace_from_layout(layout: &Layout<'_>) -> InPlace { match layout { Layout::Builtin(Builtin::EmptyList) => InPlace::InPlace, diff --git a/compiler/gen/tests/gen_records.rs b/compiler/gen/tests/gen_records.rs index a0bd197006..0319e09f17 100644 --- a/compiler/gen/tests/gen_records.rs +++ b/compiler/gen/tests/gen_records.rs @@ -533,4 +533,147 @@ mod gen_records { i64 ); } + + #[test] + fn return_record_2() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5 } + "# + ), + [3, 5], + [i64; 2] + ); + } + + #[test] + fn return_record_3() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5, z: 4 } + "# + ), + (3, 5, 4), + (i64, i64, i64) + ); + } + + #[test] + fn return_record_4() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2 } + "# + ), + [3, 5, 4, 2], + [i64; 4] + ); + } + + #[test] + fn return_record_5() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1 } + "# + ), + [3, 5, 4, 2, 1], + [i64; 5] + ); + } + + #[test] + fn return_record_6() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } + "# + ), + [3, 5, 4, 2, 1, 7], + [i64; 6] + ); + } + + #[test] + fn return_record_7() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } + "# + ), + [3, 5, 4, 2, 1, 7, 8], + [i64; 7] + ); + } + + #[test] + fn return_record_float_int() { + assert_evals_to!( + indoc!( + r#" + { a: 3.14, b: 0x1 } + "# + ), + (3.14, 0x1), + (f64, i64) + ); + } + + #[test] + fn return_record_int_float() { + assert_evals_to!( + indoc!( + r#" + { a: 0x1, b: 3.14 } + "# + ), + (0x1, 3.14), + (i64, f64) + ); + } + + #[test] + fn return_record_float_float() { + assert_evals_to!( + indoc!( + r#" + { a: 6.28, b: 3.14 } + "# + ), + (6.28, 3.14), + (f64, f64) + ); + } + + #[test] + fn return_record_float_float_float() { + assert_evals_to!( + indoc!( + r#" + { a: 6.28, b: 3.14, c: 0.1 } + "# + ), + (6.28, 3.14, 0.1), + (f64, f64, f64) + ); + } + + #[test] + fn just_to_be_sure() { + assert_evals_to!( + indoc!( + r#" + { a: 1, b : 2, c : 3 } + "# + ), + [1, 2, 3], + [i64; 3] + ); + } } diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index 42834c10f9..4cd452c5b0 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -8,11 +8,8 @@ pub fn helper_without_uniqueness<'a>( context: &'a inkwell::context::Context, ) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) { use crate::helpers::{can_expr, infer_expr, CanExprOut}; - use inkwell::types::BasicType; use inkwell::OptimizationLevel; - use roc_gen::llvm::build::Scope; use roc_gen::llvm::build::{build_proc, build_proc_header}; - use roc_gen::llvm::convert::basic_type_from_layout; use roc_mono::layout::Layout; let target = target_lexicon::Triple::host(); @@ -66,7 +63,7 @@ pub fn helper_without_uniqueness<'a>( roc_gen::llvm::build::construct_optimization_passes(module, opt_level); // Compute main_fn_type before moving subs to Env - let layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| { + let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| { panic!( "Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}", err @@ -76,10 +73,6 @@ pub fn helper_without_uniqueness<'a>( .create_jit_execution_engine(OptimizationLevel::None) .expect("Error creating JIT execution engine for test"); - let main_fn_type = - basic_type_from_layout(&arena, context, &layout, ptr_bytes).fn_type(&[], false); - let main_fn_name = "$Test.main"; - // Compile and add all the Procs before adding main let mut env = roc_gen::llvm::build::Env { arena: &arena, @@ -163,25 +156,8 @@ pub fn helper_without_uniqueness<'a>( } } - // Add main to the module. - let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); - let cc = roc_gen::llvm::build::FAST_CALL_CONV; - - main_fn.set_call_conventions(cc); - - // Add main's body - let basic_block = context.append_basic_block(main_fn, "entry"); - - builder.position_at_end(basic_block); - - // builds the function body (return statement included) - roc_gen::llvm::build::build_exp_stmt( - &env, - &mut layout_ids, - &mut Scope::default(), - main_fn, - &main_body, - ); + let (main_fn_name, main_fn) = + roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body); // Uncomment this to see the module's un-optimized LLVM instruction output: // env.module.print_to_stderr(); @@ -212,11 +188,8 @@ pub fn helper_with_uniqueness<'a>( context: &'a inkwell::context::Context, ) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) { use crate::helpers::{infer_expr, uniq_expr}; - use inkwell::types::BasicType; use inkwell::OptimizationLevel; - use roc_gen::llvm::build::Scope; use roc_gen::llvm::build::{build_proc, build_proc_header}; - use roc_gen::llvm::convert::basic_type_from_layout; use roc_mono::layout::Layout; let target = target_lexicon::Triple::host(); @@ -258,7 +231,7 @@ pub fn helper_with_uniqueness<'a>( let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level); // Compute main_fn_type before moving subs to Env - let layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| { + let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| { panic!( "Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}", err @@ -269,11 +242,6 @@ pub fn helper_with_uniqueness<'a>( .create_jit_execution_engine(OptimizationLevel::None) .expect("Error creating JIT execution engine for test"); - let main_fn_type = basic_type_from_layout(&arena, context, &layout, ptr_bytes) - .fn_type(&[], false) - .clone(); - let main_fn_name = "$Test.main"; - // Compile and add all the Procs before adding main let mut env = roc_gen::llvm::build::Env { arena: &arena, @@ -357,25 +325,8 @@ pub fn helper_with_uniqueness<'a>( } } - // Add main to the module. - let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); - let cc = roc_gen::llvm::build::FAST_CALL_CONV; - - main_fn.set_call_conventions(cc); - - // Add main's body - let basic_block = context.append_basic_block(main_fn, "entry"); - - builder.position_at_end(basic_block); - - // builds the function body (return statement included) - roc_gen::llvm::build::build_exp_stmt( - &env, - &mut layout_ids, - &mut Scope::default(), - main_fn, - &main_body, - ); + let (main_fn_name, main_fn) = + roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body); // you're in the version with uniqueness!