wasm.js (19894B)
1 if (!wasmIsSupported()) 2 quit(); 3 4 load(libdir + "asserts.js"); 5 6 function canRunHugeMemoryTests() { 7 // We're aiming for 64-bit desktop builds with no interesting analysis 8 // running that might inflate memory consumption unreasonably. It's OK if 9 // they're debug builds, though. 10 // 11 // The build configuration object may be extended at any time with new 12 // properties, so neither an allowlist of properties that can be true or a 13 // blocklist of properties that can't be true is great. But the latter is 14 // probably better. 15 let blocked = ['rooting-analysis','simulator', 16 'android','wasi','asan','tsan','ubsan','valgrind']; 17 for ( let b of blocked ) { 18 if (getBuildConfiguration(b)) { 19 print("Failing canRunHugeMemoryTests() because '" + b + "' is true"); 20 return false; 21 } 22 } 23 if (getBuildConfiguration("pointer-byte-size") != 8) { 24 print("Failing canRunHugeMemoryTests() because the build is not 64-bit"); 25 return false; 26 } 27 return true; 28 } 29 30 // On 64-bit systems with explicit bounds checking, ion and baseline can handle 31 // 65536 pages. 32 33 var PageSizeInBytes = 65536; 34 var MaxBytesIn32BitMemory = 0; 35 if (largeArrayBufferSupported()) { 36 MaxBytesIn32BitMemory = 65536*PageSizeInBytes; 37 } else { 38 // This is an overestimate twice: first, the max byte value is divisible by 39 // the page size; second, it must be a valid bounds checking immediate. But 40 // INT32_MAX is fine for testing. 41 MaxBytesIn32BitMemory = 0x7FFF_FFFF; 42 } 43 var MaxPagesIn32BitMemory = Math.floor(MaxBytesIn32BitMemory / PageSizeInBytes); 44 45 function wasmEvalBinary(binary, imports, compileOptions) { 46 let valid = WebAssembly.validate(binary, compileOptions); 47 48 let m; 49 try { 50 m = new WebAssembly.Module(binary, compileOptions); 51 assertEq(valid, true, "failed WebAssembly.validate but still compiled successfully"); 52 } catch(e) { 53 if (!e.toString().match(/out of memory/)) { 54 assertEq(valid, false, `passed WebAssembly.validate but failed to compile: ${e}`); 55 } 56 throw e; 57 } 58 59 return new WebAssembly.Instance(m, imports); 60 } 61 62 function wasmEvalText(str, imports, compileOptions) { 63 return wasmEvalBinary(wasmTextToBinary(str), imports, compileOptions); 64 } 65 66 function wasmValidateBinary(binary) { 67 let valid = WebAssembly.validate(binary); 68 if (!valid) { 69 new WebAssembly.Module(binary); 70 throw new Error("module failed WebAssembly.validate but compiled successfully"); 71 } 72 assertEq(valid, true, "wasm module was invalid"); 73 } 74 75 function wasmFailValidateBinary(binary, pattern) { 76 assertEq(WebAssembly.validate(binary), false, "module passed WebAssembly.validate when it should not have"); 77 assertErrorMessage(() => new WebAssembly.Module(binary), WebAssembly.CompileError, pattern, "module failed WebAssembly.validate but did not fail to compile in the expected way"); 78 } 79 80 function wasmValidateText(str) { 81 return wasmValidateBinary(wasmTextToBinary(str)); 82 } 83 84 function wasmFailValidateText(str, pattern) { 85 return wasmFailValidateBinary(wasmTextToBinary(str), pattern); 86 } 87 88 // Expected compilation failure can happen in a couple of ways: 89 // 90 // - The compiler can be available but not capable of recognizing some opcodes: 91 // Compilation will start, but will fail with a CompileError. This is what 92 // happens without --wasm-gc if opcodes enabled by --wasm-gc are used. 93 // 94 // - The compiler can be unavailable: Compilation will not start at all but will 95 // throw an Error. This is what happens with "--wasm-gc --wasm-compiler=X" if 96 // X does not support the features enabled by --wasm-gc. 97 98 function wasmCompilationShouldFail(bin, compile_error_regex) { 99 try { 100 new WebAssembly.Module(bin); 101 } catch (e) { 102 if (e instanceof WebAssembly.CompileError) { 103 assertEq(compile_error_regex.test(e), true); 104 } else if (e instanceof Error) { 105 assertEq(/can't use wasm debug\/gc without baseline/.test(e), true); 106 } else { 107 throw new Error("Unexpected exception value:\n" + e); 108 } 109 } 110 } 111 112 function mismatchError(actual, expect) { 113 var str = `(type mismatch: expression has type ${actual} but expected ${expect})|` + 114 `(type mismatch: expected ${expect}, found ${actual}\)`; 115 return RegExp(str); 116 } 117 118 const emptyStackError = /(from empty stack)|(nothing on stack)/; 119 const unusedValuesError = /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/; 120 121 function jsify(wasmVal) { 122 if (wasmVal === 'nan') 123 return NaN; 124 if (wasmVal === 'inf') 125 return Infinity; 126 if (wasmVal === '-inf') 127 return Infinity; 128 if (wasmVal === '-0') 129 return -0; 130 return wasmVal; 131 } 132 133 function _augmentSrc(src, assertions) { 134 let i = 0; 135 let newSrc = src.substr(0, src.lastIndexOf(')')); 136 for (let { func, args, expected, type } of assertions) { 137 newSrc += ` 138 (func (export "assert_${i++}") (result i32) 139 ${ args ? args.join('\n') : '' } 140 call ${func}`; 141 142 if (typeof expected !== 'undefined') { 143 switch (type) { 144 case 'f32': 145 newSrc += ` 146 i32.reinterpret_f32 147 ${(function () { 148 if (expected == 'nan:arithmetic') { 149 expected = '0x7FC00000'; 150 return '(i32.const 0x7FC00000) i32.and'; 151 } 152 return ''; 153 })()} 154 i32.const ${expected} 155 i32.eq`; 156 break; 157 case 'f64': 158 newSrc += ` 159 i64.reinterpret_f64 160 ${(function () { 161 if (expected == 'nan:arithmetic') { 162 expected = '0x7FF8000000000000'; 163 return '(i64.const 0x7FF8000000000000) i64.and'; 164 } 165 return ''; 166 })()} 167 i64.const ${expected} 168 i64.eq`; 169 break; 170 case 'i32': 171 newSrc += ` 172 i32.const ${expected} 173 i32.eq`; 174 break; 175 case 'i64': 176 newSrc += ` 177 i64.const ${expected} 178 i64.eq`; 179 break; 180 case 'v128': 181 newSrc += ` 182 v128.const ${expected} 183 i8x16.eq 184 i8x16.all_true`; 185 break; 186 default: 187 throw new Error("unexpected usage of wasmAssert"); 188 } 189 } else { 190 // Always true when there's no expected return value. 191 newSrc += "\ni32.const 1"; 192 } 193 194 newSrc += ')\n'; 195 } 196 newSrc += ')'; 197 return newSrc; 198 } 199 200 function wasmAssert(src, assertions, maybeImports = {}, exportBox = null) { 201 let { exports } = wasmEvalText(_augmentSrc(src, assertions), maybeImports); 202 if (exportBox !== null) 203 exportBox.exports = exports; 204 for (let i = 0; i < assertions.length; i++) { 205 let { func, expected, params } = assertions[i]; 206 let paramText = params ? params.join(', ') : ''; 207 assertEq(exports[`assert_${i}`](), 1, 208 `Unexpected value when running ${func}(${paramText}), expecting ${expected}.`); 209 } 210 } 211 212 // Fully test a module: 213 // - ensure it validates. 214 // - ensure it compiles and produces the expected result. 215 // - ensure textToBinary(binaryToText(binary)) = binary 216 // Preconditions: 217 // - the binary module must export a function called "run". 218 function wasmFullPass(text, expected, maybeImports, ...args) { 219 let binary = wasmTextToBinary(text); 220 assertEq(WebAssembly.validate(binary), true, "Must validate."); 221 222 let module = new WebAssembly.Module(binary); 223 let instance = new WebAssembly.Instance(module, maybeImports); 224 assertEq(typeof instance.exports.run, 'function', "A 'run' function must be exported."); 225 assertEq(instance.exports.run(...args), expected, "Initial module must return the expected result."); 226 } 227 228 // Ditto, but expects a function named '$run' instead of exported with this name. 229 function wasmFullPassI64(text, expected, maybeImports, ...args) { 230 let binary = wasmTextToBinary(text); 231 assertEq(WebAssembly.validate(binary), true, "Must validate."); 232 233 let augmentedSrc = _augmentSrc(text, [ { type: 'i64', func: '$run', args, expected } ]); 234 let augmentedBinary = wasmTextToBinary(augmentedSrc); 235 236 let module = new WebAssembly.Module(augmentedBinary); 237 let instance = new WebAssembly.Instance(module, maybeImports); 238 assertEq(instance.exports.assert_0(), 1); 239 } 240 241 function wasmRunWithDebugger(wast, lib, init, done) { 242 let g = newGlobal({newCompartment: true}); 243 let dbg = new Debugger(g); 244 245 g.eval(` 246 var wasm = wasmTextToBinary(\`${wast}\`); 247 var lib = ${lib || 'undefined'}; 248 var m = new WebAssembly.Instance(new WebAssembly.Module(wasm), lib);`); 249 250 var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0]; 251 252 init({dbg, wasmScript, g,}); 253 let result = undefined, error = undefined; 254 try { 255 result = g.eval("m.exports.test()"); 256 } catch (ex) { 257 error = ex; 258 } 259 done({dbg, result, error, wasmScript, g,}); 260 } 261 262 const WasmHelpers = {}; 263 264 (function() { 265 let enabled = false; 266 try { 267 enableSingleStepProfiling(); 268 disableSingleStepProfiling(); 269 enabled = true; 270 } catch (e) {} 271 WasmHelpers.isSingleStepProfilingEnabled = enabled; 272 })(); 273 274 // The cache of matched and unmatched strings seriously speeds up matching on 275 // the emulators and makes tests time out less often. 276 277 var matched = {}; 278 var unmatched = {}; 279 280 WasmHelpers._normalizeStack = (stack, preciseStacks) => { 281 var wasmFrameTypes = [ 282 {re:/^jit call to int64(?: or v128)? wasm function$/, sub:"i64>"}, 283 {re:/^out-of-line coercion for jit entry arguments \(in wasm\)$/, sub:"ool>"}, 284 {re:/^wasm-function\[(\d+)\] \(.*\)$/, sub:"$1"}, 285 {re:/^(\w+) \(.*?> WebAssembly\.Module:\d+\)$/, sub:"$1"}, 286 {re:/^(fast|slow) exit trampoline (?:to native )?\(in wasm\)$/, sub:"<"}, 287 {re:/^call to(?: asm.js)? native (.*?)(?: builtin)? \(in wasm\)$/, sub:"$1"}, 288 {re:/^call to native (.*)$/, sub:"#$1"}, 289 {re:/^tier-up request \(in wasm\)$/, sub: ""}, 290 {re:/ \(in wasm\)$/, sub:""}, 291 ]; 292 293 let entryRegexps; 294 if (preciseStacks) { 295 entryRegexps = [ 296 {re:/^slow entry trampoline \(in wasm\)$/, sub:"!>"}, 297 {re:/^fast entry trampoline \(in wasm\)$/, sub:">"}, 298 ]; 299 } else { 300 entryRegexps = [ 301 {re:/^(fast|slow) entry trampoline \(in wasm\)$/, sub:">"} 302 ]; 303 } 304 wasmFrameTypes = entryRegexps.concat(wasmFrameTypes); 305 306 var framesIn = stack.split(','); 307 var framesOut = []; 308 outer: 309 for (let frame of framesIn) { 310 if (unmatched[frame]) 311 continue; 312 let probe = matched[frame]; 313 if (probe !== undefined) { 314 framesOut.push(probe); 315 continue; 316 } 317 for (let {re, sub} of wasmFrameTypes) { 318 if (re.test(frame)) { 319 let repr = frame.replace(re, sub); 320 framesOut.push(repr); 321 matched[frame] = repr; 322 continue outer; 323 } 324 } 325 unmatched[frame] = true; 326 } 327 328 return framesOut.join(','); 329 }; 330 331 WasmHelpers._removeAdjacentDuplicates = array => { 332 if (array.length < 2) 333 return; 334 let i = 0; 335 for (let j = 1; j < array.length; j++) { 336 if (array[i] !== array[j]) 337 array[++i] = array[j]; 338 } 339 array.length = i + 1; 340 } 341 342 WasmHelpers.normalizeStacks = (stacks, preciseStacks = false) => { 343 let observed = []; 344 for (let i = 0; i < stacks.length; i++) 345 observed[i] = WasmHelpers._normalizeStack(stacks[i], preciseStacks); 346 WasmHelpers._removeAdjacentDuplicates(observed); 347 return observed; 348 }; 349 350 WasmHelpers._compareStacks = (got, expect) => { 351 if (got.length != expect.length) { 352 return false; 353 } 354 for (let i = 0; i < got.length; i++) { 355 if (got[i] !== expect[i]) 356 return false; 357 } 358 return true; 359 } 360 361 WasmHelpers.assertEqImpreciseStacks = (got, expect) => { 362 let observed = WasmHelpers.normalizeStacks(got, /* precise */ false); 363 let same = WasmHelpers._compareStacks(observed, expect); 364 if (!same) { 365 if (observed.length != expect.length) { 366 print(`Got:\n${observed.toSource()}\nExpect:\n${expect.toSource()}`); 367 assertEq(observed.length, expect.length); 368 } 369 for (let i = 0; i < observed.length; i++) { 370 if (observed[i] !== expect[i]) { 371 print(`On stack ${i}, Got:\n${observed[i]}\nExpect:\n${expect[i]}`); 372 assertEq(observed[i], expect[i]); 373 } 374 } 375 } 376 } 377 378 WasmHelpers.extractStackFrameFunction = (frameString) => { 379 var [_, name, filename, line, column] = frameString.match(/^(.*)@(.*):(.*):(.*)$/); 380 if (name) 381 return name; 382 if (/wasm-function/.test(line)) 383 return line; 384 return ""; 385 }; 386 387 WasmHelpers.assertStackTrace = (exception, expected) => { 388 let callsites = exception.stack.trim().split('\n').map(WasmHelpers.extractStackFrameFunction); 389 assertEq(callsites.length, expected.length); 390 for (let i = 0; i < callsites.length; i++) { 391 assertEq(callsites[i], expected[i]); 392 } 393 }; 394 395 WasmHelpers.nextLineNumber = (n=1) => { 396 return +(new Error().stack).split('\n')[1].split(':')[1] + n; 397 } 398 399 WasmHelpers.startProfiling = () => { 400 if (!WasmHelpers.isSingleStepProfilingEnabled) 401 return; 402 enableSingleStepProfiling(); 403 } 404 405 WasmHelpers.endProfiling = () => { 406 if (!WasmHelpers.isSingleStepProfilingEnabled) 407 return; 408 return disableSingleStepProfiling(); 409 } 410 411 WasmHelpers.assertEqPreciseStacks = (observed, expectedStacks) => { 412 if (!WasmHelpers.isSingleStepProfilingEnabled) 413 return null; 414 415 observed = WasmHelpers.normalizeStacks(observed, /* precise */ true); 416 417 for (let i = 0; i < expectedStacks.length; i++) { 418 if (WasmHelpers._compareStacks(observed, expectedStacks[i])) 419 return i; 420 } 421 422 throw new Error(`no plausible stacks found, observed: ${observed.join('/')} 423 Expected one of: 424 ${expectedStacks.map(stacks => stacks.join("/")).join('\n')}`); 425 } 426 427 function fuzzingSafe() { 428 return typeof getErrorNotes == 'undefined'; 429 } 430 431 // Common instantiations of wasm values for dynamic type check testing 432 433 // Valid values for funcref 434 let WasmFuncrefValues = [ 435 wasmEvalText(`(module (func (export "")))`).exports[''], 436 ]; 437 438 // Valid values for structref/arrayref 439 let WasmStructrefValues = []; 440 let WasmArrayrefValues = []; 441 let { newStruct, newArray } = wasmEvalText(` 442 (module 443 (type $s (sub (struct))) 444 (type $a (sub (array i32))) 445 (func (export "newStruct") (result anyref) 446 struct.new $s) 447 (func (export "newArray") (result anyref) 448 i32.const 0 449 i32.const 0 450 array.new $a) 451 )`).exports; 452 WasmStructrefValues.push(newStruct()); 453 WasmArrayrefValues.push(newArray()); 454 455 let WasmGcObjectValues = WasmStructrefValues.concat(WasmArrayrefValues); 456 457 // Valid values for eqref 458 let WasmEqrefValues = [...WasmStructrefValues, ...WasmArrayrefValues]; 459 460 // Valid values for i31ref 461 let MinI31refValue = -1 * Math.pow(2, 30); 462 let MaxI31refValue = Math.pow(2, 30) - 1; 463 let WasmI31refValues = [ 464 // first four 31-bit signed numbers 465 MinI31refValue, 466 MinI31refValue + 1, 467 MinI31refValue + 2, 468 MinI31refValue + 3, 469 // five numbers around zero 470 -2, 471 -1, 472 0, 473 1, 474 2, 475 // last four 31-bit signed numbers 476 MaxI31refValue - 3, 477 MaxI31refValue - 2, 478 MaxI31refValue - 1, 479 MaxI31refValue, 480 ]; 481 482 // Valid and invalid values for anyref 483 let WasmAnyrefValues = [...WasmEqrefValues, ...WasmI31refValues]; 484 let WasmNonAnyrefValues = [ 485 undefined, 486 true, 487 false, 488 {x:1337}, 489 ["abracadabra"], 490 13.37, 491 0x7fffffff + 0.1, 492 -0x7fffffff - 0.1, 493 0x80000000 + 0.1, 494 -0x80000000 - 0.1, 495 0xffffffff + 0.1, 496 -0xffffffff - 0.1, 497 Number.EPSILON, 498 Number.MAX_SAFE_INTEGER, 499 Number.MIN_SAFE_INTEGER, 500 Number.MIN_VALUE, 501 Number.MAX_VALUE, 502 Number.NaN, 503 "hi", 504 37n, 505 new Number(42), 506 new Boolean(true), 507 Symbol("status"), 508 () => 1337, 509 ...WasmFuncrefValues, 510 ]; 511 512 // Valid externref values 513 let WasmNonNullExternrefValues = [ 514 ...WasmNonAnyrefValues, 515 ...WasmAnyrefValues 516 ]; 517 let WasmExternrefValues = [null, ...WasmNonNullExternrefValues]; 518 519 // Max number of memories in a single wasm module. 520 let MaxMemories = 100 521 522 // Constants related to memory sizes. 523 const MaxMemory64PagesValidation = BigInt(Math.pow(2, 37) - 1); // from spec 524 const MaxTable64ElemsValidation = 0xFFFF_FFFF_FFFF_FFFFn; // from spec 525 const MaxTableElemsRuntime = 10000000; // from WasmConstants.h 526 const MaxUint32 = 0xFFFF_FFFF; 527 528 // Common array utilities 529 530 // iota(n,k) creates an Array of length n with values k..k+n-1 531 function iota(len, k=0) { 532 let xs = []; 533 for ( let i=0 ; i < len ; i++ ) 534 xs.push(i+k); 535 return xs; 536 } 537 538 // cross(A) where A is an array of length n creates an Array length n*n of 539 // two-element Arrays representing all pairs of elements of A. 540 function cross(xs) { 541 let results = []; 542 for ( let x of xs ) 543 for ( let y of xs ) 544 results.push([x,y]); 545 return results; 546 } 547 548 // Remove all values equal to v from an array xs, comparing equal for NaN. 549 function remove(v, xs) { 550 let result = []; 551 for ( let w of xs ) { 552 if (v === w || isNaN(v) && isNaN(w)) 553 continue; 554 result.push(w); 555 } 556 return result; 557 } 558 559 // permute(A) where A is an Array returns an Array of Arrays, each inner Array a 560 // distinct permutation of the elements of A. A is assumed not to have any 561 // elements that are pairwise equal in the sense of remove(). 562 function permute(xs) { 563 if (xs.length == 1) 564 return [xs]; 565 let results = []; 566 for (let v of xs) 567 for (let tail of permute(remove(v, xs))) 568 results.push([v, ...tail]); 569 return results; 570 } 571 572 // interleave([a,b,c,...],[0,1,2,...]) => [a,0,b,1,c,2,...] 573 function interleave(xs, ys) { 574 assertEq(xs.length, ys.length); 575 let res = []; 576 for ( let i=0 ; i < xs.length; i++ ) { 577 res.push(xs[i]); 578 res.push(ys[i]); 579 } 580 return res; 581 } 582 583 // assertSame([a,...],[b,...]) asserts that the two arrays have the same length 584 // and that they element-wise assertEq IGNORING Number/BigInt differences. This 585 // predicate is in this file because it is wasm-specific. 586 function assertSame(got, expected) { 587 assertEq(got.length, expected.length); 588 for ( let i=0; i < got.length; i++ ) { 589 let g = got[i]; 590 let e = expected[i]; 591 if (typeof g != typeof e) { 592 if (typeof g == "bigint") 593 e = BigInt(e); 594 else if (typeof e == "bigint") 595 g = BigInt(g); 596 } 597 assertEq(g, e); 598 } 599 } 600 601 // assertEqResults([a,...],[b,...]) asserts that the two results from a wasm 602 // call are the same. This will compare deeply inside the result array, and 603 // relax a mismatch around single element arrays. 604 // 605 // This predicate is in this file because it is wasm-specific. 606 function assertEqResults(got, expected) { 607 if (!Array.isArray(got)) { 608 got = [got]; 609 } 610 if (!Array.isArray(expected)) { 611 expected = [expected]; 612 } 613 assertSame(got, expected); 614 } 615 616 // TailCallIterations is selected to be large enough to trigger 617 // "too much recursion", but not to be slow. 618 var TailCallIterations = getBuildConfiguration("simulator") ? 1000 : 100000; 619 // TailCallBallast is selected to spill registers as parameters. 620 var TailCallBallast = 30;