diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 074e2e2522..f0aea56c81 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -22,7 +22,7 @@ use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::{BasicValue, FloatValue, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::AddressSpace; use inkwell::{IntPredicate, OptimizationLevel}; -use roc_collections::all::{ImMap, MutSet}; +use roc_collections::all::{ImMap, MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, Symbol}; use roc_mono::ir::JoinPointId; @@ -79,6 +79,11 @@ impl<'a, 'ctx> Scope<'a, 'ctx> { */ } +pub struct RcFunctions<'ctx> { + inc: FunctionValue<'ctx>, + dec: FunctionValue<'ctx>, +} + pub struct Env<'a, 'ctx, 'env> { pub arena: &'a Bump, pub context: &'ctx Context, @@ -88,6 +93,7 @@ pub struct Env<'a, 'ctx, 'env> { pub ptr_bytes: u32, pub leak: bool, pub exposed_to_host: MutSet, + pub rc_functions: MutMap, RcFunctions<'ctx>>, } impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { @@ -358,7 +364,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( // If this is an external-facing function, use the C calling convention. call.set_call_convention(C_CALL_CONV); } else { - // If it's an internal-only function, use the fast calling conention. + // If it's an internal-only function, use the fast calling convention. call.set_call_convention(FAST_CALL_CONV); } @@ -571,6 +577,10 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( ) } Tag { .. } => unreachable!("tags should have a union layout"), + + Reset(_) => todo!(), + Reuse { .. } => todo!(), + AccessAtIndex { index, structure, @@ -684,6 +694,8 @@ pub fn allocate_with_refcount<'a, 'ctx, 'env>( let len_type = env.ptr_int(); // bytes per element let bytes_len = len_type.const_int(value_bytes, false); + + // TODO fix offset let offset = (env.ptr_bytes as u64).max(value_bytes); let ptr = { @@ -1022,6 +1034,11 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( let (value, layout) = load_symbol_and_layout(env, scope, symbol); let layout = layout.clone(); + if layout.contains_refcounted() { + increment_refcount_layout(env, parent, layout_ids, value, &layout); + } + + /* match layout { Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => { increment_refcount_list(env, parent, value.into_struct_value()); @@ -1029,13 +1046,16 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( } _ => build_exp_stmt(env, layout_ids, scope, parent, cont), } + */ + + build_exp_stmt(env, layout_ids, scope, parent, cont) } Dec(symbol, cont) => { let (value, layout) = load_symbol_and_layout(env, scope, symbol); let layout = layout.clone(); if layout.contains_refcounted() { - decrement_refcount_layout(env, parent, value, &layout); + decrement_refcount_layout(env, parent, layout_ids, value, &layout); } build_exp_stmt(env, layout_ids, scope, parent, cont) @@ -1058,31 +1078,57 @@ fn refcount_is_one_comparison<'ctx>( ) } -#[allow(dead_code)] fn list_get_refcount_ptr<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, + layout: &Layout<'a>, list_wrapper: StructValue<'ctx>, ) -> PointerValue<'ctx> { - let builder = env.builder; - let ctx = env.context; - - // pointer to usize - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - // fetch the pointer to the array data, as an integer - let ptr_as_int = builder + let ptr_as_int = env + .builder .build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr") .unwrap() .into_int_value(); - // subtract ptr_size, to access the refcount + get_refcount_ptr_help(env, layout, ptr_as_int) +} + +fn get_refcount_ptr<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: &Layout<'a>, + ptr: PointerValue<'ctx>, +) -> PointerValue<'ctx> { + let ptr_as_int = + cast_basic_basic(env.builder, ptr.into(), env.context.i64_type().into()).into_int_value(); + + get_refcount_ptr_help(env, layout, ptr_as_int) +} + +fn get_refcount_ptr_help<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: &Layout<'a>, + ptr_as_int: IntValue<'ctx>, +) -> PointerValue<'ctx> { + let builder = env.builder; + let ctx = env.context; + + let value_bytes = layout.stack_size(env.ptr_bytes) as u64; + let offset = match dbg!(layout) { + Layout::Builtin(Builtin::List(_, _)) => env.ptr_bytes as u64, + _ => (env.ptr_bytes as u64).max(value_bytes), + }; + + // subtract offset, to access the refcount let refcount_ptr = builder.build_int_sub( ptr_as_int, - ctx.i64_type().const_int(env.ptr_bytes as u64, false), + ctx.i64_type().const_int(offset, false), "make_refcount_ptr", ); + // pointer to usize + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + builder.build_int_to_ptr( refcount_ptr, int_type.ptr_type(AddressSpace::Generic), @@ -1093,13 +1139,14 @@ fn list_get_refcount_ptr<'a, 'ctx, 'env>( fn decrement_refcount_layout<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, + layout_ids: &mut LayoutIds<'a>, value: BasicValueEnum<'ctx>, layout: &Layout<'a>, ) { use Layout::*; match layout { - Builtin(builtin) => decrement_refcount_builtin(env, parent, value, builtin), + Builtin(builtin) => decrement_refcount_builtin(env, parent, value, layout, builtin), Struct(layouts) => { let wrapper_struct = value.into_struct_value(); @@ -1110,14 +1157,12 @@ fn decrement_refcount_layout<'a, 'ctx, 'env>( .build_extract_value(wrapper_struct, i as u32, "decrement_struct_field") .unwrap(); - decrement_refcount_layout(env, parent, field_ptr, field_layout) + decrement_refcount_layout(env, parent, layout_ids, field_ptr, field_layout) } } } - RecursiveUnion(_) => { - println!("TODO implement decrement layout of recursive tag union"); - } RecursivePointer => todo!("TODO implement decrement layout of recursive tag union"), + Union(tags) => { debug_assert!(!tags.is_empty()); let wrapper_struct = value.into_struct_value(); @@ -1145,7 +1190,7 @@ fn decrement_refcount_layout<'a, 'ctx, 'env>( .build_extract_value(wrapper_struct, i as u32, "decrement_struct_field") .unwrap(); - decrement_refcount_layout(env, parent, field_ptr, field_layout) + decrement_refcount_layout(env, parent, layout_ids, field_ptr, field_layout) } } @@ -1161,6 +1206,10 @@ fn decrement_refcount_layout<'a, 'ctx, 'env>( env.builder.position_at_end(merge_block); } + RecursiveUnion(tags) => { + build_dec_union(env, layout_ids, tags, value); + } + FunctionPointer(_, _) | Pointer(_) => {} } } @@ -1170,6 +1219,7 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, value: BasicValueEnum<'ctx>, + layout: &Layout<'a>, builtin: &Builtin<'a>, ) { use Builtin::*; @@ -1180,7 +1230,7 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>( // TODO decrement all values } let wrapper_struct = value.into_struct_value(); - decrement_refcount_list(env, parent, wrapper_struct); + decrement_refcount_list(env, parent, layout, wrapper_struct); } List(MemoryMode::Unique, _element_layout) => { // do nothing @@ -1189,16 +1239,71 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>( if element_layout.contains_refcounted() { // TODO decrement all values } - let wrapper_struct = value.into_struct_value(); - decrement_refcount_list(env, parent, wrapper_struct); + todo!(); } Map(key_layout, value_layout) => { if key_layout.contains_refcounted() || value_layout.contains_refcounted() { // TODO decrement all values } + todo!(); + } + _ => {} + } +} + +fn increment_refcount_layout<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + layout_ids: &mut LayoutIds<'a>, + value: BasicValueEnum<'ctx>, + layout: &Layout<'a>, +) { + use Layout::*; + + match layout { + Builtin(builtin) => increment_refcount_builtin(env, parent, value, layout, builtin), + + RecursiveUnion(tags) => { + build_inc_union(env, layout_ids, tags, value); + } + _ => {} + } +} + +#[inline(always)] +fn increment_refcount_builtin<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + value: BasicValueEnum<'ctx>, + layout: &Layout<'a>, + builtin: &Builtin<'a>, +) { + use Builtin::*; + + match builtin { + List(MemoryMode::Refcounted, element_layout) => { + if element_layout.contains_refcounted() { + // TODO decrement all values + } let wrapper_struct = value.into_struct_value(); - decrement_refcount_list(env, parent, wrapper_struct); + increment_refcount_list(env, parent, layout, wrapper_struct); + } + List(MemoryMode::Unique, _element_layout) => { + // do nothing + } + Set(element_layout) => { + if element_layout.contains_refcounted() { + // TODO decrement all values + } + todo!(); + } + Map(key_layout, value_layout) => { + if key_layout.contains_refcounted() || value_layout.contains_refcounted() { + // TODO decrement all values + } + + todo!(); } _ => {} } @@ -1207,6 +1312,7 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>( fn increment_refcount_list<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, + layout: &Layout<'a>, original_wrapper: StructValue<'ctx>, ) { let builder = env.builder; @@ -1229,7 +1335,7 @@ fn increment_refcount_list<'a, 'ctx, 'env>( builder.position_at_end(increment_block); - let refcount_ptr = list_get_refcount_ptr(env, original_wrapper); + let refcount_ptr = list_get_refcount_ptr(env, layout, original_wrapper); let refcount = env .builder @@ -1253,6 +1359,7 @@ fn increment_refcount_list<'a, 'ctx, 'env>( fn decrement_refcount_list<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, + layout: &Layout<'a>, original_wrapper: StructValue<'ctx>, ) { let builder = env.builder; @@ -1282,7 +1389,7 @@ fn decrement_refcount_list<'a, 'ctx, 'env>( let then_block = ctx.append_basic_block(parent, "then"); let else_block = ctx.append_basic_block(parent, "else"); - let refcount_ptr = list_get_refcount_ptr(env, original_wrapper); + let refcount_ptr = list_get_refcount_ptr(env, layout, original_wrapper); let refcount = env .builder @@ -1326,6 +1433,65 @@ fn decrement_refcount_list<'a, 'ctx, 'env>( builder.position_at_end(cont_block); } +fn decrement_refcount_ptr<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + layout: &Layout<'a>, + field_ptr: PointerValue<'ctx>, +) { + let builder = env.builder; + let ctx = env.context; + + // the block we'll always jump to when we're done + let cont_block = ctx.append_basic_block(parent, "after_decrement_block"); + + let refcount_ptr = get_refcount_ptr(env, layout, field_ptr); + + let refcount = env + .builder + .build_load(refcount_ptr, "get_refcount") + .into_int_value(); + + let comparison = refcount_is_one_comparison(builder, env.context, refcount); + + // build blocks + let then_block = ctx.append_basic_block(parent, "then"); + let else_block = ctx.append_basic_block(parent, "else"); + + // TODO what would be most optimial for the branch predictor + // + // are most refcounts 1 most of the time? or not? + builder.build_conditional_branch(comparison, then_block, else_block); + + // build then block + { + builder.position_at_end(then_block); + if !env.leak { + builder.build_free(refcount_ptr); + } + builder.build_unconditional_branch(cont_block); + } + + // build else block + { + builder.position_at_end(else_block); + // our refcount 0 is actually usize::MAX, so decrementing the refcount means incrementing this value. + let decremented = env.builder.build_int_add( + ctx.i64_type().const_int(1 as u64, false), + refcount, + "decremented_refcount", + ); + + // Mutate the new array in-place to change the element. + builder.build_store(refcount_ptr, decremented); + + builder.build_unconditional_branch(cont_block); + } + + // emit merge block + builder.position_at_end(cont_block); +} + pub fn load_symbol<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, @@ -2004,7 +2170,8 @@ fn run_low_level<'a, 'ctx, 'env>( let ret_type = basic_type_from_layout(env.arena, ctx, list_layout, env.ptr_bytes); - let refcount_ptr = list_get_refcount_ptr(env, list_symbol.into_struct_value()); + let refcount_ptr = + list_get_refcount_ptr(env, list_layout, list_symbol.into_struct_value()); let refcount = env .builder @@ -2403,3 +2570,385 @@ fn build_float_unary_op<'a, 'ctx, 'env>( } } } + +pub fn build_inc_dec_header<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: &Layout<'a>, + fn_name: String, +) -> FunctionValue<'ctx> { + let arena = env.arena; + let context = &env.context; + + let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes); + + let fn_type = context.void_type().fn_type(&[arg_type], false); + + let fn_val = env + .module + .add_function(fn_name.as_str(), fn_type, Some(Linkage::Private)); + + // If it's an internal-only function, it should use the fast calling convention. + fn_val.set_call_conventions(FAST_CALL_CONV); + + fn_val +} + +pub fn build_dec_union<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + fields: &'a [&'a [Layout<'a>]], + value: BasicValueEnum<'ctx>, +) { + let layout = Layout::Union(fields); + + let block = env.builder.get_insert_block().expect("to be in a function"); + + let symbol = Symbol::DEC; + let fn_name = layout_ids + .get(symbol, &layout) + .to_symbol_string(symbol, &env.interns); + + let function = match env.module.get_function(fn_name.as_str()) { + Some(function_value) => function_value, + None => { + let function_value = build_inc_dec_header(env, &layout, fn_name); + + build_dec_union_help(env, layout_ids, fields, function_value); + + function_value + } + }; + + env.builder.position_at_end(block); + let call = env + .builder + .build_call(function, &[value], "decrement_union"); + + call.set_call_convention(FAST_CALL_CONV); +} + +pub fn build_dec_union_help<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + tags: &[&[Layout<'a>]], + fn_val: FunctionValue<'ctx>, +) { + debug_assert!(!tags.is_empty()); + + use inkwell::types::BasicType; + + let context = &env.context; + let builder = env.builder; + + // Add a basic block for the entry point + let entry = context.append_basic_block(fn_val, "entry"); + + builder.position_at_end(entry); + + let mut scope = Scope::default(); + + // Add args to scope + let arg_symbol = Symbol::ARG_1; + let layout = Layout::Union(tags); + let arg_val = fn_val.get_param_iter().next().unwrap(); + + set_name(arg_val, arg_symbol.ident_string(&env.interns)); + + let alloca = create_entry_block_alloca( + env, + fn_val, + arg_val.get_type(), + arg_symbol.ident_string(&env.interns), + ); + + builder.build_store(alloca, arg_val); + + scope.insert(arg_symbol, (layout.clone(), alloca)); + + let parent = fn_val; + + let layout = Layout::RecursiveUnion(tags); + let before_block = env.builder.get_insert_block().expect("to be in a function"); + + let wrapper_struct = arg_val.into_struct_value(); + + // let tag_id_u8 = cast_basic_basic(env.builder, tag_id.into(), env.context.i8_type().into()); + + // next, make a jump table for all possible values of the tag_id + let mut cases = Vec::with_capacity_in(tags.len(), env.arena); + + let merge_block = env.context.append_basic_block(parent, "decrement_merge"); + + for (tag_id, field_layouts) in tags.iter().enumerate() { + let block = env.context.append_basic_block(parent, "tag_id_decrement"); + env.builder.position_at_end(block); + + let wrapper_type = basic_type_from_layout( + env.arena, + env.context, + &Layout::Struct(field_layouts), + env.ptr_bytes, + ); + + let wrapper_struct = + cast_struct_struct(env.builder, wrapper_struct, wrapper_type.into_struct_type()); + + for (i, field_layout) in field_layouts.iter().enumerate() { + if let Layout::RecursivePointer = field_layout { + // a *i64 pointer to the recursive data + // we need to cast this pointer to the appropriate type + let field_ptr = env + .builder + .build_extract_value(wrapper_struct, i as u32, "decrement_struct_field") + .unwrap(); + + // recursively decrement + let union_type = block_of_memory(env.context, &layout, env.ptr_bytes); + let recursive_field_ptr = cast_basic_basic( + env.builder, + field_ptr, + union_type.ptr_type(AddressSpace::Generic).into(), + ) + .into_pointer_value(); + + let recursive_field = env + .builder + .build_load(recursive_field_ptr, "load_recursive_field"); + + // recursively decrement the field + let call = + env.builder + .build_call(fn_val, &[recursive_field], "recursive_tag_decrement"); + + // Because it's an internal-only function, use the fast calling convention. + call.set_call_convention(FAST_CALL_CONV); + + // first decrement the pointer itself + // This is so the recursive decrement may optimize into a tail call + decrement_refcount_ptr(env, parent, &layout, field_ptr.into_pointer_value()); + } else if field_layout.contains_refcounted() { + let field_ptr = env + .builder + .build_extract_value(wrapper_struct, i as u32, "decrement_struct_field") + .unwrap(); + + decrement_refcount_layout(env, parent, layout_ids, field_ptr, field_layout); + } + } + + env.builder.build_unconditional_branch(merge_block); + + cases.push(( + env.context.i64_type().const_int(tag_id as u64, false), + block, + )); + } + + cases.reverse(); + + let (_, default_block) = cases.pop().unwrap(); + + env.builder.position_at_end(before_block); + + // read the tag_id + let current_tag_id = { + // the first element of the wrapping struct is an array of i64 + let first_array = env + .builder + .build_extract_value(wrapper_struct, 0, "read_tag_id") + .unwrap() + .into_array_value(); + + env.builder + .build_extract_value(first_array, 0, "read_tag_id_2") + .unwrap() + .into_int_value() + }; + + // switch on it + env.builder + .build_switch(current_tag_id, default_block, &cases); + + env.builder.position_at_end(merge_block); + + // this function returns void + builder.build_return(None); +} + +pub fn build_inc_union<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + fields: &'a [&'a [Layout<'a>]], + value: BasicValueEnum<'ctx>, +) { + let layout = Layout::Union(fields); + + let block = env.builder.get_insert_block().expect("to be in a function"); + + let symbol = Symbol::INC; + let fn_name = layout_ids + .get(symbol, &layout) + .to_symbol_string(symbol, &env.interns); + + let function = match env.module.get_function(fn_name.as_str()) { + Some(function_value) => function_value, + None => { + let function_value = build_inc_dec_header(env, &layout, fn_name); + + build_inc_union_help(env, layout_ids, fields, function_value); + + function_value + } + }; + + env.builder.position_at_end(block); + let call = env + .builder + .build_call(function, &[value], "increment_union"); + + call.set_call_convention(FAST_CALL_CONV); +} + +pub fn build_inc_union_help<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + tags: &[&[Layout<'a>]], + fn_val: FunctionValue<'ctx>, +) { + debug_assert!(!tags.is_empty()); + + use inkwell::types::BasicType; + + let context = &env.context; + let builder = env.builder; + + // Add a basic block for the entry point + let entry = context.append_basic_block(fn_val, "entry"); + + builder.position_at_end(entry); + + let mut scope = Scope::default(); + + // Add args to scope + let arg_symbol = Symbol::ARG_1; + let layout = Layout::Union(tags); + let arg_val = fn_val.get_param_iter().next().unwrap(); + + set_name(arg_val, arg_symbol.ident_string(&env.interns)); + + let alloca = create_entry_block_alloca( + env, + fn_val, + arg_val.get_type(), + arg_symbol.ident_string(&env.interns), + ); + + builder.build_store(alloca, arg_val); + + scope.insert(arg_symbol, (layout.clone(), alloca)); + + let parent = fn_val; + + let layout = Layout::RecursiveUnion(tags); + 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 = { + // the first element of the wrapping struct is an array of i64 + let first_array = env + .builder + .build_extract_value(wrapper_struct, 0, "read_tag_id") + .unwrap() + .into_array_value(); + + env.builder + .build_extract_value(first_array, 0, "read_tag_id_2") + .unwrap() + .into_int_value() + }; + + let tag_id_u8 = cast_basic_basic(env.builder, tag_id.into(), env.context.i8_type().into()); + + // next, make a jump table for all possible values of the tag_id + let mut cases = Vec::with_capacity_in(tags.len(), env.arena); + + let merge_block = env.context.append_basic_block(parent, "decrement_merge"); + + for (tag_id, field_layouts) in tags.iter().enumerate() { + let block = env.context.append_basic_block(parent, "tag_id_decrement"); + env.builder.position_at_end(block); + + let wrapper_type = basic_type_from_layout( + env.arena, + env.context, + &Layout::Struct(field_layouts), + env.ptr_bytes, + ); + + let wrapper_struct = + cast_struct_struct(env.builder, wrapper_struct, wrapper_type.into_struct_type()); + + for (i, field_layout) in field_layouts.iter().enumerate() { + if let Layout::RecursivePointer = field_layout { + let field_ptr = env + .builder + .build_extract_value(wrapper_struct, i as u32, "decrement_struct_field") + .unwrap() + .into_pointer_value(); + + // recursively decrement + let recursive_field_ptr_as_int = + env.builder.build_load(field_ptr, "recursive_decrement"); + + let union_type = block_of_memory(env.context, &layout, env.ptr_bytes); + let recursive_field_ptr = cast_basic_basic( + env.builder, + recursive_field_ptr_as_int, + union_type.ptr_type(AddressSpace::Generic).into(), + ) + .into_pointer_value(); + + let recursive_field = env + .builder + .build_load(recursive_field_ptr, "load_recursive_field"); + + // first decrement the pointer itself + // This is so the recursive decrement may optimize into a tail call + //decrement_refcount_ptr(env, parent, wrapper_struct, field_ptr); + + // recursively decrement the field + let call = + env.builder + .build_call(fn_val, &[recursive_field], "recursive_tag_increment"); + + // Because it's an internal-only function, use the fast calling convention. + call.set_call_convention(FAST_CALL_CONV); + } else if field_layout.contains_refcounted() { + let field_ptr = env + .builder + .build_extract_value(wrapper_struct, i as u32, "increment_struct_field") + .unwrap(); + + decrement_refcount_layout(env, parent, layout_ids, field_ptr, field_layout); + } + } + + env.builder.build_unconditional_branch(merge_block); + + cases.push((env.context.i8_type().const_int(tag_id as u64, false), block)); + } + + let (_, default_block) = cases.pop().unwrap(); + + env.builder.position_at_end(before_block); + + env.builder + .build_switch(tag_id_u8.into_int_value(), default_block, &cases); + + env.builder.position_at_end(merge_block); + + // this function returns void + builder.build_return(None); +} diff --git a/compiler/gen/tests/gen_primitives.rs b/compiler/gen/tests/gen_primitives.rs index a69aefe045..b0d3cacb47 100644 --- a/compiler/gen/tests/gen_primitives.rs +++ b/compiler/gen/tests/gen_primitives.rs @@ -533,7 +533,36 @@ mod gen_primitives { "# ), 0, - i64 + i64, + |x| x, + false + ); + } + + #[test] + fn linked_list_len_1() { + assert_evals_to!( + indoc!( + r#" + LinkedList a : [ Nil, Cons a (LinkedList a) ] + + one : LinkedList Int + one = Cons 1 Nil + + length : LinkedList a -> Int + length = \list -> + when list is + Nil -> 0 + Cons _ rest -> 1 + length rest + + + length one + "# + ), + 1, + i64, + |x| x, + false ); } @@ -558,7 +587,9 @@ mod gen_primitives { "# ), 3, - i64 + i64, + |x| x, + false ); } diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index 42834c10f9..8886e96e94 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -1,4 +1,4 @@ -use roc_collections::all::MutSet; +use roc_collections::all::{MutMap, MutSet}; use roc_types::subs::Subs; pub fn helper_without_uniqueness<'a>( @@ -90,6 +90,7 @@ pub fn helper_without_uniqueness<'a>( ptr_bytes, leak: leak, exposed_to_host: MutSet::default(), + rc_functions: MutMap::default(), }; let mut procs = roc_mono::ir::Procs::default(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); @@ -184,7 +185,7 @@ pub fn helper_without_uniqueness<'a>( ); // Uncomment this to see the module's un-optimized LLVM instruction output: - // env.module.print_to_stderr(); + env.module.print_to_stderr(); if main_fn.verify(true) { function_pass.run_on(&main_fn); @@ -284,6 +285,7 @@ pub fn helper_with_uniqueness<'a>( ptr_bytes, leak: leak, exposed_to_host: MutSet::default(), + rc_functions: MutMap::default(), }; let mut procs = roc_mono::ir::Procs::default(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); @@ -475,7 +477,7 @@ macro_rules! assert_evals_to { assert_llvm_evals_to!($src, $expected, $ty, (|val| val)); } { - assert_opt_evals_to!($src, $expected, $ty, (|val| val)); + // assert_opt_evals_to!($src, $expected, $ty, (|val| val)); } }; ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { @@ -493,7 +495,7 @@ macro_rules! assert_evals_to { assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak); } { - assert_opt_evals_to!($src, $expected, $ty, $transform, $leak); + // assert_opt_evals_to!($src, $expected, $ty, $transform, $leak); } }; } diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 90484570cf..d25a1bbe3c 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -598,6 +598,8 @@ define_builtins! { 7 ARG_6: "#arg6" 8 ARG_7: "#arg7" 9 ARG_8: "#arg8" + 10 INC: "#inc" // internal function that increments the refcount + 11 DEC: "#dec" // internal function that increments the refcount } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias