""" Runners to help run WebAssembly code on various interpreters """ from typing import Any, TextIO import os import subprocess import sys import tempfile from phasm.compiler import phasm_compile from phasm.parser import phasm_parse from phasm import ourlang from phasm import wasm import wasm3 def wat2wasm(code_wat: str) -> bytes: """ Converts the given WebAssembly Assembly code into WebAssembly Binary """ path = os.environ.get('WAT2WASM', 'wat2wasm') with tempfile.NamedTemporaryFile('w+t') as input_fp: input_fp.write(code_wat) input_fp.flush() with tempfile.NamedTemporaryFile('w+b') as output_fp: subprocess.run( [ path, input_fp.name, '-o', output_fp.name, ], check=True, ) output_fp.seek(0) return output_fp.read() class RunnerBase: """ Base class """ phasm_code: str phasm_ast: ourlang.Module wasm_ast: wasm.Module wasm_asm: str wasm_bin: bytes def __init__(self, phasm_code: str) -> None: self.phasm_code = phasm_code def dump_phasm_code(self, textio: TextIO) -> None: """ Dumps the input Phasm code for debugging """ _dump_code(textio, self.phasm_code) def parse(self) -> None: """ Parses the Phasm code into an AST """ self.phasm_ast = phasm_parse(self.phasm_code) def compile_ast(self) -> None: """ Compiles the Phasm AST into an WebAssembly AST """ self.wasm_ast = phasm_compile(self.phasm_ast) def compile_wat(self) -> None: """ Compiles the WebAssembly AST into WebAssembly Assembly code """ self.wasm_asm = self.wasm_ast.to_wat() def dump_wasm_wat(self, textio: TextIO) -> None: """ Dumps the intermediate WebAssembly Assembly code for debugging """ _dump_code(textio, self.wasm_asm) def compile_wasm(self) -> None: """ Compiles the WebAssembly AST into WebAssembly Binary """ self.wasm_bin = wat2wasm(self.wasm_asm) def interpreter_setup(self) -> None: """ Sets up the interpreter """ raise NotImplementedError def interpreter_load(self) -> None: """ Loads the code into the interpreter """ raise NotImplementedError def interpreter_dump_memory(self, textio: TextIO) -> None: """ Dumps the interpreters memory for debugging """ raise NotImplementedError def call(self, function: str, *args: Any) -> Any: """ Calls the given function with the given arguments, returning the result """ raise NotImplementedError class RunnerPywasm3(RunnerBase): """ Implements a runner for pywasm3 See https://pypi.org/project/pywasm3/ """ env: wasm3.Environment rtime: wasm3.Runtime mod: wasm3.Module def interpreter_setup(self) -> None: self.env = wasm3.Environment() self.rtime = self.env.new_runtime(1024 * 1024) def interpreter_load(self) -> None: self.mod = self.env.parse_module(self.wasm_bin) self.rtime.load(self.mod) def interpreter_dump_memory(self, textio: TextIO) -> None: _dump_memory(textio, self.rtime.get_memory(0)) def call(self, function: str, *args: Any) -> Any: return self.rtime.find_function(function)(*args) def _dump_memory(textio: TextIO, mem: bytes) -> None: line_width = 16 prev_line = None skip = False for idx in range(0, len(mem), line_width): line = '' for idx2 in range(0, line_width): line += f'{mem[idx + idx2]:02X}' if idx2 % 2 == 1: line += ' ' if prev_line == line: if not skip: textio.write('**\n') skip = True else: textio.write(f'{idx:08x} {line}\n') prev_line = line def _dump_code(textio: TextIO, text: str) -> None: line_list = text.split('\n') line_no_width = len(str(len(line_list))) for line_no, line_txt in enumerate(line_list): textio.write('{} {}\n'.format( str(line_no + 1).zfill(line_no_width), line_txt, ))