diff --git a/py2wasm/python.py b/py2wasm/python.py index 41f7978..6d52490 100644 --- a/py2wasm/python.py +++ b/py2wasm/python.py @@ -39,6 +39,23 @@ class Visitor: if isinstance(name, ast.expr): if isinstance(name, ast.Name): name = name.id + elif isinstance(name, ast.Subscript) and isinstance(name.value, ast.Name) and name.value.id == 'Tuple': + assert isinstance(name.ctx, ast.Load) + assert isinstance(name.slice, ast.Index) + assert isinstance(name.slice.value, ast.Tuple) + + args: List[wasm.TupleMember] = [] + offset = 0 + for name_arg in name.slice.value.elts: + arg = wasm.TupleMember( + self._get_type(name_arg), + offset + ) + + args.append(arg) + offset += arg.type.alloc_size() + + return wasm.OurTypeTuple(args) else: raise NotImplementedError(f'_get_type(ast.{type(name).__name__})') @@ -332,11 +349,17 @@ class Visitor: if isinstance(node, ast.Constant): return self.visit_Constant(exp_type, node) + if isinstance(node, ast.Attribute): + return self.visit_Attribute(module, wlocals, exp_type, node) + + if isinstance(node, ast.Subscript): + return self.visit_Subscript(module, wlocals, exp_type, node) + if isinstance(node, ast.Name): return self.visit_Name(wlocals, exp_type, node) - if isinstance(node, ast.Attribute): - return self.visit_Attribute(module, wlocals, exp_type, node) + if isinstance(node, ast.Tuple): + return self.visit_Tuple(wlocals, exp_type, node) raise NotImplementedError(node) @@ -470,7 +493,7 @@ class Visitor: This instantiates the class """ - # TODO: malloc call + # TODO: free call yield wasm.Statement('i32.const', str(cls.alloc_size())) yield wasm.Statement('call', '$___new_reference___') @@ -586,6 +609,77 @@ class Visitor: yield wasm.Statement('local.get', '$' + node.value.id) yield wasm.Statement(exp_type.to_wasm() + '.load', 'offset=' + str(member.offset)) + def visit_Subscript( + self, + module: wasm.Module, + wlocals: WLocals, + exp_type: wasm.OurType, + node: ast.Subscript, + ) -> StatementGenerator: + """ + Visits an Subscript node as (part of) an expression + """ + if not isinstance(node.value, ast.Name): + raise NotImplementedError + + if not isinstance(node.slice, ast.Index): + raise NotImplementedError + + if not isinstance(node.slice.value, ast.Constant): + raise NotImplementedError + + if not isinstance(node.slice.value.value, int): + raise NotImplementedError + + if not isinstance(node.ctx, ast.Load): + raise NotImplementedError + + tpl_idx = node.slice.value.value + + tpl = wlocals[node.value.id] + + assert isinstance(tpl, wasm.OurTypeTuple), f'Cannot take index of {tpl}' + + assert 0 <= tpl_idx < len(tpl.members), \ + f'Tuple index out of bounds; {node.value.id} has {len(tpl.members)} members' + + member = tpl.members[tpl_idx] + + yield wasm.Statement('local.get', '$' + node.value.id) + yield wasm.Statement(exp_type.to_wasm() + '.load', 'offset=' + str(member.offset)) + + def visit_Tuple( + self, + wlocals: WLocals, + exp_type: wasm.OurType, + node: ast.Tuple, + ) -> StatementGenerator: + """ + Visits an Tuple node as (part of) an expression + """ + assert isinstance(exp_type, wasm.OurTypeTuple), 'Expression is not expecting a tuple' + + # TODO: free call + + tpl = exp_type + + yield wasm.Statement('nop', comment='Start tuple allocation') + + yield wasm.Statement('i32.const', str(tpl.alloc_size())) + yield wasm.Statement('call', '$___new_reference___') + yield wasm.Statement('local.set', '$___new_reference___addr', comment='Allocate for tuple') + + for member, arg in zip(tpl.members, node.elts): + if not isinstance(arg, ast.Constant): + raise NotImplementedError('TODO: Non-const tuple members') + + yield wasm.Statement('local.get', '$___new_reference___addr') + yield wasm.Statement(f'{member.type.to_wasm()}.const', str(arg.value)) + yield wasm.Statement(f'{member.type.to_wasm()}.store', 'offset=' + str(member.offset), + comment='Write tuple value to memory') + + yield wasm.Statement('local.get', '$___new_reference___addr', comment='Store tuple address on stack') + def visit_Name(self, wlocals: WLocals, exp_type: wasm.OurType, node: ast.Name) -> StatementGenerator: """ Visits a Name node as (part of) an expression diff --git a/py2wasm/wasm.py b/py2wasm/wasm.py index 1939301..2aa4ac8 100644 --- a/py2wasm/wasm.py +++ b/py2wasm/wasm.py @@ -80,6 +80,27 @@ class OurTypeClass(OurType): def alloc_size(self) -> int: return sum(x.type.alloc_size() for x in self.members) +class TupleMember: + """ + Represents a tuple member + """ + def __init__(self, type_: OurType, offset: int) -> None: + self.type = type_ + self.offset = offset + +class OurTypeTuple(OurType): + """ + Represents a tuple + """ + def __init__(self, members: List[TupleMember]) -> None: + self.members = members + + def to_wasm(self) -> str: + return 'i32' # WASM uses 32 bit pointers + + def alloc_size(self) -> int: + return sum(x.type.alloc_size() for x in self.members) + Param = Tuple[str, OurType] diff --git a/tests/integration/test_simple.py b/tests/integration/test_simple.py index dca3291..0772cca 100644 --- a/tests/integration/test_simple.py +++ b/tests/integration/test_simple.py @@ -317,3 +317,20 @@ def helper(shape1: Rectangle, shape2: Rectangle) -> i32: assert 545 == result.returned_value assert [] == result.log_int32_list + +@pytest.mark.integration_test +def test_tuple_int(): + code_py = """ + +@exported +def testEntry() -> i32: + return helper((24, 57, 80, )) + +def helper(vector: Tuple[i32, i32, i32]) -> i32: + 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