Testing with various wasm implementations
Also: - Started on SIMD before finding out no implementation supports that yet - Type fix result Import / Function - Various error reporting improvements - Non-const tuple members
This commit is contained in:
parent
6b717dba0b
commit
865eccd719
1
Makefile
1
Makefile
@ -29,5 +29,6 @@ typecheck: venv/.done
|
|||||||
|
|
||||||
venv/.done: requirements.txt
|
venv/.done: requirements.txt
|
||||||
python3.8 -m venv venv
|
python3.8 -m venv venv
|
||||||
|
venv/bin/python3 -m pip install wheel
|
||||||
venv/bin/python3 -m pip install -r $^
|
venv/bin/python3 -m pip install -r $^
|
||||||
touch $@
|
touch $@
|
||||||
|
|||||||
@ -30,6 +30,8 @@ class Visitor:
|
|||||||
'i64': wasm.OurTypeInt64(),
|
'i64': wasm.OurTypeInt64(),
|
||||||
'f32': wasm.OurTypeFloat32(),
|
'f32': wasm.OurTypeFloat32(),
|
||||||
'f64': wasm.OurTypeFloat64(),
|
'f64': wasm.OurTypeFloat64(),
|
||||||
|
|
||||||
|
'i32x4': wasm.OurTypeVectorInt32x4(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_type(self, name: Union[None, str, ast.expr]) -> wasm.OurType:
|
def _get_type(self, name: Union[None, str, ast.expr]) -> wasm.OurType:
|
||||||
@ -186,7 +188,7 @@ class Visitor:
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
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.vararg
|
||||||
assert not node.args.kwonlyargs
|
assert not node.args.kwonlyargs
|
||||||
@ -377,7 +379,7 @@ class Visitor:
|
|||||||
return self.visit_Name(wlocals, exp_type, node)
|
return self.visit_Name(wlocals, exp_type, node)
|
||||||
|
|
||||||
if isinstance(node, ast.Tuple):
|
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)
|
raise NotImplementedError(node)
|
||||||
|
|
||||||
@ -549,7 +551,8 @@ class Visitor:
|
|||||||
called_params = func.params
|
called_params = func.params
|
||||||
called_result = func.result
|
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), \
|
assert len(called_params) == len(node.args), \
|
||||||
'{}:{} Function {} requires {} arguments, but {} are supplied'.format(
|
'{}:{} Function {} requires {} arguments, but {} are supplied'.format(
|
||||||
@ -584,7 +587,8 @@ class Visitor:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(exp_type, wasm.OurTypeFloat32):
|
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?
|
# TODO: Size check?
|
||||||
|
|
||||||
yield wasm.Statement('f32.const', node.value.hex())
|
yield wasm.Statement('f32.const', node.value.hex())
|
||||||
@ -672,6 +676,7 @@ class Visitor:
|
|||||||
|
|
||||||
def visit_Tuple(
|
def visit_Tuple(
|
||||||
self,
|
self,
|
||||||
|
module: wasm.Module,
|
||||||
wlocals: WLocals,
|
wlocals: WLocals,
|
||||||
exp_type: wasm.OurType,
|
exp_type: wasm.OurType,
|
||||||
node: ast.Tuple,
|
node: ast.Tuple,
|
||||||
@ -679,7 +684,21 @@ class Visitor:
|
|||||||
"""
|
"""
|
||||||
Visits an Tuple node as (part of) an expression
|
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
|
# TODO: free call
|
||||||
|
|
||||||
@ -692,11 +711,10 @@ class Visitor:
|
|||||||
yield wasm.Statement('local.set', '$___new_reference___addr', comment='Allocate for tuple')
|
yield wasm.Statement('local.set', '$___new_reference___addr', comment='Allocate for tuple')
|
||||||
|
|
||||||
for member, arg in zip(tpl.members, node.elts):
|
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('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),
|
yield wasm.Statement(f'{member.type.to_wasm()}.store', 'offset=' + str(member.offset),
|
||||||
comment='Write tuple value to memory')
|
comment='Write tuple value to memory')
|
||||||
|
|
||||||
|
|||||||
@ -9,11 +9,14 @@ from typing import Any, Iterable, List, Optional, Tuple, Union
|
|||||||
###
|
###
|
||||||
|
|
||||||
class OurType:
|
class OurType:
|
||||||
|
def to_python(self) -> str:
|
||||||
|
raise NotImplementedError(self, 'to_python')
|
||||||
|
|
||||||
def to_wasm(self) -> str:
|
def to_wasm(self) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError(self, 'to_wasm')
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
def alloc_size(self) -> int:
|
||||||
raise NotImplementedError
|
raise NotImplementedError(self, 'alloc_size')
|
||||||
|
|
||||||
class OurTypeNone(OurType):
|
class OurTypeNone(OurType):
|
||||||
pass
|
pass
|
||||||
@ -22,6 +25,9 @@ class OurTypeBool(OurType):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class OurTypeInt32(OurType):
|
class OurTypeInt32(OurType):
|
||||||
|
def to_python(self) -> str:
|
||||||
|
return 'i32'
|
||||||
|
|
||||||
def to_wasm(self) -> str:
|
def to_wasm(self) -> str:
|
||||||
return 'i32'
|
return 'i32'
|
||||||
|
|
||||||
@ -49,6 +55,21 @@ class OurTypeFloat64(OurType):
|
|||||||
def alloc_size(self) -> int:
|
def alloc_size(self) -> int:
|
||||||
return 8
|
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:
|
class Constant:
|
||||||
"""
|
"""
|
||||||
TODO
|
TODO
|
||||||
@ -95,12 +116,28 @@ class OurTypeTuple(OurType):
|
|||||||
def __init__(self, members: List[TupleMember]) -> None:
|
def __init__(self, members: List[TupleMember]) -> None:
|
||||||
self.members = members
|
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:
|
def to_wasm(self) -> str:
|
||||||
return 'i32' # WASM uses 32 bit pointers
|
return 'i32' # WASM uses 32 bit pointers
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
def alloc_size(self) -> int:
|
||||||
return sum(x.type.alloc_size() for x in self.members)
|
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]
|
Param = Tuple[str, OurType]
|
||||||
|
|
||||||
@ -123,7 +160,7 @@ class Import:
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.intname = intname
|
self.intname = intname
|
||||||
self.params = [*params]
|
self.params = [*params]
|
||||||
self.result: str = 'None'
|
self.result: OurType = OurTypeNone()
|
||||||
|
|
||||||
def generate(self) -> str:
|
def generate(self) -> str:
|
||||||
"""
|
"""
|
||||||
@ -164,7 +201,7 @@ class Function:
|
|||||||
exported: bool,
|
exported: bool,
|
||||||
params: Iterable[Param],
|
params: Iterable[Param],
|
||||||
locals_: Iterable[Param],
|
locals_: Iterable[Param],
|
||||||
result: Optional[OurType],
|
result: OurType,
|
||||||
statements: Iterable[Statement],
|
statements: Iterable[Statement],
|
||||||
) -> None:
|
) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|||||||
@ -3,3 +3,7 @@ pylint==2.7.4
|
|||||||
pytest==6.2.2
|
pytest==6.2.2
|
||||||
pytest-integration==0.2.2
|
pytest-integration==0.2.2
|
||||||
pywasm==1.0.7
|
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 tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
from pywasm import binary
|
import pywasm
|
||||||
from pywasm import Runtime
|
|
||||||
|
import wasm3
|
||||||
|
|
||||||
|
import wasmer
|
||||||
|
import wasmer_compiler_cranelift
|
||||||
|
|
||||||
|
import wasmtime
|
||||||
|
|
||||||
from py2wasm.utils import process
|
from py2wasm.utils import process
|
||||||
|
|
||||||
|
DASHES = '-' * 16
|
||||||
|
|
||||||
def wat2wasm(code_wat):
|
def wat2wasm(code_wat):
|
||||||
path = os.environ.get('WAT2WASM', 'wat2wasm')
|
path = os.environ.get('WAT2WASM', 'wat2wasm')
|
||||||
|
|
||||||
@ -63,9 +71,7 @@ class Suite:
|
|||||||
"""
|
"""
|
||||||
code_wat = process(self.code_py, self.test_name)
|
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_list = code_wat.split('\n')
|
||||||
line_no_width = len(str(len(line_list)))
|
line_no_width = len(str(len(line_list)))
|
||||||
@ -76,20 +82,90 @@ class Suite:
|
|||||||
))
|
))
|
||||||
|
|
||||||
code_wasm = wat2wasm(code_wat)
|
code_wasm = wat2wasm(code_wat)
|
||||||
module = binary.Module.from_reader(io.BytesIO(code_wasm))
|
|
||||||
|
|
||||||
result = SuiteResult()
|
return _run_pywasm3(code_wasm, args)
|
||||||
runtime = Runtime(module, result.make_imports(), {})
|
|
||||||
|
|
||||||
sys.stderr.write(f'{dashes} Memory (pre run) {dashes}\n')
|
def _run_pywasm(code_wasm, args):
|
||||||
_dump_memory(runtime.store.mems[0].data)
|
# https://pypi.org/project/pywasm/
|
||||||
|
result = SuiteResult()
|
||||||
|
|
||||||
result.returned_value = runtime.exec('testEntry', args)
|
module = pywasm.binary.Module.from_reader(io.BytesIO(code_wasm))
|
||||||
|
|
||||||
sys.stderr.write(f'{dashes} Memory (post run) {dashes}\n')
|
runtime = pywasm.Runtime(module, result.make_imports(), {})
|
||||||
_dump_memory(runtime.store.mems[0].data)
|
|
||||||
|
|
||||||
return result
|
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')
|
||||||
|
_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):
|
def _dump_memory(mem):
|
||||||
line_width = 16
|
line_width = 16
|
||||||
|
|||||||
@ -341,7 +341,7 @@ def test_tuple_float():
|
|||||||
|
|
||||||
@exported
|
@exported
|
||||||
def testEntry() -> f32:
|
def testEntry() -> f32:
|
||||||
return helper((1, 2, 3, ))
|
return helper((1.0, 2.0, 3.0, ))
|
||||||
|
|
||||||
def helper(v: Tuple[f32, f32, f32]) -> f32:
|
def helper(v: Tuple[f32, f32, f32]) -> f32:
|
||||||
# sqrt is guaranteed by wasm, so we can use it
|
# 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 3.74 < result.returned_value < 3.75
|
||||||
assert [] == result.log_int32_list
|
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