mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-30 16:47:25 +00:00
Split ListLen into ListLenU64 and ListLenUsize
The usize one gets used internally for things like pattern matches. This is both more efficient (means they don't have to do unnecessary casts) and also less error-prone due to e.g. comparing length to capacity, which is usize.
This commit is contained in:
parent
a15cc0589c
commit
ada83561e5
17 changed files with 88 additions and 48 deletions
|
@ -1121,7 +1121,7 @@ fn lowlevel_spec<'a>(
|
|||
// just dream up a unit value
|
||||
builder.add_make_tuple(block, &[])
|
||||
}
|
||||
ListLen => {
|
||||
ListLenUsize | ListLenU64 => {
|
||||
// TODO should this touch the heap cell?
|
||||
// just dream up a unit value
|
||||
builder.add_make_tuple(block, &[])
|
||||
|
|
|
@ -129,7 +129,8 @@ map_symbol_to_lowlevel_and_arity! {
|
|||
StrWithCapacity; STR_WITH_CAPACITY; 1,
|
||||
StrReleaseExcessCapacity; STR_RELEASE_EXCESS_CAPACITY; 1,
|
||||
|
||||
ListLen; LIST_LEN; 1,
|
||||
ListLenUsize; LIST_LEN_USIZE; 1,
|
||||
ListLenU64; LIST_LEN_U64; 1,
|
||||
ListWithCapacity; LIST_WITH_CAPACITY; 1,
|
||||
ListReserve; LIST_RESERVE; 2,
|
||||
ListIsUnique; LIST_IS_UNIQUE; 1,
|
||||
|
|
|
@ -2830,8 +2830,13 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) {
|
||||
self.storage_manager.list_len(&mut self.buf, dst, list);
|
||||
fn build_list_len_usize(&mut self, dst: &Symbol, list: &Symbol) {
|
||||
self.storage_manager
|
||||
.list_len_usize(&mut self.buf, dst, list);
|
||||
}
|
||||
|
||||
fn build_list_len_u64(&mut self, dst: &Symbol, list: &Symbol) {
|
||||
self.storage_manager.list_len_u64(&mut self.buf, dst, list);
|
||||
}
|
||||
|
||||
fn build_list_clone(
|
||||
|
|
|
@ -694,7 +694,7 @@ impl<
|
|||
}
|
||||
|
||||
// Loads the dst to be the later 64 bits of a list (its length).
|
||||
pub fn list_len(&mut self, _buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) {
|
||||
pub fn list_len_u64(&mut self, _buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) {
|
||||
let owned_data = self.remove_allocation_for_sym(list);
|
||||
self.allocation_map.insert(*list, Rc::clone(&owned_data));
|
||||
self.allocation_map.insert(*dst, owned_data);
|
||||
|
@ -709,6 +709,11 @@ impl<
|
|||
);
|
||||
}
|
||||
|
||||
/// In a 64-bit backend, this is the same as list_len_u64
|
||||
pub fn list_len_usize(&mut self, buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) {
|
||||
self.list_len_u64(buf, dst, list)
|
||||
}
|
||||
|
||||
/// Creates a struct on the stack, moving the data in fields into the struct.
|
||||
pub fn create_struct(
|
||||
&mut self,
|
||||
|
|
|
@ -1484,13 +1484,21 @@ trait Backend<'a> {
|
|||
|
||||
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
|
||||
}
|
||||
LowLevel::ListLen => {
|
||||
LowLevel::ListLenU64 => {
|
||||
debug_assert_eq!(
|
||||
1,
|
||||
args.len(),
|
||||
"ListLen: expected to have exactly one argument"
|
||||
"ListLenU64: expected to have exactly one argument"
|
||||
);
|
||||
self.build_list_len(sym, &args[0])
|
||||
self.build_list_len_u64(sym, &args[0])
|
||||
}
|
||||
LowLevel::ListLenUsize => {
|
||||
debug_assert_eq!(
|
||||
1,
|
||||
args.len(),
|
||||
"ListLenUsize: expected to have exactly one argument"
|
||||
);
|
||||
self.build_list_len_usize(sym, &args[0])
|
||||
}
|
||||
LowLevel::ListWithCapacity => {
|
||||
debug_assert_eq!(
|
||||
|
@ -2372,8 +2380,11 @@ trait Backend<'a> {
|
|||
/// build_sqrt stores the result of `sqrt(src)` into dst.
|
||||
fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth);
|
||||
|
||||
/// build_list_len returns the length of a list.
|
||||
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol);
|
||||
/// build_list_len_usize returns the length of a list as a usize. This is for internal use only.
|
||||
fn build_list_len_usize(&mut self, dst: &Symbol, list: &Symbol);
|
||||
|
||||
/// build_list_len_u64 returns the length of a list and casts it from usize to u64. This is for the public List.len.
|
||||
fn build_list_len_u64(&mut self, dst: &Symbol, list: &Symbol);
|
||||
|
||||
/// generate a call to a higher-order lowlevel
|
||||
fn build_higher_order_lowlevel(
|
||||
|
|
|
@ -611,7 +611,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
|||
bitcode::STR_WITH_CAPACITY,
|
||||
)
|
||||
}
|
||||
ListLen => {
|
||||
ListLenU64 => {
|
||||
// List.len : List * -> U64
|
||||
arguments!(list);
|
||||
|
||||
|
@ -622,6 +622,12 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
|||
.new_build_int_cast(len_usize, env.context.i64_type(), "usize_to_u64")
|
||||
.into()
|
||||
}
|
||||
ListLenUsize => {
|
||||
// List.lenUsize : List * -> usize # used internally, not exposed
|
||||
arguments!(list);
|
||||
|
||||
list_len_usize(env.builder, list.into_struct_value()).into()
|
||||
}
|
||||
ListGetCapacity => {
|
||||
// List.capacity: List a -> U64
|
||||
arguments!(list);
|
||||
|
|
|
@ -262,28 +262,14 @@ impl<'a> LowLevelCall<'a> {
|
|||
StrWithCapacity => self.load_args_and_call_zig(backend, bitcode::STR_WITH_CAPACITY),
|
||||
|
||||
// List
|
||||
ListLen => match backend.storage.get(&self.arguments[0]) {
|
||||
StoredValue::StackMemory { location, .. } => {
|
||||
let (local_id, offset) =
|
||||
location.local_and_offset(backend.storage.stack_frame_pointer);
|
||||
backend.code_builder.get_local(local_id);
|
||||
// List is stored as (pointer, length, capacity),
|
||||
// with each of those fields being 4 bytes on wasm.
|
||||
// So the length is 4 bytes after the start of the struct.
|
||||
//
|
||||
// WRAPPER_LEN represents the index of the length field
|
||||
// (which is 1 as of the writing of this comment). If the field order
|
||||
// ever changes, WRAPPER_LEN should be updated and this logic should
|
||||
// continue to work even though this comment may become inaccurate.
|
||||
backend
|
||||
.code_builder
|
||||
.i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_LEN));
|
||||
ListLenU64 => {
|
||||
self.load_list_len_usize(backend);
|
||||
|
||||
// Length is stored as 32 bits in memory, but List.len returns U64
|
||||
backend.code_builder.i64_extend_u_i32();
|
||||
}
|
||||
_ => internal_error!("invalid storage for List"),
|
||||
},
|
||||
// Length is stored as 32 bits in memory on wasm32,
|
||||
// but List.len always returns U64
|
||||
backend.code_builder.i64_extend_u_i32();
|
||||
}
|
||||
ListLenUsize => self.load_list_len_usize(backend),
|
||||
|
||||
ListGetCapacity => self.load_args_and_call_zig(backend, bitcode::LIST_CAPACITY),
|
||||
|
||||
|
@ -2119,6 +2105,28 @@ impl<'a> LowLevelCall<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_list_len_usize(&self, backend: &mut WasmBackend<'_, '_>) {
|
||||
match backend.storage.get(&self.arguments[0]) {
|
||||
StoredValue::StackMemory { location, .. } => {
|
||||
let (local_id, offset) =
|
||||
location.local_and_offset(backend.storage.stack_frame_pointer);
|
||||
backend.code_builder.get_local(local_id);
|
||||
// List is stored as (pointer, length, capacity),
|
||||
// with each of those fields being 4 bytes on wasm.
|
||||
// So the length is 4 bytes after the start of the struct.
|
||||
//
|
||||
// WRAPPER_LEN represents the index of the length field
|
||||
// (which is 1 as of the writing of this comment). If the field order
|
||||
// ever changes, WRAPPER_LEN should be updated and this logic should
|
||||
// continue to work even though this comment may become inaccurate.
|
||||
backend
|
||||
.code_builder
|
||||
.i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_LEN));
|
||||
}
|
||||
_ => internal_error!("invalid storage for List"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Equality and inequality
|
||||
/// These can operate on any data type (except functions) so they're more complex than other operators.
|
||||
fn eq_or_neq(&self, backend: &mut WasmBackend<'a, '_>) {
|
||||
|
|
|
@ -4703,7 +4703,7 @@ fn synth_list_len_type(subs: &mut Subs) -> Variable {
|
|||
Content::Structure(FlatType::Apply(Symbol::LIST_LIST, a_slice)),
|
||||
);
|
||||
let fn_var = synth_import(subs, Content::Error);
|
||||
let solved_list_len = UnionLabels::insert_into_subs(subs, [(Symbol::LIST_LEN, [])]);
|
||||
let solved_list_len = UnionLabels::insert_into_subs(subs, [(Symbol::LIST_LEN_U64, [])]);
|
||||
let clos_list_len = synth_import(
|
||||
subs,
|
||||
Content::LambdaSet(LambdaSet {
|
||||
|
@ -4757,7 +4757,7 @@ pub fn add_imports(
|
|||
// Num needs List.len, but List imports Num.
|
||||
let list_len_type_var = synth_list_len_type(subs);
|
||||
let list_len_type_index = constraints.push_variable(list_len_type_var);
|
||||
def_types.push((Symbol::LIST_LEN, Loc::at_zero(list_len_type_index)));
|
||||
def_types.push((Symbol::LIST_LEN_U64, Loc::at_zero(list_len_type_index)));
|
||||
import_variables.push(list_len_type_var);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,8 @@ pub enum LowLevel {
|
|||
StrReserve,
|
||||
StrWithCapacity,
|
||||
StrReleaseExcessCapacity,
|
||||
ListLen,
|
||||
ListLenUsize,
|
||||
ListLenU64,
|
||||
ListWithCapacity,
|
||||
ListReserve,
|
||||
ListReleaseExcessCapacity,
|
||||
|
@ -268,7 +269,8 @@ map_symbol_to_lowlevel! {
|
|||
StrToNum <= STR_TO_NUM;
|
||||
StrWithCapacity <= STR_WITH_CAPACITY;
|
||||
StrReleaseExcessCapacity <= STR_RELEASE_EXCESS_CAPACITY;
|
||||
ListLen <= LIST_LEN;
|
||||
ListLenU64 <= LIST_LEN_U64;
|
||||
ListLenUsize <= LIST_LEN_USIZE;
|
||||
ListGetCapacity <= LIST_CAPACITY;
|
||||
ListWithCapacity <= LIST_WITH_CAPACITY;
|
||||
ListReserve <= LIST_RESERVE;
|
||||
|
|
|
@ -1342,7 +1342,7 @@ define_builtins! {
|
|||
3 LIST_SET: "set"
|
||||
4 LIST_APPEND: "append"
|
||||
5 LIST_MAP: "map"
|
||||
6 LIST_LEN: "len"
|
||||
6 LIST_LEN_U64: "len"
|
||||
7 LIST_WALK_BACKWARDS: "walkBackwards"
|
||||
8 LIST_CONCAT: "concat"
|
||||
9 LIST_FIRST: "first"
|
||||
|
@ -1424,6 +1424,7 @@ define_builtins! {
|
|||
85 LIST_PREPEND_IF_OK: "prependIfOk"
|
||||
86 LIST_WALK_WITH_INDEX_UNTIL: "walkWithIndexUntil"
|
||||
87 LIST_CLONE: "clone"
|
||||
88 LIST_LEN_USIZE: "lenUsize"
|
||||
}
|
||||
7 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias
|
||||
|
|
|
@ -668,8 +668,8 @@ fn eq_list<'a>(
|
|||
|
||||
let len_1 = root.create_symbol(ident_ids, "len_1");
|
||||
let len_2 = root.create_symbol(ident_ids, "len_2");
|
||||
let len_1_stmt = |next| let_lowlevel(arena, layout_isize, len_1, ListLen, &[ARG_1], next);
|
||||
let len_2_stmt = |next| let_lowlevel(arena, layout_isize, len_2, ListLen, &[ARG_2], next);
|
||||
let len_1_stmt = |next| let_lowlevel(arena, layout_isize, len_1, ListLenUsize, &[ARG_1], next);
|
||||
let len_2_stmt = |next| let_lowlevel(arena, layout_isize, len_2, ListLenUsize, &[ARG_2], next);
|
||||
|
||||
let eq_len = root.create_symbol(ident_ids, "eq_len");
|
||||
let eq_len_stmt = |next| let_lowlevel(arena, LAYOUT_BOOL, eq_len, Eq, &[len_1, len_2], next);
|
||||
|
|
|
@ -926,7 +926,7 @@ fn refcount_list<'a>(
|
|||
//
|
||||
|
||||
let len = root.create_symbol(ident_ids, "len");
|
||||
let len_stmt = |next| let_lowlevel(arena, layout_isize, len, ListLen, &[structure], next);
|
||||
let len_stmt = |next| let_lowlevel(arena, layout_isize, len, ListLenUsize, &[structure], next);
|
||||
|
||||
// let zero = 0
|
||||
let zero = root.create_symbol(ident_ids, "zero");
|
||||
|
|
|
@ -1533,8 +1533,8 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC {
|
|||
|
||||
match lowlevel {
|
||||
Unreachable => RC::Uknown,
|
||||
ListLen | StrIsEmpty | StrCountUtf8Bytes | ListGetCapacity | ListWithCapacity
|
||||
| StrWithCapacity => RC::NoRc,
|
||||
ListLenU64 | ListLenUsize | StrIsEmpty | StrCountUtf8Bytes | ListGetCapacity
|
||||
| ListWithCapacity | StrWithCapacity => RC::NoRc,
|
||||
ListReplaceUnsafe => RC::Rc,
|
||||
StrGetUnsafe | ListGetUnsafe => RC::NoRc,
|
||||
ListConcat => RC::Rc,
|
||||
|
|
|
@ -1283,7 +1283,7 @@ fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] {
|
|||
match op {
|
||||
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
|
||||
DictPseudoSeed => arena.alloc_slice_copy(&[irrelevant]),
|
||||
ListLen | StrIsEmpty | StrCountUtf8Bytes | ListGetCapacity => {
|
||||
ListLenU64 | ListLenUsize | StrIsEmpty | StrCountUtf8Bytes | ListGetCapacity => {
|
||||
arena.alloc_slice_copy(&[borrowed])
|
||||
}
|
||||
ListWithCapacity | StrWithCapacity => arena.alloc_slice_copy(&[irrelevant]),
|
||||
|
|
|
@ -1769,7 +1769,7 @@ fn test_to_comparison<'a>(
|
|||
LayoutRepr::Builtin(Builtin::List(_elem_layout)) => {
|
||||
let real_len_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::ListLen,
|
||||
op: LowLevel::ListLenUsize,
|
||||
update_mode: env.next_update_mode_id(),
|
||||
},
|
||||
arguments: env.arena.alloc([list_sym]),
|
||||
|
@ -2346,7 +2346,7 @@ fn decide_to_branching<'a>(
|
|||
|
||||
let len_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::ListLen,
|
||||
op: LowLevel::ListLenUsize,
|
||||
update_mode: env.next_update_mode_id(),
|
||||
},
|
||||
arguments: env.arena.alloc([inner_cond_symbol]),
|
||||
|
|
|
@ -1412,7 +1412,7 @@ pub(crate) fn build_list_index_probe<'a>(
|
|||
let len_sym = env.unique_symbol();
|
||||
let len_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::ListLen,
|
||||
op: LowLevel::ListLenUsize,
|
||||
update_mode: env.next_update_mode_id(),
|
||||
},
|
||||
arguments: env.arena.alloc([list_sym]),
|
||||
|
@ -1570,7 +1570,7 @@ fn store_list_rest<'a>(
|
|||
let list_len_sym = env.unique_symbol();
|
||||
let list_len_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::ListLen,
|
||||
op: LowLevel::ListLenUsize,
|
||||
update_mode: env.next_update_mode_id(),
|
||||
},
|
||||
arguments: env.arena.alloc([list_sym]),
|
||||
|
|
|
@ -69,7 +69,8 @@ enum FirstOrder {
|
|||
StrToUtf8,
|
||||
StrRepeat,
|
||||
StrFromFloat,
|
||||
ListLen,
|
||||
ListLenU64,
|
||||
ListLenUsize,
|
||||
ListGetUnsafe,
|
||||
ListSublist,
|
||||
ListDropAt,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue