harness.js (14292B)
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 === `canonical_nan` || 355 expected === `arithmetic_nan` 356 ) { 357 return expected; 358 } else if (expected instanceof F32x4Pattern) { 359 return `f32x4(${formatExpected(expected.x)}, ${ 360 formatExpected(expected.y) 361 }, ${formatExpected(expected.z)}, ${formatExpected(expected.w)})`; 362 } else if (expected instanceof F64x2Pattern) { 363 return `f64x2(${formatExpected(expected.x)}, ${ 364 formatExpected(expected.y) 365 })`; 366 } else if (expected instanceof EitherVariants) { 367 return expected.formatExpected(); 368 } else if (expected instanceof RefWithType) { 369 return expected.formatExpected(); 370 } else if (expected instanceof ExternRefResult) { 371 return expected.formatExpected(); 372 } else if (expected instanceof HostRefResult) { 373 return expected.formatExpected(); 374 } else if (typeof (expected) === "object") { 375 return wasmGlobalToString(expected); 376 } else { 377 throw new Error("unknown expected result"); 378 } 379 } 380 381 class EitherVariants { 382 constructor(arr) { 383 this.arr = arr; 384 } 385 matches(v) { 386 return this.arr.some((e) => compareResult(v, e)); 387 } 388 formatExpected() { 389 return `either(${this.arr.map(formatExpected).join(", ")})`; 390 } 391 } 392 393 function compareResults(results, expected) { 394 if (results.length !== expected.length) { 395 return false; 396 } 397 for (let i in results) { 398 if (expected[i] instanceof EitherVariants) { 399 return expected[i].matches(results[i]); 400 } 401 if (!compareResult(results[i], expected[i])) { 402 return false; 403 } 404 } 405 return true; 406 } 407 408 function compareResult(result, expected) { 409 if ( 410 expected === `canonical_nan` || 411 expected === `arithmetic_nan` 412 ) { 413 return wasmGlobalIsNaN(result, expected); 414 } else if (expected === null) { 415 return result.value === null; 416 } else if (expected instanceof F32x4Pattern) { 417 return compareResult( 418 wasmGlobalExtractLane(result, "f32x4", 0), 419 expected.x, 420 ) && 421 compareResult(wasmGlobalExtractLane(result, "f32x4", 1), expected.y) && 422 compareResult(wasmGlobalExtractLane(result, "f32x4", 2), expected.z) && 423 compareResult(wasmGlobalExtractLane(result, "f32x4", 3), expected.w); 424 } else if (expected instanceof F64x2Pattern) { 425 return compareResult( 426 wasmGlobalExtractLane(result, "f64x2", 0), 427 expected.x, 428 ) && 429 compareResult(wasmGlobalExtractLane(result, "f64x2", 1), expected.y); 430 } else if (expected instanceof RefWithType) { 431 return expected.test(result); 432 } else if (expected instanceof ExternRefResult) { 433 return expected.test(result); 434 } else if (expected instanceof HostRefResult) { 435 return expected.test(result); 436 } else if (typeof (expected) === "object") { 437 return wasmGlobalsEqual(result, expected); 438 } else { 439 throw new Error("unknown expected result"); 440 } 441 } 442 443 class Thread { 444 LOC_STATE = 0; 445 LOC_DID_ERROR = 1; 446 447 STATE_WORKER_READY = 0x60; // "GO" 448 STATE_SENDING_VALUE = 0xF00D; // feed me values 449 STATE_GOT_VALUE = 0x600DF00D; // mm delicious values 450 STATE_RUN_CODE = 0xC0DE; 451 STATE_DONE = 0xDEAD; 452 453 constructor(sharedModule, sharedModuleName, code) { 454 this._coord = new Int32Array(new SharedArrayBuffer(4*2)); 455 456 setSharedObject(this._coord.buffer); 457 evalInWorker(` 458 const _coord = new Int32Array(getSharedObject()); 459 460 ${readRelativeToScript("harness.js")} 461 462 function setState(state) { 463 Atomics.store(_coord, ${this.LOC_STATE}, state); 464 } 465 function waitForState(expected) { 466 while (Atomics.load(_coord, ${this.LOC_STATE}) !== expected) {} 467 } 468 function receive() { 469 waitForState(${this.STATE_SENDING_VALUE}); 470 const x = getSharedObject(); 471 setState(${this.STATE_GOT_VALUE}); 472 return x; 473 } 474 475 // Tell main thread we are ready 476 setState(${this.STATE_WORKER_READY}); 477 478 // Get shared module's exports from main thread. (We do this one at a 479 // time for reasons explained below.) 480 const ${sharedModuleName} = {}; 481 ${Object.keys(sharedModule).map(name => 482 `${sharedModuleName}["${name}"] = receive();` 483 )} 484 waitForState(${this.STATE_RUN_CODE}); 485 486 try { 487 ${code} 488 } catch (e) { 489 Atomics.store(_coord, ${this.LOC_DID_ERROR}, 1); 490 throw e; 491 } finally { 492 setState(${this.STATE_DONE}); 493 } 494 `); 495 496 // Wait for worker to spawn 497 this.waitForState(this.STATE_WORKER_READY); 498 499 // Send shared module exports to worker. We send values one at a time 500 // because setGlobalObject can only take very specific objects, like wasm 501 // memories, not generic objects like the whole exports object. 502 for (const exportedValue of Object.values(sharedModule)) { 503 this.send(exportedValue); 504 } 505 506 // Give the worker the all-clear to execute its workload 507 this.setState(this.STATE_RUN_CODE); 508 } 509 510 setState(state) { 511 Atomics.store(this._coord, this.LOC_STATE, state); 512 } 513 514 waitForState(expected) { 515 while (Atomics.load(this._coord, this.LOC_STATE) !== expected) {} 516 } 517 518 send(val) { 519 setSharedObject(val); 520 this.setState(this.STATE_SENDING_VALUE); 521 this.waitForState(this.STATE_GOT_VALUE); 522 } 523 524 wait() { 525 this.waitForState(this.STATE_DONE); 526 if (Atomics.load(this._coord, this.LOC_DID_ERROR)) { 527 throw new Error("Error in worker code. Note that line numbers will not be helpful because of how the harness is loaded."); 528 } 529 } 530 }