spec.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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] [-v] [-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("-v");
  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. switch (action.type) {
  61. case "invoke": return `${action.field}(${getArgsStr(action.args)})`;
  62. case "get": return `${action.module || "$$"}[${action.field}]`;
  63. default: return "Unkown action type";
  64. }
  65. }
  66. function getCommandStr(command) {
  67. const base = `(${iTest}) ${file}:${command.line}`;
  68. switch (command.type) {
  69. case "module": return `${base}: generate module ${command.filename}${command.name ? ` as ${command.name}` : ""}`;
  70. case "register": return `${base}: register module ${command.name || "$$"} as ${command.as}`;
  71. case "assert_malformed": return `${base}: assert_malformed module ${command.filename}`;
  72. case "assert_unlinkable": return `${base}: assert_unlinkable module ${command.filename}`;
  73. case "assert_invalid": return `${base}: assert_invalid module ${command.filename}`;
  74. case "action": return `${base}: action ${getActionStr(command.action)}`;
  75. case "assert_trap": return `${base}: assert_trap ${getActionStr(command.action)}`;
  76. case "assert_return": return `${base}: assert_return ${getActionStr(command.action)} == ${getArgsStr(command.expected)}`;
  77. case "assert_return_nan": return `${base}: assert_return_nan ${getActionStr(command.action)}`;
  78. }
  79. return base;
  80. }
  81. function run(inPath, iStart, iEnd) {
  82. const lastSlash = Math.max(inPath.lastIndexOf("/"), inPath.lastIndexOf("\\"));
  83. const inDir = lastSlash === -1 ? "." : inPath.slice(0, lastSlash);
  84. const data = read(inPath);
  85. const jsonData = JSON.parse(data);
  86. file = jsonData.source_filename;
  87. const registry = Object.assign({spectest: {
  88. print,
  89. global: 666,
  90. table: new WebAssembly.Table({initial: 10, maximum: 20, element: "anyfunc"}),
  91. memory: new WebAssembly.Memory({initial: 1, maximum: 2})
  92. }}, IMPORTS_FROM_OTHER_SCRIPT);
  93. const moduleRegistry = {};
  94. moduleRegistry.currentModule = null;
  95. for (const command of jsonData.commands) {
  96. ++iTest;
  97. if (iTest < iStart) {
  98. // always run module/register commands that happens before iStart
  99. if (command.type !== "module" && command.type !== "register") {
  100. continue;
  101. }
  102. } else if (iTest > iEnd) {
  103. continue;
  104. }
  105. if (verbose) {
  106. print(`Running:${getCommandStr(command)}`);
  107. }
  108. switch (command.type) {
  109. case "module":
  110. moduleCommand(inDir, command, registry, moduleRegistry);
  111. break;
  112. case "assert_malformed":
  113. assertMalformed(inDir, command);
  114. break;
  115. case "assert_unlinkable":
  116. assertUnlinkable(inDir, command, registry);
  117. break;
  118. case "assert_invalid":
  119. assertMalformed(inDir, command, registry);
  120. break;
  121. case "register": {
  122. const {as, name = "currentModule"} = command;
  123. registry[as] = moduleRegistry[name].exports;
  124. break;
  125. }
  126. case "action":
  127. runSimpleAction(moduleRegistry, command);
  128. break;
  129. case "assert_return":
  130. assertReturn(moduleRegistry, command);
  131. break;
  132. case "assert_return_nan":
  133. assertReturn(moduleRegistry, command, true);
  134. break;
  135. case "assert_trap":
  136. if (trap) {
  137. assertTrap(moduleRegistry, command);
  138. } else {
  139. print(`${getCommandStr(command)} skipped because it has traps`);
  140. }
  141. break;
  142. default:
  143. print(`Unknown command: ${JSON.stringify(command)}`);
  144. }
  145. }
  146. end();
  147. }
  148. function createModule(baseDir, filename, registry, output) {
  149. const moduleFile = readbuffer(baseDir + "/" + filename);
  150. const u8a = new Uint8Array(moduleFile);
  151. output.module = new WebAssembly.Module(u8a);
  152. // We'll know if an error occurs at instanciation because output.module will be set
  153. output.instance = new WebAssembly.Instance(output.module, registry);
  154. }
  155. function moduleCommand(baseDir, command, registry, moduleRegistry) {
  156. const {filename, name} = command;
  157. try {
  158. const output = {};
  159. createModule(baseDir, filename, registry, output);
  160. if (name) {
  161. moduleRegistry[name] = output.instance;
  162. }
  163. moduleRegistry.currentModule = output.instance;
  164. ++passed;
  165. } catch (e) {
  166. ++failed;
  167. print(`${getCommandStr(command)} failed. Unexpected Error: ${e}`);
  168. }
  169. return null;
  170. }
  171. function assertMalformed(baseDir, command) {
  172. const {filename, text} = command;
  173. // Test hook to prevent deferred parsing
  174. WScript.Flag("-off:wasmdeferred");
  175. const output = {};
  176. try {
  177. createModule(baseDir, filename, null, output);
  178. ++failed;
  179. print(`${getCommandStr(command)} failed. Should have had an error`);
  180. } catch (e) {
  181. if (output.module) {
  182. ++failed;
  183. print(`${getCommandStr(command)} failed. Had a linking error, expected a compile error: ${e}`);
  184. } else if (e instanceof WebAssembly.CompileError) {
  185. ++passed;
  186. if (verbose) {
  187. print(`${getCommandStr(command)} passed. Had compile error: ${e}`);
  188. print(` Spec expected error: ${text}`);
  189. }
  190. } else {
  191. ++failed;
  192. print(`${getCommandStr(command)} failed. Expected a compile error: ${e}`);
  193. }
  194. } finally {
  195. // Reset the test hook
  196. WScript.Flag("-on:wasmdeferred");
  197. }
  198. }
  199. function assertUnlinkable(baseDir, command, registry) {
  200. const {filename, text} = command;
  201. // Test hook to prevent deferred parsing
  202. WebAssembly.Module.prototype.deferred = false;
  203. const output = {};
  204. try {
  205. createModule(baseDir, filename, registry, output);
  206. ++failed;
  207. print(`${getCommandStr(command)} failed. Should have had an error`);
  208. } catch (e) {
  209. if (e instanceof WebAssembly.LinkError) {
  210. ++passed;
  211. if (verbose) {
  212. print(`${getCommandStr(command)} passed. Had linking error: ${e}`);
  213. print(` Spec expected error: ${text}`);
  214. }
  215. } else {
  216. ++failed;
  217. print(`${getCommandStr(command)} failed. Expected a linking error: ${e}`);
  218. }
  219. } finally {
  220. // Reset the test hook
  221. WebAssembly.Module.prototype.deferred = undefined;
  222. }
  223. }
  224. function genConverters() {
  225. /*
  226. (module $converterBuffer
  227. (func (export "convertI64") (param i64) (result i64) (get_local 0))
  228. (func (export "toF32") (param i32) (result f32) (f32.reinterpret/i32 (get_local 0)))
  229. (func (export "toF64") (param i64) (result f64) (f64.reinterpret/i64 (get_local 0)))
  230. )
  231. */
  232. const converterBuffer = "\x00\x61\x73\x6d\x0d\x00\x00\x00\x01\x90\x80\x80\x80\x00\x03\x60\x01\x7e\x01\x7e\x60\x01\x7f\x01\x7d\x60\x01\x7e\x01\x7c\x03\x84\x80\x80\x80\x00\x03\x00\x01\x02\x07\x9e\x80\x80\x80\x00\x03\x0a\x63\x6f\x6e\x76\x65\x72\x74\x49\x36\x34\x00\x00\x05\x74\x6f\x46\x33\x32\x00\x01\x05\x74\x6f\x46\x36\x34\x00\x02\x0a\x9e\x80\x80\x80\x00\x03\x84\x80\x80\x80\x00\x00\x20\x00\x0b\x85\x80\x80\x80\x00\x00\x20\x00\xbe\x0b\x85\x80\x80\x80\x00\x00\x20\x00\xbf\x0b";
  233. const buffer = new ArrayBuffer(converterBuffer.length);
  234. const view = new Uint8Array(buffer);
  235. for (let i = 0; i < converterBuffer.length; ++i) {
  236. view[i] = converterBuffer.charCodeAt(i);
  237. }
  238. const module = new WebAssembly.Module(buffer);
  239. const instance = new WebAssembly.Instance(module);
  240. return instance.exports;
  241. }
  242. const {toF32, toF64, convertI64} = genConverters();
  243. function mapWasmArg({type, value}) {
  244. switch (type) {
  245. case "i32": return parseInt(value)|0;
  246. case "i64": return convertI64(value); // will convert string to {high, low}
  247. case "f32": return toF32(parseInt(value));
  248. case "f64": return toF64(value);
  249. }
  250. throw new Error("Unknown argument type");
  251. }
  252. function assertReturn(moduleRegistry, command, checkNaN) {
  253. const {action, expected} = command;
  254. try {
  255. const res = runAction(moduleRegistry, action);
  256. let success = true;
  257. if (expected.length === 0) {
  258. success = typeof res === "undefined";
  259. } else {
  260. // We don't support multi return right now
  261. const [ex1] = expected;
  262. const expectedResult = mapWasmArg(ex1);
  263. if (ex1.type === "i64") {
  264. success = expectedResult.low === res.low && expectedResult.high === res.high;
  265. } else if (checkNaN || isNaN(expectedResult)) {
  266. success = isNaN(res);
  267. } else {
  268. success = res === expectedResult;
  269. }
  270. }
  271. if (success) {
  272. passed++;
  273. if (verbose) {
  274. print(`${getCommandStr(command)} passed.`);
  275. }
  276. } else {
  277. print(`${getCommandStr(command)} failed. Returned ${getValueStr(res)}`);
  278. failed++;
  279. }
  280. } catch (e) {
  281. print(`${getCommandStr(command)} failed. Unexpectedly threw: ${e}`);
  282. failed++;
  283. }
  284. }
  285. function assertTrap(moduleRegistry, command) {
  286. const {action, text} = command;
  287. try {
  288. runAction(moduleRegistry, action);
  289. print(`${getCommandStr(command)} failed. Should have had an error`);
  290. failed++;
  291. } catch (e) {
  292. if (e instanceof WebAssembly.RuntimeError || e.message.indexOf("Out of stack") !== -1) {
  293. passed++;
  294. if (verbose) {
  295. print(`${getCommandStr(command)} passed. Error thrown: ${e}`);
  296. print(` Spec error message expected: ${text}`);
  297. }
  298. } else {
  299. failed++;
  300. print(`${getCommandStr(command)} failed. Unexpected error thrown: ${e}`);
  301. }
  302. }
  303. }
  304. function runSimpleAction(moduleRegistry, command) {
  305. try {
  306. const res = runAction(moduleRegistry, command.action);
  307. ++passed;
  308. if (verbose) {
  309. print(`${getCommandStr(command)} = ${res}`);
  310. }
  311. } catch (e) {
  312. ++failed;
  313. print(`${getCommandStr(command)} failed. Unexpectedly threw: ${e}`);
  314. }
  315. }
  316. function runAction(moduleRegistry, action) {
  317. const m = action.module ? moduleRegistry[action.module] : moduleRegistry.currentModule;
  318. if (!m) {
  319. print("Module unavailable to run action");
  320. return;
  321. }
  322. switch (action.type) {
  323. case "invoke": {
  324. const {field, args} = action;
  325. const mappedArgs = args.map(({value}) => value);
  326. const wasmFn = m.exports[field];
  327. const res = wasmFn(...mappedArgs);
  328. return res;
  329. }
  330. case "get": {
  331. const {field} = action;
  332. return m.exports[field];
  333. }
  334. default:
  335. print(`Unknown action: ${JSON.stringify(action)}`);
  336. }
  337. }
  338. function end() {
  339. print(`${passed}/${passed + failed} tests passed.`);
  340. }
  341. run(cliArgs[0], cliArgs[1] | 0, cliArgs[2] === undefined ? undefined : cliArgs[2] | 0);