Implement List.map4

This commit is contained in:
Kevin Sjöberg 2021-10-31 08:08:24 +01:00
parent b29a029a33
commit f9ed060e49
16 changed files with 419 additions and 16 deletions

View file

@ -140,6 +140,7 @@ const Caller0 = fn (?[*]u8, ?[*]u8) callconv(.C) void;
const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller4 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn listReverse(list: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
@ -352,6 +353,70 @@ pub fn listMap3(
}
}
pub fn listMap4(
list1: RocList,
list2: RocList,
list3: RocList,
list4: RocList,
caller: Caller4,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
alignment: u32,
a_width: usize,
b_width: usize,
c_width: usize,
d_width: usize,
e_width: usize,
dec_a: Dec,
dec_b: Dec,
dec_c: Dec,
dec_d: Dec,
) callconv(.C) RocList {
const output_length = std.math.min(std.math.min(list1.len(), list2.len()), std.math.min(list3.len(), list4.len()));
decrementTail(list1, output_length, a_width, dec_a);
decrementTail(list2, output_length, b_width, dec_b);
decrementTail(list3, output_length, c_width, dec_c);
decrementTail(list4, output_length, d_width, dec_d);
if (data_is_owned) {
inc_n_data(data, output_length);
}
if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| {
if (list3.bytes) |source_c| {
if (list4.bytes) |source_d| {
const output = RocList.allocate(alignment, output_length, e_width);
const target_ptr = output.bytes orelse unreachable;
var i: usize = 0;
while (i < output_length) : (i += 1) {
const element_a = source_a + i * a_width;
const element_b = source_b + i * b_width;
const element_c = source_c + i * c_width;
const element_d = source_d + i * d_width;
const target = target_ptr + i * e_width;
caller(data, element_a, element_b, element_c, element_d, target);
}
return output;
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
}
pub fn listKeepIf(
list: RocList,
caller: Caller1,

View file

@ -26,6 +26,7 @@ comptime {
exportListFn(list.listMap, "map");
exportListFn(list.listMap2, "map2");
exportListFn(list.listMap3, "map3");
exportListFn(list.listMap4, "map4");
exportListFn(list.listMapWithIndex, "map_with_index");
exportListFn(list.listKeepIf, "keep_if");
exportListFn(list.listWalk, "walk");

View file

@ -25,6 +25,7 @@ interface List
map,
map2,
map3,
map4,
mapWithIndex,
mapOrDrop,
mapJoin,
@ -269,6 +270,11 @@ map2 : List a, List b, (a, b -> c) -> List c
## Repeat until a list runs out of elements.
map3 : List a, List b, List c, (a, b, c -> d) -> List d
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
mapWithIndex : List before, (before, Nat -> after) -> List after

View file

@ -165,6 +165,7 @@ pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list";
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_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index";
pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";

View file

@ -930,6 +930,27 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
)
};
{
let_tvars! {a, b, c, d, e, cvar};
// map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
add_top_level_function_type!(
Symbol::LIST_MAP4,
vec![
list_type(flex(a)),
list_type(flex(b)),
list_type(flex(c)),
list_type(flex(d)),
closure(
vec![flex(a), flex(b), flex(c), flex(d)],
cvar,
Box::new(flex(e))
),
],
Box::new(list_type(flex(e))),
)
};
// append : List elem, elem -> List elem
add_top_level_function_type!(
Symbol::LIST_APPEND,

View file

@ -89,6 +89,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_MAP => list_map,
LIST_MAP2 => list_map2,
LIST_MAP3 => list_map3,
LIST_MAP4 => list_map4,
LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at,
LIST_DROP_LAST => list_drop_last,
@ -290,6 +291,41 @@ fn lowlevel_4(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
)
}
fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
let arg1_var = var_store.fresh();
let arg2_var = var_store.fresh();
let arg3_var = var_store.fresh();
let arg4_var = var_store.fresh();
let arg5_var = var_store.fresh();
let ret_var = var_store.fresh();
let body = RunLowLevel {
op,
args: vec![
(arg1_var, Var(Symbol::ARG_1)),
(arg2_var, Var(Symbol::ARG_2)),
(arg3_var, Var(Symbol::ARG_3)),
(arg4_var, Var(Symbol::ARG_4)),
(arg5_var, Var(Symbol::ARG_5)),
],
ret_var,
};
defn(
symbol,
vec![
(arg1_var, Symbol::ARG_1),
(arg2_var, Symbol::ARG_2),
(arg3_var, Symbol::ARG_3),
(arg4_var, Symbol::ARG_4),
(arg5_var, Symbol::ARG_5),
],
var_store,
body,
ret_var,
)
}
/// Num.maxInt : Int
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
@ -2546,6 +2582,11 @@ fn list_map3(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_4(symbol, LowLevel::ListMap3, var_store)
}
/// List.map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
fn list_map4(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_5(symbol, LowLevel::ListMap4, var_store)
}
/// List.sortWith : List a, (a, a -> Ordering) -> List a
fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListSortWith, var_store)

View file

@ -10,9 +10,9 @@ use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{
self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat,
list_contains, list_drop, list_drop_at, list_get_unsafe, list_join, list_keep_errs,
list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index,
list_prepend, list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with,
list_swap,
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_single, list_sort_with, list_swap,
};
use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
@ -4609,6 +4609,67 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
_ => unreachable!("invalid list layout"),
}
}
ListMap4 { xs, ys, zs, ws } => {
let (list1, list1_layout) = load_symbol_and_layout(scope, &xs);
let (list2, list2_layout) = load_symbol_and_layout(scope, &ys);
let (list3, list3_layout) = load_symbol_and_layout(scope, &zs);
let (list4, list4_layout) = load_symbol_and_layout(scope, &ws);
let (function, closure, closure_layout) = function_details!();
match (
list1_layout,
list2_layout,
list3_layout,
list4_layout,
return_layout,
) {
(
Layout::Builtin(Builtin::List(element1_layout)),
Layout::Builtin(Builtin::List(element2_layout)),
Layout::Builtin(Builtin::List(element3_layout)),
Layout::Builtin(Builtin::List(element4_layout)),
Layout::Builtin(Builtin::List(result_layout)),
) => {
let argument_layouts = &[
**element1_layout,
**element2_layout,
**element3_layout,
**element4_layout,
];
let roc_function_call = roc_function_call(
env,
layout_ids,
function,
closure,
closure_layout,
function_owns_closure_data,
argument_layouts,
);
list_map4(
env,
layout_ids,
roc_function_call,
list1,
list2,
list3,
list4,
element1_layout,
element2_layout,
element3_layout,
element4_layout,
result_layout,
)
}
(Layout::Builtin(Builtin::EmptyList), _, _, _, _)
| (_, Layout::Builtin(Builtin::EmptyList), _, _, _)
| (_, _, Layout::Builtin(Builtin::EmptyList), _, _)
| (_, _, _, Layout::Builtin(Builtin::EmptyList), _) => empty_list(env),
_ => unreachable!("invalid list layout"),
}
}
ListMapWithIndex { xs } => {
// List.mapWithIndex : List before, (Nat, before -> after) -> List after
let (list, list_layout) = load_symbol_and_layout(scope, &xs);
@ -5640,7 +5701,7 @@ fn run_low_level<'a, 'ctx, 'env>(
cond
}
ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk
ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
| DictWalk => unreachable!("these are higher order, and are handled elsewhere"),
}

View file

@ -821,6 +821,51 @@ pub fn list_map3<'a, 'ctx, 'env>(
)
}
pub fn list_map4<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
list3: BasicValueEnum<'ctx>,
list4: BasicValueEnum<'ctx>,
element1_layout: &Layout<'a>,
element2_layout: &Layout<'a>,
element3_layout: &Layout<'a>,
element4_layout: &Layout<'a>,
result_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let dec_a = build_dec_wrapper(env, layout_ids, element1_layout);
let dec_b = build_dec_wrapper(env, layout_ids, element2_layout);
let dec_c = build_dec_wrapper(env, layout_ids, element3_layout);
let dec_d = build_dec_wrapper(env, layout_ids, element4_layout);
call_bitcode_fn_returns_list(
env,
&[
pass_list_cc(env, list1),
pass_list_cc(env, list2),
pass_list_cc(env, list3),
pass_list_cc(env, list4),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(result_layout),
layout_width(env, element1_layout),
layout_width(env, element2_layout),
layout_width(env, element3_layout),
layout_width(env, element4_layout),
layout_width(env, result_layout),
dec_a.as_global_value().as_pointer_value().into(),
dec_b.as_global_value().as_pointer_value().into(),
dec_c.as_global_value().as_pointer_value().into(),
dec_d.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_MAP4,
)
}
/// List.concat : List elem, List elem -> List elem
pub fn list_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -33,6 +33,7 @@ pub enum LowLevel {
ListMap,
ListMap2,
ListMap3,
ListMap4,
ListMapWithIndex,
ListKeepIf,
ListWalk,
@ -211,6 +212,7 @@ macro_rules! higher_order {
ListMap
| ListMap2
| ListMap3
| ListMap4
| ListMapWithIndex
| ListKeepIf
| ListWalk
@ -243,6 +245,7 @@ impl LowLevel {
ListMap => 1,
ListMap2 => 2,
ListMap3 => 3,
ListMap4 => 4,
ListMapWithIndex => 1,
ListKeepIf => 1,
ListWalk => 2,

View file

@ -1060,6 +1060,7 @@ define_builtins! {
37 LIST_MIN_LT: "#minlt"
38 LIST_MAX: "max"
39 LIST_MAX_GT: "#maxGt"
40 LIST_MAP4: "map4"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -771,6 +771,37 @@ fn call_spec(
builder.add_call(block, spec_var, module, name, argument)?;
}
ListMap4 { xs, ys, zs, ws } => {
let list1 = env.symbols[xs];
let list2 = env.symbols[ys];
let list3 = env.symbols[zs];
let list4 = env.symbols[ws];
let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?;
let elem2 = builder.add_bag_get(block, bag2)?;
let bag3 = builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let _cell3 = builder.add_get_tuple_field(block, list3, LIST_CELL_INDEX)?;
let elem3 = builder.add_bag_get(block, bag3)?;
let bag4 = builder.add_get_tuple_field(block, list4, LIST_BAG_INDEX)?;
let _cell4 = builder.add_get_tuple_field(block, list4, LIST_CELL_INDEX)?;
let elem4 = builder.add_bag_get(block, bag4)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem2, elem3, elem4])?
} else {
builder.add_make_tuple(block, &[elem1, elem2, elem3, elem4, closure_env])?
};
builder.add_call(block, spec_var, module, name, argument)?;
}
ListKeepIf { xs } | ListKeepOks { xs } | ListKeepErrs { xs } => {
let list = env.symbols[xs];
let closure_env = env.symbols[function_env];

View file

@ -651,6 +651,21 @@ impl<'a> BorrowInfState<'a> {
self.own_var(*zs);
}
}
ListMap4 { xs, ys, zs, ws } => {
// own the lists if the function wants to own the element
if !function_ps[0].borrow {
self.own_var(*xs);
}
if !function_ps[1].borrow {
self.own_var(*ys);
}
if !function_ps[2].borrow {
self.own_var(*zs);
}
if !function_ps[3].borrow {
self.own_var(*ws);
}
}
ListSortWith { xs } => {
// always own the input list
self.own_var(*xs);
@ -933,6 +948,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, function, closure_data]),
ListMap2 => arena.alloc_slice_copy(&[owned, owned, function, closure_data]),
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]),
ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]),
ListKeepIf | ListKeepOks | ListKeepErrs => {
arena.alloc_slice_copy(&[owned, function, closure_data])
}

View file

@ -576,6 +576,27 @@ impl<'a> Context<'a> {
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListMap4 { xs, ys, zs, ws } => {
let borrows = [
function_ps[0].borrow,
function_ps[1].borrow,
function_ps[2].borrow,
function_ps[3].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
let b = decref_if_owned!(function_ps[2].borrow, *zs, b);
let b = decref_if_owned!(function_ps[3].borrow, *ws, b);
let v = create_call!(function_ps.get(3));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListMapWithIndex { xs } => {
let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA];

View file

@ -4134,6 +4134,16 @@ pub fn with_hole<'a>(
match_on_closure_argument!(ListMap3, [xs, ys, zs])
}
ListMap4 => {
debug_assert_eq!(arg_symbols.len(), 5);
let xs = arg_symbols[0];
let ys = arg_symbols[1];
let zs = arg_symbols[2];
let ws = arg_symbols[3];
match_on_closure_argument!(ListMap4, [xs, ys, zs, ws])
}
_ => {
let call = self::Call {
call_type: CallType::LowLevel {

View file

@ -2,18 +2,55 @@ use roc_module::symbol::Symbol;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum HigherOrder {
ListMap { xs: Symbol },
ListMap2 { xs: Symbol, ys: Symbol },
ListMap3 { xs: Symbol, ys: Symbol, zs: Symbol },
ListMapWithIndex { xs: Symbol },
ListKeepIf { xs: Symbol },
ListWalk { xs: Symbol, state: Symbol },
ListWalkUntil { xs: Symbol, state: Symbol },
ListWalkBackwards { xs: Symbol, state: Symbol },
ListKeepOks { xs: Symbol },
ListKeepErrs { xs: Symbol },
ListSortWith { xs: Symbol },
DictWalk { xs: Symbol, state: Symbol },
ListMap {
xs: Symbol,
},
ListMap2 {
xs: Symbol,
ys: Symbol,
},
ListMap3 {
xs: Symbol,
ys: Symbol,
zs: Symbol,
},
ListMap4 {
xs: Symbol,
ys: Symbol,
zs: Symbol,
ws: Symbol,
},
ListMapWithIndex {
xs: Symbol,
},
ListKeepIf {
xs: Symbol,
},
ListWalk {
xs: Symbol,
state: Symbol,
},
ListWalkUntil {
xs: Symbol,
state: Symbol,
},
ListWalkBackwards {
xs: Symbol,
state: Symbol,
},
ListKeepOks {
xs: Symbol,
},
ListKeepErrs {
xs: Symbol,
},
ListSortWith {
xs: Symbol,
},
DictWalk {
xs: Symbol,
state: Symbol,
},
}
impl HigherOrder {
@ -22,6 +59,7 @@ impl HigherOrder {
HigherOrder::ListMap { .. } => 1,
HigherOrder::ListMap2 { .. } => 2,
HigherOrder::ListMap3 { .. } => 3,
HigherOrder::ListMap4 { .. } => 4,
HigherOrder::ListMapWithIndex { .. } => 2,
HigherOrder::ListKeepIf { .. } => 1,
HigherOrder::ListWalk { .. } => 2,
@ -258,6 +296,17 @@ fn from_low_level(low_level: &LowLevel, arguments: &[Symbol]) -> FirstOrHigher {
function_env: arguments[4],
})
}
LowLevel::ListMap4 => {
debug_assert_eq!(arguments.len(), 6);
Higher(ListMap4 {
xs: arguments[0],
ys: arguments[1],
zs: arguments[2],
ws: arguments[3],
function_name: arguments[4],
function_env: arguments[5],
})
}
LowLevel::ListMapWithIndex => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListMapWithIndex {

View file

@ -763,6 +763,37 @@ fn list_map_closure() {
);
}
#[test]
fn list_map4_group() {
assert_evals_to!(
indoc!(
r#"
List.map4 [1,2,3] [3,2,1] [2,1,3] [3,1,2] (\a, b, c, d -> Group a b c d)
"#
),
RocList::from_slice(&[(1, 3, 2, 3), (2, 2, 1, 1), (3, 1, 3, 2)]),
RocList<(i64, i64, i64, i64)>
);
}
#[test]
fn list_map4_different_length() {
assert_evals_to!(
indoc!(
r#"
List.map4
["h", "i", "j", "k"]
["o", "p", "q"]
["l", "m"]
["a"]
(\a, b, c, d -> Str.concat a (Str.concat b (Str.concat c d)))
"#
),
RocList::from_slice(&[RocStr::from_slice("hola".as_bytes()),]),
RocList<RocStr>
);
}
#[test]
fn list_map3_group() {
assert_evals_to!(