diff --git a/cli/src/build.rs b/cli/src/build.rs index 82b8242bc7..bbb5d6a9fa 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -247,7 +247,7 @@ pub fn build_file<'a>( link( target, binary_path.clone(), - &inputs, + &inputs, link_type ) .map_err(|_| { diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 200941919d..3e789da85b 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -218,8 +218,8 @@ pub fn gen_and_eval<'a>( // Verify the module if let Err(errors) = env.module.verify() { panic!( - "Errors defining module: {}\n\nUncomment things nearby to see more details.", - errors + "Errors defining module:\n{}\n\nUncomment things nearby to see more details.", + errors.to_string() ); } diff --git a/compiler/gen_llvm/src/llvm/bitcode.rs b/compiler/gen_llvm/src/llvm/bitcode.rs index 95dfa79c4b..159c688580 100644 --- a/compiler/gen_llvm/src/llvm/bitcode.rs +++ b/compiler/gen_llvm/src/llvm/bitcode.rs @@ -128,20 +128,19 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>( [tag_id, tag_value_ptr] => { let tag_type = basic_type_from_layout(env, &Layout::Union(union_layout)); - let argument_cast = env - .builder - .build_bitcast( - *tag_value_ptr, - tag_type.ptr_type(AddressSpace::Generic), - "load_opaque", - ) - .into_pointer_value(); - - let tag_value = env.builder.build_load(argument_cast, "get_value"); + let tag_value = env.builder.build_pointer_cast( + tag_value_ptr.into_pointer_value(), + tag_type.ptr_type(AddressSpace::Generic), + "load_opaque_get_tag_id", + ); let actual_tag_id = { - let tag_id_i64 = - crate::llvm::build::get_tag_id(env, function_value, &union_layout, tag_value); + let tag_id_i64 = crate::llvm::build::get_tag_id( + env, + function_value, + &union_layout, + tag_value.into(), + ); env.builder .build_int_cast(tag_id_i64, env.context.i16_type(), "to_i16") @@ -155,18 +154,11 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>( ); let tag_data_ptr = { - let data_index = env - .context - .i64_type() - .const_int(TAG_DATA_INDEX as u64, false); + let ptr = env + .builder + .build_struct_gep(tag_value, TAG_DATA_INDEX, "get_data_ptr") + .unwrap(); - let ptr = unsafe { - env.builder.build_gep( - tag_value_ptr.into_pointer_value(), - &[data_index], - "get_data_ptr", - ) - }; env.builder.build_bitcast(ptr, i8_ptr_type, "to_opaque") }; @@ -191,6 +183,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>( function: FunctionValue<'ctx>, closure_data_layout: LambdaSet<'a>, argument_layouts: &[Layout<'a>], + result_layout: Layout<'a>, ) -> FunctionValue<'ctx> { let fn_name: &str = &format!( "{}_zig_function_caller", @@ -204,6 +197,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>( function, closure_data_layout, argument_layouts, + result_layout, fn_name, ), } @@ -214,6 +208,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( roc_function: FunctionValue<'ctx>, closure_data_layout: LambdaSet<'a>, argument_layouts: &[Layout<'a>], + result_layout: Layout<'a>, fn_name: &str, ) -> FunctionValue<'ctx> { debug_assert!(argument_layouts.len() <= 7); @@ -260,12 +255,22 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) { let basic_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic); - let argument_cast = env - .builder - .build_bitcast(*argument_ptr, basic_type, "load_opaque") - .into_pointer_value(); + let argument = if layout.is_passed_by_reference() { + env.builder + .build_pointer_cast( + argument_ptr.into_pointer_value(), + basic_type, + "cast_ptr_to_tag_build_transform_caller_help", + ) + .into() + } else { + let argument_cast = env + .builder + .build_bitcast(*argument_ptr, basic_type, "load_opaque_1") + .into_pointer_value(); - let argument = env.builder.build_load(argument_cast, "load_opaque"); + env.builder.build_load(argument_cast, "load_opaque_2") + }; arguments_cast.push(argument); } @@ -288,31 +293,19 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( } } - let call = { - env.builder - .build_call(roc_function, arguments_cast.as_slice(), "tmp") - }; - - call.set_call_convention(FAST_CALL_CONV); - - let result = call - .try_as_basic_value() - .left() - .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")); + let result = crate::llvm::build::call_roc_function( + env, + roc_function, + &result_layout, + arguments_cast.as_slice(), + ); let result_u8_ptr = function_value .get_nth_param(argument_layouts.len() as u32 + 1) - .unwrap(); - let result_ptr = env - .builder - .build_bitcast( - result_u8_ptr, - result.get_type().ptr_type(AddressSpace::Generic), - "write_result", - ) + .unwrap() .into_pointer_value(); - env.builder.build_store(result_ptr, result); + crate::llvm::build::store_roc_value_opaque(env, result_layout, result_u8_ptr, result); env.builder.build_return(None); env.builder.position_at_end(block); @@ -414,12 +407,18 @@ fn build_rc_wrapper<'a, 'ctx, 'env>( let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic); - let value_cast = env - .builder - .build_bitcast(value_ptr, value_type, "load_opaque") - .into_pointer_value(); + let value = if layout.is_passed_by_reference() { + env.builder + .build_pointer_cast(value_ptr, value_type, "cast_ptr_to_tag_build_rc_wrapper") + .into() + } else { + let value_cast = env + .builder + .build_bitcast(value_ptr, value_type, "load_opaque") + .into_pointer_value(); - let value = env.builder.build_load(value_cast, "load_opaque"); + env.builder.build_load(value_cast, "load_opaque") + }; match rc_operation { Mode::Inc => { diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index dc0499ae67..85fb878694 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -21,7 +21,8 @@ use crate::llvm::build_str::{ }; use crate::llvm::compare::{generic_eq, generic_neq}; use crate::llvm::convert::{ - basic_type_from_builtin, basic_type_from_layout, block_of_memory_slices, ptr_int, + basic_type_from_builtin, basic_type_from_layout, basic_type_from_layout_1, + block_of_memory_slices, ptr_int, }; use crate::llvm::refcounting::{ build_reset, decrement_refcount_layout, increment_refcount_layout, PointerToRefcount, @@ -971,7 +972,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>( } } -const TAG_ID_INDEX: u32 = 1; +pub const TAG_ID_INDEX: u32 = 1; pub const TAG_DATA_INDEX: u32 = 0; pub fn struct_from_fields<'a, 'ctx, 'env, I>( @@ -1064,7 +1065,16 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( if !field_layout.is_dropped_because_empty() { field_types.push(basic_type_from_layout(env, field_layout)); - field_vals.push(field_expr); + if field_layout.is_passed_by_reference() { + let field_value = env.builder.build_load( + field_expr.into_pointer_value(), + "load_tag_to_put_in_struct", + ); + + field_vals.push(field_value); + } else { + field_vals.push(field_expr); + } } } @@ -1172,14 +1182,19 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( match (value, layout) { (StructValue(argument), Layout::Struct(fields)) => { debug_assert!(!fields.is_empty()); - env.builder + + let field_value = env + .builder .build_extract_value( argument, *index as u32, env.arena .alloc(format!("struct_field_access_record_{}", index)), ) - .unwrap() + .unwrap(); + + let field_layout = fields[*index as usize]; + use_roc_value(env, field_layout, field_value, "struct_field_tag") } ( PointerValue(argument), @@ -1228,30 +1243,26 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( index, union_layout, } => { - let builder = env.builder; - // cast the argument bytes into the desired shape for this tag let (argument, _structure_layout) = load_symbol_and_layout(scope, structure); match union_layout { UnionLayout::NonRecursive(tag_layouts) => { - debug_assert!(argument.is_struct_value()); + debug_assert!(argument.is_pointer_value()); + let field_layouts = tag_layouts[*tag_id as usize]; - let struct_layout = Layout::Struct(field_layouts); - let struct_type = basic_type_from_layout(env, &struct_layout); + let tag_id_type = + basic_type_from_layout(env, &union_layout.tag_id_layout()).into_int_type(); - let struct_value = access_index_struct_value( - builder, - argument.into_struct_value(), - struct_type.into_struct_type(), - ); - - let result = builder - .build_extract_value(struct_value, *index as u32, "") - .expect("desired field did not decode"); - - result + lookup_at_index_ptr2( + env, + union_layout, + tag_id_type, + field_layouts, + *index as usize, + argument.into_pointer_value(), + ) } UnionLayout::Recursive(tag_layouts) => { debug_assert!(argument.is_pointer_value()); @@ -1437,6 +1448,36 @@ fn build_wrapped_tag<'a, 'ctx, 'env>( } } +pub fn tag_alloca<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + type_: BasicTypeEnum<'ctx>, + name: &str, +) -> PointerValue<'ctx> { + let result_alloca = env.builder.build_alloca(type_, name); + + // Initialize all memory of the alloca. This _should_ not be required, but currently + // LLVM can access uninitialized memory after applying some optimizations. Hopefully + // we can in the future adjust code gen so this is no longer an issue. + // + // An example is + // + // main : Task.Task {} [] + // main = + // when List.len [ Ok "foo", Err 42, Ok "spam" ] is + // n -> Task.putLine (Str.fromInt n) + // + // Here the decrement function of result must first check it's an Ok tag, + // then defers to string decrement, which must check is the string is small or large + // + // After inlining, those checks are combined. That means that even if the tag is Err, + // a check is done on the "string" to see if it is big or small, which will touch the + // uninitialized memory. + let all_zeros = type_.const_zero(); + env.builder.build_store(result_alloca, all_zeros); + + result_alloca +} + pub fn build_tag<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, @@ -1459,27 +1500,7 @@ pub fn build_tag<'a, 'ctx, 'env>( let wrapper_type = env .context .struct_type(&[internal_type, tag_id_type.into()], false); - let result_alloca = env.builder.build_alloca(wrapper_type, "tag_opaque"); - - // Initialize all memory of the alloca. This _should_ not be required, but currently - // LLVM can access uninitialized memory after applying some optimizations. Hopefully - // we can in the future adjust code gen so this is no longer an issue. - // - // An example is - // - // main : Task.Task {} [] - // main = - // when List.len [ Ok "foo", Err 42, Ok "spam" ] is - // n -> Task.putLine (Str.fromInt n) - // - // Here the decrement function of result must first check it's an Ok tag, - // then defers to string decrement, which must check is the string is small or large - // - // After inlining, those checks are combined. That means that even if the tag is Err, - // a check is done on the "string" to see if it is big or small, which will touch the - // uninitialized memory. - let all_zeros = wrapper_type.const_zero(); - env.builder.build_store(result_alloca, all_zeros); + let result_alloca = tag_alloca(env, wrapper_type.into(), "opaque_tag"); // Determine types let num_fields = arguments.len() + 1; @@ -1513,7 +1534,7 @@ pub fn build_tag<'a, 'ctx, 'env>( // store the tag id let tag_id_ptr = env .builder - .build_struct_gep(result_alloca, TAG_ID_INDEX, "get_opaque_data") + .build_struct_gep(result_alloca, TAG_ID_INDEX, "tag_id_ptr") .unwrap(); let tag_id_intval = tag_id_type.const_int(tag_id as u64, false); @@ -1526,7 +1547,7 @@ pub fn build_tag<'a, 'ctx, 'env>( let struct_opaque_ptr = env .builder - .build_struct_gep(result_alloca, TAG_DATA_INDEX, "get_opaque_data") + .build_struct_gep(result_alloca, TAG_DATA_INDEX, "opaque_data_ptr") .unwrap(); let struct_ptr = env.builder.build_pointer_cast( struct_opaque_ptr, @@ -1546,10 +1567,13 @@ pub fn build_tag<'a, 'ctx, 'env>( .builder .build_struct_gep(struct_ptr, index, "get_tag_field_ptr") .unwrap(); - env.builder.build_store(ptr, field_val); + + let field_layout = tag_field_layouts[index as usize]; + store_roc_value(env, field_layout, ptr, field_val); } - env.builder.build_load(result_alloca, "load_result") + // env.builder.build_load(result_alloca, "load_result") + result_alloca.into() } UnionLayout::Recursive(tags) => { debug_assert!(union_size > 1); @@ -1857,9 +1881,10 @@ pub fn get_tag_id<'a, 'ctx, 'env>( match union_layout { UnionLayout::NonRecursive(_) => { - let tag = argument.into_struct_value(); + debug_assert!(argument.is_pointer_value(), "{:?}", argument); - get_tag_id_non_recursive(env, tag) + let argument_ptr = argument.into_pointer_value(); + get_tag_id_wrapped(env, argument_ptr) } UnionLayout::Recursive(_) => { let argument_ptr = argument.into_pointer_value(); @@ -1999,7 +2024,26 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>( .build_struct_gep(data_ptr, index as u32, "at_index_struct_gep") .unwrap(); - let result = builder.build_load(elem_ptr, "load_at_index_ptr"); + let field_layout = field_layouts[index]; + let result = if field_layout.is_passed_by_reference() { + let field_type = basic_type_from_layout(env, &field_layout); + + let align_bytes = field_layout.alignment_bytes(env.ptr_bytes); + let alloca = tag_alloca(env, field_type, "copied_tag"); + if align_bytes > 0 { + let size = env + .ptr_int() + .const_int(field_layout.stack_size(env.ptr_bytes) as u64, false); + + env.builder + .build_memcpy(alloca, align_bytes, elem_ptr, align_bytes, size) + .unwrap(); + } + + alloca.into() + } else { + builder.build_load(elem_ptr, "load_at_index_ptr") + }; if let Some(Layout::RecursivePointer) = field_layouts.get(index as usize) { // a recursive field is stored as a `i64*`, to use it we must cast it to @@ -2146,17 +2190,12 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( let ptr_type = value_type.ptr_type(AddressSpace::Generic); unsafe { - builder - .build_bitcast( - env.builder.build_in_bounds_gep( - as_usize_ptr, - &[index_intvalue], - "get_data_ptr", - ), - ptr_type, - "alloc_cast_to_desired", - ) - .into_pointer_value() + builder.build_pointer_cast( + env.builder + .build_in_bounds_gep(as_usize_ptr, &[index_intvalue], "get_data_ptr"), + ptr_type, + "alloc_cast_to_desired", + ) } }; @@ -2182,13 +2221,13 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( fn list_literal<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, - elem_layout: &Layout<'a>, + element_layout: &Layout<'a>, elems: &[ListLiteralElement], ) -> BasicValueEnum<'ctx> { let ctx = env.context; let builder = env.builder; - let element_type = basic_type_from_layout(env, elem_layout); + let element_type = basic_type_from_layout(env, element_layout); let list_length = elems.len(); let list_length_intval = env.ptr_int().const_int(list_length as _, false); @@ -2198,9 +2237,9 @@ fn list_literal<'a, 'ctx, 'env>( // if element_type.is_int_type() { if false { let element_type = element_type.into_int_type(); - let element_width = elem_layout.stack_size(env.ptr_bytes); + let element_width = element_layout.stack_size(env.ptr_bytes); let size = list_length * element_width as usize; - let alignment = elem_layout + let alignment = element_layout .alignment_bytes(env.ptr_bytes) .max(env.ptr_bytes); @@ -2232,7 +2271,7 @@ fn list_literal<'a, 'ctx, 'env>( for (index, element) in elems.iter().enumerate() { match element { ListLiteralElement::Literal(literal) => { - let val = build_exp_literal(env, elem_layout, literal); + let val = build_exp_literal(env, element_layout, literal); global_elements.push(val.into_int_value()); } ListLiteralElement::Symbol(symbol) => { @@ -2287,7 +2326,7 @@ fn list_literal<'a, 'ctx, 'env>( super::build_list::store_list(env, ptr, list_length_intval) } else { // some of our elements are non-constant, so we must allocate space on the heap - let ptr = allocate_list(env, elem_layout, list_length_intval); + let ptr = allocate_list(env, element_layout, list_length_intval); // then, copy the relevant segment from the constant section into the heap env.builder @@ -2311,26 +2350,103 @@ fn list_literal<'a, 'ctx, 'env>( super::build_list::store_list(env, ptr, list_length_intval) } } else { - let ptr = allocate_list(env, elem_layout, list_length_intval); + let ptr = allocate_list(env, element_layout, list_length_intval); // Copy the elements from the list literal into the array for (index, element) in elems.iter().enumerate() { let val = match element { ListLiteralElement::Literal(literal) => { - build_exp_literal(env, elem_layout, literal) + build_exp_literal(env, element_layout, literal) } ListLiteralElement::Symbol(symbol) => load_symbol(scope, symbol), }; let index_val = ctx.i64_type().const_int(index as u64, false); let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; - builder.build_store(elem_ptr, val); + store_roc_value(env, *element_layout, elem_ptr, val); } super::build_list::store_list(env, ptr, list_length_intval) } } +pub fn load_roc_value<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: Layout<'a>, + source: PointerValue<'ctx>, + name: &str, +) -> BasicValueEnum<'ctx> { + if layout.is_passed_by_reference() { + let alloca = tag_alloca(env, basic_type_from_layout(env, &layout), name); + + store_roc_value(env, layout, alloca, source.into()); + + alloca.into() + } else { + env.builder.build_load(source, name) + } +} + +pub fn use_roc_value<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: Layout<'a>, + source: BasicValueEnum<'ctx>, + name: &str, +) -> BasicValueEnum<'ctx> { + if layout.is_passed_by_reference() { + let alloca = tag_alloca(env, basic_type_from_layout(env, &layout), name); + + env.builder.build_store(alloca, source); + + alloca.into() + } else { + source + } +} + +pub fn store_roc_value_opaque<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: Layout<'a>, + opaque_destination: PointerValue<'ctx>, + value: BasicValueEnum<'ctx>, +) { + let target_type = basic_type_from_layout(env, &layout).ptr_type(AddressSpace::Generic); + let destination = + env.builder + .build_pointer_cast(opaque_destination, target_type, "store_roc_value_opaque"); + + store_roc_value(env, layout, destination, value) +} + +pub fn store_roc_value<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: Layout<'a>, + destination: PointerValue<'ctx>, + value: BasicValueEnum<'ctx>, +) { + if layout.is_passed_by_reference() { + let align_bytes = layout.alignment_bytes(env.ptr_bytes); + + if align_bytes > 0 { + let size = env + .ptr_int() + .const_int(layout.stack_size(env.ptr_bytes) as u64, false); + + env.builder + .build_memcpy( + destination, + align_bytes, + value.into_pointer_value(), + align_bytes, + size, + ) + .unwrap(); + } + } else { + env.builder.build_store(destination, value); + } +} + pub fn build_exp_stmt<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, @@ -2388,15 +2504,73 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( result } Ret(symbol) => { - let value = load_symbol(scope, symbol); + let (value, layout) = load_symbol_and_layout(scope, symbol); - if let Some(block) = env.builder.get_insert_block() { - if block.get_terminator().is_none() { - env.builder.build_return(Some(&value)); + match RocReturn::from_layout(env, layout) { + RocReturn::Return => { + if let Some(block) = env.builder.get_insert_block() { + if block.get_terminator().is_none() { + env.builder.build_return(Some(&value)); + } + } + + value + } + RocReturn::ByPointer => { + // we need to write our value into the final argument of the current function + let parameters = parent.get_params(); + let out_parameter = parameters.last().unwrap(); + debug_assert!(out_parameter.is_pointer_value()); + + // store_roc_value(env, *layout, out_parameter.into_pointer_value(), value); + + let destination = out_parameter.into_pointer_value(); + if layout.is_passed_by_reference() { + let align_bytes = layout.alignment_bytes(env.ptr_bytes); + + if align_bytes > 0 { + let value_ptr = value.into_pointer_value(); + + // We can only do this if the function itself writes data into this + // pointer. If the pointer is passed as an argument, then we must copy + // from one pointer to our destination pointer + if value_ptr.get_first_use().is_some() { + value_ptr.replace_all_uses_with(destination); + } else { + let size = env + .ptr_int() + .const_int(layout.stack_size(env.ptr_bytes) as u64, false); + + env.builder + .build_memcpy( + destination, + align_bytes, + value.into_pointer_value(), + align_bytes, + size, + ) + .unwrap(); + } + } + } else { + env.builder.build_store(destination, value); + } + + if let Some(block) = env.builder.get_insert_block() { + match block.get_terminator() { + None => { + env.builder.build_return(None); + } + Some(terminator) => { + terminator.remove_from_basic_block(); + env.builder.build_return(None); + } + } + } + + env.context.i8_type().const_zero().into() } } - - value } Switch { @@ -2469,7 +2643,11 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( builder.position_at_end(cont_block); for (ptr, param) in joinpoint_args.iter().zip(parameters.iter()) { - let value = env.builder.build_load(*ptr, "load_jp_argument"); + let value = if param.layout.is_passed_by_reference() { + (*ptr).into() + } else { + env.builder.build_load(*ptr, "load_jp_argument") + }; scope.insert(param.symbol, (param.layout, value)); } @@ -2496,8 +2674,9 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( let (cont_block, argument_pointers) = scope.join_points.get(join_point).unwrap(); for (pointer, argument) in argument_pointers.iter().zip(arguments.iter()) { - let value = load_symbol(scope, argument); - builder.build_store(*pointer, value); + let (value, layout) = load_symbol_and_layout(scope, argument); + + store_roc_value(env, *layout, *pointer, value); } builder.build_unconditional_branch(*cont_block); @@ -2639,20 +2818,6 @@ pub fn load_symbol_and_lambda_set<'a, 'ctx, 'b>( } } -fn access_index_struct_value<'ctx>( - builder: &Builder<'ctx>, - from_value: StructValue<'ctx>, - to_type: StructType<'ctx>, -) -> StructValue<'ctx> { - complex_bitcast( - builder, - from_value.into(), - to_type.into(), - "access_index_struct_value", - ) - .into_struct_value() -} - /// Cast a value to another value of the same (or smaller?) size pub fn cast_basic_basic<'ctx>( builder: &Builder<'ctx>, @@ -3108,8 +3273,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( return_layout: Layout<'a>, c_function_name: &str, ) -> FunctionValue<'ctx> { - // NOTE we ignore env.is_gen_test here - let wrapper_return_type = roc_function.get_type().get_return_type().unwrap(); + // NOTE we ingore env.is_gen_test here let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); for layout in arguments { @@ -3119,12 +3283,19 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` // let mut argument_types = roc_function.get_type().get_param_types(); let mut argument_types = cc_argument_types; - let return_type = wrapper_return_type; - let output_type = return_type.ptr_type(AddressSpace::Generic); - argument_types.push(output_type.into()); + let c_function_type = match roc_function.get_type().get_return_type() { + None => { + // this function already returns by-pointer + roc_function.get_type() + } + Some(return_type) => { + let output_type = return_type.ptr_type(AddressSpace::Generic); + argument_types.push(output_type.into()); - let c_function_type = env.context.void_type().fn_type(&argument_types, false); + env.context.void_type().fn_type(&argument_types, false) + } + }; let c_function = add_func( env.module, @@ -3170,11 +3341,11 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( let arguments_for_call = &arguments_for_call.into_bump_slice(); - debug_assert_eq!(args.len(), roc_function.get_params().len()); - let call_result = { if env.is_gen_test { - let roc_wrapper_function = make_exception_catcher(env, roc_function); + debug_assert_eq!(args.len(), roc_function.get_params().len()); + + let roc_wrapper_function = make_exception_catcher(env, roc_function, return_layout); debug_assert_eq!( arguments_for_call.len(), roc_wrapper_function.get_params().len() @@ -3196,7 +3367,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( .unwrap() .into_pointer_value(); - builder.build_store(output_arg, call_result); + store_roc_value(env, return_layout, output_arg, call_result); builder.build_return(None); c_function @@ -3213,8 +3384,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( // a tagged union to indicate to the test loader that a panic occurred. // especially when running 32-bit binaries on a 64-bit machine, there // does not seem to be a smarter solution - let wrapper_return_type = - roc_result_type(env, roc_function.get_type().get_return_type().unwrap()); + let wrapper_return_type = roc_result_type(env, basic_type_from_layout(env, &return_layout)); let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); for layout in arguments { @@ -3276,18 +3446,14 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( let arguments_for_call = &arguments_for_call.into_bump_slice(); let call_result = { - let roc_wrapper_function = make_exception_catcher(env, roc_function); - debug_assert_eq!( - arguments_for_call.len(), - roc_wrapper_function.get_params().len() - ); + let roc_wrapper_function = make_exception_catcher(env, roc_function, return_layout); builder.position_at_end(entry); call_roc_function( env, roc_wrapper_function, - &return_layout, + &Layout::Struct(&[Layout::Builtin(Builtin::Int64), return_layout]), arguments_for_call, ) }; @@ -3360,7 +3526,8 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let wrapper_return_type = if env.is_gen_test { roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into() } else { - roc_function.get_type().get_return_type().unwrap() + // roc_function.get_type().get_return_type().unwrap() + basic_type_from_layout(env, &return_layout) }; let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); @@ -3417,10 +3584,15 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( CCReturn::Void => { debug_assert_eq!(args.len(), roc_function.get_params().len()); } - CCReturn::ByPointer => { - args = &args[..args.len() - 1]; - debug_assert_eq!(args.len(), roc_function.get_params().len()); - } + CCReturn::ByPointer => match RocReturn::from_layout(env, &return_layout) { + RocReturn::ByPointer => { + debug_assert_eq!(args.len(), roc_function.get_params().len()); + } + RocReturn::Return => { + args = &args[..args.len() - 1]; + debug_assert_eq!(args.len(), roc_function.get_params().len()); + } + }, } let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); @@ -3437,7 +3609,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( } } - let arguments_for_call = &arguments_for_call.into_bump_slice(); + let arguments_for_call = arguments_for_call.into_bump_slice(); let call_result = call_roc_function(env, roc_function, &return_layout, arguments_for_call); @@ -3508,21 +3680,17 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu .into_pointer_value() } -fn set_jump_and_catch_long_jump<'a, 'ctx, 'env, F, T>( +fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, - function: F, - calling_convention: u32, + roc_function: FunctionValue<'ctx>, arguments: &[BasicValueEnum<'ctx>], - return_type: T, -) -> BasicValueEnum<'ctx> -where - T: inkwell::types::BasicType<'ctx>, - F: Into>, -{ + return_layout: Layout<'a>, +) -> BasicValueEnum<'ctx> { let context = env.context; let builder = env.builder; + let return_type = basic_type_from_layout(env, &return_layout); let call_result_type = roc_result_type(env, return_type.as_basic_type_enum()); let result_alloca = builder.build_alloca(call_result_type, "result"); @@ -3591,13 +3759,9 @@ where { builder.position_at_end(then_block); - let call = env.builder.build_call(function, arguments, "call_function"); + let call_result = call_roc_function(env, roc_function, &return_layout, arguments); - call.set_call_convention(calling_convention); - - let call_result = call.try_as_basic_value().left().unwrap(); - - let return_value = make_good_roc_result(env, call_result); + let return_value = make_good_roc_result(env, return_layout, call_result); builder.build_store(result_alloca, return_value); @@ -3648,16 +3812,18 @@ where env.builder.position_at_end(cont_block); - builder.build_load(result_alloca, "load_result") + builder.build_load(result_alloca, "set_jump_and_catch_long_jump_load_result") } fn make_exception_catcher<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, roc_function: FunctionValue<'ctx>, + return_layout: Layout<'a>, ) -> FunctionValue<'ctx> { let wrapper_function_name = format!("{}_catcher", roc_function.get_name().to_str().unwrap()); - let function_value = make_exception_catching_wrapper(env, roc_function, &wrapper_function_name); + let function_value = + make_exception_catching_wrapper(env, roc_function, return_layout, &wrapper_function_name); function_value.set_linkage(Linkage::Internal); @@ -3690,20 +3856,31 @@ fn roc_result_type<'a, 'ctx, 'env>( fn make_good_roc_result<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, + return_layout: Layout<'a>, return_value: BasicValueEnum<'ctx>, ) -> BasicValueEnum<'ctx> { let context = env.context; let builder = env.builder; - let v1 = roc_result_type(env, return_value.get_type()).const_zero(); + let v1 = roc_result_type(env, basic_type_from_layout(env, &return_layout)).const_zero(); let v2 = builder .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") .unwrap(); - let v3 = builder - .build_insert_value(v2, return_value, 2, "set_call_result") - .unwrap(); + let v3 = if return_layout.is_passed_by_reference() { + let loaded = env.builder.build_load( + return_value.into_pointer_value(), + "load_call_result_passed_by_ptr", + ); + builder + .build_insert_value(v2, loaded, 2, "set_call_result") + .unwrap() + } else { + builder + .build_insert_value(v2, return_value, 2, "set_call_result") + .unwrap() + }; v3.into_struct_value().into() } @@ -3711,6 +3888,7 @@ fn make_good_roc_result<'a, 'ctx, 'env>( fn make_exception_catching_wrapper<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, roc_function: FunctionValue<'ctx>, + return_layout: Layout<'a>, wrapper_function_name: &str, ) -> FunctionValue<'ctx> { // build the C calling convention wrapper @@ -3719,10 +3897,17 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( let builder = env.builder; let roc_function_type = roc_function.get_type(); - let argument_types = roc_function_type.get_param_types(); + let argument_types = match RocReturn::from_layout(env, &return_layout) { + RocReturn::Return => roc_function_type.get_param_types(), + RocReturn::ByPointer => { + let mut types = roc_function_type.get_param_types(); + types.remove(0); - let wrapper_return_type = - roc_result_type(env, roc_function.get_type().get_return_type().unwrap()); + types + } + }; + + let wrapper_return_type = roc_result_type(env, basic_type_from_layout(env, &return_layout)); // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into()); @@ -3756,9 +3941,8 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( env, wrapper_function, roc_function, - roc_function.get_call_conventions(), &arguments, - roc_function_type.get_return_type().unwrap(), + return_layout, ); builder.build_return(Some(&result)); @@ -3903,6 +4087,8 @@ fn build_procedures_help<'a, 'ctx, 'env>( app_ll_file, ); } else { + env.module.print_to_stderr(); + panic!( "The preceding code was from {:?}, which failed LLVM verification in {} build.", fn_val.get_name().to_str().unwrap(), @@ -3952,12 +4138,19 @@ fn build_proc_header<'a, 'ctx, 'env>( let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); for (layout, _) in args.iter() { - let arg_type = basic_type_from_layout(env, layout); + let arg_type = basic_type_from_layout_1(env, layout); arg_basic_types.push(arg_type); } - let fn_type = ret_type.fn_type(&arg_basic_types, false); + let fn_type = match RocReturn::from_layout(env, &proc.ret_layout) { + RocReturn::Return => ret_type.fn_type(&arg_basic_types, false), + RocReturn::ByPointer => { + // println!( "{:?} will return void instead of {:?}", symbol, proc.ret_layout); + arg_basic_types.push(ret_type.ptr_type(AddressSpace::Generic).into()); + env.context.void_type().fn_type(&arg_basic_types, false) + } + }; let fn_val = add_func( env.module, @@ -4051,26 +4244,49 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( // NOTE this may be incorrect in the long run // here we load any argument that is a pointer - for param in evaluator_arguments.iter_mut() { - if param.is_pointer_value() { + let closure_layout = lambda_set.runtime_representation(); + let layouts_it = arguments.iter().chain(std::iter::once(&closure_layout)); + for (param, layout) in evaluator_arguments.iter_mut().zip(layouts_it) { + if param.is_pointer_value() && !layout.is_passed_by_reference() { *param = builder.build_load(param.into_pointer_value(), "load_param"); } } - let call_result = if env.is_gen_test { - set_jump_and_catch_long_jump( + if env.is_gen_test { + let call_result = set_jump_and_catch_long_jump( env, function_value, evaluator, - evaluator.get_call_conventions(), &evaluator_arguments, - result_type, - ) - } else { - call_roc_function(env, evaluator, return_layout, &evaluator_arguments) - }; + *return_layout, + ); - builder.build_store(output, call_result); + builder.build_store(output, call_result); + } else { + let call_result = call_roc_function(env, evaluator, return_layout, &evaluator_arguments); + + if return_layout.is_passed_by_reference() { + let align_bytes = return_layout.alignment_bytes(env.ptr_bytes); + + if align_bytes > 0 { + let size = env + .ptr_int() + .const_int(return_layout.stack_size(env.ptr_bytes) as u64, false); + + env.builder + .build_memcpy( + output, + align_bytes, + call_result.into_pointer_value(), + align_bytes, + size, + ) + .unwrap(); + } + } else { + builder.build_store(output, call_result); + } + }; builder.build_return(None); @@ -4325,24 +4541,81 @@ fn roc_call_with_args<'a, 'ctx, 'env>( call_roc_function(env, fn_val, result_layout, arguments) } -fn call_roc_function<'a, 'ctx, 'env>( +pub fn call_roc_function<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, roc_function: FunctionValue<'ctx>, - _result_layout: &Layout<'a>, + result_layout: &Layout<'a>, arguments: &[BasicValueEnum<'ctx>], ) -> BasicValueEnum<'ctx> { - let call = env.builder.build_call(roc_function, arguments, "call"); + let pass_by_pointer = roc_function.get_type().get_param_types().len() == arguments.len() + 1; - // roc functions should have the fast calling convention - debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV); - call.set_call_convention(FAST_CALL_CONV); + match RocReturn::from_layout(env, result_layout) { + RocReturn::ByPointer if !pass_by_pointer => { + // WARNING this is a hack!! + let mut arguments = Vec::from_iter_in(arguments.iter().copied(), env.arena); + arguments.pop(); - call.try_as_basic_value().left().unwrap_or_else(|| { - panic!( - "LLVM error: Invalid call by name for name {:?}", - roc_function.get_name() - ) - }) + let result_type = basic_type_from_layout(env, result_layout); + let result_alloca = env.builder.build_alloca(result_type, "result_value"); + + arguments.push(result_alloca.into()); + + debug_assert_eq!( + roc_function.get_type().get_param_types().len(), + arguments.len() + ); + let call = env.builder.build_call(roc_function, &arguments, "call"); + + // roc functions should have the fast calling convention + debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV); + call.set_call_convention(FAST_CALL_CONV); + + env.builder.build_load(result_alloca, "load_result") + } + RocReturn::ByPointer => { + let mut arguments = Vec::from_iter_in(arguments.iter().copied(), env.arena); + + let result_type = basic_type_from_layout(env, result_layout); + let result_alloca = tag_alloca(env, result_type, "result_value"); + + arguments.push(result_alloca.into()); + + debug_assert_eq!( + roc_function.get_type().get_param_types().len(), + arguments.len() + ); + let call = env.builder.build_call(roc_function, &arguments, "call"); + + // roc functions should have the fast calling convention + debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV); + call.set_call_convention(FAST_CALL_CONV); + + if result_layout.is_passed_by_reference() { + result_alloca.into() + } else { + env.builder + .build_load(result_alloca, "return_by_pointer_load_result") + } + } + RocReturn::Return => { + debug_assert_eq!( + roc_function.get_type().get_param_types().len(), + arguments.len() + ); + let call = env.builder.build_call(roc_function, arguments, "call"); + + // roc functions should have the fast calling convention + debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV); + call.set_call_convention(FAST_CALL_CONV); + + call.try_as_basic_value().left().unwrap_or_else(|| { + panic!( + "LLVM error: Invalid call by name for name {:?}", + roc_function.get_name() + ) + }) + } + } } /// Translates a target_lexicon::Triple to a LLVM calling convention u32 @@ -4373,6 +4646,7 @@ pub struct RocFunctionCall<'ctx> { pub data_is_owned: IntValue<'ctx>, } +#[allow(clippy::too_many_arguments)] fn roc_function_call<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, @@ -4381,6 +4655,7 @@ fn roc_function_call<'a, 'ctx, 'env>( lambda_set: LambdaSet<'a>, closure_data_is_owned: bool, argument_layouts: &[Layout<'a>], + result_layout: Layout<'a>, ) -> RocFunctionCall<'ctx> { use crate::llvm::bitcode::{build_inc_n_wrapper, build_transform_caller}; @@ -4389,9 +4664,10 @@ fn roc_function_call<'a, 'ctx, 'env>( .build_alloca(closure_data.get_type(), "closure_data_ptr"); env.builder.build_store(closure_data_ptr, closure_data); - let stepper_caller = build_transform_caller(env, transform, lambda_set, argument_layouts) - .as_global_value() - .as_pointer_value(); + let stepper_caller = + build_transform_caller(env, transform, lambda_set, argument_layouts, result_layout) + .as_global_value() + .as_pointer_value(); let inc_closure_data = build_inc_n_wrapper(env, layout_ids, &lambda_set.runtime_representation()) @@ -4464,6 +4740,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); crate::llvm::build_list::list_walk_generic( @@ -4505,6 +4782,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + **result_layout, ); list_map(env, roc_function_call, list, element_layout, result_layout) @@ -4534,6 +4812,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + **result_layout, ); list_map2( @@ -4577,6 +4856,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + **result_layout, ); list_map3( @@ -4635,6 +4915,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + **result_layout, ); list_map4( @@ -4681,6 +4962,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + **result_layout, ); list_map_with_index(env, roc_function_call, list, element_layout, result_layout) @@ -4707,6 +4989,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); list_keep_if(env, layout_ids, roc_function_call, list, element_layout) @@ -4737,6 +5020,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); list_keep_oks( @@ -4777,6 +5061,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); list_keep_errs( @@ -4829,6 +5114,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); list_sort_with( @@ -4859,6 +5145,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + Layout::Builtin(Builtin::Int1), ); list_any(env, roc_function_call, list, element_layout) @@ -4891,6 +5178,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + Layout::Builtin(Builtin::Int1), ); list_find_unsafe(env, layout_ids, roc_function_call, list, element_layout) } @@ -4919,6 +5207,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); dict_walk( @@ -5828,6 +6117,34 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>( } } +enum RocReturn { + /// Return as normal + Return, + /// require an extra argument, a pointer + /// where the result is written into returns void + ByPointer, +} + +impl RocReturn { + fn roc_return_by_pointer(layout: Layout) -> bool { + match layout { + Layout::Union(UnionLayout::NonRecursive(_)) => true, + Layout::LambdaSet(lambda_set) => { + RocReturn::roc_return_by_pointer(lambda_set.runtime_representation()) + } + _ => false, + } + } + + fn from_layout<'a, 'ctx, 'env>(_env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> Self { + if Self::roc_return_by_pointer(*layout) { + RocReturn::ByPointer + } else { + RocReturn::Return + } + } +} + enum CCReturn { /// Return as normal Return, diff --git a/compiler/gen_llvm/src/llvm/build_hash.rs b/compiler/gen_llvm/src/llvm/build_hash.rs index d3dc006198..261af4e7d4 100644 --- a/compiler/gen_llvm/src/llvm/build_hash.rs +++ b/compiler/gen_llvm/src/llvm/build_hash.rs @@ -2,9 +2,9 @@ use crate::debug_info_init; use crate::llvm::bitcode::call_bitcode_fn; use crate::llvm::build::tag_pointer_clear_tag_id; use crate::llvm::build::Env; -use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX}; +use crate::llvm::build::{get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX}; use crate::llvm::build_str; -use crate::llvm::convert::basic_type_from_layout; +use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1}; use bumpalo::collections::Vec; use inkwell::values::{ BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue, @@ -339,7 +339,8 @@ fn build_hash_tag<'a, 'ctx, 'env>( None => { let seed_type = env.context.i64_type(); - let arg_type = basic_type_from_layout(env, layout); + let arg_type = basic_type_from_layout_1(env, layout); + dbg!(layout, arg_type); let function_value = crate::llvm::refcounting::build_header_help( env, @@ -423,14 +424,6 @@ fn hash_tag<'a, 'ctx, 'env>( let block = env.context.append_basic_block(parent, "tag_id_modify"); env.builder.position_at_end(block); - let struct_layout = Layout::Struct(field_layouts); - - let wrapper_type = basic_type_from_layout(env, &struct_layout); - debug_assert!(wrapper_type.is_struct_type()); - - let as_struct = - cast_block_of_memory_to_tag(env.builder, tag.into_struct_value(), wrapper_type); - // hash the tag id let hash_bytes = store_and_use_as_u8_ptr( env, @@ -440,7 +433,6 @@ fn hash_tag<'a, 'ctx, 'env>( .into(), &tag_id_layout, ); - let seed = hash_bitcode_fn( env, seed, @@ -449,14 +441,9 @@ fn hash_tag<'a, 'ctx, 'env>( ); // hash the tag data - let answer = build_hash_struct( - env, - layout_ids, - field_layouts, - WhenRecursive::Unreachable, - seed, - as_struct, - ); + let tag = tag.into_pointer_value(); + let answer = + hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag); merge_phi.add_incoming(&[(&answer, block)]); env.builder.build_unconditional_branch(merge_block); @@ -793,7 +780,15 @@ fn hash_list<'a, 'ctx, 'env>( env.builder.build_store(result, answer); }; - incrementing_elem_loop(env, parent, ptr, length, "current_index", loop_fn); + incrementing_elem_loop( + env, + parent, + *element_layout, + ptr, + length, + "current_index", + loop_fn, + ); env.builder.build_unconditional_branch(done_block); @@ -822,12 +817,12 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>( ) -> IntValue<'ctx> { use inkwell::types::BasicType; - let wrapper_type = basic_type_from_layout(env, &Layout::Union(*union_layout)); + let wrapper_type = basic_type_from_layout_1(env, &Layout::Union(*union_layout)); // cast the opaque pointer to a pointer of the correct shape let wrapper_ptr = env .builder - .build_bitcast(tag, wrapper_type, "opaque_to_correct") + .build_bitcast(tag, wrapper_type, "hash_ptr_to_struct_opaque_to_correct") .into_pointer_value(); let struct_ptr = env diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index e78fa2f765..6885b8f9cc 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -17,6 +17,8 @@ use morphic_lib::UpdateMode; use roc_builtins::bitcode; use roc_mono::layout::{Builtin, Layout, LayoutIds}; +use super::build::{load_roc_value, store_roc_value}; + pub fn pass_update_mode<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, update_mode: UpdateMode, @@ -53,9 +55,13 @@ pub fn call_bitcode_fn_returns_list<'a, 'ctx, 'env>( fn pass_element_as_opaque<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, element: BasicValueEnum<'ctx>, + layout: Layout<'a>, ) -> BasicValueEnum<'ctx> { - let element_ptr = env.builder.build_alloca(element.get_type(), "element"); - env.builder.build_store(element_ptr, element); + let element_type = basic_type_from_layout(env, &layout); + let element_ptr = env + .builder + .build_alloca(element_type, "element_to_pass_as_opaque"); + store_roc_value(env, layout, element_ptr, element); env.builder.build_bitcast( element_ptr, @@ -106,7 +112,7 @@ pub fn list_single<'a, 'ctx, 'env>( env, &[ env.alignment_intvalue(element_layout), - pass_element_as_opaque(env, element), + pass_element_as_opaque(env, element, *element_layout), layout_width(env, element_layout), ], bitcode::LIST_SINGLE, @@ -128,7 +134,7 @@ pub fn list_repeat<'a, 'ctx, 'env>( &[ list_len.into(), env.alignment_intvalue(element_layout), - pass_element_as_opaque(env, element), + pass_element_as_opaque(env, element, *element_layout), layout_width(env, element_layout), inc_element_fn.as_global_value().as_pointer_value().into(), ], @@ -216,10 +222,11 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>( // Assume the bounds have already been checked earlier // (e.g. by List.get or List.first, which wrap List.#getUnsafe) - let elem_ptr = - unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "elem") }; + let elem_ptr = unsafe { + builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "list_get_element") + }; - let result = builder.build_load(elem_ptr, "List.get"); + let result = load_roc_value(env, **elem_layout, elem_ptr, "list_get_load_element"); increment_refcount_layout(env, parent, layout_ids, 1, result, elem_layout); @@ -247,7 +254,7 @@ pub fn list_append<'a, 'ctx, 'env>( &[ pass_list_cc(env, original_wrapper.into()), env.alignment_intvalue(element_layout), - pass_element_as_opaque(env, element), + pass_element_as_opaque(env, element, *element_layout), layout_width(env, element_layout), pass_update_mode(env, update_mode), ], @@ -267,7 +274,7 @@ pub fn list_prepend<'a, 'ctx, 'env>( &[ pass_list_cc(env, original_wrapper.into()), env.alignment_intvalue(element_layout), - pass_element_as_opaque(env, element), + pass_element_as_opaque(env, element, *element_layout), layout_width(env, element_layout), ], bitcode::LIST_PREPEND, @@ -389,7 +396,7 @@ pub fn list_set<'a, 'ctx, 'env>( &[ bytes.into(), index.into(), - pass_element_as_opaque(env, element), + pass_element_as_opaque(env, element, *element_layout), layout_width(env, element_layout), dec_element_fn.as_global_value().as_pointer_value().into(), ], @@ -402,7 +409,7 @@ pub fn list_set<'a, 'ctx, 'env>( length.into(), env.alignment_intvalue(element_layout), index.into(), - pass_element_as_opaque(env, element), + pass_element_as_opaque(env, element, *element_layout), layout_width(env, element_layout), dec_element_fn.as_global_value().as_pointer_value().into(), ], @@ -578,7 +585,7 @@ pub fn list_contains<'a, 'ctx, 'env>( env, &[ pass_list_cc(env, list), - pass_element_as_opaque(env, element), + pass_element_as_opaque(env, element, *element_layout), layout_width(env, element_layout), eq_fn, ], @@ -1137,6 +1144,7 @@ where pub fn incrementing_elem_loop<'a, 'ctx, 'env, LoopFn>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, + element_layout: Layout<'a>, ptr: PointerValue<'ctx>, len: IntValue<'ctx>, index_name: &str, @@ -1149,9 +1157,14 @@ where incrementing_index_loop(env, parent, len, index_name, |index| { // The pointer to the element in the list - let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index], "load_index") }; + let element_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index], "load_index") }; - let elem = builder.build_load(elem_ptr, "get_elem"); + let elem = load_roc_value( + env, + element_layout, + element_ptr, + "incrementing_element_loop_load", + ); loop_fn(index, elem); }) diff --git a/compiler/gen_llvm/src/llvm/compare.rs b/compiler/gen_llvm/src/llvm/compare.rs index 1ca6178c0b..c624890616 100644 --- a/compiler/gen_llvm/src/llvm/compare.rs +++ b/compiler/gen_llvm/src/llvm/compare.rs @@ -1,10 +1,8 @@ use crate::llvm::bitcode::call_bitcode_fn; -use crate::llvm::build::{ - cast_block_of_memory_to_tag, get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, -}; +use crate::llvm::build::{get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV}; use crate::llvm::build_list::{list_len, load_list_ptr}; use crate::llvm::build_str::str_equal; -use crate::llvm::convert::basic_type_from_layout; +use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1}; use bumpalo::collections::Vec; use inkwell::types::BasicType; use inkwell::values::{ @@ -751,7 +749,7 @@ fn build_tag_eq<'a, 'ctx, 'env>( let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, None => { - let arg_type = basic_type_from_layout(env, tag_layout); + let arg_type = basic_type_from_layout_1(env, tag_layout); let function_value = crate::llvm::refcounting::build_header_help( env, @@ -844,9 +842,29 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( match union_layout { NonRecursive(tags) => { + let ptr_equal = env.builder.build_int_compare( + IntPredicate::EQ, + env.builder + .build_ptr_to_int(tag1.into_pointer_value(), env.ptr_int(), "pti"), + env.builder + .build_ptr_to_int(tag2.into_pointer_value(), env.ptr_int(), "pti"), + "compare_pointers", + ); + + let compare_tag_ids = ctx.append_basic_block(parent, "compare_tag_ids"); + + env.builder + .build_conditional_branch(ptr_equal, return_true, compare_tag_ids); + + env.builder.position_at_end(compare_tag_ids); + let id1 = get_tag_id(env, parent, union_layout, tag1); let id2 = get_tag_id(env, parent, union_layout, tag2); + // clear the tag_id so we get a pointer to the actual data + let tag1 = tag1.into_pointer_value(); + let tag2 = tag2.into_pointer_value(); + let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields"); let same_tag = @@ -866,30 +884,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let block = env.context.append_basic_block(parent, "tag_id_modify"); env.builder.position_at_end(block); - // TODO drop tag id? - let struct_layout = Layout::Struct(field_layouts); - - let wrapper_type = basic_type_from_layout(env, &struct_layout); - debug_assert!(wrapper_type.is_struct_type()); - - let struct1 = cast_block_of_memory_to_tag( - env.builder, - tag1.into_struct_value(), - wrapper_type, - ); - let struct2 = cast_block_of_memory_to_tag( - env.builder, - tag2.into_struct_value(), - wrapper_type, - ); - - let answer = build_struct_eq( + let answer = eq_ptr_to_struct( env, layout_ids, + union_layout, + Some(when_recursive.clone()), field_layouts, - when_recursive.clone(), - struct1, - struct2, + tag1, + tag2, ); env.builder.build_return(Some(&answer)); @@ -946,8 +948,15 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let block = env.context.append_basic_block(parent, "tag_id_modify"); env.builder.position_at_end(block); - let answer = - eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2); + let answer = eq_ptr_to_struct( + env, + layout_ids, + union_layout, + None, + field_layouts, + tag1, + tag2, + ); env.builder.build_return(Some(&answer)); @@ -1003,6 +1012,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( env, layout_ids, union_layout, + None, other_fields, tag1.into_pointer_value(), tag2.into_pointer_value(), @@ -1093,8 +1103,15 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let block = env.context.append_basic_block(parent, "tag_id_modify"); env.builder.position_at_end(block); - let answer = - eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2); + let answer = eq_ptr_to_struct( + env, + layout_ids, + union_layout, + None, + field_layouts, + tag1, + tag2, + ); env.builder.build_return(Some(&answer)); @@ -1128,6 +1145,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( env, layout_ids, union_layout, + None, field_layouts, tag1.into_pointer_value(), tag2.into_pointer_value(), @@ -1142,6 +1160,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, union_layout: &UnionLayout<'a>, + opt_when_recursive: Option>, field_layouts: &'a [Layout<'a>], tag1: PointerValue<'ctx>, tag2: PointerValue<'ctx>, @@ -1184,7 +1203,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>( env, layout_ids, field_layouts, - WhenRecursive::Loop(*union_layout), + opt_when_recursive.unwrap_or(WhenRecursive::Loop(*union_layout)), struct1, struct2, ) diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index d32216c10a..98306210ad 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -76,6 +76,66 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( } } +pub fn basic_type_from_layout_1<'a, 'ctx, 'env>( + env: &crate::llvm::build::Env<'a, 'ctx, 'env>, + layout: &Layout<'_>, +) -> BasicTypeEnum<'ctx> { + use Layout::*; + + match layout { + Struct(sorted_fields) => basic_type_from_record(env, sorted_fields), + LambdaSet(lambda_set) => { + basic_type_from_layout_1(env, &lambda_set.runtime_representation()) + } + Union(union_layout) => { + use UnionLayout::*; + + let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout()); + + match union_layout { + NonRecursive(tags) => { + let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let struct_type = env.context.struct_type(&[data, tag_id_type], false); + + struct_type.ptr_type(AddressSpace::Generic).into() + } + Recursive(tags) + | NullableWrapped { + other_tags: tags, .. + } => { + let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + + if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + env.context + .struct_type(&[data, tag_id_type], false) + .ptr_type(AddressSpace::Generic) + .into() + } else { + data.ptr_type(AddressSpace::Generic).into() + } + } + NullableUnwrapped { other_fields, .. } => { + let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); + block.ptr_type(AddressSpace::Generic).into() + } + NonNullableUnwrapped(fields) => { + let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes); + block.ptr_type(AddressSpace::Generic).into() + } + } + } + RecursivePointer => { + // TODO make this dynamic + env.context + .i64_type() + .ptr_type(AddressSpace::Generic) + .as_basic_type_enum() + } + + Builtin(builtin) => basic_type_from_builtin(env, builtin), + } +} + pub fn basic_type_from_builtin<'a, 'ctx, 'env>( env: &crate::llvm::build::Env<'a, 'ctx, 'env>, builtin: &Builtin<'_>, diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index b6cd354349..1b6c6aa4d0 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -1,11 +1,11 @@ use crate::debug_info_init; use crate::llvm::bitcode::call_void_bitcode_fn; use crate::llvm::build::{ - add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive, - tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, TAG_DATA_INDEX, + add_func, cast_basic_basic, get_tag_id, tag_pointer_clear_tag_id, use_roc_value, Env, + FAST_CALL_CONV, TAG_DATA_INDEX, TAG_ID_INDEX, }; use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list}; -use crate::llvm::convert::{basic_type_from_layout, ptr_int}; +use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1, ptr_int}; use bumpalo::collections::Vec; use inkwell::basic_block::BasicBlock; use inkwell::context::Context; @@ -139,8 +139,10 @@ impl<'ctx> PointerToRefcount<'ctx> { let block = env.builder.get_insert_block().expect("to be in a function"); let parent = block.get_parent().unwrap(); - let modify_block = env.context.append_basic_block(parent, "inc_str_modify"); - let cont_block = env.context.append_basic_block(parent, "inc_str_cont"); + let modify_block = env + .context + .append_basic_block(parent, "inc_refcount_modify"); + let cont_block = env.context.append_basic_block(parent, "inc_refcount_cont"); env.builder .build_conditional_branch(is_static_allocation, cont_block, modify_block); @@ -349,18 +351,25 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>( for (i, field_layout) in layouts.iter().enumerate() { if field_layout.contains_refcounted() { - let field_ptr = env + let raw_value = env .builder .build_extract_value(wrapper_struct, i as u32, "decrement_struct_field") .unwrap(); + let field_value = use_roc_value( + env, + *field_layout, + raw_value, + "load_struct_tag_field_for_decrement", + ); + modify_refcount_layout_help( env, parent, layout_ids, mode.to_call_mode(fn_val), when_recursive, - field_ptr, + field_value, field_layout, ); } @@ -753,7 +762,15 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>( ); }; - incrementing_elem_loop(env, parent, ptr, len, "modify_rc_index", loop_fn); + incrementing_elem_loop( + env, + parent, + *element_layout, + ptr, + len, + "modify_rc_index", + loop_fn, + ); } let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper); @@ -1289,7 +1306,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( .build_bitcast( value_ptr, wrapper_type.ptr_type(AddressSpace::Generic), - "opaque_to_correct", + "opaque_to_correct_recursive_decrement", ) .into_pointer_value(); @@ -1602,7 +1619,7 @@ fn modify_refcount_union<'a, 'ctx, 'env>( let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, None => { - let basic_type = basic_type_from_layout(env, &layout); + let basic_type = basic_type_from_layout_1(env, &layout); let function_value = build_header(env, basic_type, mode, &fn_name); modify_refcount_union_help( @@ -1647,18 +1664,24 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>( // Add args to scope let arg_symbol = Symbol::ARG_1; - let arg_val = fn_val.get_param_iter().next().unwrap(); + let arg_ptr = fn_val.get_param_iter().next().unwrap().into_pointer_value(); - arg_val.set_name(arg_symbol.as_str(&env.interns)); + arg_ptr.set_name(arg_symbol.as_str(&env.interns)); let parent = fn_val; let before_block = env.builder.get_insert_block().expect("to be in a function"); - let wrapper_struct = arg_val.into_struct_value(); - // read the tag_id - let tag_id = get_tag_id_non_recursive(env, wrapper_struct); + let tag_id_ptr = env + .builder + .build_struct_gep(arg_ptr, TAG_ID_INDEX, "tag_id_ptr") + .unwrap(); + + let tag_id = env + .builder + .build_load(tag_id_ptr, "load_tag_id") + .into_int_value(); let tag_id_u8 = env .builder @@ -1686,12 +1709,16 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>( let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts)); debug_assert!(wrapper_type.is_struct_type()); - let data_bytes = env + let opaque_tag_data_ptr = env .builder - .build_extract_value(wrapper_struct, TAG_DATA_INDEX, "read_tag_id") - .unwrap() - .into_struct_value(); - let wrapper_struct = cast_block_of_memory_to_tag(env.builder, data_bytes, wrapper_type); + .build_struct_gep(arg_ptr, TAG_DATA_INDEX, "field_ptr") + .unwrap(); + + let cast_tag_data_pointer = env.builder.build_pointer_cast( + opaque_tag_data_ptr, + wrapper_type.ptr_type(AddressSpace::Generic), + "cast_to_concrete_tag", + ); for (i, field_layout) in field_layouts.iter().enumerate() { if let Layout::RecursivePointer = field_layout { @@ -1699,16 +1726,22 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>( } else if field_layout.contains_refcounted() { let field_ptr = env .builder - .build_extract_value(wrapper_struct, i as u32, "modify_tag_field") + .build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field") .unwrap(); + let field_value = if field_layout.is_passed_by_reference() { + field_ptr.into() + } else { + env.builder.build_load(field_ptr, "field_value") + }; + modify_refcount_layout_help( env, parent, layout_ids, mode.to_call_mode(fn_val), when_recursive, - field_ptr, + field_value, field_layout, ); } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 673da22e32..c0d1ab0cc3 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -863,6 +863,16 @@ impl<'a> Layout<'a> { false } + pub fn is_passed_by_reference(&self) -> bool { + match self { + Layout::Union(UnionLayout::NonRecursive(_)) => true, + Layout::LambdaSet(lambda_set) => { + lambda_set.runtime_representation().is_passed_by_reference() + } + _ => false, + } + } + pub fn stack_size(&self, pointer_size: u32) -> u32 { let width = self.stack_size_without_alignment(pointer_size); let alignment = self.alignment_bytes(pointer_size); @@ -941,16 +951,16 @@ impl<'a> Layout<'a> { }) .max(); + let tag_id_builtin = variant.tag_id_builtin(); match max_alignment { - Some(align) => { - let tag_id_builtin = variant.tag_id_builtin(); - - round_up_to_alignment( - align, - tag_id_builtin.alignment_bytes(pointer_size), - ) + Some(align) => round_up_to_alignment( + align.max(tag_id_builtin.alignment_bytes(pointer_size)), + tag_id_builtin.alignment_bytes(pointer_size), + ), + None => { + // none of the tags had any payload, but the tag id still contains information + tag_id_builtin.alignment_bytes(pointer_size) } - None => 0, } } Recursive(_) @@ -2674,3 +2684,27 @@ impl<'a> std::convert::TryFrom<&Layout<'a>> for ListLayout<'a> { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn width_and_alignment_union_empty_struct() { + let lambda_set = LambdaSet { + set: &[(Symbol::LIST_MAP, &[])], + representation: &Layout::Struct(&[]), + }; + + let a = &[Layout::Struct(&[])] as &[_]; + let b = &[Layout::LambdaSet(lambda_set)] as &[_]; + let tt = [a, b]; + + let layout = Layout::Union(UnionLayout::NonRecursive(&tt)); + + // at the moment, the tag id uses an I64, so + let ptr_width = 8; + assert_eq!(layout.stack_size(ptr_width), 8); + assert_eq!(layout.alignment_bytes(ptr_width), 8); + } +} diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index 7626ef4d03..538272c1ff 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -453,7 +453,7 @@ fn result_with_guard_pattern() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn maybe_is_just() { +fn maybe_is_just_not_nested() { assert_evals_to!( indoc!( r#" diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index d8f209d782..a74f64eaba 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -198,6 +198,10 @@ fn create_llvm_module<'a>( if name.starts_with("roc_builtins.dict") { function.add_attribute(AttributeLoc::Function, attr); } + + if name.starts_with("roc_builtins.list") { + function.add_attribute(AttributeLoc::Function, attr); + } } // Compile and add all the Procs before adding main