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
|
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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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))
|
||||||
|
)
|
||||||
|
|||||||
@ -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
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -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