mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Merge pull request #3278 from rtfeldman/wasm-more-lowlevels
Implement more Num lowlevels in Wasm
This commit is contained in:
commit
f206194d1f
7 changed files with 262 additions and 99 deletions
|
@ -116,6 +116,12 @@ comptime {
|
||||||
num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos.");
|
num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos.");
|
||||||
num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan.");
|
num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan.");
|
||||||
|
|
||||||
|
num.exportSin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sin.");
|
||||||
|
num.exportCos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".cos.");
|
||||||
|
|
||||||
|
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow.");
|
||||||
|
num.exportLog(T, ROC_BUILTINS ++ "." ++ NUM ++ ".log.");
|
||||||
|
|
||||||
num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite.");
|
num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,33 @@ pub fn exportAtan(comptime T: type, comptime name: []const u8) void {
|
||||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn exportSin(comptime T: type, comptime name: []const u8) void {
|
||||||
|
comptime var f = struct {
|
||||||
|
fn func(input: T) callconv(.C) T {
|
||||||
|
return @sin(input);
|
||||||
|
}
|
||||||
|
}.func;
|
||||||
|
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exportCos(comptime T: type, comptime name: []const u8) void {
|
||||||
|
comptime var f = struct {
|
||||||
|
fn func(input: T) callconv(.C) T {
|
||||||
|
return @cos(input);
|
||||||
|
}
|
||||||
|
}.func;
|
||||||
|
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exportLog(comptime T: type, comptime name: []const u8) void {
|
||||||
|
comptime var f = struct {
|
||||||
|
fn func(input: T) callconv(.C) T {
|
||||||
|
return @log(input);
|
||||||
|
}
|
||||||
|
}.func;
|
||||||
|
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||||
|
}
|
||||||
|
|
||||||
pub fn exportRoundF32(comptime T: type, comptime name: []const u8) void {
|
pub fn exportRoundF32(comptime T: type, comptime name: []const u8) void {
|
||||||
comptime var f = struct {
|
comptime var f = struct {
|
||||||
fn func(input: f32) callconv(.C) T {
|
fn func(input: f32) callconv(.C) T {
|
||||||
|
|
|
@ -271,13 +271,17 @@ macro_rules! int_intrinsic {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const NUM_SIN: IntrinsicName = float_intrinsic!("roc_builtins.num.sin");
|
||||||
|
pub const NUM_COS: IntrinsicName = float_intrinsic!("roc_builtins.num.cos");
|
||||||
pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin");
|
pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin");
|
||||||
pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos");
|
pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos");
|
||||||
pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
|
pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
|
||||||
pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite");
|
pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite");
|
||||||
|
pub const NUM_LOG: IntrinsicName = float_intrinsic!("roc_builtins.num.log");
|
||||||
|
pub const NUM_POW: IntrinsicName = float_intrinsic!("roc_builtins.num.pow");
|
||||||
|
|
||||||
pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int");
|
pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int");
|
||||||
pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil");
|
pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil");
|
||||||
|
|
||||||
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
|
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
|
||||||
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
|
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ use roc_std::RocDec;
|
||||||
|
|
||||||
use crate::layout::{CallConv, ReturnMethod, WasmLayout};
|
use crate::layout::{CallConv, ReturnMethod, WasmLayout};
|
||||||
use crate::low_level::{call_higher_order_lowlevel, LowLevelCall};
|
use crate::low_level::{call_higher_order_lowlevel, LowLevelCall};
|
||||||
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
use crate::storage::{Storage, StoredValue, StoredVarKind};
|
||||||
use crate::wasm_module::linking::{DataSymbol, WasmObjectSymbol};
|
use crate::wasm_module::linking::{DataSymbol, WasmObjectSymbol};
|
||||||
use crate::wasm_module::sections::{
|
use crate::wasm_module::sections::{
|
||||||
ConstExpr, DataMode, DataSegment, Export, Global, GlobalType, Import, ImportDesc, Limits,
|
ConstExpr, DataMode, DataSegment, Export, Global, GlobalType, Import, ImportDesc, Limits,
|
||||||
|
@ -414,10 +414,8 @@ impl<'a> WasmBackend<'a> {
|
||||||
// We never use the `return` instruction. Instead, we break from this block.
|
// We never use the `return` instruction. Instead, we break from this block.
|
||||||
self.start_block();
|
self.start_block();
|
||||||
|
|
||||||
for (layout, symbol) in proc.args {
|
self.storage
|
||||||
self.storage
|
.allocate_args(proc.args, &mut self.code_builder, self.env.arena);
|
||||||
.allocate(*layout, *symbol, StoredValueKind::Parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ty) = ret_type {
|
if let Some(ty) = ret_type {
|
||||||
let ret_var = self.storage.create_anonymous_local(ty);
|
let ret_var = self.storage.create_anonymous_local(ty);
|
||||||
|
@ -660,8 +658,8 @@ impl<'a> WasmBackend<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let kind = match following {
|
let kind = match following {
|
||||||
Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredValueKind::ReturnValue,
|
Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredVarKind::ReturnValue,
|
||||||
_ => StoredValueKind::Variable,
|
_ => StoredVarKind::Variable,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.stmt_let_store_expr(*sym, layout, expr, kind);
|
self.stmt_let_store_expr(*sym, layout, expr, kind);
|
||||||
|
@ -677,9 +675,9 @@ impl<'a> WasmBackend<'a> {
|
||||||
sym: Symbol,
|
sym: Symbol,
|
||||||
layout: &Layout<'a>,
|
layout: &Layout<'a>,
|
||||||
expr: &Expr<'a>,
|
expr: &Expr<'a>,
|
||||||
kind: StoredValueKind,
|
kind: StoredVarKind,
|
||||||
) {
|
) {
|
||||||
let sym_storage = self.storage.allocate(*layout, sym, kind);
|
let sym_storage = self.storage.allocate_var(*layout, sym, kind);
|
||||||
|
|
||||||
self.expr(sym, expr, layout, &sym_storage);
|
self.expr(sym, expr, layout, &sym_storage);
|
||||||
|
|
||||||
|
@ -817,10 +815,10 @@ impl<'a> WasmBackend<'a> {
|
||||||
// make locals for join pointer parameters
|
// make locals for join pointer parameters
|
||||||
let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena);
|
let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena);
|
||||||
for parameter in parameters.iter() {
|
for parameter in parameters.iter() {
|
||||||
let mut param_storage = self.storage.allocate(
|
let mut param_storage = self.storage.allocate_var(
|
||||||
parameter.layout,
|
parameter.layout,
|
||||||
parameter.symbol,
|
parameter.symbol,
|
||||||
StoredValueKind::Variable,
|
StoredVarKind::Variable,
|
||||||
);
|
);
|
||||||
param_storage = self.storage.ensure_value_has_local(
|
param_storage = self.storage.ensure_value_has_local(
|
||||||
&mut self.code_builder,
|
&mut self.code_builder,
|
||||||
|
@ -1019,7 +1017,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
let (upper_bits, lower_bits) = RocDec::from_ne_bytes(*bytes).as_bits();
|
let (upper_bits, lower_bits) = RocDec::from_ne_bytes(*bytes).as_bits();
|
||||||
write128(lower_bits as i64, upper_bits);
|
write128(lower_bits as i64, upper_bits);
|
||||||
}
|
}
|
||||||
Literal::Int(x) => {
|
Literal::Int(x) | Literal::U128(x) => {
|
||||||
let lower_bits = (i128::from_ne_bytes(*x) & 0xffff_ffff_ffff_ffff) as i64;
|
let lower_bits = (i128::from_ne_bytes(*x) & 0xffff_ffff_ffff_ffff) as i64;
|
||||||
let upper_bits = (i128::from_ne_bytes(*x) >> 64) as i64;
|
let upper_bits = (i128::from_ne_bytes(*x) >> 64) as i64;
|
||||||
write128(lower_bits, upper_bits);
|
write128(lower_bits, upper_bits);
|
||||||
|
@ -1072,7 +1070,8 @@ impl<'a> WasmBackend<'a> {
|
||||||
self.code_builder.i32_store(Align::Bytes4, offset + 8);
|
self.code_builder.i32_store(Align::Bytes4, offset + 8);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => invalid_error(),
|
// Bools and bytes should not be stored in the stack frame
|
||||||
|
Literal::Bool(_) | Literal::Byte(_) => invalid_error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1420,7 +1419,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
elem_sym,
|
elem_sym,
|
||||||
elem_layout,
|
elem_layout,
|
||||||
&expr,
|
&expr,
|
||||||
StoredValueKind::Variable,
|
StoredVarKind::Variable,
|
||||||
);
|
);
|
||||||
|
|
||||||
elem_sym
|
elem_sym
|
||||||
|
|
|
@ -578,10 +578,39 @@ impl<'a> LowLevelCall<'a> {
|
||||||
_ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout),
|
_ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NumSin => todo!("{:?}", self.lowlevel),
|
NumSin => match self.ret_layout {
|
||||||
NumCos => todo!("{:?}", self.lowlevel),
|
Layout::Builtin(Builtin::Float(width)) => {
|
||||||
NumSqrtUnchecked => todo!("{:?}", self.lowlevel),
|
self.load_args_and_call_zig(backend, &bitcode::NUM_SIN[width]);
|
||||||
NumLogUnchecked => todo!("{:?}", self.lowlevel),
|
}
|
||||||
|
_ => panic_ret_type(),
|
||||||
|
},
|
||||||
|
NumCos => match self.ret_layout {
|
||||||
|
Layout::Builtin(Builtin::Float(width)) => {
|
||||||
|
self.load_args_and_call_zig(backend, &bitcode::NUM_COS[width]);
|
||||||
|
}
|
||||||
|
_ => panic_ret_type(),
|
||||||
|
},
|
||||||
|
NumSqrtUnchecked => {
|
||||||
|
self.load_args(backend);
|
||||||
|
match self.ret_layout {
|
||||||
|
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||||
|
backend.code_builder.f32_sqrt()
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||||
|
backend.code_builder.f64_sqrt()
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::Float(FloatWidth::F128)) => {
|
||||||
|
todo!("sqrt for f128")
|
||||||
|
}
|
||||||
|
_ => panic_ret_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumLogUnchecked => match self.ret_layout {
|
||||||
|
Layout::Builtin(Builtin::Float(width)) => {
|
||||||
|
self.load_args_and_call_zig(backend, &bitcode::NUM_LOG[width]);
|
||||||
|
}
|
||||||
|
_ => panic_ret_type(),
|
||||||
|
},
|
||||||
NumToFrac => {
|
NumToFrac => {
|
||||||
self.load_args(backend);
|
self.load_args(backend);
|
||||||
let ret_type = CodeGenNumType::from(self.ret_layout);
|
let ret_type = CodeGenNumType::from(self.ret_layout);
|
||||||
|
@ -600,7 +629,12 @@ impl<'a> LowLevelCall<'a> {
|
||||||
_ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type),
|
_ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NumPow => todo!("{:?}", self.lowlevel),
|
NumPow => match self.ret_layout {
|
||||||
|
Layout::Builtin(Builtin::Float(width)) => {
|
||||||
|
self.load_args_and_call_zig(backend, &bitcode::NUM_POW[width]);
|
||||||
|
}
|
||||||
|
_ => panic_ret_type(),
|
||||||
|
},
|
||||||
NumRound => {
|
NumRound => {
|
||||||
self.load_args(backend);
|
self.load_args(backend);
|
||||||
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
|
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
|
||||||
|
@ -687,8 +721,8 @@ impl<'a> LowLevelCall<'a> {
|
||||||
}
|
}
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
},
|
},
|
||||||
NumBytesToU16 => todo!("{:?}", self.lowlevel),
|
NumBytesToU16 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U16),
|
||||||
NumBytesToU32 => todo!("{:?}", self.lowlevel),
|
NumBytesToU32 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U32),
|
||||||
NumBitwiseAnd => {
|
NumBitwiseAnd => {
|
||||||
self.load_args(backend);
|
self.load_args(backend);
|
||||||
match CodeGenNumType::from(self.ret_layout) {
|
match CodeGenNumType::from(self.ret_layout) {
|
||||||
|
@ -785,7 +819,36 @@ impl<'a> LowLevelCall<'a> {
|
||||||
todo!("implement toF32 and toF64");
|
todo!("implement toF32 and toF64");
|
||||||
}
|
}
|
||||||
NumToIntChecked => {
|
NumToIntChecked => {
|
||||||
todo!()
|
let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]];
|
||||||
|
|
||||||
|
let (arg_width, ret_width) = match (arg_layout, self.ret_layout) {
|
||||||
|
(
|
||||||
|
Layout::Builtin(Builtin::Int(arg_width)),
|
||||||
|
Layout::Struct {
|
||||||
|
field_layouts: &[Layout::Builtin(Builtin::Int(ret_width)), ..],
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) => (arg_width, ret_width),
|
||||||
|
_ => {
|
||||||
|
internal_error!(
|
||||||
|
"NumToIntChecked is not defined for signature {:?} -> {:?}",
|
||||||
|
arg_layout,
|
||||||
|
self.ret_layout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if arg_width.is_signed() {
|
||||||
|
self.load_args_and_call_zig(
|
||||||
|
backend,
|
||||||
|
&bitcode::NUM_INT_TO_INT_CHECKING_MAX_AND_MIN[ret_width][arg_width],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
self.load_args_and_call_zig(
|
||||||
|
backend,
|
||||||
|
&bitcode::NUM_INT_TO_INT_CHECKING_MAX[ret_width][arg_width],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
NumToFloatChecked => {
|
NumToFloatChecked => {
|
||||||
todo!("implement toF32Checked and toF64Checked");
|
todo!("implement toF32Checked and toF64Checked");
|
||||||
|
@ -938,16 +1001,7 @@ impl<'a> LowLevelCall<'a> {
|
||||||
locations: [StackMemoryLocation; 2],
|
locations: [StackMemoryLocation; 2],
|
||||||
) {
|
) {
|
||||||
match format {
|
match format {
|
||||||
StackMemoryFormat::Decimal => {
|
StackMemoryFormat::Decimal => Self::eq_num128_bytes(backend, locations),
|
||||||
// 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::Int128 => Self::eq_num128_bytes(backend, locations),
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,7 @@ use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout};
|
||||||
use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
||||||
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_TYPE};
|
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_TYPE};
|
||||||
|
|
||||||
pub enum StoredValueKind {
|
pub enum StoredVarKind {
|
||||||
Parameter,
|
|
||||||
Variable,
|
Variable,
|
||||||
ReturnValue,
|
ReturnValue,
|
||||||
}
|
}
|
||||||
|
@ -124,7 +123,7 @@ impl<'a> Storage<'a> {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate storage for a Roc value
|
/// Allocate storage for a Roc variable
|
||||||
///
|
///
|
||||||
/// Wasm primitives (i32, i64, f32, f64) are allocated "storage" on the VM stack.
|
/// Wasm primitives (i32, i64, f32, f64) are allocated "storage" on the VM stack.
|
||||||
/// This is really just a way to model how the stack machine works as a sort of
|
/// This is really just a way to model how the stack machine works as a sort of
|
||||||
|
@ -134,31 +133,21 @@ impl<'a> Storage<'a> {
|
||||||
///
|
///
|
||||||
/// Structs and Tags are stored in memory rather than in Wasm primitives.
|
/// Structs and Tags are stored in memory rather than in Wasm primitives.
|
||||||
/// They are allocated a certain offset and size in the stack frame.
|
/// They are allocated a certain offset and size in the stack frame.
|
||||||
pub fn allocate(
|
pub fn allocate_var(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout<'a>,
|
layout: Layout<'a>,
|
||||||
symbol: Symbol,
|
symbol: Symbol,
|
||||||
kind: StoredValueKind,
|
kind: StoredVarKind,
|
||||||
) -> StoredValue {
|
) -> StoredValue {
|
||||||
let next_local_id = self.get_next_local_id();
|
let next_local_id = self.get_next_local_id();
|
||||||
let wasm_layout = WasmLayout::new(&layout);
|
let wasm_layout = WasmLayout::new(&layout);
|
||||||
self.symbol_layouts.insert(symbol, layout);
|
self.symbol_layouts.insert(symbol, layout);
|
||||||
|
|
||||||
let storage = match wasm_layout {
|
let storage = match wasm_layout {
|
||||||
WasmLayout::Primitive(value_type, size) => match kind {
|
WasmLayout::Primitive(value_type, size) => StoredValue::VirtualMachineStack {
|
||||||
StoredValueKind::Parameter => {
|
vm_state: VmSymbolState::NotYetPushed,
|
||||||
self.arg_types.push(value_type);
|
value_type,
|
||||||
StoredValue::Local {
|
size,
|
||||||
local_id: next_local_id,
|
|
||||||
value_type,
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => StoredValue::VirtualMachineStack {
|
|
||||||
vm_state: VmSymbolState::NotYetPushed,
|
|
||||||
value_type,
|
|
||||||
size,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
WasmLayout::StackMemory {
|
WasmLayout::StackMemory {
|
||||||
|
@ -167,18 +156,7 @@ impl<'a> Storage<'a> {
|
||||||
format,
|
format,
|
||||||
} => {
|
} => {
|
||||||
let location = match kind {
|
let location = match kind {
|
||||||
StoredValueKind::Parameter => {
|
StoredVarKind::Variable => {
|
||||||
if size > 0 {
|
|
||||||
self.arg_types.push(PTR_TYPE);
|
|
||||||
StackMemoryLocation::PointerArg(next_local_id)
|
|
||||||
} else {
|
|
||||||
// An argument with zero size is purely conceptual, and will not exist in Wasm.
|
|
||||||
// However we need to track the symbol, so we treat it like a local variable.
|
|
||||||
StackMemoryLocation::FrameOffset(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StoredValueKind::Variable => {
|
|
||||||
if self.stack_frame_pointer.is_none() && size > 0 {
|
if self.stack_frame_pointer.is_none() && size > 0 {
|
||||||
self.stack_frame_pointer = Some(next_local_id);
|
self.stack_frame_pointer = Some(next_local_id);
|
||||||
self.local_types.push(PTR_TYPE);
|
self.local_types.push(PTR_TYPE);
|
||||||
|
@ -192,7 +170,7 @@ impl<'a> Storage<'a> {
|
||||||
StackMemoryLocation::FrameOffset(offset as u32)
|
StackMemoryLocation::FrameOffset(offset as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
StoredValueKind::ReturnValue => StackMemoryLocation::PointerArg(LocalId(0)),
|
StoredVarKind::ReturnValue => StackMemoryLocation::PointerArg(LocalId(0)),
|
||||||
};
|
};
|
||||||
|
|
||||||
StoredValue::StackMemory {
|
StoredValue::StackMemory {
|
||||||
|
@ -209,6 +187,98 @@ impl<'a> Storage<'a> {
|
||||||
storage
|
storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allocate storage for a Roc procedure argument
|
||||||
|
/// Each argument is also a local variable. Their indices come before other locals.
|
||||||
|
/// Structs and Tags are passed as pointers into the caller's frame
|
||||||
|
/// 128-bit numbers are passed as two i64's, but we immediately store them in the
|
||||||
|
/// stack frame, because it's a lot easier to keep track of the data flow.
|
||||||
|
pub fn allocate_args(
|
||||||
|
&mut self,
|
||||||
|
args: &[(Layout<'a>, Symbol)],
|
||||||
|
code_builder: &mut CodeBuilder,
|
||||||
|
arena: &'a Bump,
|
||||||
|
) {
|
||||||
|
let mut wide_number_args = Vec::with_capacity_in(args.len(), arena);
|
||||||
|
|
||||||
|
for (layout, symbol) in args {
|
||||||
|
self.symbol_layouts.insert(*symbol, *layout);
|
||||||
|
let wasm_layout = WasmLayout::new(layout);
|
||||||
|
let local_index = self.arg_types.len() as u32;
|
||||||
|
|
||||||
|
let storage = match wasm_layout {
|
||||||
|
WasmLayout::Primitive(value_type, size) => {
|
||||||
|
self.arg_types.push(value_type);
|
||||||
|
StoredValue::Local {
|
||||||
|
local_id: LocalId(local_index),
|
||||||
|
value_type,
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WasmLayout::StackMemory {
|
||||||
|
size,
|
||||||
|
alignment_bytes,
|
||||||
|
format,
|
||||||
|
} => {
|
||||||
|
use StackMemoryFormat::*;
|
||||||
|
|
||||||
|
self.arg_types
|
||||||
|
.extend_from_slice(CallConv::C.stack_memory_arg_types(size, format));
|
||||||
|
|
||||||
|
let location = match format {
|
||||||
|
Int128 | Float128 | Decimal => {
|
||||||
|
// passed as two i64's but stored in the stack frame
|
||||||
|
wide_number_args.push(local_index);
|
||||||
|
let loc =
|
||||||
|
StackMemoryLocation::FrameOffset(self.stack_frame_size as u32);
|
||||||
|
self.stack_frame_size += size as i32;
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
DataStructure => {
|
||||||
|
if size == 0 {
|
||||||
|
// An argument with zero size is purely conceptual, and will not exist in Wasm.
|
||||||
|
// However we need to track the symbol, so we treat it like a local variable.
|
||||||
|
StackMemoryLocation::FrameOffset(0)
|
||||||
|
} else {
|
||||||
|
StackMemoryLocation::PointerArg(LocalId(local_index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
StoredValue::StackMemory {
|
||||||
|
location,
|
||||||
|
size,
|
||||||
|
alignment_bytes,
|
||||||
|
format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.symbol_storage_map.insert(*symbol, storage.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any arguments are 128-bit numbers, store them in the stack frame
|
||||||
|
// This makes it easier to keep track of which symbols are on the Wasm value stack
|
||||||
|
// The frame pointer will be the next local after the arguments
|
||||||
|
if self.stack_frame_size > 0 {
|
||||||
|
let frame_ptr = LocalId(self.arg_types.len() as u32);
|
||||||
|
self.stack_frame_pointer = Some(frame_ptr);
|
||||||
|
self.local_types.push(PTR_TYPE);
|
||||||
|
|
||||||
|
let mut offset = 0;
|
||||||
|
for arg_index in wide_number_args.iter().copied() {
|
||||||
|
code_builder.get_local(frame_ptr);
|
||||||
|
code_builder.get_local(LocalId(arg_index));
|
||||||
|
code_builder.i64_store(Align::Bytes8, offset);
|
||||||
|
|
||||||
|
code_builder.get_local(frame_ptr);
|
||||||
|
code_builder.get_local(LocalId(arg_index + 1));
|
||||||
|
code_builder.i64_store(Align::Bytes8, offset + 8);
|
||||||
|
|
||||||
|
offset += 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get storage info for a given symbol
|
/// Get storage info for a given symbol
|
||||||
pub fn get(&self, sym: &Symbol) -> &StoredValue {
|
pub fn get(&self, sym: &Symbol) -> &StoredValue {
|
||||||
self.symbol_storage_map.get(sym).unwrap_or_else(|| {
|
self.symbol_storage_map.get(sym).unwrap_or_else(|| {
|
||||||
|
@ -471,18 +541,21 @@ impl<'a> Storage<'a> {
|
||||||
alignment_bytes,
|
alignment_bytes,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer);
|
if size > 0 {
|
||||||
copy_memory(
|
let (from_ptr, from_offset) =
|
||||||
code_builder,
|
location.local_and_offset(self.stack_frame_pointer);
|
||||||
CopyMemoryConfig {
|
copy_memory(
|
||||||
from_ptr,
|
code_builder,
|
||||||
from_offset,
|
CopyMemoryConfig {
|
||||||
to_ptr,
|
from_ptr,
|
||||||
to_offset,
|
from_offset,
|
||||||
size,
|
to_ptr,
|
||||||
alignment_bytes,
|
to_offset,
|
||||||
},
|
size,
|
||||||
);
|
alignment_bytes,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -468,7 +468,7 @@ fn f32_float_alias() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn f64_sqrt() {
|
fn f64_sqrt() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -484,7 +484,7 @@ fn f64_sqrt() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn f64_log() {
|
fn f64_log() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -498,7 +498,7 @@ fn f64_log() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn f64_log_checked_one() {
|
fn f64_log_checked_one() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -514,7 +514,7 @@ fn f64_log_checked_one() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn f64_sqrt_zero() {
|
fn f64_sqrt_zero() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -530,7 +530,7 @@ fn f64_sqrt_zero() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn f64_sqrt_checked_negative() {
|
fn f64_sqrt_checked_negative() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -546,7 +546,7 @@ fn f64_sqrt_checked_negative() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn f64_log_checked_zero() {
|
fn f64_log_checked_zero() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -562,7 +562,7 @@ fn f64_log_checked_zero() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn f64_log_negative() {
|
fn f64_log_negative() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -1170,21 +1170,21 @@ fn gen_is_even() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn sin() {
|
fn sin() {
|
||||||
assert_evals_to!("Num.sin 0", 0.0, f64);
|
assert_evals_to!("Num.sin 0", 0.0, f64);
|
||||||
assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64);
|
assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn cos() {
|
fn cos() {
|
||||||
assert_evals_to!("Num.cos 0", 1.0, f64);
|
assert_evals_to!("Num.cos 0", 1.0, f64);
|
||||||
assert_evals_to!("Num.cos 3.14159265359", -1.0, f64);
|
assert_evals_to!("Num.cos 3.14159265359", -1.0, f64);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn tan() {
|
fn tan() {
|
||||||
assert_evals_to!("Num.tan 0", 0.0, f64);
|
assert_evals_to!("Num.tan 0", 0.0, f64);
|
||||||
assert_evals_to!("Num.tan 1", 1.557407724654902, f64);
|
assert_evals_to!("Num.tan 1", 1.557407724654902, f64);
|
||||||
|
@ -1634,7 +1634,7 @@ fn float_compare() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn pow() {
|
fn pow() {
|
||||||
assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64);
|
assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64);
|
||||||
}
|
}
|
||||||
|
@ -2487,7 +2487,7 @@ num_conversion_tests! {
|
||||||
macro_rules! to_int_checked_tests {
|
macro_rules! to_int_checked_tests {
|
||||||
($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr)*))*) => {$($(
|
($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr)*))*) => {$($(
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn $test_name() {
|
fn $test_name() {
|
||||||
let sentinel = 23;
|
let sentinel = 23;
|
||||||
// Some n = Ok n, None = OutOfBounds
|
// Some n = Ok n, None = OutOfBounds
|
||||||
|
@ -2647,7 +2647,7 @@ fn is_multiple_of() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn bytes_to_u16_clearly_out_of_bounds() {
|
fn bytes_to_u16_clearly_out_of_bounds() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -2664,7 +2664,7 @@ fn bytes_to_u16_clearly_out_of_bounds() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn bytes_to_u16_subtly_out_of_bounds() {
|
fn bytes_to_u16_subtly_out_of_bounds() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -2681,7 +2681,7 @@ fn bytes_to_u16_subtly_out_of_bounds() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn bytes_to_u32_clearly_out_of_bounds() {
|
fn bytes_to_u32_clearly_out_of_bounds() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -2698,7 +2698,7 @@ fn bytes_to_u32_clearly_out_of_bounds() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn bytes_to_u32_subtly_out_of_bounds() {
|
fn bytes_to_u32_subtly_out_of_bounds() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -2715,7 +2715,7 @@ fn bytes_to_u32_subtly_out_of_bounds() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn bytes_to_u16_max_u8s() {
|
fn bytes_to_u16_max_u8s() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -2731,7 +2731,7 @@ fn bytes_to_u16_max_u8s() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn bytes_to_u16_min_u8s() {
|
fn bytes_to_u16_min_u8s() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -2747,7 +2747,7 @@ fn bytes_to_u16_min_u8s() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn bytes_to_u16_random_u8s() {
|
fn bytes_to_u16_random_u8s() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -2763,7 +2763,7 @@ fn bytes_to_u16_random_u8s() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn bytes_to_u32_min_u8s() {
|
fn bytes_to_u32_min_u8s() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -2779,7 +2779,7 @@ fn bytes_to_u32_min_u8s() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn bytes_to_u32_max_u8s() {
|
fn bytes_to_u32_max_u8s() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -2795,7 +2795,7 @@ fn bytes_to_u32_max_u8s() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn bytes_to_u32_random_u8s() {
|
fn bytes_to_u32_random_u8s() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue