harness.js (14381B)
1 "use strict"; 2 3 /* Copyright 2021 Mozilla Foundation 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 if (!wasmIsSupported()) { 19 quit(); 20 } 21 22 function partialOobWriteMayWritePartialData() { 23 let arm_native = getBuildConfiguration("arm") && !getBuildConfiguration("arm-simulator"); 24 let arm64_native = getBuildConfiguration("arm64") && !getBuildConfiguration("arm64-simulator"); 25 let riscv64_native = getBuildConfiguration("riscv64") && !getBuildConfiguration("riscv64-simulator"); 26 return arm_native || arm64_native || riscv64_native; 27 } 28 29 function bytes(type, bytes) { 30 var typedBuffer = new Uint8Array(bytes); 31 return wasmGlobalFromArrayBuffer(type, typedBuffer.buffer); 32 } 33 function value(type, value) { 34 return new WebAssembly.Global({ 35 value: type, 36 mutable: false, 37 }, value); 38 } 39 40 function i8x16(elements) { 41 let typedBuffer = new Uint8Array(elements); 42 return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); 43 } 44 function i16x8(elements) { 45 let typedBuffer = new Uint16Array(elements); 46 return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); 47 } 48 function i32x4(elements) { 49 let typedBuffer = new Uint32Array(elements); 50 return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); 51 } 52 function i64x2(elements) { 53 let typedBuffer = new BigUint64Array(elements); 54 return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); 55 } 56 function f32x4(elements) { 57 let typedBuffer = new Float32Array(elements); 58 return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); 59 } 60 function f64x2(elements) { 61 let typedBuffer = new Float64Array(elements); 62 return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); 63 } 64 65 function either(...arr) { 66 return new EitherVariants(arr); 67 } 68 69 class F32x4Pattern { 70 constructor(x, y, z, w) { 71 this.x = x; 72 this.y = y; 73 this.z = z; 74 this.w = w; 75 } 76 } 77 78 class F64x2Pattern { 79 constructor(x, y) { 80 this.x = x; 81 this.y = y; 82 } 83 } 84 85 class RefWithType { 86 constructor(type) { 87 this.type = type; 88 } 89 90 formatExpected() { 91 return `RefWithType(${this.type})`; 92 } 93 94 test(refGlobal) { 95 try { 96 new WebAssembly.Global({value: this.type}, refGlobal.value); 97 return true; 98 } catch (err) { 99 assertEq(err instanceof TypeError, true, `wrong type of error when creating global: ${err}`); 100 assertEq(!!err.message.match(/can only pass/), true, `wrong type of error when creating global: ${err}`); 101 return false; 102 } 103 } 104 } 105 106 // ref.extern values created by spec tests will be JS objects of the form 107 // { [externsym]: <number> }. Other externref values are possible to observe 108 // if extern.convert_any is used. 109 let externsym = Symbol("externref"); 110 function externref(s) { 111 return { [externsym]: s }; 112 } 113 114 class ExternRefResult { 115 constructor(n) { 116 this.n = n; 117 } 118 119 formatExpected() { 120 return `ref.extern ${this.n}`; 121 } 122 123 test(global) { 124 // the global's value can either be an externref or just a plain old JS number 125 let result = global.value; 126 if (typeof global.value === "object" && externsym in global.value) { 127 result = global.value[externsym]; 128 } 129 return result === this.n; 130 } 131 } 132 133 // ref.host values created by spectests will be whatever the JS API does to 134 // convert the given value to anyref. It should implicitly be like any.convert_extern. 135 function hostref(v) { 136 const { internalizeNum } = new WebAssembly.Instance( 137 new WebAssembly.Module(wasmTextToBinary(`(module 138 (func (import "test" "coerce") (param i32) (result anyref)) 139 (func (export "internalizeNum") (param i32) (result anyref) 140 (call 0 (local.get 0)) 141 ) 142 )`)), 143 { "test": { "coerce": x => x } }, 144 ).exports; 145 return internalizeNum(v); 146 } 147 148 class HostRefResult { 149 constructor(n) { 150 this.n = n; 151 } 152 153 formatExpected() { 154 return `ref.host ${this.n}`; 155 } 156 157 test(externrefGlobal) { 158 assertEq(externsym in externrefGlobal.value, true, `HostRefResult only works with externref inputs`); 159 return externrefGlobal.value[externsym] === this.n; 160 } 161 } 162 163 // https://github.com/WebAssembly/spec/blob/main/interpreter/README.md#spectest-host-module 164 let linkage = { 165 "spectest": { 166 global_i32: 666, 167 global_i64: 666n, 168 global_f32: 666.6, 169 global_f64: 666.6, 170 171 table: new WebAssembly.Table({ 172 initial: 10, 173 maximum: 20, 174 element: "anyfunc", 175 }), 176 table64: new WebAssembly.Table({ 177 address: "i64", 178 initial: 10n, 179 maximum: 20n, 180 element: "anyfunc", 181 }), 182 183 memory: new WebAssembly.Memory({ initial: 1, maximum: 2 }), 184 185 print: console.log.bind(console), 186 print_i32: console.log.bind(console), 187 print_i64: console.log.bind(console), 188 print_f32: console.log.bind(console), 189 print_f64: console.log.bind(console), 190 print_i32_f32: console.log.bind(console), 191 print_f64_f64: console.log.bind(console), 192 }, 193 }; 194 195 function module(source) { 196 let bytecode = wasmTextToBinary(source); 197 let module = new WebAssembly.Module(bytecode); 198 return module; 199 } 200 201 function instantiate(source) { 202 let bytecode = wasmTextToBinary(source); 203 let module = new WebAssembly.Module(bytecode); 204 let instance = new WebAssembly.Instance(module, linkage); 205 return instance.exports; 206 } 207 208 function instantiateFromModule(module) { 209 let instance = new WebAssembly.Instance(module, linkage); 210 return instance.exports; 211 } 212 213 function register(instance, name) { 214 linkage[name] = instance; 215 } 216 217 function invoke(instance, field, params) { 218 let func = instance[field]; 219 assertEq(func instanceof Function, true, "expected a function"); 220 return wasmLosslessInvoke(func, ...params); 221 } 222 223 function get(instance, field) { 224 let global = instance[field]; 225 assertEq( 226 global instanceof WebAssembly.Global, 227 true, 228 "expected a WebAssembly.Global", 229 ); 230 return global; 231 } 232 233 function assert_trap(thunk, message) { 234 try { 235 thunk(); 236 throw new Error(`got no error`); 237 } catch (err) { 238 if (err instanceof WebAssembly.RuntimeError) { 239 return; 240 } 241 err.message = `expected trap (${message}): ${err.message}`; 242 throw err; 243 } 244 } 245 246 let StackOverflow; 247 try { 248 (function f() { 249 1 + f(); 250 })(); 251 } catch (e) { 252 StackOverflow = e.constructor; 253 } 254 function assert_exhaustion(thunk, message) { 255 try { 256 thunk(); 257 throw new Error(`got no error`); 258 } catch (err) { 259 if (err instanceof StackOverflow) { 260 return; 261 } 262 err.message = `expected exhaustion (${message}): ${err.message}`; 263 throw err; 264 } 265 } 266 267 function assert_invalid(thunk, message) { 268 try { 269 thunk(); 270 throw new Error(`got no error`); 271 } catch (err) { 272 if (err instanceof WebAssembly.LinkError || err instanceof WebAssembly.CompileError) { 273 return; 274 } 275 err.message = `expected invalid module (${message}): ${err.message}`; 276 throw err; 277 } 278 } 279 280 function assert_unlinkable(thunk, message) { 281 try { 282 thunk(); 283 throw new Error(`got no error`); 284 } catch (err) { 285 if (err instanceof WebAssembly.LinkError || err instanceof WebAssembly.CompileError) { 286 return; 287 } 288 err.message = `expected an unlinkable module (${message}): ${err.message}`; 289 throw err; 290 } 291 } 292 293 function assert_malformed(thunk, message) { 294 try { 295 thunk(); 296 throw new Error(`got no error`); 297 } catch (err) { 298 if ( 299 err instanceof TypeError || 300 err instanceof SyntaxError || 301 err instanceof WebAssembly.CompileError || 302 err instanceof WebAssembly.LinkError 303 ) { 304 return; 305 } 306 err.message = `expected a malformed module (${message}): ${err.message}`; 307 throw err; 308 } 309 } 310 311 function assert_exception(thunk) { 312 let thrown = false; 313 try { 314 thunk(); 315 } catch (err) { 316 thrown = true; 317 } 318 assertEq(thrown, true, "expected an exception to be thrown"); 319 } 320 321 function assert_return(thunk, expected) { 322 let results = thunk(); 323 324 if (results === undefined) { 325 results = []; 326 } else if (!Array.isArray(results)) { 327 results = [results]; 328 } 329 if (!Array.isArray(expected)) { 330 expected = [expected]; 331 } 332 333 if (!compareResults(results, expected)) { 334 let got = results.map((x) => formatResult(x)).join(", "); 335 let wanted = expected.map((x) => formatExpected(x)).join(", "); 336 assertEq( 337 `[${got}]`, 338 `[${wanted}]`, 339 ); 340 assertEq(true, false, `${got} !== ${wanted}`); 341 } 342 } 343 344 function formatResult(result) { 345 if (typeof (result) === "object") { 346 return wasmGlobalToString(result); 347 } else { 348 return `${result}`; 349 } 350 } 351 352 function formatExpected(expected) { 353 if ( 354 expected === `f32_canonical_nan` || 355 expected === `f32_arithmetic_nan` || 356 expected === `f64_canonical_nan` || 357 expected === `f64_arithmetic_nan` 358 ) { 359 return expected; 360 } else if (expected instanceof F32x4Pattern) { 361 return `f32x4(${formatExpected(expected.x)}, ${ 362 formatExpected(expected.y) 363 }, ${formatExpected(expected.z)}, ${formatExpected(expected.w)})`; 364 } else if (expected instanceof F64x2Pattern) { 365 return `f64x2(${formatExpected(expected.x)}, ${ 366 formatExpected(expected.y) 367 })`; 368 } else if (expected instanceof EitherVariants) { 369 return expected.formatExpected(); 370 } else if (expected instanceof RefWithType) { 371 return expected.formatExpected(); 372 } else if (expected instanceof ExternRefResult) { 373 return expected.formatExpected(); 374 } else if (expected instanceof HostRefResult) { 375 return expected.formatExpected(); 376 } else if (typeof (expected) === "object") { 377 return wasmGlobalToString(expected); 378 } else { 379 throw new Error("unknown expected result"); 380 } 381 } 382 383 class EitherVariants { 384 constructor(arr) { 385 this.arr = arr; 386 } 387 matches(v) { 388 return this.arr.some((e) => compareResult(v, e)); 389 } 390 formatExpected() { 391 return `either(${this.arr.map(formatExpected).join(", ")})`; 392 } 393 } 394 395 function compareResults(results, expected) { 396 if (results.length !== expected.length) { 397 return false; 398 } 399 for (let i in results) { 400 if (expected[i] instanceof EitherVariants) { 401 return expected[i].matches(results[i]); 402 } 403 if (!compareResult(results[i], expected[i])) { 404 return false; 405 } 406 } 407 return true; 408 } 409 410 function compareResult(result, expected) { 411 if ( 412 expected === `canonical_nan` || 413 expected === `arithmetic_nan` 414 ) { 415 return wasmGlobalIsNaN(result, expected); 416 } else if (expected === null) { 417 return result.value === null; 418 } else if (expected instanceof F32x4Pattern) { 419 return compareResult( 420 wasmGlobalExtractLane(result, "f32x4", 0), 421 expected.x, 422 ) && 423 compareResult(wasmGlobalExtractLane(result, "f32x4", 1), expected.y) && 424 compareResult(wasmGlobalExtractLane(result, "f32x4", 2), expected.z) && 425 compareResult(wasmGlobalExtractLane(result, "f32x4", 3), expected.w); 426 } else if (expected instanceof F64x2Pattern) { 427 return compareResult( 428 wasmGlobalExtractLane(result, "f64x2", 0), 429 expected.x, 430 ) && 431 compareResult(wasmGlobalExtractLane(result, "f64x2", 1), expected.y); 432 } else if (expected instanceof RefWithType) { 433 return expected.test(result); 434 } else if (expected instanceof ExternRefResult) { 435 return expected.test(result); 436 } else if (expected instanceof HostRefResult) { 437 return expected.test(result); 438 } else if (typeof (expected) === "object") { 439 return wasmGlobalsEqual(result, expected); 440 } else { 441 throw new Error("unknown expected result"); 442 } 443 } 444 445 class Thread { 446 LOC_STATE = 0; 447 LOC_DID_ERROR = 1; 448 449 STATE_WORKER_READY = 0x60; // "GO" 450 STATE_SENDING_VALUE = 0xF00D; // feed me values 451 STATE_GOT_VALUE = 0x600DF00D; // mm delicious values 452 STATE_RUN_CODE = 0xC0DE; 453 STATE_DONE = 0xDEAD; 454 455 constructor(sharedModule, sharedModuleName, code) { 456 this._coord = new Int32Array(new SharedArrayBuffer(4*2)); 457 458 setSharedObject(this._coord.buffer); 459 evalInWorker(` 460 const _coord = new Int32Array(getSharedObject()); 461 462 ${readRelativeToScript("harness.js")} 463 464 function setState(state) { 465 Atomics.store(_coord, ${this.LOC_STATE}, state); 466 } 467 function waitForState(expected) { 468 while (Atomics.load(_coord, ${this.LOC_STATE}) !== expected) {} 469 } 470 function receive() { 471 waitForState(${this.STATE_SENDING_VALUE}); 472 const x = getSharedObject(); 473 setState(${this.STATE_GOT_VALUE}); 474 return x; 475 } 476 477 // Tell main thread we are ready 478 setState(${this.STATE_WORKER_READY}); 479 480 // Get shared module's exports from main thread. (We do this one at a 481 // time for reasons explained below.) 482 const ${sharedModuleName} = {}; 483 ${Object.keys(sharedModule).map(name => 484 `${sharedModuleName}["${name}"] = receive();` 485 )} 486 waitForState(${this.STATE_RUN_CODE}); 487 488 try { 489 ${code} 490 } catch (e) { 491 Atomics.store(_coord, ${this.LOC_DID_ERROR}, 1); 492 throw e; 493 } finally { 494 setState(${this.STATE_DONE}); 495 } 496 `); 497 498 // Wait for worker to spawn 499 this.waitForState(this.STATE_WORKER_READY); 500 501 // Send shared module exports to worker. We send values one at a time 502 // because setGlobalObject can only take very specific objects, like wasm 503 // memories, not generic objects like the whole exports object. 504 for (const exportedValue of Object.values(sharedModule)) { 505 this.send(exportedValue); 506 } 507 508 // Give the worker the all-clear to execute its workload 509 this.setState(this.STATE_RUN_CODE); 510 } 511 512 setState(state) { 513 Atomics.store(this._coord, this.LOC_STATE, state); 514 } 515 516 waitForState(expected) { 517 while (Atomics.load(this._coord, this.LOC_STATE) !== expected) {} 518 } 519 520 send(val) { 521 setSharedObject(val); 522 this.setState(this.STATE_SENDING_VALUE); 523 this.waitForState(this.STATE_GOT_VALUE); 524 } 525 526 wait() { 527 this.waitForState(this.STATE_DONE); 528 if (Atomics.load(this._coord, this.LOC_DID_ERROR)) { 529 throw new Error("Error in worker code. Note that line numbers will not be helpful because of how the harness is loaded."); 530 } 531 } 532 }