Merge remote-tracking branch 'origin/main' into expect-fx-codegen

This commit is contained in:
Folkert 2022-08-23 16:28:21 +02:00
commit a22e04361c
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
222 changed files with 10039 additions and 1945 deletions

View file

@ -517,7 +517,7 @@ impl<'a> WasmBackend<'a> {
// Load all the arguments for the inner function
for (i, wrapper_arg) in wrapper_arg_layouts.iter().enumerate() {
let is_closure_data = i == 0; // Skip closure data (first for wrapper, last for inner)
let is_closure_data = i == 0; // Skip closure data (first for wrapper, last for inner). We'll handle it below.
let is_return_pointer = i == wrapper_arg_layouts.len() - 1; // Skip return pointer (may not be an arg for inner. And if it is, swaps from end to start)
if is_closure_data || is_return_pointer {
continue;
@ -540,7 +540,23 @@ impl<'a> WasmBackend<'a> {
// If the inner function has closure data, it's the last arg of the inner fn
let closure_data_layout = wrapper_arg_layouts[0];
if closure_data_layout.stack_size(TARGET_INFO) > 0 {
// The closure data exists, and will have been passed in to the wrapper as a
// one-element struct.
let inner_closure_data_layout = match closure_data_layout {
Layout::Struct {
field_layouts: [inner],
..
} => inner,
other => internal_error!(
"Expected a boxed layout for wrapped closure data, got {:?}",
other
),
};
self.code_builder.get_local(LocalId(0));
// Since the closure data is wrapped in a one-element struct, we've been passed in the
// pointer to that struct in the stack memory. To get the closure data we just need to
// dereference the pointer.
self.dereference_boxed_value(inner_closure_data_layout);
}
// Call the wrapped inner function

View file

@ -183,7 +183,7 @@ impl<'a> LowLevelCall<'a> {
/// Wrap an integer that should have less than 32 bits, but is represented in Wasm as i32.
/// This may seem like deliberately introducing an error!
/// But we want all targets to behave the same, and hash algos rely on wrapping.
/// Discussion: https://github.com/rtfeldman/roc/pull/2117#discussion_r760723063
/// Discussion: https://github.com/roc-lang/roc/pull/2117#discussion_r760723063
fn wrap_small_int(&self, backend: &mut WasmBackend<'a>, int_width: IntWidth) {
let bits = 8 * int_width.stack_size() as i32;
let shift = 32 - bits;
@ -1599,11 +1599,11 @@ impl<'a> LowLevelCall<'a> {
}
}
NumShiftLeftBy => {
// Swap order of arguments
backend.storage.load_symbols(
&mut backend.code_builder,
&[self.arguments[1], self.arguments[0]],
);
let num = self.arguments[0];
let bits = self.arguments[1];
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
match CodeGenNumType::from(self.ret_layout) {
I32 => backend.code_builder.i32_shl(),
I64 => backend.code_builder.i64_shl(),
@ -1612,8 +1612,8 @@ impl<'a> LowLevelCall<'a> {
}
}
NumShiftRightBy => {
let bits = self.arguments[0];
let num = self.arguments[1];
let num = self.arguments[0];
let bits = self.arguments[1];
match CodeGenNumType::from(self.ret_layout) {
I32 => {
// In most languages this operation is for signed numbers, but Roc defines it on all integers.
@ -1657,41 +1657,39 @@ impl<'a> LowLevelCall<'a> {
}
}
NumShiftRightZfBy => {
let num = self.arguments[0];
let bits = self.arguments[1];
match CodeGenNumType::from(self.ret_layout) {
I32 => {
// In most languages this operation is for unsigned numbers, but Roc defines it on all integers.
// So the argument is implicitly converted to unsigned before the shift operator.
// We need to make that conversion explicit for i8 and i16, which use Wasm's i32 type.
let bit_width = 8 * self.ret_layout.stack_size(TARGET_INFO);
if bit_width < 32 && symbol_is_signed_int(backend, self.arguments[0]) {
if bit_width < 32 && symbol_is_signed_int(backend, bits) {
let mask = (1 << bit_width) - 1;
backend
.storage
.load_symbols(&mut backend.code_builder, &[self.arguments[1]]);
.load_symbols(&mut backend.code_builder, &[num]);
backend.code_builder.i32_const(mask);
backend.code_builder.i32_and();
backend
.storage
.load_symbols(&mut backend.code_builder, &[self.arguments[0]]);
.load_symbols(&mut backend.code_builder, &[bits]);
} else {
// swap the arguments
backend.storage.load_symbols(
&mut backend.code_builder,
&[self.arguments[1], self.arguments[0]],
);
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
}
backend.code_builder.i32_shr_u();
}
I64 => {
// swap the arguments
backend.storage.load_symbols(
&mut backend.code_builder,
&[self.arguments[1], self.arguments[0]],
);
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
backend.code_builder.i64_shr_u();
}
I128 => todo!("{:?} for I128", self.lowlevel),
@ -1857,11 +1855,15 @@ impl<'a> LowLevelCall<'a> {
/// Equality and inequality
/// These can operate on any data type (except functions) so they're more complex than other operators.
fn eq_or_neq(&self, backend: &mut WasmBackend<'a>) {
let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]];
let other_arg_layout = backend.storage.symbol_layouts[&self.arguments[1]];
let arg_layout =
backend.storage.symbol_layouts[&self.arguments[0]].runtime_representation();
let other_arg_layout =
backend.storage.symbol_layouts[&self.arguments[1]].runtime_representation();
debug_assert!(
arg_layout == other_arg_layout,
"Cannot do `==` comparison on different types"
"Cannot do `==` comparison on different types: {:?} vs {:?}",
arg_layout,
other_arg_layout
);
let invert_result = matches!(self.lowlevel, LowLevel::NotEq);
@ -2113,6 +2115,18 @@ pub fn call_higher_order_lowlevel<'a>(
..
} = passed_function;
// The zig lowlevel builtins expect the passed functions' closure data to always
// be sent as an opaque pointer. On the Roc side, however, we need to call the passed function
// with the Roc representation of the closure data. There are three possible cases for that
// representation:
//
// 1. The closure data is a struct
// 2. The closure data is an unwrapped value
// 3. There is no closure data
//
// To uniformly deal with the first two cases, always put the closure data, when it exists,
// into a one-element struct. That way, we get a pointer (i32) that can be passed to the zig lowlevels.
// The wrapper around the passed function will access the actual closure data in the struct.
let (closure_data_layout, closure_data_exists) =
match backend.storage.symbol_layouts[captured_environment] {
Layout::LambdaSet(lambda_set) => {
@ -2131,6 +2145,46 @@ pub fn call_higher_order_lowlevel<'a>(
x => internal_error!("Closure data has an invalid layout\n{:?}", x),
};
let (wrapped_captured_environment, wrapped_captures_layout) = if closure_data_exists {
// If there is closure data, make sure we put in a struct it before passing it to the
// external builtin impl. That way it's always an `i32` pointer.
let wrapped_closure_data_sym = backend.create_symbol("wrapped_captures");
let wrapped_captures_layout =
Layout::struct_no_name_order(backend.env.arena.alloc([closure_data_layout]));
// make sure that the wrapping struct is available in stack memory, so we can hand out a
// pointer to it.
let wrapped_storage = backend.storage.allocate_var(
wrapped_captures_layout,
wrapped_closure_data_sym,
crate::storage::StoredVarKind::Variable,
);
let (wrapped_storage_local_ptr, wrapped_storage_offset) = match wrapped_storage {
StoredValue::StackMemory { location, .. } => {
location.local_and_offset(backend.storage.stack_frame_pointer)
}
other => internal_error!(
"Struct should be allocated in stack memory, but it's in {:?}",
other
),
};
// copy the actual closure data into the first and only element of the wrapping struct.
backend.storage.copy_value_to_memory(
&mut backend.code_builder,
wrapped_storage_local_ptr,
wrapped_storage_offset,
*captured_environment,
);
(wrapped_closure_data_sym, wrapped_captures_layout)
} else {
// If we don't capture anything, pass along the captured environment as-is - the wrapper
// function will take care not to unwrap this.
(*captured_environment, closure_data_layout)
};
// 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 helper_proc_source = {
@ -2164,7 +2218,7 @@ pub fn call_higher_order_lowlevel<'a>(
argument_layouts.len()
};
wrapper_arg_layouts.push(closure_data_layout);
wrapper_arg_layouts.push(wrapped_captures_layout);
wrapper_arg_layouts.extend(
argument_layouts
.iter()
@ -2204,7 +2258,7 @@ pub fn call_higher_order_lowlevel<'a>(
.get_refcount_fn_index(Layout::Builtin(Builtin::Int(IntWidth::I32)), HelperOp::Inc);
backend.get_fn_ptr(inc_fn)
} else {
let inc_fn = backend.get_refcount_fn_index(closure_data_layout, HelperOp::Inc);
let inc_fn = backend.get_refcount_fn_index(wrapped_captures_layout, HelperOp::Inc);
backend.get_fn_ptr(inc_fn)
};
@ -2218,7 +2272,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2231,7 +2285,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2244,7 +2298,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2257,7 +2311,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2280,7 +2334,9 @@ pub fn call_higher_order_lowlevel<'a>(
backend.storage.load_symbol_zig(cb, *xs);
cb.i32_const(wrapper_fn_ptr);
if closure_data_exists {
backend.storage.load_symbols(cb, &[*captured_environment]);
backend
.storage
.load_symbols(cb, &[wrapped_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.

View file

@ -719,9 +719,10 @@ impl<'a> Storage<'a> {
) => {
debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size);
// Note: load_symbols will not destroy the value, so we can use it again later.
// It will leave a Popped marker in the VM stack model in CodeBuilder
self.load_symbols(code_builder, &[from_symbol]);
code_builder.set_local(*to_local_id);
self.symbol_storage_map.insert(from_symbol, to.clone());
}
(

View file

@ -234,6 +234,14 @@ impl Wasm32Result for () {
}
}
impl Wasm32Result for std::convert::Infallible {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
code_builder.call(main_function_index, 0, false);
code_builder.get_global(0);
code_builder.build_fn_header_and_footer(&[], 0, None);
}
}
impl<T, U> Wasm32Result for (T, U)
where
T: Wasm32Result + Wasm32Sized,