summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/gc/call-indirect-subtyping.js
blob: d83f2ed6249705511a52aed0915b0edb7e58e1ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// |jit-test| test-also=--setpref=wasm_tail_calls=true; skip-if: !wasmGcEnabled()

// Test that call_indirect will respect subtyping by defining a bunch of types
// and checking every combination of (expected, actual) type.
//
// NOTE: Several of these types are identical to each other to test
// canonicalization as well, and this causes some bloat in the 'subtypeOf'
// lists.
const TESTS = [
	{
		// (type 0) (equivalent to 1)
		type: `(type (sub (func)))`,
		subtypeOf: [0, 1],
	},
	{
		// (type 1) (equivalent to 0)
		type: `(rec (type (sub (func))))`,
		subtypeOf: [0, 1],
	},
	{
		// (type 2)
		type: `(rec (type (sub (func))) (type (sub (func))))`,
		subtypeOf: [2],
	},
	{
		// (type 3)
		// Hack entry of previous to capture that it actually defines
		// two types in the recursion group.
		type: undefined,
		subtypeOf: [3],
	},
	{
		// (type 4) (equivalent to 7)
		type: `(type (sub 0 (func)))`,
		subtypeOf: [0, 1, 4, 7],
	},
	{
		// (type 5) (equivalent to 8)
		type: `(type (sub 4 (func)))`,
		subtypeOf: [0, 1, 4, 5, 7, 8],
	},
	{
		// (type 6)
		type: `(type (sub 5 (func)))`,
		subtypeOf: [0, 1, 4, 5, 6, 7, 8],
	},
	{
		// (type 7) (equivalent to 4)
		type: `(type (sub 0 (func)))`,
		subtypeOf: [0, 1, 4, 7],
	},
	{
		// (type 8) (equivalent to 5)
		type: `(type (sub 7 (func)))`,
		subtypeOf: [0, 1, 4, 5, 7, 8],
	},
	{
		// (type 9) - a final type that has an immediate form
		type: `(type (func))`,
		subtypeOf: [9],
	}
];

// Build a module with all the types, functions with those types, and functions
// that call_indirect with those types, and a table with all the functions in
// it.
let typeSection = '';
let importedFuncs = '';
let definedFuncs = '';
let callIndirectFuncs = '';
let returnCallIndirectFuncs = '';
let i = 0;
for (let {type} of TESTS) {
	if (type) {
		typeSection += type + '\n';
	}
	importedFuncs += `(func \$import${i} (import "" "import${i}") (type ${i}))\n`;
	definedFuncs += `(func \$define${i} (export "define${i}") (type ${i}))\n`;
	callIndirectFuncs += `(func (export "call_indirect ${i}") (param i32)
		(drop (ref.cast (ref ${i}) (table.get local.get 0)))
		(call_indirect (type ${i}) local.get 0)
	)\n`;
	if (wasmTailCallsEnabled()) {
		returnCallIndirectFuncs += `(func (export "return_call_indirect ${i}") (param i32)
			(drop (ref.cast (ref ${i}) (table.get local.get 0)))
			(return_call_indirect (type ${i}) local.get 0)
		)\n`;
	}
	i++;
}
let moduleText = `(module
	${typeSection}
	${importedFuncs}
	${definedFuncs}
	${callIndirectFuncs}
	${returnCallIndirectFuncs}
	(table
		(export "table")
		funcref
		(elem ${TESTS.map((x, i) => `\$import${i} \$define${i}`).join(" ")})
	)
)`;

// Now go over every combination of (actual, expected). In this case the caller
// (which does the call_indirect) specifies expected and the callee will be the
// actual.
let imports = {
	"": Object.fromEntries(TESTS.map((x, i) => [`import${i}`, () => {}])),
};
let exports = wasmEvalText(moduleText, imports).exports;
for (let callerTypeIndex = 0; callerTypeIndex < TESTS.length; callerTypeIndex++) {
	for (let calleeTypeIndex = 0; calleeTypeIndex < TESTS.length; calleeTypeIndex++) {
		let calleeType = TESTS[calleeTypeIndex];

		// If the callee (actual) is a subtype of caller (expected), then this
		// should succeed.
		let shouldPass = calleeType.subtypeOf.includes(callerTypeIndex);

		let calleeImportFuncIndex = calleeTypeIndex * 2;
		let calleeDefinedFuncIndex = calleeTypeIndex * 2 + 1;

		// print(`expected (type ${callerTypeIndex}) (actual ${calleeTypeIndex})`);
		let test = () => {
			exports[`call_indirect ${callerTypeIndex}`](calleeImportFuncIndex)
			exports[`call_indirect ${callerTypeIndex}`](calleeDefinedFuncIndex)
			if (wasmTailCallsEnabled()) {
				exports[`return_call_indirect ${callerTypeIndex}`](calleeImportFuncIndex)
				exports[`return_call_indirect ${callerTypeIndex}`](calleeDefinedFuncIndex)
			}
		};
		if (shouldPass) {
			test();
		} else {
			assertErrorMessage(test, WebAssembly.RuntimeError, /mismatch|cast/);
		}
	}
}