Source code

Revision control

Copy as Markdown

Other Tools

if (!wasmIsSupported())
quit();
load(libdir + "asserts.js");
function canRunHugeMemoryTests() {
// We're aiming for 64-bit desktop builds with no interesting analysis
// running that might inflate memory consumption unreasonably. It's OK if
// they're debug builds, though.
//
// The build configuration object may be extended at any time with new
// properties, so neither an allowlist of properties that can be true or a
// blocklist of properties that can't be true is great. But the latter is
// probably better.
let blocked = ['rooting-analysis','simulator',
'android','wasi','asan','tsan','ubsan','dtrace','valgrind'];
for ( let b of blocked ) {
if (getBuildConfiguration(b)) {
print("Failing canRunHugeMemoryTests() because '" + b + "' is true");
return false;
}
}
if (getBuildConfiguration("pointer-byte-size") != 8) {
print("Failing canRunHugeMemoryTests() because the build is not 64-bit");
return false;
}
return true;
}
// On 64-bit systems with explicit bounds checking, ion and baseline can handle
// 65536 pages.
var PageSizeInBytes = 65536;
var MaxBytesIn32BitMemory = 0;
if (largeArrayBufferSupported()) {
MaxBytesIn32BitMemory = 65536*PageSizeInBytes;
} else {
// This is an overestimate twice: first, the max byte value is divisible by
// the page size; second, it must be a valid bounds checking immediate. But
// INT32_MAX is fine for testing.
MaxBytesIn32BitMemory = 0x7FFF_FFFF;
}
var MaxPagesIn32BitMemory = Math.floor(MaxBytesIn32BitMemory / PageSizeInBytes);
function wasmEvalBinary(binary, imports, compileOptions) {
let valid = WebAssembly.validate(binary, compileOptions);
let m;
try {
m = new WebAssembly.Module(binary, compileOptions);
assertEq(valid, true, "failed WebAssembly.validate but still compiled successfully");
} catch(e) {
if (!e.toString().match(/out of memory/)) {
assertEq(valid, false, `passed WebAssembly.validate but failed to compile: ${e}`);
}
throw e;
}
return new WebAssembly.Instance(m, imports);
}
function wasmEvalText(str, imports, compileOptions) {
return wasmEvalBinary(wasmTextToBinary(str), imports, compileOptions);
}
function wasmValidateBinary(binary) {
let valid = WebAssembly.validate(binary);
if (!valid) {
new WebAssembly.Module(binary);
throw new Error("module failed WebAssembly.validate but compiled successfully");
}
assertEq(valid, true, "wasm module was invalid");
}
function wasmFailValidateBinary(binary, pattern) {
assertEq(WebAssembly.validate(binary), false, "module passed WebAssembly.validate when it should not have");
assertErrorMessage(() => new WebAssembly.Module(binary), WebAssembly.CompileError, pattern, "module failed WebAssembly.validate but did not fail to compile as expected");
}
function wasmValidateText(str) {
return wasmValidateBinary(wasmTextToBinary(str));
}
function wasmFailValidateText(str, pattern) {
return wasmFailValidateBinary(wasmTextToBinary(str), pattern);
}
// Expected compilation failure can happen in a couple of ways:
//
// - The compiler can be available but not capable of recognizing some opcodes:
// Compilation will start, but will fail with a CompileError. This is what
// happens without --wasm-gc if opcodes enabled by --wasm-gc are used.
//
// - The compiler can be unavailable: Compilation will not start at all but will
// throw an Error. This is what happens with "--wasm-gc --wasm-compiler=X" if
// X does not support the features enabled by --wasm-gc.
function wasmCompilationShouldFail(bin, compile_error_regex) {
try {
new WebAssembly.Module(bin);
} catch (e) {
if (e instanceof WebAssembly.CompileError) {
assertEq(compile_error_regex.test(e), true);
} else if (e instanceof Error) {
assertEq(/can't use wasm debug\/gc without baseline/.test(e), true);
} else {
throw new Error("Unexpected exception value:\n" + e);
}
}
}
function mismatchError(actual, expect) {
var str = `(type mismatch: expression has type ${actual} but expected ${expect})|` +
`(type mismatch: expected ${expect}, found ${actual}\)`;
return RegExp(str);
}
const emptyStackError = /(from empty stack)|(nothing on stack)/;
const unusedValuesError = /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/;
function jsify(wasmVal) {
if (wasmVal === 'nan')
return NaN;
if (wasmVal === 'inf')
return Infinity;
if (wasmVal === '-inf')
return Infinity;
if (wasmVal === '-0')
return -0;
return wasmVal;
}
function _augmentSrc(src, assertions) {
let i = 0;
let newSrc = src.substr(0, src.lastIndexOf(')'));
for (let { func, args, expected, type } of assertions) {
newSrc += `
(func (export "assert_${i++}") (result i32)
${ args ? args.join('\n') : '' }
call ${func}`;
if (typeof expected !== 'undefined') {
switch (type) {
case 'f32':
newSrc += `
i32.reinterpret_f32
${(function () {
if (expected == 'nan:arithmetic') {
expected = '0x7FC00000';
return '(i32.const 0x7FC00000) i32.and';
}
return '';
})()}
i32.const ${expected}
i32.eq`;
break;
case 'f64':
newSrc += `
i64.reinterpret_f64
${(function () {
if (expected == 'nan:arithmetic') {
expected = '0x7FF8000000000000';
return '(i64.const 0x7FF8000000000000) i64.and';
}
return '';
})()}
i64.const ${expected}
i64.eq`;
break;
case 'i32':
newSrc += `
i32.const ${expected}
i32.eq`;
break;
case 'i64':
newSrc += `
i64.const ${expected}
i64.eq`;
break;
case 'v128':
newSrc += `
v128.const ${expected}
i8x16.eq
i8x16.all_true`;
break;
default:
throw new Error("unexpected usage of wasmAssert");
}
} else {
// Always true when there's no expected return value.
newSrc += "\ni32.const 1";
}
newSrc += ')\n';
}
newSrc += ')';
return newSrc;
}
function wasmAssert(src, assertions, maybeImports = {}, exportBox = null) {
let { exports } = wasmEvalText(_augmentSrc(src, assertions), maybeImports);
if (exportBox !== null)
exportBox.exports = exports;
for (let i = 0; i < assertions.length; i++) {
let { func, expected, params } = assertions[i];
let paramText = params ? params.join(', ') : '';
assertEq(exports[`assert_${i}`](), 1,
`Unexpected value when running ${func}(${paramText}), expecting ${expected}.`);
}
}
// Fully test a module:
// - ensure it validates.
// - ensure it compiles and produces the expected result.
// - ensure textToBinary(binaryToText(binary)) = binary
// Preconditions:
// - the binary module must export a function called "run".
function wasmFullPass(text, expected, maybeImports, ...args) {
let binary = wasmTextToBinary(text);
assertEq(WebAssembly.validate(binary), true, "Must validate.");
let module = new WebAssembly.Module(binary);
let instance = new WebAssembly.Instance(module, maybeImports);
assertEq(typeof instance.exports.run, 'function', "A 'run' function must be exported.");
assertEq(instance.exports.run(...args), expected, "Initial module must return the expected result.");
}
// Ditto, but expects a function named '$run' instead of exported with this name.
function wasmFullPassI64(text, expected, maybeImports, ...args) {
let binary = wasmTextToBinary(text);
assertEq(WebAssembly.validate(binary), true, "Must validate.");
let augmentedSrc = _augmentSrc(text, [ { type: 'i64', func: '$run', args, expected } ]);
let augmentedBinary = wasmTextToBinary(augmentedSrc);
let module = new WebAssembly.Module(augmentedBinary);
let instance = new WebAssembly.Instance(module, maybeImports);
assertEq(instance.exports.assert_0(), 1);
}
function wasmRunWithDebugger(wast, lib, init, done) {
let g = newGlobal({newCompartment: true});
let dbg = new Debugger(g);
g.eval(`
var wasm = wasmTextToBinary(\`${wast}\`);
var lib = ${lib || 'undefined'};
var m = new WebAssembly.Instance(new WebAssembly.Module(wasm), lib);`);
var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
init({dbg, wasmScript, g,});
let result = undefined, error = undefined;
try {
result = g.eval("m.exports.test()");
} catch (ex) {
error = ex;
}
done({dbg, result, error, wasmScript, g,});
}
const WasmHelpers = {};
(function() {
let enabled = false;
try {
enableSingleStepProfiling();
disableSingleStepProfiling();
enabled = true;
} catch (e) {}
WasmHelpers.isSingleStepProfilingEnabled = enabled;
})();
// The cache of matched and unmatched strings seriously speeds up matching on
// the emulators and makes tests time out less often.
var matched = {};
var unmatched = {};
WasmHelpers._normalizeStack = (stack, preciseStacks) => {
var wasmFrameTypes = [
{re:/^jit call to int64(?: or v128)? wasm function$/, sub:"i64>"},
{re:/^out-of-line coercion for jit entry arguments \(in wasm\)$/, sub:"ool>"},
{re:/^wasm-function\[(\d+)\] \(.*\)$/, sub:"$1"},
{re:/^(fast|slow) exit trampoline (?:to native )?\(in wasm\)$/, sub:"<"},
{re:/^call to(?: asm.js)? native (.*) \(in wasm\)$/, sub:"$1"},
{re:/ \(in wasm\)$/, sub:""}
];
let entryRegexps;
if (preciseStacks) {
entryRegexps = [
{re:/^slow entry trampoline \(in wasm\)$/, sub:"!>"},
{re:/^fast entry trampoline \(in wasm\)$/, sub:">"},
];
} else {
entryRegexps = [
{re:/^(fast|slow) entry trampoline \(in wasm\)$/, sub:">"}
];
}
wasmFrameTypes = entryRegexps.concat(wasmFrameTypes);
var framesIn = stack.split(',');
var framesOut = [];
outer:
for (let frame of framesIn) {
if (unmatched[frame])
continue;
let probe = matched[frame];
if (probe !== undefined) {
framesOut.push(probe);
continue;
}
for (let {re, sub} of wasmFrameTypes) {
if (re.test(frame)) {
let repr = frame.replace(re, sub);
framesOut.push(repr);
matched[frame] = repr;
continue outer;
}
}
unmatched[frame] = true;
}
return framesOut.join(',');
};
WasmHelpers._removeAdjacentDuplicates = array => {
if (array.length < 2)
return;
let i = 0;
for (let j = 1; j < array.length; j++) {
if (array[i] !== array[j])
array[++i] = array[j];
}
array.length = i + 1;
}
WasmHelpers.normalizeStacks = (stacks, preciseStacks = false) => {
let observed = [];
for (let i = 0; i < stacks.length; i++)
observed[i] = WasmHelpers._normalizeStack(stacks[i], preciseStacks);
WasmHelpers._removeAdjacentDuplicates(observed);
return observed;
};
WasmHelpers._compareStacks = (got, expect) => {
if (got.length != expect.length) {
return false;
}
for (let i = 0; i < got.length; i++) {
if (got[i] !== expect[i])
return false;
}
return true;
}
WasmHelpers.assertEqImpreciseStacks = (got, expect) => {
let observed = WasmHelpers.normalizeStacks(got, /* precise */ false);
let same = WasmHelpers._compareStacks(observed, expect);
if (!same) {
if (observed.length != expect.length) {
print(`Got:\n${observed.toSource()}\nExpect:\n${expect.toSource()}`);
assertEq(observed.length, expect.length);
}
for (let i = 0; i < observed.length; i++) {
if (observed[i] !== expect[i]) {
print(`On stack ${i}, Got:\n${observed[i]}\nExpect:\n${expect[i]}`);
assertEq(observed[i], expect[i]);
}
}
}
}
WasmHelpers.extractStackFrameFunction = (frameString) => {
var [_, name, filename, line, column] = frameString.match(/^(.*)@(.*):(.*):(.*)$/);
if (name)
return name;
if (/wasm-function/.test(line))
return line;
return "";
};
WasmHelpers.assertStackTrace = (exception, expected) => {
let callsites = exception.stack.trim().split('\n').map(WasmHelpers.extractStackFrameFunction);
assertEq(callsites.length, expected.length);
for (let i = 0; i < callsites.length; i++) {
assertEq(callsites[i], expected[i]);
}
};
WasmHelpers.nextLineNumber = (n=1) => {
return +(new Error().stack).split('\n')[1].split(':')[1] + n;
}
WasmHelpers.startProfiling = () => {
if (!WasmHelpers.isSingleStepProfilingEnabled)
return;
enableSingleStepProfiling();
}
WasmHelpers.endProfiling = () => {
if (!WasmHelpers.isSingleStepProfilingEnabled)
return;
return disableSingleStepProfiling();
}
WasmHelpers.assertEqPreciseStacks = (observed, expectedStacks) => {
if (!WasmHelpers.isSingleStepProfilingEnabled)
return null;
observed = WasmHelpers.normalizeStacks(observed, /* precise */ true);
for (let i = 0; i < expectedStacks.length; i++) {
if (WasmHelpers._compareStacks(observed, expectedStacks[i]))
return i;
}
throw new Error(`no plausible stacks found, observed: ${observed.join('/')}
Expected one of:
${expectedStacks.map(stacks => stacks.join("/")).join('\n')}`);
}
function fuzzingSafe() {
return typeof getErrorNotes == 'undefined';
}
// Common instantiations of wasm values for dynamic type check testing
// Valid values for funcref
let WasmFuncrefValues = [
wasmEvalText(`(module (func (export "")))`).exports[''],
];
// Valid values for structref/arrayref
let WasmStructrefValues = [];
let WasmArrayrefValues = [];
if (wasmGcEnabled()) {
let { newStruct, newArray } = wasmEvalText(`
(module
(type $s (sub (struct)))
(type $a (sub (array i32)))
(func (export "newStruct") (result anyref)
struct.new $s)
(func (export "newArray") (result anyref)
i32.const 0
i32.const 0
array.new $a)
)`).exports;
WasmStructrefValues.push(newStruct());
WasmArrayrefValues.push(newArray());
}
let WasmGcObjectValues = WasmStructrefValues.concat(WasmArrayrefValues);
// Valid values for eqref
let WasmEqrefValues = [...WasmStructrefValues, ...WasmArrayrefValues];
// Valid values for i31ref
let MinI31refValue = -1 * Math.pow(2, 30);
let MaxI31refValue = Math.pow(2, 30) - 1;
let WasmI31refValues = [
// first four 31-bit signed numbers
MinI31refValue,
MinI31refValue + 1,
MinI31refValue + 2,
MinI31refValue + 3,
// five numbers around zero
-2,
-1,
0,
1,
2,
// last four 31-bit signed numbers
MaxI31refValue - 3,
MaxI31refValue - 2,
MaxI31refValue - 1,
MaxI31refValue,
];
// Valid and invalid values for anyref
let WasmAnyrefValues = [...WasmEqrefValues, ...WasmI31refValues];
let WasmNonAnyrefValues = [
undefined,
true,
false,
{x:1337},
["abracadabra"],
13.37,
-0,
0x7fffffff + 0.1,
-0x7fffffff - 0.1,
0x80000000 + 0.1,
-0x80000000 - 0.1,
0xffffffff + 0.1,
-0xffffffff - 0.1,
Number.EPSILON,
Number.MAX_SAFE_INTEGER,
Number.MIN_SAFE_INTEGER,
Number.MIN_VALUE,
Number.MAX_VALUE,
Number.NaN,
"hi",
37n,
new Number(42),
new Boolean(true),
Symbol("status"),
() => 1337,
...WasmFuncrefValues,
];
// Valid externref values
let WasmNonNullExternrefValues = [
...WasmNonAnyrefValues,
...WasmAnyrefValues
];
let WasmExternrefValues = [null, ...WasmNonNullExternrefValues];
// Common array utilities
// iota(n,k) creates an Array of length n with values k..k+n-1
function iota(len, k=0) {
let xs = [];
for ( let i=0 ; i < len ; i++ )
xs.push(i+k);
return xs;
}
// cross(A) where A is an array of length n creates an Array length n*n of
// two-element Arrays representing all pairs of elements of A.
function cross(xs) {
let results = [];
for ( let x of xs )
for ( let y of xs )
results.push([x,y]);
return results;
}
// Remove all values equal to v from an array xs, comparing equal for NaN.
function remove(v, xs) {
let result = [];
for ( let w of xs ) {
if (v === w || isNaN(v) && isNaN(w))
continue;
result.push(w);
}
return result;
}
// permute(A) where A is an Array returns an Array of Arrays, each inner Array a
// distinct permutation of the elements of A. A is assumed not to have any
// elements that are pairwise equal in the sense of remove().
function permute(xs) {
if (xs.length == 1)
return [xs];
let results = [];
for (let v of xs)
for (let tail of permute(remove(v, xs)))
results.push([v, ...tail]);
return results;
}
// interleave([a,b,c,...],[0,1,2,...]) => [a,0,b,1,c,2,...]
function interleave(xs, ys) {
assertEq(xs.length, ys.length);
let res = [];
for ( let i=0 ; i < xs.length; i++ ) {
res.push(xs[i]);
res.push(ys[i]);
}
return res;
}
// assertSame([a,...],[b,...]) asserts that the two arrays have the same length
// and that they element-wise assertEq IGNORING Number/BigInt differences. This
// predicate is in this file because it is wasm-specific.
function assertSame(got, expected) {
assertEq(got.length, expected.length);
for ( let i=0; i < got.length; i++ ) {
let g = got[i];
let e = expected[i];
if (typeof g != typeof e) {
if (typeof g == "bigint")
e = BigInt(e);
else if (typeof e == "bigint")
g = BigInt(g);
}
assertEq(g, e);
}
}
// assertEqResults([a,...],[b,...]) asserts that the two results from a wasm
// call are the same. This will compare deeply inside the result array, and
// relax a mismatch around single element arrays.
//
// This predicate is in this file because it is wasm-specific.
function assertEqResults(got, expected) {
if (!Array.isArray(got)) {
got = [got];
}
if (!Array.isArray(expected)) {
expected = [expected];
}
assertSame(got, expected);
}
// TailCallIterations is selected to be large enough to trigger
// "too much recursion", but not to be slow.
var TailCallIterations = getBuildConfiguration("simulator") ? 1000 : 100000;
// TailCallBallast is selected to spill registers as parameters.
var TailCallBallast = 30;