mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-13 07:16:18 +00:00
wasm: implement List.sortWith
This commit is contained in:
parent
88bcb82fef
commit
f132350ef3
4 changed files with 146 additions and 45 deletions
|
@ -36,7 +36,8 @@ pub enum ProcSource {
|
|||
Roc,
|
||||
Helper,
|
||||
/// Wrapper function for higher-order calls from Zig to Roc
|
||||
HigherOrderWrapper(usize),
|
||||
HigherOrderMapper(usize),
|
||||
HigherOrderCompare(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -463,7 +464,7 @@ impl<'a> WasmBackend<'a> {
|
|||
self.module.names.append_function(wasm_fn_index, name);
|
||||
}
|
||||
|
||||
/// Build a wrapper around a Roc procedure so that it can be called from our higher-order Zig builtins.
|
||||
/// Build a wrapper around a Roc procedure so that it can be called from Zig builtins List.map*
|
||||
///
|
||||
/// The generic Zig code passes *pointers* to all of the argument values (e.g. on the heap in a List).
|
||||
/// Numbers up to 64 bits are passed by value, so we need to load them from the provided pointer.
|
||||
|
@ -471,7 +472,7 @@ impl<'a> WasmBackend<'a> {
|
|||
///
|
||||
/// NOTE: If the builtins expected the return pointer first and closure data last, we could eliminate the wrapper
|
||||
/// when all args are pass-by-reference and non-zero size. But currently we need it to swap those around.
|
||||
pub fn build_higher_order_wrapper(
|
||||
pub fn build_higher_order_mapper(
|
||||
&mut self,
|
||||
wrapper_lookup_idx: usize,
|
||||
inner_lookup_idx: usize,
|
||||
|
@ -524,35 +525,7 @@ impl<'a> WasmBackend<'a> {
|
|||
self.code_builder.get_local(LocalId(i as u32));
|
||||
|
||||
// Dereference any primitive-valued arguments
|
||||
match wrapper_arg {
|
||||
Layout::Boxed(inner_arg) => match inner_arg {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => {
|
||||
self.code_builder.i32_load8_u(Bytes1, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U16 | IntWidth::I16)) => {
|
||||
self.code_builder.i32_load16_u(Bytes2, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U32 | IntWidth::I32)) => {
|
||||
self.code_builder.i32_load(Bytes4, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U64 | IntWidth::I64)) => {
|
||||
self.code_builder.i64_load(Bytes8, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
self.code_builder.f32_load(Bytes4, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
self.code_builder.f64_load(Bytes8, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Bool) => {
|
||||
self.code_builder.i32_load8_u(Bytes1, 0);
|
||||
}
|
||||
_ => {
|
||||
// Any other layout is a pointer, which we've already loaded. Nothing to do!
|
||||
}
|
||||
},
|
||||
x => internal_error!("Higher-order wrapper: expected a Box layout, got {:?}", x),
|
||||
}
|
||||
self.dereference_boxed_value(wrapper_arg);
|
||||
}
|
||||
|
||||
// If the inner function has closure data, it's the last arg of the inner fn
|
||||
|
@ -594,6 +567,87 @@ impl<'a> WasmBackend<'a> {
|
|||
self.reset();
|
||||
}
|
||||
|
||||
/// Build a wrapper around a Roc comparison proc so that it can be called from higher-order Zig builtins.
|
||||
/// Comparison procedure signature is: closure_data, a, b -> Order (u8)
|
||||
///
|
||||
/// The generic Zig code passes *pointers* to all of the argument values (e.g. on the heap in a List).
|
||||
/// Numbers up to 64 bits are passed by value, so we need to load them from the provided pointer.
|
||||
/// Everything else is passed by reference, so we can just pass the pointer through.
|
||||
pub fn build_higher_order_compare(
|
||||
&mut self,
|
||||
wrapper_lookup_idx: usize,
|
||||
inner_lookup_idx: usize,
|
||||
) {
|
||||
use ValueType::*;
|
||||
|
||||
let ProcLookupData {
|
||||
name: wrapper_name,
|
||||
layout: wrapper_proc_layout,
|
||||
..
|
||||
} = self.proc_lookup[wrapper_lookup_idx];
|
||||
let closure_data_layout = wrapper_proc_layout.arguments[0];
|
||||
let value_layout = wrapper_proc_layout.arguments[1];
|
||||
|
||||
let mut n_inner_args = 2;
|
||||
if closure_data_layout.stack_size(TARGET_INFO) > 0 {
|
||||
self.code_builder.get_local(LocalId(0));
|
||||
n_inner_args += 1;
|
||||
}
|
||||
self.code_builder.get_local(LocalId(1));
|
||||
self.dereference_boxed_value(&value_layout);
|
||||
self.code_builder.get_local(LocalId(2));
|
||||
self.dereference_boxed_value(&value_layout);
|
||||
|
||||
// Call the wrapped inner function
|
||||
let inner_wasm_fn_index = self.fn_index_offset + inner_lookup_idx as u32;
|
||||
self.code_builder.call(inner_wasm_fn_index, n_inner_args, true);
|
||||
|
||||
// Write empty function header (local variables array with zero length)
|
||||
self.code_builder.build_fn_header_and_footer(&[], 0, None);
|
||||
|
||||
self.module.add_function_signature(Signature {
|
||||
param_types: bumpalo::vec![in self.env.arena; I32; 3],
|
||||
ret_type: Some(ValueType::I32),
|
||||
});
|
||||
|
||||
self.append_proc_debug_name(wrapper_name);
|
||||
self.reset();
|
||||
}
|
||||
|
||||
fn dereference_boxed_value(&mut self, boxed_layout: &Layout) {
|
||||
use Align::*;
|
||||
|
||||
match boxed_layout {
|
||||
Layout::Boxed(inner) => match inner {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => {
|
||||
self.code_builder.i32_load8_u(Bytes1, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U16 | IntWidth::I16)) => {
|
||||
self.code_builder.i32_load16_u(Bytes2, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U32 | IntWidth::I32)) => {
|
||||
self.code_builder.i32_load(Bytes4, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U64 | IntWidth::I64)) => {
|
||||
self.code_builder.i64_load(Bytes8, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
self.code_builder.f32_load(Bytes4, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
self.code_builder.f64_load(Bytes8, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Bool) => {
|
||||
self.code_builder.i32_load8_u(Bytes1, 0);
|
||||
}
|
||||
_ => {
|
||||
// Any other layout is a pointer, which we've already loaded. Nothing to do!
|
||||
}
|
||||
},
|
||||
x => internal_error!("Expected a Box layout, got {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
STATEMENTS
|
||||
|
|
|
@ -169,7 +169,8 @@ pub fn build_app_module<'a>(
|
|||
match source {
|
||||
Roc => { /* already generated */ }
|
||||
Helper => backend.build_proc(helper_iter.next().unwrap()),
|
||||
HigherOrderWrapper(inner_idx) => backend.build_higher_order_wrapper(idx, *inner_idx),
|
||||
HigherOrderMapper(inner_idx) => backend.build_higher_order_mapper(idx, *inner_idx),
|
||||
HigherOrderCompare(inner_idx) => backend.build_higher_order_compare(idx, *inner_idx),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2018,7 +2018,7 @@ pub fn call_higher_order_lowlevel<'a>(
|
|||
|
||||
// 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 helper_proc_source = {
|
||||
let passed_proc_layout = ProcLayout {
|
||||
arguments: argument_layouts,
|
||||
result: *result_layout,
|
||||
|
@ -2031,7 +2031,10 @@ pub fn call_higher_order_lowlevel<'a>(
|
|||
*name == fn_name.name() && layout == &passed_proc_layout
|
||||
})
|
||||
.unwrap();
|
||||
ProcSource::HigherOrderWrapper(passed_proc_index)
|
||||
match op {
|
||||
ListSortWith { .. } => ProcSource::HigherOrderCompare(passed_proc_index),
|
||||
_ => ProcSource::HigherOrderMapper(passed_proc_index),
|
||||
}
|
||||
};
|
||||
let wrapper_sym = backend.create_symbol(&format!("#wrap#{:?}", fn_name));
|
||||
let wrapper_layout = {
|
||||
|
@ -2051,16 +2054,26 @@ pub fn call_higher_order_lowlevel<'a>(
|
|||
.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,
|
||||
captures_niche: fn_name.captures_niche(),
|
||||
if let ProcSource::HigherOrderMapper(_) = helper_proc_source {
|
||||
// Our convention for mappers is that they write to the heap via the last argument
|
||||
wrapper_arg_layouts.push(Layout::Boxed(result_layout));
|
||||
ProcLayout {
|
||||
arguments: wrapper_arg_layouts.into_bump_slice(),
|
||||
result: Layout::UNIT,
|
||||
captures_niche: fn_name.captures_niche(),
|
||||
}
|
||||
} else {
|
||||
ProcLayout {
|
||||
arguments: wrapper_arg_layouts.into_bump_slice(),
|
||||
result: *result_layout,
|
||||
captures_niche: fn_name.captures_niche(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let wrapper_fn_idx = backend.register_helper_proc(wrapper_sym, wrapper_layout, source);
|
||||
let wrapper_fn_idx =
|
||||
backend.register_helper_proc(wrapper_sym, wrapper_layout, helper_proc_source);
|
||||
let wrapper_fn_ptr = backend.get_fn_ptr(wrapper_fn_idx);
|
||||
let inc_fn_ptr = match closure_data_layout {
|
||||
Layout::Struct {
|
||||
|
@ -2132,7 +2145,40 @@ pub fn call_higher_order_lowlevel<'a>(
|
|||
*owns_captured_environment,
|
||||
),
|
||||
|
||||
ListSortWith { .. } | DictWalk { .. } => todo!("{:?}", op),
|
||||
ListSortWith { xs } => {
|
||||
let elem_layout = unwrap_list_elem_layout(backend.storage.symbol_layouts[xs]);
|
||||
let (element_width, alignment) = elem_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
|
||||
let cb = &mut backend.code_builder;
|
||||
|
||||
// (return pointer) i32
|
||||
// input: RocList, i64, i32
|
||||
// caller: CompareFn, i32
|
||||
// data: Opaque, i32
|
||||
// inc_n_data: IncN, i32
|
||||
// data_is_owned: bool, i32
|
||||
// alignment: u32, i32
|
||||
// element_width: usize, i32
|
||||
|
||||
backend.storage.load_symbols(cb, &[return_sym]);
|
||||
backend.storage.load_symbol_zig(cb, *xs);
|
||||
cb.i32_const(wrapper_fn_ptr);
|
||||
if closure_data_exists {
|
||||
backend.storage.load_symbols(cb, &[*captured_environment]);
|
||||
} else {
|
||||
// load_symbols assumes that a zero-size arg should be eliminated in code gen,
|
||||
// but that's a specialization that our Zig code doesn't have! Pass a null pointer.
|
||||
cb.i32_const(0);
|
||||
}
|
||||
cb.i32_const(inc_fn_ptr);
|
||||
cb.i32_const(*owns_captured_environment as i32);
|
||||
cb.i32_const(alignment as i32);
|
||||
cb.i32_const(element_width as i32);
|
||||
|
||||
backend.call_host_fn_after_loading_args(bitcode::LIST_SORT_WITH, 9, false);
|
||||
}
|
||||
|
||||
DictWalk { .. } => todo!("{:?}", op),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2613,7 +2613,7 @@ fn list_range() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_sort_with() {
|
||||
assert_evals_to!(
|
||||
"List.sortWith [] Num.compare",
|
||||
|
@ -2633,7 +2633,7 @@ fn list_sort_with() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_sort_asc() {
|
||||
assert_evals_to!(
|
||||
"List.sortAsc []",
|
||||
|
@ -2648,7 +2648,7 @@ fn list_sort_asc() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_sort_desc() {
|
||||
assert_evals_to!(
|
||||
"List.sortDesc []",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue