roc/compiler/gen_wasm/src/low_level.rs

530 lines
23 KiB
Rust

use roc_builtins::bitcode::{self, FloatWidth};
use roc_module::low_level::{LowLevel, LowLevel::*};
use roc_module::symbol::Symbol;
use crate::layout::{StackMemoryFormat::*, WasmLayout};
use crate::storage::{Storage, StoredValue};
use crate::wasm_module::{CodeBuilder, ValueType::*};
pub enum LowlevelBuildResult {
Done,
BuiltinCall(&'static str),
NotImplemented,
}
pub fn decode_low_level<'a>(
code_builder: &mut CodeBuilder<'a>,
storage: &mut Storage<'a>,
lowlevel: LowLevel,
args: &'a [Symbol],
ret_layout: &WasmLayout,
) -> LowlevelBuildResult {
use LowlevelBuildResult::*;
let panic_ret_type = || panic!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout);
match lowlevel {
StrConcat => return BuiltinCall(bitcode::STR_CONCAT),
StrJoinWith => return NotImplemented, // needs Array
StrIsEmpty => {
code_builder.i64_const(i64::MIN);
code_builder.i64_eq();
}
StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH),
StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT),
StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH),
StrSplit => return NotImplemented, // needs Array
StrCountGraphemes => return NotImplemented, // test needs Array
StrFromInt => return NotImplemented, // choose builtin based on storage size
StrFromUtf8 => return NotImplemented, // needs Array
StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT),
StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT),
StrFromUtf8Range => return NotImplemented, // needs Array
StrToUtf8 => return NotImplemented, // needs Array
StrRepeat => return BuiltinCall(bitcode::STR_REPEAT),
StrFromFloat => {
// linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3
// https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html
// https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html
return NotImplemented;
}
StrTrim => return BuiltinCall(bitcode::STR_TRIM),
ListLen | ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2
| ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
| ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe | DictSize | DictEmpty
| DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues
| DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
return NotImplemented;
}
NumAdd => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_add(),
I64 => code_builder.i64_add(),
F32 => code_builder.f32_add(),
F64 => code_builder.f64_add(),
},
WasmLayout::StackMemory { format, .. } => match format {
DataStructure => return NotImplemented,
Int128 => return NotImplemented,
Float128 => return NotImplemented,
Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW),
},
},
NumAddWrap => match ret_layout {
WasmLayout::Primitive(value_type, size) => match value_type {
I32 => {
code_builder.i32_add();
// TODO: is *deliberate* wrapping really in the spirit of things?
// The point of choosing NumAddWrap is to go fast by skipping checks, but we're making it slower.
wrap_i32(code_builder, *size);
}
I64 => code_builder.i64_add(),
F32 => code_builder.f32_add(),
F64 => code_builder.f64_add(),
},
WasmLayout::StackMemory { format, .. } => match format {
DataStructure => return NotImplemented,
Int128 => return NotImplemented,
Float128 => return NotImplemented,
Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW),
},
},
NumToStr => return NotImplemented,
NumAddChecked => return NotImplemented,
NumSub => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_sub(),
I64 => code_builder.i64_sub(),
F32 => code_builder.f32_sub(),
F64 => code_builder.f64_sub(),
},
WasmLayout::StackMemory { format, .. } => match format {
DataStructure => return NotImplemented,
Int128 => return NotImplemented,
Float128 => return NotImplemented,
Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW),
},
},
NumSubWrap => match ret_layout {
WasmLayout::Primitive(value_type, size) => match value_type {
I32 => {
code_builder.i32_sub();
wrap_i32(code_builder, *size);
}
I64 => code_builder.i64_sub(),
F32 => code_builder.f32_sub(),
F64 => code_builder.f64_sub(),
},
WasmLayout::StackMemory { format, .. } => match format {
DataStructure => return NotImplemented,
Int128 => return NotImplemented,
Float128 => return NotImplemented,
Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW),
},
},
NumSubChecked => return NotImplemented,
NumMul => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_mul(),
I64 => code_builder.i64_mul(),
F32 => code_builder.f32_mul(),
F64 => code_builder.f64_mul(),
},
WasmLayout::StackMemory { format, .. } => match format {
DataStructure => return NotImplemented,
Int128 => return NotImplemented,
Float128 => return NotImplemented,
Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW),
},
},
NumMulWrap => match ret_layout {
WasmLayout::Primitive(value_type, size) => match value_type {
I32 => {
code_builder.i32_mul();
wrap_i32(code_builder, *size);
}
I64 => code_builder.i64_mul(),
F32 => code_builder.f32_mul(),
F64 => code_builder.f64_mul(),
},
WasmLayout::StackMemory { format, .. } => match format {
DataStructure => return NotImplemented,
Int128 => return NotImplemented,
Float128 => return NotImplemented,
Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW),
},
},
NumMulChecked => return NotImplemented,
NumGt => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_gt_s(),
I64 => code_builder.i64_gt_s(),
F32 => code_builder.f32_gt(),
F64 => code_builder.f64_gt(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NumGte => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_ge_s(),
I64 => code_builder.i64_ge_s(),
F32 => code_builder.f32_ge(),
F64 => code_builder.f64_ge(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NumLt => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_lt_s(),
I64 => code_builder.i64_lt_s(),
F32 => code_builder.f32_lt(),
F64 => code_builder.f64_lt(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NumLte => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_le_s(),
I64 => code_builder.i64_le_s(),
F32 => code_builder.f32_le(),
F64 => code_builder.f64_le(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NumCompare => return NotImplemented,
NumDivUnchecked => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_div_s(),
I64 => code_builder.i64_div_s(),
F32 => code_builder.f32_div(),
F64 => code_builder.f64_div(),
},
StoredValue::StackMemory { format, .. } => match format {
DataStructure => return NotImplemented,
Int128 => return NotImplemented,
Float128 => return NotImplemented,
Decimal => return BuiltinCall(bitcode::DEC_DIV),
},
},
NumDivCeilUnchecked => return NotImplemented,
NumRemUnchecked => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_rem_s(),
I64 => code_builder.i64_rem_s(),
F32 => return NotImplemented,
F64 => return NotImplemented,
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NumIsMultipleOf => return NotImplemented,
NumAbs => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => {
let arg_storage = storage.get(&args[0]).to_owned();
storage.ensure_value_has_local(code_builder, args[0], arg_storage);
storage.load_symbols(code_builder, args);
code_builder.i32_const(0);
storage.load_symbols(code_builder, args);
code_builder.i32_sub();
storage.load_symbols(code_builder, args);
code_builder.i32_const(0);
code_builder.i32_ge_s();
code_builder.select();
}
I64 => {
let arg_storage = storage.get(&args[0]).to_owned();
storage.ensure_value_has_local(code_builder, args[0], arg_storage);
storage.load_symbols(code_builder, args);
code_builder.i64_const(0);
storage.load_symbols(code_builder, args);
code_builder.i64_sub();
storage.load_symbols(code_builder, args);
code_builder.i64_const(0);
code_builder.i64_ge_s();
code_builder.select();
}
F32 => code_builder.f32_abs(),
F64 => code_builder.f64_abs(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NumNeg => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => {
code_builder.i32_const(0);
storage.load_symbols(code_builder, args);
code_builder.i32_sub();
}
I64 => {
code_builder.i64_const(0);
storage.load_symbols(code_builder, args);
code_builder.i64_sub();
}
F32 => code_builder.f32_neg(),
F64 => code_builder.f64_neg(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumSin => return NotImplemented,
NumCos => return NotImplemented,
NumSqrtUnchecked => return NotImplemented,
NumLogUnchecked => return NotImplemented,
NumRound => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
F32 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F32]),
F64 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F64]),
_ => return NotImplemented,
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NumToFloat => {
use StoredValue::*;
let stored = storage.get(&args[0]);
match ret_layout {
WasmLayout::Primitive(ret_type, _) => match stored {
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
match (ret_type, value_type) {
(F32, I32) => code_builder.f32_convert_s_i32(),
(F32, I64) => code_builder.f32_convert_s_i64(),
(F32, F32) => {}
(F32, F64) => code_builder.f32_demote_f64(),
(F64, I32) => code_builder.f64_convert_s_i32(),
(F64, I64) => code_builder.f64_convert_s_i64(),
(F64, F32) => code_builder.f64_promote_f32(),
(F64, F64) => {}
_ => panic_ret_type(),
}
}
StackMemory { .. } => return NotImplemented,
},
WasmLayout::StackMemory { .. } => return NotImplemented,
}
}
NumPow => return NotImplemented,
NumCeiling => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => {
code_builder.f32_ceil();
code_builder.i32_trunc_s_f32()
}
I64 => {
code_builder.f64_ceil();
code_builder.i64_trunc_s_f64()
}
_ => panic_ret_type(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumPowInt => return NotImplemented,
NumFloor => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => {
code_builder.f32_floor();
code_builder.i32_trunc_s_f32()
}
I64 => {
code_builder.f64_floor();
code_builder.i64_trunc_s_f64()
}
_ => panic_ret_type(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumIsFinite => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_const(1),
I64 => code_builder.i32_const(1),
F32 => {
code_builder.i32_reinterpret_f32();
code_builder.i32_const(0x7f800000);
code_builder.i32_and();
code_builder.i32_const(0x7f800000);
code_builder.i32_ne();
}
F64 => {
code_builder.i64_reinterpret_f64();
code_builder.i64_const(0x7ff0000000000000);
code_builder.i64_and();
code_builder.i64_const(0x7ff0000000000000);
code_builder.i64_ne();
}
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumAtan => {
let width = float_width_from_layout(ret_layout);
return BuiltinCall(&bitcode::NUM_ATAN[width]);
}
NumAcos => {
let width = float_width_from_layout(ret_layout);
return BuiltinCall(&bitcode::NUM_ACOS[width]);
}
NumAsin => {
let width = float_width_from_layout(ret_layout);
return BuiltinCall(&bitcode::NUM_ASIN[width]);
}
NumBytesToU16 => return NotImplemented,
NumBytesToU32 => return NotImplemented,
NumBitwiseAnd => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_and(),
I64 => code_builder.i64_and(),
_ => panic_ret_type(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumBitwiseXor => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_xor(),
I64 => code_builder.i64_xor(),
_ => panic_ret_type(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumBitwiseOr => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_or(),
I64 => code_builder.i64_or(),
_ => panic_ret_type(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumShiftLeftBy => {
// Swap order of arguments
storage.load_symbols(code_builder, &[args[1], args[0]]);
match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_shl(),
I64 => code_builder.i64_shl(),
_ => panic_ret_type(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
}
}
NumShiftRightBy => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_shr_s(),
I64 => code_builder.i64_shr_s(),
_ => panic_ret_type(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumShiftRightZfBy => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_shr_u(),
I64 => code_builder.i64_shr_u(),
_ => panic_ret_type(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumIntCast => {
use StoredValue::*;
let stored = storage.get(&args[0]);
match ret_layout {
WasmLayout::Primitive(ret_type, _) => match stored {
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
match (ret_type, value_type) {
(I32, I32) => {}
(I32, I64) => code_builder.i32_wrap_i64(),
(I32, F32) => code_builder.i32_trunc_s_f32(),
(I32, F64) => code_builder.i32_trunc_s_f64(),
(I64, I32) => code_builder.i64_extend_s_i32(),
(I64, I64) => {}
(I64, F32) => code_builder.i64_trunc_s_f32(),
(I64, F64) => code_builder.i64_trunc_s_f64(),
(F32, I32) => code_builder.f32_convert_s_i32(),
(F32, I64) => code_builder.f32_convert_s_i64(),
(F32, F32) => {}
(F32, F64) => code_builder.f32_demote_f64(),
(F64, I32) => code_builder.f64_convert_s_i32(),
(F64, I64) => code_builder.f64_convert_s_i64(),
(F64, F32) => code_builder.f64_promote_f32(),
(F64, F64) => {}
}
}
StackMemory { .. } => return NotImplemented,
},
WasmLayout::StackMemory { .. } => return NotImplemented,
}
}
Eq => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_eq(),
I64 => code_builder.i64_eq(),
F32 => code_builder.f32_eq(),
F64 => code_builder.f64_eq(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NotEq => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_ne(),
I64 => code_builder.i64_ne(),
F32 => code_builder.f32_ne(),
F64 => code_builder.f64_ne(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
And => code_builder.i32_and(),
Or => code_builder.i32_or(),
Not => code_builder.i32_eqz(),
Hash => return NotImplemented,
ExpectTrue => return NotImplemented,
RefCountGetPtr => {
code_builder.i32_const(4);
code_builder.i32_sub();
}
RefCountInc => return BuiltinCall(bitcode::UTILS_INCREF),
RefCountDec => return BuiltinCall(bitcode::UTILS_DECREF),
}
Done
}
/// Wrap an integer whose Wasm representation is i32
fn wrap_i32(code_builder: &mut CodeBuilder, size: u32) {
match size {
1 => {
// Underlying Roc value is i8
code_builder.i32_const(24);
code_builder.i32_shl();
code_builder.i32_const(24);
code_builder.i32_shr_s();
}
2 => {
// Underlying Roc value is i16
code_builder.i32_const(16);
code_builder.i32_shl();
code_builder.i32_const(16);
code_builder.i32_shr_s();
}
_ => {} // the only other possible value is 4, and i32 wraps natively
}
}
fn float_width_from_layout(wasm_layout: &WasmLayout) -> FloatWidth {
match wasm_layout {
WasmLayout::Primitive(F32, _) => FloatWidth::F32,
WasmLayout::Primitive(F64, _) => FloatWidth::F64,
_ => panic!("{:?} does not have a FloatWidth", wasm_layout),
}
}