Implement builtins for Num.isNan, Num.isInfinite, and Num.isFinite

Closes #5310 and closes #5309
This commit is contained in:
Basile Henry 2023-04-30 16:28:13 +01:00
parent d84a9fa8ba
commit b8aaaaabda
12 changed files with 202 additions and 1 deletions

View file

@ -126,6 +126,8 @@ comptime {
num.exportSubWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_with_overflow.");
num.exportMulWithOverflow(T, T, ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow.");
num.exportIsNan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_nan.");
num.exportIsInfinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_infinite.");
num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite.");
}
}

View file

@ -95,6 +95,24 @@ pub fn exportPow(comptime T: type, comptime name: []const u8) void {
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportIsNan(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) bool {
return std.math.isNan(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportIsInfinite(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) bool {
return std.math.isInf(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportIsFinite(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) bool {

View file

@ -55,6 +55,9 @@ interface Num
toFrac,
isPositive,
isNegative,
isNaN,
isInfinite,
isFinite,
rem,
remChecked,
div,
@ -621,6 +624,29 @@ isNegative = \x -> x < 0
toFrac : Num * -> Frac *
## Returns `Bool.true` if the [Frac] is not a number as defined by [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
##
## ```
## Num.isNaN (0 / 0)
## ```
isNaN : Frac * -> Bool
## Returns `Bool.true` if the [Frac] is positive or negative infinity as defined by [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
##
## ```
## Num.isInfinite (1 / 0)
##
## Num.isInfinite (-1 / 0)
## ```
isInfinite : Frac * -> Bool
## Returns `Bool.true` if the [Frac] is not an infinity as defined by [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
##
## ```
## Num.isFinite 42
## ```
isFinite : Frac * -> Bool
## Return the absolute value of the number.
##
## * For a positive number, returns the same number.

View file

@ -262,6 +262,8 @@ 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_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos");
pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
pub const NUM_IS_NAN: IntrinsicName = float_intrinsic!("roc_builtins.num.is_nan");
pub const NUM_IS_INFINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_infinite");
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");

View file

@ -184,6 +184,9 @@ map_symbol_to_lowlevel_and_arity! {
NumLogUnchecked; NUM_LOG; 1,
NumRound; NUM_ROUND; 1,
NumToFrac; NUM_TO_FRAC; 1,
NumIsNan; NUM_IS_NAN; 1,
NumIsInfinite; NUM_IS_INFINITE; 1,
NumIsFinite; NUM_IS_FINITE; 1,
NumPow; NUM_POW; 2,
NumCeiling; NUM_CEILING; 1,
NumPowInt; NUM_POW_INT; 2,

View file

@ -885,6 +885,8 @@ pub(crate) fn run_low_level<'a, 'ctx>(
| NumCeiling
| NumFloor
| NumToFrac
| NumIsNan
| NumIsInfinite
| NumIsFinite
| NumAtan
| NumAcos
@ -2351,6 +2353,10 @@ fn build_float_unary_op<'a, 'ctx>(
"num_round",
)
}
NumIsNan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_NAN[float_width]),
NumIsInfinite => {
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_INFINITE[float_width])
}
NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]),
// trigonometry

View file

@ -1613,6 +1613,8 @@ impl<'a> LowLevelCall<'a> {
self.load_args_and_call_zig(backend, &bitcode::NUM_POW_INT[width])
}
NumIsNan => num_is_nan(backend, self.arguments[0]),
NumIsInfinite => num_is_infinite(backend, self.arguments[0]),
NumIsFinite => num_is_finite(backend, self.arguments[0]),
NumAtan => match self.ret_layout_raw {
@ -2167,6 +2169,112 @@ impl<'a> LowLevelCall<'a> {
}
}
/// Helper for NumIsNan op
fn num_is_nan(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 {
// Integers are never NaN. Just return False.
ValueType::I32 | ValueType::I64 => backend.code_builder.i32_const(0),
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_eq(); // Exponents are all ones
backend
.storage
.load_symbols(&mut backend.code_builder, &[argument]);
backend.code_builder.i32_reinterpret_f32();
backend.code_builder.i32_const(0x007f_ffff);
backend.code_builder.i32_and();
backend.code_builder.i32_const(0);
backend.code_builder.i32_ne(); // Mantissa is non-zero
backend.code_builder.i32_and();
}
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_eq(); // Exponents are all ones
backend
.storage
.load_symbols(&mut backend.code_builder, &[argument]);
backend.code_builder.i64_reinterpret_f64();
backend.code_builder.i64_const(0x000f_ffff_ffff_ffff);
backend.code_builder.i64_and();
backend.code_builder.i64_const(0);
backend.code_builder.i64_ne(); // Mantissa is non-zero
backend.code_builder.i32_and();
}
}
}
StackMemory { format, .. } => {
match format {
// Integers and fixed-point numbers are NaN. Just return False.
StackMemoryFormat::Int128 | StackMemoryFormat::Decimal => {
backend.code_builder.i32_const(0)
}
StackMemoryFormat::DataStructure => {
internal_error!("Tried to perform NumIsInfinite on a data structure")
}
}
}
}
}
/// Helper for NumIsInfinite op
fn num_is_infinite(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 {
// Integers are never infinite. Just return False.
ValueType::I32 | ValueType::I64 => backend.code_builder.i32_const(0),
ValueType::F32 => {
backend.code_builder.i32_reinterpret_f32();
backend.code_builder.i32_const(0x7fff_ffff);
backend.code_builder.i32_and();
backend.code_builder.i32_const(0x7f80_0000);
backend.code_builder.i32_eq();
}
ValueType::F64 => {
backend.code_builder.i64_reinterpret_f64();
backend.code_builder.i64_const(0x7fff_ffff_ffff_ffff);
backend.code_builder.i64_and();
backend.code_builder.i64_const(0x7ff0_0000_0000_0000);
backend.code_builder.i64_eq();
}
}
}
StackMemory { format, .. } => {
match format {
// Integers and fixed-point numbers are never infinite. Just return False.
StackMemoryFormat::Int128 | StackMemoryFormat::Decimal => {
backend.code_builder.i32_const(0)
}
StackMemoryFormat::DataStructure => {
internal_error!("Tried to perform NumIsInfinite on a data structure")
}
}
}
}
}
/// Helper for NumIsFinite op, and also part of Eq/NotEq
fn num_is_finite(backend: &mut WasmBackend<'_, '_>, argument: Symbol) {
use StoredValue::*;

View file

@ -86,6 +86,8 @@ pub enum LowLevel {
NumCeiling,
NumPowInt,
NumFloor,
NumIsNan,
NumIsInfinite,
NumIsFinite,
NumAtan,
NumAcos,
@ -234,7 +236,6 @@ macro_rules! map_symbol_to_lowlevel {
// these are not implemented, not sure why
LowLevel::StrFromInt => unimplemented!(),
LowLevel::StrFromFloat => unimplemented!(),
LowLevel::NumIsFinite => unimplemented!(),
}
}
};
@ -314,6 +315,9 @@ map_symbol_to_lowlevel! {
NumLogUnchecked <= NUM_LOG,
NumRound <= NUM_ROUND,
NumToFrac <= NUM_TO_FRAC,
NumIsNan <= NUM_IS_NAN,
NumIsInfinite <= NUM_IS_INFINITE,
NumIsFinite <= NUM_IS_FINITE,
NumPow <= NUM_POW,
NumCeiling <= NUM_CEILING,
NumPowInt <= NUM_POW_INT,

View file

@ -1254,6 +1254,9 @@ define_builtins! {
153 NUM_COUNT_TRAILING_ZERO_BITS: "countTrailingZeroBits"
154 NUM_COUNT_ONE_BITS: "countOneBits"
155 NUM_ABS_DIFF: "absDiff"
156 NUM_IS_NAN: "isNaN"
157 NUM_IS_INFINITE: "isInfinite"
158 NUM_IS_FINITE: "isFinite"
}
4 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias

View file

@ -1002,6 +1002,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] {
| NumFloor
| NumToFrac
| Not
| NumIsNan
| NumIsInfinite
| NumIsFinite
| NumAtan
| NumAcos

View file

@ -109,6 +109,8 @@ enum FirstOrder {
NumCeiling,
NumPowInt,
NumFloor,
NumIsNan,
NumIsInfinite,
NumIsFinite,
NumAtan,
NumAcos,

View file

@ -1705,6 +1705,31 @@ fn float_to_float() {
assert_evals_to!("Num.toFrac 0.5", 0.5, f64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn frac_is_nan() {
assert_evals_to!("Num.isNaN (0 / 0)", true, bool);
assert_evals_to!("Num.isNaN (1 / 0)", false, bool);
assert_evals_to!("Num.isNaN 42", false, bool);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn frac_is_infinite() {
assert_evals_to!("Num.isInfinite (1 / 0)", true, bool);
assert_evals_to!("Num.isInfinite (-1 / 0)", true, bool);
assert_evals_to!("Num.isInfinite (0 / 0)", false, bool);
assert_evals_to!("Num.isInfinite 42", false, bool);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn frac_is_finite() {
assert_evals_to!("Num.isFinite 42", true, bool);
assert_evals_to!("Num.isFinite (1 / 0)", false, bool);
assert_evals_to!("Num.isFinite (0 / 0)", false, bool);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn int_compare() {