wasm: ProcLayout for higher order wrapper fns

This commit is contained in:
Brian Carroll 2022-03-31 13:12:52 +01:00
parent 83cae16a60
commit 05459455ec
3 changed files with 89 additions and 22 deletions

View file

@ -36,7 +36,7 @@ pub enum ProcSource {
Helper, Helper,
/// Wrapper function for higher-order calls from Zig to Roc, /// Wrapper function for higher-order calls from Zig to Roc,
/// to work around Zig's incorrect implementation of C calling convention in Wasm /// to work around Zig's incorrect implementation of C calling convention in Wasm
ZigCallConvWrapper(usize), HigherOrderWrapper(usize),
} }
#[derive(Debug)] #[derive(Debug)]
@ -121,21 +121,25 @@ impl<'a> WasmBackend<'a> {
self.helper_proc_gen.take_procs() self.helper_proc_gen.take_procs()
} }
fn register_helper_proc(&mut self, new_proc_info: (Symbol, ProcLayout<'a>)) { pub fn register_helper_proc(
let (new_proc_sym, new_proc_layout) = new_proc_info; &mut self,
symbol: Symbol,
layout: ProcLayout<'a>,
source: ProcSource,
) -> u32 {
let wasm_fn_index = self.proc_lookup.len() as u32; let wasm_fn_index = self.proc_lookup.len() as u32;
let linker_sym_index = self.module.linking.symbol_table.len() as u32; let linker_sym_index = self.module.linking.symbol_table.len() as u32;
let name = self let name = self
.layout_ids .layout_ids
.get_toplevel(new_proc_sym, &new_proc_layout) .get_toplevel(symbol, &layout)
.to_symbol_string(new_proc_sym, self.interns); .to_symbol_string(symbol, self.interns);
self.proc_lookup.push(ProcLookupData { self.proc_lookup.push(ProcLookupData {
name: new_proc_sym, name: symbol,
layout: new_proc_layout, layout,
linker_index: linker_sym_index, linker_index: linker_sym_index,
source: ProcSource::Helper, source,
}); });
let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined { let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined {
@ -144,6 +148,8 @@ impl<'a> WasmBackend<'a> {
name, name,
}); });
self.module.linking.symbol_table.push(linker_symbol); self.module.linking.symbol_table.push(linker_symbol);
wasm_fn_index
} }
pub fn finalize(self) -> (WasmModule<'a>, Vec<'a, u32>) { pub fn finalize(self) -> (WasmModule<'a>, Vec<'a, u32>) {
@ -164,7 +170,7 @@ impl<'a> WasmBackend<'a> {
pub fn register_symbol_debug_names(&self) {} pub fn register_symbol_debug_names(&self) {}
/// Create an IR Symbol for an anonymous value (such as ListLiteral) /// Create an IR Symbol for an anonymous value (such as ListLiteral)
fn create_symbol(&mut self, debug_name: &str) -> Symbol { pub fn create_symbol(&mut self, debug_name: &str) -> Symbol {
let ident_ids = self let ident_ids = self
.interns .interns
.all_ident_ids .all_ident_ids
@ -652,8 +658,8 @@ impl<'a> WasmBackend<'a> {
} }
// If any new specializations were created, register their symbol data // If any new specializations were created, register their symbol data
for spec in new_specializations.into_iter() { for (spec_sym, spec_layout) in new_specializations.into_iter() {
self.register_helper_proc(spec); self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
} }
self.stmt(rc_stmt); self.stmt(rc_stmt);
@ -1044,8 +1050,8 @@ impl<'a> WasmBackend<'a> {
.call_specialized_equals(ident_ids, arg_layout, arguments); .call_specialized_equals(ident_ids, arg_layout, arguments);
// If any new specializations were created, register their symbol data // If any new specializations were created, register their symbol data
for spec in new_specializations.into_iter() { for (spec_sym, spec_layout) in new_specializations.into_iter() {
self.register_helper_proc(spec); self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
} }
// Generate Wasm code for the IR call expression // Generate Wasm code for the IR call expression
@ -1529,8 +1535,8 @@ impl<'a> WasmBackend<'a> {
.call_reset_refcount(ident_ids, layout, argument); .call_reset_refcount(ident_ids, layout, argument);
// If any new specializations were created, register their symbol data // If any new specializations were created, register their symbol data
for spec in new_specializations.into_iter() { for (spec_sym, spec_layout) in new_specializations.into_iter() {
self.register_helper_proc(spec); self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
} }
// Generate Wasm code for the IR call expression // Generate Wasm code for the IR call expression

View file

@ -179,7 +179,7 @@ pub fn build_module_without_wrapper<'a>(
backend.build_proc(proc); backend.build_proc(proc);
} }
} }
ZigCallConvWrapper(inner_idx) => backend.build_zigcc_wrapper(idx, *inner_idx), HigherOrderWrapper(inner_idx) => backend.build_zigcc_wrapper(idx, *inner_idx),
} }
} }

View file

@ -3,11 +3,11 @@ use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::{HigherOrderLowLevel, PassedFunction}; use roc_mono::ir::{HigherOrderLowLevel, PassedFunction, ProcLayout};
use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_mono::layout::{Builtin, Layout, UnionLayout};
use roc_mono::low_level::HigherOrder; use roc_mono::low_level::HigherOrder;
use crate::backend::WasmBackend; use crate::backend::{ProcLookupData, ProcSource, WasmBackend};
use crate::layout::CallConv; use crate::layout::CallConv;
use crate::layout::{StackMemoryFormat, WasmLayout}; use crate::layout::{StackMemoryFormat, WasmLayout};
use crate::storage::{StackMemoryLocation, Storage, StoredValue}; use crate::storage::{StackMemoryLocation, Storage, StoredValue};
@ -937,7 +937,7 @@ pub fn call_higher_order_lowlevel<'a>(
backend: &mut WasmBackend<'a>, backend: &mut WasmBackend<'a>,
return_sym: Symbol, return_sym: Symbol,
return_layout: &Layout<'a>, return_layout: &Layout<'a>,
higher_order: &HigherOrderLowLevel<'a>, higher_order: &'a HigherOrderLowLevel<'a>,
) { ) {
use HigherOrder::*; use HigherOrder::*;
@ -948,6 +948,7 @@ pub fn call_higher_order_lowlevel<'a>(
} = higher_order; } = higher_order;
let PassedFunction { let PassedFunction {
name: fn_name,
argument_layouts, argument_layouts,
return_layout: result_layout, return_layout: result_layout,
owns_captured_environment, owns_captured_environment,
@ -955,6 +956,56 @@ pub fn call_higher_order_lowlevel<'a>(
.. ..
} = passed_function; } = passed_function;
// We create a wrapper around the passed function, which just unboxes the arguments.
// This allows Zig builtins to have a generic pointer-based interface.
let source = {
let passed_proc_layout = ProcLayout {
arguments: argument_layouts,
result: *result_layout,
};
let passed_proc_index = backend
.proc_lookup
.iter()
.position(|ProcLookupData { name, layout, .. }| {
name == fn_name && layout == &passed_proc_layout
})
.unwrap();
ProcSource::HigherOrderWrapper(passed_proc_index)
};
let wrapper_sym = backend.create_symbol(&format!("#wrap#{:?}", fn_name));
let wrapper_layout = {
let mut wrapper_arg_layouts: Vec<Layout<'a>> =
Vec::with_capacity_in(argument_layouts.len() + 1, backend.env.arena);
let closure_data_layout = backend.storage.symbol_layouts[captured_environment];
let last_arg_is_closure_data: bool = match closure_data_layout {
Layout::LambdaSet(lambda_set) => lambda_set.runtime_representation() != Layout::UNIT,
x => internal_error!("Closure data has an invalid layout\n{:?}", x),
};
let n_non_closure_args = if last_arg_is_closure_data {
argument_layouts.len() - 1
} else {
argument_layouts.len()
};
wrapper_arg_layouts.push(closure_data_layout);
wrapper_arg_layouts.extend(
argument_layouts
.iter()
.take(n_non_closure_args)
.map(Layout::Boxed),
);
wrapper_arg_layouts.push(Layout::Boxed(result_layout));
ProcLayout {
arguments: wrapper_arg_layouts.into_bump_slice(),
result: Layout::UNIT,
}
};
let wrapper_fn_idx = backend.register_helper_proc(wrapper_sym, wrapper_layout, source);
/* /*
TODO indirect calls TODO indirect calls
passed_function passed_function
@ -969,10 +1020,20 @@ pub fn call_higher_order_lowlevel<'a>(
append to (or find in) backend.proc_lookup append to (or find in) backend.proc_lookup
calculate its index calculate its index
insert index into elements table insert index into elements table
questions about the closure data
- is the first arg always closure data? no, last
- Do I need a special case where if the closure layout is Unit then no argument exists on the Proc?
(Otherwise I suppose there would always an arg with Unit layout and I haven't seen that)
- if the closure exists then does it reliably appear first or last in the argument list on the Proc?
- or is it absent from that
- if there is no closure data then I need a dummy incrementor for Unit?
yes
*/ */
let caller_fn_idx: i32 = todo!(); let inc_fn_idx: i32 = 123;
let inc_fn_idx: i32 = todo!();
match op { match op {
// List.map : List elem_old, (elem_old -> elem_new) -> List elem_new // List.map : List elem_old, (elem_old -> elem_new) -> List elem_new
@ -994,7 +1055,7 @@ pub fn call_higher_order_lowlevel<'a>(
// Load return pointer & argument values // Load return pointer & argument values
backend.storage.load_symbols(cb, &[return_sym]); backend.storage.load_symbols(cb, &[return_sym]);
backend.storage.load_symbol_zig(cb, *xs); backend.storage.load_symbol_zig(cb, *xs);
cb.i32_const(caller_fn_idx); cb.i32_const(wrapper_fn_idx as i32);
backend.storage.load_symbols(cb, &[*captured_environment]); backend.storage.load_symbols(cb, &[*captured_environment]);
cb.i32_const(inc_fn_idx); cb.i32_const(inc_fn_idx);
cb.i32_const(*owns_captured_environment as i32); cb.i32_const(*owns_captured_environment as i32);