mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 07:14:46 +00:00
commit
0ae0e689fc
14 changed files with 304 additions and 118 deletions
|
@ -7,7 +7,7 @@ To add a builtin:
|
||||||
2. Make sure the function is public with the `pub` keyword and uses the C calling convention. This is really easy, just add `pub` and `callconv(.C)` to the function declaration like so: `pub fn atan(num: f64) callconv(.C) f64 { ... }`
|
2. Make sure the function is public with the `pub` keyword and uses the C calling convention. This is really easy, just add `pub` and `callconv(.C)` to the function declaration like so: `pub fn atan(num: f64) callconv(.C) f64 { ... }`
|
||||||
3. In `src/main.zig`, export the function. This is also organized by module. For example, for a `Num` function find the `Num` section and add: `comptime { exportNumFn(num.atan, "atan"); }`. The first argument is the function, the second is the name of it in LLVM.
|
3. In `src/main.zig`, export the function. This is also organized by module. For example, for a `Num` function find the `Num` section and add: `comptime { exportNumFn(num.atan, "atan"); }`. The first argument is the function, the second is the name of it in LLVM.
|
||||||
4. In `compiler/builtins/src/bitcode.rs`, add a constant for the new function. This is how we use it in Rust. Once again, this is organized by module, so just find the relevant area and add your new function.
|
4. In `compiler/builtins/src/bitcode.rs`, add a constant for the new function. This is how we use it in Rust. Once again, this is organized by module, so just find the relevant area and add your new function.
|
||||||
5. You can now your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`!
|
5. You can now use your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`!
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
|
|
|
@ -1256,95 +1256,56 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listSetInPlace(
|
pub fn listReplaceInPlace(
|
||||||
bytes: ?[*]u8,
|
list: RocList,
|
||||||
index: usize,
|
index: usize,
|
||||||
element: Opaque,
|
element: Opaque,
|
||||||
element_width: usize,
|
element_width: usize,
|
||||||
dec: Dec,
|
out_element: ?[*]u8,
|
||||||
) callconv(.C) ?[*]u8 {
|
) callconv(.C) RocList {
|
||||||
// INVARIANT: bounds checking happens on the roc side
|
// INVARIANT: bounds checking happens on the roc side
|
||||||
//
|
//
|
||||||
// at the time of writing, the function is implemented roughly as
|
// at the time of writing, the function is implemented roughly as
|
||||||
// `if inBounds then LowLevelListGet input index item else input`
|
// `if inBounds then LowLevelListReplace input index item else input`
|
||||||
// so we don't do a bounds check here. Hence, the list is also non-empty,
|
// so we don't do a bounds check here. Hence, the list is also non-empty,
|
||||||
// because inserting into an empty list is always out of bounds
|
// because inserting into an empty list is always out of bounds
|
||||||
|
return listReplaceInPlaceHelp(list, index, element, element_width, out_element);
|
||||||
return listSetInPlaceHelp(bytes, index, element, element_width, dec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listSet(
|
pub fn listReplace(
|
||||||
bytes: ?[*]u8,
|
list: RocList,
|
||||||
length: usize,
|
|
||||||
alignment: u32,
|
alignment: u32,
|
||||||
index: usize,
|
index: usize,
|
||||||
element: Opaque,
|
element: Opaque,
|
||||||
element_width: usize,
|
element_width: usize,
|
||||||
dec: Dec,
|
out_element: ?[*]u8,
|
||||||
) callconv(.C) ?[*]u8 {
|
) callconv(.C) RocList {
|
||||||
// INVARIANT: bounds checking happens on the roc side
|
// INVARIANT: bounds checking happens on the roc side
|
||||||
//
|
//
|
||||||
// at the time of writing, the function is implemented roughly as
|
// at the time of writing, the function is implemented roughly as
|
||||||
// `if inBounds then LowLevelListGet input index item else input`
|
// `if inBounds then LowLevelListReplace input index item else input`
|
||||||
// so we don't do a bounds check here. Hence, the list is also non-empty,
|
// so we don't do a bounds check here. Hence, the list is also non-empty,
|
||||||
// because inserting into an empty list is always out of bounds
|
// because inserting into an empty list is always out of bounds
|
||||||
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), bytes));
|
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width), index, element, element_width, out_element);
|
||||||
|
|
||||||
if ((ptr - 1)[0] == utils.REFCOUNT_ONE) {
|
|
||||||
return listSetInPlaceHelp(bytes, index, element, element_width, dec);
|
|
||||||
} else {
|
|
||||||
return listSetImmutable(bytes, length, alignment, index, element, element_width, dec);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn listSetInPlaceHelp(
|
inline fn listReplaceInPlaceHelp(
|
||||||
bytes: ?[*]u8,
|
list: RocList,
|
||||||
index: usize,
|
index: usize,
|
||||||
element: Opaque,
|
element: Opaque,
|
||||||
element_width: usize,
|
element_width: usize,
|
||||||
dec: Dec,
|
out_element: ?[*]u8,
|
||||||
) ?[*]u8 {
|
) RocList {
|
||||||
// the element we will replace
|
// the element we will replace
|
||||||
var element_at_index = (bytes orelse undefined) + (index * element_width);
|
var element_at_index = (list.bytes orelse undefined) + (index * element_width);
|
||||||
|
|
||||||
// decrement its refcount
|
// copy out the old element
|
||||||
dec(element_at_index);
|
@memcpy(out_element orelse undefined, element_at_index, element_width);
|
||||||
|
|
||||||
// copy in the new element
|
// copy in the new element
|
||||||
@memcpy(element_at_index, element orelse undefined, element_width);
|
@memcpy(element_at_index, element orelse undefined, element_width);
|
||||||
|
|
||||||
return bytes;
|
return list;
|
||||||
}
|
|
||||||
|
|
||||||
inline fn listSetImmutable(
|
|
||||||
old_bytes: ?[*]u8,
|
|
||||||
length: usize,
|
|
||||||
alignment: u32,
|
|
||||||
index: usize,
|
|
||||||
element: Opaque,
|
|
||||||
element_width: usize,
|
|
||||||
dec: Dec,
|
|
||||||
) ?[*]u8 {
|
|
||||||
const data_bytes = length * element_width;
|
|
||||||
|
|
||||||
var new_bytes = utils.allocateWithRefcount(data_bytes, alignment);
|
|
||||||
|
|
||||||
@memcpy(new_bytes, old_bytes orelse undefined, data_bytes);
|
|
||||||
|
|
||||||
// the element we will replace
|
|
||||||
var element_at_index = new_bytes + (index * element_width);
|
|
||||||
|
|
||||||
// decrement its refcount
|
|
||||||
dec(element_at_index);
|
|
||||||
|
|
||||||
// copy in the new element
|
|
||||||
@memcpy(element_at_index, element orelse undefined, element_width);
|
|
||||||
|
|
||||||
// consume RC token of original
|
|
||||||
utils.decref(old_bytes, data_bytes, alignment);
|
|
||||||
|
|
||||||
//return list;
|
|
||||||
return new_bytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listFindUnsafe(
|
pub fn listFindUnsafe(
|
||||||
|
|
|
@ -49,8 +49,8 @@ comptime {
|
||||||
exportListFn(list.listConcat, "concat");
|
exportListFn(list.listConcat, "concat");
|
||||||
exportListFn(list.listSublist, "sublist");
|
exportListFn(list.listSublist, "sublist");
|
||||||
exportListFn(list.listDropAt, "drop_at");
|
exportListFn(list.listDropAt, "drop_at");
|
||||||
exportListFn(list.listSet, "set");
|
exportListFn(list.listReplace, "replace");
|
||||||
exportListFn(list.listSetInPlace, "set_in_place");
|
exportListFn(list.listReplaceInPlace, "replace_in_place");
|
||||||
exportListFn(list.listSwap, "swap");
|
exportListFn(list.listSwap, "swap");
|
||||||
exportListFn(list.listAny, "any");
|
exportListFn(list.listAny, "any");
|
||||||
exportListFn(list.listAll, "all");
|
exportListFn(list.listAll, "all");
|
||||||
|
|
|
@ -354,8 +354,8 @@ pub const LIST_RANGE: &str = "roc_builtins.list.range";
|
||||||
pub const LIST_REVERSE: &str = "roc_builtins.list.reverse";
|
pub const LIST_REVERSE: &str = "roc_builtins.list.reverse";
|
||||||
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
|
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
|
||||||
pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
|
pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
|
||||||
pub const LIST_SET: &str = "roc_builtins.list.set";
|
pub const LIST_REPLACE: &str = "roc_builtins.list.replace";
|
||||||
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";
|
pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place";
|
||||||
pub const LIST_ANY: &str = "roc_builtins.list.any";
|
pub const LIST_ANY: &str = "roc_builtins.list.any";
|
||||||
pub const LIST_ALL: &str = "roc_builtins.list.all";
|
pub const LIST_ALL: &str = "roc_builtins.list.all";
|
||||||
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
|
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
|
||||||
|
|
|
@ -1056,6 +1056,19 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
Box::new(result_type(flex(TVAR1), list_was_empty.clone())),
|
Box::new(result_type(flex(TVAR1), list_was_empty.clone())),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// replace : List elem, Nat, elem -> { list: List elem, value: elem }
|
||||||
|
add_top_level_function_type!(
|
||||||
|
Symbol::LIST_REPLACE,
|
||||||
|
vec![list_type(flex(TVAR1)), nat_type(), flex(TVAR1)],
|
||||||
|
Box::new(SolvedType::Record {
|
||||||
|
fields: vec![
|
||||||
|
("list".into(), RecordField::Required(list_type(flex(TVAR1)))),
|
||||||
|
("value".into(), RecordField::Required(flex(TVAR1))),
|
||||||
|
],
|
||||||
|
ext: Box::new(SolvedType::EmptyRecord),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// set : List elem, Nat, elem -> List elem
|
// set : List elem, Nat, elem -> List elem
|
||||||
add_top_level_function_type!(
|
add_top_level_function_type!(
|
||||||
Symbol::LIST_SET,
|
Symbol::LIST_SET,
|
||||||
|
|
|
@ -57,6 +57,7 @@ pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] {
|
||||||
Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL],
|
Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL],
|
||||||
Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD],
|
Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD],
|
||||||
Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT],
|
Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT],
|
||||||
|
Symbol::LIST_SET => &[Symbol::LIST_REPLACE],
|
||||||
_ => &[],
|
_ => &[],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,6 +103,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||||
STR_TO_I8 => str_to_num,
|
STR_TO_I8 => str_to_num,
|
||||||
LIST_LEN => list_len,
|
LIST_LEN => list_len,
|
||||||
LIST_GET => list_get,
|
LIST_GET => list_get,
|
||||||
|
LIST_REPLACE => list_replace,
|
||||||
LIST_SET => list_set,
|
LIST_SET => list_set,
|
||||||
LIST_APPEND => list_append,
|
LIST_APPEND => list_append,
|
||||||
LIST_FIRST => list_first,
|
LIST_FIRST => list_first,
|
||||||
|
@ -2303,6 +2305,91 @@ fn list_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List.replace : List elem, Nat, elem -> { list: List elem, value: elem }
|
||||||
|
fn list_replace(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let arg_list = Symbol::ARG_1;
|
||||||
|
let arg_index = Symbol::ARG_2;
|
||||||
|
let arg_elem = Symbol::ARG_3;
|
||||||
|
let bool_var = var_store.fresh();
|
||||||
|
let len_var = var_store.fresh();
|
||||||
|
let elem_var = var_store.fresh();
|
||||||
|
let list_arg_var = var_store.fresh();
|
||||||
|
let ret_record_var = var_store.fresh();
|
||||||
|
let ret_result_var = var_store.fresh();
|
||||||
|
|
||||||
|
let list_field = Field {
|
||||||
|
var: list_arg_var,
|
||||||
|
region: Region::zero(),
|
||||||
|
loc_expr: Box::new(Loc::at_zero(Expr::Var(arg_list))),
|
||||||
|
};
|
||||||
|
|
||||||
|
let value_field = Field {
|
||||||
|
var: elem_var,
|
||||||
|
region: Region::zero(),
|
||||||
|
loc_expr: Box::new(Loc::at_zero(Expr::Var(arg_elem))),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Perform a bounds check. If it passes, run LowLevel::ListReplaceUnsafe.
|
||||||
|
// Otherwise, return the list unmodified.
|
||||||
|
let body = If {
|
||||||
|
cond_var: bool_var,
|
||||||
|
branch_var: ret_result_var,
|
||||||
|
branches: vec![(
|
||||||
|
// if-condition
|
||||||
|
no_region(
|
||||||
|
// index < List.len list
|
||||||
|
RunLowLevel {
|
||||||
|
op: LowLevel::NumLt,
|
||||||
|
args: vec![
|
||||||
|
(len_var, Var(arg_index)),
|
||||||
|
(
|
||||||
|
len_var,
|
||||||
|
RunLowLevel {
|
||||||
|
op: LowLevel::ListLen,
|
||||||
|
args: vec![(list_arg_var, Var(arg_list))],
|
||||||
|
ret_var: len_var,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
ret_var: bool_var,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// then-branch
|
||||||
|
no_region(
|
||||||
|
// List.replaceUnsafe list index elem
|
||||||
|
RunLowLevel {
|
||||||
|
op: LowLevel::ListReplaceUnsafe,
|
||||||
|
args: vec![
|
||||||
|
(list_arg_var, Var(arg_list)),
|
||||||
|
(len_var, Var(arg_index)),
|
||||||
|
(elem_var, Var(arg_elem)),
|
||||||
|
],
|
||||||
|
ret_var: ret_record_var,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
final_else: Box::new(
|
||||||
|
// else-branch
|
||||||
|
no_region(record(
|
||||||
|
vec![("list".into(), list_field), ("value".into(), value_field)],
|
||||||
|
var_store,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
defn(
|
||||||
|
symbol,
|
||||||
|
vec![
|
||||||
|
(list_arg_var, Symbol::ARG_1),
|
||||||
|
(len_var, Symbol::ARG_2),
|
||||||
|
(elem_var, Symbol::ARG_3),
|
||||||
|
],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
ret_result_var,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// List.set : List elem, Nat, elem -> List elem
|
/// List.set : List elem, Nat, elem -> List elem
|
||||||
///
|
///
|
||||||
/// List.set :
|
/// List.set :
|
||||||
|
@ -2317,9 +2404,27 @@ fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
let bool_var = var_store.fresh();
|
let bool_var = var_store.fresh();
|
||||||
let len_var = var_store.fresh();
|
let len_var = var_store.fresh();
|
||||||
let elem_var = var_store.fresh();
|
let elem_var = var_store.fresh();
|
||||||
|
let replace_record_var = var_store.fresh();
|
||||||
let list_arg_var = var_store.fresh(); // Uniqueness type Attr differs between
|
let list_arg_var = var_store.fresh(); // Uniqueness type Attr differs between
|
||||||
let list_ret_var = var_store.fresh(); // the arg list and the returned list
|
let list_ret_var = var_store.fresh(); // the arg list and the returned list
|
||||||
|
|
||||||
|
let replace_function = (
|
||||||
|
var_store.fresh(),
|
||||||
|
Loc::at_zero(Expr::Var(Symbol::LIST_REPLACE)),
|
||||||
|
var_store.fresh(),
|
||||||
|
replace_record_var,
|
||||||
|
);
|
||||||
|
|
||||||
|
let replace_call = Expr::Call(
|
||||||
|
Box::new(replace_function),
|
||||||
|
vec![
|
||||||
|
(list_arg_var, Loc::at_zero(Var(arg_list))),
|
||||||
|
(len_var, Loc::at_zero(Var(arg_index))),
|
||||||
|
(elem_var, Loc::at_zero(Var(arg_elem))),
|
||||||
|
],
|
||||||
|
CalledVia::Space,
|
||||||
|
);
|
||||||
|
|
||||||
// Perform a bounds check. If it passes, run LowLevel::ListSet.
|
// Perform a bounds check. If it passes, run LowLevel::ListSet.
|
||||||
// Otherwise, return the list unmodified.
|
// Otherwise, return the list unmodified.
|
||||||
let body = If {
|
let body = If {
|
||||||
|
@ -2346,18 +2451,16 @@ fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// then-branch
|
// then-branch
|
||||||
no_region(
|
no_region(Access {
|
||||||
// List.setUnsafe list index
|
record_var: replace_record_var,
|
||||||
RunLowLevel {
|
ext_var: var_store.fresh(),
|
||||||
op: LowLevel::ListSet,
|
field_var: list_ret_var,
|
||||||
args: vec![
|
loc_expr: Box::new(no_region(
|
||||||
(list_arg_var, Var(arg_list)),
|
// List.replaceUnsafe list index elem
|
||||||
(len_var, Var(arg_index)),
|
replace_call,
|
||||||
(elem_var, Var(arg_elem)),
|
)),
|
||||||
],
|
field: "list".into(),
|
||||||
ret_var: list_ret_var,
|
}),
|
||||||
},
|
|
||||||
),
|
|
||||||
)],
|
)],
|
||||||
final_else: Box::new(
|
final_else: Box::new(
|
||||||
// else-branch
|
// else-branch
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::llvm::build_list::{
|
||||||
self, allocate_list, empty_polymorphic_list, list_all, list_any, list_append, list_concat,
|
self, allocate_list, empty_polymorphic_list, list_all, list_any, list_append, list_concat,
|
||||||
list_contains, list_drop_at, list_find_unsafe, list_get_unsafe, list_join, list_keep_errs,
|
list_contains, list_drop_at, list_find_unsafe, list_get_unsafe, list_join, list_keep_errs,
|
||||||
list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map4,
|
list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map4,
|
||||||
list_map_with_index, list_prepend, list_range, list_repeat, list_reverse, list_set,
|
list_map_with_index, list_prepend, list_range, list_repeat, list_replace_unsafe, list_reverse,
|
||||||
list_single, list_sort_with, list_sublist, list_swap,
|
list_single, list_sort_with, list_sublist, list_swap,
|
||||||
};
|
};
|
||||||
use crate::llvm::build_str::{
|
use crate::llvm::build_str::{
|
||||||
|
@ -5666,12 +5666,12 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
wrapper_struct,
|
wrapper_struct,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ListSet => {
|
ListReplaceUnsafe => {
|
||||||
let list = load_symbol(scope, &args[0]);
|
let list = load_symbol(scope, &args[0]);
|
||||||
let index = load_symbol(scope, &args[1]);
|
let index = load_symbol(scope, &args[1]);
|
||||||
let (element, element_layout) = load_symbol_and_layout(scope, &args[2]);
|
let (element, element_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||||
|
|
||||||
list_set(
|
list_replace_unsafe(
|
||||||
env,
|
env,
|
||||||
layout_ids,
|
layout_ids,
|
||||||
list,
|
list,
|
||||||
|
|
|
@ -291,52 +291,70 @@ pub fn list_drop_at<'a, 'ctx, 'env>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List.set : List elem, Nat, elem -> List elem
|
/// List.replace_unsafe : List elem, Nat, elem -> { list: List elem, value: elem }
|
||||||
pub fn list_set<'a, 'ctx, 'env>(
|
pub fn list_replace_unsafe<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
layout_ids: &mut LayoutIds<'a>,
|
_layout_ids: &mut LayoutIds<'a>,
|
||||||
list: BasicValueEnum<'ctx>,
|
list: BasicValueEnum<'ctx>,
|
||||||
index: IntValue<'ctx>,
|
index: IntValue<'ctx>,
|
||||||
element: BasicValueEnum<'ctx>,
|
element: BasicValueEnum<'ctx>,
|
||||||
element_layout: &Layout<'a>,
|
element_layout: &Layout<'a>,
|
||||||
update_mode: UpdateMode,
|
update_mode: UpdateMode,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
|
let element_type = basic_type_from_layout(env, element_layout);
|
||||||
|
let element_ptr = env
|
||||||
|
.builder
|
||||||
|
.build_alloca(element_type, "output_element_as_opaque");
|
||||||
|
|
||||||
let (length, bytes) = load_list(
|
// Assume the bounds have already been checked earlier
|
||||||
env.builder,
|
// (e.g. by List.replace or List.set, which wrap List.#replaceUnsafe)
|
||||||
list.into_struct_value(),
|
let new_list = match update_mode {
|
||||||
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
UpdateMode::InPlace => call_list_bitcode_fn(
|
||||||
);
|
|
||||||
|
|
||||||
let new_bytes = match update_mode {
|
|
||||||
UpdateMode::InPlace => call_bitcode_fn(
|
|
||||||
env,
|
env,
|
||||||
&[
|
&[
|
||||||
bytes.into(),
|
pass_list_cc(env, list),
|
||||||
index.into(),
|
index.into(),
|
||||||
pass_element_as_opaque(env, element, *element_layout),
|
pass_element_as_opaque(env, element, *element_layout),
|
||||||
layout_width(env, element_layout),
|
layout_width(env, element_layout),
|
||||||
dec_element_fn.as_global_value().as_pointer_value().into(),
|
pass_as_opaque(env, element_ptr),
|
||||||
],
|
],
|
||||||
bitcode::LIST_SET_IN_PLACE,
|
bitcode::LIST_REPLACE_IN_PLACE,
|
||||||
),
|
),
|
||||||
UpdateMode::Immutable => call_bitcode_fn(
|
UpdateMode::Immutable => call_list_bitcode_fn(
|
||||||
env,
|
env,
|
||||||
&[
|
&[
|
||||||
bytes.into(),
|
pass_list_cc(env, list),
|
||||||
length.into(),
|
|
||||||
env.alignment_intvalue(element_layout),
|
env.alignment_intvalue(element_layout),
|
||||||
index.into(),
|
index.into(),
|
||||||
pass_element_as_opaque(env, element, *element_layout),
|
pass_element_as_opaque(env, element, *element_layout),
|
||||||
layout_width(env, element_layout),
|
layout_width(env, element_layout),
|
||||||
dec_element_fn.as_global_value().as_pointer_value().into(),
|
pass_as_opaque(env, element_ptr),
|
||||||
],
|
],
|
||||||
bitcode::LIST_SET,
|
bitcode::LIST_REPLACE,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
store_list(env, new_bytes.into_pointer_value(), length)
|
// Load the element and returned list into a struct.
|
||||||
|
let old_element = env.builder.build_load(element_ptr, "load_element");
|
||||||
|
|
||||||
|
let result = env
|
||||||
|
.context
|
||||||
|
.struct_type(
|
||||||
|
&[super::convert::zig_list_type(env).into(), element_type],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.const_zero();
|
||||||
|
|
||||||
|
let result = env
|
||||||
|
.builder
|
||||||
|
.build_insert_value(result, new_list, 0, "insert_list")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_insert_value(result, old_element, 1, "insert_value")
|
||||||
|
.unwrap()
|
||||||
|
.into_struct_value()
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds_check_comparison<'ctx>(
|
fn bounds_check_comparison<'ctx>(
|
||||||
|
|
|
@ -260,14 +260,14 @@ impl<'a> LowLevelCall<'a> {
|
||||||
_ => internal_error!("invalid storage for List"),
|
_ => internal_error!("invalid storage for List"),
|
||||||
},
|
},
|
||||||
|
|
||||||
ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
|
ListGetUnsafe | ListReplaceUnsafe | ListSingle | ListRepeat | ListReverse
|
||||||
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap
|
| ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange
|
||||||
| ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
|
| ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf
|
||||||
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
|
| ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs
|
||||||
| ListSublist | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe
|
| ListSortWith | ListSublist | ListDropAt | ListSwap | ListAny | ListAll
|
||||||
| DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe
|
| ListFindUnsafe | DictSize | DictEmpty | DictInsert | DictRemove | DictContains
|
||||||
| DictKeys | DictValues | DictUnion | DictIntersection | DictDifference | DictWalk
|
| DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection
|
||||||
| SetFromList => {
|
| DictDifference | DictWalk | SetFromList => {
|
||||||
todo!("{:?}", self.lowlevel);
|
todo!("{:?}", self.lowlevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,9 @@ pub enum LowLevel {
|
||||||
StrToNum,
|
StrToNum,
|
||||||
ListLen,
|
ListLen,
|
||||||
ListGetUnsafe,
|
ListGetUnsafe,
|
||||||
ListSet,
|
|
||||||
ListSingle,
|
ListSingle,
|
||||||
ListRepeat,
|
ListRepeat,
|
||||||
|
ListReplaceUnsafe,
|
||||||
ListReverse,
|
ListReverse,
|
||||||
ListConcat,
|
ListConcat,
|
||||||
ListContains,
|
ListContains,
|
||||||
|
@ -228,7 +228,7 @@ impl LowLevelWrapperType {
|
||||||
Symbol::STR_TO_I8 => WrapperIsRequired,
|
Symbol::STR_TO_I8 => WrapperIsRequired,
|
||||||
Symbol::LIST_LEN => CanBeReplacedBy(ListLen),
|
Symbol::LIST_LEN => CanBeReplacedBy(ListLen),
|
||||||
Symbol::LIST_GET => WrapperIsRequired,
|
Symbol::LIST_GET => WrapperIsRequired,
|
||||||
Symbol::LIST_SET => WrapperIsRequired,
|
Symbol::LIST_REPLACE => WrapperIsRequired,
|
||||||
Symbol::LIST_SINGLE => CanBeReplacedBy(ListSingle),
|
Symbol::LIST_SINGLE => CanBeReplacedBy(ListSingle),
|
||||||
Symbol::LIST_REPEAT => CanBeReplacedBy(ListRepeat),
|
Symbol::LIST_REPEAT => CanBeReplacedBy(ListRepeat),
|
||||||
Symbol::LIST_REVERSE => CanBeReplacedBy(ListReverse),
|
Symbol::LIST_REVERSE => CanBeReplacedBy(ListReverse),
|
||||||
|
|
|
@ -1142,6 +1142,7 @@ define_builtins! {
|
||||||
55 LIST_SORT_ASC: "sortAsc"
|
55 LIST_SORT_ASC: "sortAsc"
|
||||||
56 LIST_SORT_DESC: "sortDesc"
|
56 LIST_SORT_DESC: "sortDesc"
|
||||||
57 LIST_SORT_DESC_COMPARE: "#sortDescCompare"
|
57 LIST_SORT_DESC_COMPARE: "#sortDescCompare"
|
||||||
|
58 LIST_REPLACE: "replace"
|
||||||
}
|
}
|
||||||
5 RESULT: "Result" => {
|
5 RESULT: "Result" => {
|
||||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||||
|
|
|
@ -1258,22 +1258,21 @@ fn lowlevel_spec(
|
||||||
|
|
||||||
builder.add_bag_get(block, bag)
|
builder.add_bag_get(block, bag)
|
||||||
}
|
}
|
||||||
ListSet => {
|
ListReplaceUnsafe => {
|
||||||
let list = env.symbols[&arguments[0]];
|
let list = env.symbols[&arguments[0]];
|
||||||
let to_insert = env.symbols[&arguments[2]];
|
let to_insert = env.symbols[&arguments[2]];
|
||||||
|
|
||||||
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
|
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
|
||||||
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
|
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
|
||||||
|
|
||||||
// decrement the overwritten element
|
let _unit1 = builder.add_touch(block, cell)?;
|
||||||
let overwritten = builder.add_bag_get(block, bag)?;
|
let _unit2 = builder.add_update(block, update_mode_var, cell)?;
|
||||||
let _unit = builder.add_recursive_touch(block, overwritten)?;
|
|
||||||
|
|
||||||
let _unit = builder.add_update(block, update_mode_var, cell)?;
|
|
||||||
|
|
||||||
builder.add_bag_insert(block, bag, to_insert)?;
|
builder.add_bag_insert(block, bag, to_insert)?;
|
||||||
|
|
||||||
with_new_heap_cell(builder, block, bag)
|
let old_value = builder.add_bag_get(block, bag)?;
|
||||||
|
let new_list = with_new_heap_cell(builder, block, bag)?;
|
||||||
|
builder.add_make_tuple(block, &[new_list, old_value])
|
||||||
}
|
}
|
||||||
ListSwap => {
|
ListSwap => {
|
||||||
let list = env.symbols[&arguments[0]];
|
let list = env.symbols[&arguments[0]];
|
||||||
|
|
|
@ -934,7 +934,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||||
// - other refcounted arguments are Borrowed
|
// - other refcounted arguments are Borrowed
|
||||||
match op {
|
match op {
|
||||||
ListLen | StrIsEmpty | StrCountGraphemes => arena.alloc_slice_copy(&[borrowed]),
|
ListLen | StrIsEmpty | StrCountGraphemes => arena.alloc_slice_copy(&[borrowed]),
|
||||||
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||||
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||||
ListConcat => arena.alloc_slice_copy(&[owned, owned]),
|
ListConcat => arena.alloc_slice_copy(&[owned, owned]),
|
||||||
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
|
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||||
|
|
|
@ -1763,6 +1763,97 @@ fn get_int_list_oob() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "gen-llvm"))]
|
||||||
|
fn replace_unique_int_list() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
record = List.replace [ 12, 9, 7, 1, 5 ] 2 33
|
||||||
|
record.list
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
RocList::from_slice(&[12, 9, 33, 1, 5]),
|
||||||
|
RocList<i64>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "gen-llvm"))]
|
||||||
|
fn replace_unique_int_list_out_of_bounds() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
record = List.replace [ 12, 9, 7, 1, 5 ] 5 33
|
||||||
|
record.value
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
33,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "gen-llvm"))]
|
||||||
|
fn replace_unique_int_list_get_old_value() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
record = List.replace [ 12, 9, 7, 1, 5 ] 2 33
|
||||||
|
record.value
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
7,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "gen-llvm"))]
|
||||||
|
fn replace_unique_get_large_value() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
list : List { a : U64, b: U64, c: U64, d: U64 }
|
||||||
|
list = [ { a: 1, b: 2, c: 3, d: 4 }, { a: 5, b: 6, c: 7, d: 8 }, { a: 9, b: 10, c: 11, d: 12 } ]
|
||||||
|
record = List.replace list 1 { a: 13, b: 14, c: 15, d: 16 }
|
||||||
|
record.value
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
(5, 6, 7, 8),
|
||||||
|
(u64, u64, u64, u64)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "gen-llvm"))]
|
||||||
|
fn replace_shared_int_list() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
wrapper = \shared ->
|
||||||
|
# This should not mutate the original
|
||||||
|
replaced = (List.replace shared 1 7.7).list
|
||||||
|
x =
|
||||||
|
when List.get replaced 1 is
|
||||||
|
Ok num -> num
|
||||||
|
Err _ -> 0
|
||||||
|
|
||||||
|
y =
|
||||||
|
when List.get shared 1 is
|
||||||
|
Ok num -> num
|
||||||
|
Err _ -> 0
|
||||||
|
|
||||||
|
{ x, y }
|
||||||
|
|
||||||
|
wrapper [ 2.1, 4.3 ]
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
(7.7, 4.3),
|
||||||
|
(f64, f64)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm"))]
|
||||||
fn get_set_unique_int_list() {
|
fn get_set_unique_int_list() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue