Source code
Revision control
Copy as Markdown
Other Tools
// |jit-test| --disable-main-thread-wasm-denormals; skip-if: !getBuildConfiguration("can-disable-main-thread-wasm-denormals")
// Verify that --disable-main-thread-wasm-denormals scopes FTZ+DAZ to wasm
// JIT code only.
// Load a denormal out of a typed array in an obfuscated way to prevent
// compiler optimizations from fusing the multiply.
const tiny = new Float64Array([Number.MIN_VALUE]);
function jsFlushesDenormals() {
return tiny[0] * 1 === 0;
}
const flushFn = wasmEvalText(`(module
(func (export "flush") (param f64) (result f64)
(f64.mul (local.get 0) (f64.const 1))))`).exports.flush;
// Wasm sees FTZ.
assertEq(flushFn(5e-324), 0);
// JS does not.
assertEq(jsFlushesDenormals(), false);
// Boundary conversion (JS Value -> wasm f32) are IEEE.
const f32EchoFn = wasmEvalText(`(module
(func (export "echo") (param f32) (result f32)
(local.get 0)))`).exports.echo;
assertEq(f32EchoFn(Math.pow(2, -140)) !== 0, true);
// JS import always sees IEEE, and wasm afterwards gets FTZ still.
{
let sawFlushed = true;
function checkImport() {
sawFlushed = jsFlushesDenormals();
return 0;
}
const { run } = wasmEvalText(`(module
(import "" "checkImport" (func $ci (result i32)))
(func (export "run") (param f64) (result f64)
(drop (call $ci))
(f64.mul (local.get 0) (f64.const 1))))`,
{ "": { checkImport } }).exports;
assertEq(run(5e-324), 0);
assertEq(sawFlushed, false);
}
// Direct call from Ion and the jit-exit path: hot loops on both sides.
{
const { flush } = wasmEvalText(`(module
(func (export "flush") (param f64) (result f64)
(f64.mul (local.get 0) (f64.const 1))))`).exports;
let bad = 0;
for (let i = 0; i < 100000; i++) {
if (flush(5e-324) !== 0) bad++;
}
assertEq(bad, 0);
assertEq(jsFlushesDenormals(), false);
}
{
let bad = 0;
function checkImport() {
if (jsFlushesDenormals()) bad++;
return 0;
}
const { run } = wasmEvalText(`(module
(import "" "checkImport" (func $ci (result i32)))
(func (export "run") (result i32) (local i32)
(loop $l
(local.set 0 (call $ci))
(br_if $l (i32.lt_s (local.get 0) (i32.const 0))))
(local.get 0)))`,
{ "": { checkImport } }).exports;
for (let i = 0; i < 20000; i++) run();
assertEq(bad, 0);
}
// Throw across the boundary: JS import throws, caught in JS. IEEE must be
// restored after the unwind.
{
function throwingImport() {
throw new Error("boom");
}
const { run } = wasmEvalText(`(module
(import "" "throwingImport" (func $ti))
(func (export "run")
(call $ti)))`,
{ "": { throwingImport } }).exports;
let caught = false;
try {
run();
} catch (e) {
caught = true;
}
assertEq(caught, true);
assertEq(jsFlushesDenormals(), false);
}
// Trap: OOB memory access, caught in JS.
{
const { oob } = wasmEvalText(`(module
(memory 1)
(func (export "oob") (result i32)
(i32.load (i32.const 100000))))`).exports;
let caught = false;
try {
oob();
} catch (e) {
caught = true;
}
assertEq(caught, true);
assertEq(jsFlushesDenormals(), false);
}
// Calls to builtins.
{
const { copy } = wasmEvalText(`(module
(memory 1)
(func (export "copy")
(memory.copy (i32.const 0) (i32.const 100) (i32.const 200))))`).exports;
assertEq(jsFlushesDenormals(), false);
copy();
assertEq(jsFlushesDenormals(), false);
}
// Interrupt trap: the callback sees IEEE, and wasm resumes with FTZ.
{
let callbackFlushed = true;
setInterruptCallback(() => {
callbackFlushed = jsFlushesDenormals();
return true;
});
function requestInterrupt() {
interruptIf(true);
}
// Keep many values live across the loop so that Ion allocates every GPR,
// including the one that normally holds the instance.
const locals = Array.from({ length: 8 }, (_, i) => `$l${i}`);
const { run } = wasmEvalText(`(module
(import "" "requestInterrupt" (func $ri))
(func (export "run") (param f64) (result f64)
(local $i i32) ${locals.map(l => `(local ${l} i32)`).join(" ")}
(call $ri)
(loop $l
${locals.map((l, i) =>
`(local.set ${l} (i32.add (local.get ${l}) (i32.const ${i + 1})))`
).join("\n")}
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br_if $l (i32.lt_s (local.get $i) (i32.const 1000))))
(drop ${locals.reduce((acc, l) => `(i32.add ${acc} (local.get ${l}))`,
"(i32.const 0)")})
(f64.mul (local.get 0) (f64.const 1))))`,
{ "": { requestInterrupt } }).exports;
for (let i = 0; i < 100; i++) {
callbackFlushed = true;
assertEq(run(5e-324), 0);
assertEq(callbackFlushed, false);
}
setInterruptCallback(() => true);
}