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:
parent
769eaaf243
commit
f4f068137a
2
Makefile
2
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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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))
|
||||
)
|
||||
|
||||
@ -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
|
||||
"""
|
||||
|
||||
@ -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!', )"
|
||||
}
|
||||
5
tests/integration/test_lang/generator_tuple_nested.json
Normal file
5
tests/integration/test_lang/generator_tuple_nested.json
Normal 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, ), )"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user