diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index fcea7197c2..9a38e3518d 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -489,7 +489,20 @@ pub fn types() -> MutMap { ), ); - // walkRight : List elem, (elem -> accum -> accum), accum -> accum + // walk : List elem, (elem -> accum -> accum), accum -> accum + add_type( + Symbol::LIST_WALK, + top_level_function( + vec![ + list_type(flex(TVAR1)), + closure(vec![flex(TVAR1), flex(TVAR2)], TVAR3, Box::new(flex(TVAR2))), + flex(TVAR2), + ], + Box::new(flex(TVAR2)), + ), + ); + + // walkBackwards : List elem, (elem -> accum -> accum), accum -> accum add_type( Symbol::LIST_WALK_BACKWARDS, top_level_function( diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index e5a2e5b1ef..463e22ee07 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -777,7 +777,34 @@ pub fn types() -> MutMap { ) }); - // walkRight : Attr (* | u) (List (Attr u a)) + // walk : Attr (* | u) (List (Attr u a)) + // , Attr Shared (Attr u a -> b -> b) + // , b + // -> b + add_type(Symbol::LIST_WALK, { + let_tvars! { u, a, b, star1, closure }; + + unique_function( + vec![ + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + container(star1, vec![u]), + SolvedType::Apply(Symbol::LIST_LIST, vec![attr_type(u, a)]), + ], + ), + shared(SolvedType::Func( + vec![attr_type(u, a), flex(b)], + Box::new(flex(closure)), + Box::new(flex(b)), + )), + flex(b), + ], + flex(b), + ) + }); + + // walkBackwards : Attr (* | u) (List (Attr u a)) // , Attr Shared (Attr u a -> b -> b) // , b // -> b diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 5e6e794381..fda8b43831 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -71,6 +71,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::LIST_JOIN => list_join, Symbol::LIST_MAP => list_map, Symbol::LIST_KEEP_IF => list_keep_if, + Symbol::LIST_WALK => list_walk, Symbol::LIST_WALK_BACKWARDS => list_walk_backwards, Symbol::NUM_ADD => num_add, Symbol::NUM_ADD_CHECKED => num_add_checked, @@ -1313,14 +1314,43 @@ fn list_join(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum +/// List.walk : List elem, (elem -> accum -> accum), accum -> accum +fn list_walk(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let func_var = var_store.fresh(); + let accum_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::ListWalk, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (func_var, Var(Symbol::ARG_2)), + (accum_var, Var(Symbol::ARG_3)), + ], + ret_var: accum_var, + }; + + defn( + symbol, + vec![ + (list_var, Symbol::ARG_1), + (func_var, Symbol::ARG_2), + (accum_var, Symbol::ARG_3), + ], + var_store, + body, + accum_var, + ) +} + +/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let func_var = var_store.fresh(); let accum_var = var_store.fresh(); let body = RunLowLevel { - op: LowLevel::ListWalkRight, + op: LowLevel::ListWalkBackwards, args: vec![ (list_var, Var(Symbol::ARG_1)), (func_var, Var(Symbol::ARG_2)), diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 34e815237a..65a7926c36 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1,7 +1,7 @@ use crate::llvm::build_list::{ allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, list_get_unsafe, list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat, - list_reverse, list_set, list_single, list_sum, list_walk_backwards, + list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards, }; use crate::llvm::build_str::{ str_concat, str_count_graphemes, str_len, str_split, str_starts_with, CHAR_LAYOUT, @@ -2491,8 +2491,28 @@ fn run_low_level<'a, 'ctx, 'env>( list_contains(env, parent, elem, elem_layout, list, list_layout) } - ListWalkRight => { - // List.walkRight : List elem, (elem -> accum -> accum), accum -> accum + ListWalk => { + debug_assert_eq!(args.len(), 3); + + let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]); + + let (func, func_layout) = load_symbol_and_layout(env, scope, &args[1]); + + let (default, default_layout) = load_symbol_and_layout(env, scope, &args[2]); + + list_walk( + env, + parent, + list, + list_layout, + func, + func_layout, + default, + default_layout, + ) + } + ListWalkBackwards => { + // List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum debug_assert_eq!(args.len(), 3); let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]); diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index 76db9297e3..b3fc8a564b 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -809,7 +809,98 @@ pub fn list_sum<'a, 'ctx, 'env>( builder.build_load(accum_alloca, "load_final_acum") } -/// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum +/// List.walk : List elem, (elem -> accum -> accum), accum -> accum +pub fn list_walk<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + list: BasicValueEnum<'ctx>, + list_layout: &Layout<'a>, + func: BasicValueEnum<'ctx>, + func_layout: &Layout<'a>, + default: BasicValueEnum<'ctx>, + default_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let ctx = env.context; + let builder = env.builder; + + let list_wrapper = list.into_struct_value(); + let len = list_len(env.builder, list_wrapper); + + let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes); + let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum"); + builder.build_store(accum_alloca, default); + + let then_block = ctx.append_basic_block(parent, "then"); + let cont_block = ctx.append_basic_block(parent, "branchcont"); + + let condition = builder.build_int_compare( + IntPredicate::UGT, + len, + ctx.i64_type().const_zero(), + "list_non_empty", + ); + + builder.build_conditional_branch(condition, then_block, cont_block); + + builder.position_at_end(then_block); + + match (func, func_layout) { + (BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, _)) => { + let elem_layout = match list_layout { + Layout::Builtin(Builtin::List(_, layout)) => layout, + _ => unreachable!("can only fold over a list"), + }; + + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let elem_ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type); + + let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| { + // load current accumulator + let current = builder.build_load(accum_alloca, "retrieve_accum"); + + let call_site_value = + builder.build_call(func_ptr, &[elem, current], "#walk_right_func"); + + // set the calling convention explicitly for this call + call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV); + + let new_current = call_site_value + .try_as_basic_value() + .left() + .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")); + + builder.build_store(accum_alloca, new_current); + }; + + incrementing_elem_loop( + builder, + ctx, + parent, + list_ptr, + len, + "#index", + walk_right_loop, + ); + } + + _ => { + unreachable!( + "Invalid function basic value enum or layout for List.keepIf : {:?}", + (func, func_layout) + ); + } + } + + builder.build_unconditional_branch(cont_block); + + builder.position_at_end(cont_block); + + builder.build_load(accum_alloca, "load_final_acum") +} + +/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum #[allow(clippy::too_many_arguments)] pub fn list_walk_backwards<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -875,7 +966,7 @@ pub fn list_walk_backwards<'a, 'ctx, 'env>( builder.build_store(accum_alloca, new_current); }; - incrementing_elem_loop( + decrementing_elem_loop( builder, ctx, parent, @@ -1537,6 +1628,7 @@ where let current_index = builder .build_load(index_alloca, index_name) .into_int_value(); + let next_index = builder.build_int_sub(current_index, one, "nextindex"); builder.build_store(index_alloca, next_index); @@ -1546,7 +1638,7 @@ where // #index >= 0 let condition = builder.build_int_compare( - IntPredicate::UGE, + IntPredicate::SGE, next_index, ctx.i64_type().const_zero(), "bounds_check", diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index a155dad2b1..2f5692169c 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -267,13 +267,13 @@ mod gen_list { fn list_walk_backwards_with_str() { assert_evals_to!( r#"List.walkBackwards [ "x", "y", "z" ] Str.concat "<""#, - RocStr::from("zyx<"), + RocStr::from("xyz<"), RocStr ); assert_evals_to!( r#"List.walkBackwards [ "Third", "Second", "First" ] Str.concat "Fourth""#, - RocStr::from("FirstSecondThirdFourth"), + RocStr::from("ThirdSecondFirstFourth"), RocStr ); } diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 5c9a90f1ae..f2d03586b9 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -22,7 +22,8 @@ pub enum LowLevel { ListJoin, ListMap, ListKeepIf, - ListWalkRight, + ListWalk, + ListWalkBackwards, ListSum, NumAdd, NumAddWrap, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index fd6e1f1a20..fac0c0a100 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -683,18 +683,18 @@ define_builtins! { 5 LIST_APPEND: "append" 6 LIST_MAP: "map" 7 LIST_LEN: "len" - 8 LIST_FOLDL: "foldl" - 9 LIST_WALK_BACKWARDS: "walkBackwards" - 10 LIST_CONCAT: "concat" - 11 LIST_FIRST: "first" - 12 LIST_SINGLE: "single" - 13 LIST_REPEAT: "repeat" - 14 LIST_REVERSE: "reverse" - 15 LIST_PREPEND: "prepend" - 16 LIST_JOIN: "join" - 17 LIST_KEEP_IF: "keepIf" - 18 LIST_CONTAINS: "contains" - 19 LIST_SUM: "sum" + 8 LIST_WALK_BACKWARDS: "walkBackwards" + 9 LIST_CONCAT: "concat" + 10 LIST_FIRST: "first" + 11 LIST_SINGLE: "single" + 12 LIST_REPEAT: "repeat" + 13 LIST_REVERSE: "reverse" + 14 LIST_PREPEND: "prepend" + 15 LIST_JOIN: "join" + 16 LIST_KEEP_IF: "keepIf" + 17 LIST_CONTAINS: "contains" + 18 LIST_SUM: "sum" + 19 LIST_WALK: "walk" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 82789d81e0..b6fd077d54 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -535,7 +535,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListMap => arena.alloc_slice_copy(&[owned, irrelevant]), ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]), ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), - ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]), + ListWalk => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]), + ListWalkBackwards => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]), ListSum => arena.alloc_slice_copy(&[borrowed]), Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt