From 58424cf2a052d47ea9bf3a4733dca83da76b6f76 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sat, 6 Aug 2022 20:50:43 +0200 Subject: [PATCH] Adds runner for pywasm --- stubs/pywasm/__init__.pyi | 14 ++++++ stubs/pywasm/binary.pyi | 6 +++ stubs/pywasm/execution.pyi | 10 +++++ stubs/pywasm/option.pyi | 2 + tests/integration/runners.py | 62 ++++++++++++++++++++++++-- tests/integration/test_stdlib_alloc.py | 20 +++++---- 6 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 stubs/pywasm/__init__.pyi create mode 100644 stubs/pywasm/binary.pyi create mode 100644 stubs/pywasm/execution.pyi create mode 100644 stubs/pywasm/option.pyi diff --git a/stubs/pywasm/__init__.pyi b/stubs/pywasm/__init__.pyi new file mode 100644 index 0000000..4d94109 --- /dev/null +++ b/stubs/pywasm/__init__.pyi @@ -0,0 +1,14 @@ +from typing import Any, Dict, List, Optional, Union + +from . import binary +from . import option +from . import execution + +class Runtime: + store: execution.Store + + def __init__(self, module: binary.Module, imps: Optional[Dict[str, Any]] = None, opts: Optional[option.Option] = None): + ... + + def exec(self, name: str, args: List[Union[int, float]]) -> Any: + ... diff --git a/stubs/pywasm/binary.pyi b/stubs/pywasm/binary.pyi new file mode 100644 index 0000000..3af65a0 --- /dev/null +++ b/stubs/pywasm/binary.pyi @@ -0,0 +1,6 @@ +from typing import BinaryIO + +class Module: + @classmethod + def from_reader(cls, reader: BinaryIO) -> 'Module': + ... diff --git a/stubs/pywasm/execution.pyi b/stubs/pywasm/execution.pyi new file mode 100644 index 0000000..54db4fb --- /dev/null +++ b/stubs/pywasm/execution.pyi @@ -0,0 +1,10 @@ +from typing import List + +class Result: + ... + +class MemoryInstance: + data: bytearray + +class Store: + memory_list: List[MemoryInstance] diff --git a/stubs/pywasm/option.pyi b/stubs/pywasm/option.pyi new file mode 100644 index 0000000..fd461ee --- /dev/null +++ b/stubs/pywasm/option.pyi @@ -0,0 +1,2 @@ +class Option: + ... diff --git a/tests/integration/runners.py b/tests/integration/runners.py index 0108fe4..5d26797 100644 --- a/tests/integration/runners.py +++ b/tests/integration/runners.py @@ -1,20 +1,21 @@ """ Runners to help run WebAssembly code on various interpreters """ -from typing import Any, TextIO +from typing import Any, Iterable, TextIO +import io import os import subprocess -import sys import tempfile +import pywasm.binary +import wasm3 + 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 @@ -101,6 +102,18 @@ class RunnerBase: """ raise NotImplementedError + def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None: + """ + Writes into the interpreters memory + """ + raise NotImplementedError + + def interpreter_read_memory(self, offset: int, length: int) -> bytes: + """ + Reads from the interpreters memory + """ + raise NotImplementedError + def interpreter_dump_memory(self, textio: TextIO) -> None: """ Dumps the interpreters memory for debugging @@ -113,6 +126,37 @@ class RunnerBase: """ raise NotImplementedError +class RunnerPywasm(RunnerBase): + """ + Implements a runner for pywasm + + See https://pypi.org/project/pywasm/ + """ + module: pywasm.binary.Module + runtime: pywasm.Runtime + + def interpreter_setup(self) -> None: + # Nothing to set up + pass + + def interpreter_load(self) -> None: + bytesio = io.BytesIO(self.wasm_bin) + self.module = pywasm.binary.Module.from_reader(bytesio) + self.runtime = pywasm.Runtime(self.module, {}, None) + + def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None: + for idx, byt in enumerate(data): + self.runtime.store.memory_list[0].data[offset + idx] = byt + + def interpreter_read_memory(self, offset: int, length: int) -> bytes: + return self.runtime.store.memory_list[0].data[offset:length] + + def interpreter_dump_memory(self, textio: TextIO) -> None: + _dump_memory(textio, self.runtime.store.memory_list[0].data) + + def call(self, function: str, *args: Any) -> Any: + return self.runtime.exec(function, [*args]) + class RunnerPywasm3(RunnerBase): """ Implements a runner for pywasm3 @@ -131,6 +175,16 @@ class RunnerPywasm3(RunnerBase): self.mod = self.env.parse_module(self.wasm_bin) self.rtime.load(self.mod) + def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None: + memory = self.rtime.get_memory(0) + + for idx, byt in enumerate(data): + memory[offset + idx] = byt # type: ignore + + def interpreter_read_memory(self, offset: int, length: int) -> bytes: + memory = self.rtime.get_memory(0) + return memory[offset:length].tobytes() + def interpreter_dump_memory(self, textio: TextIO) -> None: _dump_memory(textio, self.rtime.get_memory(0)) diff --git a/tests/integration/test_stdlib_alloc.py b/tests/integration/test_stdlib_alloc.py index 31c6e00..850c87f 100644 --- a/tests/integration/test_stdlib_alloc.py +++ b/tests/integration/test_stdlib_alloc.py @@ -31,11 +31,9 @@ def testEntry() -> u8: """ runner = setup_interpreter(code_py) - memory = runner.rtime.get_memory(0) # Garbage in the memory so we can test for it - for idx in range(128): - memory[idx] = idx + runner.interpreter_write_memory(0, range(128)) sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n') runner.interpreter_dump_memory(sys.stderr) @@ -45,15 +43,13 @@ def testEntry() -> u8: sys.stderr.write(f'{DASHES} Memory (post run) {DASHES}\n') runner.interpreter_dump_memory(sys.stderr) - memory = memory.tobytes() - assert ( b'\xC0\xA1\x00\x00' b'\x00\x00\x00\x00' b'\x00\x00\x00\x00' b'\x10\x00\x00\x00' b'\x10\x11\x12\x13' # Untouched because unused - ) == memory[0:20] + ) == runner.interpreter_read_memory(0, 20) @pytest.mark.integration_test def test___alloc___no_init(): @@ -68,7 +64,7 @@ def testEntry() -> u8: sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n') runner.interpreter_dump_memory(sys.stderr) - with pytest.raises(RuntimeError, match='unreachable executed'): + with pytest.raises(Exception, match='unreachable'): runner.call('stdlib.alloc.__alloc__', 32) @pytest.mark.integration_test @@ -80,7 +76,6 @@ def testEntry() -> u8: """ runner = setup_interpreter(code_py) - memory = runner.rtime.get_memory(0) sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n') runner.interpreter_dump_memory(sys.stderr) @@ -93,7 +88,14 @@ def testEntry() -> u8: sys.stderr.write(f'{DASHES} Memory (post run) {DASHES}\n') runner.interpreter_dump_memory(sys.stderr) - assert b'\xC0\xA1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' == memory[0:12] + assert ( + b'\xC0\xA1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7C\x00\x00\x00' + b'\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00' + ) == runner.interpreter_read_memory(0, 0x60) assert 0x14 == offset0 assert 0x38 == offset1