Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runtime): Allow modulo on floating point numbers #1914

Merged
merged 3 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/test/TestFramework.re
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ let test_data_dir = Fp.At.(test_dir / "test-data");
let test_input_dir = Fp.At.(test_dir / "input");
let test_output_dir = Fp.At.(test_dir / "output");
let test_stdlib_dir = Fp.At.(test_dir / "stdlib");
let test_runtime_dir = Fp.At.(test_dir / "runtime");
let test_snapshots_dir = Fp.At.(test_dir / "__snapshots__");

let test_grainfmt_dir = Fp.At.(test_dir / "grainfmt");
Expand Down
17 changes: 17 additions & 0 deletions compiler/test/runner.re
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ let grainfile = name =>
Filepath.to_string(Fp.At.(test_input_dir / (name ++ ".gr")));
let stdlibfile = name =>
Filepath.to_string(Fp.At.(test_stdlib_dir / (name ++ ".gr")));
let runtimefile = name =>
Filepath.to_string(Fp.At.(test_runtime_dir / (name ++ ".gr")));
let wasmfile = name =>
Filepath.to_string(Fp.At.(test_output_dir / (name ++ ".gr.wasm")));
let watfile = name =>
Expand Down Expand Up @@ -392,6 +394,21 @@ let makeStdlibRunner = (test, ~code=0, name) => {
});
};

let makeRuntimeRunner = (test, ~code=0, name) => {
test(name, ({expect}) => {
Config.preserve_all_configs(() => {
// Run stdlib suites in release mode
Config.profile := Some(Release);
let infile = runtimefile(name);
let outfile = wasmfile(name);
ignore @@ compile_file(infile, outfile);
let (result, exit_code) = run(outfile);
expect.int(exit_code).toBe(code);
expect.string(result).toEqual("");
})
});
};

let parse = (name, lexbuf, source) => {
let ret = Grain_parsing.Driver.parse(~name, lexbuf, source);
open Grain_parsing;
Expand Down
95 changes: 95 additions & 0 deletions compiler/test/runtime/numbers.test.gr
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
module NumberTest

include "runtime/numbers"

// Simple isNaN
let isNaN = x => x != x
let isInfinite = x => x == Infinity || x == -Infinity
from Numbers use { (%) }

// (%)
assert 20 % 4 == 0
assert 20 % 3 == 2
assert 15 % 6 == 3
assert -10 % 3 == 2
assert 5 % 2.75 == 2.25
assert 3.0 % 2.0 == 1.0
assert 3.0 % -2.0 == 1.0
assert -3.0 % 2.0 == -1.0
assert -3.0 % -2.0 == -1.0
assert 3.5 % 2.0 == 1.5
assert 3.5 % -2.0 == 1.5
assert -3.5 % 2.0 == -1.5
assert -3.5 % -2.0 == -1.5
assert 3.0 % 2.5 == 0.5
assert 3.0 % -2.5 == 0.5
assert -3.0 % 2.5 == -0.5
assert -3.0 % -2.5 == -0.5
assert 0.5 % 1.0 == 0.5
assert 0.5 % -1.0 == 0.5
assert -0.5 % 1.0 == -0.5
assert -0.5 % -1.0 == -0.5
assert 1.5 % 1.0 == 0.5
assert 1.5 % -1.0 == 0.5
assert -1.5 % 1.0 == -0.5
assert -1.5 % -1.0 == -0.5
assert 1.25 % 1.0 == 0.25
assert 1.25 % -1.0 == 0.25
assert -1.25 % 1.0 == -0.25
assert -1.25 % -1.0 == -0.25
assert 1.0 % 1.25 == 1.0
assert 1.0 % -1.25 == 1.0
assert -1.0 % 1.25 == -1.0
assert -1.0 % -1.25 == -1.0
assert -13 % 64 == 51
assert isNaN(0.0 % 0.0)
assert isNaN(-0.0 % 0.0)
assert isNaN(0.0 % -0.0)
assert isNaN(-0.0 % -0.0)
assert 0.0 % 1.0 == 0.0
assert -0.0 % 1.0 == -0.0
assert 0.0 % -1.0 == 0.0
assert -0.0 % -1.0 == -0.0
assert isNaN(1.0 % 0.0)
assert isNaN(-1.0 % 0.0)
assert isNaN(1.0 % -0.0)
assert isNaN(-1.0 % -0.0)
assert isNaN(NaN % 0.0)
assert isNaN(NaN % -0.0)
assert isNaN(NaN % 1.0)
assert isNaN(NaN % -1.0)
assert isNaN(NaN % 0.0)
assert isNaN(NaN % -0.0)
assert isNaN(NaN % 1.0)
assert isNaN(NaN % -1.0)
assert isNaN(NaN % NaN)
assert 0.0 % Infinity == 0.0
assert -0.0 % Infinity == -0.0
assert 0.0 % -Infinity == 0.0
assert -0.0 % -Infinity == -0.0
assert 1.0 % Infinity == 1.0
assert -1.0 % Infinity == -1.0
assert 1.0 % -Infinity == 1.0
assert -1.0 % -Infinity == -1.0
assert isNaN(Infinity % 0.0)
assert isNaN(Infinity % -0.0)
assert isNaN(-Infinity % 0.0)
assert isNaN(-Infinity % -0.0)
assert isNaN(Infinity % 1.0)
assert isNaN(Infinity % -1.0)
assert isNaN(-Infinity % 1.0)
assert isNaN(-Infinity % -1.0)
assert isNaN(Infinity % Infinity)
assert isNaN(-Infinity % Infinity)
assert isNaN(Infinity % -Infinity)
assert isNaN(-Infinity % -Infinity)
assert isNaN(Infinity % NaN)
assert isNaN(-Infinity % NaN)
assert isNaN(NaN % Infinity)
assert isNaN(NaN % -Infinity)
assert -17 % 4 == 3
assert -17 % -4 == -1
assert 17 % -4 == 5
assert -17 % 17 == 0
assert 17 % -17 == 0
assert 17 % 17 == 0
10 changes: 10 additions & 0 deletions compiler/test/suites/runtime.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
open Grain_tests.TestFramework;
open Grain_tests.Runner;

describe("runtime", ({test, testSkip}) => {
let test_or_skip =
Sys.backend_type == Other("js_of_ocaml") ? testSkip : test;

let assertRuntime = makeRuntimeRunner(test_or_skip);
assertRuntime("numbers.test");
});
42 changes: 29 additions & 13 deletions stdlib/runtime/numbers.gr
Original file line number Diff line number Diff line change
Expand Up @@ -1592,22 +1592,38 @@ let numberMod = (x, y) => {
// incRef x and y to reuse them via WasmI32.toGrain
Memory.incRef(x)
Memory.incRef(y)
let xval = coerceNumberToWasmI64(WasmI32.toGrain(x): Number)
let yval = coerceNumberToWasmI64(WasmI32.toGrain(y): Number)
if (WasmI64.eqz(yval)) {
throw Exception.ModuloByZero
}
// We implement true modulo
if (xval < 0N && yval > 0N || xval > 0N && yval < 0N) {
let modval = WasmI64.remS(i64abs(xval), i64abs(yval))
let result = if (modval != 0N) {
i64abs(yval) - modval * (if (yval < 0N) -1N else 1N)
if (isFloat(x) || isFloat(y) || isRational(x) || isRational(y)) {
from WasmF64 use { (==), (/), (*), (-) }
let xval = coerceNumberToWasmF64(WasmI32.toGrain(x): Number)
let yval = coerceNumberToWasmF64(WasmI32.toGrain(y): Number)
let yInfinite = yval == InfinityW || yval == -InfinityW
if (
yval == 0.0W || yInfinite && (xval == InfinityW || xval == -InfinityW)
) {
newFloat64(NaNW)
} else if (yInfinite) {
newFloat64(xval)
} else {
modval
newFloat64(xval - WasmF64.trunc(xval / yval) * yval)
}
reducedInteger(result)
} else {
reducedInteger(WasmI64.remS(xval, yval))
let xval = coerceNumberToWasmI64(WasmI32.toGrain(x): Number)
let yval = coerceNumberToWasmI64(WasmI32.toGrain(y): Number)
if (WasmI64.eqz(yval)) {
throw Exception.ModuloByZero
}
// We implement true modulo
if (xval < 0N && yval > 0N || xval > 0N && yval < 0N) {
let modval = WasmI64.remS(i64abs(xval), i64abs(yval))
let result = if (modval != 0N) {
i64abs(yval) - modval * (if (yval < 0N) -1N else 1N)
} else {
modval
}
reducedInteger(result)
} else {
reducedInteger(WasmI64.remS(xval, yval))
}
}
}

Expand Down
Loading