// Copyright 2023 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Flags: --wasm-to-js-generic-wrapper --expose-gc d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); const debug = false; // Use consecutive values as parameters for easier debugging. const easyValues = false; // This test tests the wasm-to-js wrapper with random signatures and random // values. The test creates a WebAssembly function with the random signature. // The WebAssembly function passes the incoming parameters directly to an // imported JavaScript function. The JavaScript function checks that the // incoming parameters match the original values passed to the WebAssembly // function. The imported JavaScript function then returns random return values, // which the WebAssembly function again forwards to the original caller. There // the return values of the WebAssembly function are compared with the original // random return values. `RunTest` is the core function, which runs the test // case for a specific signature and value mapping. `RunTest` can be used well // for debugging a specific signature. `GenerateAndRunTest` additionally // generates the inputs for `RunTest`, which is good for fuzzing the js-to-wasm // wrappers. // Some documentation if the test fails in the CQ. console.log("This test is a fuzzer, it tests the generic wasm-to-js wrapper"); console.log("with random signatures. If this test fails, then it may not fail"); console.log("for the CL that actually introduced the issue, but for a"); console.log("later CL in the CQ. You may want to use flako to identify that"); console.log("actual culprit."); const kPossibleTypes = [kWasmI32, kWasmI64, kWasmF32, kWasmF64, kWasmExternRef]; function getRandomInt(max) { return Math.floor(Math.random() * max); } function TypeString(type) { switch (type) { case kWasmI32: return 'kWasmI32'; case kWasmI64: return 'kWasmI64'; case kWasmF32: return 'kWasmF32'; case kWasmF64: return 'kWasmF64'; case kWasmExternRef: return 'kWasmExternRef'; } } // A set of interesting values that will be used as parameters. let interestingParams = {}; interestingParams[kWasmI32] = [ -1500000000, -0x4000_0000, -0x3fff_ffff, -1, 0, 1, 2, 0x3fff_ffff, 0x4000_0000, 1500000000, 0x7fff_ffff, {valueOf: () => 15}, { toString: () => { gc(); return '17'; } } ]; interestingParams[kWasmI64] = [ -0x7fff_ffff_ffff_ffffn, -0xffff_ffffn, -0x8000_0000n, -1500000000n, -0x4000_0000n, -0x3fff_ffffn, -1n, 0n, 1n, 2n, 0x3fff_ffffn, 0x4000_0000n, 1500000000n, 0x7fff_ffffn, 0x8000_0000n, 0xffff_ffffn, 0x7fff_ffff_ffff_ffffn, {valueOf: () => 15n}, { toString: () => { gc(); return '17'; } } ]; interestingParams[kWasmF32] = [ -Infinity, -(2 ** 65), -(2 ** 64), -(2 ** 63), -(2 ** 62), -(2 ** 29), -(2 ** 33), -(2 ** 32), -(2 ** 31), -(2 ** 30), -(2 ** 29), -1.5, -1, 0, -0, 1, 1.5, 2, 2 ** 29, 2 ** 30, 2 ** 31, 2 ** 32, 2 ** 33, 2 ** 62, 2 ** 63, 2 ** 64, 2 ** 65, {valueOf: () => 16}, { toString: () => { gc(); return '18'; } }, Infinity, NaN ]; interestingParams[kWasmF64] = [ -Infinity, -(2 ** 65), -(2 ** 64), -(2 ** 63), -(2 ** 62), -(2 ** 29), -(2 ** 33), -(2 ** 32), -(2 ** 31), -(2 ** 30), -(2 ** 29), -1.5, -1, 0, -0, 1, 1.5, 2, // 28 set bits, too many for float32. 0xfffffff, 2 ** 29, 2 ** 30, 2 ** 31, 2 ** 32, 2 ** 33, 2 ** 62, 2 ** 63, 2 ** 64, 2 ** 65, {valueOf: () => 14}, { toString: () => { gc(); return '19'; } }, Infinity, NaN ]; interestingParams[kWasmExternRef] = [ 17, 12.5, 17n, 0x7fff_ffff, {foo: 18}, [1, 2, 3], 'bar', () => 38, null, undefined ]; function GenerateValueArray(params) { let result = []; let nextValue = 1; for (let param of params) { if (easyValues) { switch (param) { case kWasmI32: case kWasmF32: case kWasmF64: result.push(nextValue++); break; case kWasmI64: result.push(BigInt(nextValue++)); break; case kWasmExternRef: const val = nextValue++; result.push({ val: val }); } } else { result.push(interestingParams[param][getRandomInt( interestingParams[param].length)]); } } return result; } function assertValueArray(original, transformed) { assertEquals(transformed.length, original.length); for (let i = 0; i < transformed.length; ++i) { const arg = transformed[i]; if (typeof arg === 'bigint') { // For values of type I64. assertEquals(BigInt(original[i]), arg); } else if (typeof arg === 'number') { // For values of type I32, F32, and F64. assertEquals(Number(original[i]), arg); } else { // For values of type externref. assertEquals(original[i], arg); } } } function RunTest(params, returns) { if (debug) { let testcase = 'RunTest(['; for (let i = 0; i < params.length; ++i) { testcase += (i == 0 ? '' : ', ') + TypeString(params[i]); } testcase += '], ['; if (returns.length > 0) { testcase += TypeString(returns[0]); for (let i = 1; i < returns.length; ++i) { testcase += ', ' + TypeString(returns[i]); } } testcase += ']);'; console.log(testcase); } const builder = new WasmModuleBuilder(); const impSig = makeSig(params, returns); const impIndex = builder.addImport('m', 'f', impSig); const paramValues = GenerateValueArray(params); const returnValues = GenerateValueArray(returns); let body = []; for (let i = 0; i < params.length; ++i) { body.push(kExprLocalGet, i); } body.push(kExprCallFunction, impIndex); function impFunction() { assertValueArray(paramValues, arguments); if (returns.length === 0) return; if (returns.length === 1) return returnValues[0]; return returnValues; }; const sig = makeSig(params, returns); builder.addFunction('main', sig).addBody(body).exportFunc(); const instance = builder.instantiate({ m: { f: impFunction } }); let result = instance.exports.main(...paramValues); if (returns.length === 0) return; if (returns.length === 1) result = [result]; assertValueArray(returnValues, result); } function GenerateAndRunTest() { // Generate signature. const kMaxParams = 20; const kMaxReturns = 10; const numParams = getRandomInt(kMaxParams) + 1; const numReturns = getRandomInt(kMaxReturns + 1); // The array of parameter types. const params = []; // The array of return types. const returns = []; for (let i = 0; i < numParams; ++i) { params.push(kPossibleTypes[getRandomInt(kPossibleTypes.length)]); } for (let i = 0; i < numReturns; ++i) { returns.push(kPossibleTypes[getRandomInt(kPossibleTypes.length)]); } RunTest(params, returns); } // Regression tests: RunTest( [ kWasmF32, kWasmF64, kWasmF64, kWasmF32, kWasmI32, kWasmI64, kWasmF32, kWasmF64, kWasmF64, kWasmF64, kWasmF32, kWasmF32 ], [kWasmI64, kWasmI64, kWasmI32, kWasmF32]); for (let i = 0; i < (debug ? 200 : 2); ++i) { GenerateAndRunTest(); }