calls.js (15110B)
1 // ----------------------------------------------------------------------------- 2 // This file contains tests checking Wasm functions with throwing functions and 3 // try-catch code involving more complex control flow, testing that multiple 4 // values returned from calls in try code are not affected by multiple branching 5 // towards the landing pad, as well as making sure exceptions carrying multiple 6 // values of any Wasm numtype transport the exception values correctly across 7 // calls. 8 // 9 // There are tests for local direct calls, for imported direct calls, for 10 // indirect calls in a local table with local functions, for indirect calls in a 11 // local table of imported functions, and for indirect calls in an imported 12 // table of imported functions. 13 // 14 // - TODO: Add reftype values, when support for reftypes in exceptions is 15 // implemented. 16 // ----------------------------------------------------------------------------- 17 18 load(libdir + "eqArrayHelper.js"); 19 20 // All individual tests take a string 'localThrow' as argument, and will be run 21 // with each element of the result of the following function. 22 23 function generateLocalThrows(types, baseThrow) { 24 // Arguments: 25 // - 'types': A string of space separated Wasm types. 26 // - 'baseThrow': A string with a Wasm instruction sequence of Wasm functype 27 // `[${types}]-> [t*], which takes `types` arguments and ends 28 // up throwing the tag '$exn'. 29 // Result: 30 // - A JS array of strings, each representing a Wasm instruction sequence 31 // which is like `baseThrow', i.e., a Wasm instruction sequence of Wasm 32 // functype `[${types}]-> [t*], which takes `types` arguments and ends up 33 // throwing the tag '$exn'. The result does not include 'baseThrow'. 34 // 35 // All strings in Wasm text format. 36 37 // Basic throws; 38 let catchlessTryThrow = 39 `try (param ${types}) 40 ${baseThrow} 41 end`; 42 43 let catchlessThrowExnref = 44 `try_table (param ${types}) 45 ${baseThrow} 46 end`; 47 48 let catchAndThrow = 49 `try (param ${types}) 50 ${baseThrow} 51 catch $exn 52 ${baseThrow} 53 catch_all 54 end`; 55 56 let catchAndThrowExnref = 57 `(block $join (param ${types}) 58 (block $catch (param ${types}) (result ${types}) 59 (block $catchAll (param ${types}) 60 try_table (param ${types}) (catch $exn $catch) (catch_all $catchAll) 61 ${baseThrow} 62 unreachable 63 end 64 ) 65 br $join 66 ) 67 ${baseThrow} 68 )`; 69 70 let blockThrow = 71 `(block (param ${types}) 72 ${baseThrow})`; 73 74 // This Wasm code requires that the function it appears in has an i32 local 75 // with name "$ifPredicate". 76 let conditionalThrow = 77 `(if (param ${types}) 78 (local.get $ifPredicate) 79 (then ${baseThrow}) 80 (else ${baseThrow}))`; 81 82 // Including try-delegate. 83 let baseDelegate = 84 `try (param ${types}) 85 ${baseThrow} 86 delegate 0`; 87 88 // Delegate just outside the block. 89 let nestedDelegate1InBlock = 90 `(block $label1 (param ${types}) 91 try (param ${types}) 92 ${baseThrow} 93 delegate $label1)`; 94 95 let basicThrows = [catchlessTryThrow, blockThrow, conditionalThrow, 96 baseDelegate, nestedDelegate1InBlock, catchlessThrowExnref, catchAndThrowExnref]; 97 98 // Secondary throws (will end up inside a catch block). 99 100 let baseRethrow = 101 `(rethrow 0)`; 102 103 let nestedRethrow = 104 `try (param ${types}) 105 ${baseThrow} 106 catch $exn 107 (rethrow 1) 108 catch_all 109 (rethrow 0) 110 end`; 111 112 let catchAllRethrowOriginal = 113 `try (param ${types}) 114 ${baseThrow} 115 catch_all 116 (rethrow 1) 117 end`; 118 119 let secondaryThrows = 120 [].concat(basicThrows, 121 [baseRethrow, nestedRethrow, catchAllRethrowOriginal]); 122 123 // Nestings. 124 125 function basicNesting (basicThrow, secondaryThrow) { 126 return `try (param ${types}) 127 ${basicThrow} 128 catch $exn 129 ${secondaryThrow} 130 catch_all 131 end`; 132 }; 133 134 let result = []; 135 136 for (let basicThrow of basicThrows) { 137 result.push(basicThrow); 138 let isExnref = basicThrow == catchlessThrowExnref || basicThrow == catchAndThrowExnref; 139 for (let secondaryThrow of secondaryThrows) { 140 let isRethrow = secondaryThrow == baseRethrow || secondaryThrow == nestedRethrow || secondaryThrow == catchAllRethrowOriginal; 141 if (isExnref && isRethrow) { 142 continue; 143 } 144 result.push(basicNesting(basicThrow, secondaryThrow)); 145 } 146 } 147 148 return result; 149 }; 150 151 { 152 // Some variables to be used in all tests. 153 let typesJS = ["i32", "i64", "f32", "f64", "externref"]; 154 let types = typesJS.join(" "); 155 156 // The following depend on whether simd is enabled or not. We write it like 157 // this so we can run this test also when SIMD is not enabled. 158 let exntype = ""; 159 let wrongV128 = ""; 160 let throwV128 = ""; 161 let checkV128Value = ""; 162 163 if (wasmSimdEnabled()) { 164 exntype = types + " v128"; 165 wrongV128 = `(v128.const i32x4 11 22 33 44)`; 166 throwV128 = `(v128.const i32x4 55 66 77 88)`; 167 checkV128Value = `;; Check the V128 value 168 ${throwV128} 169 (i32x4.eq) 170 (i32x4.all_true)`; 171 } else { 172 exntype = types + " i32"; 173 wrongV128 = "(i32.const 0)"; 174 throwV128 = "(i32.const 1)"; 175 checkV128Value = ""; 176 } 177 178 let exnTypeDef = `(type $exnType (func (param ${exntype})))`; 179 180 let throwValues = 181 `;; Values to throw. 182 (i32.const 2) 183 (i64.const 3) 184 (f32.const 4) 185 (f64.const 13.37) 186 (local.get $correctRef) 187 ${throwV128}`; 188 189 // The last 1 is the result of the test that the v128 value is correct, done 190 // in wasm code (if simd is enabled). 191 let correctResultsJS = [2, 3n, 4, 13.37, "foo", 1]; 192 193 let wrongValues = 194 `;; Wrong values 195 (i32.const 5) 196 (i64.const 6) 197 (f32.const 0.1) 198 (f64.const 0.6437) 199 (local.get $wrongRef) 200 ${wrongV128}`; 201 202 // The individual tests. ----------------------------------------------------- 203 204 function testDirectCallsThrowing(localThrow) { 205 // Test direct function calls throwing any numeric value. 206 207 let throwifTypeInline = 208 // The result of the "throwif" function will be used as an argument the 209 // second time "throwif" is called. 210 `(param $ifPredicate i32) (param $correctRef externref) (result i32)`; 211 212 let moduleHeaderThrowif = 213 `(module 214 ${exnTypeDef} 215 (tag $exn (export "exn") (type $exnType)) 216 (func $throwif (export "throwif") ${throwifTypeInline} 217 (if 218 (local.get $ifPredicate) 219 (then 220 ${throwValues} 221 ${localThrow})) 222 (i32.const 1))`; 223 224 let testModuleRest = 225 `(tag $notThrownExn) 226 (func $doNothing) 227 (func (export "testFunc") (param $correctRef externref) 228 (param $wrongRef externref) 229 (result ${types} i32) 230 (local $ifPredicate i32) 231 (local.get $ifPredicate) 232 try (param i32) (result ${exntype}) 233 (local.get $wrongRef) 234 (call $throwif) ;; Returns 1. 235 (call $doNothing) ;; Does nothing. 236 (local.get $correctRef) 237 (call $throwif) ;; Throws $exn. 238 (drop) 239 ${wrongValues} ;; Won't reach this point. 240 ${localThrow} 241 unreachable 242 catch $notThrownExn 243 ${wrongValues} 244 catch $exn 245 end 246 ${checkV128Value}))`; 247 248 function testDirectLocalCallsThrowing() { 249 let mod = moduleHeaderThrowif + testModuleRest; 250 // console.log("DIRECT LOCAL MOD = " + mod); // Uncomment for debugging. 251 252 assertEqArray(wasmEvalText(mod).exports.testFunc("foo", "bar"), 253 correctResultsJS); 254 }; 255 256 function testDirectImportedCallsThrowing() { 257 let exports = wasmEvalText(moduleHeaderThrowif + `)`).exports; 258 // Uncomment for debugging. 259 //console.log("DIRECT EXPORTS = " + moduleHeaderThrowif + ")"); 260 261 let mod = 262 `(module 263 ${exnTypeDef} 264 (import "m" "exn" (tag $exn (type $exnType))) 265 (import "m" "throwif" (func $throwif ${throwifTypeInline}))` + 266 testModuleRest; 267 // console.log("DIRECT IMPORT MOD = " + mod); // Uncomment for debugging. 268 269 assertEqArray( 270 wasmEvalText(mod, { m : exports}).exports.testFunc("foo", "bar"), 271 correctResultsJS); 272 }; 273 274 testDirectLocalCallsThrowing(); 275 testDirectImportedCallsThrowing(); 276 }; 277 278 function testIndirectCallsThrowing(localThrow) { 279 // Test indirect calls throwing exceptions. 280 281 let indirectFunctypeInline = `(param ${exntype}) 282 (result ${exntype})`; 283 let getIndirectArgs = `(local.get 0) ;; i32 284 (local.get 1) ;; i64 285 (local.get 2) ;; f32 286 (local.get 3) ;; f64 287 (local.get 4) ;; ref 288 ;; v128 289 (local.get 5)`; 290 291 let testFunctypeInline = `(param $correctRef externref) 292 (param $wrongRef externref) 293 ;; The last i32 result is the v128 check. 294 (result ${types} i32)`; 295 296 let moduleHeader = 297 `(module 298 ${exnTypeDef} 299 (type $indirectFunctype (func ${indirectFunctypeInline})) 300 (tag $exn (export "exn") (type $exnType)) 301 (tag $emptyExn (export "emptyExn")) 302 (func $throwExn (export "throwExn") ${indirectFunctypeInline} 303 (local $ifPredicate i32) 304 ${getIndirectArgs} 305 ${localThrow} 306 unreachable) 307 (func $throwEmptyExn (export "throwEmptyExn") 308 ${indirectFunctypeInline} 309 (throw $emptyExn) 310 unreachable) 311 (func $returnArgs (export "returnArgs") ${indirectFunctypeInline} 312 ${getIndirectArgs}) 313 (table (export "tab") funcref (elem $throwExn ;; 0 314 $throwEmptyExn ;; 1 315 $returnArgs)) ;; 2 316 `; 317 318 // The main test function happens to have the same Wasm functype as the 319 // indirect calls. 320 let testFuncHeader = `(func (export "testFunc") ${testFunctypeInline} 321 (local $ifPredicate i32) 322 `; 323 324 // To test indirect calls to a local table of local functions 325 function moduleIndirectLocalLocal(functionBody) { 326 return moduleHeader + testFuncHeader + functionBody + `))`; 327 }; 328 329 let exports = wasmEvalText(moduleHeader + ")").exports; 330 // Uncomment for debugging. 331 //console.log("INDIRECT EXPORTS = " + moduleHeader + ")"); 332 333 let moduleHeaderImporting = 334 `(module 335 ${exnTypeDef} 336 (type $indirectFunctype (func ${indirectFunctypeInline})) 337 (import "m" "exn" (tag $exn (type $exnType))) 338 (import "m" "emptyExn" (tag $emptyExn)) 339 (import "m" "throwExn" (func $throwExn (type $indirectFunctype))) 340 (import "m" "throwEmptyExn" 341 (func $throwEmptyExn (type $indirectFunctype))) 342 (import "m" "returnArgs" 343 (func $returnArgs (type $indirectFunctype)))`; 344 345 // To test indirect calls to a local table of imported functions. 346 function moduleIndirectLocalImport(functionBody) { 347 return moduleHeaderImporting + 348 `(table funcref (elem $throwExn $throwEmptyExn $returnArgs))` + 349 testFuncHeader + functionBody + `))`; 350 }; 351 352 // To test indirect calls to an imported table of imported functions. 353 function moduleIndirectImportImport(functionBody) { 354 return moduleHeaderImporting + 355 `(import "m" "tab" (table 3 funcref))` + 356 testFuncHeader + functionBody + `))`; 357 }; 358 359 function getModuleTextsForFunctionBody(functionBody) { 360 return [moduleIndirectLocalLocal(functionBody), 361 moduleIndirectLocalImport(functionBody), 362 moduleIndirectImportImport(functionBody)]; 363 }; 364 365 // The function bodies for the tests. 366 367 // Three indirect calls, the middle of which throws and will be caught. The 368 // results of the first and second indirect calls are used by the next 369 // indirect call. This should be called from try code, to check that the 370 // pad-branches don't interfere with the results of each call. 371 let indirectThrow = `${throwValues} 372 (call_indirect (type $indirectFunctype) (i32.const 2)) ;; returnArgs 373 (call_indirect (type $indirectFunctype) (i32.const 0)) ;; throwExn 374 drop drop ;; Drop v128 and externref to do trivial and irrelevant ops. 375 (f64.const 5) 376 (f64.add) 377 (local.get $wrongRef) 378 ${wrongV128} 379 ;; throwEmptyExn 380 (call_indirect (type $indirectFunctype) (i32.const 1)) 381 unreachable`; 382 383 // Simple try indirect throw and catch. 384 let simpleTryIndirect = ` 385 try (result ${exntype}) 386 ${indirectThrow} 387 catch $emptyExn 388 ${wrongValues} 389 catch $exn 390 catch_all 391 ${wrongValues} 392 end 393 ${checkV128Value}`; 394 395 // Indirect throw/catch_all, with a simple try indirect throw nested in the 396 // catch_all. 397 let nestedTryIndirect = 398 `try (result ${types} i32) 399 ${wrongValues} 400 ;; throwEmptyExn 401 (call_indirect (type $indirectFunctype) (i32.const 1)) 402 drop ;; Drop the last v128 value. 403 (i32.const 0) 404 catch_all 405 ${simpleTryIndirect} 406 end`; 407 408 let functionBodies = [simpleTryIndirect, nestedTryIndirect]; 409 410 // Test throwing from indirect calls. 411 for (let functionBody of functionBodies) { 412 // console.log("functionBody = : " + functionBody); // Uncomment for debugging. 413 414 for (let mod of getModuleTextsForFunctionBody(functionBody)) { 415 //console.log("mod = : " + mod); // Uncomment for debugging. 416 417 let testFunction = wasmEvalText(mod, { m : exports}).exports.testFunc; 418 assertEqArray(testFunction("foo", "bar"), 419 correctResultsJS); 420 } 421 } 422 }; 423 424 // Run all tests. ------------------------------------------------------------ 425 426 let localThrows = 427 ["(throw $exn)"].concat(generateLocalThrows(exntype, "(throw $exn)")); 428 429 for (let localThrow of localThrows) { 430 // Uncomment for debugging. 431 // console.log("Testing with localThrow = " + localThrow); 432 433 testDirectCallsThrowing(localThrow); 434 testIndirectCallsThrowing(localThrow); 435 } 436 }