MVP #1
1
TODO.md
1
TODO.md
@ -1,5 +1,4 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- Implement foldl for bytes
|
|
||||||
- Implement a trace() builtin for debugging
|
- Implement a trace() builtin for debugging
|
||||||
- Implement a proper type matching / checking system
|
- Implement a proper type matching / checking system
|
||||||
|
|||||||
65
examples/fold.html
Normal file
65
examples/fold.html
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Examples - Fold</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Fold</h1>
|
||||||
|
|
||||||
|
<a href="index.html">List</a> - <a href="fold.py.html">Source</a> - <a href="fold.wat.html">WebAssembly</a>
|
||||||
|
|
||||||
|
<div style="white-space: pre;" id="results"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
let importObject = {
|
||||||
|
'imports': {
|
||||||
|
'log': log,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let results = document.getElementById('results');
|
||||||
|
|
||||||
|
function log(txt)
|
||||||
|
{
|
||||||
|
let span = document.createElement('span');
|
||||||
|
span.innerHTML = txt;
|
||||||
|
results.appendChild(span);
|
||||||
|
let br = document.createElement('br');
|
||||||
|
results.appendChild(br);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebAssembly.instantiateStreaming(fetch('fold.wasm'), importObject)
|
||||||
|
.then(app => {
|
||||||
|
stdlib_alloc___init__ = app.instance.exports['stdlib.alloc.__init__']
|
||||||
|
stdlib_alloc___init__()
|
||||||
|
|
||||||
|
stdlib_types___alloc_bytes__ = app.instance.exports['stdlib.types.__alloc_bytes__']
|
||||||
|
|
||||||
|
let offset0 = stdlib_types___alloc_bytes__(0);
|
||||||
|
let offset1 = stdlib_types___alloc_bytes__(1);
|
||||||
|
let offset2 = stdlib_types___alloc_bytes__(2);
|
||||||
|
let offset4 = stdlib_types___alloc_bytes__(4);
|
||||||
|
|
||||||
|
var i8arr0 = new Uint8Array(app.instance.exports.memory.buffer, offset0 + 4, 0);
|
||||||
|
var i8arr1 = new Uint8Array(app.instance.exports.memory.buffer, offset1 + 4, 1);
|
||||||
|
i8arr1[0] = 0x20;
|
||||||
|
var i8arr2 = new Uint8Array(app.instance.exports.memory.buffer, offset2 + 4, 2);
|
||||||
|
i8arr2[0] = 0x20;
|
||||||
|
i8arr2[1] = 0x10;
|
||||||
|
var i8arr4 = new Uint8Array(app.instance.exports.memory.buffer, offset4 + 4, 4);
|
||||||
|
i8arr4[0] = 0x20;
|
||||||
|
i8arr4[1] = 0x10;
|
||||||
|
i8arr4[2] = 0x08;
|
||||||
|
i8arr4[3] = 0x04;
|
||||||
|
i8arr4[4] = 0x02;
|
||||||
|
|
||||||
|
log('foldl(or, 1, [' + i8arr0 + ']) = ' + app.instance.exports.foldl_u8_or_1(offset0));
|
||||||
|
log('foldl(or, 1, [' + i8arr1 + ']) = ' + app.instance.exports.foldl_u8_or_1(offset1));
|
||||||
|
log('foldl(or, 1, [' + i8arr2 + ']) = ' + app.instance.exports.foldl_u8_or_1(offset2));
|
||||||
|
log('foldl(or, 1, [' + i8arr4 + ']) = ' + app.instance.exports.foldl_u8_or_1(offset4));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
6
examples/fold.py
Normal file
6
examples/fold.py
Normal file
@ -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)
|
||||||
@ -11,6 +11,7 @@
|
|||||||
<h2>Technical</h2>
|
<h2>Technical</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="buffer.html">Buffer</a></li>
|
<li><a href="buffer.html">Buffer</a></li>
|
||||||
|
<li><a href="fold.html">Folding</a></li>
|
||||||
<li><a href="imported.html">Imported</a></li>
|
<li><a href="imported.html">Imported</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -125,6 +125,10 @@ def expression(inp: ourlang.Expression) -> str:
|
|||||||
if isinstance(inp, ourlang.AccessTupleMember):
|
if isinstance(inp, ourlang.AccessTupleMember):
|
||||||
return f'{expression(inp.varref)}[{inp.member.idx}]'
|
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)
|
raise NotImplementedError(expression, inp)
|
||||||
|
|
||||||
def statement(inp: ourlang.Statement) -> Statements:
|
def statement(inp: ourlang.Statement) -> Statements:
|
||||||
|
|||||||
@ -77,9 +77,10 @@ OPERATOR_MAP = {
|
|||||||
|
|
||||||
U8_OPERATOR_MAP = {
|
U8_OPERATOR_MAP = {
|
||||||
# Under the hood, this is an i32
|
# 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
|
# bytes stay zero after this operation
|
||||||
'^': 'xor',
|
'^': 'xor',
|
||||||
|
'|': 'or',
|
||||||
}
|
}
|
||||||
|
|
||||||
U32_OPERATOR_MAP = {
|
U32_OPERATOR_MAP = {
|
||||||
@ -254,25 +255,81 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(inp, ourlang.Fold):
|
if isinstance(inp, ourlang.Fold):
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(inp.base.type.__class__)
|
expression_fold(wgn, inp)
|
||||||
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')
|
|
||||||
return
|
return
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp)
|
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:
|
def statement_return(wgn: WasmGenerator, inp: ourlang.StatementReturn) -> None:
|
||||||
"""
|
"""
|
||||||
Compile: Return statement
|
Compile: Return statement
|
||||||
|
|||||||
@ -244,6 +244,8 @@ class OurVisitor:
|
|||||||
operator = '-'
|
operator = '-'
|
||||||
elif isinstance(node.op, ast.Mult):
|
elif isinstance(node.op, ast.Mult):
|
||||||
operator = '*'
|
operator = '*'
|
||||||
|
elif isinstance(node.op, ast.BitOr):
|
||||||
|
operator = '|'
|
||||||
elif isinstance(node.op, ast.BitXor):
|
elif isinstance(node.op, ast.BitXor):
|
||||||
operator = '^'
|
operator = '^'
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -35,6 +35,7 @@ class Generator_i32i64:
|
|||||||
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
|
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
|
||||||
self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne')
|
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.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
|
# 2.4.4. Memory Instructions
|
||||||
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
|
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.nop = functools.partial(self.add_statement, 'nop')
|
||||||
self.unreachable = functools.partial(self.add_statement, 'unreachable')
|
self.unreachable = functools.partial(self.add_statement, 'unreachable')
|
||||||
# block
|
# block
|
||||||
# loop
|
self.loop = functools.partial(GeneratorBlock, self, 'loop')
|
||||||
self.if_ = functools.partial(GeneratorBlock, self, 'if')
|
self.if_ = functools.partial(GeneratorBlock, self, 'if')
|
||||||
# br
|
# br
|
||||||
# br_if
|
# br_if - see below
|
||||||
# br_table
|
# br_table
|
||||||
self.return_ = functools.partial(self.add_statement, 'return')
|
self.return_ = functools.partial(self.add_statement, 'return')
|
||||||
# call
|
# call - see below
|
||||||
# call_indirect
|
# call_indirect
|
||||||
|
|
||||||
|
def br_if(self, idx: int) -> None:
|
||||||
|
self.add_statement('br_if', f'{idx}')
|
||||||
|
|
||||||
def call(self, function: wasm.Function) -> None:
|
def call(self, function: wasm.Function) -> None:
|
||||||
self.add_statement('call', f'${function.name}')
|
self.add_statement('call', f'${function.name}')
|
||||||
|
|
||||||
|
|||||||
65
tests/integration/test_builtins.py
Normal file
65
tests/integration/test_builtins.py
Normal file
@ -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
|
||||||
Loading…
x
Reference in New Issue
Block a user