MVP #1
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>
|
<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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
@ -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),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user