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
This commit is contained in:
Johan B.W. de Vries 2023-11-13 13:00:34 +01:00
parent 769eaaf243
commit f4f068137a
8 changed files with 173 additions and 32 deletions

View File

@ -31,7 +31,7 @@ lint: venv/.done
venv/bin/pylint phasm venv/bin/pylint phasm
typecheck: venv/.done 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 venv/.done: requirements.txt
python3.10 -m venv venv python3.10 -m venv venv

View File

@ -16,7 +16,9 @@ from .runtime import calculate_alloc_size, calculate_member_offset
from .wasmgenerator import Generator as WasmGenerator from .wasmgenerator import Generator as WasmGenerator
LOAD_STORE_TYPE_MAP = { 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 'u8': 'i32', # Have to use an u32, since there is no native u8 type
'i32': 'i32', 'i32': 'i32',
'i64': 'i64', 'i64': 'i64',
'u32': 'i32', 'u32': 'i32',
@ -211,7 +213,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if isinstance(inp, ourlang.ConstantPrimitive): if isinstance(inp, ourlang.ConstantPrimitive):
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR 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 # No native u8 type - treat as i32, with caution
assert isinstance(inp.value, int) assert isinstance(inp.value, int)
wgn.i32.const(inp.value) wgn.i32.const(inp.value)
@ -665,7 +667,7 @@ def module_data_u8(inp: int) -> bytes:
# FIXME: All u8 values are stored as u32 # FIXME: All u8 values are stored as u32
""" """
return struct.pack('<i', inp) # Should be B return struct.pack('<I', inp) # Should be 'B'
def module_data_u32(inp: int) -> bytes: def module_data_u32(inp: int) -> bytes:
""" """
@ -679,6 +681,14 @@ def module_data_u64(inp: int) -> bytes:
""" """
return struct.pack('<Q', inp) return struct.pack('<Q', inp)
def module_data_i8(inp: int) -> bytes:
"""
Compile: module data, i8 value
# FIXME: All i8 values are stored as i32
"""
return struct.pack('<i', inp) # Should be a 'b'
def module_data_i32(inp: int) -> bytes: def module_data_i32(inp: int) -> bytes:
""" """
Compile: module data, i32 value Compile: module data, i32 value
@ -746,6 +756,12 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
data_list.append(module_data_u64(constant.value)) data_list.append(module_data_u64(constant.value))
continue 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: if constant.type3 == type3types.i32:
assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, int) assert isinstance(constant.value, int)

View File

@ -2,8 +2,8 @@ from typing import Union
from .type3 import types as type3types from .type3 import types as type3types
def calculate_alloc_size(typ: Union[type3types.StructType3, type3types.Type3], is_member: bool = False) -> int: def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
if typ == type3types.u8: if typ in (type3types.u8, type3types.i8, ):
return 4 # FIXME: We allocate 4 bytes for every u8 since you load them into an i32 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, ): 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, ): if typ in (type3types.u64, type3types.i64, type3types.f64, ):
return 8 return 8
if typ == type3types.bytes:
if is_member:
return 4
raise NotImplementedError # When does this happen?
if isinstance(typ, type3types.StructType3): if isinstance(typ, type3types.StructType3):
if is_member: if is_member:
# Structs referred to by other structs or tuples are pointers # Structs referred to by other structs or tuples are pointers

View File

@ -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_constraint_ids = {id(x) for x in constraint_list}
old_placeholder_substitutes_len = len(placeholder_substitutes) old_placeholder_substitutes_len = len(placeholder_substitutes)
back_on_todo_list_count = 0
new_constraint_list = [] new_constraint_list = []
for constraint in constraint_list: for constraint in constraint_list:
check_result = constraint.check() check_result = constraint.check()
@ -60,9 +62,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
if isinstance(check_result, RequireTypeSubstitutes): if isinstance(check_result, RequireTypeSubstitutes):
new_constraint_list.append(constraint) new_constraint_list.append(constraint)
if verbose: back_on_todo_list_count += 1
print_constraint(placeholder_id_map, constraint)
print('-> Back on the todo list')
continue continue
if isinstance(check_result, list): if isinstance(check_result, list):
@ -75,6 +75,9 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
raise NotImplementedError(constraint, check_result) 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: if not new_constraint_list:
constraint_list = new_constraint_list constraint_list = new_constraint_list
break break
@ -92,6 +95,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
constraint_list = new_constraint_list constraint_list = new_constraint_list
if verbose: if verbose:
print()
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes) print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
if constraint_list: if constraint_list:

View File

@ -1,4 +1,4 @@
from typing import Any from typing import Any, Generator, Iterable, TextIO
import struct import struct
import sys import sys
@ -12,7 +12,7 @@ from . import runners
DASHES = '-' * 16 DASHES = '-' * 16
class SuiteResult: class SuiteResult:
def __init__(self): def __init__(self) -> None:
self.returned_value = None self.returned_value = None
RUNNER_CLASS_MAP = { RUNNER_CLASS_MAP = {
@ -26,10 +26,10 @@ class Suite:
""" """
WebAssembly test suite WebAssembly test suite
""" """
def __init__(self, code_py): def __init__(self, code_py: str) -> None:
self.code_py = code_py 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 Compiles the given python code into wasm and
then runs it then runs it
@ -92,7 +92,7 @@ class Suite:
return result 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') textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')
def _load_memory_stored_returned_value( def _load_memory_stored_returned_value(
@ -102,11 +102,14 @@ def _load_memory_stored_returned_value(
) -> Any: ) -> Any:
ret_type3 = runner.phasm_ast.functions[func_name].returns_type3 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): if ret_type3 in (type3types.i32, type3types.i64):
assert isinstance(wasm_value, int), wasm_value assert isinstance(wasm_value, int), wasm_value
return 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 isinstance(wasm_value, int), wasm_value
assert 0 <= wasm_value, 'TODO: Extract negative values' assert 0 <= wasm_value, 'TODO: Extract negative values'
return wasm_value return wasm_value
@ -117,30 +120,130 @@ def _load_memory_stored_returned_value(
if ret_type3 is type3types.bytes: if ret_type3 is type3types.bytes:
assert isinstance(wasm_value, int), wasm_value assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
sys.stderr.write(f'Reading 0x{adr:08x}\n') return _load_bytes_from_address(runner, ret_type3, wasm_value)
read_bytes = runner.interpreter_read_memory(adr, 4)
bytes_len, = struct.unpack('<I', read_bytes)
adr += 4
sys.stderr.write(f'Reading 0x{adr:08x}\n')
return runner.interpreter_read_memory(adr, bytes_len)
if isinstance(ret_type3, type3types.AppliedType3): if isinstance(ret_type3, type3types.AppliedType3):
if ret_type3.base is type3types.static_array: if ret_type3.base is type3types.static_array:
assert isinstance(wasm_value, int), wasm_value assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
assert ret_type3.args[0] is type3types.u64, 'Not Implemented yet' return _load_static_array_from_address(runner, ret_type3, wasm_value)
assert isinstance(ret_type3.args[1], type3types.IntType3)
sa_len = ret_type3.args[1].value if ret_type3.base is type3types.tuple:
assert isinstance(wasm_value, int), wasm_value
alloc_size = calculate_alloc_size(ret_type3) return _load_tuple_from_address(runner, ret_type3, wasm_value)
read_bytes = runner.interpreter_read_memory(adr, alloc_size)
print('read_bytes', read_bytes)
return struct.unpack(f'<{sa_len}q', read_bytes)
raise NotImplementedError(ret_type3, wasm_value) raise NotImplementedError(ret_type3, wasm_value)
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
if typ is type3types.u8:
# See compiler.py, LOAD_STORE_TYPE_MAP and module_data_u8
assert len(inp) == 4
return struct.unpack('<I', inp)[0]
if typ is type3types.u32:
assert len(inp) == 4
return struct.unpack('<I', inp)[0]
if typ is type3types.u64:
assert len(inp) == 8
return struct.unpack('<Q', inp)[0]
if typ is type3types.i8:
# See compiler.py, LOAD_STORE_TYPE_MAP and module_data_i8
assert len(inp) == 4
return struct.unpack('<i', inp)[0]
if typ is type3types.i32:
assert len(inp) == 4
return struct.unpack('<i', inp)[0]
if typ is type3types.i64:
assert len(inp) == 8
return struct.unpack('<q', inp)[0]
if typ is type3types.f32:
assert len(inp) == 4
return struct.unpack('<f', inp)[0]
if typ is type3types.f64:
assert len(inp) == 8
return struct.unpack('<d', inp)[0]
if typ is type3types.bytes:
# Note: For applied types, inp should contain a 4 byte pointer
assert len(inp) == 4
adr = struct.unpack('<I', inp)[0]
return _load_bytes_from_address(runner, typ, adr)
if isinstance(typ, type3types.AppliedType3):
# Note: For applied types, inp should contain a 4 byte pointer
assert len(inp) == 4
adr = struct.unpack('<I', inp)[0]
if typ.base is type3types.static_array:
return _load_static_array_from_address(runner, typ, adr)
if typ.base is type3types.tuple:
return _load_tuple_from_address(runner, typ, adr)
raise NotImplementedError(typ, inp)
def _load_bytes_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> bytes:
sys.stderr.write(f'Reading 0x{adr:08x}\n')
read_bytes = runner.interpreter_read_memory(adr, 4)
bytes_len, = struct.unpack('<I', read_bytes)
adr += 4
sys.stderr.write(f'Reading 0x{adr:08x}\n')
return runner.interpreter_read_memory(adr, bytes_len)
def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> 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))
)

View File

@ -17,6 +17,8 @@ from phasm.type3.entry import phasm_type3
from phasm import ourlang from phasm import ourlang
from phasm import wasm from phasm import wasm
Imports = Optional[Dict[str, Callable[[Any], Any]]]
class RunnerBase: class RunnerBase:
""" """
Base class Base class
@ -73,7 +75,7 @@ class RunnerBase:
""" """
raise NotImplementedError 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 Loads the code into the interpreter
""" """

View File

@ -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!', )"
}

View File

@ -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, ), )"
}