diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 39d089bf67..0fe7cf0a9a 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -510,7 +510,7 @@ pub fn types() -> MutMap { ), ); - // push : List elem -> elem -> List elem + // push : List elem, elem -> List elem add_type( Symbol::LIST_PUSH, SolvedType::Func( @@ -519,6 +519,15 @@ pub fn types() -> MutMap { ), ); + // prepend : List elem, elem -> List elem + add_type( + Symbol::LIST_PREPEND, + SolvedType::Func( + vec![list_type(flex(TVAR1)), flex(TVAR1)], + Box::new(list_type(flex(TVAR1))), + ), + ); + // single : a -> List a add_type( Symbol::LIST_SINGLE, diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 29fd91bb3c..e2e0b514e0 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -699,6 +699,36 @@ pub fn types() -> MutMap { ) }); + // prepend : Attr * (List a) + // , a + // -> Attr * (List a) + // + // NOTE: we demand the new item to have the same uniqueness as the other list items. + // It could be allowed to add unique items to shared lists, but that requires special code gen + add_type(Symbol::LIST_PREPEND, { + let_tvars! { a, star1, star2 }; + + unique_function( + vec![ + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + flex(star1), + SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]), + ], + ), + flex(a), + ], + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + boolean(star2), + SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]), + ], + ), + ) + }); + // List.map does not need to check the container rule on the input list. // There is no way in which this signature can cause unique values to be duplicated // diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 2291a5eff8..4646dd70bb 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -60,6 +60,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::LIST_REPEAT => list_repeat, Symbol::LIST_REVERSE => list_reverse, Symbol::LIST_APPEND => list_append, + Symbol::LIST_PREPEND => list_prepend, Symbol::NUM_ADD => num_add, Symbol::NUM_SUB => num_sub, Symbol::NUM_MUL => num_mul, @@ -879,6 +880,29 @@ fn list_push(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// List.prepend : List elem, elem -> List elem +fn list_prepend(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let elem_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::ListPrepend, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (elem_var, Var(Symbol::ARG_2)), + ], + ret_var: list_var, + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1), (elem_var, Symbol::ARG_2)], + var_store, + body, + list_var, + ) +} + /// Num.rem : Int, Int -> Result Int [ DivByZero ]* fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 1830390078..85872dc002 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1462,7 +1462,7 @@ fn list_push<'a, 'ctx, 'env>( 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 elems_ptr = load_list_ptr(builder, original_wrapper, ptr_type); + 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( @@ -1471,7 +1471,6 @@ fn list_push<'a, 'ctx, 'env>( "new_list_length", ); - let ctx = env.context; let ptr_bytes = env.ptr_bytes; // Calculate the number of bytes we'll need to allocate. @@ -1486,7 +1485,6 @@ fn list_push<'a, 'ctx, 'env>( .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 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(); @@ -1500,7 +1498,111 @@ fn list_push<'a, 'ctx, 'env>( // 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, list_size); + 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); + + let answer = builder.build_bitcast( + struct_val.into_struct_value(), + collection(ctx, ptr_bytes), + "cast_collection", + ); + + answer +} + +/// 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"); + + let elem_ptr = unsafe { + builder.build_in_bounds_gep( + clone_ptr, + &[ctx.i64_type().const_int(0 as u64, false)], + "load_index", + ) + }; + + builder.build_store(elem_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."); } @@ -1529,11 +1631,6 @@ fn list_push<'a, 'ctx, 'env>( collection(ctx, ptr_bytes), "cast_collection", ); - - let elem_ptr = unsafe { builder.build_in_bounds_gep(clone_ptr, &[list_len], "load_index") }; - - builder.build_store(elem_ptr, elem); - answer } @@ -1831,6 +1928,17 @@ fn run_low_level<'a, 'ctx, 'env>( list_push(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) + } NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumToFloat => { debug_assert_eq!(args.len(), 1); diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index e5d440ba89..4b3d0e28fd 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -67,6 +67,37 @@ mod gen_list { ); } + #[test] + fn list_prepend() { + assert_evals_to!("List.prepend [] 1", &[1], &'static [i64]); + assert_evals_to!("List.prepend [2] 1", &[1, 2], &'static [i64]); + + assert_evals_to!( + indoc!( + r#" + init : List Int + init = + [] + + List.prepend (List.prepend init 4) 6 + "# + ), + &[6, 4], + &'static [i64] + ); + + assert_evals_to!( + "List.prepend [ True, False ] True", + &[true, true, false], + &'static [bool] + ); + assert_evals_to!( + "List.prepend [ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 100, 100, 100, 100 ] 9", + &[9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 100, 100, 100, 100], + &'static [i64] + ); + } + #[test] fn list_single() { assert_evals_to!("List.single 1", &[1], &'static [i64]); diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index e4de53d5f0..261a871e10 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -11,6 +11,7 @@ pub enum LowLevel { ListRepeat, ListReverse, ListAppend, + ListPrepend, ListPush, NumAdd, NumSub, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 65b9264c04..90c68cf241 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -659,6 +659,7 @@ define_builtins! { 13 LIST_REPEAT: "repeat" 14 LIST_REVERSE: "reverse" 15 LIST_APPEND: "append" + 16 LIST_PREPEND: "prepend" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias