383 lines
9.5 KiB
JavaScript
383 lines
9.5 KiB
JavaScript
// META: global=window,dedicatedworker,jsshell
|
|
// META: script=/wasm/jsapi/assertions.js
|
|
// META: script=/wasm/jsapi/wasm-module-builder.js
|
|
// META: script=/wasm/jsapi/js-string/polyfill.js
|
|
|
|
// Generate two sets of exports, one from a polyfill implementation and another
|
|
// from the builtins provided by the host.
|
|
let polyfillExports;
|
|
let builtinExports;
|
|
setup(() => {
|
|
// Compile a module that exports a function for each builtin that will call
|
|
// it. We could just generate a module that re-exports the builtins, but that
|
|
// would not catch any special codegen that could happen when direct calling
|
|
// a known builtin function from wasm.
|
|
const builder = new WasmModuleBuilder();
|
|
const arrayIndex = builder.addArray(kWasmI16, true, kNoSuperType, true);
|
|
const builtins = [
|
|
{
|
|
name: "test",
|
|
params: [kWasmExternRef],
|
|
results: [kWasmI32],
|
|
},
|
|
{
|
|
name: "cast",
|
|
params: [kWasmExternRef],
|
|
results: [wasmRefType(kWasmExternRef)],
|
|
},
|
|
{
|
|
name: "fromCharCodeArray",
|
|
params: [wasmRefNullType(arrayIndex), kWasmI32, kWasmI32],
|
|
results: [wasmRefType(kWasmExternRef)],
|
|
},
|
|
{
|
|
name: "intoCharCodeArray",
|
|
params: [kWasmExternRef, wasmRefNullType(arrayIndex), kWasmI32],
|
|
results: [kWasmI32],
|
|
},
|
|
{
|
|
name: "fromCharCode",
|
|
params: [kWasmI32],
|
|
results: [wasmRefType(kWasmExternRef)],
|
|
},
|
|
{
|
|
name: "fromCodePoint",
|
|
params: [kWasmI32],
|
|
results: [wasmRefType(kWasmExternRef)],
|
|
},
|
|
{
|
|
name: "charCodeAt",
|
|
params: [kWasmExternRef, kWasmI32],
|
|
results: [kWasmI32],
|
|
},
|
|
{
|
|
name: "codePointAt",
|
|
params: [kWasmExternRef, kWasmI32],
|
|
results: [kWasmI32],
|
|
},
|
|
{
|
|
name: "length",
|
|
params: [kWasmExternRef],
|
|
results: [kWasmI32],
|
|
},
|
|
{
|
|
name: "concat",
|
|
params: [kWasmExternRef, kWasmExternRef],
|
|
results: [wasmRefType(kWasmExternRef)],
|
|
},
|
|
{
|
|
name: "substring",
|
|
params: [kWasmExternRef, kWasmI32, kWasmI32],
|
|
results: [wasmRefType(kWasmExternRef)],
|
|
},
|
|
{
|
|
name: "equals",
|
|
params: [kWasmExternRef, kWasmExternRef],
|
|
results: [kWasmI32],
|
|
},
|
|
{
|
|
name: "compare",
|
|
params: [kWasmExternRef, kWasmExternRef],
|
|
results: [kWasmI32],
|
|
},
|
|
];
|
|
|
|
// Add a function type for each builtin
|
|
for (let builtin of builtins) {
|
|
builtin.type = builder.addType({
|
|
params: builtin.params,
|
|
results: builtin.results
|
|
});
|
|
}
|
|
|
|
// Add an import for each builtin
|
|
for (let builtin of builtins) {
|
|
builtin.importFuncIndex = builder.addImport(
|
|
"wasm:js-string",
|
|
builtin.name,
|
|
builtin.type);
|
|
}
|
|
|
|
// Generate an exported function to call the builtin
|
|
for (let builtin of builtins) {
|
|
let func = builder.addFunction(builtin.name + "Imp", builtin.type);
|
|
func.addLocals(builtin.params.length);
|
|
let body = [];
|
|
for (let i = 0; i < builtin.params.length; i++) {
|
|
body.push(kExprLocalGet);
|
|
body.push(...wasmSignedLeb(i));
|
|
}
|
|
body.push(kExprCallFunction);
|
|
body.push(...wasmSignedLeb(builtin.importFuncIndex));
|
|
func.addBody(body);
|
|
func.exportAs(builtin.name);
|
|
}
|
|
|
|
const buffer = builder.toBuffer();
|
|
|
|
// Instantiate this module using the builtins from the host
|
|
const builtinModule = new WebAssembly.Module(buffer, {
|
|
builtins: ["js-string"]
|
|
});
|
|
const builtinInstance = new WebAssembly.Instance(builtinModule, {});
|
|
builtinExports = builtinInstance.exports;
|
|
|
|
// Instantiate this module using the polyfill module
|
|
const polyfillModule = new WebAssembly.Module(buffer);
|
|
const polyfillInstance = new WebAssembly.Instance(polyfillModule, {
|
|
"wasm:js-string": polyfillImports
|
|
});
|
|
polyfillExports = polyfillInstance.exports;
|
|
});
|
|
|
|
// A helper function to assert that the behavior of two functions are the
|
|
// same.
|
|
function assert_same_behavior(funcA, funcB, ...params) {
|
|
let resultA;
|
|
let errA = null;
|
|
try {
|
|
resultA = funcA(...params);
|
|
} catch (err) {
|
|
errA = err;
|
|
}
|
|
|
|
let resultB;
|
|
let errB = null;
|
|
try {
|
|
resultB = funcB(...params);
|
|
} catch (err) {
|
|
errB = err;
|
|
}
|
|
|
|
if (errA || errB) {
|
|
assert_equals(errA === null, errB === null, errA ? errA.message : errB.message);
|
|
assert_equals(Object.getPrototypeOf(errA), Object.getPrototypeOf(errB));
|
|
}
|
|
assert_equals(resultA, resultB);
|
|
|
|
if (errA) {
|
|
throw errA;
|
|
}
|
|
return resultA;
|
|
}
|
|
|
|
function assert_throws_if(func, shouldThrow, constructor) {
|
|
let error = null;
|
|
try {
|
|
func();
|
|
} catch (e) {
|
|
error = e;
|
|
}
|
|
assert_equals(error !== null, shouldThrow, "shouldThrow mismatch");
|
|
if (shouldThrow && error !== null) {
|
|
assert_true(error instanceof constructor);
|
|
}
|
|
}
|
|
|
|
// Constant values used in the tests below
|
|
const testStrings = [
|
|
"",
|
|
"a",
|
|
"1",
|
|
"ab",
|
|
"hello, world",
|
|
"\n",
|
|
"☺",
|
|
"☺☺",
|
|
String.fromCodePoint(0x10000, 0x10001)
|
|
];
|
|
const testCharCodes = [1, 2, 3, 10, 0x7f, 0xff, 0xfffe, 0xffff];
|
|
const testCodePoints = [1, 2, 3, 10, 0x7f, 0xff, 0xfffe, 0xffff, 0x10000, 0x10001];
|
|
const testExternRefValues = [
|
|
null,
|
|
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,
|
|
];
|
|
|
|
// Test that `test` and `cast` work on various JS values. Run all the
|
|
// other builtins and assert that they also perform equivalent type
|
|
// checks.
|
|
test(() => {
|
|
for (let a of testExternRefValues) {
|
|
let isString = assert_same_behavior(
|
|
builtinExports['test'],
|
|
polyfillExports['test'],
|
|
a
|
|
);
|
|
|
|
assert_throws_if(() => assert_same_behavior(
|
|
builtinExports['cast'],
|
|
polyfillExports['cast'],
|
|
a
|
|
), !isString, WebAssembly.RuntimeError);
|
|
|
|
let arrayMutI16 = helperExports.createArrayMutI16(10);
|
|
assert_throws_if(() => assert_same_behavior(
|
|
builtinExports['intoCharCodeArray'],
|
|
polyfillExports['intoCharCodeArray'],
|
|
a, arrayMutI16, 0
|
|
), !isString, WebAssembly.RuntimeError);
|
|
|
|
assert_throws_if(() => assert_same_behavior(
|
|
builtinExports['charCodeAt'],
|
|
polyfillExports['charCodeAt'],
|
|
a, 0
|
|
), !isString, WebAssembly.RuntimeError);
|
|
|
|
assert_throws_if(() => assert_same_behavior(
|
|
builtinExports['codePointAt'],
|
|
polyfillExports['codePointAt'],
|
|
a, 0
|
|
), !isString, WebAssembly.RuntimeError);
|
|
|
|
assert_throws_if(() => assert_same_behavior(
|
|
builtinExports['length'],
|
|
polyfillExports['length'],
|
|
a
|
|
), !isString, WebAssembly.RuntimeError);
|
|
|
|
assert_throws_if(() => assert_same_behavior(
|
|
builtinExports['concat'],
|
|
polyfillExports['concat'],
|
|
a, a
|
|
), !isString, WebAssembly.RuntimeError);
|
|
|
|
assert_throws_if(() => assert_same_behavior(
|
|
builtinExports['substring'],
|
|
polyfillExports['substring'],
|
|
a, 0, 0
|
|
), !isString, WebAssembly.RuntimeError);
|
|
|
|
assert_throws_if(() => assert_same_behavior(
|
|
builtinExports['equals'],
|
|
polyfillExports['equals'],
|
|
a, a
|
|
), a !== null && !isString, WebAssembly.RuntimeError);
|
|
|
|
assert_throws_if(() => assert_same_behavior(
|
|
builtinExports['compare'],
|
|
polyfillExports['compare'],
|
|
a, a
|
|
), !isString, WebAssembly.RuntimeError);
|
|
}
|
|
});
|
|
|
|
// Test that `fromCharCode` works on various char codes
|
|
test(() => {
|
|
for (let a of testCharCodes) {
|
|
assert_same_behavior(
|
|
builtinExports['fromCharCode'],
|
|
polyfillExports['fromCharCode'],
|
|
a
|
|
);
|
|
}
|
|
});
|
|
|
|
// Test that `fromCodePoint` works on various code points
|
|
test(() => {
|
|
for (let a of testCodePoints) {
|
|
assert_same_behavior(
|
|
builtinExports['fromCodePoint'],
|
|
polyfillExports['fromCodePoint'],
|
|
a
|
|
);
|
|
}
|
|
});
|
|
|
|
// Perform tests on various strings
|
|
test(() => {
|
|
for (let a of testStrings) {
|
|
let length = assert_same_behavior(
|
|
builtinExports['length'],
|
|
polyfillExports['length'],
|
|
a
|
|
);
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
let charCode = assert_same_behavior(
|
|
builtinExports['charCodeAt'],
|
|
polyfillExports['charCodeAt'],
|
|
a, i
|
|
);
|
|
}
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
let charCode = assert_same_behavior(
|
|
builtinExports['codePointAt'],
|
|
polyfillExports['codePointAt'],
|
|
a, i
|
|
);
|
|
}
|
|
|
|
let arrayMutI16 = helperExports.createArrayMutI16(length);
|
|
assert_same_behavior(
|
|
builtinExports['intoCharCodeArray'],
|
|
polyfillExports['intoCharCodeArray'],
|
|
a, arrayMutI16, 0
|
|
);
|
|
|
|
assert_same_behavior(
|
|
builtinExports['fromCharCodeArray'],
|
|
polyfillExports['fromCharCodeArray'],
|
|
arrayMutI16, 0, length
|
|
);
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
for (let j = 0; j < length; j++) {
|
|
assert_same_behavior(
|
|
builtinExports['substring'],
|
|
polyfillExports['substring'],
|
|
a, i, j
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Test various binary operations
|
|
test(() => {
|
|
for (let a of testStrings) {
|
|
for (let b of testStrings) {
|
|
assert_same_behavior(
|
|
builtinExports['concat'],
|
|
polyfillExports['concat'],
|
|
a, b
|
|
);
|
|
|
|
assert_same_behavior(
|
|
builtinExports['equals'],
|
|
polyfillExports['equals'],
|
|
a, b
|
|
);
|
|
|
|
assert_same_behavior(
|
|
builtinExports['compare'],
|
|
polyfillExports['compare'],
|
|
a, b
|
|
);
|
|
}
|
|
}
|
|
});
|