diff --git a/phasm/compiler.py b/phasm/compiler.py index 2a59f2c..c4cca6a 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -12,6 +12,7 @@ from . import wasm from .stdlib import alloc as stdlib_alloc from .stdlib import types as stdlib_types +from .runtime import calculate_alloc_size, calculate_member_offset from .wasmgenerator import Generator as WasmGenerator LOAD_STORE_TYPE_MAP = { @@ -173,7 +174,7 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> wgn.add_statement('nop', comment=f'{tmp_var.name} := ({comment_elements})') # Allocated the required amounts of bytes in memory - wgn.i32.const(_calculate_alloc_size(inp.type3, is_member=False)) + wgn.i32.const(calculate_alloc_size(inp.type3, is_member=False)) wgn.call(stdlib_alloc.__alloc__) wgn.local.set(tmp_var) @@ -198,7 +199,7 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> wgn.add_statement(f'{mtyp}.store', 'offset=' + str(offset)) wgn.add_statement('nop', comment='POST') - offset += _calculate_alloc_size(exp_type3, is_member=True) + offset += calculate_alloc_size(exp_type3, is_member=True) # Return the allocated address wgn.local.get(tmp_var) @@ -435,7 +436,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: wgn.unreachable(comment='Out of bounds') wgn.local.get(tmp_var) - wgn.i32.const(_calculate_alloc_size(el_type)) + wgn.i32.const(calculate_alloc_size(el_type)) wgn.i32.mul() wgn.i32.add() @@ -452,7 +453,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: offset = 0 for el_type in inp.varref.type3.args[0:inp.index.value]: assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR - offset += _calculate_alloc_size(el_type) + offset += calculate_alloc_size(el_type) # This doubles as the out of bounds check el_type = inp.varref.type3.args[inp.index.value] @@ -478,7 +479,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: mtyp = LOAD_STORE_TYPE_MAP[inp.struct_type3.members[inp.member].name] expression(wgn, inp.varref) - wgn.add_statement(f'{mtyp}.load', 'offset=' + str(_calculate_member_offset( + wgn.add_statement(f'{mtyp}.load', 'offset=' + str(calculate_member_offset( inp.struct_type3, inp.member ))) return @@ -830,7 +831,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc tmp_var = wgn.temp_var_i32('struct_adr') # Allocated the required amounts of bytes in memory - wgn.i32.const(_calculate_alloc_size(inp.struct_type3)) + wgn.i32.const(calculate_alloc_size(inp.struct_type3)) wgn.call(stdlib_alloc.__alloc__) wgn.local.set(tmp_var) @@ -844,60 +845,9 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc wgn.local.get(tmp_var) wgn.add_statement('local.get', f'${memname}') - wgn.add_statement(f'{mtyp}.store', 'offset=' + str(_calculate_member_offset( + wgn.add_statement(f'{mtyp}.store', 'offset=' + str(calculate_member_offset( inp.struct_type3, memname ))) # Return the allocated address wgn.local.get(tmp_var) - -def _calculate_alloc_size(typ: Union[type3types.StructType3, type3types.Type3], is_member: bool = False) -> int: - if typ == type3types.u8: - 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, ): - return 4 - - if typ in (type3types.u64, type3types.i64, type3types.f64, ): - return 8 - - if isinstance(typ, type3types.StructType3): - if is_member: - # Structs referred to by other structs or tuples are pointers - return 4 - - return sum( - _calculate_alloc_size(x) - for x in typ.members.values() - ) - - if isinstance(typ, type3types.AppliedType3): - if typ.base is type3types.tuple: - if is_member: - # tuples referred to by other structs or tuples are pointers - return 4 - - size = 0 - for arg in typ.args: - assert not isinstance(arg, type3types.IntType3) - - if isinstance(arg, type3types.PlaceholderForType): - assert not arg.resolve_as is None - arg = arg.resolve_as - - size += _calculate_alloc_size(arg, is_member=True) - - return size - - raise NotImplementedError(_calculate_alloc_size, typ) - -def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int: - result = 0 - - for mem, memtyp in struct_type3.members.items(): - if member == mem: - return result - - result += _calculate_alloc_size(memtyp, is_member=True) - - raise Exception(f'{member} not in {struct_type3}') diff --git a/phasm/runtime.py b/phasm/runtime.py new file mode 100644 index 0000000..2b9e659 --- /dev/null +++ b/phasm/runtime.py @@ -0,0 +1,64 @@ +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: + 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, ): + return 4 + + if typ in (type3types.u64, type3types.i64, type3types.f64, ): + return 8 + + if isinstance(typ, type3types.StructType3): + if is_member: + # Structs referred to by other structs or tuples are pointers + return 4 + + return sum( + calculate_alloc_size(x) + for x in typ.members.values() + ) + + if isinstance(typ, type3types.AppliedType3): + if typ.base is type3types.static_array: + if is_member: + # tuples referred to by other structs or tuples are pointers + return 4 + + assert isinstance(typ.args[0], type3types.Type3) + assert isinstance(typ.args[1], type3types.IntType3) + + return typ.args[1].value * calculate_alloc_size(typ.args[0], is_member=True) + + if typ.base is type3types.tuple: + if is_member: + # tuples referred to by other structs or tuples are pointers + return 4 + + size = 0 + for arg in typ.args: + assert not isinstance(arg, type3types.IntType3) + + if isinstance(arg, type3types.PlaceholderForType): + assert not arg.resolve_as is None + arg = arg.resolve_as + + size += calculate_alloc_size(arg, is_member=True) + + return size + + raise NotImplementedError(calculate_alloc_size, typ) + +def calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int: + result = 0 + + for mem, memtyp in struct_type3.members.items(): + if member == mem: + return result + + result += calculate_alloc_size(memtyp, is_member=True) + + raise Exception(f'{member} not in {struct_type3}') diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 3cd682c..1ba6826 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -1,6 +1,11 @@ +from typing import Any + +import struct import sys from phasm.codestyle import phasm_render +from phasm.type3 import types as type3types +from phasm.runtime import calculate_alloc_size from . import runners @@ -76,6 +81,12 @@ class Suite: result = SuiteResult() result.returned_value = runner.call(func_name, *wasm_args) + result.returned_value = _load_memory_stored_returned_value( + runner, + func_name, + result.returned_value, + ) + write_header(sys.stderr, 'Memory (post run)') runner.interpreter_dump_memory(sys.stderr) @@ -83,3 +94,53 @@ class Suite: def write_header(textio, msg: str) -> None: textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n') + +def _load_memory_stored_returned_value( + runner: runners.RunnerBase, + func_name: str, + wasm_value: Any, + ) -> Any: + ret_type3 = runner.phasm_ast.functions[func_name].returns_type3 + + 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): + assert isinstance(wasm_value, int), wasm_value + assert 0 <= wasm_value, 'TODO: Extract negative values' + return wasm_value + + if ret_type3 in (type3types.f32, type3types.f64, ): + assert isinstance(wasm_value, float), wasm_value + return wasm_value + + if ret_type3 is type3types.bytes: + assert isinstance(wasm_value, int), wasm_value + adr = wasm_value + + sys.stderr.write(f'Reading 0x{adr:08x}\n') + read_bytes = runner.interpreter_read_memory(adr, 4) + bytes_len, = struct.unpack(' bytes: memory = self.rtime.get_memory(0) - return memory[offset:length].tobytes() + return memory[offset:offset + length].tobytes() def interpreter_dump_memory(self, textio: TextIO) -> None: _dump_memory(textio, self.rtime.get_memory(0)) diff --git a/tests/integration/test_lang/generator.md b/tests/integration/test_lang/generator.md index c53ab86..f5041b7 100644 --- a/tests/integration/test_lang/generator.md +++ b/tests/integration/test_lang/generator.md @@ -1,3 +1,19 @@ +# runtime_extract_value + +As a developer +I want to extract a $TYPE value +In order get the result I calculated with my phasm code + +```py +@exported +def testEntry() -> $TYPE: + return $VAL0 +``` + +```py +expect(VAL0) +``` + # module_constant_def_ok As a developer diff --git a/tests/integration/test_lang/generator.py b/tests/integration/test_lang/generator.py index 57562fb..7cb486a 100644 --- a/tests/integration/test_lang/generator.py +++ b/tests/integration/test_lang/generator.py @@ -44,6 +44,7 @@ def generate_assertions(settings, result_code): result = [] locals_ = { + 'VAL0': eval(settings['VAL0']), 'TYPE_NAME': settings['TYPE_NAME'], 'expect': functools.partial(generate_assertion_expect, result), 'expect_type_error': functools.partial(generate_assertion_expect_type_error, result),