MVP #1
1
Makefile
1
Makefile
@ -29,5 +29,6 @@ typecheck: venv/.done
|
||||
|
||||
venv/.done: requirements.txt
|
||||
python3.8 -m venv venv
|
||||
venv/bin/python3 -m pip install wheel
|
||||
venv/bin/python3 -m pip install -r $^
|
||||
touch $@
|
||||
|
||||
@ -30,6 +30,8 @@ class Visitor:
|
||||
'i64': wasm.OurTypeInt64(),
|
||||
'f32': wasm.OurTypeFloat32(),
|
||||
'f64': wasm.OurTypeFloat64(),
|
||||
|
||||
'i32x4': wasm.OurTypeVectorInt32x4(),
|
||||
}
|
||||
|
||||
def _get_type(self, name: Union[None, str, ast.expr]) -> wasm.OurType:
|
||||
@ -186,7 +188,7 @@ class Visitor:
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
result = None if node.returns is None else self._get_type(node.returns)
|
||||
result = self._get_type(node.returns)
|
||||
|
||||
assert not node.args.vararg
|
||||
assert not node.args.kwonlyargs
|
||||
@ -377,7 +379,7 @@ class Visitor:
|
||||
return self.visit_Name(wlocals, exp_type, node)
|
||||
|
||||
if isinstance(node, ast.Tuple):
|
||||
return self.visit_Tuple(wlocals, exp_type, node)
|
||||
return self.visit_Tuple(module, wlocals, exp_type, node)
|
||||
|
||||
raise NotImplementedError(node)
|
||||
|
||||
@ -549,7 +551,8 @@ class Visitor:
|
||||
called_params = func.params
|
||||
called_result = func.result
|
||||
|
||||
assert exp_type == called_result, 'Function does not match expected type'
|
||||
assert exp_type == called_result, \
|
||||
f'Function does not match expected type; {called_name} returns {called_result.to_python()} but {exp_type.to_python()} is expected'
|
||||
|
||||
assert len(called_params) == len(node.args), \
|
||||
'{}:{} Function {} requires {} arguments, but {} are supplied'.format(
|
||||
@ -584,7 +587,8 @@ class Visitor:
|
||||
return
|
||||
|
||||
if isinstance(exp_type, wasm.OurTypeFloat32):
|
||||
assert isinstance(node.value, float)
|
||||
assert isinstance(node.value, float), \
|
||||
f'Expression expected a float, but received {node.value}'
|
||||
# TODO: Size check?
|
||||
|
||||
yield wasm.Statement('f32.const', node.value.hex())
|
||||
@ -672,6 +676,7 @@ class Visitor:
|
||||
|
||||
def visit_Tuple(
|
||||
self,
|
||||
module: wasm.Module,
|
||||
wlocals: WLocals,
|
||||
exp_type: wasm.OurType,
|
||||
node: ast.Tuple,
|
||||
@ -679,7 +684,21 @@ class Visitor:
|
||||
"""
|
||||
Visits an Tuple node as (part of) an expression
|
||||
"""
|
||||
assert isinstance(exp_type, wasm.OurTypeTuple), 'Expression is not expecting a tuple'
|
||||
if isinstance(exp_type, wasm.OurTypeVector):
|
||||
assert isinstance(exp_type, wasm.OurTypeVectorInt32x4), 'Not implemented yet'
|
||||
assert len(node.elts) == 4, 'Not implemented yet'
|
||||
|
||||
values: List[str] = []
|
||||
for arg in node.elts:
|
||||
assert isinstance(arg, ast.Constant)
|
||||
assert isinstance(arg.value, int)
|
||||
values.append(str(arg.value))
|
||||
|
||||
yield wasm.Statement('v128.const', 'i32x4', *values)
|
||||
return
|
||||
|
||||
assert isinstance(exp_type, wasm.OurTypeTuple), \
|
||||
f'Expression is expecting {exp_type}, not a tuple'
|
||||
|
||||
# TODO: free call
|
||||
|
||||
@ -692,11 +711,10 @@ class Visitor:
|
||||
yield wasm.Statement('local.set', '$___new_reference___addr', comment='Allocate for tuple')
|
||||
|
||||
for member, arg in zip(tpl.members, node.elts):
|
||||
if not isinstance(arg, ast.Constant):
|
||||
raise NotImplementedError('TODO: Non-const tuple members')
|
||||
|
||||
yield wasm.Statement('local.get', '$___new_reference___addr')
|
||||
yield wasm.Statement(f'{member.type.to_wasm()}.const', str(arg.value))
|
||||
|
||||
yield from self.visit_expr(module, wlocals, member.type, arg)
|
||||
|
||||
yield wasm.Statement(f'{member.type.to_wasm()}.store', 'offset=' + str(member.offset),
|
||||
comment='Write tuple value to memory')
|
||||
|
||||
|
||||
@ -9,11 +9,14 @@ from typing import Any, Iterable, List, Optional, Tuple, Union
|
||||
###
|
||||
|
||||
class OurType:
|
||||
def to_python(self) -> str:
|
||||
raise NotImplementedError(self, 'to_python')
|
||||
|
||||
def to_wasm(self) -> str:
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError(self, 'to_wasm')
|
||||
|
||||
def alloc_size(self) -> int:
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError(self, 'alloc_size')
|
||||
|
||||
class OurTypeNone(OurType):
|
||||
pass
|
||||
@ -22,6 +25,9 @@ class OurTypeBool(OurType):
|
||||
pass
|
||||
|
||||
class OurTypeInt32(OurType):
|
||||
def to_python(self) -> str:
|
||||
return 'i32'
|
||||
|
||||
def to_wasm(self) -> str:
|
||||
return 'i32'
|
||||
|
||||
@ -49,6 +55,21 @@ class OurTypeFloat64(OurType):
|
||||
def alloc_size(self) -> int:
|
||||
return 8
|
||||
|
||||
class OurTypeVector(OurType):
|
||||
"""
|
||||
A vector is a 128-bit value
|
||||
"""
|
||||
def to_wasm(self) -> str:
|
||||
return 'v128'
|
||||
|
||||
def alloc_size(self) -> int:
|
||||
return 16
|
||||
|
||||
class OurTypeVectorInt32x4(OurTypeVector):
|
||||
"""
|
||||
4 Int32 values in a single vector
|
||||
"""
|
||||
|
||||
class Constant:
|
||||
"""
|
||||
TODO
|
||||
@ -95,12 +116,28 @@ class OurTypeTuple(OurType):
|
||||
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]
|
||||
|
||||
@ -123,7 +160,7 @@ class Import:
|
||||
self.name = name
|
||||
self.intname = intname
|
||||
self.params = [*params]
|
||||
self.result: str = 'None'
|
||||
self.result: OurType = OurTypeNone()
|
||||
|
||||
def generate(self) -> str:
|
||||
"""
|
||||
@ -164,7 +201,7 @@ class Function:
|
||||
exported: bool,
|
||||
params: Iterable[Param],
|
||||
locals_: Iterable[Param],
|
||||
result: Optional[OurType],
|
||||
result: OurType,
|
||||
statements: Iterable[Statement],
|
||||
) -> None:
|
||||
self.name = name
|
||||
|
||||
@ -3,3 +3,7 @@ pylint==2.7.4
|
||||
pytest==6.2.2
|
||||
pytest-integration==0.2.2
|
||||
pywasm==1.0.7
|
||||
pywasm3==0.5.0
|
||||
wasmer==1.0.0
|
||||
wasmer_compiler_cranelift==1.0.0
|
||||
wasmtime==0.36.0
|
||||
|
||||
@ -5,11 +5,19 @@ import sys
|
||||
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from pywasm import binary
|
||||
from pywasm import Runtime
|
||||
import pywasm
|
||||
|
||||
import wasm3
|
||||
|
||||
import wasmer
|
||||
import wasmer_compiler_cranelift
|
||||
|
||||
import wasmtime
|
||||
|
||||
from py2wasm.utils import process
|
||||
|
||||
DASHES = '-' * 16
|
||||
|
||||
def wat2wasm(code_wat):
|
||||
path = os.environ.get('WAT2WASM', 'wat2wasm')
|
||||
|
||||
@ -63,9 +71,7 @@ class Suite:
|
||||
"""
|
||||
code_wat = process(self.code_py, self.test_name)
|
||||
|
||||
dashes = '-' * 16
|
||||
|
||||
sys.stderr.write(f'{dashes} Assembly {dashes}\n')
|
||||
sys.stderr.write(f'{DASHES} Assembly {DASHES}\n')
|
||||
|
||||
line_list = code_wat.split('\n')
|
||||
line_no_width = len(str(len(line_list)))
|
||||
@ -76,21 +82,91 @@ class Suite:
|
||||
))
|
||||
|
||||
code_wasm = wat2wasm(code_wat)
|
||||
module = binary.Module.from_reader(io.BytesIO(code_wasm))
|
||||
|
||||
return _run_pywasm3(code_wasm, args)
|
||||
|
||||
def _run_pywasm(code_wasm, args):
|
||||
# https://pypi.org/project/pywasm/
|
||||
result = SuiteResult()
|
||||
runtime = Runtime(module, result.make_imports(), {})
|
||||
|
||||
sys.stderr.write(f'{dashes} Memory (pre run) {dashes}\n')
|
||||
module = pywasm.binary.Module.from_reader(io.BytesIO(code_wasm))
|
||||
|
||||
runtime = pywasm.Runtime(module, result.make_imports(), {})
|
||||
|
||||
sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n')
|
||||
_dump_memory(runtime.store.mems[0].data)
|
||||
|
||||
result.returned_value = runtime.exec('testEntry', args)
|
||||
|
||||
sys.stderr.write(f'{dashes} Memory (post run) {dashes}\n')
|
||||
sys.stderr.write(f'{DASHES} Memory (post run) {DASHES}\n')
|
||||
_dump_memory(runtime.store.mems[0].data)
|
||||
|
||||
return result
|
||||
|
||||
def _run_pywasm3(code_wasm, args):
|
||||
# https://pypi.org/project/pywasm3/
|
||||
result = SuiteResult()
|
||||
|
||||
env = wasm3.Environment()
|
||||
|
||||
mod = env.parse_module(code_wasm)
|
||||
|
||||
rtime = env.new_runtime(1024 * 1024)
|
||||
rtime.load(mod)
|
||||
|
||||
sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n')
|
||||
_dump_memory(rtime.get_memory(0).tobytes())
|
||||
|
||||
result.returned_value = rtime.find_function('testEntry')(*args)
|
||||
|
||||
sys.stderr.write(f'{DASHES} Memory (post run) {DASHES}\n')
|
||||
_dump_memory(rtime.get_memory(0).tobytes())
|
||||
|
||||
return result
|
||||
|
||||
def _run_wasmtime(code_wasm, args):
|
||||
# https://pypi.org/project/wasmtime/
|
||||
result = SuiteResult()
|
||||
|
||||
store = wasmtime.Store()
|
||||
|
||||
module = wasmtime.Module(store.engine, code_wasm)
|
||||
|
||||
instance = wasmtime.Instance(store, module, [])
|
||||
|
||||
sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n')
|
||||
sys.stderr.write('<Not available on wasmtime>\n')
|
||||
|
||||
result.returned_value = instance.exports(store)['testEntry'](store, *args)
|
||||
|
||||
sys.stderr.write(f'{DASHES} Memory (post run) {DASHES}\n')
|
||||
sys.stderr.write('<Not available on wasmtime>\n')
|
||||
|
||||
return result
|
||||
|
||||
def _run_wasmer(code_wasm, args):
|
||||
# https://pypi.org/project/wasmer/
|
||||
result = SuiteResult()
|
||||
|
||||
store = wasmer.Store(wasmer.engine.JIT(wasmer_compiler_cranelift.Compiler))
|
||||
|
||||
# Let's compile the module to be able to execute it!
|
||||
module = wasmer.Module(store, code_wasm)
|
||||
|
||||
# Now the module is compiled, we can instantiate it.
|
||||
instance = wasmer.Instance(module)
|
||||
|
||||
sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n')
|
||||
sys.stderr.write('<Not available on wasmer>\n')
|
||||
|
||||
# Call the exported `sum` function.
|
||||
result.returned_value = instance.exports.testEntry(*args)
|
||||
|
||||
sys.stderr.write(f'{DASHES} Memory (post run) {DASHES}\n')
|
||||
sys.stderr.write('<Not available on wasmer>\n')
|
||||
|
||||
return result
|
||||
|
||||
def _dump_memory(mem):
|
||||
line_width = 16
|
||||
|
||||
|
||||
@ -341,7 +341,7 @@ def test_tuple_float():
|
||||
|
||||
@exported
|
||||
def testEntry() -> f32:
|
||||
return helper((1, 2, 3, ))
|
||||
return helper((1.0, 2.0, 3.0, ))
|
||||
|
||||
def helper(v: Tuple[f32, f32, f32]) -> f32:
|
||||
# sqrt is guaranteed by wasm, so we can use it
|
||||
@ -353,3 +353,16 @@ def helper(v: Tuple[f32, f32, f32]) -> f32:
|
||||
|
||||
assert 3.74 < result.returned_value < 3.75
|
||||
assert [] == result.log_int32_list
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.skip('SIMD support is but a dream')
|
||||
def test_tuple_i32x4():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry() -> i32x4:
|
||||
return (51, 153, 204, 0, )
|
||||
"""
|
||||
|
||||
result = Suite(code_py, 'test_rgb2hsl').run_code()
|
||||
|
||||
assert (1, 2, 3, 0) == result.returned_value
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user