diff --git a/py2wasm/__main__.py b/py2wasm/__main__.py index f66b313..7c65724 100644 --- a/py2wasm/__main__.py +++ b/py2wasm/__main__.py @@ -16,8 +16,8 @@ def main(source: str, sink: str) -> int: code_py = fil.read() our_module = our_process(code_py, source) - wat_module = module(our_module) - code_wat = wat_module.generate() + wasm_module = module(our_module) + code_wat = wasm_module.to_wat() with open(sink, 'w') as fil: fil.write(code_wat) diff --git a/py2wasm/compiler.py b/py2wasm/compiler.py index 3605ba5..cf2d99c 100644 --- a/py2wasm/compiler.py +++ b/py2wasm/compiler.py @@ -8,31 +8,31 @@ from . import wasm Statements = Generator[wasm.Statement, None, None] -def type_(inp: ourlang.OurType) -> wasm.OurType: +def type_(inp: ourlang.OurType) -> wasm.WasmType: if isinstance(inp, ourlang.OurTypeNone): - return wasm.OurTypeNone() + return wasm.WasmTypeNone() if isinstance(inp, ourlang.OurTypeUInt8): # WebAssembly has only support for 32 and 64 bits # So we need to store more memory per byte - return wasm.OurTypeInt32() + return wasm.WasmTypeInt32() if isinstance(inp, ourlang.OurTypeInt32): - return wasm.OurTypeInt32() + return wasm.WasmTypeInt32() if isinstance(inp, ourlang.OurTypeInt64): - return wasm.OurTypeInt64() + return wasm.WasmTypeInt64() if isinstance(inp, ourlang.OurTypeFloat32): - return wasm.OurTypeFloat32() + return wasm.WasmTypeFloat32() if isinstance(inp, ourlang.OurTypeFloat64): - return wasm.OurTypeFloat64() + return wasm.WasmTypeFloat64() if isinstance(inp, (ourlang.Struct, ourlang.OurTypeTuple, ourlang.OurTypeBytes)): # Structs and tuples are passed as pointer # And pointers are i32 - return wasm.OurTypeInt32() + return wasm.WasmTypeInt32() raise NotImplementedError(type_, inp) @@ -238,14 +238,14 @@ def function(inp: ourlang.Function) -> wasm.Function: *_generate_tuple_constructor(inp) ] locals_ = [ - ('___new_reference___addr', wasm.OurTypeInt32(), ), + ('___new_reference___addr', wasm.WasmTypeInt32(), ), ] elif isinstance(inp, ourlang.StructConstructor): statements = [ *_generate_struct_constructor(inp) ] locals_ = [ - ('___new_reference___addr', wasm.OurTypeInt32(), ), + ('___new_reference___addr', wasm.WasmTypeInt32(), ), ] else: statements = [ @@ -257,7 +257,7 @@ def function(inp: ourlang.Function) -> wasm.Function: return wasm.Function( inp.name, - inp.exported, + inp.name if inp.exported else None, [ function_argument(x) for x in inp.posonlyargs @@ -290,7 +290,7 @@ def module(inp: ourlang.Module) -> wasm.Module: def _generate____new_reference___(mod: ourlang.Module) -> wasm.Function: return wasm.Function( '___new_reference___', - True, + '___new_reference___', [ ('alloc_size', type_(mod.types['i32']), ), ], @@ -313,7 +313,7 @@ def _generate____new_reference___(mod: ourlang.Module) -> wasm.Function: def _generate____access_bytes_index___(mod: ourlang.Module) -> wasm.Function: return wasm.Function( '___access_bytes_index___', - False, + None, [ ('byt', type_(mod.types['i32']), ), ('ofs', type_(mod.types['i32']), ), diff --git a/py2wasm/ourlang.py b/py2wasm/ourlang.py index 303d94d..99156be 100644 --- a/py2wasm/ourlang.py +++ b/py2wasm/ourlang.py @@ -458,13 +458,12 @@ class Function: """ A function processes input and produces output """ - __slots__ = ('name', 'lineno', 'exported', 'imported', 'buildin', 'statements', 'returns', 'posonlyargs', ) + __slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns', 'posonlyargs', ) name: str lineno: int exported: bool imported: bool - buildin: bool statements: List[Statement] returns: OurType posonlyargs: List[Tuple[str, OurType]] @@ -474,7 +473,6 @@ class Function: self.lineno = lineno self.exported = False self.imported = False - self.buildin = False self.statements = [] self.returns = OurTypeNone() self.posonlyargs = [] diff --git a/py2wasm/wasm.py b/py2wasm/wasm.py index a4c274a..3e5cff4 100644 --- a/py2wasm/wasm.py +++ b/py2wasm/wasm.py @@ -3,150 +3,77 @@ Python classes for storing the representation of Web Assembly code, and being able to conver it to Web Assembly Text Format """ -from typing import Any, Iterable, List, Optional, Tuple, Union +from typing import Iterable, List, Optional, Tuple -### -### This part is more intermediate code -### +class WatSerializable: + """ + Mixin for clases that can be serialized as WebAssembly Text + """ + def to_wat(self) -> str: + """ + Renders this object as WebAssembly Text + """ + raise NotImplementedError(self, 'to_wat') -class OurType: - def to_python(self) -> str: - raise NotImplementedError(self, 'to_python') +class WasmType(WatSerializable): + """ + Type base class + """ - def to_wasm(self) -> str: - raise NotImplementedError(self, 'to_wasm') +class WasmTypeNone(WasmType): + """ + Type when there is no type + """ + def to_wat(self) -> str: + raise Exception('None type is only a placeholder') - def alloc_size(self) -> int: - raise NotImplementedError(self, 'alloc_size') +class WasmTypeInt32(WasmType): + """ + i32 value -class OurTypeNone(OurType): - pass - -class OurTypeBool(OurType): - pass - -class OurTypeInt32(OurType): - def to_python(self) -> str: + Signed or not depends on the operations, not the type + """ + def to_wat(self) -> str: return 'i32' - def to_wasm(self) -> str: - return 'i32' +class WasmTypeInt64(WasmType): + """ + i64 value - def alloc_size(self) -> int: - return 4 - -class OurTypeInt64(OurType): - def to_wasm(self) -> str: + Signed or not depends on the operations, not the type + """ + def to_wat(self) -> str: return 'i64' - def alloc_size(self) -> int: - return 8 - -class OurTypeFloat32(OurType): - def to_wasm(self) -> str: +class WasmTypeFloat32(WasmType): + """ + f32 value + """ + def to_wat(self) -> str: return 'f32' - def alloc_size(self) -> int: - return 4 - -class OurTypeFloat64(OurType): - def to_wasm(self) -> str: +class WasmTypeFloat64(WasmType): + """ + f64 value + """ + def to_wat(self) -> str: return 'f64' - def alloc_size(self) -> int: - return 8 - -class OurTypeVector(OurType): +class WasmTypeVector(WasmType): """ A vector is a 128-bit value """ - def to_wasm(self) -> str: + def to_wat(self) -> str: return 'v128' - def alloc_size(self) -> int: - return 16 - -class OurTypeVectorInt32x4(OurTypeVector): +class WasmTypeVectorInt32x4(WasmTypeVector): """ 4 Int32 values in a single vector """ -class Constant: - """ - TODO - """ - def __init__(self, value: Union[None, bool, int, float]) -> None: - self.value = value +Param = Tuple[str, WasmType] -class ClassMember: - """ - Represents a class member - """ - def __init__(self, name: str, type_: OurType, offset: int, default: Optional[Constant]) -> None: - self.name = name - self.type = type_ - self.offset = offset - self.default = default - -class OurTypeClass(OurType): - """ - Represents a class - """ - def __init__(self, name: str, members: List[ClassMember]) -> None: - self.name = name - 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) - -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_python(self) -> str: - return 'Tuple[' + ( - ', '.join(x.type.to_python() for x in self.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) - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, OurTypeTuple): - raise NotImplementedError - - return ( - len(self.members) == len(other.members) - and all( - self.members[x].type == other.members[x].type - for x in range(len(self.members)) - ) - ) - -Param = Tuple[str, OurType] - -### -## This part is more actual web assembly -### - -class Import: +class Import(WatSerializable): """ Represents a Web Assembly import """ @@ -156,7 +83,7 @@ class Import: name: str, intname: str, params: Iterable[Param], - result: OurType, + result: WasmType, ) -> None: self.module = module self.name = name @@ -164,23 +91,20 @@ class Import: self.params = [*params] self.result = result - def generate(self) -> str: - """ - Generates the text version - """ + def to_wat(self) -> str: return '(import "{}" "{}" (func ${}{}{}))'.format( self.module, self.name, self.intname, ''.join( - f' (param {typ.to_wasm()})' + f' (param {typ.to_wat()})' for _, typ in self.params ), - '' if isinstance(self.result, OurTypeNone) - else f' (result {self.result.to_wasm()})' + '' if isinstance(self.result, WasmTypeNone) + else f' (result {self.result.to_wat()})' ) -class Statement: +class Statement(WatSerializable): """ Represents a Web Assembly statement """ @@ -189,73 +113,76 @@ class Statement: self.args = args self.comment = comment - def generate(self) -> str: - """ - Generates the text version - """ + def to_wat(self) -> str: args = ' '.join(self.args) comment = f' ;; {self.comment}' if self.comment else '' return f'{self.name} {args}{comment}' -class Function: +class Function(WatSerializable): """ Represents a Web Assembly function """ def __init__( self, name: str, - exported: bool, + exported_name: Optional[str], params: Iterable[Param], locals_: Iterable[Param], - result: OurType, + result: WasmType, statements: Iterable[Statement], ) -> None: self.name = name - self.exported = exported + self.exported_name = exported_name self.params = [*params] self.locals = [*locals_] self.result = result self.statements = [*statements] - def generate(self) -> str: - """ - Generates the text version - """ + def to_wat(self) -> str: header = f'${self.name}' # Name for internal use - if self.exported: + if self.exported_name is not None: # Name for external use - # TODO: Consider: Make exported('export_name') work - header += f' (export "{self.name}")' + header += f' (export "{self.exported_name}")' for nam, typ in self.params: - header += f' (param ${nam} {typ.to_wasm()})' + header += f' (param ${nam} {typ.to_wat()})' - if not isinstance(self.result, OurTypeNone): - header += f' (result {self.result.to_wasm()})' + if not isinstance(self.result, WasmTypeNone): + header += f' (result {self.result.to_wat()})' for nam, typ in self.locals: - header += f' (local ${nam} {typ.to_wasm()})' + header += f' (local ${nam} {typ.to_wat()})' return '(func {}\n {}\n )'.format( header, - '\n '.join(x.generate() for x in self.statements), + '\n '.join(x.to_wat() for x in self.statements), ) -class ModuleMemory: +class ModuleMemory(WatSerializable): + """ + Represents a WebAssembly module's memory + """ def __init__(self, data: bytes = b'') -> None: self.data = data - def generate(self) -> str: - return '(memory 1)\n (data (memory 0) (i32.const 0) "{}")\n (export "memory" (memory 0))\n'.format( - ''.join( - f'\\{x:02x}' - for x in self.data - ) + def to_wat(self) -> str: + """ + Renders this memory as WebAssembly Text + """ + data = ''.join( + f'\\{x:02x}' + for x in self.data ) -class Module: + return ( + '(memory 1)\n ' + f'(data (memory 0) (i32.const 0) "{data}")\n ' + '(export "memory" (memory 0))\n' + ) + +class Module(WatSerializable): """ Represents a Web Assembly module """ @@ -264,12 +191,12 @@ class Module: self.functions: List[Function] = [] self.memory = ModuleMemory(b'\x04') # For ___new_reference___ - def generate(self) -> str: + def to_wat(self) -> str: """ Generates the text version """ return '(module\n {}\n {}\n {})\n'.format( - '\n '.join(x.generate() for x in self.imports), - self.memory.generate(), - '\n '.join(x.generate() for x in self.functions), + '\n '.join(x.to_wat() for x in self.imports), + self.memory.to_wat(), + '\n '.join(x.to_wat() for x in self.functions), ) diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index af90b92..c1ec6a7 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -59,6 +59,9 @@ class SuiteResult: } class Suite: + """ + WebAssembly test suite + """ def __init__(self, code_py, test_name): self.code_py = code_py self.test_name = test_name @@ -76,10 +79,10 @@ class Suite: assert self.code_py == '\n' + our_module.render() # \n for formatting in tests # Compile - wat_module = module(our_module) + wasm_module = module(our_module) # Render as text - code_wat = wat_module.generate() + code_wat = wasm_module.to_wat() sys.stderr.write(f'{DASHES} Assembly {DASHES}\n')