diff --git a/TODO.md b/TODO.md index b2ab013..29f8e80 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,4 @@ # TODO -- Implement foldl for bytes - Implement a trace() builtin for debugging - Implement a proper type matching / checking system diff --git a/examples/fold.html b/examples/fold.html new file mode 100644 index 0000000..0007e6f --- /dev/null +++ b/examples/fold.html @@ -0,0 +1,65 @@ + + + +Examples - Fold + + +

Fold

+ +List - Source - WebAssembly + +
+ + + + + + diff --git a/examples/fold.py b/examples/fold.py new file mode 100644 index 0000000..58b4b83 --- /dev/null +++ b/examples/fold.py @@ -0,0 +1,6 @@ +def u8_or(l: u8, r: u8) -> u8: + return l | r + +@exported +def foldl_u8_or_1(b: bytes) -> u8: + return foldl(u8_or, 1, b) diff --git a/examples/index.html b/examples/index.html index 46e1fc1..f01ade3 100644 --- a/examples/index.html +++ b/examples/index.html @@ -11,6 +11,7 @@

Technical

diff --git a/phasm/codestyle.py b/phasm/codestyle.py index 93853dd..f700d9b 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -125,6 +125,10 @@ def expression(inp: ourlang.Expression) -> str: if isinstance(inp, ourlang.AccessTupleMember): return f'{expression(inp.varref)}[{inp.member.idx}]' + if isinstance(inp, ourlang.Fold): + fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr' + return f'{fold_name}({inp.func.name}, {expression(inp.base)}, {expression(inp.iter)})' + raise NotImplementedError(expression, inp) def statement(inp: ourlang.Statement) -> Statements: diff --git a/phasm/compiler.py b/phasm/compiler.py index e693601..cb640bf 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -77,9 +77,10 @@ OPERATOR_MAP = { U8_OPERATOR_MAP = { # Under the hood, this is an i32 - # Implementing XOR is fine since the 3 remaining + # Implementing XOR, OR is fine since the 3 remaining # bytes stay zero after this operation '^': 'xor', + '|': 'or', } U32_OPERATOR_MAP = { @@ -254,25 +255,81 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: return if isinstance(inp, ourlang.Fold): - mtyp = LOAD_STORE_TYPE_MAP.get(inp.base.type.__class__) - if mtyp is None: - # In the future might extend this by having structs or tuples - # as members of struct or tuples - raise NotImplementedError(expression, inp, inp.base) - - if inp.iter.type.__class__.__name__ != 'TypeBytes': - raise NotImplementedError(expression, inp, inp.iter.type) - - tmp_var = wgn.temp_var_u8(f'fold_{codestyle.type_(inp.type)}') - expression(wgn, inp.base) - wgn.local.set(tmp_var) - - # FIXME: Do a loop - wgn.unreachable(comment='Not implemented yet') + expression_fold(wgn, inp) return raise NotImplementedError(expression, inp) +def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None: + """ + Compile: Fold expression + """ + mtyp = LOAD_STORE_TYPE_MAP.get(inp.base.type.__class__) + if mtyp is None: + # In the future might extend this by having structs or tuples + # as members of struct or tuples + raise NotImplementedError(expression, inp, inp.base) + + if inp.iter.type.__class__.__name__ != 'TypeBytes': + raise NotImplementedError(expression, inp, inp.iter.type) + + wgn.add_statement('nop', comment='acu :: u8') + acu_var = wgn.temp_var_u8(f'fold_{codestyle.type_(inp.type)}_acu') + wgn.add_statement('nop', comment='adr :: bytes*') + adr_var = wgn.temp_var_i32(f'fold_i32_adr') + wgn.add_statement('nop', comment='len :: i32') + len_var = wgn.temp_var_i32(f'fold_i32_len') + + wgn.add_statement('nop', comment='acu = base') + expression(wgn, inp.base) + wgn.local.set(acu_var) + + wgn.add_statement('nop', comment='adr = adr(iter)') + expression(wgn, inp.iter) + wgn.local.set(adr_var) + + wgn.add_statement('nop', comment='len = len(iter)') + wgn.local.get(adr_var) + wgn.i32.load() + wgn.local.set(len_var) + + wgn.add_statement('nop', comment='i = 0') + idx_var = wgn.temp_var_i32(f'fold_{codestyle.type_(inp.type)}_idx') + wgn.i32.const(0) + wgn.local.set(idx_var) + + wgn.add_statement('nop', comment='if i < len') + wgn.local.get(idx_var) + wgn.local.get(len_var) + wgn.i32.lt_u() + with wgn.if_(): + wgn.add_statement('nop', comment='while True') + with wgn.loop(): + wgn.add_statement('nop', comment='acu = func(acu, iter[i])') + wgn.local.get(acu_var) + wgn.local.get(adr_var) + wgn.local.get(idx_var) + wgn.call(stdlib_types.__subscript_bytes__) + wgn.add_statement('call', f'${inp.func.name}') + wgn.local.set(acu_var) + + wgn.add_statement('nop', comment='i = i + 1') + wgn.local.get(idx_var) + wgn.i32.const(1) + wgn.i32.add() + wgn.local.set(idx_var) + + wgn.add_statement('nop', comment='if i >= len: break') + wgn.local.get(idx_var) + wgn.local.get(len_var) + wgn.i32.lt_u() + wgn.br_if(0) + + # return acu + wgn.local.get(acu_var) + + return + def statement_return(wgn: WasmGenerator, inp: ourlang.StatementReturn) -> None: """ Compile: Return statement diff --git a/phasm/parser.py b/phasm/parser.py index d9fbbc1..2cb1b4a 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -244,6 +244,8 @@ class OurVisitor: operator = '-' elif isinstance(node.op, ast.Mult): operator = '*' + elif isinstance(node.op, ast.BitOr): + operator = '|' elif isinstance(node.op, ast.BitXor): operator = '^' else: diff --git a/phasm/wasmgenerator.py b/phasm/wasmgenerator.py index dc16589..57b1fe5 100644 --- a/phasm/wasmgenerator.py +++ b/phasm/wasmgenerator.py @@ -35,6 +35,7 @@ class Generator_i32i64: self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq') self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne') self.lt_u = functools.partial(self.generator.add_statement, f'{prefix}.lt_u') + self.ge_u = functools.partial(self.generator.add_statement, f'{prefix}.ge_u') # 2.4.4. Memory Instructions self.load = functools.partial(self.generator.add_statement, f'{prefix}.load') @@ -128,15 +129,18 @@ class Generator: self.nop = functools.partial(self.add_statement, 'nop') self.unreachable = functools.partial(self.add_statement, 'unreachable') # block - # loop + self.loop = functools.partial(GeneratorBlock, self, 'loop') self.if_ = functools.partial(GeneratorBlock, self, 'if') # br - # br_if + # br_if - see below # br_table self.return_ = functools.partial(self.add_statement, 'return') - # call + # call - see below # call_indirect + def br_if(self, idx: int) -> None: + self.add_statement('br_if', f'{idx}') + def call(self, function: wasm.Function) -> None: self.add_statement('call', f'${function.name}') diff --git a/tests/integration/test_builtins.py b/tests/integration/test_builtins.py new file mode 100644 index 0000000..4d0035c --- /dev/null +++ b/tests/integration/test_builtins.py @@ -0,0 +1,65 @@ +import sys + +import pytest + +from .helpers import Suite, write_header +from .runners import RunnerPywasm + +def setup_interpreter(phash_code: str) -> RunnerPywasm: + runner = RunnerPywasm(phash_code) + + runner.parse() + runner.compile_ast() + runner.compile_wat() + runner.compile_wasm() + runner.interpreter_setup() + runner.interpreter_load() + + write_header(sys.stderr, 'Phasm') + runner.dump_phasm_code(sys.stderr) + write_header(sys.stderr, 'Assembly') + runner.dump_wasm_wat(sys.stderr) + + return runner + +@pytest.mark.integration_test +def test_foldl_1(): + code_py = """ +def u8_or(l: u8, r: u8) -> u8: + return l | r + +@exported +def testEntry(b: bytes) -> u8: + return foldl(u8_or, 128, b) +""" + suite = Suite(code_py) + + result = suite.run_code(b'') + assert 128 == result.returned_value + + result = suite.run_code(b'\x80', runtime='pywasm') + assert 128 == result.returned_value + + result = suite.run_code(b'\x80\x40', runtime='pywasm') + assert 192 == result.returned_value + + result = suite.run_code(b'\x80\x40\x20\x10', runtime='pywasm') + assert 240 == result.returned_value + + result = suite.run_code(b'\x80\x40\x20\x10\x08\x04\x02\x01', runtime='pywasm') + assert 255 == result.returned_value + +@pytest.mark.integration_test +def test_foldl_2(): + code_py = """ +def xor(l: u8, r: u8) -> u8: + return l ^ r + +@exported +def testEntry(a: bytes, b: bytes) -> u8: + return foldl(xor, 0, a) ^ foldl(xor, 0, b) +""" + suite = Suite(code_py) + + result = suite.run_code(b'\x55\x0F', b'\x33\x80') + assert 233 == result.returned_value