List.appendUnsafe and List.reserve

This commit is contained in:
Folkert 2022-07-07 22:35:32 +02:00
parent dda79a255e
commit 56c9787e8f
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
13 changed files with 165 additions and 81 deletions

View file

@ -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]];

View file

@ -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);

View file

@ -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");

View file

@ -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]

View file

@ -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";

View file

@ -110,9 +110,10 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
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

View file

@ -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

View file

@ -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,
)
}

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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]),

View file

@ -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]),