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
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

View File

@ -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('<i', inp) # Should be B
return struct.pack('<I', inp) # Should be 'B'
def module_data_u32(inp: int) -> bytes:
"""
@ -679,6 +681,14 @@ def module_data_u64(inp: int) -> bytes:
"""
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:
"""
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)

View File

@ -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

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_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:

View File

@ -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,8 +120,78 @@ def _load_memory_stored_returned_value(
if ret_type3 is type3types.bytes:
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
return _load_bytes_from_address(runner, ret_type3, wasm_value)
if isinstance(ret_type3, type3types.AppliedType3):
if ret_type3.base is type3types.static_array:
assert isinstance(wasm_value, int), wasm_value
return _load_static_array_from_address(runner, ret_type3, wasm_value)
if ret_type3.base is type3types.tuple:
assert isinstance(wasm_value, int), wasm_value
return _load_tuple_from_address(runner, 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)
@ -127,20 +200,50 @@ def _load_memory_stored_returned_value(
sys.stderr.write(f'Reading 0x{adr:08x}\n')
return runner.interpreter_read_memory(adr, bytes_len)
if isinstance(ret_type3, type3types.AppliedType3):
if ret_type3.base is type3types.static_array:
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
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
assert ret_type3.args[0] is type3types.u64, 'Not Implemented yet'
assert isinstance(ret_type3.args[1], type3types.IntType3)
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
sa_len = ret_type3.args[1].value
assert not isinstance(sub_typ, type3types.PlaceholderForType)
assert isinstance(len_typ, type3types.IntType3)
alloc_size = calculate_alloc_size(ret_type3)
read_bytes = runner.interpreter_read_memory(adr, alloc_size)
print('read_bytes', read_bytes)
sa_len = len_typ.value
return struct.unpack(f'<{sa_len}q', read_bytes)
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
raise NotImplementedError(ret_type3, wasm_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 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
"""

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