Cleanup to wasm.py

This commit is contained in:
Johan B.W. de Vries 2022-07-09 12:30:28 +02:00
parent c181c61040
commit 14eede6b06
5 changed files with 109 additions and 181 deletions

View File

@ -16,8 +16,8 @@ def main(source: str, sink: str) -> int:
code_py = fil.read() code_py = fil.read()
our_module = our_process(code_py, source) our_module = our_process(code_py, source)
wat_module = module(our_module) wasm_module = module(our_module)
code_wat = wat_module.generate() code_wat = wasm_module.to_wat()
with open(sink, 'w') as fil: with open(sink, 'w') as fil:
fil.write(code_wat) fil.write(code_wat)

View File

@ -8,31 +8,31 @@ from . import wasm
Statements = Generator[wasm.Statement, None, None] 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): if isinstance(inp, ourlang.OurTypeNone):
return wasm.OurTypeNone() return wasm.WasmTypeNone()
if isinstance(inp, ourlang.OurTypeUInt8): if isinstance(inp, ourlang.OurTypeUInt8):
# WebAssembly has only support for 32 and 64 bits # WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte # So we need to store more memory per byte
return wasm.OurTypeInt32() return wasm.WasmTypeInt32()
if isinstance(inp, ourlang.OurTypeInt32): if isinstance(inp, ourlang.OurTypeInt32):
return wasm.OurTypeInt32() return wasm.WasmTypeInt32()
if isinstance(inp, ourlang.OurTypeInt64): if isinstance(inp, ourlang.OurTypeInt64):
return wasm.OurTypeInt64() return wasm.WasmTypeInt64()
if isinstance(inp, ourlang.OurTypeFloat32): if isinstance(inp, ourlang.OurTypeFloat32):
return wasm.OurTypeFloat32() return wasm.WasmTypeFloat32()
if isinstance(inp, ourlang.OurTypeFloat64): if isinstance(inp, ourlang.OurTypeFloat64):
return wasm.OurTypeFloat64() return wasm.WasmTypeFloat64()
if isinstance(inp, (ourlang.Struct, ourlang.OurTypeTuple, ourlang.OurTypeBytes)): if isinstance(inp, (ourlang.Struct, ourlang.OurTypeTuple, ourlang.OurTypeBytes)):
# Structs and tuples are passed as pointer # Structs and tuples are passed as pointer
# And pointers are i32 # And pointers are i32
return wasm.OurTypeInt32() return wasm.WasmTypeInt32()
raise NotImplementedError(type_, inp) raise NotImplementedError(type_, inp)
@ -238,14 +238,14 @@ def function(inp: ourlang.Function) -> wasm.Function:
*_generate_tuple_constructor(inp) *_generate_tuple_constructor(inp)
] ]
locals_ = [ locals_ = [
('___new_reference___addr', wasm.OurTypeInt32(), ), ('___new_reference___addr', wasm.WasmTypeInt32(), ),
] ]
elif isinstance(inp, ourlang.StructConstructor): elif isinstance(inp, ourlang.StructConstructor):
statements = [ statements = [
*_generate_struct_constructor(inp) *_generate_struct_constructor(inp)
] ]
locals_ = [ locals_ = [
('___new_reference___addr', wasm.OurTypeInt32(), ), ('___new_reference___addr', wasm.WasmTypeInt32(), ),
] ]
else: else:
statements = [ statements = [
@ -257,7 +257,7 @@ def function(inp: ourlang.Function) -> wasm.Function:
return wasm.Function( return wasm.Function(
inp.name, inp.name,
inp.exported, inp.name if inp.exported else None,
[ [
function_argument(x) function_argument(x)
for x in inp.posonlyargs 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: def _generate____new_reference___(mod: ourlang.Module) -> wasm.Function:
return wasm.Function( return wasm.Function(
'___new_reference___', '___new_reference___',
True, '___new_reference___',
[ [
('alloc_size', type_(mod.types['i32']), ), ('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: def _generate____access_bytes_index___(mod: ourlang.Module) -> wasm.Function:
return wasm.Function( return wasm.Function(
'___access_bytes_index___', '___access_bytes_index___',
False, None,
[ [
('byt', type_(mod.types['i32']), ), ('byt', type_(mod.types['i32']), ),
('ofs', type_(mod.types['i32']), ), ('ofs', type_(mod.types['i32']), ),

View File

@ -458,13 +458,12 @@ class Function:
""" """
A function processes input and produces output 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 name: str
lineno: int lineno: int
exported: bool exported: bool
imported: bool imported: bool
buildin: bool
statements: List[Statement] statements: List[Statement]
returns: OurType returns: OurType
posonlyargs: List[Tuple[str, OurType]] posonlyargs: List[Tuple[str, OurType]]
@ -474,7 +473,6 @@ class Function:
self.lineno = lineno self.lineno = lineno
self.exported = False self.exported = False
self.imported = False self.imported = False
self.buildin = False
self.statements = [] self.statements = []
self.returns = OurTypeNone() self.returns = OurTypeNone()
self.posonlyargs = [] self.posonlyargs = []

View File

@ -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 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
### class WatSerializable:
### This part is more intermediate code """
### 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: class WasmType(WatSerializable):
def to_python(self) -> str: """
raise NotImplementedError(self, 'to_python') Type base class
"""
def to_wasm(self) -> str: class WasmTypeNone(WasmType):
raise NotImplementedError(self, 'to_wasm') """
Type when there is no type
"""
def to_wat(self) -> str:
raise Exception('None type is only a placeholder')
def alloc_size(self) -> int: class WasmTypeInt32(WasmType):
raise NotImplementedError(self, 'alloc_size') """
i32 value
class OurTypeNone(OurType): Signed or not depends on the operations, not the type
pass """
def to_wat(self) -> str:
class OurTypeBool(OurType):
pass
class OurTypeInt32(OurType):
def to_python(self) -> str:
return 'i32' return 'i32'
def to_wasm(self) -> str: class WasmTypeInt64(WasmType):
return 'i32' """
i64 value
def alloc_size(self) -> int: Signed or not depends on the operations, not the type
return 4 """
def to_wat(self) -> str:
class OurTypeInt64(OurType):
def to_wasm(self) -> str:
return 'i64' return 'i64'
def alloc_size(self) -> int: class WasmTypeFloat32(WasmType):
return 8 """
f32 value
class OurTypeFloat32(OurType): """
def to_wasm(self) -> str: def to_wat(self) -> str:
return 'f32' return 'f32'
def alloc_size(self) -> int: class WasmTypeFloat64(WasmType):
return 4 """
f64 value
class OurTypeFloat64(OurType): """
def to_wasm(self) -> str: def to_wat(self) -> str:
return 'f64' return 'f64'
def alloc_size(self) -> int: class WasmTypeVector(WasmType):
return 8
class OurTypeVector(OurType):
""" """
A vector is a 128-bit value A vector is a 128-bit value
""" """
def to_wasm(self) -> str: def to_wat(self) -> str:
return 'v128' return 'v128'
def alloc_size(self) -> int: class WasmTypeVectorInt32x4(WasmTypeVector):
return 16
class OurTypeVectorInt32x4(OurTypeVector):
""" """
4 Int32 values in a single vector 4 Int32 values in a single vector
""" """
class Constant: Param = Tuple[str, WasmType]
"""
TODO
"""
def __init__(self, value: Union[None, bool, int, float]) -> None:
self.value = value
class ClassMember: class Import(WatSerializable):
"""
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:
""" """
Represents a Web Assembly import Represents a Web Assembly import
""" """
@ -156,7 +83,7 @@ class Import:
name: str, name: str,
intname: str, intname: str,
params: Iterable[Param], params: Iterable[Param],
result: OurType, result: WasmType,
) -> None: ) -> None:
self.module = module self.module = module
self.name = name self.name = name
@ -164,23 +91,20 @@ class Import:
self.params = [*params] self.params = [*params]
self.result = result self.result = result
def generate(self) -> str: def to_wat(self) -> str:
"""
Generates the text version
"""
return '(import "{}" "{}" (func ${}{}{}))'.format( return '(import "{}" "{}" (func ${}{}{}))'.format(
self.module, self.module,
self.name, self.name,
self.intname, self.intname,
''.join( ''.join(
f' (param {typ.to_wasm()})' f' (param {typ.to_wat()})'
for _, typ in self.params for _, typ in self.params
), ),
'' if isinstance(self.result, OurTypeNone) '' if isinstance(self.result, WasmTypeNone)
else f' (result {self.result.to_wasm()})' else f' (result {self.result.to_wat()})'
) )
class Statement: class Statement(WatSerializable):
""" """
Represents a Web Assembly statement Represents a Web Assembly statement
""" """
@ -189,73 +113,76 @@ class Statement:
self.args = args self.args = args
self.comment = comment self.comment = comment
def generate(self) -> str: def to_wat(self) -> str:
"""
Generates the text version
"""
args = ' '.join(self.args) args = ' '.join(self.args)
comment = f' ;; {self.comment}' if self.comment else '' comment = f' ;; {self.comment}' if self.comment else ''
return f'{self.name} {args}{comment}' return f'{self.name} {args}{comment}'
class Function: class Function(WatSerializable):
""" """
Represents a Web Assembly function Represents a Web Assembly function
""" """
def __init__( def __init__(
self, self,
name: str, name: str,
exported: bool, exported_name: Optional[str],
params: Iterable[Param], params: Iterable[Param],
locals_: Iterable[Param], locals_: Iterable[Param],
result: OurType, result: WasmType,
statements: Iterable[Statement], statements: Iterable[Statement],
) -> None: ) -> None:
self.name = name self.name = name
self.exported = exported self.exported_name = exported_name
self.params = [*params] self.params = [*params]
self.locals = [*locals_] self.locals = [*locals_]
self.result = result self.result = result
self.statements = [*statements] self.statements = [*statements]
def generate(self) -> str: def to_wat(self) -> str:
"""
Generates the text version
"""
header = f'${self.name}' # Name for internal use header = f'${self.name}' # Name for internal use
if self.exported: if self.exported_name is not None:
# Name for external use # Name for external use
# TODO: Consider: Make exported('export_name') work header += f' (export "{self.exported_name}")'
header += f' (export "{self.name}")'
for nam, typ in self.params: 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): if not isinstance(self.result, WasmTypeNone):
header += f' (result {self.result.to_wasm()})' header += f' (result {self.result.to_wat()})'
for nam, typ in self.locals: 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( return '(func {}\n {}\n )'.format(
header, 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: def __init__(self, data: bytes = b'') -> None:
self.data = data self.data = data
def generate(self) -> str: def to_wat(self) -> str:
return '(memory 1)\n (data (memory 0) (i32.const 0) "{}")\n (export "memory" (memory 0))\n'.format( """
''.join( Renders this memory as WebAssembly Text
f'\\{x:02x}' """
for x in self.data 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 Represents a Web Assembly module
""" """
@ -264,12 +191,12 @@ class Module:
self.functions: List[Function] = [] self.functions: List[Function] = []
self.memory = ModuleMemory(b'\x04') # For ___new_reference___ self.memory = ModuleMemory(b'\x04') # For ___new_reference___
def generate(self) -> str: def to_wat(self) -> str:
""" """
Generates the text version Generates the text version
""" """
return '(module\n {}\n {}\n {})\n'.format( return '(module\n {}\n {}\n {})\n'.format(
'\n '.join(x.generate() for x in self.imports), '\n '.join(x.to_wat() for x in self.imports),
self.memory.generate(), self.memory.to_wat(),
'\n '.join(x.generate() for x in self.functions), '\n '.join(x.to_wat() for x in self.functions),
) )

View File

@ -59,6 +59,9 @@ class SuiteResult:
} }
class Suite: class Suite:
"""
WebAssembly test suite
"""
def __init__(self, code_py, test_name): def __init__(self, code_py, test_name):
self.code_py = code_py self.code_py = code_py
self.test_name = test_name self.test_name = test_name
@ -76,10 +79,10 @@ class Suite:
assert self.code_py == '\n' + our_module.render() # \n for formatting in tests assert self.code_py == '\n' + our_module.render() # \n for formatting in tests
# Compile # Compile
wat_module = module(our_module) wasm_module = module(our_module)
# Render as text # Render as text
code_wat = wat_module.generate() code_wat = wasm_module.to_wat()
sys.stderr.write(f'{DASHES} Assembly {DASHES}\n') sys.stderr.write(f'{DASHES} Assembly {DASHES}\n')