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