Imports
This commit is contained in:
parent
eb74c8770d
commit
76d80f57cb
44
examples/imported.html
Normal file
44
examples/imported.html
Normal 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
7
examples/imported.py
Normal file
@ -0,0 +1,7 @@
|
||||
@imported
|
||||
def log(no: i32) -> None:
|
||||
pass
|
||||
|
||||
@exported
|
||||
def run(a: i32, b: i32) -> None:
|
||||
return log(a * b)
|
||||
@ -11,6 +11,7 @@
|
||||
<h2>Technical</h2>
|
||||
<ul>
|
||||
<li><a href="buffer.html">Buffer</a></li>
|
||||
<li><a href="imported.html">Imported</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -9,6 +9,9 @@ from . import wasm
|
||||
Statements = Generator[wasm.Statement, None, None]
|
||||
|
||||
def type_(inp: ourlang.OurType) -> wasm.OurType:
|
||||
if isinstance(inp, ourlang.OurTypeNone):
|
||||
return wasm.OurTypeNone()
|
||||
|
||||
if isinstance(inp, ourlang.OurTypeUInt8):
|
||||
# WebAssembly has only support for 32 and 64 bits
|
||||
# So we need to store more memory per byte
|
||||
@ -205,12 +208,31 @@ def statement(inp: ourlang.Statement) -> Statements:
|
||||
yield from statement_if(inp)
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.StatementPass):
|
||||
return
|
||||
|
||||
raise NotImplementedError(statement, inp)
|
||||
|
||||
def function_argument(inp: Tuple[str, ourlang.OurType]) -> wasm.Param:
|
||||
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:
|
||||
assert not inp.imported
|
||||
|
||||
if isinstance(inp, ourlang.TupleConstructor):
|
||||
statements = [
|
||||
*_generate_tuple_constructor(inp)
|
||||
@ -248,12 +270,19 @@ def function(inp: ourlang.Function) -> wasm.Function:
|
||||
def module(inp: ourlang.Module) -> wasm.Module:
|
||||
result = wasm.Module()
|
||||
|
||||
result.imports = [
|
||||
import_(x)
|
||||
for x in inp.functions.values()
|
||||
if x.imported
|
||||
]
|
||||
|
||||
result.functions = [
|
||||
_generate____new_reference___(inp),
|
||||
_generate____access_bytes_index___(inp),
|
||||
] + [
|
||||
function(x)
|
||||
for x in inp.functions.values()
|
||||
if not x.imported
|
||||
]
|
||||
|
||||
return result
|
||||
|
||||
@ -458,11 +458,12 @@ class Function:
|
||||
"""
|
||||
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
|
||||
lineno: int
|
||||
exported: bool
|
||||
imported: bool
|
||||
buildin: bool
|
||||
statements: List[Statement]
|
||||
returns: OurType
|
||||
@ -472,6 +473,7 @@ class Function:
|
||||
self.name = name
|
||||
self.lineno = lineno
|
||||
self.exported = False
|
||||
self.imported = False
|
||||
self.buildin = False
|
||||
self.statements = []
|
||||
self.returns = OurTypeNone()
|
||||
@ -483,9 +485,14 @@ class Function:
|
||||
|
||||
This'll look like Python code.
|
||||
"""
|
||||
statements = self.statements
|
||||
|
||||
result = ''
|
||||
if self.exported:
|
||||
result += '@exported\n'
|
||||
if self.imported:
|
||||
result += '@imported\n'
|
||||
statements = [StatementPass()]
|
||||
|
||||
args = ', '.join(
|
||||
f'{x}: {y.render()}'
|
||||
@ -493,7 +500,7 @@ class Function:
|
||||
)
|
||||
|
||||
result += f'def {self.name}({args}) -> {self.returns.render()}:\n'
|
||||
for stmt in self.statements:
|
||||
for stmt in statements:
|
||||
for line in stmt.render():
|
||||
result += f' {line}\n' if line else '\n'
|
||||
return result
|
||||
@ -608,6 +615,7 @@ class Module:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.types = {
|
||||
'None': OurTypeNone(),
|
||||
'u8': OurTypeUInt8(),
|
||||
'i32': OurTypeInt32(),
|
||||
'i64': OurTypeInt64(),
|
||||
@ -730,8 +738,12 @@ class OurVisitor:
|
||||
_raise_static_error(decorator, 'Function decorators must be string')
|
||||
if not isinstance(decorator.ctx, ast.Load):
|
||||
_raise_static_error(decorator, 'Must be load context')
|
||||
_not_implemented(decorator.id != 'exports', 'Custom decorators')
|
||||
function.exported = True
|
||||
_not_implemented(decorator.id in ('exported', 'imported'), 'Custom decorators')
|
||||
|
||||
if decorator.id == 'exported':
|
||||
function.exported = True
|
||||
else:
|
||||
function.imported = True
|
||||
|
||||
if node.returns:
|
||||
function.returns = self.visit_type(module, node.returns)
|
||||
@ -816,6 +828,9 @@ class OurVisitor:
|
||||
|
||||
return result
|
||||
|
||||
if isinstance(node, ast.Pass):
|
||||
return StatementPass()
|
||||
|
||||
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:
|
||||
@ -1108,6 +1123,12 @@ class OurVisitor:
|
||||
raise NotImplementedError(f'{node} as const for type {exp_type.render()}')
|
||||
|
||||
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 not isinstance(node.ctx, ast.Load):
|
||||
_raise_static_error(node, 'Must be load context')
|
||||
|
||||
@ -156,22 +156,28 @@ class Import:
|
||||
name: str,
|
||||
intname: str,
|
||||
params: Iterable[Param],
|
||||
result: OurType,
|
||||
) -> None:
|
||||
self.module = module
|
||||
self.name = name
|
||||
self.intname = intname
|
||||
self.params = [*params]
|
||||
self.result: OurType = OurTypeNone()
|
||||
self.result = result
|
||||
|
||||
def generate(self) -> str:
|
||||
"""
|
||||
Generates the text version
|
||||
"""
|
||||
return '(import "{}" "{}" (func ${}{}))'.format(
|
||||
return '(import "{}" "{}" (func ${}{}{}))'.format(
|
||||
self.module,
|
||||
self.name,
|
||||
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:
|
||||
@ -263,7 +269,7 @@ class Module:
|
||||
Generates the text version
|
||||
"""
|
||||
return '(module\n {}\n {}\n {})\n'.format(
|
||||
self.memory.generate(),
|
||||
'\n '.join(x.generate() for x in self.imports),
|
||||
self.memory.generate(),
|
||||
'\n '.join(x.generate() for x in self.functions),
|
||||
)
|
||||
|
||||
@ -63,7 +63,7 @@ class Suite:
|
||||
self.code_py = code_py
|
||||
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
|
||||
then runs it
|
||||
@ -89,7 +89,16 @@ class Suite:
|
||||
code_wasm = wat2wasm(code_wat)
|
||||
|
||||
# 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):
|
||||
# https://pypi.org/project/pywasm/
|
||||
@ -168,17 +177,23 @@ def _run_wasmtime(code_wasm, args):
|
||||
|
||||
return result
|
||||
|
||||
def _run_wasmer(code_wasm, args):
|
||||
def _run_wasmer(code_wasm, args, imports):
|
||||
# https://pypi.org/project/wasmer/
|
||||
result = SuiteResult()
|
||||
|
||||
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!
|
||||
module = wasmer.Module(store, code_wasm)
|
||||
|
||||
# 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('<Not available on wasmer>\n')
|
||||
|
||||
@ -416,3 +416,27 @@ def testEntry() -> i32x4:
|
||||
result = Suite(code_py, 'test_rgb2hsl').run_code()
|
||||
|
||||
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user