summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/exceptions/calls.js
blob: f12e5d1dbe7b1476d0ce2e37a941bbc93459ea49 (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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
// -----------------------------------------------------------------------------
// This file contains tests checking Wasm functions with throwing functions and
// try-catch code involving more complex control flow, testing that multiple
// values returned from calls in try code are not affected by multiple branching
// towards the landing pad, as well as making sure exceptions carrying multiple
// values of any Wasm numtype transport the exception values correctly across
// calls.
//
// There are tests for local direct calls, for imported direct calls, for
// indirect calls in a local table with local functions, for indirect calls in a
// local table of imported functions, and for indirect calls in an imported
// table of imported functions.
//
// - TODO: Add reftype values, when support for reftypes in exceptions is
//   implemented.
// -----------------------------------------------------------------------------

load(libdir + "eqArrayHelper.js");

// All individual tests take a string 'localThrow' as argument, and will be run
// with each element of the result of the following function.

function generateLocalThrows(types, baseThrow) {
  // Arguments:
  // - 'types': A string of space separated Wasm types.
  // - 'baseThrow': A string with a Wasm instruction sequence of Wasm functype
  //                `[${types}]-> [t*], which takes `types` arguments and ends
  //                up throwing the tag '$exn'.
  // Result:
  // - A JS array of strings, each representing a Wasm instruction sequence
  //   which is like `baseThrow', i.e., a Wasm instruction sequence of Wasm
  //   functype `[${types}]-> [t*], which takes `types` arguments and ends up
  //   throwing the tag '$exn'. The result does not include 'baseThrow'.
  //
  // All strings in Wasm text format.

  // Basic throws;
  let catchlessTryThrow =
      `(try (param ${types})
         (do ${baseThrow}))`;

  let catchAndThrow =
      `(try (param ${types})
         (do ${baseThrow})
         (catch $exn
           ${baseThrow})
         (catch_all))`;

  let blockThrow =
      `(block (param ${types})
         ${baseThrow})`;

  // This Wasm code requires that the function it appears in has an i32 local
  // with name "$ifPredicate".
  let conditionalThrow =
      `(if (param ${types})
         (local.get $ifPredicate)
         (then ${baseThrow})
         (else ${baseThrow}))`;

  // Including try-delegate.
  let baseDelegate =
      `(try (param ${types})
         (do ${baseThrow})
         (delegate 0))`;

  // Delegate just outside the block.
  let nestedDelegate1InBlock =
      `(block $label1 (param ${types})
         (try (param ${types})
           (do ${baseThrow})
           (delegate $label1)))`;

  let basicThrows = [catchlessTryThrow, blockThrow, conditionalThrow,
                     baseDelegate, nestedDelegate1InBlock];

  // Secondary throws (will end up inside a catch block).

  let baseRethrow =
      `(rethrow 0)`;

  let nestedRethrow =
      `(try (param ${types})
         (do
           ${baseThrow})
         (catch $exn
           (rethrow 1))
         (catch_all
           (rethrow 0)))`;

  let catchAllRethrowOriginal =
      `(try (param ${types})
         (do
           ${baseThrow})
         (catch_all
           (rethrow 1)))`;

  let secondaryThrows =
      [].concat(basicThrows,
                [baseRethrow, nestedRethrow, catchAllRethrowOriginal]);

  // Nestings.

  function basicNesting (basicThrow, secondaryThrow) {
    return `(try (param ${types})
              (do ${basicThrow})
              (catch $exn
                ${secondaryThrow})
              (catch_all))`;
  };

  let result = [];

  for (let basicThrow of basicThrows) {
    result.push(basicThrow);
    for (let secondaryThrow of secondaryThrows) {
      result.push(basicNesting(basicThrow, secondaryThrow));
    }
  }

  return result;
};

{
  // Some variables to be used in all tests.
  let typesJS = ["i32", "i64", "f32", "f64", "externref"];
  let types = typesJS.join(" ");

  // The following depend on whether simd is enabled or not. We write it like
  // this so we can run this test also when SIMD is not enabled.
  let exntype = "";
  let wrongV128 = "";
  let throwV128 = "";
  let checkV128Value = "";

  if (wasmSimdEnabled()) {
    exntype = types + " v128";
    wrongV128 = `(v128.const i32x4 11 22 33 44)`;
    throwV128 = `(v128.const i32x4 55 66 77 88)`;
    checkV128Value = `;; Check the V128 value
             ${throwV128}
             (i32x4.eq)
             (i32x4.all_true)`;
  } else {
    exntype = types + " i32";
    wrongV128 = "(i32.const 0)";
    throwV128 = "(i32.const 1)";
    checkV128Value = "";
  }

  let exnTypeDef = `(type $exnType (func (param ${exntype})))`;

  let throwValues =
      `;; Values to throw.
               (i32.const 2)
               (i64.const 3)
               (f32.const 4)
               (f64.const 13.37)
               (local.get $correctRef)
               ${throwV128}`;

  // The last 1 is the result of the test that the v128 value is correct, done
  // in wasm code (if simd is enabled).
  let correctResultsJS = [2, 3n, 4, 13.37, "foo", 1];

  let wrongValues =
      `;; Wrong values
                    (i32.const 5)
                    (i64.const 6)
                    (f32.const 0.1)
                    (f64.const 0.6437)
                    (local.get $wrongRef)
                    ${wrongV128}`;

  // The individual tests. -----------------------------------------------------

  function testDirectCallsThrowing(localThrow) {
    // Test direct function calls throwing any numeric value.

    let throwifTypeInline =
        // The result of the "throwif" function will be used as an argument the
        // second time "throwif" is called.
        `(param $ifPredicate i32) (param $correctRef externref) (result i32)`;

    let moduleHeaderThrowif =
        `(module
           ${exnTypeDef}
           (tag $exn (export "exn") (type $exnType))
           (func $throwif (export "throwif") ${throwifTypeInline}
             (if
               (local.get $ifPredicate)
               (then
                 ${throwValues}
                 ${localThrow}))
             (i32.const 1))`;

    let testModuleRest =
        `(tag $notThrownExn)
         (func $doNothing)
         (func (export "testFunc") (param $correctRef externref)
                                   (param $wrongRef externref)
                                   (result ${types} i32)
                                   (local $ifPredicate i32)
           (local.get $ifPredicate)
           (try (param i32) (result ${exntype})
             (do
               (local.get $wrongRef)
               (call $throwif) ;; Returns 1.
               (call $doNothing) ;; Does nothing.
               (local.get $correctRef)
               (call $throwif) ;; Throws $exn.
               (drop)
               ${wrongValues} ;; Won't reach this point.
               ${localThrow}
               unreachable)
             (catch $notThrownExn
               ${wrongValues})
             (catch $exn))
           ${checkV128Value}))`;

    function testDirectLocalCallsThrowing() {
      let mod = moduleHeaderThrowif + testModuleRest;
      // console.log("DIRECT LOCAL MOD = " + mod);  // Uncomment for debugging.

      assertEqArray(wasmEvalText(mod).exports.testFunc("foo", "bar"),
                    correctResultsJS);
    };

    function testDirectImportedCallsThrowing() {
      let exports = wasmEvalText(moduleHeaderThrowif + `)`).exports;
      // Uncomment for debugging.
      //console.log("DIRECT EXPORTS = " + moduleHeaderThrowif + ")");

      let mod =
          `(module
             ${exnTypeDef}
             (import "m" "exn" (tag $exn (type $exnType)))
             (import "m" "throwif" (func $throwif ${throwifTypeInline}))` +
          testModuleRest;
      // console.log("DIRECT IMPORT MOD = " + mod); // Uncomment for debugging.

      assertEqArray(
        wasmEvalText(mod, { m : exports}).exports.testFunc("foo", "bar"),
        correctResultsJS);
    };

    testDirectLocalCallsThrowing();
    testDirectImportedCallsThrowing();
  };

  function testIndirectCallsThrowing(localThrow) {
    // Test indirect calls throwing exceptions.

    let indirectFunctypeInline = `(param ${exntype})
                                  (result ${exntype})`;
    let getIndirectArgs = `(local.get 0) ;; i32
             (local.get 1) ;; i64
             (local.get 2) ;; f32
             (local.get 3) ;; f64
             (local.get 4) ;; ref
             ;; v128
             (local.get 5)`;

    let testFunctypeInline = `(param $correctRef externref)
                                     (param $wrongRef externref)
                                     ;; The last i32 result is the v128 check.
                                     (result ${types} i32)`;

    let moduleHeader =
        `(module
           ${exnTypeDef}
           (type $indirectFunctype (func ${indirectFunctypeInline}))
           (tag $exn (export "exn") (type $exnType))
           (tag $emptyExn (export "emptyExn"))
           (func $throwExn (export "throwExn") ${indirectFunctypeInline}
                                               (local $ifPredicate i32)
             ${getIndirectArgs}
             ${localThrow}
             unreachable)
           (func $throwEmptyExn (export "throwEmptyExn")
                                ${indirectFunctypeInline}
             (throw $emptyExn)
             unreachable)
           (func $returnArgs (export "returnArgs") ${indirectFunctypeInline}
             ${getIndirectArgs})
           (table (export "tab") funcref (elem $throwExn       ;; 0
                                               $throwEmptyExn  ;; 1
                                               $returnArgs))   ;; 2
           `;

    // The main test function happens to have the same Wasm functype as the
    // indirect calls.
    let testFuncHeader = `(func (export "testFunc") ${testFunctypeInline}
                                     (local $ifPredicate i32)
             `;

    // To test indirect calls to a local table of local functions
    function moduleIndirectLocalLocal(functionBody) {
      return moduleHeader + testFuncHeader + functionBody + `))`;
    };

    let exports = wasmEvalText(moduleHeader + ")").exports;
    // Uncomment for debugging.
    //console.log("INDIRECT EXPORTS = " + moduleHeader + ")");

    let moduleHeaderImporting =
        `(module
           ${exnTypeDef}
           (type $indirectFunctype (func ${indirectFunctypeInline}))
           (import "m" "exn" (tag $exn (type $exnType)))
           (import "m" "emptyExn" (tag $emptyExn))
           (import "m" "throwExn" (func $throwExn (type $indirectFunctype)))
           (import "m" "throwEmptyExn"
                   (func $throwEmptyExn (type $indirectFunctype)))
           (import "m" "returnArgs"
                   (func $returnArgs (type $indirectFunctype)))`;

    // To test indirect calls to a local table of imported functions.
    function moduleIndirectLocalImport(functionBody) {
      return moduleHeaderImporting +
        `(table funcref (elem $throwExn $throwEmptyExn $returnArgs))` +
        testFuncHeader + functionBody + `))`;
    };

    // To test indirect calls to an imported table of imported functions.
    function moduleIndirectImportImport(functionBody) {
      return moduleHeaderImporting +
        `(import "m" "tab" (table 3 funcref))` +
        testFuncHeader + functionBody + `))`;
    };

    function getModuleTextsForFunctionBody(functionBody) {
      return [moduleIndirectLocalLocal(functionBody),
              moduleIndirectLocalImport(functionBody),
              moduleIndirectImportImport(functionBody)];
    };

    // The function bodies for the tests.

    // Three indirect calls, the middle of which throws and will be caught. The
    // results of the first and second indirect calls are used by the next
    // indirect call. This should be called from try code, to check that the
    // pad-branches don't interfere with the results of each call.
    let indirectThrow = `${throwValues}
               (call_indirect (type $indirectFunctype) (i32.const 2)) ;; returnArgs
               (call_indirect (type $indirectFunctype) (i32.const 0)) ;; throwExn
               drop drop ;; Drop v128 and externref to do trivial and irrelevant ops.
               (f64.const 5)
               (f64.add)
               (local.get $wrongRef)
               ${wrongV128}
               ;; throwEmptyExn
               (call_indirect (type $indirectFunctype) (i32.const 1))
               unreachable`;

    // Simple try indirect throw and catch.
    let simpleTryIndirect = `(try (result ${exntype})
              (do ${indirectThrow})
              (catch $emptyExn
                 ${wrongValues})
              (catch $exn)
              (catch_all
                 ${wrongValues}))
              ${checkV128Value}`;

    // Indirect throw/catch_all, with a simple try indirect throw nested in the
    // catch_all.
    let nestedTryIndirect =
        `(try (result ${types} i32)
           (do
             ${wrongValues}
             ;; throwEmptyExn
             (call_indirect (type $indirectFunctype) (i32.const 1))
             drop ;; Drop the last v128 value.
             (i32.const 0))
           (catch_all
             ${simpleTryIndirect}))`;

    let functionBodies = [simpleTryIndirect, nestedTryIndirect];

    // Test throwing from indirect calls.
    for (let functionBody of functionBodies) {
      console.log("functionBody = : " + functionBody); // Uncomment for debugging.

      for (let mod of getModuleTextsForFunctionBody(functionBody)) {
        //console.log("mod = : " + mod); // Uncomment for debugging.

        let testFunction = wasmEvalText(mod, { m : exports}).exports.testFunc;
        assertEqArray(testFunction("foo", "bar"),
                      correctResultsJS);
      }
    }
  };

  // Run all tests. ------------------------------------------------------------

  let localThrows =
      ["(throw $exn)"].concat(generateLocalThrows(exntype, "(throw $exn)"));

  for (let localThrow of localThrows) {
    // Uncomment for debugging.
    //console.log("Testing with localThrow = " + localThrow);

    testDirectCallsThrowing(localThrow);
    testIndirectCallsThrowing(localThrow);
  }
}