mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 15:03:46 +00:00
List.appendUnsafe and List.reserve
This commit is contained in:
parent
dda79a255e
commit
56c9787e8f
13 changed files with 165 additions and 81 deletions
|
@ -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]];
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]),
|
||||
|
|
|
@ -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]),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue