| // 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 |
| ); |
| } |
| } |
| }); |