Implemented round trip input / output

This commit is contained in:
Johan B.W. de Vries 2023-11-13 14:32:17 +01:00
parent f4f068137a
commit 0131b84146
5 changed files with 168 additions and 49 deletions

View File

@ -1,8 +1,9 @@
from typing import Any, Generator, Iterable, TextIO
from typing import Any, Generator, Iterable, List, TextIO, Union
import struct
import sys
from phasm import compiler
from phasm.codestyle import phasm_render
from phasm.type3 import types as type3types
from phasm.runtime import calculate_alloc_size
@ -55,24 +56,50 @@ class Suite:
# Check if code formatting works
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
wasm_args = []
func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs]
if len(func_args) != len(args):
raise RuntimeError(f'Invalid number of args for {func_name}')
wasm_args: List[Union[float, int]] = []
if args:
write_header(sys.stderr, 'Memory (pre alloc)')
runner.interpreter_dump_memory(sys.stderr)
for arg in args:
if isinstance(arg, (int, float, )):
for arg, arg_typ in zip(args, func_args):
assert not isinstance(arg_typ, type3types.PlaceholderForType), \
'Cannot call polymorphic function from outside'
if arg_typ in (type3types.u8, type3types.u32, type3types.u64, ):
assert isinstance(arg, int)
wasm_args.append(arg)
continue
if isinstance(arg, bytes):
adr = runner.call('stdlib.types.__alloc_bytes__', len(arg))
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(arg)}\n')
if arg_typ in (type3types.i8, type3types.i32, type3types.i64, ):
assert isinstance(arg, int)
wasm_args.append(arg)
continue
runner.interpreter_write_memory(adr + 4, arg)
if arg_typ in (type3types.f32, type3types.f64, ):
assert isinstance(arg, float)
wasm_args.append(arg)
continue
if arg_typ is type3types.bytes:
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
if isinstance(arg_typ, type3types.AppliedType3):
if arg_typ.base is type3types.static_array:
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
if arg_typ.base is type3types.tuple:
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
raise NotImplementedError(arg)
write_header(sys.stderr, 'Memory (pre run)')
@ -95,6 +122,98 @@ class Suite:
def write_header(textio: TextIO, msg: str) -> None:
textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')
WRITE_LOOKUP_MAP = {
'u8': compiler.module_data_u8,
'u32': compiler.module_data_u32,
'u64': compiler.module_data_u64,
'i8': compiler.module_data_i8,
'i32': compiler.module_data_i32,
'i64': compiler.module_data_i64,
'f32': compiler.module_data_f32,
'f64': compiler.module_data_f64,
}
def _write_memory_stored_value(
runner: runners.RunnerBase,
adr: int,
val_typ: type3types.Type3,
val: Any,
) -> int:
if val_typ is type3types.bytes:
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
if isinstance(val_typ, type3types.PrimitiveType3):
to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
runner.interpreter_write_memory(adr, to_write)
return len(to_write)
if isinstance(val_typ, type3types.AppliedType3):
if val_typ.base in (type3types.static_array, type3types.tuple, ):
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
raise NotImplementedError(val_typ, val)
def _allocate_memory_stored_value(
runner: runners.RunnerBase,
val_typ: type3types.Type3,
val: Any
) -> int:
if val_typ is type3types.bytes:
assert isinstance(val, bytes)
adr = runner.call('stdlib.types.__alloc_bytes__', len(val))
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
runner.interpreter_write_memory(adr + 4, val)
return adr
if isinstance(val_typ, type3types.AppliedType3):
if val_typ.base is type3types.static_array:
assert isinstance(val, tuple)
alloc_size = calculate_alloc_size(val_typ)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
val_el_typ = val_typ.args[0]
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
val_el_alloc_size = calculate_alloc_size(val_el_typ)
tuple_len_obj = val_typ.args[1]
assert isinstance(tuple_len_obj, type3types.IntType3)
tuple_len = tuple_len_obj.value
assert tuple_len == len(val)
offset = adr
for val_el_val in val:
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr
if val_typ.base is type3types.tuple:
assert isinstance(val, tuple)
alloc_size = calculate_alloc_size(val_typ)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
assert len(val) == len(val_typ.args)
offset = adr
for val_el_val, val_el_typ in zip(val, val_typ.args):
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr
raise NotImplementedError(val_typ, val)
def _load_memory_stored_returned_value(
runner: runners.RunnerBase,
func_name: str,
@ -111,7 +230,19 @@ def _load_memory_stored_returned_value(
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'
if wasm_value < 0:
# WASM does not support unsigned values through its interface
# Cast and then reinterpret
letter = {
'u32': 'i',
'u64': 'q',
}[ret_type3.name]
data = struct.pack(f'<{letter}', wasm_value)
wasm_value, = struct.unpack(f'<{letter.upper()}', data)
return wasm_value
if ret_type3 in (type3types.f32, type3types.f64, ):
@ -192,12 +323,11 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
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')
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\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]:
@ -207,6 +337,8 @@ def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator
offset += size
def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.AppliedType3, adr: int) -> Any:
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
assert 2 == len(typ.args)
sub_typ, len_typ = typ.args
@ -226,6 +358,8 @@ def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.
)
def _load_tuple_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any:
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
assert isinstance(typ, type3types.AppliedType3)
assert typ.base is type3types.tuple

View File

@ -31,9 +31,4 @@ def testEntry(data: bytes) -> u32:
result = Suite(code_py).run_code(b'a')
# exp_result returns a unsigned integer, as is proper
exp_data = struct.pack('I', exp_result)
# ints extracted from WebAssembly are always signed
data = struct.pack('i', result.returned_value)
assert exp_data == data
assert exp_result == result.returned_value

View File

@ -1,4 +1,4 @@
# runtime_extract_value
# runtime_extract_value_literal
As a developer
I want to extract a $TYPE value
@ -14,6 +14,22 @@ def testEntry() -> $TYPE:
expect(VAL0)
```
# runtime_extract_value_round_trip
As a developer
I want to extract a $TYPE value that I've input
In order let the code select a value that I've predefined
```py
@exported
def testEntry(x: $TYPE) -> $TYPE:
return x
```
```py
expect(VAL0, given=[VAL0])
```
# module_constant_def_ok
As a developer

View File

@ -30,8 +30,10 @@ def apply_settings(settings, txt):
txt = txt.replace(f'${k}', v)
return txt
def generate_assertion_expect(result, arg):
result.append('result = Suite(code_py).run_code()')
def generate_assertion_expect(result, arg, given=None):
given = given or []
result.append('result = Suite(code_py).run_code(' + ', '.join(repr(x) for x in given) + ')')
result.append(f'assert {repr(arg)} == result.returned_value')
def generate_assertion_expect_type_error(result, error_msg, error_comment = None):

View File

@ -4,20 +4,6 @@ from phasm.type3.entry import Type3Exception
from ..helpers import Suite
@pytest.mark.integration_test
def test_bytes_address():
code_py = """
@exported
def testEntry(f: bytes) -> bytes:
return f
"""
result = Suite(code_py).run_code(b'This is a test')
# THIS DEPENDS ON THE ALLOCATOR
# A different allocator will return a different value
assert 20 == result.returned_value
@pytest.mark.integration_test
def test_bytes_length():
code_py = """
@ -42,25 +28,11 @@ def testEntry(f: bytes) -> u8:
assert 0x61 == result.returned_value
@pytest.mark.integration_test
def test_constant():
code_py = """
CONSTANT: bytes = b'ABCDEF'
@exported
def testEntry() -> u8:
return CONSTANT[0]
"""
result = Suite(code_py).run_code()
assert 0x41 == result.returned_value
@pytest.mark.integration_test
def test_bytes_index_out_of_bounds():
code_py = """
@exported
def testEntry(f: bytes) -> u8:
def testEntry(f: bytes, g: bytes) -> u8:
return f[50]
"""