mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 17:51:35 +00:00

PR-URL: https://github.com/nodejs/node/pull/54077 Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Richard Lau <rlau@redhat.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
400 lines
9.9 KiB
JavaScript
400 lines
9.9 KiB
JavaScript
// 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: --expose-gc
|
|
//
|
|
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
|
|
|
|
// This test randomly generates WebAssembly signatures, creates WebAssembly
|
|
// functions with the generated signature which randomly maps parameters to
|
|
// returns, and then calls the WebAssembly function from JavaScript with some
|
|
// inputs and checks the 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 js-to-wasm 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 debug = false;
|
|
|
|
let kSig_r_i = makeSig([kWasmI32], [kWasmExternRef]);
|
|
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';
|
|
}
|
|
}
|
|
|
|
function ConvertFunctionName(to, from) {
|
|
return TypeString(from) + 'To' + TypeString(to);
|
|
}
|
|
|
|
// The ConversionFunction mimics the value conversion done within the wasm
|
|
// function, in addition to the `ToPrimitive` conversion done in the js-to-wasm
|
|
// wrapper.
|
|
function ConversionFunction(to, from, val) {
|
|
if (to == from) {
|
|
// If {to} and {from} are the same type, then we only have to do the
|
|
// conversion done in the js-to-wasm wrapper.
|
|
if (to == kWasmI64) return BigInt.asIntN(64, val);
|
|
if (to == kWasmExternRef) return val;
|
|
|
|
// In all other cases convert {val} into a Number so that the `valueOf`
|
|
// function of an object gets called.
|
|
return Number(val);
|
|
}
|
|
if (from == kWasmExternRef) {
|
|
if (val == null) {
|
|
val = 0;
|
|
} else {
|
|
val = val.value;
|
|
}
|
|
}
|
|
if (from == kWasmI64) {
|
|
// Convert BigInt to BigInt64.
|
|
val = BigInt.asIntN(64, val);
|
|
}
|
|
switch (to) {
|
|
case kWasmI32:
|
|
if (from == kWasmI64) return Number(BigInt.asIntN(32, val));
|
|
// Imitate the saturating conversions in WebAssembly.
|
|
if (val > 0x7fff_ffff) return 0x7fff_ffff;
|
|
if (val < -0x8000_0000) return -0x8000_0000;
|
|
return val | 0;
|
|
case kWasmI64:
|
|
// Imitate the saturating conversions in WebAssembly.
|
|
// Compare with 0x8000..., because 0x7fff... is not representable as
|
|
// float64.
|
|
if (val >= 0x8000_0000_0000_0000) return 0x7fff_ffff_ffff_ffffn;
|
|
if (val < -0x8000_0000_0000_0000) return -0x8000_0000_0000_0000n;
|
|
return BigInt(Math.trunc(val));
|
|
case kWasmF32:
|
|
return Math.fround(Number(val));
|
|
case kWasmF64:
|
|
return Number(val);
|
|
case kWasmExternRef:
|
|
return itoo(ConversionFunction(kWasmI32, from, val));
|
|
}
|
|
}
|
|
|
|
function ConvertOpcode(to, from, itooIndex, otoiIndex) {
|
|
if (from == kWasmExternRef) {
|
|
if (to == kWasmExternRef) {
|
|
return [kExprNop];
|
|
}
|
|
return [
|
|
kExprCallFunction, otoiIndex,
|
|
...ConvertOpcode(to, kWasmI32, itooIndex, otoiIndex)
|
|
];
|
|
}
|
|
switch (to) {
|
|
case kWasmI32:
|
|
switch (from) {
|
|
case kWasmI32:
|
|
return [kExprNop];
|
|
case kWasmI64:
|
|
return [kExprI32ConvertI64];
|
|
case kWasmF32:
|
|
return [kNumericPrefix, kExprI32SConvertSatF32];
|
|
case kWasmF64:
|
|
return [kNumericPrefix, kExprI32SConvertSatF64];
|
|
}
|
|
case kWasmI64:
|
|
switch (from) {
|
|
case kWasmI32:
|
|
return [kExprI64SConvertI32];
|
|
case kWasmI64:
|
|
return [kExprNop];
|
|
case kWasmF32:
|
|
return [kNumericPrefix, kExprI64SConvertSatF32];
|
|
case kWasmF64:
|
|
return [kNumericPrefix, kExprI64SConvertSatF64];
|
|
}
|
|
case kWasmF32:
|
|
switch (from) {
|
|
case kWasmI32:
|
|
return [kExprF32SConvertI32];
|
|
case kWasmI64:
|
|
return [kExprF32SConvertI64];
|
|
case kWasmF32:
|
|
return [kExprNop];
|
|
case kWasmF64:
|
|
return [kExprF32ConvertF64];
|
|
}
|
|
case kWasmF64:
|
|
switch (from) {
|
|
case kWasmI32:
|
|
return [kExprF64SConvertI32];
|
|
case kWasmI64:
|
|
return [kExprF64SConvertI64];
|
|
case kWasmF32:
|
|
return [kExprF64ConvertF32];
|
|
case kWasmF64:
|
|
return [kExprNop];
|
|
}
|
|
case kWasmExternRef:
|
|
return [
|
|
...ConvertOpcode(kWasmI32, from, itooIndex, otoiIndex),
|
|
kExprCallFunction, itooIndex
|
|
];
|
|
}
|
|
}
|
|
|
|
function itoo(val) {
|
|
return {value: val};
|
|
}
|
|
|
|
function otoi(val) {
|
|
return val.value;
|
|
}
|
|
|
|
// A set of interesting values that will be used as parameters.
|
|
let interestingParams = {};
|
|
interestingParams[kWasmI32] = [
|
|
-0x8000_0000, -1500000000, -0x4000_0000, -0x3fff_ffff, -1, 0, 1, 2,
|
|
0x3fff_ffff, 0x4000_0000, 1500000000, 0x7fff_ffff, {valueOf: () => 15}, {
|
|
toString: () => {
|
|
gc();
|
|
return '17';
|
|
}
|
|
}
|
|
];
|
|
|
|
interestingParams[kWasmI64] = [
|
|
-0xffffffffffffffffn,
|
|
-0x8000_0000_0000_0000n,
|
|
-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,
|
|
0x8000_0000_0000_0000n,
|
|
0xffff_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
|
|
];
|
|
|
|
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
|
|
];
|
|
|
|
function GetParam(type) {
|
|
if (type == kWasmExternRef) {
|
|
return itoo(interestingParams[kWasmI32][getRandomInt(
|
|
interestingParams[kWasmI32].length)]);
|
|
}
|
|
return interestingParams[type][getRandomInt(interestingParams[type].length)];
|
|
}
|
|
|
|
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 = [];
|
|
// This array stores which parameters map to which returns. {map} has the same
|
|
// length as {returns}. `map[i] == j` means that parameter {j} gets returned
|
|
// by return {i}.
|
|
const map = [];
|
|
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)]);
|
|
}
|
|
for (let i = 0; i < numReturns; ++i) {
|
|
map.push(getRandomInt(numParams));
|
|
}
|
|
RunTest(params, returns, map);
|
|
}
|
|
|
|
function RunTest(params, returns, map) {
|
|
if (debug) {
|
|
for (let i = 0; i < map.length; ++i) {
|
|
map[i] = map[i] % params.length;
|
|
}
|
|
while (map.length < returns.length) {
|
|
map.push(0);
|
|
}
|
|
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 += '], [' + map + ']);';
|
|
print(testcase);
|
|
}
|
|
const builder = new WasmModuleBuilder();
|
|
|
|
const itooIndex = builder.addImport('imp', 'itoo', kSig_r_i);
|
|
const otoiIndex = builder.addImport('imp', 'otoi', kSig_i_r);
|
|
|
|
const sig = makeSig(params, returns);
|
|
// Generate module
|
|
const body = [];
|
|
for (let i = 0; i < returns.length; ++i) {
|
|
body.push(kExprLocalGet);
|
|
body.push(map[i]);
|
|
|
|
body.push(
|
|
...ConvertOpcode(returns[i], params[map[i]], itooIndex, otoiIndex));
|
|
}
|
|
builder.addFunction('main', sig).addBody(body).exportFunc();
|
|
|
|
const instance = builder.instantiate({imp: {itoo: itoo, otoi: otoi}});
|
|
|
|
// Generate call.
|
|
const args = [];
|
|
for (let i = 0; i < params.length; ++i) {
|
|
args.push(GetParam(params[i]));
|
|
}
|
|
let result = instance.exports.main(...args);
|
|
if (returns.length == 0) {
|
|
assertEquals(result, undefined);
|
|
return;
|
|
}
|
|
if (returns.length == 1) {
|
|
// Turn result into an array, so that the code below can be used.
|
|
result = [result];
|
|
}
|
|
|
|
// Check result
|
|
for (let i = 0; i < returns.length; ++i) {
|
|
let details = undefined;
|
|
if (debug) {
|
|
details = `${i}, converting ${args[map[i]]} \
|
|
from ${TypeString(params[map[i]])} \
|
|
to ${TypeString(returns[i])}`;
|
|
}
|
|
assertEquals(
|
|
ConversionFunction(returns[i], params[map[i]], args[map[i]]),
|
|
result[i], details);
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < (debug ? 200 : 2); ++i) {
|
|
GenerateAndRunTest();
|
|
}
|