mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Wasm: Move Eq/NotEq into LowLevelCall
This commit is contained in:
parent
f635dd8776
commit
f354b4842b
2 changed files with 292 additions and 329 deletions
|
@ -1,7 +1,7 @@
|
||||||
use bumpalo::{self, collections::Vec};
|
use bumpalo::{self, collections::Vec};
|
||||||
|
|
||||||
use code_builder::Align;
|
use code_builder::Align;
|
||||||
use roc_builtins::bitcode::{self, IntWidth};
|
use roc_builtins::bitcode::IntWidth;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_module::ident::Ident;
|
use roc_module::ident::Ident;
|
||||||
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
|
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
|
||||||
|
@ -15,9 +15,9 @@ use roc_mono::ir::{
|
||||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout};
|
use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout};
|
||||||
use roc_reporting::internal_error;
|
use roc_reporting::internal_error;
|
||||||
|
|
||||||
use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout};
|
use crate::layout::{CallConv, ReturnMethod, WasmLayout};
|
||||||
use crate::low_level::LowLevelCall;
|
use crate::low_level::LowLevelCall;
|
||||||
use crate::storage::{StackMemoryLocation, Storage, StoredValue, StoredValueKind};
|
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
||||||
use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol};
|
use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol};
|
||||||
use crate::wasm_module::sections::{DataMode, DataSegment};
|
use crate::wasm_module::sections::{DataMode, DataSegment};
|
||||||
use crate::wasm_module::{
|
use crate::wasm_module::{
|
||||||
|
@ -809,26 +809,6 @@ impl<'a> WasmBackend<'a> {
|
||||||
ret_layout: &Layout<'a>,
|
ret_layout: &Layout<'a>,
|
||||||
ret_storage: &StoredValue,
|
ret_storage: &StoredValue,
|
||||||
) {
|
) {
|
||||||
use LowLevel::*;
|
|
||||||
let wasm_layout = WasmLayout::new(ret_layout);
|
|
||||||
|
|
||||||
match lowlevel {
|
|
||||||
Eq | NotEq => self.build_eq_or_neq(
|
|
||||||
lowlevel,
|
|
||||||
arguments,
|
|
||||||
ret_symbol,
|
|
||||||
wasm_layout,
|
|
||||||
ret_layout,
|
|
||||||
ret_storage,
|
|
||||||
),
|
|
||||||
PtrCast => {
|
|
||||||
// Don't want Zig calling convention when casting pointers.
|
|
||||||
self.storage.load_symbols(&mut self.code_builder, arguments);
|
|
||||||
}
|
|
||||||
Hash => todo!("Generic hash function generation"),
|
|
||||||
|
|
||||||
// Almost all lowlevels take this branch, except for the special cases above
|
|
||||||
_ => {
|
|
||||||
let low_level_call = LowLevelCall {
|
let low_level_call = LowLevelCall {
|
||||||
lowlevel,
|
lowlevel,
|
||||||
arguments,
|
arguments,
|
||||||
|
@ -838,8 +818,6 @@ impl<'a> WasmBackend<'a> {
|
||||||
};
|
};
|
||||||
low_level_call.generate(self);
|
low_level_call.generate(self);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a call instruction to a Zig builtin function.
|
/// Generate a call instruction to a Zig builtin function.
|
||||||
/// And if we haven't seen it before, add an Import and linker data for it.
|
/// And if we haven't seen it before, add an Import and linker data for it.
|
||||||
|
@ -847,11 +825,9 @@ impl<'a> WasmBackend<'a> {
|
||||||
pub fn call_zig_builtin_after_loading_args(
|
pub fn call_zig_builtin_after_loading_args(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
param_types: Vec<'a, ValueType>,
|
num_wasm_args: usize,
|
||||||
ret_type: Option<ValueType>,
|
has_return_val: bool,
|
||||||
) {
|
) {
|
||||||
let num_wasm_args = param_types.len();
|
|
||||||
let has_return_val = ret_type.is_some();
|
|
||||||
let fn_index = self.module.names.functions[name.as_bytes()];
|
let fn_index = self.module.names.functions[name.as_bytes()];
|
||||||
self.called_preload_fns.push(fn_index);
|
self.called_preload_fns.push(fn_index);
|
||||||
let linker_symbol_index = u32::MAX;
|
let linker_symbol_index = u32::MAX;
|
||||||
|
@ -860,6 +836,43 @@ impl<'a> WasmBackend<'a> {
|
||||||
.call(fn_index, linker_symbol_index, num_wasm_args, has_return_val);
|
.call(fn_index, linker_symbol_index, num_wasm_args, has_return_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call a helper procedure that implements `==` for a data structure (not numbers or Str)
|
||||||
|
/// If this is the first call for this Layout, it will generate the IR for the procedure.
|
||||||
|
/// Call stack is expr_call_low_level -> LowLevelCall::generate -> call_eq_specialized
|
||||||
|
/// It's a bit circuitous, but the alternative is to give low_level.rs `pub` access to
|
||||||
|
/// interns, helper_proc_gen, and expr(). That just seemed all wrong.
|
||||||
|
pub fn call_eq_specialized(
|
||||||
|
&mut self,
|
||||||
|
arguments: &'a [Symbol],
|
||||||
|
arg_layout: &Layout<'a>,
|
||||||
|
ret_symbol: Symbol,
|
||||||
|
ret_storage: &StoredValue,
|
||||||
|
) {
|
||||||
|
let ident_ids = self
|
||||||
|
.interns
|
||||||
|
.all_ident_ids
|
||||||
|
.get_mut(&self.env.module_id)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Get an IR expression for the call to the specialized procedure
|
||||||
|
let (specialized_call_expr, new_specializations) = self
|
||||||
|
.helper_proc_gen
|
||||||
|
.call_specialized_equals(ident_ids, arg_layout, arguments);
|
||||||
|
|
||||||
|
// If any new specializations were created, register their symbol data
|
||||||
|
for spec in new_specializations.into_iter() {
|
||||||
|
self.register_helper_proc(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Wasm code for the IR call expression
|
||||||
|
self.expr(
|
||||||
|
ret_symbol,
|
||||||
|
self.env.arena.alloc(specialized_call_expr),
|
||||||
|
&Layout::Builtin(Builtin::Bool),
|
||||||
|
ret_storage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************
|
/*******************************************************************
|
||||||
* Structs
|
* Structs
|
||||||
*******************************************************************/
|
*******************************************************************/
|
||||||
|
@ -967,9 +980,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
self.code_builder.i32_const(alignment_bytes as i32);
|
self.code_builder.i32_const(alignment_bytes as i32);
|
||||||
|
|
||||||
// Call the foreign function. (Zig and C calling conventions are the same for this signature)
|
// Call the foreign function. (Zig and C calling conventions are the same for this signature)
|
||||||
let param_types = bumpalo::vec![in self.env.arena; ValueType::I32, ValueType::I32];
|
self.call_zig_builtin_after_loading_args("roc_alloc", 2, true);
|
||||||
let ret_type = Some(ValueType::I32);
|
|
||||||
self.call_zig_builtin_after_loading_args("roc_alloc", param_types, ret_type);
|
|
||||||
|
|
||||||
// Save the allocation address to a temporary local variable
|
// Save the allocation address to a temporary local variable
|
||||||
let local_id = self.storage.create_anonymous_local(ValueType::I32);
|
let local_id = self.storage.create_anonymous_local(ValueType::I32);
|
||||||
|
@ -1310,232 +1321,4 @@ impl<'a> WasmBackend<'a> {
|
||||||
self.storage
|
self.storage
|
||||||
.copy_value_from_memory(&mut self.code_builder, symbol, from_ptr, from_offset);
|
.copy_value_from_memory(&mut self.code_builder, symbol, from_ptr, from_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************
|
|
||||||
* Equality
|
|
||||||
*******************************************************************/
|
|
||||||
|
|
||||||
fn build_eq_or_neq(
|
|
||||||
&mut self,
|
|
||||||
lowlevel: LowLevel,
|
|
||||||
arguments: &'a [Symbol],
|
|
||||||
return_sym: Symbol,
|
|
||||||
return_layout: WasmLayout,
|
|
||||||
mono_layout: &Layout<'a>,
|
|
||||||
storage: &StoredValue,
|
|
||||||
) {
|
|
||||||
let arg_layout = self.storage.symbol_layouts[&arguments[0]];
|
|
||||||
let other_arg_layout = self.storage.symbol_layouts[&arguments[1]];
|
|
||||||
debug_assert!(
|
|
||||||
arg_layout == other_arg_layout,
|
|
||||||
"Cannot do `==` comparison on different types"
|
|
||||||
);
|
|
||||||
|
|
||||||
match arg_layout {
|
|
||||||
Layout::Builtin(
|
|
||||||
Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal,
|
|
||||||
) => self.build_eq_or_neq_number(lowlevel, arguments, return_layout, mono_layout),
|
|
||||||
|
|
||||||
Layout::Builtin(Builtin::Str) => {
|
|
||||||
let (param_types, ret_type) = self.storage.load_symbols_for_call(
|
|
||||||
self.env.arena,
|
|
||||||
&mut self.code_builder,
|
|
||||||
arguments,
|
|
||||||
return_sym,
|
|
||||||
&return_layout,
|
|
||||||
CallConv::Zig,
|
|
||||||
);
|
|
||||||
self.call_zig_builtin_after_loading_args(bitcode::STR_EQUAL, param_types, ret_type);
|
|
||||||
if matches!(lowlevel, LowLevel::NotEq) {
|
|
||||||
self.code_builder.i32_eqz();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty record is always equal to empty record.
|
|
||||||
// There are no runtime arguments to check, so just emit true or false.
|
|
||||||
Layout::Struct(fields) if fields.is_empty() => {
|
|
||||||
self.code_builder
|
|
||||||
.i32_const(if lowlevel == LowLevel::Eq { 1 } else { 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Void is always equal to void. This is the type for the contents of the empty list in `[] == []`
|
|
||||||
// This code will never execute, but we need a true or false value to type-check
|
|
||||||
Layout::Union(UnionLayout::NonRecursive(tags)) if tags.is_empty() => {
|
|
||||||
self.code_builder
|
|
||||||
.i32_const(if lowlevel == LowLevel::Eq { 1 } else { 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_))
|
|
||||||
| Layout::Struct(_)
|
|
||||||
| Layout::Union(_)
|
|
||||||
| Layout::LambdaSet(_) => {
|
|
||||||
self.build_eq_specialized(&arg_layout, arguments, return_sym, storage);
|
|
||||||
if matches!(lowlevel, LowLevel::NotEq) {
|
|
||||||
self.code_builder.i32_eqz();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout::RecursivePointer => {
|
|
||||||
internal_error!(
|
|
||||||
"Tried to apply `==` to RecursivePointer values {:?}",
|
|
||||||
arguments,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_eq_or_neq_number(
|
|
||||||
&mut self,
|
|
||||||
lowlevel: LowLevel,
|
|
||||||
arguments: &'a [Symbol],
|
|
||||||
return_layout: WasmLayout,
|
|
||||||
mono_layout: &Layout<'a>,
|
|
||||||
) {
|
|
||||||
use StoredValue::*;
|
|
||||||
match self.storage.get(&arguments[0]).to_owned() {
|
|
||||||
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
|
|
||||||
self.storage.load_symbols(&mut self.code_builder, arguments);
|
|
||||||
match lowlevel {
|
|
||||||
LowLevel::Eq => match value_type {
|
|
||||||
ValueType::I32 => self.code_builder.i32_eq(),
|
|
||||||
ValueType::I64 => self.code_builder.i64_eq(),
|
|
||||||
ValueType::F32 => self.code_builder.f32_eq(),
|
|
||||||
ValueType::F64 => self.code_builder.f64_eq(),
|
|
||||||
},
|
|
||||||
LowLevel::NotEq => match value_type {
|
|
||||||
ValueType::I32 => self.code_builder.i32_ne(),
|
|
||||||
ValueType::I64 => self.code_builder.i64_ne(),
|
|
||||||
ValueType::F32 => self.code_builder.f32_ne(),
|
|
||||||
ValueType::F64 => self.code_builder.f64_ne(),
|
|
||||||
},
|
|
||||||
_ => internal_error!("Low-level op {:?} handled in the wrong place", lowlevel),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StackMemory {
|
|
||||||
format,
|
|
||||||
location: location0,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if let StackMemory {
|
|
||||||
location: location1,
|
|
||||||
..
|
|
||||||
} = self.storage.get(&arguments[1]).to_owned()
|
|
||||||
{
|
|
||||||
self.build_eq_num128(
|
|
||||||
format,
|
|
||||||
[location0, location1],
|
|
||||||
arguments,
|
|
||||||
return_layout,
|
|
||||||
mono_layout,
|
|
||||||
);
|
|
||||||
if matches!(lowlevel, LowLevel::NotEq) {
|
|
||||||
self.code_builder.i32_eqz();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_eq_num128(
|
|
||||||
&mut self,
|
|
||||||
format: StackMemoryFormat,
|
|
||||||
locations: [StackMemoryLocation; 2],
|
|
||||||
arguments: &'a [Symbol],
|
|
||||||
return_layout: WasmLayout,
|
|
||||||
mono_layout: &Layout<'a>,
|
|
||||||
) {
|
|
||||||
match format {
|
|
||||||
StackMemoryFormat::Decimal => {
|
|
||||||
// Both args are finite
|
|
||||||
let first = [arguments[0]];
|
|
||||||
let second = [arguments[1]];
|
|
||||||
|
|
||||||
// TODO!
|
|
||||||
//
|
|
||||||
// dispatch_low_level(
|
|
||||||
// &mut self.code_builder,
|
|
||||||
// &mut self.storage,
|
|
||||||
// LowLevel::NumIsFinite,
|
|
||||||
// &first,
|
|
||||||
// &return_layout,
|
|
||||||
// mono_layout,
|
|
||||||
// );
|
|
||||||
// dispatch_low_level(
|
|
||||||
// &mut self.code_builder,
|
|
||||||
// &mut self.storage,
|
|
||||||
// LowLevel::NumIsFinite,
|
|
||||||
// &second,
|
|
||||||
// &return_layout,
|
|
||||||
// mono_layout,
|
|
||||||
// );
|
|
||||||
self.code_builder.i32_and();
|
|
||||||
|
|
||||||
// AND they have the same bytes
|
|
||||||
self.build_eq_num128_bytes(locations);
|
|
||||||
self.code_builder.i32_and();
|
|
||||||
}
|
|
||||||
|
|
||||||
StackMemoryFormat::Int128 => self.build_eq_num128_bytes(locations),
|
|
||||||
|
|
||||||
StackMemoryFormat::Float128 => todo!("equality for f128"),
|
|
||||||
|
|
||||||
StackMemoryFormat::DataStructure => {
|
|
||||||
internal_error!("Data structure equality is handled elsewhere")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check that two 128-bit numbers contain the same bytes
|
|
||||||
fn build_eq_num128_bytes(&mut self, locations: [StackMemoryLocation; 2]) {
|
|
||||||
let (local0, offset0) = locations[0].local_and_offset(self.storage.stack_frame_pointer);
|
|
||||||
let (local1, offset1) = locations[1].local_and_offset(self.storage.stack_frame_pointer);
|
|
||||||
|
|
||||||
self.code_builder.get_local(local0);
|
|
||||||
self.code_builder.i64_load(Align::Bytes8, offset0);
|
|
||||||
self.code_builder.get_local(local1);
|
|
||||||
self.code_builder.i64_load(Align::Bytes8, offset1);
|
|
||||||
self.code_builder.i64_eq();
|
|
||||||
|
|
||||||
self.code_builder.get_local(local0);
|
|
||||||
self.code_builder.i64_load(Align::Bytes8, offset0 + 8);
|
|
||||||
self.code_builder.get_local(local1);
|
|
||||||
self.code_builder.i64_load(Align::Bytes8, offset1 + 8);
|
|
||||||
self.code_builder.i64_eq();
|
|
||||||
|
|
||||||
self.code_builder.i32_and();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call a helper procedure that implements `==` for a specific data structure
|
|
||||||
fn build_eq_specialized(
|
|
||||||
&mut self,
|
|
||||||
arg_layout: &Layout<'a>,
|
|
||||||
arguments: &'a [Symbol],
|
|
||||||
return_sym: Symbol,
|
|
||||||
storage: &StoredValue,
|
|
||||||
) {
|
|
||||||
let ident_ids = self
|
|
||||||
.interns
|
|
||||||
.all_ident_ids
|
|
||||||
.get_mut(&self.env.module_id)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Get an IR expression for the call to the specialized procedure
|
|
||||||
let (specialized_call_expr, new_specializations) = self
|
|
||||||
.helper_proc_gen
|
|
||||||
.call_specialized_equals(ident_ids, arg_layout, arguments);
|
|
||||||
|
|
||||||
// If any new specializations were created, register their symbol data
|
|
||||||
for spec in new_specializations.into_iter() {
|
|
||||||
self.register_helper_proc(spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate Wasm code for the IR call expression
|
|
||||||
let bool_layout = Layout::Builtin(Builtin::Bool);
|
|
||||||
self.expr(
|
|
||||||
return_sym,
|
|
||||||
self.env.arena.alloc(specialized_call_expr),
|
|
||||||
&bool_layout,
|
|
||||||
storage,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@ use bumpalo::collections::Vec;
|
||||||
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
|
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
|
||||||
use roc_module::low_level::{LowLevel, LowLevel::*};
|
use roc_module::low_level::{LowLevel, LowLevel::*};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_mono::layout::{Builtin, Layout};
|
use roc_mono::layout::{Builtin, Layout, UnionLayout};
|
||||||
use roc_reporting::internal_error;
|
use roc_reporting::internal_error;
|
||||||
|
|
||||||
use crate::backend::WasmBackend;
|
use crate::backend::WasmBackend;
|
||||||
use crate::layout::CallConv;
|
use crate::layout::CallConv;
|
||||||
use crate::layout::{StackMemoryFormat, WasmLayout};
|
use crate::layout::{StackMemoryFormat, WasmLayout};
|
||||||
use crate::storage::StoredValue;
|
use crate::storage::{StackMemoryLocation, StoredValue};
|
||||||
use crate::wasm_module::{Align, ValueType};
|
use crate::wasm_module::{Align, ValueType};
|
||||||
|
|
||||||
/// Number types used for Wasm code gen
|
/// Number types used for Wasm code gen
|
||||||
|
@ -31,7 +31,7 @@ enum CodeGenNumType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeGenNumType {
|
impl CodeGenNumType {
|
||||||
pub fn for_symbol<'a>(backend: &WasmBackend<'a>, symbol: Symbol) -> Self {
|
pub fn for_symbol(backend: &WasmBackend<'_>, symbol: Symbol) -> Self {
|
||||||
Self::from(backend.storage.get(&symbol))
|
Self::from(backend.storage.get(&symbol))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,22 +125,21 @@ impl<'a> LowLevelCall<'a> {
|
||||||
/// Load symbol values for a Zig call or numerical operation
|
/// Load symbol values for a Zig call or numerical operation
|
||||||
/// For numerical ops, this just pushes the arguments to the Wasm VM's value stack
|
/// For numerical ops, this just pushes the arguments to the Wasm VM's value stack
|
||||||
/// It implements the calling convention used by Zig for both numbers and structs
|
/// It implements the calling convention used by Zig for both numbers and structs
|
||||||
/// When returning structs, it also loads a stack frame pointer for the return value
|
/// Result is the type signature of the call
|
||||||
fn load_args(&self, backend: &mut WasmBackend<'a>) -> (Vec<'a, ValueType>, Option<ValueType>) {
|
fn load_args(&self, backend: &mut WasmBackend<'a>) -> (Vec<'a, ValueType>, Option<ValueType>) {
|
||||||
let fn_signature = backend.storage.load_symbols_for_call(
|
backend.storage.load_symbols_for_call(
|
||||||
backend.env.arena,
|
backend.env.arena,
|
||||||
&mut backend.code_builder,
|
&mut backend.code_builder,
|
||||||
self.arguments,
|
self.arguments,
|
||||||
self.ret_symbol,
|
self.ret_symbol,
|
||||||
&WasmLayout::new(&self.ret_layout),
|
&WasmLayout::new(&self.ret_layout),
|
||||||
CallConv::Zig,
|
CallConv::Zig,
|
||||||
);
|
)
|
||||||
fn_signature
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_args_and_call_zig(&self, backend: &mut WasmBackend<'a>, name: &'a str) {
|
fn load_args_and_call_zig(&self, backend: &mut WasmBackend<'a>, name: &'a str) {
|
||||||
let (param_types, ret_type) = self.load_args(backend);
|
let (param_types, ret_type) = self.load_args(backend);
|
||||||
backend.call_zig_builtin_after_loading_args(name, param_types, ret_type);
|
backend.call_zig_builtin_after_loading_args(name, param_types.len(), ret_type.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrap an integer whose Wasm representation is i32
|
/// Wrap an integer whose Wasm representation is i32
|
||||||
|
@ -173,6 +172,7 @@ impl<'a> LowLevelCall<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Main entrypoint from WasmBackend
|
||||||
pub fn generate(&self, backend: &mut WasmBackend<'a>) {
|
pub fn generate(&self, backend: &mut WasmBackend<'a>) {
|
||||||
use CodeGenNumType::*;
|
use CodeGenNumType::*;
|
||||||
|
|
||||||
|
@ -547,58 +547,8 @@ impl<'a> LowLevelCall<'a> {
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NumIsFinite => {
|
NumIsFinite => num_is_finite(backend, self.arguments[0]),
|
||||||
use StoredValue::*;
|
|
||||||
self.load_args(backend);
|
|
||||||
match backend.storage.get(&self.arguments[0]) {
|
|
||||||
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
|
|
||||||
match value_type {
|
|
||||||
ValueType::I32 | ValueType::I64 => backend.code_builder.i32_const(1), // always true for integers
|
|
||||||
ValueType::F32 => {
|
|
||||||
backend.code_builder.i32_reinterpret_f32();
|
|
||||||
backend.code_builder.i32_const(0x7f80_0000);
|
|
||||||
backend.code_builder.i32_and();
|
|
||||||
backend.code_builder.i32_const(0x7f80_0000);
|
|
||||||
backend.code_builder.i32_ne();
|
|
||||||
}
|
|
||||||
ValueType::F64 => {
|
|
||||||
backend.code_builder.i64_reinterpret_f64();
|
|
||||||
backend.code_builder.i64_const(0x7ff0_0000_0000_0000);
|
|
||||||
backend.code_builder.i64_and();
|
|
||||||
backend.code_builder.i64_const(0x7ff0_0000_0000_0000);
|
|
||||||
backend.code_builder.i64_ne();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StackMemory {
|
|
||||||
format, location, ..
|
|
||||||
} => {
|
|
||||||
let (local_id, offset) =
|
|
||||||
location.local_and_offset(backend.storage.stack_frame_pointer);
|
|
||||||
|
|
||||||
match format {
|
|
||||||
StackMemoryFormat::Int128 => backend.code_builder.i32_const(1),
|
|
||||||
StackMemoryFormat::Float128 => {
|
|
||||||
backend.code_builder.get_local(local_id);
|
|
||||||
backend.code_builder.i64_load(Align::Bytes4, offset + 8);
|
|
||||||
backend.code_builder.i64_const(0x7fff_0000_0000_0000);
|
|
||||||
backend.code_builder.i64_and();
|
|
||||||
backend.code_builder.i64_const(0x7fff_0000_0000_0000);
|
|
||||||
backend.code_builder.i64_ne();
|
|
||||||
}
|
|
||||||
StackMemoryFormat::Decimal => {
|
|
||||||
backend.code_builder.get_local(local_id);
|
|
||||||
backend.code_builder.i64_load(Align::Bytes4, offset + 8);
|
|
||||||
backend.code_builder.i64_const(0x7100_0000_0000_0000);
|
|
||||||
backend.code_builder.i64_and();
|
|
||||||
backend.code_builder.i64_const(0x7100_0000_0000_0000);
|
|
||||||
backend.code_builder.i64_ne();
|
|
||||||
}
|
|
||||||
StackMemoryFormat::DataStructure => panic_ret_type(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NumAtan => match self.ret_layout {
|
NumAtan => match self.ret_layout {
|
||||||
Layout::Builtin(Builtin::Float(width)) => {
|
Layout::Builtin(Builtin::Float(width)) => {
|
||||||
self.load_args_and_call_zig(backend, &bitcode::NUM_ATAN[width]);
|
self.load_args_and_call_zig(backend, &bitcode::NUM_ATAN[width]);
|
||||||
|
@ -720,8 +670,238 @@ impl<'a> LowLevelCall<'a> {
|
||||||
ExpectTrue => todo!("{:?}", self.lowlevel),
|
ExpectTrue => todo!("{:?}", self.lowlevel),
|
||||||
RefCountInc => self.load_args_and_call_zig(backend, bitcode::UTILS_INCREF),
|
RefCountInc => self.load_args_and_call_zig(backend, bitcode::UTILS_INCREF),
|
||||||
RefCountDec => self.load_args_and_call_zig(backend, bitcode::UTILS_DECREF),
|
RefCountDec => self.load_args_and_call_zig(backend, bitcode::UTILS_DECREF),
|
||||||
Eq | NotEq | Hash | PtrCast => {
|
|
||||||
internal_error!("{:?} should be handled in backend.rs", self.lowlevel)
|
PtrCast => {
|
||||||
|
let code_builder = &mut backend.code_builder;
|
||||||
|
backend.storage.load_symbols(code_builder, self.arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hash => todo!("{:?}", self.lowlevel),
|
||||||
|
|
||||||
|
Eq | NotEq => self.eq_or_neq(backend),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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]];
|
||||||
|
debug_assert!(
|
||||||
|
arg_layout == other_arg_layout,
|
||||||
|
"Cannot do `==` comparison on different types"
|
||||||
|
);
|
||||||
|
|
||||||
|
let invert_result = matches!(self.lowlevel, NotEq);
|
||||||
|
|
||||||
|
match arg_layout {
|
||||||
|
Layout::Builtin(
|
||||||
|
Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal,
|
||||||
|
) => self.eq_or_neq_number(backend),
|
||||||
|
|
||||||
|
Layout::Builtin(Builtin::Str) => {
|
||||||
|
self.load_args_and_call_zig(backend, bitcode::STR_EQUAL);
|
||||||
|
if invert_result {
|
||||||
|
backend.code_builder.i32_eqz();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty record is always equal to empty record.
|
||||||
|
// There are no runtime arguments to check, so just emit true or false.
|
||||||
|
Layout::Struct(fields) if fields.is_empty() => {
|
||||||
|
backend.code_builder.i32_const(!invert_result as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Void is always equal to void. This is the type for the contents of the empty list in `[] == []`
|
||||||
|
// This instruction will never execute, but we need an i32 for module validation
|
||||||
|
Layout::Union(UnionLayout::NonRecursive(tags)) if tags.is_empty() => {
|
||||||
|
backend.code_builder.i32_const(!invert_result as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_))
|
||||||
|
| Layout::Struct(_)
|
||||||
|
| Layout::Union(_)
|
||||||
|
| Layout::LambdaSet(_) => {
|
||||||
|
// Don't want Zig calling convention here, we're calling internal Roc functions
|
||||||
|
backend
|
||||||
|
.storage
|
||||||
|
.load_symbols(&mut backend.code_builder, self.arguments);
|
||||||
|
|
||||||
|
backend.call_eq_specialized(
|
||||||
|
self.arguments,
|
||||||
|
&arg_layout,
|
||||||
|
self.ret_symbol,
|
||||||
|
&self.ret_storage,
|
||||||
|
);
|
||||||
|
|
||||||
|
if invert_result {
|
||||||
|
backend.code_builder.i32_eqz();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout::RecursivePointer => {
|
||||||
|
internal_error!(
|
||||||
|
"Tried to apply `==` to RecursivePointer values {:?}",
|
||||||
|
self.arguments,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eq_or_neq_number(&self, backend: &mut WasmBackend<'a>) {
|
||||||
|
use StoredValue::*;
|
||||||
|
|
||||||
|
match backend.storage.get(&self.arguments[0]).to_owned() {
|
||||||
|
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
|
||||||
|
self.load_args(backend);
|
||||||
|
match self.lowlevel {
|
||||||
|
LowLevel::Eq => match value_type {
|
||||||
|
ValueType::I32 => backend.code_builder.i32_eq(),
|
||||||
|
ValueType::I64 => backend.code_builder.i64_eq(),
|
||||||
|
ValueType::F32 => backend.code_builder.f32_eq(),
|
||||||
|
ValueType::F64 => backend.code_builder.f64_eq(),
|
||||||
|
},
|
||||||
|
LowLevel::NotEq => match value_type {
|
||||||
|
ValueType::I32 => backend.code_builder.i32_ne(),
|
||||||
|
ValueType::I64 => backend.code_builder.i64_ne(),
|
||||||
|
ValueType::F32 => backend.code_builder.f32_ne(),
|
||||||
|
ValueType::F64 => backend.code_builder.f64_ne(),
|
||||||
|
},
|
||||||
|
_ => internal_error!("{:?} ended up in Equality code", self.lowlevel),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StackMemory {
|
||||||
|
format,
|
||||||
|
location: location0,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if let StackMemory {
|
||||||
|
location: location1,
|
||||||
|
..
|
||||||
|
} = backend.storage.get(&self.arguments[1]).to_owned()
|
||||||
|
{
|
||||||
|
self.eq_num128(backend, format, [location0, location1]);
|
||||||
|
if matches!(self.lowlevel, LowLevel::NotEq) {
|
||||||
|
backend.code_builder.i32_eqz();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Equality for 12-bit numbers. Checks if they're finite and contain the same bytes
|
||||||
|
/// Takes care of loading the arguments
|
||||||
|
fn eq_num128(
|
||||||
|
&self,
|
||||||
|
backend: &mut WasmBackend<'a>,
|
||||||
|
format: StackMemoryFormat,
|
||||||
|
locations: [StackMemoryLocation; 2],
|
||||||
|
) {
|
||||||
|
match format {
|
||||||
|
StackMemoryFormat::Decimal => {
|
||||||
|
// Both args are finite
|
||||||
|
num_is_finite(backend, self.arguments[0]);
|
||||||
|
num_is_finite(backend, self.arguments[1]);
|
||||||
|
backend.code_builder.i32_and();
|
||||||
|
|
||||||
|
// AND they have the same bytes
|
||||||
|
Self::eq_num128_bytes(backend, locations);
|
||||||
|
backend.code_builder.i32_and();
|
||||||
|
}
|
||||||
|
|
||||||
|
StackMemoryFormat::Int128 => Self::eq_num128_bytes(backend, locations),
|
||||||
|
|
||||||
|
StackMemoryFormat::Float128 => todo!("equality for f128"),
|
||||||
|
|
||||||
|
StackMemoryFormat::DataStructure => {
|
||||||
|
internal_error!("Data structure equality is handled elsewhere")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that two 128-bit numbers contain the same bytes
|
||||||
|
/// Loads *half* an argument at a time
|
||||||
|
/// (Don't call "load arguments" or "load symbols" helpers before this, it'll just waste instructions)
|
||||||
|
fn eq_num128_bytes(backend: &mut WasmBackend<'a>, locations: [StackMemoryLocation; 2]) {
|
||||||
|
let (local0, offset0) = locations[0].local_and_offset(backend.storage.stack_frame_pointer);
|
||||||
|
let (local1, offset1) = locations[1].local_and_offset(backend.storage.stack_frame_pointer);
|
||||||
|
|
||||||
|
// Load & compare the first half of each argument
|
||||||
|
backend.code_builder.get_local(local0);
|
||||||
|
backend.code_builder.i64_load(Align::Bytes8, offset0);
|
||||||
|
backend.code_builder.get_local(local1);
|
||||||
|
backend.code_builder.i64_load(Align::Bytes8, offset1);
|
||||||
|
backend.code_builder.i64_eq();
|
||||||
|
|
||||||
|
// Load & compare the second half of each argument
|
||||||
|
backend.code_builder.get_local(local0);
|
||||||
|
backend.code_builder.i64_load(Align::Bytes8, offset0 + 8);
|
||||||
|
backend.code_builder.get_local(local1);
|
||||||
|
backend.code_builder.i64_load(Align::Bytes8, offset1 + 8);
|
||||||
|
backend.code_builder.i64_eq();
|
||||||
|
|
||||||
|
// First half matches AND second half matches
|
||||||
|
backend.code_builder.i32_and();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper for NumIsFinite op, and also part of Eq/NotEq
|
||||||
|
fn num_is_finite(backend: &mut WasmBackend<'_>, argument: Symbol) {
|
||||||
|
use StoredValue::*;
|
||||||
|
let stored = backend.storage.get(&argument).to_owned();
|
||||||
|
match stored {
|
||||||
|
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
|
||||||
|
backend
|
||||||
|
.storage
|
||||||
|
.load_symbols(&mut backend.code_builder, &[argument]);
|
||||||
|
match value_type {
|
||||||
|
ValueType::I32 | ValueType::I64 => backend.code_builder.i32_const(1), // always true for integers
|
||||||
|
ValueType::F32 => {
|
||||||
|
backend.code_builder.i32_reinterpret_f32();
|
||||||
|
backend.code_builder.i32_const(0x7f80_0000);
|
||||||
|
backend.code_builder.i32_and();
|
||||||
|
backend.code_builder.i32_const(0x7f80_0000);
|
||||||
|
backend.code_builder.i32_ne();
|
||||||
|
}
|
||||||
|
ValueType::F64 => {
|
||||||
|
backend.code_builder.i64_reinterpret_f64();
|
||||||
|
backend.code_builder.i64_const(0x7ff0_0000_0000_0000);
|
||||||
|
backend.code_builder.i64_and();
|
||||||
|
backend.code_builder.i64_const(0x7ff0_0000_0000_0000);
|
||||||
|
backend.code_builder.i64_ne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StackMemory {
|
||||||
|
format, location, ..
|
||||||
|
} => {
|
||||||
|
let (local_id, offset) = location.local_and_offset(backend.storage.stack_frame_pointer);
|
||||||
|
|
||||||
|
match format {
|
||||||
|
StackMemoryFormat::Int128 => backend.code_builder.i32_const(1),
|
||||||
|
|
||||||
|
// f128 is not supported anywhere else but it's easy to support it here, so why not...
|
||||||
|
StackMemoryFormat::Float128 => {
|
||||||
|
backend.code_builder.get_local(local_id);
|
||||||
|
backend.code_builder.i64_load(Align::Bytes4, offset + 8);
|
||||||
|
backend.code_builder.i64_const(0x7fff_0000_0000_0000);
|
||||||
|
backend.code_builder.i64_and();
|
||||||
|
backend.code_builder.i64_const(0x7fff_0000_0000_0000);
|
||||||
|
backend.code_builder.i64_ne();
|
||||||
|
}
|
||||||
|
|
||||||
|
StackMemoryFormat::Decimal => {
|
||||||
|
backend.code_builder.get_local(local_id);
|
||||||
|
backend.code_builder.i64_load(Align::Bytes4, offset + 8);
|
||||||
|
backend.code_builder.i64_const(0x7100_0000_0000_0000);
|
||||||
|
backend.code_builder.i64_and();
|
||||||
|
backend.code_builder.i64_const(0x7100_0000_0000_0000);
|
||||||
|
backend.code_builder.i64_ne();
|
||||||
|
}
|
||||||
|
|
||||||
|
StackMemoryFormat::DataStructure => {
|
||||||
|
internal_error!("Tried to perform NumIsFinite on a data structure")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue