From fea817ca00dd3e161acd5adad89645b286896ab9 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Thu, 4 Aug 2022 21:18:53 +0200 Subject: [PATCH] Added more robust and easy way to generate WASM --- phasm/compiler.py | 139 +++++++++++++------------ phasm/wasmeasy.py | 69 ++++++++++++ tests/integration/test_stdlib_alloc.py | 2 - 3 files changed, 140 insertions(+), 70 deletions(-) create mode 100644 phasm/wasmeasy.py diff --git a/phasm/compiler.py b/phasm/compiler.py index b74b1c7..52624a8 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -6,6 +6,9 @@ from typing import Generator from . import ourlang from . import typing from . import wasm +from . import wasmeasy + +from .wasmeasy import i32, i64 Statements = Generator[wasm.Statement, None, None] @@ -107,23 +110,23 @@ def expression(inp: ourlang.Expression) -> Statements: Compile: Any expression """ if isinstance(inp, ourlang.ConstantUInt8): - yield wasm.Statement('i32.const', str(inp.value)) + yield i32.const(inp.value) return if isinstance(inp, ourlang.ConstantUInt32): - yield wasm.Statement('i32.const', str(inp.value)) + yield i32.const(inp.value) return if isinstance(inp, ourlang.ConstantUInt64): - yield wasm.Statement('i64.const', str(inp.value)) + yield i64.const(inp.value) return if isinstance(inp, ourlang.ConstantInt32): - yield wasm.Statement('i32.const', str(inp.value)) + yield i32.const(inp.value) return if isinstance(inp, ourlang.ConstantInt64): - yield wasm.Statement('i64.const', str(inp.value)) + yield i64.const(inp.value) return if isinstance(inp, ourlang.ConstantFloat32): @@ -196,7 +199,7 @@ def expression(inp: ourlang.Expression) -> Statements: if isinstance(inp.type, typing.TypeInt32): if inp.operator == 'len': if isinstance(inp.right.type, typing.TypeBytes): - yield wasm.Statement('i32.load') + yield i32.load() return raise NotImplementedError(expression, inp.type, inp.operator) @@ -384,19 +387,21 @@ def _generate____new_reference___(mod: ourlang.Module) -> wasm.Function: ], type_(mod.types['i32']), [ - wasm.Statement('i32.const', '0'), - wasm.Statement('i32.const', '0'), - wasm.Statement('i32.load'), + i32.const(0), + i32.const(0), + i32.load(), wasm.Statement('local.tee', '$result', comment='Address for this call'), wasm.Statement('local.get', '$alloc_size'), - wasm.Statement('i32.add'), - wasm.Statement('i32.store', comment='Address for the next call'), + i32.add(), + i32.store(comment='Address for the next call'), wasm.Statement('local.get', '$result'), ], ) -STDLIB_ALLOC__FREE_BLOCK = '0x08' -STDLIB_ALLOC__UNALLOC_ADDR = '0x0C' +STDLIB_ALLOC__IDENTIFIER = 0xA1C0 +STDLIB_ALLOC__RESERVED0 = 0x04 +STDLIB_ALLOC__FREE_BLOCK = 0x08 +STDLIB_ALLOC__UNALLOC_ADDR = 0x0C def _generate_stdlib_alloc___init__(mod: ourlang.Module) -> wasm.Function: return wasm.Function( @@ -406,31 +411,31 @@ def _generate_stdlib_alloc___init__(mod: ourlang.Module) -> wasm.Function: [], wasm.WasmTypeNone(), [ - wasm.Statement('i32.const', '0x00'), - wasm.Statement('i32.load'), - wasm.Statement('i32.const', '0xA1C0'), - wasm.Statement('i32.eq'), - wasm.Statement('if', comment='Already set up'), - wasm.Statement('return'), - wasm.Statement('end'), + i32.const(0), + i32.load(), + i32.const(STDLIB_ALLOC__IDENTIFIER), + i32.eq(), + *wasmeasy.if_( + wasm.Statement('return', comment='Already set up'), + ), - wasm.Statement('i32.const', '0x04', comment='Reserved'), - wasm.Statement('i32.const', '0x00000000'), - wasm.Statement('i32.store'), + i32.const(STDLIB_ALLOC__RESERVED0, comment='Reserved'), + i32.const(0), + i32.store(), - wasm.Statement('i32.const', STDLIB_ALLOC__FREE_BLOCK, + i32.const(STDLIB_ALLOC__FREE_BLOCK, comment='Address of next free block'), - wasm.Statement('i32.const', '0x00000000'), - wasm.Statement('i32.store'), + i32.const(0, comment='None to start with'), + i32.store(), - wasm.Statement('i32.const', STDLIB_ALLOC__UNALLOC_ADDR, + i32.const(STDLIB_ALLOC__UNALLOC_ADDR, comment='Address of first unallocated byte'), - wasm.Statement('i32.const', '0x10'), - wasm.Statement('i32.store'), + i32.const(0x10), + i32.store(), - wasm.Statement('i32.const', '0x00', comment='Done setting up'), - wasm.Statement('i32.const', '0xA1C0'), - wasm.Statement('i32.store'), + i32.const(0, comment='Done setting up'), + i32.const(STDLIB_ALLOC__IDENTIFIER), + i32.store(), ], ) @@ -446,14 +451,14 @@ def _generate_stdlib_alloc___find_free_block__(mod: ourlang.Module) -> wasm.Func ], type_(mod.types['i32']), [ - wasm.Statement('i32.const', STDLIB_ALLOC__FREE_BLOCK), - wasm.Statement('i32.load'), - wasm.Statement('i32.const', '0x00000000'), - wasm.Statement('i32.eq'), - wasm.Statement('if'), - wasm.Statement('i32.const', '0x00000000'), - wasm.Statement('return'), - wasm.Statement('end'), + i32.const(STDLIB_ALLOC__FREE_BLOCK), + i32.load(), + i32.const(0), + i32.eq(), + *wasmeasy.if_( + i32.const(0), + wasm.Statement('return'), + ), wasm.Statement('unreachable'), ], ) @@ -470,13 +475,13 @@ def _generate_stdlib_alloc___alloc__(mod: ourlang.Module) -> wasm.Function: ], type_(mod.types['i32']), [ - wasm.Statement('i32.const', '0'), - wasm.Statement('i32.load'), - wasm.Statement('i32.const', '0xA1C0'), - wasm.Statement('i32.ne'), - wasm.Statement('if', comment='Failed to set up or memory corruption'), - wasm.Statement('unreachable'), - wasm.Statement('end'), + i32.const(0), + i32.load(), + i32.const(STDLIB_ALLOC__IDENTIFIER), + i32.ne(), + *wasmeasy.if_( + wasm.Statement('unreachable'), + ), wasm.Statement('local.get', '$alloc_size'), wasm.Statement('call', '$stdlib.alloc.__find_free_block__'), @@ -484,35 +489,33 @@ def _generate_stdlib_alloc___alloc__(mod: ourlang.Module) -> wasm.Function: # Check if there was a free block wasm.Statement('local.get', '$result'), - wasm.Statement('i32.const', '0x00000000'), - wasm.Statement('i32.eq'), - wasm.Statement('if', comment='No free block found'), + i32.const(0), + i32.eq(), + *wasmeasy.if_( + # Use unallocated space + i32.const(STDLIB_ALLOC__UNALLOC_ADDR), - # Use unallocated space - wasm.Statement('i32.const', STDLIB_ALLOC__UNALLOC_ADDR), + i32.const(STDLIB_ALLOC__UNALLOC_ADDR), + i32.load(), + wasm.Statement('local.tee', '$result'), - wasm.Statement('i32.const', STDLIB_ALLOC__UNALLOC_ADDR), - wasm.Statement('i32.load'), - wasm.Statement('local.tee', '$result'), - - # Updated unalloc pointer (address already set on stack) - wasm.Statement('i32.const', '0x04'), # Struct size - wasm.Statement('i32.add'), - wasm.Statement('local.get', '$alloc_size'), - wasm.Statement('i32.add'), - wasm.Statement('i32.store', 'offset=0'), - - wasm.Statement('end'), + # Updated unalloc pointer (address already set on stack) + i32.const(4), # Header size + i32.add(), + wasm.Statement('local.get', '$alloc_size'), + i32.add(), + i32.store('offset=0'), + ), # Store block size wasm.Statement('local.get', '$result'), wasm.Statement('local.get', '$alloc_size'), - wasm.Statement('i32.store', 'offset=0'), + i32.store('offset=0'), # Return address of the allocated bytes wasm.Statement('local.get', '$result'), - wasm.Statement('i32.const', '0x04'), - wasm.Statement('i32.add'), + i32.const(4), # Header size + i32.add(), ], ) @@ -530,7 +533,7 @@ def _generate____access_bytes_index___(mod: ourlang.Module) -> wasm.Function: [ wasm.Statement('local.get', '$ofs'), wasm.Statement('local.get', '$byt'), - wasm.Statement('i32.load'), + i32.load(), wasm.Statement('i32.lt_u'), wasm.Statement('if', comment='$ofs < len($byt)'), diff --git a/phasm/wasmeasy.py b/phasm/wasmeasy.py new file mode 100644 index 0000000..01c5d0c --- /dev/null +++ b/phasm/wasmeasy.py @@ -0,0 +1,69 @@ +""" +Helper functions to quickly generate WASM code +""" +from typing import List, Optional + +import functools + +from . import wasm + +#pylint: disable=C0103,C0115,C0116,R0201,R0902 + +class Prefix_inn_fnn: + def __init__(self, prefix: str) -> None: + self.prefix = prefix + + # 6.5.5. Memory Instructions + self.load = functools.partial(wasm.Statement, f'{self.prefix}.load') + self.store = functools.partial(wasm.Statement, f'{self.prefix}.store') + + # 6.5.6. Numeric Instructions + self.clz = functools.partial(wasm.Statement, f'{self.prefix}.clz') + self.ctz = functools.partial(wasm.Statement, f'{self.prefix}.ctz') + self.popcnt = functools.partial(wasm.Statement, f'{self.prefix}.popcnt') + self.add = functools.partial(wasm.Statement, f'{self.prefix}.add') + self.sub = functools.partial(wasm.Statement, f'{self.prefix}.sub') + self.mul = functools.partial(wasm.Statement, f'{self.prefix}.mul') + self.div_s = functools.partial(wasm.Statement, f'{self.prefix}.div_s') + self.div_u = functools.partial(wasm.Statement, f'{self.prefix}.div_u') + self.rem_s = functools.partial(wasm.Statement, f'{self.prefix}.rem_s') + self.rem_u = functools.partial(wasm.Statement, f'{self.prefix}.rem_u') + self.and_ = functools.partial(wasm.Statement, f'{self.prefix}.and') + self.or_ = functools.partial(wasm.Statement, f'{self.prefix}.or') + self.xor = functools.partial(wasm.Statement, f'{self.prefix}.xor') + self.shl = functools.partial(wasm.Statement, f'{self.prefix}.shl') + self.shr_s = functools.partial(wasm.Statement, f'{self.prefix}.shr_s') + self.shr_u = functools.partial(wasm.Statement, f'{self.prefix}.shr_u') + self.rotl = functools.partial(wasm.Statement, f'{self.prefix}.rotl') + self.rotr = functools.partial(wasm.Statement, f'{self.prefix}.rotr') + + self.eqz = functools.partial(wasm.Statement, f'{self.prefix}.eqz') + self.eq = functools.partial(wasm.Statement, f'{self.prefix}.eq') + self.ne = functools.partial(wasm.Statement, f'{self.prefix}.ne') + self.lt_s = functools.partial(wasm.Statement, f'{self.prefix}.lt_s') + self.lt_u = functools.partial(wasm.Statement, f'{self.prefix}.lt_u') + self.gt_s = functools.partial(wasm.Statement, f'{self.prefix}.gt_s') + self.gt_u = functools.partial(wasm.Statement, f'{self.prefix}.gt_u') + self.le_s = functools.partial(wasm.Statement, f'{self.prefix}.le_s') + self.le_u = functools.partial(wasm.Statement, f'{self.prefix}.le_u') + self.ge_s = functools.partial(wasm.Statement, f'{self.prefix}.ge_s') + self.ge_u = functools.partial(wasm.Statement, f'{self.prefix}.ge_u') + + def const(self, value: int, comment: Optional[str] = None) -> wasm.Statement: + return wasm.Statement(f'{self.prefix}.const', f'0x{value:08x}', comment=comment) + +i32 = Prefix_inn_fnn('i32') +i64 = Prefix_inn_fnn('i64') + +class Block: + def __init__(self, start: str) -> None: + self.start = start + + def __call__(self, *statements: wasm.Statement) -> List[wasm.Statement]: + return [ + wasm.Statement('if'), + *statements, + wasm.Statement('end'), + ] + +if_ = Block('if') diff --git a/tests/integration/test_stdlib_alloc.py b/tests/integration/test_stdlib_alloc.py index 80c3065..8763e46 100644 --- a/tests/integration/test_stdlib_alloc.py +++ b/tests/integration/test_stdlib_alloc.py @@ -108,5 +108,3 @@ def testEntry() -> u8: assert 0x14 == offset0 assert 0x38 == offset1 assert 0x5C == offset2 - - assert False