runtest.py 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606
  1. #!/usr/bin/env python
  2. from __future__ import print_function
  3. import argparse
  4. import array
  5. import atexit
  6. import math
  7. import os
  8. import pathlib
  9. import re
  10. import shutil
  11. import struct
  12. import subprocess
  13. import sys
  14. import tempfile
  15. import time
  16. import threading
  17. import traceback
  18. from select import select
  19. from queue import Queue
  20. from subprocess import PIPE, STDOUT, Popen
  21. from typing import BinaryIO, Optional, Tuple
  22. if sys.version_info[0] == 2:
  23. IS_PY_3 = False
  24. else:
  25. IS_PY_3 = True
  26. test_aot = False
  27. # Available targets:
  28. # "aarch64" "aarch64_vfp" "armv7" "armv7_vfp" "thumbv7" "thumbv7_vfp"
  29. # "riscv32" "riscv32_ilp32f" "riscv32_ilp32d" "riscv64" "riscv64_lp64f" "riscv64_lp64d"
  30. test_target = "x86_64"
  31. debug_file = None
  32. log_file = None
  33. # to save the register module with self-define name
  34. temp_file_repo = []
  35. # to save the mapping of module files in /tmp by name
  36. temp_module_table = {}
  37. # AOT compilation options mapping
  38. aot_target_options_map = {
  39. "i386": ["--target=i386"],
  40. "x86_32": ["--target=i386"],
  41. # cf. https://github.com/bytecodealliance/wasm-micro-runtime/issues/3035
  42. "x86_64": ["--target=x86_64", "--cpu=skylake", "--size-level=0"],
  43. "aarch64": ["--target=aarch64", "--target-abi=eabi", "--cpu=cortex-a53"],
  44. "aarch64_vfp": ["--target=aarch64", "--target-abi=gnueabihf", "--cpu=cortex-a53"],
  45. "armv7": ["--target=armv7", "--target-abi=eabi", "--cpu=cortex-a9", "--cpu-features=-neon"],
  46. "armv7_vfp": ["--target=armv7", "--target-abi=gnueabihf", "--cpu=cortex-a9"],
  47. "thumbv7": ["--target=thumbv7", "--target-abi=eabi", "--cpu=cortex-a9", "--cpu-features=-neon,-vfpv3"],
  48. "thumbv7_vfp": ["--target=thumbv7", "--target-abi=gnueabihf", "--cpu=cortex-a9", "--cpu-features=-neon"],
  49. "riscv32": ["--target=riscv32", "--target-abi=ilp32", "--cpu=generic-rv32", "--cpu-features=+m,+a,+c"],
  50. "riscv32_ilp32f": ["--target=riscv32", "--target-abi=ilp32f", "--cpu=generic-rv32", "--cpu-features=+m,+a,+c,+f"],
  51. "riscv32_ilp32d": ["--target=riscv32", "--target-abi=ilp32d", "--cpu=generic-rv32", "--cpu-features=+m,+a,+c,+f,+d"],
  52. # RISCV64 requires -mcmodel=medany, which can be set by --size-level=1
  53. "riscv64": ["--target=riscv64", "--target-abi=lp64", "--cpu=generic-rv64", "--cpu-features=+m,+a,+c", "--size-level=1"],
  54. "riscv64_lp64f": ["--target=riscv64", "--target-abi=lp64f", "--cpu=generic-rv64", "--cpu-features=+m,+a,+c,+f", "--size-level=1"],
  55. "riscv64_lp64d": ["--target=riscv64", "--target-abi=lp64d", "--cpu=generic-rv64", "--cpu-features=+m,+a,+c,+f,+d", "--size-level=1"],
  56. "xtensa": ["--target=xtensa"],
  57. }
  58. # AOT compilation options mapping for XIP mode
  59. aot_target_options_map_xip = {
  60. # avoid l32r relocations for xtensa
  61. "xtensa": ["--mllvm=-mtext-section-literals"],
  62. "riscv32_ilp32f": ["--enable-builtin-intrinsics=i64.common,f64.common,f32.const,f64.const,f64xi32,f64xi64,f64_promote_f32,f32_demote_f64"],
  63. }
  64. def debug(data):
  65. if debug_file:
  66. debug_file.write(data)
  67. debug_file.flush()
  68. def log(data, end='\n'):
  69. if log_file:
  70. log_file.write(data + end)
  71. log_file.flush()
  72. print(data, end=end)
  73. sys.stdout.flush()
  74. def create_tmp_file(prefix: str, suffix: str) -> str:
  75. return tempfile.NamedTemporaryFile(prefix=prefix, suffix=suffix, delete=False).name
  76. # TODO: do we need to support '\n' too
  77. import platform
  78. if platform.system().find("CYGWIN_NT") >= 0:
  79. # TODO: this is weird, is this really right on Cygwin?
  80. sep = "\n\r\n"
  81. else:
  82. sep = "\r\n"
  83. rundir = None
  84. class AsyncStreamReader:
  85. def __init__(self, stream: BinaryIO) -> None:
  86. self._queue = Queue()
  87. self._reader_thread = threading.Thread(
  88. daemon=True,
  89. target=AsyncStreamReader._stdout_reader,
  90. args=(self._queue, stream))
  91. self._reader_thread.start()
  92. def read(self) -> Optional[bytes]:
  93. return self._queue.get()
  94. def cleanup(self) -> None:
  95. self._reader_thread.join()
  96. @staticmethod
  97. def _stdout_reader(queue: Queue, stdout: BinaryIO) -> None:
  98. while True:
  99. try:
  100. queue.put(stdout.read(1))
  101. except ValueError as e:
  102. if stdout.closed:
  103. queue.put(None)
  104. break
  105. raise e
  106. class Runner():
  107. def __init__(self, args, no_pty=False):
  108. self.no_pty = no_pty
  109. # Cleanup child process on exit
  110. atexit.register(self.cleanup)
  111. self.process = None
  112. env = os.environ
  113. env['TERM'] = 'dumb'
  114. env['INPUTRC'] = '/dev/null'
  115. env['PERL_RL'] = 'false'
  116. if no_pty:
  117. self.process = Popen(args, bufsize=0,
  118. stdin=PIPE, stdout=PIPE, stderr=STDOUT,
  119. env=env)
  120. self.stdin = self.process.stdin
  121. self.stdout = self.process.stdout
  122. else:
  123. import fcntl
  124. # Pseudo-TTY and terminal manipulation
  125. import pty
  126. import termios
  127. # Use tty to setup an interactive environment
  128. master, slave = pty.openpty()
  129. # Set terminal size large so that readline will not send
  130. # ANSI/VT escape codes when the lines are long.
  131. buf = array.array('h', [100, 200, 0, 0])
  132. fcntl.ioctl(master, termios.TIOCSWINSZ, buf, True)
  133. self.process = Popen(args, bufsize=0,
  134. stdin=slave, stdout=slave, stderr=STDOUT,
  135. preexec_fn=os.setsid,
  136. env=env)
  137. # Now close slave so that we will get an exception from
  138. # read when the child exits early
  139. # http://stackoverflow.com/questions/11165521
  140. os.close(slave)
  141. self.stdin = os.fdopen(master, 'r+b', 0)
  142. self.stdout = self.stdin
  143. if platform.system().lower() == "windows":
  144. self._stream_reader = AsyncStreamReader(self.stdout)
  145. else:
  146. self._stream_reader = None
  147. self.buf = ""
  148. def _read_stdout_byte(self) -> Tuple[bool, Optional[bytes]]:
  149. if self._stream_reader:
  150. return True, self._stream_reader.read()
  151. else:
  152. # select doesn't work on file descriptors on Windows.
  153. # however, this method is much faster than using
  154. # queue, so we keep it for non-windows platforms.
  155. [outs,_,_] = select([self.stdout], [], [], 1)
  156. if self.stdout in outs:
  157. try:
  158. stdout_byte = self.stdout.read(1)
  159. except ValueError:
  160. return True, None
  161. except OSError:
  162. return True, None
  163. except Exception as e:
  164. print("Exception: ", e)
  165. return False, None
  166. return True, stdout_byte
  167. else:
  168. return False, None
  169. def read_to_prompt(self, prompts, timeout):
  170. wait_until = time.time() + timeout
  171. while time.time() < wait_until:
  172. has_value, read_byte = self._read_stdout_byte()
  173. if not has_value:
  174. continue
  175. if not read_byte:
  176. # EOF on macOS ends up here.
  177. break
  178. read_byte = read_byte.decode('utf-8') if IS_PY_3 else read_byte
  179. debug(read_byte)
  180. if self.no_pty:
  181. self.buf += read_byte.replace('\n', '\r\n')
  182. else:
  183. self.buf += read_byte
  184. self.buf = self.buf.replace('\r\r', '\r')
  185. # filter the prompts
  186. for prompt in prompts:
  187. pattern = re.compile(prompt)
  188. match = pattern.search(self.buf)
  189. if match:
  190. end = match.end()
  191. buf = self.buf[0:end-len(prompt)]
  192. self.buf = self.buf[end:]
  193. return buf
  194. log("left read_to_prompt() because of timeout")
  195. return None
  196. def writeline(self, str):
  197. str_to_write = str + '\n'
  198. str_to_write = bytes(
  199. str_to_write, 'utf-8') if IS_PY_3 else str_to_write
  200. self.stdin.write(str_to_write)
  201. def cleanup(self):
  202. atexit.unregister(self.cleanup)
  203. if self.process:
  204. try:
  205. self.writeline("__exit__")
  206. time.sleep(.020)
  207. self.process.kill()
  208. except OSError:
  209. pass
  210. except IOError:
  211. pass
  212. self.process = None
  213. self.stdin.close()
  214. if self.stdin != self.stdout:
  215. self.stdout.close()
  216. self.stdin = None
  217. self.stdout = None
  218. if not IS_PY_3:
  219. sys.exc_clear()
  220. if self._stream_reader:
  221. self._stream_reader.cleanup()
  222. def assert_prompt(runner, prompts, timeout, is_need_execute_result):
  223. # Wait for the initial prompt
  224. header = runner.read_to_prompt(prompts, timeout=timeout)
  225. if not header and is_need_execute_result:
  226. log(" ---------- will terminate cause the case needs result while there is none inside of buf. ----------")
  227. raise Exception("get nothing from Runner")
  228. if not header == None:
  229. if header:
  230. log("Started with:\n%s" % header)
  231. else:
  232. log("Did not one of following prompt(s): %s" % repr(prompts))
  233. log(" Got : %s" % repr(r.buf))
  234. raise Exception("Did not one of following prompt(s)")
  235. ### WebAssembly specific
  236. parser = argparse.ArgumentParser(
  237. description="Run a test file against a WebAssembly interpreter")
  238. parser.add_argument('--wast2wasm', type=str,
  239. default=os.environ.get("WAST2WASM", "wast2wasm"),
  240. help="Path to wast2wasm program")
  241. parser.add_argument('--interpreter', type=str,
  242. default=os.environ.get("IWASM_CMD", "iwasm"),
  243. help="Path to WebAssembly interpreter")
  244. parser.add_argument('--aot-compiler', type=str,
  245. default=os.environ.get("WAMRC_CMD", "wamrc"),
  246. help="Path to WebAssembly AoT compiler")
  247. parser.add_argument('--no_cleanup', action='store_true',
  248. help="Keep temporary *.wasm files")
  249. parser.add_argument('--rundir',
  250. help="change to the directory before running tests")
  251. parser.add_argument('--start-timeout', default=30, type=int,
  252. help="default timeout for initial prompt")
  253. parser.add_argument('--start-fail-timeout', default=2, type=int,
  254. help="default timeout for initial prompt (when expected to fail)")
  255. parser.add_argument('--test-timeout', default=20, type=int,
  256. help="default timeout for each individual test action")
  257. parser.add_argument('--no-pty', action='store_true',
  258. help="Use direct pipes instead of pseudo-tty")
  259. parser.add_argument('--log-file', type=str,
  260. help="Write messages to the named file in addition the screen")
  261. parser.add_argument('--log-dir', type=str,
  262. help="The log directory to save the case file if test failed")
  263. parser.add_argument('--debug-file', type=str,
  264. help="Write all test interaction the named file")
  265. parser.add_argument('test_file', type=argparse.FileType('r'),
  266. help="a WebAssembly *.wast test file")
  267. parser.add_argument('--aot', action='store_true',
  268. help="Test with AOT")
  269. parser.add_argument('--target', type=str,
  270. default="x86_64",
  271. help="Set running target")
  272. parser.add_argument('--sgx', action='store_true',
  273. help="Test SGX")
  274. parser.add_argument('--simd', default=False, action='store_true',
  275. help="Enable SIMD")
  276. parser.add_argument('--xip', default=False, action='store_true',
  277. help="Enable XIP")
  278. parser.add_argument('--eh', default=False, action='store_true',
  279. help="Enable Exception Handling")
  280. parser.add_argument('--multi-module', default=False, action='store_true',
  281. help="Enable Multi-thread")
  282. parser.add_argument('--multi-thread', default=False, action='store_true',
  283. help="Enable Multi-thread")
  284. parser.add_argument('--gc', default=False, action='store_true',
  285. help='Test with GC')
  286. parser.add_argument('--extended-const', action='store_true',
  287. help='Enable extended const expression feature')
  288. parser.add_argument('--memory64', default=False, action='store_true',
  289. help='Test with Memory64')
  290. parser.add_argument('--multi-memory', default=False, action='store_true',
  291. help='Test with multi-memory(with multi-module auto enabled)')
  292. parser.add_argument('--qemu', default=False, action='store_true',
  293. help="Enable QEMU")
  294. parser.add_argument('--qemu-firmware', default='', help="Firmware required by qemu")
  295. parser.add_argument('--verbose', default=False, action='store_true',
  296. help='show more logs')
  297. # regex patterns of tests to skip
  298. C_SKIP_TESTS = ()
  299. PY_SKIP_TESTS = (
  300. # names.wast
  301. 'invoke \"~!',
  302. # conversions.wast
  303. '18446742974197923840.0',
  304. '18446744073709549568.0',
  305. '9223372036854775808',
  306. 'reinterpret_f.*nan',
  307. # endianness
  308. '.const 0x1.fff' )
  309. def read_forms(string):
  310. forms = []
  311. form = ""
  312. depth = 0
  313. line = 0
  314. pos = 0
  315. while pos < len(string):
  316. # Keep track of line number
  317. if string[pos] == '\n': line += 1
  318. # Handle top-level elements
  319. if depth == 0:
  320. # Add top-level comments
  321. if string[pos:pos+2] == ";;":
  322. end = string.find("\n", pos)
  323. if end == -1: end == len(string)
  324. forms.append(string[pos:end])
  325. pos = end
  326. continue
  327. # TODO: handle nested multi-line comments
  328. if string[pos:pos+2] == "(;":
  329. # Skip multi-line comment
  330. end = string.find(";)", pos)
  331. if end == -1:
  332. raise Exception("mismatch multiline comment on line %d: '%s'" % (
  333. line, string[pos:pos+80]))
  334. pos = end+2
  335. continue
  336. # Ignore whitespace between top-level forms
  337. if string[pos] in (' ', '\n', '\t'):
  338. pos += 1
  339. continue
  340. # Read a top-level form
  341. if string[pos] == '(': depth += 1
  342. if string[pos] == ')': depth -= 1
  343. if depth == 0 and not form:
  344. raise Exception("garbage on line %d: '%s'" % (
  345. line, string[pos:pos+80]))
  346. form += string[pos]
  347. if depth == 0 and form:
  348. forms.append(form)
  349. form = ""
  350. pos += 1
  351. return forms
  352. def get_module_exp_from_assert(string):
  353. depth = 0
  354. pos = 0
  355. module = ""
  356. exception = ""
  357. start_record = False
  358. result = []
  359. while pos < len(string):
  360. # record from the " (module "
  361. if string[pos:pos+7] == "(module":
  362. start_record = True
  363. if start_record:
  364. if string[pos] == '(' : depth += 1
  365. if string[pos] == ')' : depth -= 1
  366. module += string[pos]
  367. # if we get all (module ) .
  368. if depth == 0 and module:
  369. result.append(module)
  370. start_record = False
  371. # get expected exception
  372. if string[pos] == '"':
  373. end = string.find("\"", pos+1)
  374. if end != -1:
  375. end_rel = string.find("\"",end+1)
  376. if end_rel == -1:
  377. result.append(string[pos+1:end])
  378. pos += 1
  379. return result
  380. def string_to_unsigned(number_in_string, lane_type):
  381. if not lane_type in ['i8x16', 'i16x8', 'i32x4', 'i64x2']:
  382. raise Exception("invalid value {} and type {} and lane_type {}".format(number_in_string, type, lane_type))
  383. number = int(number_in_string, 16) if '0x' in number_in_string else int(number_in_string)
  384. if "i8x16" == lane_type:
  385. if number < 0:
  386. packed = struct.pack('b', number)
  387. number = struct.unpack('B', packed)[0]
  388. elif "i16x8" == lane_type:
  389. if number < 0:
  390. packed = struct.pack('h', number)
  391. number = struct.unpack('H', packed)[0]
  392. elif "i32x4" == lane_type:
  393. if number < 0:
  394. packed = struct.pack('i', number)
  395. number = struct.unpack('I', packed)[0]
  396. else: # "i64x2" == lane_type:
  397. if number < 0:
  398. packed = struct.pack('q', number)
  399. number = struct.unpack('Q', packed)[0]
  400. return number
  401. def cast_v128_to_i64x2(numbers, type, lane_type):
  402. numbers = [n.replace("_", "") for n in numbers]
  403. if "i8x16" == lane_type:
  404. assert(16 == len(numbers)), "{} should like {}".format(numbers, lane_type)
  405. # str -> int
  406. numbers = [string_to_unsigned(n, lane_type) for n in numbers]
  407. # i8 -> i64
  408. packed = struct.pack(16 * "B", *numbers)
  409. elif "i16x8" == lane_type:
  410. assert(8 == len(numbers)), "{} should like {}".format(numbers, lane_type)
  411. # str -> int
  412. numbers = [string_to_unsigned(n, lane_type) for n in numbers]
  413. # i16 -> i64
  414. packed = struct.pack(8 * "H", *numbers)
  415. elif "i32x4" == lane_type:
  416. assert(4 == len(numbers)), "{} should like {}".format(numbers, lane_type)
  417. # str -> int
  418. numbers = [string_to_unsigned(n, lane_type) for n in numbers]
  419. # i32 -> i64
  420. packed = struct.pack(4 * "I", *numbers)
  421. elif "i64x2" == lane_type:
  422. assert(2 == len(numbers)), "{} should like {}".format(numbers, lane_type)
  423. # str -> int
  424. numbers = [string_to_unsigned(n, lane_type) for n in numbers]
  425. # i64 -> i64
  426. packed = struct.pack(2 * "Q", *numbers)
  427. elif "f32x4" == lane_type:
  428. assert(4 == len(numbers)), "{} should like {}".format(numbers, lane_type)
  429. # str -> int
  430. numbers = [parse_simple_const_w_type(n, "f32")[0] for n in numbers]
  431. # f32 -> i64
  432. packed = struct.pack(4 * "f", *numbers)
  433. elif "f64x2" == lane_type:
  434. assert(2 == len(numbers)), "{} should like {}".format(numbers, lane_type)
  435. # str -> int
  436. numbers = [parse_simple_const_w_type(n, "f64")[0] for n in numbers]
  437. # f64 -> i64
  438. packed = struct.pack(2 * "d", *numbers)
  439. else:
  440. raise Exception("invalid value {} and type {} and lane_type {}".format(numbers, type, lane_type))
  441. assert(packed)
  442. unpacked = struct.unpack("Q Q", packed)
  443. return unpacked, f"[{unpacked[0]:#x} {unpacked[1]:#x}]:{lane_type}:v128"
  444. def parse_simple_const_w_type(number, type):
  445. number = number.replace('_', '')
  446. number = re.sub(r"nan\((ind|snan)\)", "nan", number)
  447. if type in ["i32", "i64"]:
  448. number = int(number, 16) if '0x' in number else int(number)
  449. return number, "0x{:x}:{}".format(number, type) \
  450. if number >= 0 \
  451. else "-0x{:x}:{}".format(0 - number, type)
  452. elif type in ["f32", "f64"]:
  453. if "nan:" in number:
  454. return float('nan'), "nan:{}".format(type)
  455. else:
  456. number = float.fromhex(number) if '0x' in number else float(number)
  457. return number, "{:.7g}:{}".format(number, type)
  458. elif type == "ref.null":
  459. if number == "func":
  460. return "func", "func:ref.null"
  461. elif number == "extern":
  462. return "extern", "extern:ref.null"
  463. elif number == "any":
  464. return "any", "any:ref.null"
  465. else:
  466. raise Exception("invalid value {} and type {}".format(number, type))
  467. elif type == "ref.extern":
  468. number = int(number, 16) if '0x' in number else int(number)
  469. return number, "0x{:x}:ref.extern".format(number)
  470. elif type == "ref.host":
  471. number = int(number, 16) if '0x' in number else int(number)
  472. return number, "0x{:x}:ref.host".format(number)
  473. else:
  474. raise Exception("invalid value {} and type {}".format(number, type))
  475. def parse_assertion_value(val):
  476. """
  477. Parse something like:
  478. "ref.null extern" in (assert_return (invoke "get-externref" (i32.const 0)) (ref.null extern))
  479. "ref.extern 1" in (assert_return (invoke "get-externref" (i32.const 1)) (ref.extern 1))
  480. "i32.const 0" in (assert_return (invoke "is_null-funcref" (i32.const 1)) (i32.const 0))
  481. in summary:
  482. type.const (sub-type) (val1 val2 val3 val4) ...
  483. type.const val
  484. ref.extern val
  485. ref.null ref_type
  486. ref.array
  487. ref.struct
  488. ref.func
  489. ref.i31
  490. """
  491. if not val:
  492. return None, ""
  493. splitted = re.split(r'\s+', val)
  494. splitted = [s for s in splitted if s]
  495. type = splitted[0].split(".")[0]
  496. lane_type = splitted[1] if len(splitted) > 2 else ""
  497. numbers = splitted[2:] if len(splitted) > 2 else splitted[1:]
  498. if type in ["i32", "i64", "f32", "f64"]:
  499. return parse_simple_const_w_type(numbers[0], type)
  500. elif type == "ref":
  501. if splitted[0] in ["ref.array", "ref.struct", "ref.func", "ref.i31"]:
  502. return splitted[0]
  503. # need to distinguish between "ref.null" and "ref.extern"
  504. return parse_simple_const_w_type(numbers[0], splitted[0])
  505. else:
  506. return cast_v128_to_i64x2(numbers, type, lane_type)
  507. def int2uint32(i):
  508. return i & 0xffffffff
  509. def int2int32(i):
  510. val = i & 0xffffffff
  511. if val & 0x80000000:
  512. return val - 0x100000000
  513. else:
  514. return val
  515. def int2uint64(i):
  516. return i & 0xffffffffffffffff
  517. def int2int64(i):
  518. val = i & 0xffffffffffffffff
  519. if val & 0x8000000000000000:
  520. return val - 0x10000000000000000
  521. else:
  522. return val
  523. def num_repr(i):
  524. if isinstance(i, int) or isinstance(i, long):
  525. return re.sub("L$", "", hex(i))
  526. else:
  527. return "%.16g" % i
  528. def hexpad16(i):
  529. return "0x%04x" % i
  530. def hexpad24(i):
  531. return "0x%06x" % i
  532. def hexpad32(i):
  533. return "0x%08x" % i
  534. def hexpad64(i):
  535. return "0x%016x" % i
  536. def invoke(r, args, cmd):
  537. r.writeline(cmd)
  538. return r.read_to_prompt(['\r\nwebassembly> ', '\nwebassembly> '],
  539. timeout=args.test_timeout)
  540. def vector_value_comparison(out, expected):
  541. """
  542. out likes "<number number>:v128"
  543. expected likes "[number number]:v128"
  544. """
  545. # print("vector value comparision {} vs {}".format(out, expected))
  546. out_val, out_type = out.split(':')
  547. # <number nubmer> => number number
  548. out_val = out_val[1:-1]
  549. expected_val, lane_type, expected_type = expected.split(':')
  550. # [number nubmer] => number number
  551. expected_val = expected_val[1:-1]
  552. assert("v128" == out_type), "out_type should be v128"
  553. assert("v128" == expected_type), "expected_type should be v128"
  554. if out_type != expected_type:
  555. return False
  556. out_val = out_val.split(" ")
  557. expected_val = expected_val.split(" ")
  558. # since i64x2
  559. out_packed = struct.pack("QQ", int(out_val[0], 16), int(out_val[1], 16))
  560. expected_packed = struct.pack("QQ",
  561. int(expected_val[0]) if not "0x" in expected_val[0] else int(expected_val[0], 16),
  562. int(expected_val[1]) if not "0x" in expected_val[1] else int(expected_val[1], 16))
  563. if lane_type in ["i8x16", "i16x8", "i32x4", "i64x2"]:
  564. return out_packed == expected_packed
  565. else:
  566. assert(lane_type in ["f32x4", "f64x2"]), "unexpected lane_type"
  567. if "f32x4" == lane_type:
  568. out_unpacked = struct.unpack("ffff", out_packed)
  569. expected_unpacked = struct.unpack("ffff", expected_packed)
  570. else:
  571. out_unpacked = struct.unpack("dd", out_packed)
  572. expected_unpacked = struct.unpack("dd", expected_packed)
  573. out_is_nan = [math.isnan(o) for o in out_unpacked]
  574. expected_is_nan = [math.isnan(e) for e in expected_unpacked]
  575. if any(out_is_nan):
  576. nan_comparision = [o == e for o, e in zip(out_is_nan, expected_is_nan)]
  577. if all(nan_comparision):
  578. print(f"Pass NaN comparision")
  579. return True
  580. # print(f"compare {out_unpacked} and {expected_unpacked}")
  581. result = [o == e for o, e in zip(out_unpacked, expected_unpacked)]
  582. if not all(result):
  583. result = [
  584. "{:.7g}".format(o) == "{:.7g}".format(e)
  585. for o, e in zip(out_unpacked, expected_packed)
  586. ]
  587. return all(result)
  588. def simple_value_comparison(out, expected):
  589. """
  590. compare out of simple types which may like val:i32, val:f64 and so on
  591. """
  592. if expected == "2.360523e+13:f32" and out == "2.360522e+13:f32":
  593. # one case in float_literals.wast, due to float precision of python
  594. return True
  595. if expected == "1.797693e+308:f64" and out == "inf:f64":
  596. # one case in float_misc.wast:
  597. # (assert_return (invoke "f64.add" (f64.const 0x1.fffffffffffffp+1023)
  598. # (f64.const 0x1.fffffffffffffp+969))
  599. # (f64.const 0x1.fffffffffffffp+1023))
  600. # the add result in x86_32 is inf
  601. return True
  602. out_val, out_type = out.split(':')
  603. expected_val, expected_type = expected.split(':')
  604. if not out_type == expected_type:
  605. return False
  606. out_val, _ = parse_simple_const_w_type(out_val, out_type)
  607. expected_val, _ = parse_simple_const_w_type(expected_val, expected_type)
  608. if out_val == expected_val \
  609. or (math.isnan(out_val) and math.isnan(expected_val)):
  610. return True
  611. if "i32" == expected_type:
  612. out_val_binary = struct.pack('I', out_val) if out_val > 0 \
  613. else struct.pack('i', out_val)
  614. expected_val_binary = struct.pack('I', expected_val) \
  615. if expected_val > 0 \
  616. else struct.pack('i', expected_val)
  617. elif "i64" == expected_type:
  618. out_val_binary = struct.pack('Q', out_val) if out_val > 0 \
  619. else struct.pack('q', out_val)
  620. expected_val_binary = struct.pack('Q', expected_val) \
  621. if expected_val > 0 \
  622. else struct.pack('q', expected_val)
  623. elif "f32" == expected_type:
  624. out_val_binary = struct.pack('f', out_val)
  625. expected_val_binary = struct.pack('f', expected_val)
  626. elif "f64" == expected_type:
  627. out_val_binary = struct.pack('d', out_val)
  628. expected_val_binary = struct.pack('d', expected_val)
  629. elif "ref.extern" == expected_type:
  630. out_val_binary = out_val
  631. expected_val_binary = expected_val
  632. elif "ref.host" == expected_type:
  633. out_val_binary = out_val
  634. expected_val_binary = expected_val
  635. else:
  636. assert(0), "unknown 'expected_type' {}".format(expected_type)
  637. if out_val_binary == expected_val_binary:
  638. return True
  639. if expected_type in ["f32", "f64"]:
  640. # compare with a lower precision
  641. out_str = "{:.7g}".format(out_val)
  642. expected_str = "{:.7g}".format(expected_val)
  643. if out_str == expected_str:
  644. return True
  645. return False
  646. def value_comparison(out, expected):
  647. if out == expected:
  648. return True
  649. if not expected:
  650. return False
  651. if not out in ["ref.array", "ref.struct", "ref.func", "ref.any", "ref.i31"]:
  652. assert(':' in out), "out should be in a form likes numbers:type, but {}".format(out)
  653. if not expected in ["ref.array", "ref.struct", "ref.func", "ref.any", "ref.i31"]:
  654. assert(':' in expected), "expected should be in a form likes numbers:type, but {}".format(expected)
  655. if 'v128' in out:
  656. return vector_value_comparison(out, expected)
  657. else:
  658. return simple_value_comparison(out, expected)
  659. def is_result_match_expected(out, expected):
  660. # compare value instead of comparing strings of values
  661. return value_comparison(out, expected)
  662. def test_assert(r, opts, mode, cmd, expected):
  663. log("Testing(%s) %s = %s" % (mode, cmd, expected))
  664. out = invoke(r, opts, cmd)
  665. if '\n' in out or ' ' in out:
  666. outs = [''] + out.split('\n')[1:]
  667. out = outs[-1]
  668. if mode=='trap':
  669. o = re.sub('^Exception: ', '', out)
  670. e = re.sub('^Exception: ', '', expected)
  671. if o.find(e) >= 0 or e.find(o) >= 0:
  672. return True
  673. if mode=='exhaustion':
  674. o = re.sub('^Exception: ', '', out)
  675. expected = 'Exception: stack overflow'
  676. e = re.sub('^Exception: ', '', expected)
  677. if o.find(e) >= 0 or e.find(o) >= 0:
  678. return True
  679. # wasm-exception thrown out of function call, not a trap
  680. if mode=='wasmexception':
  681. o = re.sub('^Exception: ', '', out)
  682. e = re.sub('^Exception: ', '', expected)
  683. if o.find(e) >= 0 or e.find(o) >= 0:
  684. return True
  685. ## 0x9:i32,-0x1:i32 -> ['0x9:i32', '-0x1:i32']
  686. expected_list = re.split(r',', expected)
  687. out_list = re.split(r',', out)
  688. if len(expected_list) != len(out_list):
  689. raise Exception("Failed:\n Results count incorrect:\n expected: '%s'\n got: '%s'" % (expected, out))
  690. for i in range(len(expected_list)):
  691. if not is_result_match_expected(out_list[i], expected_list[i]):
  692. raise Exception("Failed:\n Result %d incorrect:\n expected: '%s'\n got: '%s'" % (i, expected_list[i], out_list[i]))
  693. return True
  694. def test_assert_return(r, opts, form):
  695. """
  696. m. to search a pattern like (assert_return (invoke function_name ... ) ...)
  697. n. to search a pattern like (assert_return (invoke $module_name function_name ... ) ...)
  698. """
  699. # params, return
  700. m = re.search(r'^\(assert_return\s+\(invoke\s+"((?:[^"]|\\\")*)"\s+(\(.*\))\s*\)\s*(\(.*\))\s*\)\s*$', form, re.S)
  701. # judge if assert_return cmd includes the module name
  702. n = re.search(r'^\(assert_return\s+\(invoke\s+\$((?:[^\s])*)\s+"((?:[^"]|\\\")*)"\s+(\(.*\))\s*\)\s*(\(.*\))\s*\)\s*$', form, re.S)
  703. # print("assert_return with {}".format(form))
  704. if not m:
  705. # no params, return
  706. m = re.search(r'^\(assert_return\s+\(invoke\s+"((?:[^"]|\\\")*)"\s*\)\s+()(\(.*\))\s*\)\s*$', form, re.S)
  707. if not m:
  708. # params, no return
  709. m = re.search(r'^\(assert_return\s+\(invoke\s+"([^"]*)"\s+(\(.*\))()\s*\)\s*\)\s*$', form, re.S)
  710. if not m:
  711. # no params, no return
  712. m = re.search(r'^\(assert_return\s+\(invoke\s+"([^"]*)"\s*()()\)\s*\)\s*$', form, re.S)
  713. if not m:
  714. # params, return
  715. if not n:
  716. # no params, return
  717. n = re.search(r'^\(assert_return\s+\(invoke\s+\$((?:[^\s])*)\s+"((?:[^"]|\\\")*)"\s*\)\s+()(\(.*\))\s*\)\s*$', form, re.S)
  718. if not n:
  719. # params, no return
  720. n = re.search(r'^\(assert_return\s+\(invoke\s+\$((?:[^\s])*)\s+"([^"]*)"\s+(\(.*\))()\s*\)\s*\)\s*$', form, re.S)
  721. if not n:
  722. # no params, no return
  723. n = re.search(r'^\(assert_return\s+\(invoke\s+\$((?:[^\s])*)\s+"([^"]*)"*()()\)\s*\)\s*$', form, re.S)
  724. if not m and not n:
  725. if re.search(r'^\(assert_return\s+\(get.*\).*\)$', form, re.S):
  726. log("ignoring assert_return get")
  727. return
  728. else:
  729. raise Exception("unparsed assert_return: '%s'" % form)
  730. if m and not n:
  731. func = m.group(1)
  732. if ' ' in func:
  733. func = func.replace(' ', '\\')
  734. # Note: 'as-memory.grow-first' doesn't actually grow memory.
  735. # (thus not in this list)
  736. if opts.qemu and opts.target == 'xtensa' and func in {'as-memory.grow-value', 'as-memory.grow-size', 'as-memory.grow-last', 'as-memory.grow-everywhere'}:
  737. log("ignoring memory.grow test")
  738. return
  739. if m.group(2) == '':
  740. args = []
  741. else:
  742. #args = [re.split(r' +', v)[1].replace('_', "") for v in re.split(r"\)\s*\(", m.group(2)[1:-1])]
  743. # split arguments with ')spaces(', remove leading and tailing ) and (
  744. args_type_and_value = re.split(r'\)\s+\(', m.group(2)[1:-1])
  745. args_type_and_value = [s.replace('_', '') for s in args_type_and_value]
  746. # args are in two forms:
  747. # f32.const -0x1.000001fffffffffffp-50
  748. # v128.const i32x4 0 0 0 0
  749. args = []
  750. for arg in args_type_and_value:
  751. # remove leading and tailing spaces, it might confuse following assertions
  752. arg = arg.strip()
  753. splitted = re.split(r'\s+', arg)
  754. splitted = [s for s in splitted if s]
  755. if splitted[0] in ["i32.const", "i64.const"]:
  756. assert(2 == len(splitted)), "{} should have two parts".format(splitted)
  757. # in wast 01234 means 1234
  758. # in c 0123 means 83 in oct
  759. number, _ = parse_simple_const_w_type(splitted[1], splitted[0][:3])
  760. args.append(str(number))
  761. elif splitted[0] in ["f32.const", "f64.const"]:
  762. # let strtof or strtod handle original arguments
  763. assert(2 == len(splitted)), "{} should have two parts".format(splitted)
  764. args.append(splitted[1])
  765. elif "v128.const" == splitted[0]:
  766. assert(len(splitted) > 2), "{} should have more than two parts".format(splitted)
  767. numbers, _ = cast_v128_to_i64x2(splitted[2:], 'v128', splitted[1])
  768. assert(len(numbers) == 2), "has to reform arguments into i64x2"
  769. args.append(f"{numbers[0]:#x}\\{numbers[1]:#x}")
  770. elif "ref.null" == splitted[0]:
  771. args.append("null")
  772. elif "ref.extern" == splitted[0]:
  773. number, _ = parse_simple_const_w_type(splitted[1], splitted[0])
  774. args.append(str(number))
  775. elif "ref.host" == splitted[0]:
  776. number, _ = parse_simple_const_w_type(splitted[1], splitted[0])
  777. args.append(str(number))
  778. else:
  779. assert(0), "an unkonwn parameter type"
  780. if m.group(3) == '':
  781. returns= []
  782. else:
  783. returns = re.split(r"\)\s*\(", m.group(3)[1:-1])
  784. # processed numbers in strings
  785. if len(returns) == 1 and returns[0] in ["ref.array", "ref.struct", "ref.i31",
  786. "ref.eq", "ref.any", "ref.extern",
  787. "ref.func", "ref.null"]:
  788. expected = [returns[0]]
  789. elif len(returns) == 1 and returns[0] in ["func:ref.null", "any:ref.null",
  790. "extern:ref.null"]:
  791. expected = [returns[0]]
  792. else:
  793. expected = [parse_assertion_value(v)[1] for v in returns]
  794. expected = ",".join(expected)
  795. test_assert(r, opts, "return", "%s %s" % (func, " ".join(args)), expected)
  796. elif not m and n:
  797. module = temp_module_table[n.group(1)].split(".wasm")[0]
  798. # assume the cmd is (assert_return(invoke $ABC "func")).
  799. # run the ABC.wasm firstly
  800. if test_aot:
  801. r = compile_wasm_to_aot(module+".wasm", module+".aot", True, opts, r)
  802. try:
  803. assert_prompt(r, ['Compile success'], opts.start_timeout, False)
  804. except:
  805. _, exc, _ = sys.exc_info()
  806. log("Run wamrc failed:\n got: '%s'" % r.buf)
  807. raise Exception("Run wamrc failed 1")
  808. r = run_wasm_with_repl(module+".wasm", module+".aot" if test_aot else module, opts, r)
  809. # Wait for the initial prompt
  810. try:
  811. assert_prompt(r, ['webassembly> '], opts.start_timeout, False)
  812. except:
  813. _, exc, _ = sys.exc_info()
  814. raise Exception("Failed:\n expected: '%s'\n got: '%s'" % \
  815. (repr(exc), r.buf))
  816. func = n.group(2)
  817. if ' ' in func:
  818. func = func.replace(' ', '\\')
  819. if n.group(3) == '':
  820. args=[]
  821. else:
  822. # convert (ref.null extern/func) into (ref.null null)
  823. n1 = n.group(3).replace("(ref.null extern)", "(ref.null null)")
  824. n1 = n1.replace("ref.null func)", "(ref.null null)")
  825. args = [re.split(r' +', v)[1] for v in re.split(r"\)\s*\(", n1[1:-1])]
  826. _, expected = parse_assertion_value(n.group(4)[1:-1])
  827. test_assert(r, opts, "return", "%s %s" % (func, " ".join(args)), expected)
  828. def test_assert_trap(r, opts, form):
  829. # params
  830. m = re.search(r'^\(assert_trap\s+\(invoke\s+"([^"]*)"\s+(\(.*\))\s*\)\s*"([^"]+)"\s*\)\s*$', form)
  831. # judge if assert_return cmd includes the module name
  832. n = re.search(r'^\(assert_trap\s+\(invoke\s+\$((?:[^\s])*)\s+"([^"]*)"\s+(\(.*\))\s*\)\s*"([^"]+)"\s*\)\s*$', form, re.S)
  833. if not m:
  834. # no params
  835. m = re.search(r'^\(assert_trap\s+\(invoke\s+"([^"]*)"\s*()\)\s*"([^"]+)"\s*\)\s*$', form)
  836. if not m:
  837. if not n:
  838. # no params
  839. n = re.search(r'^\(assert_trap\s+\(invoke\s+\$((?:[^\s])*)\s+"([^"]*)"\s*()\)\s*"([^"]+)"\s*\)\s*$', form, re.S)
  840. if not m and not n:
  841. raise Exception("unparsed assert_trap: '%s'" % form)
  842. if m and not n:
  843. func = m.group(1)
  844. if m.group(2) == '':
  845. args = []
  846. else:
  847. # convert (ref.null extern/func) into (ref.null null)
  848. m1 = m.group(2).replace("(ref.null extern)", "(ref.null null)")
  849. m1 = m1.replace("ref.null func)", "(ref.null null)")
  850. args = [re.split(r' +', v)[1] for v in re.split(r"\)\s*\(", m1[1:-1])]
  851. expected = "Exception: %s" % m.group(3)
  852. test_assert(r, opts, "trap", "%s %s" % (func, " ".join(args)), expected)
  853. elif not m and n:
  854. module = n.group(1)
  855. module = tempfile.gettempdir() + "/" + module
  856. # will trigger the module named in assert_return(invoke $ABC).
  857. # run the ABC.wasm firstly
  858. if test_aot:
  859. r = compile_wasm_to_aot(module+".wasm", module+".aot", True, opts, r)
  860. try:
  861. assert_prompt(r, ['Compile success'], opts.start_timeout, False)
  862. except:
  863. _, exc, _ = sys.exc_info()
  864. log("Run wamrc failed:\n got: '%s'" % r.buf)
  865. raise Exception("Run wamrc failed 2")
  866. r = run_wasm_with_repl(module+".wasm", module+".aot" if test_aot else module, opts, r)
  867. # Wait for the initial prompt
  868. try:
  869. assert_prompt(r, ['webassembly> '], opts.start_timeout, False)
  870. except:
  871. _, exc, _ = sys.exc_info()
  872. raise Exception("Failed:\n expected: '%s'\n got: '%s'" % \
  873. (repr(exc), r.buf))
  874. func = n.group(2)
  875. if n.group(3) == '':
  876. args = []
  877. else:
  878. args = [re.split(r' +', v)[1] for v in re.split(r"\)\s*\(", n.group(3)[1:-1])]
  879. expected = "Exception: %s" % n.group(4)
  880. test_assert(r, opts, "trap", "%s %s" % (func, " ".join(args)), expected)
  881. def test_assert_exhaustion(r,opts,form):
  882. # params
  883. m = re.search(r'^\(assert_exhaustion\s+\(invoke\s+"([^"]*)"\s+(\(.*\))\s*\)\s*"([^"]+)"\s*\)\s*$', form)
  884. if not m:
  885. # no params
  886. m = re.search(r'^\(assert_exhaustion\s+\(invoke\s+"([^"]*)"\s*()\)\s*"([^"]+)"\s*\)\s*$', form)
  887. if not m:
  888. raise Exception("unparsed assert_exhaustion: '%s'" % form)
  889. func = m.group(1)
  890. if m.group(2) == '':
  891. args = []
  892. else:
  893. args = [re.split(r' +', v)[1] for v in re.split(r"\)\s*\(", m.group(2)[1:-1])]
  894. expected = "Exception: %s\n" % m.group(3)
  895. test_assert(r, opts, "exhaustion", "%s %s" % (func, " ".join(args)), expected)
  896. # added to support WASM_ENABLE_EXCE_HANDLING
  897. def test_assert_wasmexception(r,opts,form):
  898. # params
  899. # ^
  900. # \(assert_exception\s+
  901. # \(invoke\s+"([^"]+)"\s+
  902. # (\(.*\))\s*
  903. # ()
  904. # \)\s*
  905. # \)\s*
  906. # $
  907. m = re.search(r'^\(assert_exception\s+\(invoke\s+"([^"]+)"\s+(\(.*\))\s*\)\s*\)\s*$', form)
  908. if not m:
  909. # no params
  910. # ^
  911. # \(assert_exception\s+
  912. # \(invoke\s+"([^"]+)"\s*
  913. # ()
  914. # \)\s*
  915. # \)\s*
  916. # $
  917. m = re.search(r'^\(assert_exception\s+\(invoke\s+"([^"]+)"\s*()\)\s*\)\s*$', form)
  918. if not m:
  919. raise Exception("unparsed assert_exception: '%s'" % form)
  920. func = m.group(1) # function name
  921. if m.group(2) == '': # arguments
  922. args = []
  923. else:
  924. args = [re.split(r' +', v)[1] for v in re.split(r"\)\s*\(", m.group(2)[1:-1])]
  925. expected = "Exception: uncaught wasm exception\n"
  926. test_assert(r, opts, "wasmexception", "%s %s" % (func, " ".join(args)), expected)
  927. def do_invoke(r, opts, form):
  928. # params
  929. m = re.search(r'^\(invoke\s+"([^"]+)"\s+(\(.*\))\s*\)\s*$', form)
  930. if not m:
  931. # no params
  932. m = re.search(r'^\(invoke\s+"([^"]+)"\s*()\)\s*$', form)
  933. if not m:
  934. raise Exception("unparsed invoke: '%s'" % form)
  935. func = m.group(1)
  936. if ' ' in func:
  937. func = func.replace(' ', '\\')
  938. if m.group(2) == '':
  939. args = []
  940. else:
  941. args = [re.split(r' +', v)[1] for v in re.split(r"\)\s*\(", m.group(2)[1:-1])]
  942. log("Invoking %s(%s)" % (
  943. func, ", ".join([str(a) for a in args])))
  944. invoke(r, opts, "%s %s" % (func, " ".join(args)))
  945. def skip_test(form, skip_list):
  946. for s in skip_list:
  947. if re.search(s, form):
  948. return True
  949. return False
  950. def compile_wast_to_wasm(form, wast_tempfile, wasm_tempfile, opts):
  951. log("Writing WAST module to '%s'" % wast_tempfile)
  952. with open(wast_tempfile, 'w') as file:
  953. file.write(form)
  954. log("Compiling WASM to '%s'" % wasm_tempfile)
  955. # default arguments
  956. if opts.gc:
  957. cmd = [opts.wast2wasm, "-u", "-d", wast_tempfile, "-o", wasm_tempfile]
  958. elif opts.eh:
  959. cmd = [opts.wast2wasm, "--enable-threads", "--no-check", "--enable-exceptions", "--enable-tail-call", wast_tempfile, "-o", wasm_tempfile ]
  960. elif opts.memory64:
  961. cmd = [opts.wast2wasm, "--enable-memory64", "--no-check", wast_tempfile, "-o", wasm_tempfile ]
  962. elif opts.multi_memory:
  963. cmd = [opts.wast2wasm, "--enable-multi-memory", "--no-check", wast_tempfile, "-o", wasm_tempfile ]
  964. elif opts.extended_const:
  965. cmd = [opts.wast2wasm, "--enable-extended-const", "--no-check", wast_tempfile, "-o", wasm_tempfile ]
  966. else:
  967. # `--enable-multi-memory` for a case in memory.wast but doesn't require runtime support
  968. cmd = [opts.wast2wasm, "--enable-multi-memory", "--enable-threads", "--no-check",
  969. wast_tempfile, "-o", wasm_tempfile ]
  970. # remove reference-type and bulk-memory enabling options since a WABT
  971. # commit 30c1e983d30b33a8004b39fd60cbd64477a7956c
  972. # Enable reference types by default (#1729)
  973. log("Running: %s" % " ".join(cmd))
  974. try:
  975. subprocess.check_call(cmd)
  976. except Exception as e:
  977. print(e)
  978. return False
  979. return True
  980. def compile_wasm_to_aot(wasm_tempfile, aot_tempfile, runner, opts, r, output = 'default'):
  981. log("Compiling '%s' to '%s'" % (wasm_tempfile, aot_tempfile))
  982. cmd = [opts.aot_compiler]
  983. if test_target in aot_target_options_map:
  984. cmd += aot_target_options_map[test_target]
  985. if opts.sgx:
  986. cmd.append("-sgx")
  987. if not opts.simd:
  988. cmd.append("--disable-simd")
  989. if opts.xip:
  990. cmd.append("--xip")
  991. if test_target in aot_target_options_map_xip:
  992. cmd += aot_target_options_map_xip[test_target]
  993. if opts.multi_thread:
  994. cmd.append("--enable-multi-thread")
  995. if opts.gc:
  996. cmd.append("--enable-gc")
  997. cmd.append("--enable-tail-call")
  998. if opts.extended_const:
  999. cmd.append("--enable-extended-const")
  1000. if output == 'object':
  1001. cmd.append("--format=object")
  1002. elif output == 'ir':
  1003. cmd.append("--format=llvmir-opt")
  1004. # disable llvm link time optimization as it might convert
  1005. # code of tail call into code of dead loop, and stack overflow
  1006. # exception isn't thrown in several cases
  1007. cmd.append("--disable-llvm-lto")
  1008. # Bounds checks is disabled by default for 64-bit targets, to
  1009. # use the hardware based bounds checks. But it is not supported
  1010. # in QEMU with NuttX and in memory64 mode.
  1011. # Enable bounds checks explicitly for all targets if running in QEMU or all targets
  1012. # running in memory64 mode.
  1013. if opts.qemu or opts.memory64:
  1014. cmd.append("--bounds-checks=1")
  1015. cmd += ["-o", aot_tempfile, wasm_tempfile]
  1016. log("Running: %s" % " ".join(cmd))
  1017. if not runner:
  1018. subprocess.check_call(cmd)
  1019. else:
  1020. if (r != None):
  1021. r.cleanup()
  1022. r = Runner(cmd, no_pty=opts.no_pty)
  1023. return r
  1024. def run_wasm_with_repl(wasm_tempfile, aot_tempfile, opts, r):
  1025. tmpfile = aot_tempfile if test_aot else wasm_tempfile
  1026. log("Starting interpreter for module '%s'" % tmpfile)
  1027. if opts.qemu:
  1028. tmpfile = f"/tmp/{os.path.basename(tmpfile)}"
  1029. cmd_iwasm = [opts.interpreter, "--heap-size=0", "--repl"]
  1030. if opts.multi_module:
  1031. cmd_iwasm.append("--module-path=" + (tempfile.gettempdir() if not opts.qemu else "/tmp" ))
  1032. if opts.gc:
  1033. # our tail-call implementation is known broken.
  1034. # work it around by using a huge stack.
  1035. # cf. https://github.com/bytecodealliance/wasm-micro-runtime/issues/2231
  1036. cmd_iwasm.append("--stack-size=10485760") # 10MB (!)
  1037. else:
  1038. if opts.aot:
  1039. # Note: aot w/o gc doesn't require the interpreter stack at all.
  1040. # Note: 1 is the minimum value we can specify because 0 means
  1041. # the default.
  1042. cmd_iwasm.append("--stack-size=1")
  1043. else:
  1044. cmd_iwasm.append("--stack-size=131072") # 128KB
  1045. if opts.verbose:
  1046. cmd_iwasm.append("-v=5")
  1047. cmd_iwasm.append(tmpfile)
  1048. if opts.qemu:
  1049. if opts.qemu_firmware == '':
  1050. raise Exception("QEMU firmware missing")
  1051. if opts.target.startswith("aarch64"):
  1052. cmd = "qemu-system-aarch64 -cpu cortex-a53 -nographic -machine virt,virtualization=on,gic-version=3 -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel".split()
  1053. cmd.append(opts.qemu_firmware)
  1054. elif opts.target.startswith("thumbv7"):
  1055. cmd = "qemu-system-arm -semihosting -M sabrelite -m 1024 -smp 1 -nographic -kernel".split()
  1056. cmd.append(opts.qemu_firmware)
  1057. elif opts.target.startswith("riscv32"):
  1058. cmd = "qemu-system-riscv32 -semihosting -M virt,aclint=on -cpu rv32 -smp 1 -nographic -bios none -kernel".split()
  1059. cmd.append(opts.qemu_firmware)
  1060. elif opts.target.startswith("riscv64"):
  1061. cmd = "qemu-system-riscv64 -semihosting -M virt,aclint=on -cpu rv64 -smp 1 -nographic -bios none -kernel".split()
  1062. cmd.append(opts.qemu_firmware)
  1063. elif opts.target.startswith("xtensa"):
  1064. cmd = f"qemu-system-xtensa -semihosting -nographic -serial mon:stdio -machine esp32s3 -drive file={opts.qemu_firmware},if=mtd,format=raw".split()
  1065. else:
  1066. raise Exception("Unknwon target for QEMU: %s" % opts.target)
  1067. else:
  1068. cmd = cmd_iwasm
  1069. log("Running: %s" % " ".join(cmd))
  1070. if (r != None):
  1071. r.cleanup()
  1072. r = Runner(cmd, no_pty=opts.no_pty)
  1073. if opts.qemu:
  1074. r.read_to_prompt(['nsh> '], 10)
  1075. r.writeline("mount -t hostfs -o fs={} /tmp".format(tempfile.gettempdir()))
  1076. r.read_to_prompt(['nsh> '], 10)
  1077. r.writeline(" ".join(cmd_iwasm))
  1078. return r
  1079. def create_tmpfiles(file_name, test_aot, temp_file_repo):
  1080. tempfiles = []
  1081. tempfiles.append(create_tmp_file(file_name, ".wast"))
  1082. tempfiles.append(create_tmp_file(file_name, ".wasm"))
  1083. if test_aot:
  1084. tempfiles.append(create_tmp_file(file_name, ".aot"))
  1085. else:
  1086. tempfiles.append(None)
  1087. assert len(tempfiles) == 3, "tempfiles should have 3 elements"
  1088. # add these temp file to temporal repo, will be deleted when finishing the test
  1089. temp_file_repo.extend(tempfiles)
  1090. return tempfiles
  1091. def test_assert_with_exception(form, wast_tempfile, wasm_tempfile, aot_tempfile, opts, r, loadable = True):
  1092. details_inside_ast = get_module_exp_from_assert(form)
  1093. log("module is ....'%s'"%details_inside_ast[0])
  1094. log("exception is ....'%s'"%details_inside_ast[1])
  1095. # parse the module
  1096. module = details_inside_ast[0]
  1097. expected = details_inside_ast[1]
  1098. if not compile_wast_to_wasm(module, wast_tempfile, wasm_tempfile, opts):
  1099. raise Exception("compile wast to wasm failed")
  1100. if test_aot:
  1101. r = compile_wasm_to_aot(wasm_tempfile, aot_tempfile, True, opts, r)
  1102. if not loadable:
  1103. return
  1104. try:
  1105. assert_prompt(r, ['Compile success'], opts.start_fail_timeout, True)
  1106. except:
  1107. _, exc, _ = sys.exc_info()
  1108. if (r.buf.find(expected) >= 0):
  1109. log("Out exception includes expected one, pass:")
  1110. log(" Expected: %s" % expected)
  1111. log(" Got: %s" % r.buf)
  1112. return
  1113. else:
  1114. log("Run wamrc failed:\n expected: '%s'\n got: '%s'" % \
  1115. (expected, r.buf))
  1116. raise Exception("Run wamrc failed 3")
  1117. r = run_wasm_with_repl(wasm_tempfile, aot_tempfile if test_aot else None, opts, r)
  1118. # Some module couldn't load so will raise an error directly, so shell prompt won't show here
  1119. if loadable:
  1120. # Wait for the initial prompt
  1121. try:
  1122. assert_prompt(r, ['webassembly> '], opts.start_fail_timeout, True)
  1123. except:
  1124. _, exc, _ = sys.exc_info()
  1125. if (r.buf.find(expected) >= 0):
  1126. log("Out exception includes expected one, pass:")
  1127. log(" Expected: %s" %expected)
  1128. log(" Got: %s" % r.buf)
  1129. else:
  1130. raise Exception("Failed:\n expected: '%s'\n got: '%s'" % \
  1131. (expected, r.buf))
  1132. def recently_added_wasm(temp_file_repo):
  1133. for f in reversed(temp_file_repo):
  1134. if not f:
  1135. continue
  1136. assert os.path.exists(f), f"temp file {f} should exist"
  1137. if os.path.getsize(f) == 0:
  1138. continue
  1139. if f.endswith(".wasm"):
  1140. return f
  1141. if __name__ == "__main__":
  1142. opts = parser.parse_args(sys.argv[1:])
  1143. # print('Input param :',opts)
  1144. if opts.aot: test_aot = True
  1145. # default x86_64
  1146. test_target = opts.target
  1147. if opts.rundir: os.chdir(opts.rundir)
  1148. if opts.log_file: log_file = open(opts.log_file, "a")
  1149. if opts.debug_file: debug_file = open(opts.debug_file, "a")
  1150. if opts.interpreter.endswith(".py"):
  1151. SKIP_TESTS = PY_SKIP_TESTS
  1152. else:
  1153. SKIP_TESTS = C_SKIP_TESTS
  1154. case_file = pathlib.Path(opts.test_file.name)
  1155. assert(case_file.exists()), f"Test file {case_file} doesn't exist"
  1156. tmpfile_stem = case_file.stem + "_"
  1157. ret_code = 0
  1158. try:
  1159. log("\n################################################")
  1160. log("### Testing %s" % opts.test_file.name)
  1161. log("################################################")
  1162. forms = read_forms(opts.test_file.read())
  1163. r = None
  1164. for form in forms:
  1165. # log("\n### Current Case is " + form + "\n")
  1166. wast_tempfile, wasm_tempfile, aot_tempfile = create_tmpfiles(
  1167. tmpfile_stem, test_aot, temp_file_repo)
  1168. if ";;" == form[0:2]:
  1169. log(form)
  1170. elif skip_test(form, SKIP_TESTS):
  1171. log("Skipping test: %s" % form[0:60])
  1172. elif re.match(r"^\(assert_trap\s+\(module", form):
  1173. test_assert_with_exception(form, wast_tempfile, wasm_tempfile, aot_tempfile if test_aot else None, opts, r)
  1174. elif re.match(r"^\(assert_exhaustion\b.*", form):
  1175. test_assert_exhaustion(r, opts, form)
  1176. elif re.match(r"^\(assert_exception\b.*", form):
  1177. test_assert_wasmexception(r, opts, form)
  1178. elif re.match(r"^\(assert_unlinkable\b.*", form):
  1179. test_assert_with_exception(form, wast_tempfile, wasm_tempfile, aot_tempfile if test_aot else None, opts, r, False)
  1180. elif re.match(r"^\(assert_malformed\b.*", form):
  1181. # remove comments in wast
  1182. form,n = re.subn(";;.*\n", "", form)
  1183. m = re.match(r"^\(assert_malformed\s*\(module binary\s*(\".*\").*\)\s*\"(.*)\"\s*\)$", form, re.DOTALL)
  1184. if m:
  1185. # workaround: spec test changes error message to "malformed" while iwasm still use "invalid"
  1186. error_msg = m.group(2).replace("malformed", "invalid")
  1187. log("Testing(malformed)")
  1188. with open(wasm_tempfile, 'wb') as f:
  1189. s = m.group(1)
  1190. while s:
  1191. res = re.match(r"[^\"]*\"([^\"]*)\"(.*)", s, re.DOTALL)
  1192. if IS_PY_3:
  1193. context = res.group(1).replace("\\", "\\x").encode("latin1").decode("unicode-escape").encode("latin1")
  1194. f.write(context)
  1195. else:
  1196. f.write(res.group(1).replace("\\", "\\x").decode("string-escape"))
  1197. s = res.group(2)
  1198. # compile wasm to aot
  1199. if test_aot:
  1200. r = compile_wasm_to_aot(wasm_tempfile, aot_tempfile, True, opts, r)
  1201. try:
  1202. assert_prompt(r, ['Compile success'], opts.start_fail_timeout, True)
  1203. except:
  1204. _, exc, _ = sys.exc_info()
  1205. if (r.buf.find(error_msg) >= 0):
  1206. log("Out exception includes expected one, pass:")
  1207. log(" Expected: %s" % error_msg)
  1208. log(" Got: %s" % r.buf)
  1209. continue
  1210. # one case in binary.wast
  1211. elif (error_msg == "unexpected end of section or function"
  1212. and r.buf.find("unexpected end")):
  1213. continue
  1214. # one case in binary.wast
  1215. elif (error_msg == "invalid value type"
  1216. and r.buf.find("unexpected end")):
  1217. continue
  1218. # one case in binary.wast
  1219. elif (error_msg == "integer too large"
  1220. and r.buf.find("tables cannot be shared")):
  1221. continue
  1222. # one case in binary.wast
  1223. elif (error_msg == "zero byte expected"
  1224. and r.buf.find("unknown table")):
  1225. continue
  1226. # one case in binary.wast
  1227. elif (error_msg == "invalid section id"
  1228. and r.buf.find("unexpected end of section or function")):
  1229. continue
  1230. # one case in binary.wast
  1231. elif (error_msg == "illegal opcode"
  1232. and r.buf.find("unexpected end of section or function")):
  1233. continue
  1234. # one case in custom.wast
  1235. elif (error_msg == "length out of bounds"
  1236. and r.buf.find("unexpected end")):
  1237. continue
  1238. # several cases in binary-leb128.wast
  1239. elif (error_msg == "integer representation too long"
  1240. and r.buf.find("invalid section id")):
  1241. continue
  1242. else:
  1243. log("Run wamrc failed:\n expected: '%s'\n got: '%s'" % \
  1244. (error_msg, r.buf))
  1245. raise Exception("Run wamrc failed 4")
  1246. r = run_wasm_with_repl(wasm_tempfile, aot_tempfile if test_aot else None, opts, r)
  1247. elif re.match(r"^\(assert_malformed\s*\(module quote", form):
  1248. log("ignoring assert_malformed module quote")
  1249. else:
  1250. log("unrecognized assert_malformed")
  1251. elif re.match(r"^\(assert_return[_a-z]*_nan\b.*", form):
  1252. log("ignoring assert_return_.*_nan")
  1253. pass
  1254. elif re.match(r".*\(invoke\s+\$\b.*", form):
  1255. # invoke a particular named module's function
  1256. if form.startswith("(assert_return"):
  1257. test_assert_return(r,opts,form)
  1258. elif form.startswith("(assert_trap"):
  1259. test_assert_trap(r,opts,form)
  1260. elif re.match(r"^\(module\b.*", form):
  1261. # if the module includes the particular name startswith $
  1262. m = re.search(r"^\(module\s+\$.\S+", form)
  1263. if m:
  1264. # get module name
  1265. module_name = re.split(r'\$', m.group(0).strip())[1]
  1266. if module_name:
  1267. # create temporal files
  1268. temp_files = create_tmpfiles(module_name, test_aot, temp_file_repo)
  1269. if not compile_wast_to_wasm(form, temp_files[0], temp_files[1], opts):
  1270. raise Exception("compile wast to wasm failed")
  1271. if test_aot:
  1272. r = compile_wasm_to_aot(temp_files[1], temp_files[2], True, opts, r)
  1273. try:
  1274. assert_prompt(r, ['Compile success'], opts.start_timeout, False)
  1275. except:
  1276. _, exc, _ = sys.exc_info()
  1277. log("Run wamrc failed:\n got: '%s'" % r.buf)
  1278. raise Exception("Run wamrc failed 5")
  1279. temp_module_table[module_name] = temp_files[1]
  1280. r = run_wasm_with_repl(temp_files[1], temp_files[2] if test_aot else None, opts, r)
  1281. else:
  1282. if not compile_wast_to_wasm(form, wast_tempfile, wasm_tempfile, opts):
  1283. raise Exception("compile wast to wasm failed")
  1284. if test_aot:
  1285. r = compile_wasm_to_aot(wasm_tempfile, aot_tempfile, True, opts, r)
  1286. try:
  1287. assert_prompt(r, ['Compile success'], opts.start_timeout, False)
  1288. except:
  1289. _, exc, _ = sys.exc_info()
  1290. log("Run wamrc failed:\n got: '%s'" % r.buf)
  1291. raise Exception("Run wamrc failed 6")
  1292. r = run_wasm_with_repl(wasm_tempfile, aot_tempfile if test_aot else None, opts, r)
  1293. # Wait for the initial prompt
  1294. try:
  1295. assert_prompt(r, ['webassembly> '], opts.start_timeout, False)
  1296. except:
  1297. _, exc, _ = sys.exc_info()
  1298. raise Exception("Failed:\n expected: '%s'\n got: '%s'" % \
  1299. (repr(exc), r.buf))
  1300. elif re.match(r"^\(assert_return\b.*", form):
  1301. assert(r), "iwasm repl runtime should be not null"
  1302. test_assert_return(r, opts, form)
  1303. elif re.match(r"^\(assert_trap\b.*", form):
  1304. test_assert_trap(r, opts, form)
  1305. elif re.match(r"^\(invoke\b.*", form):
  1306. assert(r), "iwasm repl runtime should be not null"
  1307. do_invoke(r, opts, form)
  1308. elif re.match(r"^\(assert_invalid\b.*", form):
  1309. # loading invalid module will raise an error directly, so shell prompt won't show here
  1310. test_assert_with_exception(form, wast_tempfile, wasm_tempfile, aot_tempfile if test_aot else None, opts, r, False)
  1311. elif re.match(r"^\(register\b.*", form):
  1312. # get module's new name from the register cmd
  1313. name_new =re.split(r'\"',re.search(r'\".*\"',form).group(0))[1]
  1314. if not name_new:
  1315. # there is no name defined in register cmd
  1316. raise Exception(f"Not following register cmd pattern {form}")
  1317. # assumption
  1318. # - There exists a module in the form of (module $name).
  1319. # - The nearest module in the form of (module), without $name, is the candidate for registration.
  1320. recently_wasm = recently_added_wasm(temp_file_repo)
  1321. if not name_new in temp_module_table:
  1322. print(temp_file_repo)
  1323. print(f"Module {name_new} is not found in temp_module_table. use the nearest module {recently_wasm}")
  1324. for_registration = temp_module_table.get(name_new, recently_wasm)
  1325. assert os.path.exists(for_registration), f"module {for_registration} is not found"
  1326. new_module = os.path.join(tempfile.gettempdir(), name_new + ".wasm")
  1327. # for_registration(tmpfile) --copy-> name_new.wasm
  1328. shutil.copyfile(for_registration, new_module)
  1329. # add new_module copied from the old into temp_file_repo[]
  1330. temp_file_repo.append(new_module)
  1331. if test_aot:
  1332. new_module_aot = os.path.join(tempfile.gettempdir(), name_new + ".aot")
  1333. try:
  1334. compile_wasm_to_aot(new_module, new_module_aot, None, opts, r)
  1335. except Exception as e:
  1336. raise Exception(f"compile wasm to aot failed. {e}")
  1337. # add aot module into temp_file_repo[]
  1338. temp_file_repo.append(new_module_aot)
  1339. else:
  1340. raise Exception("unrecognized form '%s...'" % form[0:40])
  1341. except Exception as e:
  1342. traceback.print_exc()
  1343. print("THE FINAL EXCEPTION IS {}".format(e))
  1344. ret_code = 101
  1345. try:
  1346. shutil.copyfile(wasm_tempfile, os.path.join(opts.log_dir, os.path.basename(wasm_tempfile)))
  1347. if opts.aot or opts.xip:
  1348. shutil.copyfile(aot_tempfile, os.path.join(opts.log_dir,os.path.basename(aot_tempfile)))
  1349. if "indirect-mode" in str(e):
  1350. compile_wasm_to_aot(wasm_tempfile, aot_tempfile, None, opts, None, "object")
  1351. shutil.copyfile(aot_tempfile, os.path.join(opts.log_dir,os.path.basename(aot_tempfile)+'.o'))
  1352. subprocess.check_call(["llvm-objdump", "-r", aot_tempfile])
  1353. compile_wasm_to_aot(wasm_tempfile, aot_tempfile, None, opts, None, "ir")
  1354. shutil.copyfile(aot_tempfile, os.path.join(opts.log_dir,os.path.basename(aot_tempfile)+".ir"))
  1355. except Exception as e:
  1356. print("Failed to copy files to log directory: %s" % e)
  1357. ret_code = 102
  1358. else:
  1359. ret_code = 0
  1360. finally:
  1361. try:
  1362. if not opts.no_cleanup:
  1363. # remove the files under /tempfiles/ and copy of .wasm files
  1364. log(f"Removing tmp*")
  1365. # log(f"Removing {temp_file_repo}")
  1366. for t in temp_file_repo:
  1367. # None and empty
  1368. if not t:
  1369. continue
  1370. if os.path.exists(t):
  1371. os.remove(t)
  1372. else:
  1373. log(f"Leaving tmp*")
  1374. # log(f"Leaving {temp_file_repo}")
  1375. except Exception as e:
  1376. print("Failed to remove tempfiles: %s" % e)
  1377. # ignore the exception
  1378. ret_code = 0
  1379. log(f"### End testing {opts.test_file.name} with {ret_code}")
  1380. sys.exit(ret_code)