This commit is contained in:
Johan B.W. de Vries 2022-07-08 21:06:13 +02:00
parent eb74c8770d
commit 76d80f57cb
8 changed files with 159 additions and 12 deletions

44
examples/imported.html Normal file
View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>Examples - Imported</title>
</head>
<body>
<h1>Imported</h1>
<a href="index.html">List</a> - <a href="imported.py.html">Source</a> - <a href="imported.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('imported.wasm'), importObject)
.then(app => {
console.log(WebAssembly.Module.imports(app.module));
app.instance.exports.run(1, 1);
app.instance.exports.run(3, 5);
app.instance.exports.run(8, 19);
app.instance.exports.run(12, 127);
app.instance.exports.run(79, 193);
});
</script>
</body>
</html>

7
examples/imported.py Normal file
View File

@ -0,0 +1,7 @@
@imported
def log(no: i32) -> None:
pass
@exported
def run(a: i32, b: i32) -> None:
return log(a * b)

View File

@ -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="imported.html">Imported</a></li>
</ul> </ul>
</body> </body>
</html> </html>

View File

@ -9,6 +9,9 @@ from . import wasm
Statements = Generator[wasm.Statement, None, None] Statements = Generator[wasm.Statement, None, None]
def type_(inp: ourlang.OurType) -> wasm.OurType: def type_(inp: ourlang.OurType) -> wasm.OurType:
if isinstance(inp, ourlang.OurTypeNone):
return wasm.OurTypeNone()
if isinstance(inp, ourlang.OurTypeUInt8): if isinstance(inp, ourlang.OurTypeUInt8):
# WebAssembly has only support for 32 and 64 bits # WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte # So we need to store more memory per byte
@ -205,12 +208,31 @@ def statement(inp: ourlang.Statement) -> Statements:
yield from statement_if(inp) yield from statement_if(inp)
return return
if isinstance(inp, ourlang.StatementPass):
return
raise NotImplementedError(statement, inp) raise NotImplementedError(statement, inp)
def function_argument(inp: Tuple[str, ourlang.OurType]) -> wasm.Param: def function_argument(inp: Tuple[str, ourlang.OurType]) -> wasm.Param:
return (inp[0], type_(inp[1]), ) return (inp[0], type_(inp[1]), )
def import_(inp: ourlang.Function) -> wasm.Import:
assert inp.imported
return wasm.Import(
'imports',
inp.name,
inp.name,
[
function_argument(x)
for x in inp.posonlyargs
],
type_(inp.returns)
)
def function(inp: ourlang.Function) -> wasm.Function: def function(inp: ourlang.Function) -> wasm.Function:
assert not inp.imported
if isinstance(inp, ourlang.TupleConstructor): if isinstance(inp, ourlang.TupleConstructor):
statements = [ statements = [
*_generate_tuple_constructor(inp) *_generate_tuple_constructor(inp)
@ -248,12 +270,19 @@ def function(inp: ourlang.Function) -> wasm.Function:
def module(inp: ourlang.Module) -> wasm.Module: def module(inp: ourlang.Module) -> wasm.Module:
result = wasm.Module() result = wasm.Module()
result.imports = [
import_(x)
for x in inp.functions.values()
if x.imported
]
result.functions = [ result.functions = [
_generate____new_reference___(inp), _generate____new_reference___(inp),
_generate____access_bytes_index___(inp), _generate____access_bytes_index___(inp),
] + [ ] + [
function(x) function(x)
for x in inp.functions.values() for x in inp.functions.values()
if not x.imported
] ]
return result return result

View File

@ -458,11 +458,12 @@ class Function:
""" """
A function processes input and produces output A function processes input and produces output
""" """
__slots__ = ('name', 'lineno', 'exported', 'buildin', 'statements', 'returns', 'posonlyargs', ) __slots__ = ('name', 'lineno', 'exported', 'imported', 'buildin', 'statements', 'returns', 'posonlyargs', )
name: str name: str
lineno: int lineno: int
exported: bool exported: bool
imported: bool
buildin: bool buildin: bool
statements: List[Statement] statements: List[Statement]
returns: OurType returns: OurType
@ -472,6 +473,7 @@ class Function:
self.name = name self.name = name
self.lineno = lineno self.lineno = lineno
self.exported = False self.exported = False
self.imported = False
self.buildin = False self.buildin = False
self.statements = [] self.statements = []
self.returns = OurTypeNone() self.returns = OurTypeNone()
@ -483,9 +485,14 @@ class Function:
This'll look like Python code. This'll look like Python code.
""" """
statements = self.statements
result = '' result = ''
if self.exported: if self.exported:
result += '@exported\n' result += '@exported\n'
if self.imported:
result += '@imported\n'
statements = [StatementPass()]
args = ', '.join( args = ', '.join(
f'{x}: {y.render()}' f'{x}: {y.render()}'
@ -493,7 +500,7 @@ class Function:
) )
result += f'def {self.name}({args}) -> {self.returns.render()}:\n' result += f'def {self.name}({args}) -> {self.returns.render()}:\n'
for stmt in self.statements: for stmt in statements:
for line in stmt.render(): for line in stmt.render():
result += f' {line}\n' if line else '\n' result += f' {line}\n' if line else '\n'
return result return result
@ -608,6 +615,7 @@ class Module:
def __init__(self) -> None: def __init__(self) -> None:
self.types = { self.types = {
'None': OurTypeNone(),
'u8': OurTypeUInt8(), 'u8': OurTypeUInt8(),
'i32': OurTypeInt32(), 'i32': OurTypeInt32(),
'i64': OurTypeInt64(), 'i64': OurTypeInt64(),
@ -730,8 +738,12 @@ class OurVisitor:
_raise_static_error(decorator, 'Function decorators must be string') _raise_static_error(decorator, 'Function decorators must be string')
if not isinstance(decorator.ctx, ast.Load): if not isinstance(decorator.ctx, ast.Load):
_raise_static_error(decorator, 'Must be load context') _raise_static_error(decorator, 'Must be load context')
_not_implemented(decorator.id != 'exports', 'Custom decorators') _not_implemented(decorator.id in ('exported', 'imported'), 'Custom decorators')
function.exported = True
if decorator.id == 'exported':
function.exported = True
else:
function.imported = True
if node.returns: if node.returns:
function.returns = self.visit_type(module, node.returns) function.returns = self.visit_type(module, node.returns)
@ -816,6 +828,9 @@ class OurVisitor:
return result return result
if isinstance(node, ast.Pass):
return StatementPass()
raise NotImplementedError(f'{node} as stmt in FunctionDef') raise NotImplementedError(f'{node} as stmt in FunctionDef')
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, exp_type: OurType, node: ast.expr) -> Expression: def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, exp_type: OurType, node: ast.expr) -> Expression:
@ -1108,6 +1123,12 @@ class OurVisitor:
raise NotImplementedError(f'{node} as const for type {exp_type.render()}') raise NotImplementedError(f'{node} as const for type {exp_type.render()}')
def visit_type(self, module: Module, node: ast.expr) -> OurType: def visit_type(self, module: Module, node: ast.expr) -> OurType:
if isinstance(node, ast.Constant):
if node.value is None:
return module.types['None']
_raise_static_error(node, f'Unrecognized type {node.value}')
if isinstance(node, ast.Name): if isinstance(node, ast.Name):
if not isinstance(node.ctx, ast.Load): if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')

View File

@ -156,22 +156,28 @@ class Import:
name: str, name: str,
intname: str, intname: str,
params: Iterable[Param], params: Iterable[Param],
result: OurType,
) -> None: ) -> None:
self.module = module self.module = module
self.name = name self.name = name
self.intname = intname self.intname = intname
self.params = [*params] self.params = [*params]
self.result: OurType = OurTypeNone() self.result = result
def generate(self) -> str: def generate(self) -> str:
""" """
Generates the text version Generates the text version
""" """
return '(import "{}" "{}" (func ${}{}))'.format( return '(import "{}" "{}" (func ${}{}{}))'.format(
self.module, self.module,
self.name, self.name,
self.intname, self.intname,
''.join(' (param {})'.format(x[1]) for x in self.params) ''.join(
f' (param {typ.to_wasm()})'
for _, typ in self.params
),
'' if isinstance(self.result, OurTypeNone)
else f' (result {self.result.to_wasm()})'
) )
class Statement: class Statement:
@ -263,7 +269,7 @@ class Module:
Generates the text version Generates the text version
""" """
return '(module\n {}\n {}\n {})\n'.format( return '(module\n {}\n {}\n {})\n'.format(
self.memory.generate(),
'\n '.join(x.generate() for x in self.imports), '\n '.join(x.generate() for x in self.imports),
self.memory.generate(),
'\n '.join(x.generate() for x in self.functions), '\n '.join(x.generate() for x in self.functions),
) )

View File

@ -63,7 +63,7 @@ class Suite:
self.code_py = code_py self.code_py = code_py
self.test_name = test_name self.test_name = test_name
def run_code(self, *args): def run_code(self, *args, runtime='pywasm3', imports=None):
""" """
Compiles the given python code into wasm and Compiles the given python code into wasm and
then runs it then runs it
@ -89,7 +89,16 @@ class Suite:
code_wasm = wat2wasm(code_wat) code_wasm = wat2wasm(code_wat)
# Run assembly code # Run assembly code
return _run_pywasm3(code_wasm, args) if 'pywasm' == runtime:
return _run_pywasm(code_wasm, args)
if 'pywasm3' == runtime:
return _run_pywasm3(code_wasm, args)
if 'wasmtime' == runtime:
return _run_wasmtime(code_wasm, args)
if 'wasmer' == runtime:
return _run_wasmer(code_wasm, args, imports)
raise Exception(f'Invalid runtime: {runtime}')
def _run_pywasm(code_wasm, args): def _run_pywasm(code_wasm, args):
# https://pypi.org/project/pywasm/ # https://pypi.org/project/pywasm/
@ -168,17 +177,23 @@ def _run_wasmtime(code_wasm, args):
return result return result
def _run_wasmer(code_wasm, args): def _run_wasmer(code_wasm, args, imports):
# https://pypi.org/project/wasmer/ # https://pypi.org/project/wasmer/
result = SuiteResult() result = SuiteResult()
store = wasmer.Store(wasmer.engine.JIT(wasmer_compiler_cranelift.Compiler)) store = wasmer.Store(wasmer.engine.JIT(wasmer_compiler_cranelift.Compiler))
import_object = wasmer.ImportObject()
import_object.register('imports', {
k: wasmer.Function(store, v)
for k, v in (imports or {}).items()
})
# Let's compile the module to be able to execute it! # Let's compile the module to be able to execute it!
module = wasmer.Module(store, code_wasm) module = wasmer.Module(store, code_wasm)
# Now the module is compiled, we can instantiate it. # Now the module is compiled, we can instantiate it.
instance = wasmer.Instance(module) instance = wasmer.Instance(module, import_object)
sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n') sys.stderr.write(f'{DASHES} Memory (pre run) {DASHES}\n')
sys.stderr.write('<Not available on wasmer>\n') sys.stderr.write('<Not available on wasmer>\n')

View File

@ -416,3 +416,27 @@ def testEntry() -> i32x4:
result = Suite(code_py, 'test_rgb2hsl').run_code() result = Suite(code_py, 'test_rgb2hsl').run_code()
assert (1, 2, 3, 0) == result.returned_value assert (1, 2, 3, 0) == result.returned_value
@pytest.mark.integration_test
def test_imported():
code_py = """
@imported
def helper() -> i32:
pass
@exported
def testEntry() -> i32:
return helper()
"""
def helper() -> int:
return 4238
result = Suite(code_py, 'test_imported').run_code(
runtime='wasmer',
imports={
'helper': helper,
}
)
assert 4238 == result.returned_value