use crate::llvm::build::{build_num_binop, Env, InPlace}; use crate::llvm::compare::build_eq; use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type, ptr_int}; use inkwell::builder::Builder; use inkwell::context::Context; use inkwell::types::{BasicTypeEnum, PointerType}; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::{AddressSpace, IntPredicate}; use roc_mono::layout::{Builtin, Layout, MemoryMode}; /// List.single : a -> List a pub fn list_single<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, inplace: InPlace, elem: BasicValueEnum<'ctx>, elem_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let ctx = env.context; // allocate a list of size 1 on the heap let size = ctx.i64_type().const_int(1, false); let ptr = allocate_list(env, inplace, elem_layout, size); // Put the element into the list let elem_ptr = unsafe { builder.build_in_bounds_gep( ptr, &[ctx.i64_type().const_int( // 0 as in 0 index of our new list 0 as u64, false, )], "index", ) }; builder.build_store(elem_ptr, elem); store_list(env, ptr, env.ptr_int().const_int(1, false)) } /// List.repeat : Int, elem -> List elem pub fn list_repeat<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, inplace: InPlace, parent: FunctionValue<'ctx>, list_len: IntValue<'ctx>, elem: BasicValueEnum<'ctx>, elem_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let ctx = env.context; // list_len > 0 // We have to do a loop below, continuously adding the `elem` // to the output list `List elem` until we have reached the // number of repeats. This `comparison` is used to check // if we need to do any looping; because if we dont, then we // dont need to allocate memory for the index or the check // if index != 0 let comparison = builder.build_int_compare( IntPredicate::UGT, list_len, ctx.i64_type().const_int(0, false), "atleastzero", ); let build_then = || { // Allocate space for the new array that we'll copy into. let list_ptr = allocate_list(env, inplace, elem_layout, list_len); // TODO check if malloc returned null; if so, runtime error for OOM! let index_name = "#index"; let start_alloca = builder.build_alloca(ctx.i64_type(), index_name); // Start at the last element in the list. let last_elem_index = builder.build_int_sub( list_len, ctx.i64_type().const_int(1, false), "lastelemindex", ); builder.build_store(start_alloca, last_elem_index); let loop_bb = ctx.append_basic_block(parent, "loop"); builder.build_unconditional_branch(loop_bb); builder.position_at_end(loop_bb); // #index = #index - 1 let curr_index = builder .build_load(start_alloca, index_name) .into_int_value(); let next_index = builder.build_int_sub(curr_index, ctx.i64_type().const_int(1, false), "nextindex"); builder.build_store(start_alloca, next_index); let elem_ptr = unsafe { builder.build_in_bounds_gep(list_ptr, &[curr_index], "load_index") }; // Mutate the new array in-place to change the element. builder.build_store(elem_ptr, elem); // #index != 0 let end_cond = builder.build_int_compare( IntPredicate::NE, ctx.i64_type().const_int(0, false), curr_index, "loopcond", ); let after_bb = ctx.append_basic_block(parent, "afterloop"); builder.build_conditional_branch(end_cond, loop_bb, after_bb); builder.position_at_end(after_bb); store_list(env, list_ptr, list_len) }; let build_else = || empty_polymorphic_list(env); let struct_type = collection(ctx, env.ptr_bytes); build_basic_phi2( env, parent, comparison, build_then, build_else, BasicTypeEnum::StructType(struct_type), ) } /// List.prepend List elem, elem -> List elem pub fn list_prepend<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, inplace: InPlace, original_wrapper: StructValue<'ctx>, elem: BasicValueEnum<'ctx>, elem_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let ctx = env.context; // Load the usize length from the wrapper. let len = list_len(builder, original_wrapper); let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); let list_ptr = load_list_ptr(builder, original_wrapper, ptr_type); // The output list length, which is the old list length + 1 let new_list_len = env.builder.build_int_add( ctx.i64_type().const_int(1 as u64, false), len, "new_list_length", ); // Allocate space for the new array that we'll copy into. let clone_ptr = allocate_list(env, inplace, elem_layout, new_list_len); builder.build_store(clone_ptr, elem); let index_1_ptr = unsafe { builder.build_in_bounds_gep( clone_ptr, &[ctx.i64_type().const_int(1 as u64, false)], "load_index", ) }; // Calculate the number of bytes we'll need to allocate. let elem_bytes = env .ptr_int() .const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false); // This is the size of the list coming in, before we have added an element // to the beginning. let list_size = env .builder .build_int_mul(elem_bytes, len, "mul_old_len_by_elem_bytes"); let ptr_bytes = env.ptr_bytes; if elem_layout.safe_to_memcpy() { // Copy the bytes from the original array into the new // one we just malloc'd. // // TODO how do we decide when to do the small memcpy vs the normal one? builder .build_memcpy(index_1_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size) .unwrap(); } else { panic!("TODO Cranelift currently only knows how to clone list elements that are Copy."); } store_list(env, clone_ptr, new_list_len) } /// List.join : List (List elem) -> List elem pub fn list_join<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, inplace: InPlace, parent: FunctionValue<'ctx>, outer_list: BasicValueEnum<'ctx>, outer_list_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { // List.join is implemented as follows: // 1. loop over every list to sum the list lengths // 2. using the sum of all the list lengths, allocate an output list of // that size. // 3. loop over every list, for every list, loop over every element // putting it into the output list match outer_list_layout { // If the input list is empty, or if it is a list of empty lists // then simply return an empty list Layout::Builtin(Builtin::EmptyList) | Layout::Builtin(Builtin::List(_, Layout::Builtin(Builtin::EmptyList))) => empty_list(env), Layout::Builtin(Builtin::List(_, Layout::Builtin(Builtin::List(_, elem_layout)))) => { let inner_list_layout = Layout::Builtin(Builtin::List(MemoryMode::Refcounted, elem_layout)); let builder = env.builder; let ctx = env.context; let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let elem_ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); let inner_list_type = basic_type_from_layout(env.arena, ctx, &inner_list_layout, env.ptr_bytes); let outer_list_wrapper = outer_list.into_struct_value(); let outer_list_len = list_len(builder, outer_list_wrapper); let outer_list_ptr = { let elem_ptr_type = get_ptr_type(&inner_list_type, AddressSpace::Generic); load_list_ptr(builder, outer_list_wrapper, elem_ptr_type) }; // outer_list_len > 0 // We do this check to avoid allocating memory. If the input // list is empty, then we can just return an empty list. let comparison = list_is_not_empty(env, outer_list_len); let build_then = || { let list_len_sum_name = "#listslengthsum"; let list_len_sum_alloca = builder.build_alloca(ctx.i64_type(), list_len_sum_name); builder.build_store(list_len_sum_alloca, ctx.i64_type().const_int(0, false)); // List Sum Loop let sum_loop = |_, inner_list: BasicValueEnum<'ctx>| { let inner_list_len = list_len(builder, inner_list.into_struct_value()); let next_list_sum = builder.build_int_add( builder .build_load(list_len_sum_alloca, list_len_sum_name) .into_int_value(), inner_list_len, "nextlistsum", ); builder.build_store(list_len_sum_alloca, next_list_sum); }; incrementing_elem_loop( builder, ctx, parent, outer_list_ptr, outer_list_len, "#sum_index", sum_loop, ); let final_list_sum = builder .build_load(list_len_sum_alloca, list_len_sum_name) .into_int_value(); let final_list_ptr = allocate_list(env, inplace, elem_layout, final_list_sum); let dest_elem_ptr_alloca = builder.build_alloca(elem_ptr_type, "dest_elem"); builder.build_store(dest_elem_ptr_alloca, final_list_ptr); // Inner List Loop let inner_list_loop = |_, inner_list: BasicValueEnum<'ctx>| { let inner_list_wrapper = inner_list.into_struct_value(); let inner_list_len = list_len(builder, inner_list_wrapper); // inner_list_len > 0 let inner_list_comparison = list_is_not_empty(env, inner_list_len); let inner_list_non_empty_block = ctx.append_basic_block(parent, "inner_list_non_empty"); let after_inner_list_non_empty_block = ctx.append_basic_block(parent, "branchcont"); builder.build_conditional_branch( inner_list_comparison, inner_list_non_empty_block, after_inner_list_non_empty_block, ); builder.position_at_end(inner_list_non_empty_block); let inner_list_ptr = load_list_ptr(builder, inner_list_wrapper, elem_ptr_type); // Element Inserting Loop let inner_elem_loop = |_, src_elem| { // TODO clone src_elem let curr_dest_elem_ptr = builder .build_load(dest_elem_ptr_alloca, "load_dest_elem_ptr") .into_pointer_value(); builder.build_store(curr_dest_elem_ptr, src_elem); let inc_dest_elem_ptr = BasicValueEnum::PointerValue(unsafe { builder.build_in_bounds_gep( curr_dest_elem_ptr, &[env.ptr_int().const_int(1 as u64, false)], "increment_dest_elem", ) }); builder.build_store(dest_elem_ptr_alloca, inc_dest_elem_ptr); }; incrementing_elem_loop( builder, ctx, parent, inner_list_ptr, inner_list_len, "#inner_index", inner_elem_loop, ); builder.build_unconditional_branch(after_inner_list_non_empty_block); builder.position_at_end(after_inner_list_non_empty_block); }; incrementing_elem_loop( builder, ctx, parent, outer_list_ptr, outer_list_len, "#inner_list_index", inner_list_loop, ); store_list(env, final_list_ptr, final_list_sum) }; let build_else = || empty_list(env); let struct_type = collection(ctx, env.ptr_bytes); build_basic_phi2( env, parent, comparison, build_then, build_else, BasicTypeEnum::StructType(struct_type), ) } _ => { unreachable!("Invalid List layout for List.join {:?}", outer_list_layout); } } } /// List.reverse : List elem -> List elem pub fn list_reverse_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, inplace: InPlace, length: IntValue<'ctx>, source_ptr: PointerValue<'ctx>, dest_ptr: PointerValue<'ctx>, ) { let builder = env.builder; let ctx = env.context; // constant 1i64 let one = ctx.i64_type().const_int(1, false); let low_alloca = builder.build_alloca(ctx.i64_type(), "low"); let high_alloca = builder.build_alloca(ctx.i64_type(), "high"); let high_val = builder.build_int_sub(length, one, "subtract 1"); builder.build_store(low_alloca, ctx.i64_type().const_zero()); builder.build_store(high_alloca, high_val); // while (high > low) let condition_bb = ctx.append_basic_block(parent, "condition"); builder.build_unconditional_branch(condition_bb); builder.position_at_end(condition_bb); let high = builder.build_load(high_alloca, "high").into_int_value(); let low = builder.build_load(low_alloca, "low").into_int_value(); // if updating in-place, then the "middle element" can be left untouched // otherwise, the middle element needs to be copied over from the source to the target let predicate = match inplace { InPlace::InPlace => IntPredicate::SGT, InPlace::Clone => IntPredicate::SGE, }; let condition = builder.build_int_compare(predicate, high, low, "loopcond"); let body_bb = ctx.append_basic_block(parent, "body"); let cont_bb = ctx.append_basic_block(parent, "cont"); builder.build_conditional_branch(condition, body_bb, cont_bb); // loop body builder.position_at_end(body_bb); // assumption: calculating pointer offsets for both the source and target is let mut low_ptr = unsafe { builder.build_in_bounds_gep(source_ptr, &[low], "low_ptr") }; let mut high_ptr = unsafe { builder.build_in_bounds_gep(source_ptr, &[high], "high_ptr") }; // TODO use memmove? let low_value = builder.build_load(low_ptr, "load_low"); let high_value = builder.build_load(high_ptr, "load_high"); // swap the two values if let InPlace::Clone = inplace { low_ptr = unsafe { builder.build_in_bounds_gep(dest_ptr, &[low], "low_ptr") }; high_ptr = unsafe { builder.build_in_bounds_gep(dest_ptr, &[high], "high_ptr") }; } builder.build_store(high_ptr, low_value); builder.build_store(low_ptr, high_value); builder.build_store(low_alloca, builder.build_int_add(low, one, "increment")); builder.build_store(high_alloca, builder.build_int_sub(high, one, "decrement")); builder.build_unconditional_branch(condition_bb); // continuation builder.position_at_end(cont_bb); } /// List.reverse : List elem -> List elem pub fn list_reverse<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, output_inplace: InPlace, list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { use inkwell::types::BasicType; let builder = env.builder; let ctx = env.context; let wrapper_struct = list.into_struct_value(); let (input_inplace, element_layout) = match list_layout.clone() { Layout::Builtin(Builtin::EmptyList) => ( InPlace::InPlace, // this pointer will never actually be dereferenced Layout::Builtin(Builtin::Int64), ), Layout::Builtin(Builtin::List(memory_mode, elem_layout)) => ( match memory_mode { MemoryMode::Unique => InPlace::InPlace, MemoryMode::Refcounted => InPlace::Clone, }, elem_layout.clone(), ), _ => unreachable!("Invalid layout {:?} in List.reverse", list_layout), }; let list_type = basic_type_from_layout(env.arena, env.context, &element_layout, env.ptr_bytes); let ptr_type = list_type.ptr_type(AddressSpace::Generic); let list_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); let length = list_len(builder, list.into_struct_value()); match input_inplace { InPlace::InPlace => { list_reverse_help(env, parent, input_inplace, length, list_ptr, list_ptr); list } InPlace::Clone => { let len_0_block = ctx.append_basic_block(parent, "len_0_block"); let len_1_block = ctx.append_basic_block(parent, "len_1_block"); let len_n_block = ctx.append_basic_block(parent, "len_n_block"); let cont_block = ctx.append_basic_block(parent, "cont_block"); let one = ctx.i64_type().const_int(1, false); let zero = ctx.i64_type().const_zero(); let result = builder.build_alloca(ptr_type, "result"); builder.build_switch( length, len_n_block, &[(zero, len_0_block), (one, len_1_block)], ); // build block for length 0 { builder.position_at_end(len_0_block); // store NULL pointer there builder.build_store(result, ptr_type.const_zero()); builder.build_unconditional_branch(cont_block); } // build block for length 1 { builder.position_at_end(len_1_block); let new_list_ptr = clone_list(env, output_inplace, &element_layout, one, list_ptr); builder.build_store(result, new_list_ptr); builder.build_unconditional_branch(cont_block); } // build block for length > 1 { builder.position_at_end(len_n_block); let new_list_ptr = allocate_list(env, output_inplace, &element_layout, length); list_reverse_help(env, parent, InPlace::Clone, length, list_ptr, new_list_ptr); // store new list pointer there builder.build_store(result, new_list_ptr); builder.build_unconditional_branch(cont_block); } builder.position_at_end(cont_block); let new_list_ptr = builder.build_load(result, "result").into_pointer_value(); store_list(env, new_list_ptr, length) } } } pub fn list_get_unsafe<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, list_layout: &Layout<'a>, elem_index: IntValue<'ctx>, wrapper_struct: StructValue<'ctx>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; match list_layout { Layout::Builtin(Builtin::List(_, elem_layout)) => { let ctx = env.context; let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); // Load the pointer to the array data let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); // 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") }; builder.build_load(elem_ptr, "List.get") } _ => { unreachable!( "Invalid List layout for ListGetUnsafe operation: {:?}", list_layout ); } } } /// List.append : List elem, elem -> List elem pub fn list_append<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, inplace: InPlace, original_wrapper: StructValue<'ctx>, elem: BasicValueEnum<'ctx>, elem_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let ctx = env.context; // Load the usize length from the wrapper. let list_len = list_len(builder, original_wrapper); let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); let list_ptr = load_list_ptr(builder, original_wrapper, ptr_type); // The output list length, which is the old list length + 1 let new_list_len = env.builder.build_int_add( ctx.i64_type().const_int(1 as u64, false), list_len, "new_list_length", ); let ptr_bytes = env.ptr_bytes; // Calculate the number of bytes we'll need to allocate. let elem_bytes = env .ptr_int() .const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false); // This is the size of the list coming in, before we have added an element // to the end. let list_size = env .builder .build_int_mul(elem_bytes, list_len, "mul_old_len_by_elem_bytes"); // Allocate space for the new array that we'll copy into. let clone_ptr = allocate_list(env, inplace, elem_layout, new_list_len); // TODO check if malloc returned null; if so, runtime error for OOM! if elem_layout.safe_to_memcpy() { // Copy the bytes from the original array into the new // one we just malloc'd. // // TODO how do we decide when to do the small memcpy vs the normal one? builder .build_memcpy(clone_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size) .unwrap(); } else { panic!("TODO Cranelift currently only knows how to clone list elements that are Copy."); } let elem_ptr = unsafe { builder.build_in_bounds_gep(clone_ptr, &[list_len], "load_index") }; builder.build_store(elem_ptr, elem); store_list(env, clone_ptr, new_list_len) } /// List.set : List elem, Int, elem -> List elem pub fn list_set<'a, 'ctx, 'env>( parent: FunctionValue<'ctx>, args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)], env: &Env<'a, 'ctx, 'env>, input_inplace: InPlace, output_inplace: InPlace, ) -> BasicValueEnum<'ctx> { let builder = env.builder; debug_assert_eq!(args.len(), 3); let original_wrapper = args[0].0.into_struct_value(); let elem_index = args[1].0.into_int_value(); // Load the usize length from the wrapper. We need it for bounds checking. let list_len = list_len(builder, original_wrapper); // Bounds check: only proceed if index < length. // Otherwise, return the list unaltered. let comparison = bounds_check_comparison(builder, elem_index, list_len); // If the index is in bounds, clone and mutate in place. let build_then = || { let (elem, elem_layout) = args[2]; let ctx = env.context; let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); let (new_wrapper, array_data_ptr) = match input_inplace { InPlace::InPlace => ( original_wrapper, load_list_ptr(builder, original_wrapper, ptr_type), ), InPlace::Clone => clone_nonempty_list( env, output_inplace, list_len, load_list_ptr(builder, original_wrapper, ptr_type), elem_layout, ), }; // If we got here, we passed the bounds check, so this is an in-bounds GEP let elem_ptr = unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "load_index") }; // Mutate the new array in-place to change the element. builder.build_store(elem_ptr, elem); BasicValueEnum::StructValue(new_wrapper) }; // If the index was out of bounds, return the original list unaltered. let build_else = || BasicValueEnum::StructValue(original_wrapper); let ret_type = original_wrapper.get_type(); build_basic_phi2( env, parent, comparison, build_then, build_else, ret_type.into(), ) } fn bounds_check_comparison<'ctx>( builder: &Builder<'ctx>, elem_index: IntValue<'ctx>, len: IntValue<'ctx>, ) -> IntValue<'ctx> { // Note: Check for index < length as the "true" condition, // to avoid misprediction. (In practice this should usually pass, // and CPUs generally default to predicting that a forward jump // shouldn't be taken; that is, they predict "else" won't be taken.) builder.build_int_compare(IntPredicate::ULT, elem_index, len, "bounds_check") } /// List.len : List elem -> Int pub fn list_len<'ctx>( builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>, ) -> IntValue<'ctx> { builder .build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len") .unwrap() .into_int_value() } /// List.sum : List (Num a) -> Num a pub fn list_sum<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, list: BasicValueEnum<'ctx>, default_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { let ctx = env.context; let builder = env.builder; let list_wrapper = list.into_struct_value(); let len = list_len(env.builder, list_wrapper); let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes); let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum"); let default: BasicValueEnum = match accum_type { BasicTypeEnum::IntType(int_type) => int_type.const_zero().into(), BasicTypeEnum::FloatType(float_type) => float_type.const_zero().into(), _ => unreachable!(""), }; builder.build_store(accum_alloca, default); let then_block = ctx.append_basic_block(parent, "then"); let cont_block = ctx.append_basic_block(parent, "branchcont"); let condition = builder.build_int_compare( IntPredicate::UGT, len, ctx.i64_type().const_zero(), "list_non_empty", ); builder.build_conditional_branch(condition, then_block, cont_block); builder.position_at_end(then_block); let elem_ptr_type = get_ptr_type(&accum_type, AddressSpace::Generic); let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type); let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| { // load current accumulator let current = builder.build_load(accum_alloca, "retrieve_accum"); let new_current = build_num_binop( env, parent, current, default_layout, elem, default_layout, roc_module::low_level::LowLevel::NumAdd, ); builder.build_store(accum_alloca, new_current); }; incrementing_elem_loop( builder, ctx, parent, list_ptr, len, "#index", walk_right_loop, ); builder.build_unconditional_branch(cont_block); builder.position_at_end(cont_block); builder.build_load(accum_alloca, "load_final_acum") } /// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum #[allow(clippy::too_many_arguments)] pub fn list_walk_right<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, func: BasicValueEnum<'ctx>, func_layout: &Layout<'a>, default: BasicValueEnum<'ctx>, default_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { let ctx = env.context; let builder = env.builder; let list_wrapper = list.into_struct_value(); let len = list_len(env.builder, list_wrapper); let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes); let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum"); builder.build_store(accum_alloca, default); let then_block = ctx.append_basic_block(parent, "then"); let cont_block = ctx.append_basic_block(parent, "branchcont"); let condition = builder.build_int_compare( IntPredicate::UGT, len, ctx.i64_type().const_zero(), "list_non_empty", ); builder.build_conditional_branch(condition, then_block, cont_block); builder.position_at_end(then_block); match (func, func_layout) { (BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, _)) => { let elem_layout = match list_layout { Layout::Builtin(Builtin::List(_, layout)) => layout, _ => unreachable!("can only fold over a list"), }; let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let elem_ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type); let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| { // load current accumulator let current = builder.build_load(accum_alloca, "retrieve_accum"); let call_site_value = builder.build_call(func_ptr, &[elem, current], "#walk_right_func"); // set the calling convention explicitly for this call call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV); let new_current = call_site_value .try_as_basic_value() .left() .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")); builder.build_store(accum_alloca, new_current); }; incrementing_elem_loop( builder, ctx, parent, list_ptr, len, "#index", walk_right_loop, ); } _ => { unreachable!( "Invalid function basic value enum or layout for List.keepIf : {:?}", (func, func_layout) ); } } builder.build_unconditional_branch(cont_block); builder.position_at_end(cont_block); builder.build_load(accum_alloca, "load_final_acum") } /// List.contains : List elem, elem -> Bool pub fn list_contains<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, elem: BasicValueEnum<'ctx>, elem_layout: &Layout<'a>, list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { use inkwell::types::BasicType; let builder = env.builder; let wrapper_struct = list.into_struct_value(); let list_elem_layout = match &list_layout { // this pointer will never actually be dereferenced Layout::Builtin(Builtin::EmptyList) => &Layout::Builtin(Builtin::Int64), Layout::Builtin(Builtin::List(_, element_layout)) => element_layout, _ => unreachable!("Invalid layout {:?} in List.contains", list_layout), }; let list_elem_type = basic_type_from_layout(env.arena, env.context, list_elem_layout, env.ptr_bytes); let list_ptr = load_list_ptr( builder, wrapper_struct, list_elem_type.ptr_type(AddressSpace::Generic), ); let length = list_len(builder, list.into_struct_value()); list_contains_help( env, parent, length, list_ptr, list_elem_layout, elem, elem_layout, ) } pub fn list_contains_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, length: IntValue<'ctx>, source_ptr: PointerValue<'ctx>, list_elem_layout: &Layout<'a>, elem: BasicValueEnum<'ctx>, elem_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let ctx = env.context; let bool_alloca = builder.build_alloca(ctx.bool_type(), "bool_alloca"); let index_alloca = builder.build_alloca(ctx.i64_type(), "index_alloca"); let next_free_index_alloca = builder.build_alloca(ctx.i64_type(), "next_free_index_alloca"); builder.build_store(bool_alloca, ctx.bool_type().const_zero()); builder.build_store(index_alloca, ctx.i64_type().const_zero()); builder.build_store(next_free_index_alloca, ctx.i64_type().const_zero()); let condition_bb = ctx.append_basic_block(parent, "condition"); builder.build_unconditional_branch(condition_bb); builder.position_at_end(condition_bb); let index = builder.build_load(index_alloca, "index").into_int_value(); let condition = builder.build_int_compare(IntPredicate::SGT, length, index, "loopcond"); let body_bb = ctx.append_basic_block(parent, "body"); let cont_bb = ctx.append_basic_block(parent, "cont"); builder.build_conditional_branch(condition, body_bb, cont_bb); // loop body builder.position_at_end(body_bb); let current_elem_ptr = unsafe { builder.build_in_bounds_gep(source_ptr, &[index], "elem_ptr") }; let current_elem = builder.build_load(current_elem_ptr, "load_elem"); let has_found = build_eq(env, current_elem, elem, list_elem_layout, elem_layout); builder.build_store(bool_alloca, has_found.into_int_value()); let one = ctx.i64_type().const_int(1, false); let next_free_index = builder .build_load(next_free_index_alloca, "load_next_free") .into_int_value(); builder.build_store( next_free_index_alloca, builder.build_int_add(next_free_index, one, "incremented_next_free_index"), ); builder.build_store( index_alloca, builder.build_int_add(index, one, "incremented_index"), ); builder.build_conditional_branch(has_found.into_int_value(), cont_bb, condition_bb); // continuation builder.position_at_end(cont_bb); builder.build_load(bool_alloca, "answer") } /// List.keepIf : List elem, (elem -> Bool) -> List elem pub fn list_keep_if<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, output_inplace: InPlace, parent: FunctionValue<'ctx>, func: BasicValueEnum<'ctx>, func_layout: &Layout<'a>, list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { use inkwell::types::BasicType; let builder = env.builder; let ctx = env.context; let wrapper_struct = list.into_struct_value(); let (input_inplace, element_layout) = match list_layout.clone() { Layout::Builtin(Builtin::EmptyList) => ( InPlace::InPlace, // this pointer will never actually be dereferenced Layout::Builtin(Builtin::Int64), ), Layout::Builtin(Builtin::List(memory_mode, elem_layout)) => ( match memory_mode { MemoryMode::Unique => InPlace::InPlace, MemoryMode::Refcounted => InPlace::Clone, }, elem_layout.clone(), ), _ => unreachable!("Invalid layout {:?} in List.keepIf", list_layout), }; let list_type = basic_type_from_layout(env.arena, env.context, &list_layout, env.ptr_bytes); let elem_type = basic_type_from_layout(env.arena, env.context, &element_layout, env.ptr_bytes); let ptr_type = elem_type.ptr_type(AddressSpace::Generic); let list_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); let length = list_len(builder, list.into_struct_value()); let zero = ctx.i64_type().const_zero(); match input_inplace { InPlace::InPlace => { let new_length = list_keep_if_help( env, input_inplace, parent, length, list_ptr, list_ptr, func, func_layout, ); store_list(env, list_ptr, new_length) } InPlace::Clone => { let len_0_block = ctx.append_basic_block(parent, "len_0_block"); let len_n_block = ctx.append_basic_block(parent, "len_n_block"); let cont_block = ctx.append_basic_block(parent, "cont_block"); let result = builder.build_alloca(list_type, "result"); builder.build_switch(length, len_n_block, &[(zero, len_0_block)]); // build block for length 0 { builder.position_at_end(len_0_block); let new_list = store_list(env, ptr_type.const_zero(), zero); builder.build_store(result, new_list); builder.build_unconditional_branch(cont_block); } // build block for length > 0 { builder.position_at_end(len_n_block); let new_list_ptr = allocate_list(env, output_inplace, &element_layout, length); let new_length = list_keep_if_help( env, InPlace::Clone, parent, length, list_ptr, new_list_ptr, func, func_layout, ); // store new list pointer there let new_list = store_list(env, new_list_ptr, new_length); builder.build_store(result, new_list); builder.build_unconditional_branch(cont_block); } builder.position_at_end(cont_block); builder.build_load(result, "load_result") } } } #[allow(clippy::too_many_arguments)] pub fn list_keep_if_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, _inplace: InPlace, parent: FunctionValue<'ctx>, length: IntValue<'ctx>, source_ptr: PointerValue<'ctx>, dest_ptr: PointerValue<'ctx>, func: BasicValueEnum<'ctx>, func_layout: &Layout<'a>, ) -> IntValue<'ctx> { match (func, func_layout) { ( BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, Layout::Builtin(Builtin::Int1)), ) => { let builder = env.builder; let ctx = env.context; let index_alloca = builder.build_alloca(ctx.i64_type(), "index_alloca"); let next_free_index_alloca = builder.build_alloca(ctx.i64_type(), "next_free_index_alloca"); builder.build_store(index_alloca, ctx.i64_type().const_zero()); builder.build_store(next_free_index_alloca, ctx.i64_type().const_zero()); // while (length > next_index) let condition_bb = ctx.append_basic_block(parent, "condition"); builder.build_unconditional_branch(condition_bb); builder.position_at_end(condition_bb); let index = builder.build_load(index_alloca, "index").into_int_value(); let condition = builder.build_int_compare(IntPredicate::SGT, length, index, "loopcond"); let body_bb = ctx.append_basic_block(parent, "body"); let cont_bb = ctx.append_basic_block(parent, "cont"); builder.build_conditional_branch(condition, body_bb, cont_bb); // loop body builder.position_at_end(body_bb); let elem_ptr = unsafe { builder.build_in_bounds_gep(source_ptr, &[index], "elem_ptr") }; let elem = builder.build_load(elem_ptr, "load_elem"); let call_site_value = builder.build_call(func_ptr, env.arena.alloc([elem]), "#keep_if_insert_func"); // set the calling convention explicitly for this call call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV); let should_keep = call_site_value .try_as_basic_value() .left() .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")) .into_int_value(); let filter_pass_bb = ctx.append_basic_block(parent, "loop"); let after_filter_pass_bb = ctx.append_basic_block(parent, "after_loop"); let one = ctx.i64_type().const_int(1, false); builder.build_conditional_branch(should_keep, filter_pass_bb, after_filter_pass_bb); builder.position_at_end(filter_pass_bb); let next_free_index = builder .build_load(next_free_index_alloca, "load_next_free") .into_int_value(); // TODO if next_free_index equals index, and we are mutating in place, // then maybe we should not write this value back into memory let dest_elem_ptr = unsafe { builder.build_in_bounds_gep(dest_ptr, &[next_free_index], "dest_elem_ptr") }; builder.build_store(dest_elem_ptr, elem); builder.build_store( next_free_index_alloca, builder.build_int_add(next_free_index, one, "incremented_next_free_index"), ); builder.build_unconditional_branch(after_filter_pass_bb); builder.position_at_end(after_filter_pass_bb); builder.build_store( index_alloca, builder.build_int_add(index, one, "incremented_index"), ); builder.build_unconditional_branch(condition_bb); // continuation builder.position_at_end(cont_bb); builder .build_load(next_free_index_alloca, "new_length") .into_int_value() } _ => unreachable!( "Invalid function basic value enum or layout for List.keepIf : {:?}", (func, func_layout) ), } } /// List.map : List before, (before -> after) -> List after pub fn list_map<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, inplace: InPlace, parent: FunctionValue<'ctx>, func: BasicValueEnum<'ctx>, func_layout: &Layout<'a>, list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { match (func, func_layout) { (BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, ret_elem_layout)) => { let non_empty_fn = |elem_layout: &Layout<'a>, len: IntValue<'ctx>, list_wrapper: StructValue<'ctx>| { let ctx = env.context; let builder = env.builder; let ret_list_ptr = allocate_list(env, inplace, ret_elem_layout, len); let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type); let list_loop = |index, before_elem| { let call_site_value = builder.build_call(func_ptr, env.arena.alloc([before_elem]), "map_func"); // set the calling convention explicitly for this call call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV); let after_elem = call_site_value .try_as_basic_value() .left() .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")); // The pointer to the element in the mapped-over list let after_elem_ptr = unsafe { builder.build_in_bounds_gep(ret_list_ptr, &[index], "load_index_after_list") }; // Mutate the new array in-place to change the element. builder.build_store(after_elem_ptr, after_elem); }; incrementing_elem_loop(builder, ctx, parent, list_ptr, len, "#index", list_loop); store_list(env, ret_list_ptr, len) }; if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout, "List.map") } _ => { unreachable!( "Invalid function basic value enum or layout for List.map : {:?}", (func, func_layout) ); } } } /// List.concat : List elem, List elem -> List elem pub fn list_concat<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, inplace: InPlace, parent: FunctionValue<'ctx>, first_list: BasicValueEnum<'ctx>, second_list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let ctx = env.context; let second_list_wrapper = second_list.into_struct_value(); let second_list_len = list_len(builder, second_list_wrapper); // We only match on the first lists layout // because the first and second input lists // necessarily have the same layout match list_layout { Layout::Builtin(Builtin::EmptyList) => empty_list(env), Layout::Builtin(Builtin::List(_, elem_layout)) => { let first_list_wrapper = first_list.into_struct_value(); let first_list_len = list_len(builder, first_list_wrapper); // first_list_len > 0 // We do this check to avoid allocating memory. If the first input // list is empty, then we can just return the second list cloned let first_list_length_comparison = list_is_not_empty(env, first_list_len); let if_first_list_is_empty = || { // second_list_len > 0 // We do this check to avoid allocating memory. If the second input // list is empty, then we can just return an empty list let second_list_length_comparison = list_is_not_empty(env, second_list_len); let build_second_list_then = || { let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); let (new_wrapper, _) = clone_nonempty_list( env, inplace, second_list_len, load_list_ptr(builder, second_list_wrapper, ptr_type), elem_layout, ); BasicValueEnum::StructValue(new_wrapper) }; let build_second_list_else = || empty_list(env); build_basic_phi2( env, parent, second_list_length_comparison, build_second_list_then, build_second_list_else, BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), ) }; let if_first_list_is_not_empty = || { let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); let if_second_list_is_empty = || { let (new_wrapper, _) = clone_nonempty_list( env, inplace, first_list_len, load_list_ptr(builder, first_list_wrapper, ptr_type), elem_layout, ); BasicValueEnum::StructValue(new_wrapper) }; // second_list_len > 0 // We do this check to avoid allocating memory. If the second input // list is empty, then we can just return the first list cloned let second_list_length_comparison = list_is_not_empty(env, second_list_len); let if_second_list_is_not_empty = || { let combined_list_len = builder.build_int_add(first_list_len, second_list_len, "add_list_lengths"); let combined_list_ptr = allocate_list(env, inplace, elem_layout, combined_list_len); let first_list_ptr = load_list_ptr(builder, first_list_wrapper, ptr_type); // FIRST LOOP // TODO when the element type supports it, replace FIRST_LOOP with a memcpy! let first_loop = |first_index, first_list_elem| { // The pointer to the element in the combined list let combined_list_elem_ptr = unsafe { builder.build_in_bounds_gep( combined_list_ptr, &[first_index], "load_index_combined_list", ) }; // Mutate the new array in-place to change the element. builder.build_store(combined_list_elem_ptr, first_list_elem); }; let index_name = "#index"; let index_alloca = incrementing_elem_loop( builder, ctx, parent, first_list_ptr, first_list_len, index_name, first_loop, ); // Reset the index variable to 0 builder.build_store(index_alloca, ctx.i64_type().const_int(0, false)); let second_list_ptr = load_list_ptr(builder, second_list_wrapper, ptr_type); // SECOND LOOP // TODO when the element type supports it, replace SECOND_LOOP with a memcpy! let second_loop = |second_index, second_list_elem| { // The pointer to the element in the combined list. // Note that the pointer does not start at the index // 0, it starts at the index of first_list_len. In that // sense it is "offset". let offset_combined_list_elem_ptr = unsafe { builder.build_in_bounds_gep( combined_list_ptr, &[first_list_len], "elem", ) }; // The pointer to the element from the second list // in the combined list let combined_list_elem_ptr = unsafe { builder.build_in_bounds_gep( offset_combined_list_elem_ptr, &[second_index], "load_index_combined_list", ) }; // Mutate the new array in-place to change the element. builder.build_store(combined_list_elem_ptr, second_list_elem); }; incrementing_elem_loop( builder, ctx, parent, second_list_ptr, second_list_len, index_name, second_loop, ); store_list(env, combined_list_ptr, combined_list_len) }; build_basic_phi2( env, parent, second_list_length_comparison, if_second_list_is_not_empty, if_second_list_is_empty, BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), ) }; build_basic_phi2( env, parent, first_list_length_comparison, if_first_list_is_not_empty, if_first_list_is_empty, BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), ) } _ => { unreachable!( "Invalid List layout for first list in List.concat : {:?}", list_layout ); } } } pub fn decrementing_elem_loop<'ctx, LoopFn>( builder: &Builder<'ctx>, ctx: &'ctx Context, parent: FunctionValue<'ctx>, ptr: PointerValue<'ctx>, len: IntValue<'ctx>, index_name: &str, mut loop_fn: LoopFn, ) -> PointerValue<'ctx> where LoopFn: FnMut(IntValue<'ctx>, BasicValueEnum<'ctx>), { decrementing_index_loop(builder, ctx, 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 elem = builder.build_load(elem_ptr, "get_elem"); loop_fn(index, elem); }) } // a for-loop from the back to the front fn decrementing_index_loop<'ctx, LoopFn>( builder: &Builder<'ctx>, ctx: &'ctx Context, parent: FunctionValue<'ctx>, end: IntValue<'ctx>, index_name: &str, mut loop_fn: LoopFn, ) -> PointerValue<'ctx> where LoopFn: FnMut(IntValue<'ctx>), { // constant 1i64 let one = ctx.i64_type().const_int(1, false); // allocate a stack slot for the current index let index_alloca = builder.build_alloca(ctx.i64_type(), index_name); // we assume `end` is the length of the list // the final index is therefore `end - 1` let end_index = builder.build_int_sub(end, one, "end_index"); builder.build_store(index_alloca, end_index); let loop_bb = ctx.append_basic_block(parent, "loop"); builder.build_unconditional_branch(loop_bb); builder.position_at_end(loop_bb); let current_index = builder .build_load(index_alloca, index_name) .into_int_value(); let next_index = builder.build_int_sub(current_index, one, "nextindex"); builder.build_store(index_alloca, next_index); // The body of the loop loop_fn(current_index); // #index >= 0 let condition = builder.build_int_compare( IntPredicate::UGE, next_index, ctx.i64_type().const_zero(), "bounds_check", ); let after_loop_bb = ctx.append_basic_block(parent, "after_outer_loop"); builder.build_conditional_branch(condition, loop_bb, after_loop_bb); builder.position_at_end(after_loop_bb); index_alloca } pub fn incrementing_elem_loop<'ctx, LoopFn>( builder: &Builder<'ctx>, ctx: &'ctx Context, parent: FunctionValue<'ctx>, ptr: PointerValue<'ctx>, len: IntValue<'ctx>, index_name: &str, mut loop_fn: LoopFn, ) -> PointerValue<'ctx> where LoopFn: FnMut(IntValue<'ctx>, BasicValueEnum<'ctx>), { incrementing_index_loop(builder, ctx, 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 elem = builder.build_load(elem_ptr, "get_elem"); loop_fn(index, elem); }) } // This helper simulates a basic for loop, where // and index increments up from 0 to some end value pub fn incrementing_index_loop<'ctx, LoopFn>( builder: &Builder<'ctx>, ctx: &'ctx Context, parent: FunctionValue<'ctx>, end: IntValue<'ctx>, index_name: &str, mut loop_fn: LoopFn, ) -> PointerValue<'ctx> where LoopFn: FnMut(IntValue<'ctx>), { // constant 1i64 let one = ctx.i64_type().const_int(1, false); // allocate a stack slot for the current index let index_alloca = builder.build_alloca(ctx.i64_type(), index_name); builder.build_store(index_alloca, ctx.i64_type().const_zero()); let loop_bb = ctx.append_basic_block(parent, "loop"); builder.build_unconditional_branch(loop_bb); builder.position_at_end(loop_bb); let curr_index = builder .build_load(index_alloca, index_name) .into_int_value(); let next_index = builder.build_int_add(curr_index, one, "nextindex"); builder.build_store(index_alloca, next_index); // The body of the loop loop_fn(curr_index); // #index < end let loop_end_cond = bounds_check_comparison(builder, next_index, end); let after_loop_bb = ctx.append_basic_block(parent, "after_outer_loop"); builder.build_conditional_branch(loop_end_cond, loop_bb, after_loop_bb); builder.position_at_end(after_loop_bb); index_alloca } // This function checks if the list is empty, and // if it is, it returns an empty list, and if not // it runs whatever code is passed in under `build_non_empty` // This is the avoid allocating memory if the list is empty. fn if_list_is_not_empty<'a, 'ctx, 'env, 'b, NonEmptyFn>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, mut build_non_empty: NonEmptyFn, list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, list_fn_name: &str, ) -> BasicValueEnum<'ctx> where NonEmptyFn: FnMut(&Layout<'a>, IntValue<'ctx>, StructValue<'ctx>) -> BasicValueEnum<'ctx>, { match list_layout { Layout::Builtin(Builtin::EmptyList) => empty_list(env), Layout::Builtin(Builtin::List(_, elem_layout)) => { let builder = env.builder; let ctx = env.context; let wrapper_struct = list.into_struct_value(); let len = list_len(builder, wrapper_struct); // list_len > 0 let comparison = builder.build_int_compare( IntPredicate::UGT, len, ctx.i64_type().const_int(0, false), "greaterthanzero", ); let build_empty = || empty_list(env); let struct_type = collection(ctx, env.ptr_bytes); build_basic_phi2( env, parent, comparison, || build_non_empty(elem_layout, len, wrapper_struct), build_empty, BasicTypeEnum::StructType(struct_type), ) } _ => { unreachable!("Invalid List layout for {} {:?}", list_fn_name, list_layout); } } } pub fn build_basic_phi2<'a, 'ctx, 'env, PassFn, FailFn>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, comparison: IntValue<'ctx>, mut build_pass: PassFn, mut build_fail: FailFn, ret_type: BasicTypeEnum<'ctx>, ) -> BasicValueEnum<'ctx> where PassFn: FnMut() -> BasicValueEnum<'ctx>, FailFn: FnMut() -> BasicValueEnum<'ctx>, { let builder = env.builder; let context = env.context; // build blocks let then_block = context.append_basic_block(parent, "then"); let else_block = context.append_basic_block(parent, "else"); let cont_block = context.append_basic_block(parent, "branchcont"); builder.build_conditional_branch(comparison, then_block, else_block); // build then block builder.position_at_end(then_block); let then_val = build_pass(); builder.build_unconditional_branch(cont_block); let then_block = builder.get_insert_block().unwrap(); // build else block builder.position_at_end(else_block); let else_val = build_fail(); builder.build_unconditional_branch(cont_block); let else_block = builder.get_insert_block().unwrap(); // emit merge block builder.position_at_end(cont_block); let phi = builder.build_phi(ret_type, "branch"); phi.add_incoming(&[(&then_val, then_block), (&else_val, else_block)]); phi.as_basic_value() } pub fn empty_polymorphic_list<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> { let ctx = env.context; let struct_type = collection(ctx, env.ptr_bytes); // The pointer should be null (aka zero) and the length should be zero, // so the whole struct should be a const_zero BasicValueEnum::StructValue(struct_type.const_zero()) } // TODO investigate: does this cause problems when the layout is known? this value is now not refcounted! pub fn empty_list<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> { let ctx = env.context; let struct_type = collection(ctx, env.ptr_bytes); // The pointer should be null (aka zero) and the length should be zero, // so the whole struct should be a const_zero BasicValueEnum::StructValue(struct_type.const_zero()) } pub fn list_is_not_empty<'ctx>(env: &Env<'_, 'ctx, '_>, len: IntValue<'ctx>) -> IntValue<'ctx> { env.builder.build_int_compare( IntPredicate::UGT, len, env.ptr_int().const_zero(), "list_len_is_nonzero", ) } pub fn load_list<'ctx>( builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>, ptr_type: PointerType<'ctx>, ) -> (IntValue<'ctx>, PointerValue<'ctx>) { let ptr_as_int = builder .build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr") .unwrap() .into_int_value(); let ptr = builder.build_int_to_ptr(ptr_as_int, ptr_type, "list_cast_ptr"); let length = builder .build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len") .unwrap() .into_int_value(); (length, ptr) } pub fn load_list_ptr<'ctx>( builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>, ptr_type: PointerType<'ctx>, ) -> PointerValue<'ctx> { let ptr_as_int = builder .build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr") .unwrap() .into_int_value(); builder.build_int_to_ptr(ptr_as_int, ptr_type, "list_cast_ptr") } pub fn clone_nonempty_list<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, inplace: InPlace, list_len: IntValue<'ctx>, elems_ptr: PointerValue<'ctx>, elem_layout: &Layout<'_>, ) -> (StructValue<'ctx>, PointerValue<'ctx>) { let builder = env.builder; let ctx = env.context; let ptr_bytes = env.ptr_bytes; // Calculate the number of bytes we'll need to allocate. let elem_bytes = env .ptr_int() .const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false); let size = env .builder .build_int_mul(elem_bytes, list_len, "clone_mul_len_by_elem_bytes"); // Allocate space for the new array that we'll copy into. let clone_ptr = allocate_list(env, inplace, elem_layout, list_len); let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr"); // TODO check if malloc returned null; if so, runtime error for OOM! // Either memcpy or deep clone the array elements if elem_layout.safe_to_memcpy() { // Copy the bytes from the original array into the new // one we just malloc'd. // // TODO how do we decide when to do the small memcpy vs the normal one? builder .build_memcpy(clone_ptr, ptr_bytes, elems_ptr, ptr_bytes, size) .unwrap(); } else { panic!("TODO Cranelift currently only knows how to clone list elements that are Copy."); } // Create a fresh wrapper struct for the newly populated array let struct_type = collection(ctx, env.ptr_bytes); let mut struct_val; // Store the pointer struct_val = builder .build_insert_value( struct_type.get_undef(), ptr_as_int, Builtin::WRAPPER_PTR, "insert_ptr", ) .unwrap(); // Store the length struct_val = builder .build_insert_value(struct_val, list_len, Builtin::WRAPPER_LEN, "insert_len") .unwrap(); let answer = builder .build_bitcast( struct_val.into_struct_value(), collection(ctx, ptr_bytes), "cast_collection", ) .into_struct_value(); (answer, clone_ptr) } pub fn clone_list<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, output_inplace: InPlace, elem_layout: &Layout<'a>, length: IntValue<'ctx>, old_ptr: PointerValue<'ctx>, ) -> PointerValue<'ctx> { let builder = env.builder; let ptr_bytes = env.ptr_bytes; // allocate new empty list (with refcount 1) let new_ptr = allocate_list(env, output_inplace, elem_layout, length); let stack_size = elem_layout.stack_size(env.ptr_bytes); let bytes = builder.build_int_mul( length, env.context.i64_type().const_int(stack_size as u64, false), "size_in_bytes", ); // copy old elements in builder .build_memcpy(new_ptr, ptr_bytes, old_ptr, ptr_bytes, bytes) .unwrap(); new_ptr } pub fn allocate_list<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, inplace: InPlace, elem_layout: &Layout<'a>, length: IntValue<'ctx>, ) -> PointerValue<'ctx> { let builder = env.builder; let ctx = env.context; let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; let len_type = env.ptr_int(); // bytes per element let bytes_len = len_type.const_int(elem_bytes, false); let offset = (env.ptr_bytes as u64).max(elem_bytes); let ptr = { let len = builder.build_int_mul(bytes_len, length, "data_length"); let len = builder.build_int_add(len, len_type.const_int(offset, false), "add_refcount_space"); env.builder .build_array_malloc(ctx.i8_type(), len, "create_list_ptr") .unwrap() // TODO check if malloc returned null; if so, runtime error for OOM! }; // We must return a pointer to the first element: let ptr_bytes = env.ptr_bytes; let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr"); let incremented = builder.build_int_add( ptr_as_int, ctx.i64_type().const_int(offset, false), "increment_list_ptr", ); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); let list_element_ptr = builder.build_int_to_ptr(incremented, ptr_type, "list_cast_ptr"); // subtract ptr_size, to access the refcount let refcount_ptr = builder.build_int_sub( incremented, ctx.i64_type().const_int(env.ptr_bytes as u64, false), "refcount_ptr", ); let refcount_ptr = builder.build_int_to_ptr( refcount_ptr, int_type.ptr_type(AddressSpace::Generic), "make ptr", ); let ref_count_one = match inplace { InPlace::InPlace => length, InPlace::Clone => { // the refcount of a new list is initially 1 // we assume that the list is indeed used (dead variables are eliminated) crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes) } }; builder.build_store(refcount_ptr, ref_count_one); list_element_ptr } pub fn store_list<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, list_ptr: PointerValue<'ctx>, len: IntValue<'ctx>, ) -> BasicValueEnum<'ctx> { let ctx = env.context; let builder = env.builder; let ptr_bytes = env.ptr_bytes; let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int(list_ptr, int_type, "list_cast_ptr"); let struct_type = collection(ctx, ptr_bytes); let mut struct_val; // Store the pointer struct_val = builder .build_insert_value( struct_type.get_undef(), ptr_as_int, Builtin::WRAPPER_PTR, "insert_ptr", ) .unwrap(); // Store the length struct_val = builder .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") .unwrap(); builder.build_bitcast( struct_val.into_struct_value(), collection(ctx, ptr_bytes), "cast_collection", ) }