spec.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  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) {
  47. if (typeof value === "object") {
  48. const {high, low} = value;
  49. const convert = a => (a >>> 0).toString(16).padStart(8, "0");
  50. return `0x${convert(high)}${convert(low)}`;
  51. }
  52. return "" + value;
  53. }
  54. function getArgsStr(args) {
  55. return args
  56. .map(({type, value}) => `${type}:${getValueStr(mapWasmArg({type, value}))}`)
  57. .join(", ");
  58. }
  59. function getActionStr(action) {
  60. const moduleName = action.module || "$$";
  61. switch (action.type) {
  62. case "invoke": return `${moduleName}.${action.field}(${getArgsStr(action.args)})`;
  63. case "get": return `${moduleName}[${action.field}]`;
  64. default: return "Unkown action type";
  65. }
  66. }
  67. function getCommandStr(command) {
  68. const base = `(${iTest}) ${file}:${command.line}`;
  69. switch (command.type) {
  70. case "module": return `${base}: generate module ${command.name ? ` as ${command.name}` : ""}`;
  71. case "register": return `${base}: register module ${command.name || "$$"} as ${command.as}`;
  72. case "assert_malformed":
  73. case "assert_unlinkable":
  74. case "assert_uninstantiable":
  75. case "assert_invalid": return `${base}: ${command.type} module`;
  76. case "assert_return": return `${base}: assert_return(${getActionStr(command.action)} == ${getArgsStr(command.expected)})`;
  77. case "action":
  78. case "assert_trap":
  79. case "assert_return_nan":
  80. case "assert_exhaustion":
  81. return `${base}: ${command.type}(${getActionStr(command.action)})`;
  82. }
  83. return base;
  84. }
  85. function run(inPath, iStart, iEnd) {
  86. const lastSlash = Math.max(inPath.lastIndexOf("/"), inPath.lastIndexOf("\\"));
  87. const inDir = lastSlash === -1 ? "." : inPath.slice(0, lastSlash);
  88. file = inPath;
  89. const data = read(inPath);
  90. const {commands} = WebAssembly.wabt.convertWast2Wasm(data, {spec: true});
  91. const registry = Object.assign({spectest: {
  92. print,
  93. global: 666,
  94. table: new WebAssembly.Table({initial: 10, maximum: 20, element: "anyfunc"}),
  95. memory: new WebAssembly.Memory({initial: 1, maximum: 2})
  96. }}, IMPORTS_FROM_OTHER_SCRIPT);
  97. const moduleRegistry = {};
  98. moduleRegistry.currentModule = null;
  99. for (const command of commands) {
  100. ++iTest;
  101. if (iTest < iStart) {
  102. // always run module/register commands that happens before iStart
  103. if (command.type !== "module" && command.type !== "register") {
  104. continue;
  105. }
  106. } else if (iTest > iEnd) {
  107. continue;
  108. }
  109. if (verbose) {
  110. print(`Running:${getCommandStr(command)}`);
  111. }
  112. switch (command.type) {
  113. case "module":
  114. moduleCommand(inDir, command, registry, moduleRegistry);
  115. break;
  116. case "assert_malformed":
  117. assertMalformed(inDir, command);
  118. break;
  119. case "assert_unlinkable":
  120. assertUnlinkable(inDir, command, registry);
  121. break;
  122. case "assert_uninstantiable":
  123. assertUninstantiable(inDir, command, registry);
  124. break;
  125. case "assert_invalid":
  126. assertMalformed(inDir, command, registry);
  127. break;
  128. case "register": {
  129. const {as, name = "currentModule"} = command;
  130. registry[as] = moduleRegistry[name].exports;
  131. break;
  132. }
  133. case "action":
  134. runSimpleAction(moduleRegistry, command);
  135. break;
  136. case "assert_return":
  137. assertReturn(moduleRegistry, command);
  138. break;
  139. case "assert_return_canonical_nan":
  140. assertReturn(moduleRegistry, command, {canonicalNan: true});
  141. break;
  142. case "assert_return_arithmetic_nan":
  143. assertReturn(moduleRegistry, command, {arithmeticNan: true});
  144. break;
  145. case "assert_exhaustion":
  146. assertStackExhaustion(moduleRegistry, command);
  147. break;
  148. case "assert_trap":
  149. if (trap) {
  150. assertTrap(moduleRegistry, command);
  151. } else {
  152. print(`${getCommandStr(command)} skipped because it has traps`);
  153. }
  154. break;
  155. default:
  156. print(`Unknown command: ${JSON.stringify(command)}`);
  157. }
  158. }
  159. end();
  160. }
  161. function createModule(baseDir, buffer, registry, output) {
  162. if (verbose) {
  163. const u8a = new Uint8Array(buffer);
  164. console.log(u8a);
  165. }
  166. output.module = new WebAssembly.Module(buffer);
  167. // We'll know if an error occurs at instanciation because output.module will be set
  168. output.instance = new WebAssembly.Instance(output.module, registry);
  169. }
  170. function moduleCommand(baseDir, command, registry, moduleRegistry) {
  171. const {buffer, name} = command;
  172. try {
  173. const output = {};
  174. createModule(baseDir, buffer, registry, output);
  175. if (name) {
  176. moduleRegistry[name] = output.instance;
  177. }
  178. moduleRegistry.currentModule = output.instance;
  179. ++passed;
  180. } catch (e) {
  181. ++failed;
  182. print(`${getCommandStr(command)} failed. Unexpected Error: ${e}`);
  183. }
  184. return null;
  185. }
  186. function assertMalformed(baseDir, command) {
  187. const {buffer, text} = command;
  188. // Test hook to prevent deferred parsing
  189. WScript.Flag("-off:wasmdeferred");
  190. const output = {};
  191. try {
  192. createModule(baseDir, buffer, null, output);
  193. ++failed;
  194. print(`${getCommandStr(command)} failed. Should have had an error`);
  195. } catch (e) {
  196. if (output.module) {
  197. ++failed;
  198. print(`${getCommandStr(command)} failed. Had a linking error, expected a compile error: ${e}`);
  199. } else if (e instanceof WebAssembly.CompileError) {
  200. ++passed;
  201. if (verbose) {
  202. print(`${getCommandStr(command)} passed. Had compile error: ${e}`);
  203. print(` Spec expected error: ${text}`);
  204. }
  205. } else {
  206. ++failed;
  207. print(`${getCommandStr(command)} failed. Expected a compile error: ${e}`);
  208. }
  209. } finally {
  210. // Reset the test hook
  211. WScript.Flag("-on:wasmdeferred");
  212. }
  213. }
  214. function assertUnlinkable(baseDir, command, registry) {
  215. const {buffer, text} = command;
  216. // Test hook to prevent deferred parsing
  217. WScript.Flag("-off:wasmdeferred");
  218. const output = {};
  219. try {
  220. createModule(baseDir, buffer, registry, output);
  221. ++failed;
  222. print(`${getCommandStr(command)} failed. Should have had an error`);
  223. } catch (e) {
  224. if (e instanceof WebAssembly.LinkError) {
  225. ++passed;
  226. if (verbose) {
  227. print(`${getCommandStr(command)} passed. Had linking error: ${e}`);
  228. print(` Spec expected error: ${text}`);
  229. }
  230. } else {
  231. ++failed;
  232. print(`${getCommandStr(command)} failed. Expected a linking error: ${e}`);
  233. }
  234. } finally {
  235. // Reset the test hook
  236. WScript.Flag("-on:wasmdeferred");
  237. }
  238. }
  239. function assertUninstantiable(baseDir, command, registry) {
  240. const {buffer, text} = command;
  241. // Test hook to prevent deferred parsing
  242. WScript.Flag("-off:wasmdeferred");
  243. const output = {};
  244. try {
  245. createModule(baseDir, buffer, registry, output);
  246. ++failed;
  247. print(`${getCommandStr(command)} failed. Should have had an error`);
  248. } catch (e) {
  249. if (e instanceof WebAssembly.RuntimeError) {
  250. ++passed;
  251. if (verbose) {
  252. print(`${getCommandStr(command)} passed. Had instanciation error: ${e}`);
  253. print(` Spec expected error: ${text}`);
  254. }
  255. } else {
  256. ++failed;
  257. print(`${getCommandStr(command)} failed. Expected a instanciation error: ${e}`);
  258. }
  259. } finally {
  260. // Reset the test hook
  261. WScript.Flag("-on:wasmdeferred");
  262. }
  263. }
  264. function genConverters() {
  265. const buffer = WebAssembly.wabt.convertWast2Wasm(`
  266. (module
  267. (func (export "convertI64") (param i64) (result i64) (get_local 0))
  268. (func (export "toF32") (param i32) (result f32) (f32.reinterpret/i32 (get_local 0)))
  269. (func (export "toF64") (param i64) (result f64) (f64.reinterpret/i64 (get_local 0)))
  270. )`);
  271. const module = new WebAssembly.Module(buffer);
  272. const instance = new WebAssembly.Instance(module);
  273. return instance.exports;
  274. }
  275. const {toF32, toF64, convertI64} = genConverters();
  276. function mapWasmArg({type, value}) {
  277. switch (type) {
  278. case "i32": return parseInt(value)|0;
  279. case "i64": return convertI64(value); // will convert string to {high, low}
  280. case "f32": return toF32(parseInt(value));
  281. case "f64": return toF64(value);
  282. }
  283. throw new Error("Unknown argument type");
  284. }
  285. const wrappers = {};
  286. function getArthimeticNanWrapper(action, expected) {
  287. if (action.type === "invoke") {
  288. const args = action.args.map(({type}) => type);
  289. const resultType = expected[0].type;
  290. const signature = resultType + args.join("");
  291. let wasmModule = wrappers[signature];
  292. if (!wasmModule) {
  293. const resultSize = resultType === "f32" ? 32 : 64;
  294. const matchingIntType = resultSize === 32 ? "i32" : "i64";
  295. const expectedResult = resultSize === 32 ? "0x7f800000" : "0x7ff0000000000000";
  296. const params = args.length > 0 ? `(param ${args.join(" ")})` : "";
  297. const newMod = `
  298. (module
  299. (import "test" "fn" (func $fn ${params} (result ${resultType})))
  300. (func (export "compare") ${params} (result i32)
  301. ${args.map((arg, i) => `(get_local ${i|0})`).join(" ")}
  302. (call $fn)
  303. (${matchingIntType}.reinterpret/${resultType})
  304. (${matchingIntType}.and (${matchingIntType}.const ${expectedResult}))
  305. (${matchingIntType}.eq (${matchingIntType}.const ${expectedResult}))
  306. )
  307. )`;
  308. if (verbose) {
  309. console.log(newMod);
  310. }
  311. const buf = WebAssembly.wabt.convertWast2Wasm(newMod);
  312. wrappers[signature] = wasmModule = new WebAssembly.Module(buf);
  313. }
  314. return (fn, ...args) => {
  315. const {exports: {compare}} = new WebAssembly.Instance(wasmModule, {test: {fn}});
  316. return compare(...args);
  317. }
  318. }
  319. }
  320. function assertReturn(moduleRegistry, command, {canonicalNan, arithmeticNan} = {}) {
  321. const {action, expected} = command;
  322. try {
  323. const wrapper = null; // arithmeticNan ? getArthimeticNanWrapper(action, expected) : null;
  324. const res = runAction(moduleRegistry, action, wrapper);
  325. let success = true;
  326. if (expected.length === 0) {
  327. success = typeof res === "undefined";
  328. } else {
  329. // We don't support multi return right now
  330. const [ex1] = expected;
  331. const expectedResult = mapWasmArg(ex1);
  332. if (ex1.type === "i64") {
  333. success = expectedResult.low === res.low && expectedResult.high === res.high;
  334. } else if (arithmeticNan || canonicalNan || isNaN(expectedResult)) {
  335. // todo:: do exact compare for nan once bug resolved
  336. success = isNaN(res);
  337. } else {
  338. success = res === expectedResult;
  339. }
  340. }
  341. if (success) {
  342. passed++;
  343. if (verbose) {
  344. print(`${getCommandStr(command)} passed.`);
  345. }
  346. } else {
  347. print(`${getCommandStr(command)} failed. Returned ${getValueStr(res)}`);
  348. failed++;
  349. }
  350. } catch (e) {
  351. print(`${getCommandStr(command)} failed. Unexpectedly threw: ${e}`);
  352. failed++;
  353. }
  354. }
  355. function assertTrap(moduleRegistry, command) {
  356. const {action, text} = command;
  357. try {
  358. runAction(moduleRegistry, action);
  359. print(`${getCommandStr(command)} failed. Should have had an error`);
  360. failed++;
  361. } catch (e) {
  362. if (e instanceof WebAssembly.RuntimeError) {
  363. passed++;
  364. if (verbose) {
  365. print(`${getCommandStr(command)} passed. Error thrown: ${e}`);
  366. print(` Spec error message expected: ${text}`);
  367. }
  368. } else {
  369. failed++;
  370. print(`${getCommandStr(command)} failed. Unexpected error thrown: ${e}`);
  371. }
  372. }
  373. }
  374. let StackOverflow;
  375. function assertStackExhaustion(moduleRegistry, command) {
  376. if (!StackOverflow) {
  377. // Get the stack overflow exception type
  378. // Javascript and WebAssembly must throw the same type of error
  379. try { (function f() { 1 + f() })() } catch (e) { StackOverflow = e.constructor }
  380. }
  381. const {action, text} = command;
  382. try {
  383. runAction(moduleRegistry, action);
  384. print(`${getCommandStr(command)} failed. Should have exhausted the stack`);
  385. failed++;
  386. } catch (e) {
  387. if (e instanceof StackOverflow) {
  388. passed++;
  389. if (verbose) {
  390. print(`${getCommandStr(command)} passed. Had a StackOverflow`);
  391. }
  392. } else {
  393. failed++;
  394. print(`${getCommandStr(command)} failed. Unexpected error thrown: ${e}`);
  395. }
  396. }
  397. }
  398. function runSimpleAction(moduleRegistry, command) {
  399. try {
  400. const res = runAction(moduleRegistry, command.action);
  401. ++passed;
  402. if (verbose) {
  403. print(`${getCommandStr(command)} = ${res}`);
  404. }
  405. } catch (e) {
  406. ++failed;
  407. print(`${getCommandStr(command)} failed. Unexpectedly threw: ${e}`);
  408. }
  409. }
  410. function runAction(moduleRegistry, action, wrapper) {
  411. const m = action.module ? moduleRegistry[action.module] : moduleRegistry.currentModule;
  412. if (!m) {
  413. print("Module unavailable to run action");
  414. return;
  415. }
  416. switch (action.type) {
  417. case "invoke": {
  418. const {field, args} = action;
  419. const mappedArgs = args.map(({value}) => value);
  420. let wasmFn = m.exports[field];
  421. if (wrapper) {
  422. wasmFn = wrapper.bind(null, wasmFn);
  423. }
  424. const res = wasmFn(...mappedArgs);
  425. return res;
  426. }
  427. case "get": {
  428. const {field} = action;
  429. if (wrapper) {
  430. return wrapper(m, field);
  431. }
  432. return m.exports[field];
  433. }
  434. default:
  435. print(`Unknown action: ${JSON.stringify(action)}`);
  436. }
  437. }
  438. function end() {
  439. print(`${passed}/${passed + failed} tests passed.`);
  440. }
  441. run(cliArgs[0], cliArgs[1] | 0, cliArgs[2] === undefined ? undefined : cliArgs[2] | 0);