use crate::layout_id::LayoutIds; use crate::llvm::compare::{build_eq, build_neq}; use crate::llvm::convert::{ basic_type_from_layout, collection, get_fn_type, get_ptr_type, ptr_int, }; use bumpalo::collections::Vec; use bumpalo::Bump; use inkwell::builder::Builder; use inkwell::context::Context; use inkwell::memory_buffer::MemoryBuffer; use inkwell::module::{Linkage, Module}; use inkwell::passes::{PassManager, PassManagerBuilder}; use inkwell::types::{BasicTypeEnum, FunctionType, IntType, PointerType, StructType}; use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::{FloatValue, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::AddressSpace; use inkwell::{IntPredicate, OptimizationLevel}; use roc_collections::all::ImMap; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::{Expr, Proc}; use roc_mono::layout::{Builtin, Layout}; use target_lexicon::CallingConvention; /// This is for Inkwell's FunctionValue::verify - we want to know the verification /// output in debug builds, but we don't want it to print to stdout in release builds! #[cfg(debug_assertions)] const PRINT_FN_VERIFICATION_OUTPUT: bool = true; #[cfg(not(debug_assertions))] const PRINT_FN_VERIFICATION_OUTPUT: bool = false; pub enum OptLevel { Normal, Optimize, } pub type Scope<'a, 'ctx> = ImMap, PointerValue<'ctx>)>; pub struct Env<'a, 'ctx, 'env> { pub arena: &'a Bump, pub context: &'ctx Context, pub builder: &'env Builder<'ctx>, pub module: &'ctx Module<'ctx>, pub interns: Interns, pub ptr_bytes: u32, } impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { pub fn ptr_int(&self) -> IntType<'ctx> { ptr_int(self.context, self.ptr_bytes) } } pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Module<'ctx> { let memory_buffer = MemoryBuffer::create_from_memory_range(include_bytes!("builtins.bc"), module_name); let module = Module::parse_bitcode_from_buffer(&memory_buffer, ctx) .unwrap_or_else(|err| panic!("Unable to import builtins bitcode. LLVM error: {:?}", err)); // Add LLVM intrinsics. add_intrinsics(ctx, &module); module } fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { // List of all supported LLVM intrinsics: // // https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics let i64_type = ctx.i64_type(); let f64_type = ctx.f64_type(); add_intrinsic( module, LLVM_SQRT_F64, f64_type.fn_type(&[f64_type.into()], false), ); add_intrinsic( module, LLVM_LROUND_I64_F64, i64_type.fn_type(&[f64_type.into()], false), ); add_intrinsic( module, LLVM_FABS_F64, f64_type.fn_type(&[f64_type.into()], false), ); add_intrinsic( module, LLVM_SIN_F64, f64_type.fn_type(&[f64_type.into()], false), ); add_intrinsic( module, LLVM_COS_F64, f64_type.fn_type(&[f64_type.into()], false), ); } static LLVM_SQRT_F64: &str = "llvm.sqrt.f64"; static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64"; static LLVM_FABS_F64: &str = "llvm.fabs.f64"; static LLVM_SIN_F64: &str = "llvm.sin.f64"; static LLVM_COS_F64: &str = "llvm.cos.f64"; fn add_intrinsic<'ctx>( module: &Module<'ctx>, intrinsic_name: &'static str, fn_type: FunctionType<'ctx>, ) -> FunctionValue<'ctx> { let fn_val = module.add_function(intrinsic_name, fn_type, None); fn_val.set_call_conventions(C_CALL_CONV); fn_val } pub fn add_passes(fpm: &PassManager>, opt_level: OptLevel) { // tail-call elimination is always on fpm.add_instruction_combining_pass(); fpm.add_tail_call_elimination_pass(); let pmb = PassManagerBuilder::create(); // Enable more optimizations when running cargo test --release match opt_level { OptLevel::Normal => { pmb.set_optimization_level(OptimizationLevel::None); } OptLevel::Optimize => { // Default is O2, Aggressive is O3 // // See https://llvm.org/doxygen/CodeGen_8h_source.html pmb.set_optimization_level(OptimizationLevel::Aggressive); // TODO figure out how enabling these individually differs from // the broad "aggressive optimizations" setting. // fpm.add_reassociate_pass(); // fpm.add_basic_alias_analysis_pass(); // fpm.add_promote_memory_to_register_pass(); // fpm.add_cfg_simplification_pass(); // fpm.add_gvn_pass(); // TODO figure out why enabling any of these (even alone) causes LLVM to segfault // fpm.add_strip_dead_prototypes_pass(); // fpm.add_dead_arg_elimination_pass(); // fpm.add_function_inlining_pass(); // pmb.set_inliner_with_threshold(4); } } pmb.populate_function_pass_manager(&fpm); } #[allow(clippy::cognitive_complexity)] pub fn build_expr<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, scope: &Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, expr: &Expr<'a>, ) -> BasicValueEnum<'ctx> { use roc_mono::expr::Expr::*; match expr { Int(num) => env.context.i64_type().const_int(*num as u64, true).into(), Float(num) => env.context.f64_type().const_float(*num).into(), Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(), Cond { branch_symbol, pass: (pass_stores, pass_expr), fail: (fail_stores, fail_expr), ret_layout, .. } => { let pass = env.arena.alloc(Expr::Store(pass_stores, pass_expr)); let fail = env.arena.alloc(Expr::Store(fail_stores, fail_expr)); let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes); let cond_expr = load_symbol(env, scope, branch_symbol); match cond_expr { IntValue(value) => { // This is a call tobuild_basic_phi2, except inlined to prevent // problems with lifetimes and closures involving layout_ids. 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(value, then_block, else_block); // build then block builder.position_at_end(then_block); let then_val = build_expr(env, layout_ids, scope, parent, 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_expr(env, layout_ids, scope, parent, 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() } _ => panic!( "Tried to make a branch out of an invalid condition: cond_expr = {:?}", cond_expr, ), } } Switch { cond, branches, default_branch: (default_stores, default_expr), ret_layout, cond_layout, } => { let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes); let default_branch = env.arena.alloc(Expr::Store(default_stores, default_expr)); let mut combined = Vec::with_capacity_in(branches.len(), env.arena); for (int, stores, expr) in branches.iter() { combined.push((*int, Expr::Store(stores, expr))); } let switch_args = SwitchArgs { cond_layout: cond_layout.clone(), cond_expr: cond, branches: combined.into_bump_slice(), default_branch, ret_type, }; build_switch(env, layout_ids, scope, parent, switch_args) } Store(stores, ret) => { let mut scope = im_rc::HashMap::clone(scope); let context = &env.context; for (symbol, layout, expr) in stores.iter() { let val = build_expr(env, layout_ids, &scope, parent, &expr); let expr_bt = basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes); let alloca = create_entry_block_alloca( env, parent, expr_bt, symbol.ident_string(&env.interns), ); env.builder.build_store(alloca, val); // Make a new scope which includes the binding we just encountered. // This should be done *after* compiling the bound expr, since any // recursive (in the LetRec sense) bindings should already have // been extracted as procedures. Nothing in here should need to // access itself! scope = im_rc::HashMap::clone(&scope); scope.insert(*symbol, (layout.clone(), alloca)); } build_expr(env, layout_ids, &scope, parent, ret) } CallByName { name, layout, args } => { let mut arg_tuples: Vec<(BasicValueEnum, &'a Layout<'a>)> = Vec::with_capacity_in(args.len(), env.arena); for (arg, arg_layout) in args.iter() { arg_tuples.push((build_expr(env, layout_ids, scope, parent, arg), arg_layout)); } call_with_args( env, layout_ids, layout, *name, parent, arg_tuples.into_bump_slice(), ) } FunctionPointer(symbol, layout) => { let fn_name = layout_ids .get(*symbol, layout) .to_symbol_string(*symbol, &env.interns); let ptr = env .module .get_function(fn_name.as_str()) .unwrap_or_else(|| panic!("Could not get pointer to unknown function {:?}", symbol)) .as_global_value() .as_pointer_value(); BasicValueEnum::PointerValue(ptr) } CallByPointer(sub_expr, args, _var) => { let mut arg_vals: Vec = Vec::with_capacity_in(args.len(), env.arena); for arg in args.iter() { arg_vals.push(build_expr(env, layout_ids, scope, parent, arg)); } let call = match build_expr(env, layout_ids, scope, parent, sub_expr) { BasicValueEnum::PointerValue(ptr) => { env.builder.build_call(ptr, arg_vals.as_slice(), "tmp") } non_ptr => { panic!( "Tried to call by pointer, but encountered a non-pointer: {:?}", non_ptr ); } }; // TODO FIXME this should not be hardcoded! // Need to look up what calling convention is the right one for that function. // If this is an external-facing function, it'll use the C calling convention. // If it's an internal-only function, it should (someday) use the fast calling conention. call.set_call_convention(C_CALL_CONV); call.try_as_basic_value() .left() .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")) } Load(symbol) => load_symbol(env, scope, symbol), Str(str_literal) => { if str_literal.is_empty() { panic!("TODO build an empty string in LLVM"); } else { let ctx = env.context; let builder = env.builder; let str_len = str_literal.len() + 1/* TODO drop the +1 when we have structs and this is no longer a NUL-terminated CString.*/; let byte_type = ctx.i8_type(); let nul_terminator = byte_type.const_zero(); let len_val = ctx.i64_type().const_int(str_len as u64, false); let ptr = env .builder .build_array_malloc(ctx.i8_type(), len_val, "str_ptr") .unwrap(); // TODO check if malloc returned null; if so, runtime error for OOM! // Copy the bytes from the string literal into the array for (index, byte) in str_literal.bytes().enumerate() { let index_val = ctx.i64_type().const_int(index as u64, false); let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "byte") }; builder.build_store(elem_ptr, byte_type.const_int(byte as u64, false)); } // Add a NUL terminator at the end. // TODO: Instead of NUL-terminating, return a struct // with the pointer and also the length and capacity. let index_val = ctx.i64_type().const_int(str_len as u64 - 1, false); let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "nul_terminator") }; builder.build_store(elem_ptr, nul_terminator); BasicValueEnum::PointerValue(ptr) } } EmptyArray => { let struct_type = collection(env.context, 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()) } Array { elem_layout, elems } => { let ctx = env.context; let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let builder = env.builder; if elems.is_empty() { empty_list(env) } else { let len_u64 = elems.len() as u64; let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; let ptr = { let bytes_len = elem_bytes * len_u64; let len_type = env.ptr_int(); let len = len_type.const_int(bytes_len, false); env.builder .build_array_malloc(elem_type, len, "create_list_ptr") .unwrap() // TODO check if malloc returned null; if so, runtime error for OOM! }; // Copy the elements from the list literal into the array for (index, elem) in elems.iter().enumerate() { 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") }; let val = build_expr(env, layout_ids, &scope, parent, &elem); builder.build_store(elem_ptr, val); } 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 struct_type = collection(ctx, ptr_bytes); let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false)); 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(); // Bitcast to an array of raw bytes builder.build_bitcast( struct_val.into_struct_value(), collection(ctx, ptr_bytes), "cast_collection", ) } } Struct(sorted_fields) => { let ctx = env.context; let builder = env.builder; let ptr_bytes = env.ptr_bytes; // Determine types let num_fields = sorted_fields.len(); let mut field_types = Vec::with_capacity_in(num_fields, env.arena); let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); for (field_expr, field_layout) in sorted_fields.iter() { // Zero-sized fields have no runtime representation. // The layout of the struct expects them to be dropped! if field_layout.stack_size(ptr_bytes) != 0 { field_types.push(basic_type_from_layout( env.arena, env.context, &field_layout, env.ptr_bytes, )); field_vals.push(build_expr(env, layout_ids, &scope, parent, field_expr)); } } // If the record has only one field that isn't zero-sized, // unwrap it. This is what the layout expects us to do. if field_vals.len() == 1 { field_vals.pop().unwrap() } else { // Create the struct_type let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); let mut struct_val = struct_type.const_zero().into(); // Insert field exprs into struct_val for (index, field_val) in field_vals.into_iter().enumerate() { struct_val = builder .build_insert_value(struct_val, field_val, index as u32, "insert_field") .unwrap(); } BasicValueEnum::StructValue(struct_val.into_struct_value()) } } Tag { union_size, arguments, .. } if *union_size == 1 => { let it = arguments.iter(); let ctx = env.context; let ptr_bytes = env.ptr_bytes; let builder = env.builder; // Determine types let num_fields = arguments.len() + 1; let mut field_types = Vec::with_capacity_in(num_fields, env.arena); let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); for (field_expr, field_layout) in it { // Zero-sized fields have no runtime representation. // The layout of the struct expects them to be dropped! if field_layout.stack_size(ptr_bytes) != 0 { let val = build_expr(env, layout_ids, &scope, parent, field_expr); let field_type = basic_type_from_layout( env.arena, env.context, &field_layout, env.ptr_bytes, ); field_types.push(field_type); field_vals.push(val); } } // If the struct has only one field that isn't zero-sized, // unwrap it. This is what the layout expects us to do. if field_vals.len() == 1 { field_vals.pop().unwrap() } else { // Create the struct_type let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); let mut struct_val = struct_type.const_zero().into(); // Insert field exprs into struct_val for (index, field_val) in field_vals.into_iter().enumerate() { struct_val = builder .build_insert_value(struct_val, field_val, index as u32, "insert_field") .unwrap(); } BasicValueEnum::StructValue(struct_val.into_struct_value()) } } Tag { arguments, tag_layout, .. } => { let ptr_size = env.ptr_bytes; let whole_size = tag_layout.stack_size(ptr_size); let mut filler = tag_layout.stack_size(ptr_size); let ctx = env.context; let builder = env.builder; // Determine types let num_fields = arguments.len() + 1; let mut field_types = Vec::with_capacity_in(num_fields, env.arena); let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); for (field_expr, field_layout) in arguments.iter() { let field_size = field_layout.stack_size(ptr_size); // Zero-sized fields have no runtime representation. // The layout of the struct expects them to be dropped! if field_size != 0 { let val = build_expr(env, layout_ids, &scope, parent, field_expr); let field_type = basic_type_from_layout(env.arena, env.context, &field_layout, ptr_size); field_types.push(field_type); field_vals.push(val); filler -= field_size; } } // TODO verify that this is required (better safe than sorry) if filler > 0 { field_types.push(env.context.i8_type().array_type(filler).into()); } // Create the struct_type let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); let mut struct_val = struct_type.const_zero().into(); // Insert field exprs into struct_val for (index, field_val) in field_vals.into_iter().enumerate() { struct_val = builder .build_insert_value(struct_val, field_val, index as u32, "insert_field") .unwrap(); } // How we create tag values // // The memory layout of tags can be different. e.g. in // // [ Ok Int, Err Str ] // // the `Ok` tag stores a 64-bit integer, the `Err` tag stores a struct. // All tags of a union must have the same length, for easy addressing (e.g. array lookups). // So we need to ask for the maximum of all tag's sizes, even if most tags won't use // all that memory, and certainly won't use it in the same way (the tags have fields of // different types/sizes) // // In llvm, we must be explicit about the type of value we're creating: we can't just // make a unspecified block of memory. So what we do is create a byte array of the // desired size. Then when we know which tag we have (which is here, in this function), // we need to cast that down to the array of bytes that llvm expects // // There is the bitcast instruction, but it doesn't work for arrays. So we need to jump // through some hoops using store and load to get this to work: the array is put into a // one-element struct, which can be cast to the desired type. // // This tricks comes from // https://github.com/raviqqe/ssf/blob/bc32aae68940d5bddf5984128e85af75ca4f4686/ssf-llvm/src/expression_compiler.rs#L116 let array_type = ctx.i8_type().array_type(whole_size); let result = cast_basic_basic( builder, struct_val.into_struct_value().into(), array_type.into(), ); // For unclear reasons, we can't cast an array to a struct on the other side. // the solution is to wrap the array in a struct (yea...) let wrapper_type = ctx.struct_type(&[array_type.into()], false); let mut wrapper_val = wrapper_type.const_zero().into(); wrapper_val = builder .build_insert_value(wrapper_val, result, 0, "insert_field") .unwrap(); wrapper_val.into_struct_value().into() } AccessAtIndex { index, expr, is_unwrapped, .. } if *is_unwrapped => { use inkwell::values::BasicValueEnum::*; let builder = env.builder; // Get Struct val // Since this is a one-element tag union, we get the underlying value // right away. However, that struct might have only one field which // is not zero-sized, which would make it unwrapped. If that happens, // we must be match build_expr(env, layout_ids, &scope, parent, expr) { StructValue(argument) => builder .build_extract_value( argument, *index as u32, env.arena.alloc(format!("tag_field_access_{}_", index)), ) .unwrap(), other => { // If it's not a Struct, that means it was unwrapped, // so we should return it directly. other } } } AccessAtIndex { index, expr, field_layouts, .. } => { let builder = env.builder; // Determine types, assumes the descriminant is in the field layouts let num_fields = field_layouts.len(); let mut field_types = Vec::with_capacity_in(num_fields, env.arena); let ptr_bytes = env.ptr_bytes; for field_layout in field_layouts.iter() { let field_type = basic_type_from_layout(env.arena, env.context, &field_layout, ptr_bytes); field_types.push(field_type); } // Create the struct_type let struct_type = env .context .struct_type(field_types.into_bump_slice(), false); // cast the argument bytes into the desired shape for this tag let argument = build_expr(env, layout_ids, &scope, parent, expr).into_struct_value(); let struct_value = cast_struct_struct(builder, argument, struct_type); builder .build_extract_value(struct_value, *index as u32, "") .expect("desired field did not decode") } RuntimeErrorFunction(_) => { todo!("LLVM build runtime error function of {:?}", expr); } RuntimeError(_) => { todo!("LLVM build runtime error of {:?}", expr); } RunLowLevel(op, args) => run_low_level(env, layout_ids, scope, parent, *op, args), } } fn load_symbol<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, symbol: &Symbol, ) -> BasicValueEnum<'ctx> { match scope.get(symbol) { Some((_, ptr)) => env .builder .build_load(*ptr, symbol.ident_string(&env.interns)), None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope), } } /// Cast a struct to another struct of the same (or smaller?) size fn cast_struct_struct<'ctx>( builder: &Builder<'ctx>, from_value: StructValue<'ctx>, to_type: StructType<'ctx>, ) -> StructValue<'ctx> { cast_basic_basic(builder, from_value.into(), to_type.into()).into_struct_value() } /// Cast a value to another value of the same (or smaller?) size fn cast_basic_basic<'ctx>( builder: &Builder<'ctx>, from_value: BasicValueEnum<'ctx>, to_type: BasicTypeEnum<'ctx>, ) -> BasicValueEnum<'ctx> { use inkwell::types::BasicType; // store the value in memory let argument_pointer = builder.build_alloca(from_value.get_type(), ""); builder.build_store(argument_pointer, from_value); // then read it back as a different type let to_type_pointer = builder .build_bitcast( argument_pointer, to_type.ptr_type(inkwell::AddressSpace::Generic), "", ) .into_pointer_value(); builder.build_load(to_type_pointer, "") } fn extract_tag_discriminant<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, from_value: StructValue<'ctx>, ) -> IntValue<'ctx> { let struct_type = env .context .struct_type(&[env.context.i64_type().into()], false); let struct_value = cast_struct_struct(env.builder, from_value, struct_type); env.builder .build_extract_value(struct_value, 0, "") .expect("desired field did not decode") .into_int_value() } struct SwitchArgs<'a, 'ctx> { pub cond_expr: &'a Expr<'a>, pub cond_layout: Layout<'a>, pub branches: &'a [(u64, Expr<'a>)], pub default_branch: &'a Expr<'a>, pub ret_type: BasicTypeEnum<'ctx>, } fn build_switch<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, scope: &Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, switch_args: SwitchArgs<'a, 'ctx>, ) -> BasicValueEnum<'ctx> { let arena = env.arena; let builder = env.builder; let context = env.context; let SwitchArgs { branches, cond_expr, mut cond_layout, default_branch, ret_type, .. } = switch_args; let cont_block = context.append_basic_block(parent, "cont"); // Build the condition let cond = match cond_layout { Layout::Builtin(Builtin::Float64) => { // float matches are done on the bit pattern cond_layout = Layout::Builtin(Builtin::Int64); let full_cond = build_expr(env, layout_ids, scope, parent, cond_expr); builder .build_bitcast(full_cond, env.context.i64_type(), "") .into_int_value() } Layout::Union(_) => { // we match on the discriminant, not the whole Tag cond_layout = Layout::Builtin(Builtin::Int64); let full_cond = build_expr(env, layout_ids, scope, parent, cond_expr).into_struct_value(); extract_tag_discriminant(env, full_cond) } Layout::Builtin(_) => { build_expr(env, layout_ids, scope, parent, cond_expr).into_int_value() } other => todo!("Build switch value from layout: {:?}", other), }; // Build the cases let mut incoming = Vec::with_capacity_in(branches.len(), arena); let mut cases = Vec::with_capacity_in(branches.len(), arena); for (int, _) in branches.iter() { // Switch constants must all be same type as switch value! // e.g. this is incorrect, and will trigger a LLVM warning: // // switch i8 %apple1, label %default [ // i64 2, label %branch2 // i64 0, label %branch0 // i64 1, label %branch1 // ] // // they either need to all be i8, or i64 let int_val = match cond_layout { Layout::Builtin(Builtin::Int128) => context.i128_type().const_int(*int as u64, false), /* TODO file an issue: you can't currently have an int literal bigger than 64 bits long, and also (as we see here), you can't currently have (at least in Inkwell) a when-branch with an i128 literal in its pattren */ Layout::Builtin(Builtin::Int64) => context.i64_type().const_int(*int as u64, false), Layout::Builtin(Builtin::Int32) => context.i32_type().const_int(*int as u64, false), Layout::Builtin(Builtin::Int16) => context.i16_type().const_int(*int as u64, false), Layout::Builtin(Builtin::Int8) => context.i8_type().const_int(*int as u64, false), Layout::Builtin(Builtin::Int1) => context.bool_type().const_int(*int as u64, false), _ => panic!("Can't cast to cond_layout = {:?}", cond_layout), }; let block = context.append_basic_block(parent, format!("branch{}", int).as_str()); cases.push((int_val, block)); } let default_block = context.append_basic_block(parent, "default"); builder.build_switch(cond, default_block, &cases); for ((_, branch_expr), (_, block)) in branches.iter().zip(cases) { builder.position_at_end(block); let branch_val = build_expr(env, layout_ids, scope, parent, branch_expr); builder.build_unconditional_branch(cont_block); incoming.push((branch_val, block)); } // The block for the conditional's default branch. builder.position_at_end(default_block); let default_val = build_expr(env, layout_ids, scope, parent, default_branch); builder.build_unconditional_branch(cont_block); incoming.push((default_val, default_block)); // emit merge block builder.position_at_end(cont_block); let phi = builder.build_phi(ret_type, "branch"); for (branch_val, block) in incoming { phi.add_incoming(&[(&Into::::into(branch_val), block)]); } phi.as_basic_value() } 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() } /// TODO could this be added to Inkwell itself as a method on BasicValueEnum? fn set_name(bv_enum: BasicValueEnum<'_>, name: &str) { match bv_enum { ArrayValue(val) => val.set_name(name), IntValue(val) => val.set_name(name), FloatValue(val) => val.set_name(name), PointerValue(val) => val.set_name(name), StructValue(val) => val.set_name(name), VectorValue(val) => val.set_name(name), } } /// Creates a new stack allocation instruction in the entry block of the function. pub fn create_entry_block_alloca<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, parent: FunctionValue<'_>, basic_type: BasicTypeEnum<'ctx>, name: &str, ) -> PointerValue<'ctx> { let builder = env.context.create_builder(); let entry = parent.get_first_basic_block().unwrap(); match entry.get_first_instruction() { Some(first_instr) => builder.position_before(&first_instr), None => builder.position_at_end(entry), } builder.build_alloca(basic_type, name) } pub fn build_proc_header<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, symbol: Symbol, layout: &Layout<'a>, proc: &Proc<'a>, ) -> (FunctionValue<'ctx>, Vec<'a, BasicTypeEnum<'ctx>>) { let args = proc.args; let arena = env.arena; let context = &env.context; let ret_type = basic_type_from_layout(arena, context, &proc.ret_layout, env.ptr_bytes); let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); let mut arg_symbols = Vec::new_in(arena); for (layout, arg_symbol) in args.iter() { let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes); arg_basic_types.push(arg_type); arg_symbols.push(arg_symbol); } let fn_type = get_fn_type(&ret_type, &arg_basic_types); let fn_name = layout_ids .get(symbol, layout) .to_symbol_string(symbol, &env.interns); let fn_val = env .module .add_function(fn_name.as_str(), fn_type, Some(Linkage::Private)); fn_val.set_call_conventions(fn_val.get_call_conventions()); (fn_val, arg_basic_types) } pub fn build_proc<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, proc: Proc<'a>, fn_val: FunctionValue<'ctx>, arg_basic_types: Vec<'a, BasicTypeEnum<'ctx>>, ) { let args = proc.args; let context = &env.context; // Add a basic block for the entry point let entry = context.append_basic_block(fn_val, "entry"); let builder = env.builder; builder.position_at_end(entry); let mut scope = ImMap::default(); // Add args to scope for ((arg_val, arg_type), (layout, arg_symbol)) in fn_val.get_param_iter().zip(arg_basic_types).zip(args) { set_name(arg_val, arg_symbol.ident_string(&env.interns)); let alloca = create_entry_block_alloca(env, fn_val, arg_type, arg_symbol.ident_string(&env.interns)); builder.build_store(alloca, arg_val); scope.insert(*arg_symbol, (layout.clone(), alloca)); } let body = build_expr(env, layout_ids, &scope, fn_val, &proc.body); builder.build_return(Some(&body)); } pub fn verify_fn(fn_val: FunctionValue<'_>) { if !fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) { unsafe { fn_val.delete(); } panic!("Invalid generated fn_val.") } } /// List.single : a -> List a fn list_single<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, elem: BasicValueEnum<'ctx>, elem_layout: &Layout<'a>, ) -> BasicValueEnum<'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 ptr = { let bytes_len = elem_bytes; let len_type = env.ptr_int(); let len = len_type.const_int(bytes_len, false); env.builder .build_array_malloc(elem_type, len, "create_list_ptr") .unwrap() // TODO check if malloc returned null; if so, runtime error for OOM! }; // 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); 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 struct_type = collection(ctx, ptr_bytes); let len = BasicValueEnum::IntValue(env.ptr_int().const_int(1, false)); 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", ) } /// List.repeat : Int, elem -> List elem fn list_repeat<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, list_len: IntValue<'ctx>, elem: BasicValueEnum<'ctx>, elem_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let ctx = env.context; let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); // 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 = builder .build_array_malloc(elem_type, list_len, "create_list_ptr") .unwrap(); // 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); 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, list_len, Builtin::WRAPPER_LEN, "insert_len") .unwrap(); builder.build_bitcast( struct_val.into_struct_value(), collection(ctx, ptr_bytes), "cast_collection", ) }; 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), ) } #[inline(always)] #[allow(clippy::cognitive_complexity)] fn call_with_args<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, layout: &Layout<'a>, symbol: Symbol, _parent: FunctionValue<'ctx>, args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)], ) -> BasicValueEnum<'ctx> { let fn_name = layout_ids .get(symbol, layout) .to_symbol_string(symbol, &env.interns); let fn_val = env .module .get_function(fn_name.as_str()) .unwrap_or_else(|| { if symbol.is_builtin() { panic!("Unrecognized builtin function: {:?}", symbol) } else { panic!("Unrecognized non-builtin function: {:?}", symbol) } }); let mut arg_vals: Vec = Vec::with_capacity_in(args.len(), env.arena); for (arg, _layout) in args.iter() { arg_vals.push(*arg); } let call = env .builder .build_call(fn_val, arg_vals.into_bump_slice(), "call"); call.set_call_convention(fn_val.get_call_conventions()); call.try_as_basic_value() .left() .unwrap_or_else(|| panic!("LLVM error: Invalid call by name for name {:?}", symbol)) } fn call_intrinsic<'a, 'ctx, 'env>( intrinsic_name: &'static str, env: &Env<'a, 'ctx, 'env>, args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)], ) -> BasicValueEnum<'ctx> { let fn_val = env .module .get_function(intrinsic_name) .unwrap_or_else(|| panic!("Unrecognized intrinsic function: {}", intrinsic_name)); let mut arg_vals: Vec = Vec::with_capacity_in(args.len(), env.arena); for (arg, _layout) in args.iter() { arg_vals.push(*arg); } let call = env .builder .build_call(fn_val, arg_vals.into_bump_slice(), "call"); call.set_call_convention(fn_val.get_call_conventions()); call.try_as_basic_value().left().unwrap_or_else(|| { panic!( "LLVM error: Invalid call by name for intrinsic {}", intrinsic_name ) }) } pub fn load_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() } fn list_is_not_empty<'ctx>( builder: &Builder<'ctx>, ctx: &'ctx Context, list_len: IntValue<'ctx>, ) -> IntValue<'ctx> { builder.build_int_compare( IntPredicate::UGT, list_len, ctx.i64_type().const_int(0, false), "greaterthanzero", ) } 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") } fn clone_nonempty_list<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, 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, "mul_len_by_elem_bytes"); // Allocate space for the new array that we'll copy into. let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let clone_ptr = builder .build_array_malloc(elem_type, list_len, "list_ptr") .unwrap(); 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); } 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) } enum InPlace { InPlace, Clone, } 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()) } 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.push List elem, elem -> List elem fn list_append<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, 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 = load_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 = builder .build_array_malloc(elem_type, new_list_len, "list_ptr") .unwrap(); 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! 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); } 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, new_list_len, Builtin::WRAPPER_LEN, "insert_len") .unwrap(); let elem_ptr = unsafe { builder.build_in_bounds_gep(clone_ptr, &[list_len], "load_index") }; builder.build_store(elem_ptr, elem); builder.build_bitcast( struct_val.into_struct_value(), collection(ctx, ptr_bytes), "cast_collection", ) } /// List.join : List (List elem) -> List elem fn list_join<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, outer_list_wrapper: StructValue<'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(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_len = load_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) }; let dest_elem_ptr_alloca = builder.build_alloca(elem_ptr_type, "dest_elem"); // 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 = builder.build_int_compare( IntPredicate::UGT, outer_list_len, ctx.i64_type().const_int(0, false), "greaterthanzero", ); 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 index_name = "#index"; let index_alloca = builder.build_alloca(ctx.i64_type(), index_name); // index in the outer list, pointing to a inner list let outer_list_index = ctx.i64_type().const_int(0, false); builder.build_store(index_alloca, outer_list_index); let outer_loop_bb = ctx.append_basic_block(parent, "loop"); builder.build_unconditional_branch(outer_loop_bb); builder.position_at_end(outer_loop_bb); // #index = #index + 1 let curr_index = builder .build_load(index_alloca, index_name) .into_int_value(); let next_index = builder.build_int_add( curr_index, ctx.i64_type().const_int(1, false), "nextindex", ); builder.build_store(index_alloca, next_index); let inner_list_wrapper_ptr = unsafe { builder.build_in_bounds_gep(outer_list_ptr, &[curr_index], "load_index") }; let inner_list = builder.build_load(inner_list_wrapper_ptr, "inner_list"); let inner_list_len = load_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); // #index < outer_list_len let outer_loop_end_cond = builder.build_int_compare( IntPredicate::ULT, next_index, outer_list_len, "loopcond", ); let after_outer_loop_bb = ctx.append_basic_block(parent, "after_outer_loop"); builder.build_conditional_branch( outer_loop_end_cond, outer_loop_bb, after_outer_loop_bb, ); builder.position_at_end(after_outer_loop_bb); } let final_list_sum = builder .build_load(list_len_sum_alloca, list_len_sum_name) .into_int_value(); let final_list_ptr = builder .build_array_malloc(elem_type, final_list_sum, "final_list_sum") .unwrap(); builder.build_store(dest_elem_ptr_alloca, final_list_ptr); // Element inserting loop { let index_name = "#index"; let index_alloca = builder.build_alloca(ctx.i64_type(), index_name); // index in the outer list, pointing to a inner list let outer_list_index = ctx.i64_type().const_int(0, false); builder.build_store(index_alloca, outer_list_index); let outer_loop_bb = ctx.append_basic_block(parent, "loop"); builder.build_unconditional_branch(outer_loop_bb); builder.position_at_end(outer_loop_bb); // #index = #index + 1 let curr_index = builder .build_load(index_alloca, index_name) .into_int_value(); let next_index = builder.build_int_add( curr_index, ctx.i64_type().const_int(1, false), "nextindex", ); builder.build_store(index_alloca, next_index); // The pointer to the list in the outer list (the list of lists) let (inner_list_len, inner_list_ptr) = { let wrapper_ptr = unsafe { builder.build_in_bounds_gep(outer_list_ptr, &[curr_index], "load_index") }; let wrapper = builder .build_load(wrapper_ptr, "inner_list_wrapper") .into_struct_value(); let elem_ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); ( load_list_len(builder, wrapper), load_list_ptr(builder, wrapper, elem_ptr_type), ) }; // Inner Loop { let dest_elem_ptr = builder .build_load(dest_elem_ptr_alloca, "load_dest_elem") .into_pointer_value(); let inner_index_name = "#inner_index"; let inner_index_alloca = builder.build_alloca(ctx.i64_type(), inner_index_name); let inner_list_index = ctx.i64_type().const_int(0, false); builder.build_store(inner_index_alloca, inner_list_index); let inner_loop_bb = ctx.append_basic_block(parent, "loop"); builder.build_unconditional_branch(inner_loop_bb); builder.position_at_end(inner_loop_bb); // #index = #index + 1 let curr_index = builder .build_load(inner_index_alloca, inner_index_name) .into_int_value(); let next_index = builder.build_int_add( curr_index, ctx.i64_type().const_int(1, false), "nextindex", ); builder.build_store(inner_index_alloca, next_index); let src_elem_ptr = unsafe { builder.build_in_bounds_gep(inner_list_ptr, &[curr_index], "load_index") }; let src_elem = builder.build_load(src_elem_ptr, "get_elem"); // TODO clone src_elem builder.build_store(dest_elem_ptr, src_elem); let next_dest_elem_ptr = unsafe { builder.build_in_bounds_gep( dest_elem_ptr, &[env.ptr_int().const_int(1, false)], "increment_dest_elem", ) }; builder.build_store(dest_elem_ptr_alloca, next_dest_elem_ptr); let inner_loop_end_cond = builder.build_int_compare( IntPredicate::ULT, next_index, inner_list_len, "loopcond", ); let after_inner_loop_bb = ctx.append_basic_block(parent, "after_inner_loop"); builder.build_conditional_branch( inner_loop_end_cond, inner_loop_bb, after_inner_loop_bb, ); builder.position_at_end(after_inner_loop_bb); } // #index < outer_list_len let outer_loop_end_cond = builder.build_int_compare( IntPredicate::ULT, next_index, outer_list_len, "loopcond", ); let after_outer_loop_bb = ctx.append_basic_block(parent, "after_outer_loop"); builder.build_conditional_branch( outer_loop_end_cond, outer_loop_bb, after_outer_loop_bb, ); builder.position_at_end(after_outer_loop_bb); let ptr_bytes = env.ptr_bytes; let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int(final_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, final_list_sum, Builtin::WRAPPER_LEN, "insert_len", ) .unwrap(); builder.build_bitcast( struct_val.into_struct_value(), collection(ctx, ptr_bytes), "cast_collection", ) } }; 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.prepend List elem, elem -> List elem fn list_prepend<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, 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 = load_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; // Allocate space for the new array that we'll copy into. let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let clone_ptr = builder .build_array_malloc(elem_type, new_list_len, "list_ptr") .unwrap(); let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr"); 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, list_len, "mul_old_len_by_elem_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); } 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, new_list_len, Builtin::WRAPPER_LEN, "insert_len") .unwrap(); builder.build_bitcast( struct_val.into_struct_value(), collection(ctx, ptr_bytes), "cast_collection", ) } fn list_set<'a, 'ctx, 'env>( parent: FunctionValue<'ctx>, args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)], env: &Env<'a, 'ctx, 'env>, in_place: InPlace, ) -> BasicValueEnum<'ctx> { // List.set : List elem, Int, elem -> List elem 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 = load_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 in_place { InPlace::InPlace => ( original_wrapper, load_list_ptr(builder, original_wrapper, ptr_type), ), InPlace::Clone => clone_nonempty_list( env, 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(), ) } /// Translates a target_lexicon::Triple to a LLVM calling convention u32 /// as described in https://llvm.org/doxygen/namespacellvm_1_1CallingConv.html pub fn get_call_conventions(cc: CallingConvention) -> u32 { use CallingConvention::*; // For now, we're returning 0 for the C calling convention on all of these. // Not sure if we should be picking something more specific! match cc { SystemV => C_CALL_CONV, WasmBasicCAbi => C_CALL_CONV, WindowsFastcall => C_CALL_CONV, } } /// Source: https://llvm.org/doxygen/namespacellvm_1_1CallingConv.html pub static C_CALL_CONV: u32 = 0; pub static COLD_CALL_CONV: u32 = 9; fn run_low_level<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, scope: &Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, op: LowLevel, args: &[(Expr<'a>, Layout<'a>)], ) -> BasicValueEnum<'ctx> { use LowLevel::*; match op { ListLen => { // List.len : List * -> Int debug_assert_eq!(args.len(), 1); let arg = build_expr(env, layout_ids, scope, parent, &args[0].0); load_list_len(env.builder, arg.into_struct_value()).into() } ListSingle => { // List.single : a -> List a debug_assert_eq!(args.len(), 1); let arg = build_expr(env, layout_ids, scope, parent, &args[0].0); list_single(env, arg, &args[0].1) } ListRepeat => { // List.repeat : Int, elem -> List elem debug_assert_eq!(args.len(), 2); let list_len = build_expr(env, layout_ids, scope, parent, &args[0].0).into_int_value(); let elem = build_expr(env, layout_ids, scope, parent, &args[1].0); let elem_layout = &args[1].1; list_repeat(env, parent, list_len, elem, elem_layout) } ListReverse => { // List.reverse : List elem -> List elem debug_assert_eq!(args.len(), 1); let (list, list_layout) = &args[0]; match list_layout { Layout::Builtin(Builtin::List(elem_layout)) => { let wrapper_struct = build_expr(env, layout_ids, scope, parent, list).into_struct_value(); let builder = env.builder; let ctx = env.context; let list_len = load_list_len(builder, wrapper_struct); // 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 = builder.build_int_compare( IntPredicate::UGT, list_len, ctx.i64_type().const_int(0, false), "greaterthanzero", ); let build_then = || { // Allocate space for the new array that we'll copy into. 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 reversed_list_ptr = env .builder .build_array_malloc(elem_type, list_len, "create_reversed_list_ptr") .unwrap(); // 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 list_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); // The pointer to the element in the input list let elem_ptr = unsafe { builder.build_in_bounds_gep(list_ptr, &[curr_index], "load_index") }; // The pointer to the element in the reversed list let reverse_elem_ptr = unsafe { builder.build_in_bounds_gep( reversed_list_ptr, &[builder.build_int_sub( list_len, builder.build_int_add( curr_index, ctx.i64_type().const_int(1, false), "curr_index_plus_one", ), "next_index", )], "load_index_reversed_list", ) }; let elem = builder.build_load(elem_ptr, "get_elem"); // Mutate the new array in-place to change the element. builder.build_store(reverse_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); let ptr_bytes = env.ptr_bytes; let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int(reversed_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, list_len, Builtin::WRAPPER_LEN, "insert_len", ) .unwrap(); builder.build_bitcast( struct_val.into_struct_value(), collection(ctx, ptr_bytes), "cast_collection", ) }; 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), ) } Layout::Builtin(Builtin::EmptyList) => empty_list(env), _ => { unreachable!("Invalid List layout for List.reverse {:?}", list_layout); } } } ListConcat => list_concat(env, layout_ids, scope, parent, args), ListAppend => { // List.append : List elem, elem -> List elem debug_assert_eq!(args.len(), 2); let original_wrapper = build_expr(env, layout_ids, scope, parent, &args[0].0).into_struct_value(); let elem = build_expr(env, layout_ids, scope, parent, &args[1].0); let elem_layout = &args[1].1; list_append(env, original_wrapper, elem, elem_layout) } ListPrepend => { // List.prepend : List elem, elem -> List elem debug_assert_eq!(args.len(), 2); let original_wrapper = build_expr(env, layout_ids, scope, parent, &args[0].0).into_struct_value(); let elem = build_expr(env, layout_ids, scope, parent, &args[1].0); let elem_layout = &args[1].1; list_prepend(env, original_wrapper, elem, elem_layout) } ListJoin => { // List.join : List (List elem) -> List elem debug_assert_eq!(args.len(), 1); let (list, outer_list_layout) = &args[0]; let outer_wrapper_struct = build_expr(env, layout_ids, scope, parent, list).into_struct_value(); list_join(env, parent, outer_wrapper_struct, outer_list_layout) } NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumToFloat => { debug_assert_eq!(args.len(), 1); let arg = build_expr(env, layout_ids, scope, parent, &args[0].0); let arg_layout = &args[0].1; match arg_layout { Layout::Builtin(arg_builtin) => { use roc_mono::layout::Builtin::*; match arg_builtin { Int128 | Int64 | Int32 | Int16 | Int8 => { build_int_unary_op(env, arg.into_int_value(), arg_layout, op) } Float128 | Float64 | Float32 | Float16 => { build_float_unary_op(env, arg.into_float_value(), arg_layout, op) } _ => { unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, arg_layout); } } } _ => { unreachable!( "Compiler bug: tried to run numeric operation {:?} on invalid layout: {:?}", op, arg_layout ); } } } NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked | NumDivUnchecked => { debug_assert_eq!(args.len(), 2); let lhs_arg = build_expr(env, layout_ids, scope, parent, &args[0].0); let lhs_layout = &args[0].1; let rhs_arg = build_expr(env, layout_ids, scope, parent, &args[1].0); let rhs_layout = &args[1].1; match (lhs_layout, rhs_layout) { (Layout::Builtin(lhs_builtin), Layout::Builtin(rhs_builtin)) if lhs_builtin == rhs_builtin => { use roc_mono::layout::Builtin::*; match lhs_builtin { Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop( env, lhs_arg.into_int_value(), lhs_layout, rhs_arg.into_int_value(), rhs_layout, op, ), Float128 | Float64 | Float32 | Float16 => build_float_binop( env, lhs_arg.into_float_value(), lhs_layout, rhs_arg.into_float_value(), rhs_layout, op, ), _ => { unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, lhs_layout); } } } _ => { unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid layouts. The 2 layouts were: ({:?}) and ({:?})", op, lhs_layout, rhs_layout); } } } Eq => { debug_assert_eq!(args.len(), 2); let lhs_arg = build_expr(env, layout_ids, scope, parent, &args[0].0); let lhs_layout = &args[0].1; let rhs_arg = build_expr(env, layout_ids, scope, parent, &args[1].0); let rhs_layout = &args[1].1; build_eq(env, lhs_arg, rhs_arg, lhs_layout, rhs_layout) } NotEq => { debug_assert_eq!(args.len(), 2); let lhs_arg = build_expr(env, layout_ids, scope, parent, &args[0].0); let lhs_layout = &args[0].1; let rhs_arg = build_expr(env, layout_ids, scope, parent, &args[1].0); let rhs_layout = &args[1].1; build_neq(env, lhs_arg, rhs_arg, lhs_layout, rhs_layout) } And => { // The (&&) operator debug_assert_eq!(args.len(), 2); let lhs_arg = build_expr(env, layout_ids, scope, parent, &args[0].0); let rhs_arg = build_expr(env, layout_ids, scope, parent, &args[1].0); let bool_val = env.builder.build_and( lhs_arg.into_int_value(), rhs_arg.into_int_value(), "bool_and", ); BasicValueEnum::IntValue(bool_val) } Or => { // The (||) operator debug_assert_eq!(args.len(), 2); let lhs_arg = build_expr(env, layout_ids, scope, parent, &args[0].0); let rhs_arg = build_expr(env, layout_ids, scope, parent, &args[1].0); let bool_val = env.builder.build_or( lhs_arg.into_int_value(), rhs_arg.into_int_value(), "bool_or", ); BasicValueEnum::IntValue(bool_val) } Not => { // The (!) operator debug_assert_eq!(args.len(), 1); let arg = build_expr(env, layout_ids, scope, parent, &args[0].0); let bool_val = env.builder.build_not(arg.into_int_value(), "bool_not"); BasicValueEnum::IntValue(bool_val) } ListGetUnsafe => { // List.get : List elem, Int -> [ Ok elem, OutOfBounds ]* debug_assert_eq!(args.len(), 2); let builder = env.builder; let (_, list_layout) = &args[0]; let wrapper_struct = build_expr(env, layout_ids, scope, parent, &args[0].0).into_struct_value(); let elem_index = build_expr(env, layout_ids, scope, parent, &args[1].0).into_int_value(); 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 ); } } } ListSet => list_set( parent, &[ ( build_expr(env, layout_ids, scope, parent, &args[0].0), &args[0].1, ), ( build_expr(env, layout_ids, scope, parent, &args[1].0), &args[1].1, ), ( build_expr(env, layout_ids, scope, parent, &args[2].0), &args[2].1, ), ], env, InPlace::Clone, ), ListSetInPlace => list_set( parent, &[ ( build_expr(env, layout_ids, scope, parent, &args[0].0), &args[0].1, ), ( build_expr(env, layout_ids, scope, parent, &args[1].0), &args[1].1, ), ( build_expr(env, layout_ids, scope, parent, &args[2].0), &args[2].1, ), ], env, InPlace::InPlace, ), } } fn build_int_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, lhs: IntValue<'ctx>, _lhs_layout: &Layout<'a>, rhs: IntValue<'ctx>, _rhs_layout: &Layout<'a>, op: LowLevel, ) -> BasicValueEnum<'ctx> { use inkwell::IntPredicate::*; use roc_module::low_level::LowLevel::*; let bd = env.builder; match op { NumAdd => bd.build_int_add(lhs, rhs, "add_int").into(), NumSub => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumMul => bd.build_int_mul(lhs, rhs, "mul_int").into(), NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(), NumGte => bd.build_int_compare(SGE, lhs, rhs, "int_gte").into(), NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(), NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(), NumRemUnchecked => bd.build_int_signed_rem(lhs, rhs, "rem_int").into(), NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(), _ => { unreachable!("Unrecognized int binary operation: {:?}", op); } } } fn list_concat<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, scope: &Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, args: &[(Expr<'a>, Layout<'a>)], ) -> BasicValueEnum<'ctx> { // List.concat : List elem, List elem -> List elem debug_assert_eq!(args.len(), 2); // This implementation is quite long, let me explain what is complicating it. Here are our // contraints: // // constraint 1. lists might be empty because they have the layout `EmptyList`, or they might // be empty because they have a `List` layout, but happen to be empty, such as in this code: // // list : List Int // list = // [] // // So we have two sources of truth for emptiness. // // constraint 2. iterating over a non-empty list involves allocating memory for a index and // a loop, and allocating memory is costly, so we dont want to even try to iterate over empty // lists. // // Accounting for all the possibilities in the two constraints above gives us 9 code paths: // // first list EmptyList List(list) List(list) // second list where list.length = 0 where list.length > 0 // --------------------------------------------------------------------- // EmptyList | [] | [] | clone(1st_list) | // --------------------------------------------------------------------- // List(list) | [] | [] | clone(1st_list) | // where list.length = 0 | | | | // --------------------------------------------------------------------- // List(list) | clone(2nd_list) | clone(2nd_list) | 2nd_list ++ 1st_list | // where list.length > 0 | | | | // --------------------------------------------------------------------- // let builder = env.builder; let ctx = env.context; let (first_list, first_list_layout) = &args[0]; let (second_list, second_list_layout) = &args[1]; let second_list_wrapper = build_expr(env, layout_ids, scope, parent, second_list).into_struct_value(); let second_list_len = load_list_len(builder, second_list_wrapper); match first_list_layout { Layout::Builtin(Builtin::EmptyList) => { match second_list_layout { Layout::Builtin(Builtin::EmptyList) => empty_list(env), Layout::Builtin(Builtin::List(elem_layout)) => { // THIS IS A COPY AND PASTE // All the code under the Layout::Builtin(Builtin::List()) match branch // is the same as what is under `if_first_list_is_empty`. Re-using // `if_first_list_is_empty` here however, creates memory problems. // 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(builder, ctx, 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, 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)), ) } _ => { unreachable!( "Invalid List layout for second input list of List.concat: {:?}", second_list_layout ); } } } Layout::Builtin(Builtin::List(elem_layout)) => { let first_list_wrapper = build_expr(env, layout_ids, scope, parent, first_list).into_struct_value(); let first_list_len = load_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(builder, ctx, first_list_len); 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 elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let if_second_list_is_empty = || { let (new_wrapper, _) = clone_nonempty_list( env, first_list_len, load_list_ptr(builder, first_list_wrapper, ptr_type), elem_layout, ); BasicValueEnum::StructValue(new_wrapper) }; match second_list_layout { Layout::Builtin(Builtin::EmptyList) => { let (new_wrapper, _) = clone_nonempty_list( env, first_list_len, load_list_ptr(builder, first_list_wrapper, ptr_type), elem_layout, ); BasicValueEnum::StructValue(new_wrapper) } Layout::Builtin(Builtin::List(_)) => { // 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(builder, ctx, 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 = env .builder .build_array_malloc( elem_type, combined_list_len, "create_combined_list_ptr", ) .unwrap(); let index_name = "#index"; let index_alloca = builder.build_alloca(ctx.i64_type(), index_name); // The index variable begins at 0 and increments // on each iteration of the loop builder.build_store(index_alloca, ctx.i64_type().const_int(0, false)); // FIRST LOOP { let first_loop_bb = ctx.append_basic_block(parent, "first_list_concat_loop"); builder.build_unconditional_branch(first_loop_bb); builder.position_at_end(first_loop_bb); let curr_first_loop_index = builder .build_load(index_alloca, index_name) .into_int_value(); let first_list_ptr = load_list_ptr(builder, first_list_wrapper, ptr_type); // The pointer to the element in the first list let first_list_elem_ptr = unsafe { builder.build_in_bounds_gep( first_list_ptr, &[curr_first_loop_index], "load_index", ) }; // The pointer to the element in the combined list let combined_list_elem_ptr = unsafe { builder.build_in_bounds_gep( combined_list_ptr, &[curr_first_loop_index], "load_index_combined_list", ) }; let first_list_elem = builder.build_load(first_list_elem_ptr, "get_elem"); // Mutate the new array in-place to change the element. builder.build_store(combined_list_elem_ptr, first_list_elem); // #index = #index + 1 let next_first_loop_index = builder.build_int_add( curr_first_loop_index, ctx.i64_type().const_int(1, false), "nextindex", ); builder.build_store(index_alloca, next_first_loop_index); // #index < first_list_len let first_loop_end_cond = builder.build_int_compare( IntPredicate::ULT, next_first_loop_index, first_list_len, "loopcond", ); let after_first_loop_bb = ctx.append_basic_block(parent, "after_first_loop"); builder.build_conditional_branch( first_loop_end_cond, first_loop_bb, after_first_loop_bb, ); builder.position_at_end(after_first_loop_bb); } // Reset the index variable to 0 builder.build_store(index_alloca, ctx.i64_type().const_int(0, false)); // SECOND LOOP { let second_loop_bb = ctx.append_basic_block(parent, "second_list_concat_loop"); builder.build_unconditional_branch(second_loop_bb); builder.position_at_end(second_loop_bb); let curr_second_index = builder .build_load(index_alloca, index_name) .into_int_value(); let second_list_ptr = load_list_ptr(builder, second_list_wrapper, ptr_type); // The pointer to the element in the second list let second_list_elem_ptr = unsafe { builder.build_in_bounds_gep( second_list_ptr, &[curr_second_index], "load_index", ) }; // 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, &[curr_second_index], "load_index_combined_list", ) }; let second_list_elem = builder.build_load(second_list_elem_ptr, "get_elem"); // Mutate the new array in-place to change the element. builder.build_store(combined_list_elem_ptr, second_list_elem); // #index = #index + 1 let next_second_index = builder.build_int_add( curr_second_index, ctx.i64_type().const_int(1, false), "increment_index", ); builder.build_store(index_alloca, next_second_index); // #index < second_list_len let second_loop_end_cond = builder.build_int_compare( IntPredicate::ULT, next_second_index, second_list_len, "loopcond", ); let after_second_loop_bb = ctx.append_basic_block(parent, "after_second_loop"); builder.build_conditional_branch( second_loop_end_cond, second_loop_bb, after_second_loop_bb, ); builder.position_at_end(after_second_loop_bb); let ptr_bytes = env.ptr_bytes; let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int( combined_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, combined_list_len, Builtin::WRAPPER_LEN, "insert_len", ) .unwrap(); builder.build_bitcast( struct_val.into_struct_value(), collection(ctx, ptr_bytes), "cast_collection", ) } }; 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)), ) } _ => { unreachable!( "Invalid List layout for second input list of List.concat: {:?}", second_list_layout ); } } }; let if_first_list_is_empty = || { match second_list_layout { Layout::Builtin(Builtin::EmptyList) => empty_list(env), Layout::Builtin(Builtin::List(elem_layout)) => { // 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(builder, ctx, 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, 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)), ) } _ => { unreachable!( "Invalid List layout for second input list of List.concat: {:?}", second_list_layout ); } } }; 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 : {:?}", first_list_layout ); } } } fn build_float_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, lhs: FloatValue<'ctx>, _lhs_layout: &Layout<'a>, rhs: FloatValue<'ctx>, _rhs_layout: &Layout<'a>, op: LowLevel, ) -> BasicValueEnum<'ctx> { use inkwell::FloatPredicate::*; use roc_module::low_level::LowLevel::*; let bd = env.builder; match op { NumAdd => bd.build_float_add(lhs, rhs, "add_float").into(), NumSub => bd.build_float_sub(lhs, rhs, "sub_float").into(), NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(), NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(), NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(), NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(), NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(), NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(), NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(), _ => { unreachable!("Unrecognized int binary operation: {:?}", op); } } } fn build_int_unary_op<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, arg: IntValue<'ctx>, arg_layout: &Layout<'a>, op: LowLevel, ) -> BasicValueEnum<'ctx> { use roc_module::low_level::LowLevel::*; let bd = env.builder; match op { NumNeg => bd.build_int_neg(arg, "negate_int").into(), NumAbs => { // This is how libc's abs() is implemented - it uses no branching! // // abs = \arg -> // shifted = arg >>> 63 // // (xor arg shifted) - shifted let ctx = env.context; let shifted_name = "abs_shift_right"; let shifted_alloca = { let bits_to_shift = ((arg_layout.stack_size(env.ptr_bytes) as u64) * 8) - 1; let shift_val = ctx.i64_type().const_int(bits_to_shift, false); let shifted = bd.build_right_shift(arg, shift_val, true, shifted_name); let alloca = bd.build_alloca( basic_type_from_layout(env.arena, ctx, arg_layout, env.ptr_bytes), "#int_abs_help", ); // shifted = arg >>> 63 bd.build_store(alloca, shifted); alloca }; let xored_arg = bd.build_xor( arg, bd.build_load(shifted_alloca, shifted_name).into_int_value(), "xor_arg_shifted", ); BasicValueEnum::IntValue(bd.build_int_sub( xored_arg, bd.build_load(shifted_alloca, shifted_name).into_int_value(), "sub_xored_shifted", )) } NumToFloat => { // TODO specialize this to be not just for i64! let builtin_fn_name = "i64_to_f64_"; let fn_val = env .module .get_function(builtin_fn_name) .unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", builtin_fn_name)); let call = env .builder .build_call(fn_val, &[arg.into()], "call_builtin"); call.set_call_convention(fn_val.get_call_conventions()); call.try_as_basic_value() .left() .unwrap_or_else(|| panic!("LLVM error: Invalid call for low-level op {:?}", op)) } _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } } } fn build_float_unary_op<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, arg: FloatValue<'ctx>, arg_layout: &Layout<'a>, op: LowLevel, ) -> BasicValueEnum<'ctx> { use roc_module::low_level::LowLevel::*; let bd = env.builder; match op { NumNeg => bd.build_float_neg(arg, "negate_float").into(), NumAbs => call_intrinsic(LLVM_FABS_F64, env, &[(arg.into(), arg_layout)]), NumSqrtUnchecked => call_intrinsic(LLVM_SQRT_F64, env, &[(arg.into(), arg_layout)]), NumRound => call_intrinsic(LLVM_LROUND_I64_F64, env, &[(arg.into(), arg_layout)]), NumSin => call_intrinsic(LLVM_SIN_F64, env, &[(arg.into(), arg_layout)]), NumCos => call_intrinsic(LLVM_COS_F64, env, &[(arg.into(), arg_layout)]), NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */ _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } } }