From 56c9787e8f0d710604085d01b2f6f1ebf0d2870a Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 7 Jul 2022 22:35:32 +0200 Subject: [PATCH] List.appendUnsafe and List.reserve --- crates/compiler/alias_analysis/src/lib.rs | 2 +- crates/compiler/builtins/bitcode/src/list.zig | 39 +++++++++--- crates/compiler/builtins/bitcode/src/main.zig | 3 +- crates/compiler/builtins/roc/List.roc | 15 +++++ crates/compiler/builtins/src/bitcode.rs | 5 +- crates/compiler/can/src/builtins.rs | 54 ++++------------- crates/compiler/gen_llvm/src/llvm/build.rs | 22 +++++-- .../compiler/gen_llvm/src/llvm/build_list.rs | 32 +++++++--- crates/compiler/gen_wasm/src/low_level.rs | 60 ++++++++++++++++--- crates/compiler/module/src/low_level.rs | 5 +- crates/compiler/module/src/symbol.rs | 2 + crates/compiler/mono/src/borrow.rs | 5 +- crates/compiler/test_gen/src/gen_list.rs | 2 +- 13 files changed, 165 insertions(+), 81 deletions(-) diff --git a/crates/compiler/alias_analysis/src/lib.rs b/crates/compiler/alias_analysis/src/lib.rs index b00c712ad2..d335ce385d 100644 --- a/crates/compiler/alias_analysis/src/lib.rs +++ b/crates/compiler/alias_analysis/src/lib.rs @@ -1029,7 +1029,7 @@ fn lowlevel_spec( with_new_heap_cell(builder, block, bag) } - ListAppend => { + ListAppendUnsafe => { let list = env.symbols[&arguments[0]]; let to_insert = env.symbols[&arguments[1]]; diff --git a/crates/compiler/builtins/bitcode/src/list.zig b/crates/compiler/builtins/bitcode/src/list.zig index cb38bf92ab..aef91ef969 100644 --- a/crates/compiler/builtins/bitcode/src/list.zig +++ b/crates/compiler/builtins/bitcode/src/list.zig @@ -404,21 +404,41 @@ pub fn listMap4( } } -pub fn listWithCapacity(capacity: usize, alignment: u32, element_width: usize) callconv(.C) RocList { +pub fn listWithCapacity( + capacity: usize, + alignment: u32, + element_width: usize, +) callconv(.C) RocList { var output = RocList.allocate(alignment, capacity, element_width); output.length = 0; return output; } -pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList { +pub fn listReserve( + list: RocList, + alignment: u32, + spare: usize, + element_width: usize, + update_mode: UpdateMode, +) callconv(.C) RocList { const old_length = list.len(); - var output: RocList = undefined; - if (update_mode == .InPlace and list.capacity >= old_length + 1) { - output = list; - output.length += 1; + if ((update_mode == .InPlace or list.isUnique()) and list.capacity >= list.len() + spare) { + return list; } else { - output = list.reallocate(alignment, old_length + 1, element_width); + var output = list.reallocate(alignment, old_length + spare, element_width); + output.length = old_length; + return output; } +} + +pub fn listAppendUnsafe( + list: RocList, + element: Opaque, + element_width: usize, +) callconv(.C) RocList { + const old_length = list.len(); + var output = list; + output.length += 1; if (output.bytes) |target| { if (element) |source| { @@ -429,6 +449,11 @@ pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: return output; } +fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList { + const with_capacity = listReserve(list, alignment, 1, element_width, update_mode); + return listAppendUnsafe(with_capacity, element, element_width); +} + pub fn listPrepend(list: RocList, alignment: u32, element: Opaque, element_width: usize) callconv(.C) RocList { const old_length = list.len(); var output = list.reallocate(alignment, old_length + 1, element_width); diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index 3b7c1cab3b..44223f4045 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -40,7 +40,8 @@ comptime { exportListFn(list.listMap2, "map2"); exportListFn(list.listMap3, "map3"); exportListFn(list.listMap4, "map4"); - exportListFn(list.listAppend, "append"); + exportListFn(list.listAppendUnsafe, "append_unsafe"); + exportListFn(list.listReserve, "reserve"); exportListFn(list.listPrepend, "prepend"); exportListFn(list.listWithCapacity, "with_capacity"); exportListFn(list.listSortWith, "sort_with"); diff --git a/crates/compiler/builtins/roc/List.roc b/crates/compiler/builtins/roc/List.roc index 8d3e8df243..932db1ee9e 100644 --- a/crates/compiler/builtins/roc/List.roc +++ b/crates/compiler/builtins/roc/List.roc @@ -52,6 +52,7 @@ interface List dropIf, sortAsc, sortDesc, + reserve, ] imports [ Bool.{ Bool }, @@ -243,6 +244,17 @@ set = \list, index, value -> ## >>> [0, 1, 2] ## >>> |> List.append 3 append : List a, a -> List a +append = \list, element -> + list + |> List.reserve 1 + |> List.appendUnsafe element + +## Writes the element after the current last element unconditionally. +## In other words, it is assumed that +## +## - the list is owned (i.e. can be updated in-place +## - the list has at least one element of spare capacity +appendUnsafe : List a, a -> List a ## Add a single element to the beginning of a list. ## @@ -262,6 +274,9 @@ len : List a -> Nat ## Create a list with space for at least capacity elements withCapacity : Nat -> List a +## Enlarge the list for at least capacity additional elements +reserve : List a, Nat -> List a + ## Put two lists together. ## ## >>> List.concat [1, 2, 3] [4, 5] diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index bb137e5419..db641fc488 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -359,8 +359,6 @@ pub const LIST_MAP: &str = "roc_builtins.list.map"; pub const LIST_MAP2: &str = "roc_builtins.list.map2"; pub const LIST_MAP3: &str = "roc_builtins.list.map3"; pub const LIST_MAP4: &str = "roc_builtins.list.map4"; -pub const LIST_APPEND: &str = "roc_builtins.list.append"; -pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; pub const LIST_SUBLIST: &str = "roc_builtins.list.sublist"; pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at"; pub const LIST_SWAP: &str = "roc_builtins.list.swap"; @@ -370,6 +368,9 @@ pub const LIST_CONCAT: &str = "roc_builtins.list.concat"; pub const LIST_REPLACE: &str = "roc_builtins.list.replace"; pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place"; pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique"; +pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; +pub const LIST_APPEND_UNSAFE: &str = "roc_builtins.list.append_unsafe"; +pub const LIST_RESERVE: &str = "roc_builtins.list.reserve"; pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str"; pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64"; diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index b8697d15ac..f27c584e11 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -110,9 +110,10 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_UNREACHABLE => roc_unreachable, LIST_LEN => list_len, LIST_WITH_CAPACITY => list_with_capacity, + LIST_RESERVE => list_reserve, + LIST_APPEND_UNSAFE => list_append_unsafe, LIST_GET_UNSAFE => list_get_unsafe, LIST_REPLACE_UNSAFE => list_replace_unsafe, - LIST_APPEND => list_append, LIST_IS_EMPTY => list_is_empty, LIST_CONCAT => list_concat, LIST_PREPEND => list_prepend, @@ -2069,6 +2070,14 @@ fn list_with_capacity(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_1(symbol, LowLevel::ListWithCapacity, var_store) } +fn list_reserve(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_2(symbol, LowLevel::ListReserve, var_store) +} + +fn list_append_unsafe(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_2(symbol, LowLevel::ListAppendUnsafe, var_store) +} + /// List.getUnsafe : List elem, Int -> elem fn list_get_unsafe(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListGetUnsafe, var_store) @@ -2314,50 +2323,9 @@ fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListDropAt, var_store) } -/// List.append : List elem, elem -> List elem -fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def { - let list_var = var_store.fresh(); - let elem_var = var_store.fresh(); - - let body = RunLowLevel { - op: LowLevel::ListAppend, - 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, - ) -} - /// 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, - ) + lowlevel_2(symbol, LowLevel::ListPrepend, var_store) } /// List.unreachable : [] -> a diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index f940fb0d5e..65229f012a 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -8,10 +8,10 @@ use crate::llvm::build_dict::{ }; use crate::llvm::build_hash::generic_hash; use crate::llvm::build_list::{ - self, allocate_list, empty_polymorphic_list, list_append, list_concat, list_drop_at, + self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_concat, list_drop_at, list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4, list_prepend, - list_replace_unsafe, list_sort_with, list_sublist, list_swap, list_symbol_to_c_abi, - list_to_c_abi, list_with_capacity, + list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap, + list_symbol_to_c_abi, list_to_c_abi, list_with_capacity, }; use crate::llvm::build_str::{str_from_float, str_from_int, str_from_utf8, str_from_utf8_range}; use crate::llvm::compare::{generic_eq, generic_neq}; @@ -5531,14 +5531,24 @@ fn run_low_level<'a, 'ctx, 'env>( list_concat(env, first_list, second_list, element_layout) } - ListAppend => { - // List.append : List elem, elem -> List elem + ListAppendUnsafe => { + // List.appendUnsafe : List elem, elem -> List elem debug_assert_eq!(args.len(), 2); let original_wrapper = load_symbol(scope, &args[0]).into_struct_value(); let (elem, elem_layout) = load_symbol_and_layout(scope, &args[1]); - list_append(env, original_wrapper, elem, elem_layout, update_mode) + list_append_unsafe(env, original_wrapper, elem, elem_layout) + } + ListReserve => { + // List.reserve : List elem, Nat -> List elem + debug_assert_eq!(args.len(), 2); + + let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); + let element_layout = list_element_layout!(list_layout); + let spare = load_symbol(scope, &args[1]); + + list_reserve(env, list, spare, element_layout, update_mode) } ListSwap => { // List.swap : List elem, Nat, Nat -> List elem diff --git a/crates/compiler/gen_llvm/src/llvm/build_list.rs b/crates/compiler/gen_llvm/src/llvm/build_list.rs index 5d794210aa..b753cf687f 100644 --- a/crates/compiler/gen_llvm/src/llvm/build_list.rs +++ b/crates/compiler/gen_llvm/src/llvm/build_list.rs @@ -146,24 +146,42 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>( result } -/// List.append : List elem, elem -> List elem -pub fn list_append<'a, 'ctx, 'env>( +/// List.reserve : List elem, Nat -> List elem +pub fn list_reserve<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - original_wrapper: StructValue<'ctx>, - element: BasicValueEnum<'ctx>, + list: BasicValueEnum<'ctx>, + spare: BasicValueEnum<'ctx>, element_layout: &Layout<'a>, update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { call_list_bitcode_fn( env, &[ - list_to_c_abi(env, original_wrapper.into()).into(), + list_to_c_abi(env, list).into(), env.alignment_intvalue(element_layout), - pass_element_as_opaque(env, element, *element_layout), + spare, layout_width(env, element_layout), pass_update_mode(env, update_mode), ], - bitcode::LIST_APPEND, + bitcode::LIST_RESERVE, + ) +} + +/// List.appendUnsafe : List elem, elem -> List elem +pub fn list_append_unsafe<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + original_wrapper: StructValue<'ctx>, + element: BasicValueEnum<'ctx>, + element_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + call_list_bitcode_fn( + env, + &[ + list_to_c_abi(env, original_wrapper.into()).into(), + pass_element_as_opaque(env, element, *element_layout), + layout_width(env, element_layout), + ], + bitcode::LIST_APPEND_UNSAFE, ) } diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index f0038316ac..eeb3311f5f 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -488,22 +488,27 @@ impl<'a> LowLevelCall<'a> { backend.call_host_fn_after_loading_args(bitcode::LIST_CONCAT, 7, false); } - ListAppend => { - // List.append : List elem, elem -> List elem + + ListReserve => { + // List.reserve : List elem, Nat -> List elem let list: Symbol = self.arguments[0]; - let elem: Symbol = self.arguments[1]; + let spare: Symbol = self.arguments[1]; let elem_layout = unwrap_list_elem_layout(self.ret_layout); let (elem_width, elem_align) = elem_layout.stack_size_and_alignment(TARGET_INFO); - let (elem_local, elem_offset, _) = - ensure_symbol_is_in_memory(backend, elem, *elem_layout, backend.env.arena); + let (spare_local, spare_offset, _) = ensure_symbol_is_in_memory( + backend, + spare, + Layout::usize(TARGET_INFO), + backend.env.arena, + ); // Zig arguments Wasm types // (return pointer) i32 // list: RocList i64, i32 // alignment: u32 i32 - // element: Opaque i32 + // spare: usize i32 // element_width: usize i32 // update_mode: UpdateMode i32 @@ -519,6 +524,46 @@ impl<'a> LowLevelCall<'a> { backend.code_builder.i32_const(elem_align as i32); + backend.code_builder.get_local(spare_local); + if spare_offset > 0 { + backend.code_builder.i32_const(spare_offset as i32); + backend.code_builder.i32_add(); + } + + backend.code_builder.i32_const(elem_width as i32); + + backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE); + + backend.call_host_fn_after_loading_args(bitcode::LIST_RESERVE, 7, false); + } + + ListAppendUnsafe => { + // List.append : List elem, elem -> List elem + + let list: Symbol = self.arguments[0]; + let elem: Symbol = self.arguments[1]; + + let elem_layout = unwrap_list_elem_layout(self.ret_layout); + let elem_width = elem_layout.stack_size(TARGET_INFO); + let (elem_local, elem_offset, _) = + ensure_symbol_is_in_memory(backend, elem, *elem_layout, backend.env.arena); + + // Zig arguments Wasm types + // (return pointer) i32 + // list: RocList i64, i32 + // element: Opaque i32 + // element_width: usize i32 + + // return pointer and list + backend.storage.load_symbols_for_call( + backend.env.arena, + &mut backend.code_builder, + &[list], + self.ret_symbol, + &WasmLayout::new(&self.ret_layout), + CallConv::Zig, + ); + backend.code_builder.get_local(elem_local); if elem_offset > 0 { backend.code_builder.i32_const(elem_offset as i32); @@ -526,9 +571,8 @@ impl<'a> LowLevelCall<'a> { } backend.code_builder.i32_const(elem_width as i32); - backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE); - backend.call_host_fn_after_loading_args(bitcode::LIST_APPEND, 7, false); + backend.call_host_fn_after_loading_args(bitcode::LIST_APPEND_UNSAFE, 4, false); } ListPrepend => { // List.prepend : List elem, elem -> List elem diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs index beeb9c483c..70d468c156 100644 --- a/crates/compiler/module/src/low_level.rs +++ b/crates/compiler/module/src/low_level.rs @@ -32,10 +32,11 @@ pub enum LowLevel { StrGetScalarUnsafe, ListLen, ListWithCapacity, + ListReserve, + ListAppendUnsafe, ListGetUnsafe, ListReplaceUnsafe, ListConcat, - ListAppend, ListPrepend, ListMap, ListMap2, @@ -209,7 +210,7 @@ impl LowLevelWrapperType { Symbol::LIST_GET => WrapperIsRequired, Symbol::LIST_REPLACE => WrapperIsRequired, Symbol::LIST_CONCAT => CanBeReplacedBy(ListConcat), - Symbol::LIST_APPEND => CanBeReplacedBy(ListAppend), + Symbol::LIST_APPEND_UNSAFE => CanBeReplacedBy(ListAppendUnsafe), Symbol::LIST_PREPEND => CanBeReplacedBy(ListPrepend), Symbol::LIST_MAP => WrapperIsRequired, Symbol::LIST_MAP2 => WrapperIsRequired, diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 0575e95ea4..a19a493694 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1272,6 +1272,8 @@ define_builtins! { 62 LIST_WITH_CAPACITY: "withCapacity" 63 LIST_ITERATE: "iterate" 64 LIST_UNREACHABLE: "unreachable" + 65 LIST_RESERVE: "reserve" + 66 LIST_APPEND_UNSAFE: "appendUnsafe" } 6 RESULT: "Result" => { 0 RESULT_RESULT: "Result" // the Result.Result type alias diff --git a/crates/compiler/mono/src/borrow.rs b/crates/compiler/mono/src/borrow.rs index 00acfa7dbe..aaa4197733 100644 --- a/crates/compiler/mono/src/borrow.rs +++ b/crates/compiler/mono/src/borrow.rs @@ -910,9 +910,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]), ListSortWith => arena.alloc_slice_copy(&[owned, function, closure_data]), - // TODO when we have lists with capacity (if ever) - // List.append should own its first argument - ListAppend => arena.alloc_slice_copy(&[owned, owned]), + ListAppendUnsafe => arena.alloc_slice_copy(&[owned, owned]), + ListReserve => arena.alloc_slice_copy(&[owned, irrelevant]), ListSublist => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]), ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index e5268772a1..796c3a278a 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -154,7 +154,7 @@ fn variously_sized_list_literals() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn list_append() { +fn list_append_basic() { assert_evals_to!( "List.append [1] 2", RocList::from_slice(&[1, 2]),