From f4f068137a49125d533648185e3a27767b5243c4 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Mon, 13 Nov 2023 13:00:34 +0100 Subject: [PATCH] More work on type testing. Also, reduced spam on typing dump by only showing the 'back on todo list' count, rather than repeat them all. Also, typing on tests.integration.helpers --- Makefile | 2 +- phasm/compiler.py | 20 ++- phasm/runtime.py | 10 +- phasm/type3/entry.py | 10 +- tests/integration/helpers.py | 149 +++++++++++++++--- tests/integration/runners.py | 4 +- .../generator_tuple_all_primitives.json | 5 + .../test_lang/generator_tuple_nested.json | 5 + 8 files changed, 173 insertions(+), 32 deletions(-) create mode 100644 tests/integration/test_lang/generator_tuple_all_primitives.json create mode 100644 tests/integration/test_lang/generator_tuple_nested.json diff --git a/Makefile b/Makefile index d2dc3a6..6422080 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ lint: venv/.done venv/bin/pylint phasm typecheck: venv/.done - venv/bin/mypy --strict phasm tests/integration/runners.py + venv/bin/mypy --strict phasm tests/integration/helpers.py tests/integration/runners.py venv/.done: requirements.txt python3.10 -m venv venv diff --git a/phasm/compiler.py b/phasm/compiler.py index c4cca6a..7258ef6 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -16,7 +16,9 @@ from .runtime import calculate_alloc_size, calculate_member_offset from .wasmgenerator import Generator as WasmGenerator LOAD_STORE_TYPE_MAP = { + 'i8': 'i32', # Have to use an u32, since there is no native i8 type 'u8': 'i32', # Have to use an u32, since there is no native u8 type + 'i32': 'i32', 'i64': 'i64', 'u32': 'i32', @@ -211,7 +213,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: if isinstance(inp, ourlang.ConstantPrimitive): assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR - if inp.type3 == type3types.u8: + if inp.type3 in (type3types.i8, type3types.u8, ): # No native u8 type - treat as i32, with caution assert isinstance(inp.value, int) wgn.i32.const(inp.value) @@ -665,7 +667,7 @@ def module_data_u8(inp: int) -> bytes: # FIXME: All u8 values are stored as u32 """ - return struct.pack(' bytes: """ @@ -679,6 +681,14 @@ def module_data_u64(inp: int) -> bytes: """ return struct.pack(' bytes: + """ + Compile: module data, i8 value + + # FIXME: All i8 values are stored as i32 + """ + return struct.pack(' bytes: """ Compile: module data, i32 value @@ -746,6 +756,12 @@ def module_data(inp: ourlang.ModuleData) -> bytes: data_list.append(module_data_u64(constant.value)) continue + if constant.type3 == type3types.i8: + assert isinstance(constant, ourlang.ConstantPrimitive) + assert isinstance(constant.value, int) + data_list.append(module_data_i8(constant.value)) + continue + if constant.type3 == type3types.i32: assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, int) diff --git a/phasm/runtime.py b/phasm/runtime.py index 2b9e659..a09c29c 100644 --- a/phasm/runtime.py +++ b/phasm/runtime.py @@ -2,8 +2,8 @@ from typing import Union from .type3 import types as type3types -def calculate_alloc_size(typ: Union[type3types.StructType3, type3types.Type3], is_member: bool = False) -> int: - if typ == type3types.u8: +def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int: + if typ in (type3types.u8, type3types.i8, ): return 4 # FIXME: We allocate 4 bytes for every u8 since you load them into an i32 if typ in (type3types.u32, type3types.i32, type3types.f32, ): @@ -12,6 +12,12 @@ def calculate_alloc_size(typ: Union[type3types.StructType3, type3types.Type3], i if typ in (type3types.u64, type3types.i64, type3types.f64, ): return 8 + if typ == type3types.bytes: + if is_member: + return 4 + + raise NotImplementedError # When does this happen? + if isinstance(typ, type3types.StructType3): if is_member: # Structs referred to by other structs or tuples are pointers diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py index c14c393..dfeb06e 100644 --- a/phasm/type3/entry.py +++ b/phasm/type3/entry.py @@ -33,6 +33,8 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None: old_constraint_ids = {id(x) for x in constraint_list} old_placeholder_substitutes_len = len(placeholder_substitutes) + back_on_todo_list_count = 0 + new_constraint_list = [] for constraint in constraint_list: check_result = constraint.check() @@ -60,9 +62,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None: if isinstance(check_result, RequireTypeSubstitutes): new_constraint_list.append(constraint) - if verbose: - print_constraint(placeholder_id_map, constraint) - print('-> Back on the todo list') + back_on_todo_list_count += 1 continue if isinstance(check_result, list): @@ -75,6 +75,9 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None: raise NotImplementedError(constraint, check_result) + if verbose and 0 < back_on_todo_list_count: + print(f'{back_on_todo_list_count} constraints skipped for now') + if not new_constraint_list: constraint_list = new_constraint_list break @@ -92,6 +95,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None: constraint_list = new_constraint_list if verbose: + print() print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes) if constraint_list: diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 1ba6826..2f21776 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Generator, Iterable, TextIO import struct import sys @@ -12,7 +12,7 @@ from . import runners DASHES = '-' * 16 class SuiteResult: - def __init__(self): + def __init__(self) -> None: self.returned_value = None RUNNER_CLASS_MAP = { @@ -26,10 +26,10 @@ class Suite: """ WebAssembly test suite """ - def __init__(self, code_py): + def __init__(self, code_py: str) -> None: self.code_py = code_py - def run_code(self, *args, runtime='pywasm3', func_name='testEntry', imports=None): + def run_code(self, *args: Any, runtime: str = 'pywasm3', func_name: str = 'testEntry', imports: runners.Imports = None) -> Any: """ Compiles the given python code into wasm and then runs it @@ -92,7 +92,7 @@ class Suite: return result -def write_header(textio, msg: str) -> None: +def write_header(textio: TextIO, msg: str) -> None: textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n') def _load_memory_stored_returned_value( @@ -102,11 +102,14 @@ def _load_memory_stored_returned_value( ) -> Any: ret_type3 = runner.phasm_ast.functions[func_name].returns_type3 + if ret_type3 is type3types.none: + return None + if ret_type3 in (type3types.i32, type3types.i64): assert isinstance(wasm_value, int), wasm_value return wasm_value - if ret_type3 in (type3types.u32, type3types.u64): + if ret_type3 in (type3types.u8, type3types.u32, type3types.u64): assert isinstance(wasm_value, int), wasm_value assert 0 <= wasm_value, 'TODO: Extract negative values' return wasm_value @@ -117,30 +120,130 @@ def _load_memory_stored_returned_value( if ret_type3 is type3types.bytes: assert isinstance(wasm_value, int), wasm_value - adr = wasm_value - sys.stderr.write(f'Reading 0x{adr:08x}\n') - read_bytes = runner.interpreter_read_memory(adr, 4) - bytes_len, = struct.unpack(' Any: + if typ is type3types.u8: + # See compiler.py, LOAD_STORE_TYPE_MAP and module_data_u8 + assert len(inp) == 4 + return struct.unpack(' bytes: + sys.stderr.write(f'Reading 0x{adr:08x}\n') + read_bytes = runner.interpreter_read_memory(adr, 4) + bytes_len, = struct.unpack(' Generator[bytes, None, None]: + offset = 0 + for size in split_sizes: + yield all_bytes[offset:offset + size] + offset += size + +def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.AppliedType3, adr: int) -> Any: + assert 2 == len(typ.args) + sub_typ, len_typ = typ.args + + assert not isinstance(sub_typ, type3types.PlaceholderForType) + assert isinstance(len_typ, type3types.IntType3) + + sa_len = len_typ.value + + arg_size_1 = calculate_alloc_size(sub_typ, is_member=True) + arg_sizes = [arg_size_1 for _ in range(sa_len)] # _split_read_bytes requires one arg per value + + read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes)) + + return tuple( + _unpack(runner, sub_typ, arg_bytes) + for arg_bytes in _split_read_bytes(read_bytes, arg_sizes) + ) + +def _load_tuple_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any: + assert isinstance(typ, type3types.AppliedType3) + assert typ.base is type3types.tuple + + typ_list = [ + x + for x in typ.args + if not isinstance(x, type3types.PlaceholderForType) + ] + assert len(typ_list) == len(typ.args) + + arg_sizes = [ + calculate_alloc_size(x, is_member=True) + for x in typ_list + ] + + read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes)) + + return tuple( + _unpack(runner, arg_typ, arg_bytes) + for arg_typ, arg_bytes in zip(typ_list, _split_read_bytes(read_bytes, arg_sizes)) + ) diff --git a/tests/integration/runners.py b/tests/integration/runners.py index 803243d..97df7fd 100644 --- a/tests/integration/runners.py +++ b/tests/integration/runners.py @@ -17,6 +17,8 @@ from phasm.type3.entry import phasm_type3 from phasm import ourlang from phasm import wasm +Imports = Optional[Dict[str, Callable[[Any], Any]]] + class RunnerBase: """ Base class @@ -73,7 +75,7 @@ class RunnerBase: """ raise NotImplementedError - def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None: + def interpreter_load(self, imports: Imports = None) -> None: """ Loads the code into the interpreter """ diff --git a/tests/integration/test_lang/generator_tuple_all_primitives.json b/tests/integration/test_lang/generator_tuple_all_primitives.json new file mode 100644 index 0000000..94785ff --- /dev/null +++ b/tests/integration/test_lang/generator_tuple_all_primitives.json @@ -0,0 +1,5 @@ +{ + "TYPE_NAME": "tuple_all_primitives", + "TYPE": "(u8, u32, u64, i8, i32, i64, f32, f64, bytes, )", + "VAL0": "(1, 4, 8, 1, 4, 8, 125.125, 5000.5, b'Hello, world!', )" +} diff --git a/tests/integration/test_lang/generator_tuple_nested.json b/tests/integration/test_lang/generator_tuple_nested.json new file mode 100644 index 0000000..8d966b4 --- /dev/null +++ b/tests/integration/test_lang/generator_tuple_nested.json @@ -0,0 +1,5 @@ +{ + "TYPE_NAME": "tuple_nested", + "TYPE": "(u64, (u32, bytes, u32, ), (u8, u32[3], u8, ), )", + "VAL0": "(1000000, (1, b'test', 2, ), (1, (4, 4, 4, ), 1, ), )" +}