implement List.map in the dev backend

This commit is contained in:
Folkert 2023-04-23 17:35:17 +02:00
parent b62ee37d8e
commit b59ada4bc8
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
8 changed files with 449 additions and 21 deletions

View file

@ -496,8 +496,8 @@ all = \list, predicate ->
Break {}
when List.iterate list {} looper is
Continue {} -> Bool.true
Break {} -> Bool.false
Continue {} -> Bool.true
Break {} -> "" != ""
## Run the given function on each element of a list, and return all the
## elements for which the function returned `Bool.true`.

View file

@ -7,13 +7,15 @@ use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::code_gen_help::{CallerProc, CodeGenHelp, HelperOp};
use roc_mono::ir::{
BranchInfo, JoinPointId, ListLiteralElement, Literal, Param, ProcLayout, SelfRecursive, Stmt,
BranchInfo, HigherOrderLowLevel, JoinPointId, ListLiteralElement, Literal, Param, ProcLayout,
SelfRecursive, Stmt,
};
use roc_mono::layout::{
Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout,
};
use roc_mono::low_level::HigherOrder;
use roc_target::TargetInfo;
use std::marker::PhantomData;
@ -496,6 +498,7 @@ pub struct Backend64Bit<
interns: &'r mut Interns,
helper_proc_gen: CodeGenHelp<'a>,
helper_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>,
caller_procs: Vec<'a, CallerProc<'a>>,
buf: Vec<'a, u8>,
relocs: Vec<'a, Relocation>,
proc_name: Option<String>,
@ -533,6 +536,7 @@ pub fn new_backend_64bit<
layout_interner,
helper_proc_gen: CodeGenHelp::new(env.arena, target_info, env.module_id),
helper_proc_symbols: bumpalo::vec![in env.arena],
caller_procs: bumpalo::vec![in env.arena],
proc_name: None,
is_self_recursive: None,
buf: bumpalo::vec![in env.arena],
@ -574,6 +578,9 @@ impl<
fn interns(&self) -> &Interns {
self.interns
}
fn interns_mut(&mut self) -> &mut Interns {
self.interns
}
fn interner(&self) -> &STLayoutInterner<'a> {
self.layout_interner
}
@ -584,12 +591,14 @@ impl<
&mut STLayoutInterner<'a>,
&mut Interns,
&mut CodeGenHelp<'a>,
&mut Vec<'a, CallerProc<'a>>,
) {
(
self.env.module_id,
self.layout_interner,
self.interns,
&mut self.helper_proc_gen,
&mut self.caller_procs,
)
}
fn helper_proc_gen_mut(&mut self) -> &mut CodeGenHelp<'a> {
@ -1579,6 +1588,174 @@ impl<
}
}
fn build_higher_order_lowlevel(
&mut self,
dst: &Symbol,
higher_order: &HigherOrderLowLevel<'a>,
ret_layout: InLayout<'a>,
) {
let ident_ids = self
.interns
.all_ident_ids
.get_mut(&self.env.module_id)
.unwrap();
let (inc_n_data_symbol, inc_n_data_linker_data) = self.helper_proc_gen.gen_refcount_proc(
ident_ids,
self.layout_interner,
Layout::UNIT,
HelperOp::Inc,
);
let caller_proc = CallerProc::new(
self.env.arena,
self.env.module_id,
ident_ids,
self.layout_interner,
&higher_order.passed_function,
higher_order.closure_env_layout,
);
match higher_order.op {
HigherOrder::ListMap { xs } => {
let old_element_layout = higher_order.passed_function.argument_layouts[0];
let new_element_layout = higher_order.passed_function.return_layout;
let input_list_layout = Layout::Builtin(Builtin::List(old_element_layout));
let input_list_in_layout = self.layout_interner.insert(input_list_layout);
let caller = self.debug_symbol("caller");
let data = self.debug_symbol("data");
let alignment = self.debug_symbol("alignment");
let old_element_width = self.debug_symbol("old_element_width");
let new_element_width = self.debug_symbol("new_element_width");
self.load_layout_alignment(new_element_layout, alignment);
self.load_layout_stack_size(old_element_layout, old_element_width);
self.load_layout_stack_size(new_element_layout, new_element_width);
self.helper_proc_symbols.extend(inc_n_data_linker_data);
self.helper_proc_symbols
.extend([(caller_proc.proc_symbol, caller_proc.proc_layout)]);
let inc_n_data_string = self.function_symbol_to_string(
inc_n_data_symbol,
std::iter::empty(),
None,
Layout::UNIT,
);
let caller_string = self.function_symbol_to_string(
caller_proc.proc_symbol,
std::iter::empty(),
None,
Layout::UNIT,
);
self.caller_procs.push(caller_proc);
let inc_n_data = Symbol::DEV_TMP5;
self.build_fn_pointer(&inc_n_data, inc_n_data_string);
self.build_fn_pointer(&caller, caller_string);
if let Some(_closure_data_layout) = higher_order.closure_env_layout {
let data_symbol = higher_order.passed_function.captured_environment;
self.storage_manager
.ensure_symbol_on_stack(&mut self.buf, &data_symbol);
let (new_elem_offset, _) =
self.storage_manager.stack_offset_and_size(&data_symbol);
// Load address of output element into register.
let reg = self.storage_manager.claim_general_reg(&mut self.buf, &data);
ASM::add_reg64_reg64_imm32(
&mut self.buf,
reg,
CC::BASE_PTR_REG,
new_elem_offset,
);
} else {
// use a null pointer
self.load_literal(&data, &Layout::U64, &Literal::Int(0u128.to_be_bytes()));
}
self.load_literal(
&Symbol::DEV_TMP3,
&Layout::BOOL,
&Literal::Bool(higher_order.passed_function.owns_captured_environment),
);
// list: RocList,
// caller: Caller1,
// data: Opaque,
// inc_n_data: IncN,
// data_is_owned: bool,
// alignment: u32,
// old_element_width: usize,
// new_element_width: usize,
let arguments = [
xs,
caller,
data,
inc_n_data,
Symbol::DEV_TMP3,
alignment,
old_element_width,
new_element_width,
];
let ptr = Layout::U64;
let usize_ = Layout::U64;
let layouts = [
input_list_in_layout,
ptr,
ptr,
ptr,
Layout::BOOL,
Layout::U32,
usize_,
usize_,
];
// Setup the return location.
let base_offset = self
.storage_manager
.claim_stack_area(dst, self.layout_interner.stack_size(ret_layout));
self.build_fn_call(
&Symbol::DEV_TMP4,
bitcode::LIST_MAP.to_string(),
&arguments,
&layouts,
&ret_layout,
);
self.free_symbol(&Symbol::DEV_TMP3);
self.free_symbol(&Symbol::DEV_TMP5);
// Return list value from fn call
self.storage_manager.copy_symbol_to_stack_offset(
self.layout_interner,
&mut self.buf,
base_offset,
&Symbol::DEV_TMP4,
&ret_layout,
);
self.free_symbol(&Symbol::DEV_TMP4);
}
HigherOrder::ListMap2 { xs, ys } => todo!(),
HigherOrder::ListMap3 { xs, ys, zs } => todo!(),
HigherOrder::ListMap4 { xs, ys, zs, ws } => {
todo!()
}
HigherOrder::ListSortWith { xs } => todo!(),
}
}
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) {
self.storage_manager.list_len(&mut self.buf, dst, list);
}

View file

@ -12,10 +12,10 @@ use roc_error_macros::internal_error;
use roc_module::ident::ModuleName;
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::code_gen_help::{CallerProc, CodeGenHelp};
use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
SelfRecursive, Stmt,
BranchInfo, CallType, Expr, HigherOrderLowLevel, JoinPointId, ListLiteralElement, Literal,
Param, Proc, ProcLayout, SelfRecursive, Stmt,
};
use roc_mono::layout::{
Builtin, InLayout, Layout, LayoutIds, LayoutInterner, STLayoutInterner, TagIdIntType,
@ -65,8 +65,21 @@ pub enum Relocation {
trait Backend<'a> {
fn env(&self) -> &Env<'a>;
fn interns(&self) -> &Interns;
fn interns_mut(&mut self) -> &mut Interns;
fn interner(&self) -> &STLayoutInterner<'a>;
fn debug_symbol(&mut self, name: &str) -> Symbol {
let module_id = self.env().module_id;
let ident_ids = self
.interns_mut()
.all_ident_ids
.get_mut(&module_id)
.unwrap();
let ident_id = ident_ids.add_str(name);
Symbol::new(self.env().module_id, ident_id)
}
// This method is suboptimal, but it seems to be the only way to make rust understand
// that all of these values can be mutable at the same time. By returning them together,
// rust understands that they are part of a single use of mutable self.
@ -77,6 +90,7 @@ trait Backend<'a> {
&mut STLayoutInterner<'a>,
&mut Interns,
&mut CodeGenHelp<'a>,
&mut Vec<'a, CallerProc<'a>>,
);
fn function_symbol_to_string<'b, I>(
@ -201,7 +215,7 @@ trait Backend<'a> {
// If this layout requires a new RC proc, we get enough info to create a linker symbol
// for it. Here we don't create linker symbols at this time, but in Wasm backend, we do.
let (rc_stmt, new_specializations) = {
let (module_id, layout_interner, interns, rc_proc_gen) =
let (module_id, layout_interner, interns, rc_proc_gen, _) =
self.module_interns_helpers_mut();
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
@ -319,6 +333,12 @@ trait Backend<'a> {
ret_layout,
..
} => {
dbg!(
func_sym,
func_sym.name() == Symbol::BOOL_TRUE,
func_sym.name().is_builtin()
);
if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) =
LowLevelWrapperType::from_symbol(func_sym.name())
{
@ -373,6 +393,9 @@ trait Backend<'a> {
layout,
)
}
CallType::HigherOrder(higher_order) => {
self.build_higher_order_lowlevel(sym, higher_order, *layout)
}
x => todo!("the call type, {:?}", x),
}
}
@ -1366,6 +1389,14 @@ trait Backend<'a> {
/// build_list_len returns the length of a list.
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol);
/// generate a call to a higher-order lowlevel
fn build_higher_order_lowlevel(
&mut self,
dst: &Symbol,
holl: &HigherOrderLowLevel<'a>,
ret_layout: InLayout<'a>,
);
/// build_list_with_capacity creates and returns a list with the given capacity.
fn build_list_with_capacity(
&mut self,

View file

@ -246,10 +246,16 @@ fn build_object<'a, B: Backend<'a>>(
// Generate IR for specialized helper procs (refcounting & equality)
let helper_procs = {
let (module_id, _interner, interns, helper_proc_gen) = backend.module_interns_helpers_mut();
let (module_id, _interner, interns, helper_proc_gen, caller_procs) =
backend.module_interns_helpers_mut();
let mut owned_caller_procs = bumpalo::collections::Vec::new_in(arena);
std::mem::swap(caller_procs, &mut owned_caller_procs);
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
let helper_procs = helper_proc_gen.take_procs();
let mut helper_procs = helper_proc_gen.take_procs();
helper_procs.extend(owned_caller_procs.into_iter().map(|cp| cp.proc));
module_id.register_debug_idents(ident_ids);
helper_procs

View file

@ -5,8 +5,8 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_target::TargetInfo;
use crate::ir::{
Call, CallSpecId, CallType, Expr, HostExposedLayouts, JoinPointId, ModifyRc, Proc, ProcLayout,
SelfRecursive, Stmt, UpdateModeId,
Call, CallSpecId, CallType, Expr, HostExposedLayouts, JoinPointId, ModifyRc, PassedFunction,
Proc, ProcLayout, SelfRecursive, Stmt, UpdateModeId,
};
use crate::layout::{
Builtin, InLayout, LambdaName, Layout, LayoutInterner, Niche, STLayoutInterner, UnionLayout,
@ -581,6 +581,175 @@ impl<'a> CodeGenHelp<'a> {
}
}
pub struct CallerProc<'a> {
pub proc_symbol: Symbol,
pub proc_layout: ProcLayout<'a>,
pub proc: Proc<'a>,
}
impl<'a> CallerProc<'a> {
fn create_symbol(home: ModuleId, ident_ids: &mut IdentIds, debug_name: &str) -> Symbol {
let ident_id = ident_ids.add_str(debug_name);
Symbol::new(home, ident_id)
}
fn create_caller_proc_symbol(
home: ModuleId,
ident_ids: &mut IdentIds,
operation: &str,
wrapped_function: Symbol,
) -> Symbol {
let debug_name = format!("#help_{}_{}_{:?}", "caller", operation, wrapped_function,);
Self::create_symbol(home, ident_ids, &debug_name)
}
pub fn new(
arena: &'a Bump,
home: ModuleId,
ident_ids: &mut IdentIds,
layout_interner: &mut STLayoutInterner<'a>,
passed_function: &PassedFunction<'a>,
capture_layout: Option<InLayout<'a>>,
) -> Self {
let mut ctx = Context {
new_linker_data: Vec::new_in(arena),
recursive_union: None,
op: HelperOp::Eq,
};
let box_capture_layout = if let Some(capture_layout) = capture_layout {
layout_interner.insert(Layout::Boxed(capture_layout))
} else {
layout_interner.insert(Layout::Boxed(Layout::UNIT))
};
let box_argument_layout =
layout_interner.insert(Layout::Boxed(passed_function.argument_layouts[0]));
let box_return_layout =
layout_interner.insert(Layout::Boxed(passed_function.return_layout));
let proc_layout = ProcLayout {
arguments: arena.alloc([box_capture_layout, box_argument_layout, box_return_layout]),
result: Layout::UNIT,
niche: Niche::NONE,
};
let proc_symbol =
Self::create_caller_proc_symbol(home, ident_ids, "map", passed_function.name.name());
ctx.new_linker_data.push((proc_symbol, proc_layout));
let unbox_capture = Expr::ExprUnbox {
symbol: Symbol::ARG_1,
};
let unbox_argument = Expr::ExprUnbox {
symbol: Symbol::ARG_2,
};
let unboxed_capture = Self::create_symbol(home, ident_ids, "unboxed_capture");
let unboxed_argument = Self::create_symbol(home, ident_ids, "unboxed_argument");
let call_result = Self::create_symbol(home, ident_ids, "call_result");
let unit_symbol = Self::create_symbol(home, ident_ids, "unit_symbol");
let ignored = Self::create_symbol(home, ident_ids, "ignored");
let call = Expr::Call(Call {
call_type: CallType::ByName {
name: passed_function.name,
ret_layout: passed_function.return_layout,
arg_layouts: passed_function.argument_layouts,
specialization_id: passed_function.specialization_id,
},
arguments: if capture_layout.is_some() {
arena.alloc([unboxed_argument, unboxed_capture])
} else {
arena.alloc([unboxed_argument])
},
});
let ptr_write = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::PtrWrite,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: arena.alloc([Symbol::ARG_3, call_result]),
});
let mut body = Stmt::Let(
unboxed_argument,
unbox_argument,
passed_function.argument_layouts[0],
arena.alloc(Stmt::Let(
call_result,
call,
passed_function.return_layout,
arena.alloc(Stmt::Let(
ignored,
ptr_write,
box_return_layout,
arena.alloc(Stmt::Let(
unit_symbol,
Expr::Struct(&[]),
Layout::UNIT,
arena.alloc(Stmt::Ret(unit_symbol)),
)),
)),
)),
);
if let Some(capture_layout) = capture_layout {
body = Stmt::Let(
unboxed_capture,
unbox_capture,
capture_layout,
arena.alloc(body),
);
}
let args: &'a [(InLayout<'a>, Symbol)] = {
arena.alloc([
(box_capture_layout, ARG_1),
(box_argument_layout, ARG_2),
(box_return_layout, ARG_3),
])
};
let proc = Proc {
name: LambdaName::no_niche(proc_symbol),
args,
body,
closure_data_layout: None,
ret_layout: Layout::UNIT,
is_self_recursive: SelfRecursive::NotSelfRecursive,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};
if false {
let allocator = ven_pretty::BoxAllocator;
let doc = proc
.to_doc::<_, (), _>(
&allocator,
layout_interner,
true,
crate::ir::Parens::NotNeeded,
)
.1
.pretty(80)
.to_string();
println!("{}", doc);
}
Self {
proc_symbol,
proc_layout,
proc,
}
}
}
fn let_lowlevel<'a>(
arena: &'a Bump,
result_layout: InLayout<'a>,

View file

@ -1200,7 +1200,7 @@ fn list_count_if_str() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_map_on_empty_list_with_int_layout() {
assert_evals_to!(
indoc!(
@ -1218,7 +1218,7 @@ fn list_map_on_empty_list_with_int_layout() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_map_on_non_empty_list() {
assert_evals_to!(
indoc!(
@ -1236,7 +1236,7 @@ fn list_map_on_non_empty_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_map_changes_input() {
assert_evals_to!(
indoc!(
@ -1254,7 +1254,7 @@ fn list_map_changes_input() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_map_on_big_list() {
assert_evals_to!(
indoc!(
@ -1274,7 +1274,7 @@ fn list_map_on_big_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_map_with_type_change() {
assert_evals_to!(
indoc!(
@ -1293,7 +1293,7 @@ fn list_map_with_type_change() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_map_using_defined_function() {
assert_evals_to!(
indoc!(
@ -1315,7 +1315,7 @@ fn list_map_using_defined_function() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_map_all_inline() {
assert_evals_to!(
indoc!(
@ -1328,9 +1328,30 @@ fn list_map_all_inline() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn list_map_closure_int() {
assert_evals_to!(
indoc!(
r#"
int : I64
int = 123
single : List I64
single =
[0]
List.map single (\x -> x + int)
"#
),
RocList::from_slice(&[123]),
RocList<i64>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map_closure() {
fn list_map_closure_float() {
assert_evals_to!(
indoc!(
r#"

View file

@ -4055,3 +4055,27 @@ fn num_abs_large_bits_min_overflow() {
fn num_abs_float_overflow() {
assert_evals_to!("Num.absDiff Num.maxF64 Num.minF64", f64::INFINITY, f64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn bool_in_switch() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
loop : [ Continue {}, Break {} ]
loop = Continue {}
all = \{} ->
when loop is
Continue {} -> Bool.true
Break {} -> Bool.false
main = all {}
"#
),
true,
bool
);
}

View file

@ -210,7 +210,7 @@ pub fn helper(
let builtins_host_tempfile =
roc_bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
if false {
if true {
std::fs::copy(&app_o_file, "/tmp/app.o").unwrap();
}