spec.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. // This file has been modified by Microsoft on [12/2016].
  6. /*
  7. * Copyright 2016 WebAssembly Community Group participants
  8. *
  9. * Licensed under the Apache License, Version 2.0 (the "License");
  10. * you may not use this file except in compliance with the License.
  11. * You may obtain a copy of the License at
  12. *
  13. * http://www.apache.org/licenses/LICENSE-2.0
  14. *
  15. * Unless required by applicable law or agreed to in writing, software
  16. * distributed under the License is distributed on an "AS IS" BASIS,
  17. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. * See the License for the specific language governing permissions and
  19. * limitations under the License.
  20. */
  21. const cliArgs = WScript.Arguments || [];
  22. WScript.Flag("-wasmI64");
  23. if (cliArgs.length < 1) {
  24. print("usage: <exe> spec.js -args <filename.json> [start index] [end index] [-verbose] [-nt] -endargs");
  25. WScript.Quit(0);
  26. }
  27. if (typeof IMPORTS_FROM_OTHER_SCRIPT === "undefined") {
  28. IMPORTS_FROM_OTHER_SCRIPT = {};
  29. }
  30. let passed = 0;
  31. let failed = 0;
  32. // Parse arguments
  33. const iVerbose = cliArgs.indexOf("-verbose");
  34. const verbose = iVerbose !== -1;
  35. if (verbose) {
  36. cliArgs.splice(iVerbose, 1);
  37. }
  38. const iNoTrap = cliArgs.indexOf("-nt");
  39. const noTrap = iNoTrap !== -1;
  40. const trap = !noTrap;
  41. if (noTrap) {
  42. cliArgs.splice(iNoTrap, 1);
  43. }
  44. let file = "";
  45. let iTest = 0;
  46. function getValueStr(value, type) {
  47. if (typeof value === "undefined") {
  48. return "undefined";
  49. }
  50. switch (type) {
  51. case "i64":
  52. if (typeof value === "object") {
  53. const {high, low} = value;
  54. const convert = a => (a >>> 0).toString(16);
  55. return `0x${convert(high)}${convert(low).padStart(8, "0")}`;
  56. }
  57. // Fallthrough
  58. case "i32": return value.toString();
  59. case "f64":
  60. // Fallthrough
  61. case "f32":
  62. if (Object.is(value, -0)) {
  63. return "-0";
  64. }
  65. // Fallthrough
  66. }
  67. // Default case
  68. return value.toString();
  69. }
  70. function getArgsStr(args) {
  71. return args
  72. .map(({type, value}) => `${type}:${getValueStr(mapWasmArg({type, value}), type)}`)
  73. .join(", ");
  74. }
  75. function getActionStr(action) {
  76. const moduleName = action.module || "$$";
  77. switch (action.type) {
  78. case "invoke": return `${moduleName}.${action.field}(${getArgsStr(action.args)})`;
  79. case "get": return `${moduleName}[${action.field}]`;
  80. default: return "Unkown action type";
  81. }
  82. }
  83. function getCommandStr(command) {
  84. const base = `(${iTest}) ${file}:${command.line}`;
  85. switch (command.type) {
  86. case "module": return `${base}: generate module ${command.name ? ` as ${command.name}` : ""}`;
  87. case "register": return `${base}: register module ${command.name || "$$"} as ${command.as}`;
  88. case "assert_malformed":
  89. case "assert_unlinkable":
  90. case "assert_uninstantiable":
  91. case "assert_invalid": return `${base}: ${command.type} module`;
  92. case "assert_return": return `${base}: assert_return(${getActionStr(command.action)} == ${getArgsStr(command.expected)})`;
  93. case "assert_return_canonical_nan": return `${base}: assert_return_canonical_nan(${getActionStr(command.action)}`;
  94. case "assert_return_arithmetic_nan": return `${base}: assert_return_arithmetic_nan(${getActionStr(command.action)})`;
  95. case "action":
  96. case "assert_trap":
  97. case "assert_return_nan":
  98. case "assert_exhaustion":
  99. return `${base}: ${command.type}(${getActionStr(command.action)})`;
  100. }
  101. return base;
  102. }
  103. const CompareType = {
  104. none: Symbol(),
  105. exact: Symbol(),
  106. arithmeticNan: Symbol(),
  107. canonicalNan: Symbol()
  108. };
  109. function run(inPath, iStart, iEnd) {
  110. const lastSlash = Math.max(inPath.lastIndexOf("/"), inPath.lastIndexOf("\\"));
  111. const inDir = lastSlash === -1 ? "." : inPath.slice(0, lastSlash);
  112. file = inPath;
  113. const data = read(inPath);
  114. const {commands} = WebAssembly.wabt.convertWast2Wasm(data, {spec: true});
  115. const registry = Object.assign({
  116. "not wasm": {
  117. overload() {}
  118. },
  119. spectest: {
  120. print,
  121. print_i32: console.log.bind(console),
  122. print_i32_f32: console.log.bind(console),
  123. print_f64_f64: console.log.bind(console),
  124. print_f32: console.log.bind(console),
  125. print_f64: console.log.bind(console),
  126. global_i32: 666,
  127. global_f32: 666,
  128. global_f64: 666,
  129. table: new WebAssembly.Table({initial: 10, maximum: 20, element: "anyfunc"}),
  130. memory: new WebAssembly.Memory({initial: 1, maximum: 2})
  131. }
  132. }, IMPORTS_FROM_OTHER_SCRIPT);
  133. const moduleRegistry = {};
  134. moduleRegistry.currentModule = null;
  135. for (const command of commands) {
  136. ++iTest;
  137. if (iTest < iStart) {
  138. // always run module/register commands that happens before iStart
  139. if (command.type !== "module" && command.type !== "register") {
  140. continue;
  141. }
  142. } else if (iTest > iEnd) {
  143. continue;
  144. }
  145. if (verbose) {
  146. print(`Running:${getCommandStr(command)}`);
  147. }
  148. switch (command.type) {
  149. case "module":
  150. moduleCommand(inDir, command, registry, moduleRegistry);
  151. break;
  152. case "assert_malformed":
  153. assertMalformed(inDir, command);
  154. break;
  155. case "assert_unlinkable":
  156. assertUnlinkable(inDir, command, registry);
  157. break;
  158. case "assert_uninstantiable":
  159. assertUninstantiable(inDir, command, registry);
  160. break;
  161. case "assert_invalid":
  162. assertMalformed(inDir, command, registry);
  163. break;
  164. case "register": {
  165. const {as, name = "currentModule"} = command;
  166. if (moduleRegistry[name]) {
  167. registry[as] = moduleRegistry[name].exports;
  168. }
  169. break;
  170. }
  171. case "action":
  172. runSimpleAction(moduleRegistry, command);
  173. break;
  174. case "assert_return":
  175. assertReturn(moduleRegistry, command, CompareType.exact);
  176. break;
  177. case "assert_return_canonical_nan":
  178. assertReturn(moduleRegistry, command, CompareType.canonicalNan);
  179. break;
  180. case "assert_return_arithmetic_nan":
  181. assertReturn(moduleRegistry, command, CompareType.arithmeticNan);
  182. break;
  183. case "assert_exhaustion":
  184. assertStackExhaustion(moduleRegistry, command);
  185. break;
  186. case "assert_trap":
  187. if (trap) {
  188. assertTrap(moduleRegistry, command);
  189. } else {
  190. print(`${getCommandStr(command)} skipped because it has traps`);
  191. }
  192. break;
  193. default:
  194. print(`Unknown command: ${JSON.stringify(command)}`);
  195. }
  196. }
  197. end();
  198. }
  199. function createModule(baseDir, buffer, registry, output) {
  200. if (verbose) {
  201. const u8a = new Uint8Array(buffer);
  202. console.log(u8a);
  203. }
  204. output.module = new WebAssembly.Module(buffer);
  205. // We'll know if an error occurs at instanciation because output.module will be set
  206. output.instance = new WebAssembly.Instance(output.module, registry || undefined);
  207. }
  208. function moduleCommand(baseDir, command, registry, moduleRegistry) {
  209. const {buffer, name} = command;
  210. try {
  211. const output = {};
  212. createModule(baseDir, buffer, registry, output);
  213. if (name) {
  214. moduleRegistry[name] = output.instance;
  215. }
  216. moduleRegistry.currentModule = output.instance;
  217. ++passed;
  218. } catch (e) {
  219. ++failed;
  220. print(`${getCommandStr(command)} failed. Unexpected Error: ${e}`);
  221. }
  222. return null;
  223. }
  224. function assertMalformed(baseDir, command) {
  225. const {buffer, text} = command;
  226. // Test hook to prevent deferred parsing
  227. WScript.Flag("-off:wasmdeferred");
  228. const output = {};
  229. try {
  230. createModule(baseDir, buffer, null, output);
  231. ++failed;
  232. print(`${getCommandStr(command)} failed. Should have had an error`);
  233. } catch (e) {
  234. if (output.module) {
  235. ++failed;
  236. print(`${getCommandStr(command)} failed. Had a linking error, expected a compile error: ${e}`);
  237. } else if (e instanceof WebAssembly.CompileError) {
  238. ++passed;
  239. if (verbose) {
  240. print(`${getCommandStr(command)} passed. Had compile error: ${e}`);
  241. print(` Spec expected error: ${text}`);
  242. }
  243. } else {
  244. ++failed;
  245. print(`${getCommandStr(command)} failed. Expected a compile error: ${e}`);
  246. }
  247. } finally {
  248. // Reset the test hook
  249. WScript.Flag("-on:wasmdeferred");
  250. }
  251. }
  252. function assertUnlinkable(baseDir, command, registry) {
  253. const {buffer, text} = command;
  254. // Test hook to prevent deferred parsing
  255. WScript.Flag("-off:wasmdeferred");
  256. const output = {};
  257. try {
  258. createModule(baseDir, buffer, registry, output);
  259. ++failed;
  260. print(`${getCommandStr(command)} failed. Should have had an error`);
  261. } catch (e) {
  262. if (e instanceof WebAssembly.LinkError) {
  263. ++passed;
  264. if (verbose) {
  265. print(`${getCommandStr(command)} passed. Had linking error: ${e}`);
  266. print(` Spec expected error: ${text}`);
  267. }
  268. } else {
  269. ++failed;
  270. print(`${getCommandStr(command)} failed. Expected a linking error: ${e}`);
  271. }
  272. } finally {
  273. // Reset the test hook
  274. WScript.Flag("-on:wasmdeferred");
  275. }
  276. }
  277. function assertUninstantiable(baseDir, command, registry) {
  278. const {buffer, text} = command;
  279. // Test hook to prevent deferred parsing
  280. WScript.Flag("-off:wasmdeferred");
  281. const output = {};
  282. try {
  283. createModule(baseDir, buffer, registry, output);
  284. ++failed;
  285. print(`${getCommandStr(command)} failed. Should have had an error`);
  286. } catch (e) {
  287. if (e instanceof WebAssembly.RuntimeError) {
  288. ++passed;
  289. if (verbose) {
  290. print(`${getCommandStr(command)} passed. Had instanciation error: ${e}`);
  291. print(` Spec expected error: ${text}`);
  292. }
  293. } else {
  294. ++failed;
  295. print(`${getCommandStr(command)} failed. Expected a instanciation error: ${e}`);
  296. }
  297. } finally {
  298. // Reset the test hook
  299. WScript.Flag("-on:wasmdeferred");
  300. }
  301. }
  302. function genConverters() {
  303. const buffer = WebAssembly.wabt.convertWast2Wasm(`
  304. (module
  305. (func (export "convertI64") (param i64) (result i64) (get_local 0))
  306. (func (export "toF32") (param i32) (result f32) (f32.reinterpret/i32 (get_local 0)))
  307. (func (export "toF64") (param i64) (result f64) (f64.reinterpret/i64 (get_local 0)))
  308. )`);
  309. const module = new WebAssembly.Module(buffer);
  310. const instance = new WebAssembly.Instance(module);
  311. return instance.exports;
  312. }
  313. const {toF32, toF64, convertI64} = genConverters();
  314. function mapWasmArg({type, value}) {
  315. switch (type) {
  316. case "i32": return parseInt(value)|0;
  317. case "i64": return convertI64(value); // will convert string to {high, low}
  318. case "f32": return toF32(parseInt(value));
  319. case "f64": return toF64(value);
  320. }
  321. throw new Error("Unknown argument type");
  322. }
  323. function runCompare(wasmFn, action, expected, compareType) {
  324. let moduleTxt;
  325. const argTypes = action.args.map(({type}) => type);
  326. const params = argTypes.length > 0 ? `(param ${argTypes.join(" ")})` : "";
  327. let returnType = "void";
  328. if (expected.length === 0) {
  329. if (compareType !== CompareType.exact) {
  330. throw new Error("Must have expected type to runCompare if not doing Exact comparison");
  331. }
  332. // Without expected result, we are just making sure we are able to call the function
  333. moduleTxt = `
  334. (module
  335. (import "test" "fn" (func $fn ${params}))
  336. (func (export "res"))
  337. (func (export "compare") (result i32)
  338. ${action.args.map(({type, value}) => {
  339. switch (type) {
  340. case "i32": return `(i32.const ${value})`;
  341. case "i64": return `(i64.const ${value})`;
  342. case "f32": return `(f32.reinterpret/i32 (i32.const ${value}))`;
  343. case "f64": return `(f64.reinterpret/i64 (i64.const ${value}))`;
  344. }
  345. throw new Error("Unknown argument type");
  346. }).join("\n")}
  347. (call $fn)
  348. (i32.const 1)
  349. )
  350. )`;
  351. } else {
  352. let compareTxt = "";
  353. const {type: expectedType, value: expectedValue} = expected[0];
  354. returnType = expectedType;
  355. const resultSize = expectedType.endsWith("32") ? 32 : 64;
  356. const matchingIntType = resultSize === 32 ? "i32" : "i64";
  357. switch (compareType) {
  358. case CompareType.exact:
  359. switch (expectedType) {
  360. case "i32":
  361. case "i64":
  362. compareTxt += `(${expectedType}.eq (${expectedType}.const ${expectedValue}))`;
  363. break;
  364. case "f32":
  365. compareTxt += `(i32.reinterpret/f32) (i32.eq (i32.const ${expectedValue}))`;
  366. break;
  367. case "f64":
  368. compareTxt += `(i64.reinterpret/f64) (i64.eq (i64.const ${expectedValue}))`;
  369. break;
  370. }
  371. break;
  372. case CompareType.arithmeticNan: {
  373. const expectedResult = resultSize === 32 ? "0x7f800000" : "0x7ff0000000000000";
  374. compareTxt = `
  375. (${matchingIntType}.reinterpret/${expectedType})
  376. (${matchingIntType}.and (${matchingIntType}.const ${expectedResult}))
  377. (${matchingIntType}.eq (${matchingIntType}.const ${expectedResult}))`;
  378. break;
  379. }
  380. case CompareType.canonicalNan: {
  381. const posNaN = resultSize === 32 ? "0x7fc00000" : "0x7ff8000000000000";
  382. const negNaN = resultSize === 32 ? "0xffc00000" : "0xfff8000000000000";
  383. compareTxt = `
  384. (${matchingIntType}.reinterpret/${expectedType})
  385. (tee_local $intNan)
  386. (${matchingIntType}.eq (${matchingIntType}.const ${posNaN}))
  387. (get_local $intNan)
  388. (${matchingIntType}.eq (${matchingIntType}.const ${negNaN}))
  389. (i32.or)`;
  390. break;
  391. }
  392. default:
  393. throw new Error("runCompare: Unsupported compare type");
  394. }
  395. moduleTxt = `
  396. (module
  397. (import "test" "fn" (func $fn ${params} (result ${expectedType})))
  398. (global $res (mut ${expectedType}) (${expectedType}.const 0))
  399. (func (export "res") (result ${expectedType}) (get_global $res))
  400. (func (export "compare") (result i32)
  401. (local $localRes ${expectedType}) (local $intNan ${matchingIntType})
  402. ${action.args.map(({type, value}) => {
  403. switch (type) {
  404. case "i32": return `(i32.const ${value})`;
  405. case "i64": return `(i64.const ${value})`;
  406. case "f32": return `(f32.reinterpret/i32 (i32.const ${value}))`;
  407. case "f64": return `(f64.reinterpret/i64 (i64.const ${value}))`;
  408. }
  409. throw new Error("Unknown argument type");
  410. }).join("\n")}
  411. (call $fn)
  412. (tee_local $localRes)
  413. (set_global $res)
  414. (get_local $localRes)
  415. ${compareTxt}
  416. )
  417. )`;
  418. }
  419. if (verbose) {
  420. console.log(moduleTxt);
  421. }
  422. const module = new WebAssembly.Module(WebAssembly.wabt.convertWast2Wasm(moduleTxt));
  423. const {exports: {compare, res}} = new WebAssembly.Instance(module, {test: {fn: wasmFn}});
  424. const hasPassed = compare() === 1;
  425. const original = res();
  426. return {passed: hasPassed, original: {value: original, type: returnType}};
  427. }
  428. function assertReturn(moduleRegistry, command, compareType) {
  429. const {action, expected} = command;
  430. try {
  431. let success = false;
  432. let originalResult;
  433. if (action.type === "invoke") {
  434. const wasmFn = getField(moduleRegistry, action);
  435. const res = runCompare(wasmFn, action, expected, compareType);
  436. originalResult = res.original;
  437. success = res.passed;
  438. } else if (action.type === "get") {
  439. const field = getField(moduleRegistry, action);
  440. originalResult = {value: field, type: expected[0].type};
  441. switch (compareType) {
  442. case CompareType.exact: success = field === mapWasmArg(expected[0]); break;
  443. case CompareType.arithmeticNan:
  444. case CompareType.canonicalNan: success = isNaN(field); break;
  445. default:
  446. throw new Error("assertReturn: Unsupported compare type");
  447. }
  448. }
  449. if (success) {
  450. passed++;
  451. if (verbose) {
  452. print(`${getCommandStr(command)} passed.`);
  453. }
  454. } else {
  455. print(`${getCommandStr(command)} failed. Returned ${getValueStr(originalResult.value, originalResult.type)}`);
  456. failed++;
  457. }
  458. } catch (e) {
  459. print(`${getCommandStr(command)} failed. Unexpectedly threw: ${e}`);
  460. failed++;
  461. }
  462. }
  463. function assertTrap(moduleRegistry, command) {
  464. const {action, text} = command;
  465. try {
  466. runAction(moduleRegistry, action);
  467. print(`${getCommandStr(command)} failed. Should have had an error`);
  468. failed++;
  469. } catch (e) {
  470. if (e instanceof WebAssembly.RuntimeError) {
  471. passed++;
  472. if (verbose) {
  473. print(`${getCommandStr(command)} passed. Error thrown: ${e}`);
  474. print(` Spec error message expected: ${text}`);
  475. }
  476. } else {
  477. failed++;
  478. print(`${getCommandStr(command)} failed. Unexpected error thrown: ${e}`);
  479. }
  480. }
  481. }
  482. let StackOverflow;
  483. function assertStackExhaustion(moduleRegistry, command) {
  484. if (!StackOverflow) {
  485. // Get the stack overflow exception type
  486. // Javascript and WebAssembly must throw the same type of error
  487. try { (function f() { 1 + f() })() } catch (e) { StackOverflow = e.constructor }
  488. }
  489. const {action} = command;
  490. try {
  491. runAction(moduleRegistry, action);
  492. print(`${getCommandStr(command)} failed. Should have exhausted the stack`);
  493. failed++;
  494. } catch (e) {
  495. if (e instanceof StackOverflow) {
  496. passed++;
  497. if (verbose) {
  498. print(`${getCommandStr(command)} passed. Had a StackOverflow`);
  499. }
  500. } else {
  501. failed++;
  502. print(`${getCommandStr(command)} failed. Unexpected error thrown: ${e}`);
  503. }
  504. }
  505. }
  506. function runSimpleAction(moduleRegistry, command) {
  507. try {
  508. const res = runAction(moduleRegistry, command.action);
  509. ++passed;
  510. if (verbose) {
  511. print(`${getCommandStr(command)} = ${res}`);
  512. }
  513. } catch (e) {
  514. ++failed;
  515. print(`${getCommandStr(command)} failed. Unexpectedly threw: ${e}`);
  516. }
  517. }
  518. function getField(moduleRegistry, action) {
  519. const moduleName = action.module !== undefined ? action.module : "currentModule";
  520. const m = moduleRegistry[moduleName];
  521. if (!m) {
  522. throw new Error("Module unavailable to run action");
  523. }
  524. const {field} = action;
  525. if (!(field in m.exports)) {
  526. throw new Error(`Unable to find field ${field} in ${moduleName}`)
  527. }
  528. return m.exports[field];
  529. }
  530. function runAction(moduleRegistry, action) {
  531. const field = getField(moduleRegistry, action);
  532. switch (action.type) {
  533. case "invoke": {
  534. const {args} = action;
  535. const mappedArgs = args.map(({value}) => value);
  536. const wasmFn = field;
  537. const res = wasmFn(...mappedArgs);
  538. return res;
  539. }
  540. case "get": {
  541. return field;
  542. }
  543. default:
  544. print(`Unknown action: ${JSON.stringify(action)}`);
  545. }
  546. }
  547. function end() {
  548. print(`${passed}/${passed + failed} tests passed.`);
  549. }
  550. run(cliArgs[0], cliArgs[1] | 0, cliArgs[2] === undefined ? undefined : cliArgs[2] | 0);