From ac0c49a92c039b29ae5ae607976750501a2e6a06 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sun, 19 Jun 2022 16:54:14 +0200 Subject: [PATCH] Now runs on new code --- TODO.md | 6 ++ py2wasm/compiler.py | 50 +++++++++++++- py2wasm/ourlang.py | 109 ++++++++++++++++++++++++------- tests/integration/helpers.py | 41 ++++++++---- tests/integration/test_simple.py | 25 +++++-- 5 files changed, 189 insertions(+), 42 deletions(-) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..afe6948 --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +# TODO + +- Rename ourlang.Struct to OurTypeStruct + - Maybe rename this to a types module? +- Remove references to log_int32_list +- Fix naming in Suite() calls diff --git a/py2wasm/compiler.py b/py2wasm/compiler.py index d04bb2c..7563c75 100644 --- a/py2wasm/compiler.py +++ b/py2wasm/compiler.py @@ -21,16 +21,19 @@ def type_(inp: ourlang.OurType) -> wasm.OurType: if isinstance(inp, ourlang.OurTypeFloat64): return wasm.OurTypeFloat64() - if isinstance(inp, ourlang.Struct): - # Structs are passed as pointer + if isinstance(inp, (ourlang.Struct, ourlang.OurTypeTuple, )): + # Structs and tuples are passed as pointer # And pointers are i32 return wasm.OurTypeInt32() raise NotImplementedError(type_, inp) +# Operators that work for i32, i64, f32, f64 OPERATOR_MAP = { '+': 'add', '-': 'sub', + '*': 'mul', + '==': 'eq', } I32_OPERATOR_MAP = { # TODO: Introduce UInt32 type @@ -121,6 +124,19 @@ def expression(inp: ourlang.Expression) -> Statements: yield wasm.Statement(inp.type.render() + '.load', 'offset=' + str(inp.member.offset)) return + if isinstance(inp, ourlang.AccessTupleMember): + # FIXME: Properly implement this + # inp.type.render() is also a hack that doesn't really work consistently + if not isinstance(inp.type, ( + ourlang.OurTypeInt32, ourlang.OurTypeFloat32, + ourlang.OurTypeInt64, ourlang.OurTypeFloat64, + )): + raise NotImplementedError(inp, inp.type) + + yield from expression(inp.varref) + yield wasm.Statement(inp.type.render() + '.load', 'offset=' + str(inp.member.offset)) + return + raise NotImplementedError(expression, inp) def statement_return(inp: ourlang.StatementReturn) -> Statements: @@ -157,7 +173,14 @@ def function_argument(inp: Tuple[str, ourlang.OurType]) -> wasm.Param: return (inp[0], type_(inp[1]), ) def function(inp: ourlang.Function) -> wasm.Function: - if isinstance(inp, ourlang.StructConstructor): + if isinstance(inp, ourlang.TupleConstructor): + statements = [ + *_generate_tuple_constructor(inp) + ] + locals_ = [ + ('___new_reference___addr', wasm.OurTypeInt32(), ), + ] + elif isinstance(inp, ourlang.StructConstructor): statements = [ *_generate_struct_constructor(inp) ] @@ -219,6 +242,27 @@ def _generate_allocator(mod: ourlang.Module) -> wasm.Function: ], ) +def _generate_tuple_constructor(inp: ourlang.TupleConstructor) -> Statements: + yield wasm.Statement('i32.const', str(inp.tuple.alloc_size())) + yield wasm.Statement('call', '$___new_reference___') + + yield wasm.Statement('local.set', '$___new_reference___addr') + + for member in inp.tuple.members: + # FIXME: Properly implement this + # inp.type.render() is also a hack that doesn't really work consistently + if not isinstance(member.type, ( + ourlang.OurTypeInt32, ourlang.OurTypeFloat32, + ourlang.OurTypeInt64, ourlang.OurTypeFloat64, + )): + raise NotImplementedError + + yield wasm.Statement('local.get', '$___new_reference___addr') + yield wasm.Statement('local.get', f'$arg{member.idx}') + yield wasm.Statement(f'{member.type.render()}.store', 'offset=' + str(member.offset)) + + yield wasm.Statement('local.get', '$___new_reference___addr') + def _generate_struct_constructor(inp: ourlang.StructConstructor) -> Statements: yield wasm.Statement('i32.const', str(inp.struct.alloc_size())) yield wasm.Statement('call', '$___new_reference___') diff --git a/py2wasm/ourlang.py b/py2wasm/ourlang.py index 1855e21..b7c5d68 100644 --- a/py2wasm/ourlang.py +++ b/py2wasm/ourlang.py @@ -59,6 +59,9 @@ class OurTypeInt64(OurType): def render(self) -> str: return 'i64' + def alloc_size(self) -> int: + return 8 + class OurTypeFloat32(OurType): """ The Float type, 32 bits wide @@ -68,6 +71,9 @@ class OurTypeFloat32(OurType): def render(self) -> str: return 'f32' + def alloc_size(self) -> int: + return 4 + class OurTypeFloat64(OurType): """ The Float type, 64 bits wide @@ -77,21 +83,44 @@ class OurTypeFloat64(OurType): def render(self) -> str: return 'f64' + def alloc_size(self) -> int: + return 8 + +class TupleMember: + """ + Represents a tuple member + """ + def __init__(self, idx: int, type_: OurType, offset: int) -> None: + self.idx = idx + self.type = type_ + self.offset = offset + class OurTypeTuple(OurType): """ The tuple type, 64 bits wide """ __slots__ = ('members', ) - members: List[OurType] + members: List[TupleMember] def __init__(self) -> None: self.members = [] def render(self) -> str: - mems = ', '.join(x.render() for x in self.members) + mems = ', '.join(x.type.render() for x in self.members) return f'({mems}, )' + def render_internal_name(self) -> str: + mems = '@'.join(x.type.render() for x in self.members) + assert ' ' not in mems, 'Not implement yet: subtuples' + return f'tuple@{mems}' + + def alloc_size(self) -> int: + return sum( + x.type.alloc_size() + for x in self.members + ) + class Expression: """ An expression within a statement @@ -257,6 +286,9 @@ class FunctionCall(Expression): if isinstance(self.function, StructConstructor): return f'{self.function.struct.name}({args})' + if isinstance(self.function, TupleConstructor): + return f'({args}, )' + return f'{self.function.name}({args})' class AccessStructMember(Expression): @@ -281,21 +313,19 @@ class AccessTupleMember(Expression): """ Access a tuple member for reading of writing """ - __slots__ = ('varref', 'member_idx', 'member_type', ) + __slots__ = ('varref', 'member', ) varref: VariableReference - member_idx: int - member_type: 'OurType' + member: TupleMember - def __init__(self, varref: VariableReference, member_idx: int, member_type: 'OurType') -> None: - super().__init__(member_type) + def __init__(self, varref: VariableReference, member: TupleMember, ) -> None: + super().__init__(member.type) self.varref = varref - self.member_idx = member_idx - self.member_type = member_type + self.member = member def render(self) -> str: - return f'{self.varref.render()}[{self.member_idx}]' + return f'{self.varref.render()}[{self.member.idx}]' class TupleCreation(Expression): """ @@ -450,6 +480,26 @@ class StructConstructor(Function): self.struct = struct +class TupleConstructor(Function): + """ + The constructor method for a tuple + """ + __slots__ = ('tuple', ) + + tuple: OurTypeTuple + + def __init__(self, tuple_: OurTypeTuple) -> None: + name = tuple_.render_internal_name() + + super().__init__(f'@{name}@__init___@', -1) + + self.returns = tuple_ + + for mem in tuple_.members: + self.posonlyargs.append((f'arg{mem.idx}', mem.type, )) + + self.tuple = tuple_ + class StructMember: """ Represents a struct member @@ -823,10 +873,14 @@ class OurVisitor: if len(exp_type.members) != len(node.elts): _raise_static_error(node, f'Expression is expecting a tuple of size {len(exp_type.members)}, but {len(node.elts)} are given') - result = TupleCreation(exp_type) - result.members = [ - self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_type, arg_node) - for arg_node, arg_type in zip(node.elts, exp_type.members) + tuple_constructor = TupleConstructor(exp_type) + + func = module.functions[tuple_constructor.name] + + result = FunctionCall(func) + result.arguments = [ + self.visit_Module_FunctionDef_expr(module, function, our_locals, mem.type, arg_node) + for arg_node, mem in zip(node.elts, exp_type.members) ] return result @@ -930,12 +984,11 @@ class OurVisitor: _raise_static_error(node, f'Index {node.slice.value.value} out of bounds for tuple {node.value.id}') member = node_typ.members[node.slice.value.value] - if exp_type != member: - _raise_static_error(node, f'Expected {exp_type.render()}, got {member.render()} instead') + if exp_type != member.type: + _raise_static_error(node, f'Expected {exp_type.render()}, got {member.type.render()} instead') return AccessTupleMember( VariableReference(node_typ, node.value.id), - node.slice.value.value, member, ) @@ -997,11 +1050,23 @@ class OurVisitor: _raise_static_error(node, 'Must be load context') result = OurTypeTuple() - result.members = [ - self.visit_type(module, elt) - for elt in node.elts - ] - return result + + offset = 0 + + for idx, elt in enumerate(node.elts): + member = TupleMember(idx, self.visit_type(module, elt), offset) + + result.members.append(member) + offset += member.type.alloc_size() + + key = result.render_internal_name() + + if key not in module.types: + module.types[key] = result + constructor = TupleConstructor(result) + module.functions[constructor.name] = constructor + + return module.types[key] raise NotImplementedError(f'{node} as type') diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 634612f..356c8d3 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -15,6 +15,7 @@ import wasmer_compiler_cranelift import wasmtime from py2wasm.utils import our_process, process +from py2wasm.compiler import module DASHES = '-' * 16 @@ -69,23 +70,30 @@ class Suite: Returned is an object with the results set """ + # sys.stderr.write(f'{DASHES} Old Assembly {DASHES}\n') + # + # code_wat_old = process(self.code_py, self.test_name) + # _write_numbered_lines(code_wat_old) + our_module = our_process(self.code_py, self.test_name) + + # Check if code formatting works assert self.code_py == '\n' + our_module.render() # \n for formatting in tests - code_wat = process(self.code_py, self.test_name) + # Compile + wat_module = module(our_module) + + # Render as text + code_wat = wat_module.generate() sys.stderr.write(f'{DASHES} Assembly {DASHES}\n') - line_list = code_wat.split('\n') - line_no_width = len(str(len(line_list))) - for line_no, line_txt in enumerate(line_list): - sys.stderr.write('{} {}\n'.format( - str(line_no + 1).zfill(line_no_width), - line_txt, - )) + _write_numbered_lines(code_wat) + # Compile to assembly code code_wasm = wat2wasm(code_wat) + # Run assembly code return _run_pywasm3(code_wasm, args) def _run_pywasm(code_wasm, args): @@ -117,13 +125,13 @@ def _run_pywasm3(code_wasm, args): rtime = env.new_runtime(1024 * 1024) rtime.load(mod) - sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n') - _dump_memory(rtime.get_memory(0).tobytes()) + # sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n') + # _dump_memory(rtime.get_memory(0).tobytes()) result.returned_value = rtime.find_function('testEntry')(*args) - sys.stderr.write(f'{DASHES} Memory (post run) {DASHES}\n') - _dump_memory(rtime.get_memory(0).tobytes()) + # sys.stderr.write(f'{DASHES} Memory (post run) {DASHES}\n') + # _dump_memory(rtime.get_memory(0).tobytes()) return result @@ -190,3 +198,12 @@ def _dump_memory(mem): sys.stderr.write(f'{idx:08x} {line}\n') prev_line = line + +def _write_numbered_lines(text: str) -> None: + line_list = text.split('\n') + line_no_width = len(str(len(line_list))) + for line_no, line_txt in enumerate(line_list): + sys.stderr.write('{} {}\n'.format( + str(line_no + 1).zfill(line_no_width), + line_txt, + )) diff --git a/tests/integration/test_simple.py b/tests/integration/test_simple.py index 97cd31b..ea281e7 100644 --- a/tests/integration/test_simple.py +++ b/tests/integration/test_simple.py @@ -51,6 +51,20 @@ def testEntry() -> {type_}: assert 7 == result.returned_value assert TYPE_MAP[type_] == type(result.returned_value) +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', ['f32', 'f64']) +def test_buildins_sqrt(type_): + code_py = f""" +@exported +def testEntry() -> {type_}: + return sqrt(25) +""" + + result = Suite(code_py, 'test_addition').run_code() + + assert 5 == result.returned_value + assert TYPE_MAP[type_] == type(result.returned_value) + @pytest.mark.integration_test @pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) def test_arg(type_): @@ -320,20 +334,21 @@ def helper(shape1: Rectangle, shape2: Rectangle) -> i32: assert [] == result.log_int32_list @pytest.mark.integration_test -def test_tuple_int(): - code_py = """ +@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) +def test_tuple_simple(type_): + code_py = f""" @exported -def testEntry() -> i32: +def testEntry() -> {type_}: return helper((24, 57, 80, )) -def helper(vector: (i32, i32, i32, )) -> i32: +def helper(vector: ({type_}, {type_}, {type_}, )) -> {type_}: return vector[0] + vector[1] + vector[2] """ result = Suite(code_py, 'test_call').run_code() assert 161 == result.returned_value - assert [] == result.log_int32_list + assert TYPE_MAP[type_] == type(result.returned_value) @pytest.mark.integration_test def test_tuple_float():