Compare commits
No commits in common. "2970093c8f7d93b03ac03a8b8e922bbdcd4cd945" and "547576de00e621ca1273231bcfb3d165af83bbc6" have entirely different histories.
2970093c8f
...
547576de00
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +0,0 @@
|
|||||||
/*.wasm
|
|
||||||
/*.wat
|
|
||||||
/.coverage
|
|
||||||
/venv
|
|
||||||
|
|
||||||
__pycache__
|
|
||||||
46
Makefile
46
Makefile
@ -1,44 +1,8 @@
|
|||||||
WABT_DIR := /home/johan/Sources/github.com/WebAssembly/wabt
|
%.wat: %.py compile.py
|
||||||
|
python3.8 compile.py $< > $@
|
||||||
WAT2WASM := $(WABT_DIR)/bin/wat2wasm
|
|
||||||
WASM2C := $(WABT_DIR)/bin/wasm2c
|
|
||||||
|
|
||||||
%.wat: %.py $(shell find phasm -name '*.py') venv/.done
|
|
||||||
venv/bin/python -m phasm $< $@
|
|
||||||
|
|
||||||
%.wat.html: %.wat
|
|
||||||
venv/bin/pygmentize -l wat -O full -f html $^ -o $@
|
|
||||||
|
|
||||||
%.py.html: %.py
|
|
||||||
venv/bin/pygmentize -l py -O full -f html $^ -o $@
|
|
||||||
|
|
||||||
%.wasm: %.wat
|
%.wasm: %.wat
|
||||||
$(WAT2WASM) $^ -o $@
|
wat2wasm $^ -o $@
|
||||||
|
|
||||||
%.c: %.wasm
|
server:
|
||||||
$(WASM2C) $^ -o $@
|
python3.8 -m http.server
|
||||||
|
|
||||||
# %.exe: %.c
|
|
||||||
# cc $^ -o $@ -I $(WABT_DIR)/wasm2c
|
|
||||||
|
|
||||||
examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py))
|
|
||||||
venv/bin/python3 -m http.server --directory examples
|
|
||||||
|
|
||||||
test: venv/.done
|
|
||||||
venv/bin/pytest tests $(TEST_FLAGS)
|
|
||||||
|
|
||||||
lint: venv/.done
|
|
||||||
venv/bin/pylint phasm
|
|
||||||
|
|
||||||
typecheck: venv/.done
|
|
||||||
venv/bin/mypy --strict phasm tests/integration/runners.py
|
|
||||||
|
|
||||||
venv/.done: requirements.txt
|
|
||||||
python3.8 -m venv venv
|
|
||||||
venv/bin/python3 -m pip install wheel pip --upgrade
|
|
||||||
venv/bin/python3 -m pip install -r $^
|
|
||||||
touch $@
|
|
||||||
|
|
||||||
.SECONDARY: # Keep intermediate files
|
|
||||||
|
|
||||||
.PHONY: examples
|
|
||||||
|
|||||||
99
README.md
99
README.md
@ -1,99 +0,0 @@
|
|||||||
phasm
|
|
||||||
=====
|
|
||||||
|
|
||||||
Elevator pitch
|
|
||||||
--------------
|
|
||||||
A programming language, that looks like Python, handles like Haskell,
|
|
||||||
and compiles directly to WebAssembly.
|
|
||||||
|
|
||||||
Project state
|
|
||||||
-------------
|
|
||||||
This is a hobby project for now. Use at your own risk.
|
|
||||||
|
|
||||||
How to run
|
|
||||||
----------
|
|
||||||
You should only need make and python3. Currently, we're working with python3.8,
|
|
||||||
since we're using the python ast parser, it might not work on other versions.
|
|
||||||
|
|
||||||
To run the examples:
|
|
||||||
```sh
|
|
||||||
make examples
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the tests:
|
|
||||||
```sh
|
|
||||||
make test
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the linting and type checking:
|
|
||||||
```sh
|
|
||||||
make lint typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
To compile a Phasm file:
|
|
||||||
```sh
|
|
||||||
python3.8 -m phasm source.py output.wat
|
|
||||||
```
|
|
||||||
|
|
||||||
Additional required tools
|
|
||||||
-------------------------
|
|
||||||
At the moment, the compiler outputs WebAssembly text format. To actually
|
|
||||||
get a binary, you will need the wat2wasm tool[6].
|
|
||||||
|
|
||||||
Example
|
|
||||||
-------
|
|
||||||
For more examples, see the examples directory.
|
|
||||||
```py
|
|
||||||
def helper(n: u64, a: u64, b: u64) -> u64:
|
|
||||||
if n < 1:
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
return helper(n - 1, a + b, a)
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def fib(n: u64) -> u64:
|
|
||||||
if n == 0:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if n == 1:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return helper(n - 1, 0, 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
Gotcha's
|
|
||||||
--------
|
|
||||||
- When importing and exporting unsigned values to WebAssembly, they will become
|
|
||||||
signed, as WebAssembly has no native unsigned type. You may need to cast
|
|
||||||
or reinterpret them.
|
|
||||||
- Currently, Phasm files have the .py extension, which helps with syntax
|
|
||||||
highlighting, that might change in the future.
|
|
||||||
|
|
||||||
Contributing
|
|
||||||
------------
|
|
||||||
At this time, we're mostly looking for use cases for WebAssembly, other than to
|
|
||||||
compile existing C code and running them in the browser. The goal of WebAssembly
|
|
||||||
is to enable high-performance applications on web pages[5]. Though most people
|
|
||||||
seem to use it to have existing code run in the browser.
|
|
||||||
|
|
||||||
If you have a situation where WebAssembly would be useful for it's speed, we're
|
|
||||||
interested to see what you want to use it for.
|
|
||||||
|
|
||||||
Also, if you are trying out Phasm, and you're running into a limitation, we're
|
|
||||||
interested in a minimal test case that shows what you want to achieve and how
|
|
||||||
Phasm currently fails you.
|
|
||||||
|
|
||||||
Name origin
|
|
||||||
-----------
|
|
||||||
- p from python
|
|
||||||
- ha from Haskell
|
|
||||||
- asm from WebAssembly
|
|
||||||
|
|
||||||
References
|
|
||||||
----------
|
|
||||||
[1] https://www.python.org/
|
|
||||||
[2] https://www.haskell.org/
|
|
||||||
[3] https://webassembly.org/
|
|
||||||
[4] https://www.w3.org/TR/wasm-core-1/
|
|
||||||
[5] https://en.wikipedia.org/w/index.php?title=WebAssembly&oldid=1103639883
|
|
||||||
[6] https://github.com/WebAssembly/wabt
|
|
||||||
8
TODO.md
8
TODO.md
@ -1,8 +0,0 @@
|
|||||||
# TODO
|
|
||||||
|
|
||||||
- Implement a trace() builtin for debugging
|
|
||||||
- Implement a proper type matching / checking system
|
|
||||||
- Check if we can use DataView in the Javascript examples, e.g. with setUint32
|
|
||||||
- Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this.
|
|
||||||
- Implement a FizzBuzz example
|
|
||||||
- Also, check the codes for FIXME and TODO
|
|
||||||
133
compile.py
Normal file
133
compile.py
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import _ast
|
||||||
|
import ast
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class Import:
|
||||||
|
def __init__(self, module, name, intname):
|
||||||
|
self.module = module
|
||||||
|
self.name = name
|
||||||
|
self.intname = intname
|
||||||
|
self.params = None
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
return '(import "{}" "{}" (func ${}{}))'.format(
|
||||||
|
self.module,
|
||||||
|
self.name,
|
||||||
|
self.intname,
|
||||||
|
''.join(' (param {})'.format(x) for x in self.params)
|
||||||
|
)
|
||||||
|
|
||||||
|
class Statement:
|
||||||
|
def __init__(self, name, *args):
|
||||||
|
self.name = name
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
return '{} {}'.format(self.name, ' '.join(self.args))
|
||||||
|
|
||||||
|
class Function:
|
||||||
|
def __init__(self, name, exported=True):
|
||||||
|
self.name = name
|
||||||
|
self.exported = exported # TODO: Use __all__!
|
||||||
|
self.statements = []
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
return '(func {}\n {})'.format(
|
||||||
|
('(export "{}")' if self.exported else '${}').format(self.name),
|
||||||
|
'\n '.join(x.generate() for x in self.statements),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Visitor(ast.NodeVisitor):
|
||||||
|
def __init__(self):
|
||||||
|
self._stack = []
|
||||||
|
self.imports = []
|
||||||
|
self.functions = []
|
||||||
|
|
||||||
|
def visit_ImportFrom(self, node):
|
||||||
|
for alias in node.names:
|
||||||
|
self.imports.append(Import(
|
||||||
|
node.module,
|
||||||
|
alias.name,
|
||||||
|
alias.asname,
|
||||||
|
))
|
||||||
|
|
||||||
|
def visit_FunctionDef(self, node):
|
||||||
|
func = Function(
|
||||||
|
node.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._stack.append(func)
|
||||||
|
self.generic_visit(node)
|
||||||
|
self._stack.pop()
|
||||||
|
|
||||||
|
self.functions.append(func)
|
||||||
|
|
||||||
|
def visit_Expr(self, node):
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
def visit_Call(self, node):
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
func = self._stack[-1]
|
||||||
|
func.statements.append(
|
||||||
|
Statement('call', '$' + node.func.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
def visit_BinOp(self, node):
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
func = self._stack[-1]
|
||||||
|
|
||||||
|
if 'Add' == node.op.__class__.__name__:
|
||||||
|
func.statements.append(
|
||||||
|
Statement('i32.add')
|
||||||
|
)
|
||||||
|
elif 'Mult' == node.op.__class__.__name__:
|
||||||
|
func.statements.append(
|
||||||
|
Statement('i32.mul')
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
err(node.op)
|
||||||
|
|
||||||
|
def visit_Constant(self, node):
|
||||||
|
if not self._stack:
|
||||||
|
# Constant outside of any function
|
||||||
|
imp = self.imports[-1]
|
||||||
|
prefix = imp.name + '('
|
||||||
|
val = node.value.strip()
|
||||||
|
|
||||||
|
if val.startswith(prefix) and val.endswith(')'):
|
||||||
|
imp.params = val[len(prefix):-1].split(',')
|
||||||
|
else:
|
||||||
|
func = self._stack[-1]
|
||||||
|
if isinstance(node.value, int):
|
||||||
|
func.statements.append(
|
||||||
|
Statement('i32.const', str(node.value))
|
||||||
|
)
|
||||||
|
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
return '(module\n {}\n {})'.format(
|
||||||
|
'\n '.join(x.generate() for x in self.imports),
|
||||||
|
'\n '.join(x.generate() for x in self.functions),
|
||||||
|
)
|
||||||
|
|
||||||
|
def err(msg: str) -> None:
|
||||||
|
sys.stderr.write('{}\n'.format(msg))
|
||||||
|
|
||||||
|
def main(source: str) -> int:
|
||||||
|
with open(source, 'r') as fil:
|
||||||
|
code = fil.read()
|
||||||
|
|
||||||
|
res = ast.parse(code, source)
|
||||||
|
|
||||||
|
visitor = Visitor()
|
||||||
|
visitor.visit(res)
|
||||||
|
|
||||||
|
print(visitor.generate())
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(*sys.argv[1:]))
|
||||||
4
examples/.gitignore
vendored
4
examples/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
*.py.html
|
|
||||||
*.wasm
|
|
||||||
*.wat
|
|
||||||
*.wat.html
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Examples - Buffer</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Buffer</h1>
|
|
||||||
|
|
||||||
<a href="index.html">List</a> - <a href="buffer.py.html">Source</a> - <a href="buffer.wat.html">WebAssembly</a>
|
|
||||||
|
|
||||||
<div style="white-space: pre;" id="results"></div>
|
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript" src="./include.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
let importObject = {};
|
|
||||||
|
|
||||||
// Run a single test
|
|
||||||
function run_test(app, str)
|
|
||||||
{
|
|
||||||
let offset = alloc_bytes(app, str);
|
|
||||||
|
|
||||||
let js_chars = [];
|
|
||||||
let wasm_chars = [];
|
|
||||||
for(let idx = 0; idx < str.length; ++idx) {
|
|
||||||
js_chars.push(str.charCodeAt(idx));
|
|
||||||
wasm_chars.push(app.instance.exports.index(offset, idx));
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = js_chars.every(function(value, index) { return value === wasm_chars[index]})
|
|
||||||
|
|
||||||
test_result(result, {
|
|
||||||
'summary': 'js_chars == wasm_chars, for "' + str + '"',
|
|
||||||
'attributes': {
|
|
||||||
'js_chars': js_chars,
|
|
||||||
'wasm_chars': wasm_chars,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
WebAssembly.instantiateStreaming(fetch('buffer.wasm'), importObject)
|
|
||||||
.then(app => {
|
|
||||||
run_test(app, '');
|
|
||||||
run_test(app, 'a');
|
|
||||||
run_test(app, 'Hello');
|
|
||||||
run_test(app, 'The quick brown fox jumps over the lazy dog');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
@exported
|
|
||||||
def index(inp: bytes, idx: u32) -> u8:
|
|
||||||
return inp[idx]
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def length(inp: bytes) -> i32:
|
|
||||||
return len(inp)
|
|
||||||
@ -1,205 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Examples - CRC32</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Buffer</h1>
|
|
||||||
|
|
||||||
<a href="index.html">List</a> - <a href="crc32.py.html">Source</a> - <a href="crc32.wat.html">WebAssembly</a><br />
|
|
||||||
<br />
|
|
||||||
Note: This tests performs some timing comparison, please wait a few seconds for the results.<br />
|
|
||||||
<div style="white-space: pre;" id="results"></div>
|
|
||||||
|
|
||||||
<h2>Measurement log</h2>
|
|
||||||
<h3>AMD Ryzen 7 3700X 8-Core, Ubuntu 20.04, Linux 5.4.0-124-generic</h3>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Test</td>
|
|
||||||
<td>Interpreter</td>
|
|
||||||
<td>Setup</td>
|
|
||||||
<td>WebAssembly</td>
|
|
||||||
<td>Javascript</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Lynx * 65536</td>
|
|
||||||
<td>Chromium 104.0.5112.101</td>
|
|
||||||
<td>DevTools closed</td>
|
|
||||||
<td>9.35</td>
|
|
||||||
<td>12.56</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Lynx * 65536</td>
|
|
||||||
<td>Chromium 104.0.5112.101</td>
|
|
||||||
<td>DevTools open</td>
|
|
||||||
<td>14.71</td>
|
|
||||||
<td>12.72</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Lynx * 65536</td>
|
|
||||||
<td>Chromium 104.0.5112.101</td>
|
|
||||||
<td>Record page load</td>
|
|
||||||
<td>9.44</td>
|
|
||||||
<td>12.69</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Lynx * 65536</td>
|
|
||||||
<td>Firefox 103</td>
|
|
||||||
<td>DevTools closed</td>
|
|
||||||
<td>9.02</td>
|
|
||||||
<td>5.86</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Lynx * 65536</td>
|
|
||||||
<td>Firefox 103</td>
|
|
||||||
<td>DevTools open</td>
|
|
||||||
<td>9.01</td>
|
|
||||||
<td>5.83</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Lynx * 65536</td>
|
|
||||||
<td>Firefox 103</td>
|
|
||||||
<td>Record page load</td>
|
|
||||||
<td>72.41</td>
|
|
||||||
<td>5.85</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>Lynx * 1048576</td>
|
|
||||||
<td>Chromium 104.0.5112.101</td>
|
|
||||||
<td>DevTools closed</td>
|
|
||||||
<td>149.24</td>
|
|
||||||
<td>202.36</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Lynx * 1048576</td>
|
|
||||||
<td>Firefox 103</td>
|
|
||||||
<td>DevTools closed</td>
|
|
||||||
<td>145.01</td>
|
|
||||||
<td>91.44</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
Notes:<br />
|
|
||||||
- Firefox seems faster than Chromium in my setup for Javascript, WebAssembly seems about the same.<br />
|
|
||||||
- Having DevTools open in Chromium seems to slow down the WebAssembly by about 30%, but not when doing a recording of the page load.<br />
|
|
||||||
- WebAssembly in Firefox seems to slow down when doing a recording of the page load, which makes sense, but the Javascript does not.<br />
|
|
||||||
|
|
||||||
<script type="text/javascript" src="./include.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
let importObject = {};
|
|
||||||
|
|
||||||
// Build up a JS version
|
|
||||||
var makeCRCTable = function(){
|
|
||||||
var c;
|
|
||||||
var crcTable = [];
|
|
||||||
for(var n =0; n < 256; n++){
|
|
||||||
c = n;
|
|
||||||
for(var k =0; k < 8; k++){
|
|
||||||
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
|
||||||
}
|
|
||||||
crcTable[n] = c;
|
|
||||||
}
|
|
||||||
return crcTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.crcTable = makeCRCTable();
|
|
||||||
|
|
||||||
var crc32_js = function(i8arr) {
|
|
||||||
// console.log('crc32_js', i8arr.length);
|
|
||||||
|
|
||||||
var crcTable = window.crcTable;
|
|
||||||
var crc = 0 ^ (-1);
|
|
||||||
|
|
||||||
for (var i = 0; i < i8arr.length; i++ ) {
|
|
||||||
crc = (crc >>> 8) ^ crcTable[(crc ^ i8arr[i]) & 0xFF];
|
|
||||||
}
|
|
||||||
|
|
||||||
return (crc ^ (-1)) >>> 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run a single test
|
|
||||||
function run_test(app, str, str_repeat)
|
|
||||||
{
|
|
||||||
// Cast to Uint32 in Javascript
|
|
||||||
let crc32_wasm = function(offset) {
|
|
||||||
// console.log('crc32_wasm', str.length);
|
|
||||||
return app.instance.exports.crc32(offset) >>> 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
let orig_str = str;
|
|
||||||
if( str_repeat ) {
|
|
||||||
str = str.repeat(str_repeat);
|
|
||||||
} else {
|
|
||||||
str_repeat = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = Uint8Array.from(str.split('').map(x => x.charCodeAt()));
|
|
||||||
|
|
||||||
offset = alloc_bytes(app, data);
|
|
||||||
|
|
||||||
let tweak = () => {
|
|
||||||
data[0] = data[0] + 1;
|
|
||||||
|
|
||||||
let i8arr = new Uint8Array(app.instance.exports.memory.buffer, offset + 4, data.length);
|
|
||||||
i8arr[0] = i8arr[0] + 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
let tweak_reset = () => {
|
|
||||||
data[0] = 'T'.charCodeAt(0);
|
|
||||||
|
|
||||||
let i8arr = new Uint8Array(app.instance.exports.memory.buffer, offset + 4, data.length);
|
|
||||||
i8arr[0] = 'T'.charCodeAt(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run once to get the result
|
|
||||||
// For some reason, the JS version takes 2ms on the first run
|
|
||||||
// let wasm_result = crc32_wasm(offset);
|
|
||||||
// let js_result = crc32_js(data);
|
|
||||||
|
|
||||||
let wasm_timing = run_times(100, () => crc32_wasm(offset));
|
|
||||||
let js_timing = run_times(100, () => crc32_js(data));
|
|
||||||
|
|
||||||
let wasm_time = wasm_timing.avg;
|
|
||||||
let js_time = js_timing.avg;
|
|
||||||
|
|
||||||
let check = wasm_timing.values.every(function(value, index) {
|
|
||||||
return value.result === js_timing.values[index].result;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Don't test speedup for small strings, it varies a lot
|
|
||||||
let speedup = str.length < 16 ? 1 : (js_time == wasm_time ? 1 : js_time / wasm_time);
|
|
||||||
|
|
||||||
test_result(check && 0.999 < speedup, { // At least as fast as Javascript
|
|
||||||
'summary': 'crc32(' + (str
|
|
||||||
? (str.length < 64 ? '"' + str + '"' : '"' + str.substring(0, 64) + '..." (' + str.length + ')')
|
|
||||||
: '""') + ')',
|
|
||||||
'attributes': {
|
|
||||||
'str': orig_str,
|
|
||||||
'str_repeat': str_repeat,
|
|
||||||
'wasm_timing': wasm_timing,
|
|
||||||
'js_timing': js_timing,
|
|
||||||
'check': check,
|
|
||||||
'speedup': speedup,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load WebAssembly, and run all tests
|
|
||||||
WebAssembly.instantiateStreaming(fetch('crc32.wasm'), importObject)
|
|
||||||
.then(app => {
|
|
||||||
app.instance.exports.memory.grow(640);
|
|
||||||
|
|
||||||
run_test(app, "");
|
|
||||||
run_test(app, "a");
|
|
||||||
run_test(app, "Z");
|
|
||||||
run_test(app, "ab");
|
|
||||||
run_test(app, "abcdefghijklmnopqrstuvwxyz");
|
|
||||||
run_test(app, "The quick brown fox jumps over the lazy dog");
|
|
||||||
run_test(app, "The quick brown fox jumps over the lazy dog", 1024);
|
|
||||||
run_test(app, "Lynx c.q. vos prikt bh: dag zwemjuf!", 1048576);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
# #include <inttypes.h> // uint32_t, uint8_t
|
|
||||||
#
|
|
||||||
# uint32_t CRC32(const uint8_t data[], size_t data_length) {
|
|
||||||
# uint32_t crc32 = 0xFFFFFFFFu;
|
|
||||||
#
|
|
||||||
# for (size_t i = 0; i < data_length; i++) {
|
|
||||||
# const uint32_t lookupIndex = (crc32 ^ data[i]) & 0xff;
|
|
||||||
# crc32 = (crc32 >> 8) ^ CRCTable[lookupIndex]; // CRCTable is an array of 256 32-bit constants
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# // Finalize the CRC-32 value by inverting all the bits
|
|
||||||
# crc32 ^= 0xFFFFFFFFu;
|
|
||||||
# return crc32;
|
|
||||||
# }
|
|
||||||
|
|
||||||
_CRC32_Table: u32[256] = (
|
|
||||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
|
||||||
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
|
|
||||||
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
|
||||||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
|
|
||||||
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
|
||||||
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
|
||||||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
|
|
||||||
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
|
|
||||||
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
|
||||||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
|
||||||
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
|
|
||||||
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
|
||||||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
|
|
||||||
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
|
|
||||||
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
|
||||||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
|
|
||||||
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
|
|
||||||
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
|
||||||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
|
|
||||||
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
|
||||||
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
|
||||||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
|
|
||||||
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
|
|
||||||
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
|
||||||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
|
||||||
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
|
|
||||||
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
|
||||||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
|
|
||||||
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
|
|
||||||
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
|
||||||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
|
|
||||||
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _crc32_f(crc: u32, byt: u8) -> u32:
|
|
||||||
return (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ u32(byt)]
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def crc32(data: bytes) -> u32:
|
|
||||||
return 0xFFFFFFFF ^ foldl(_crc32_f, 0xFFFFFFFF, data)
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Examples - Fibonacci</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Fibonacci</h1>
|
|
||||||
|
|
||||||
<a href="index.html">List</a> - <a href="fib.py.html">Source</a> - <a href="fib.wat.html">WebAssembly</a>
|
|
||||||
|
|
||||||
<div style="white-space: pre;" id="results"></div>
|
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript" src="./include.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
let importObject = {};
|
|
||||||
|
|
||||||
function run_test(app, number, expected)
|
|
||||||
{
|
|
||||||
let actual = app.instance.exports.fib(BigInt(number));
|
|
||||||
console.log(actual);
|
|
||||||
|
|
||||||
test_result(BigInt(expected) == actual, {
|
|
||||||
'summary': 'fib(' + number + ')',
|
|
||||||
'attributes': {
|
|
||||||
'expected': expected,
|
|
||||||
'actual': actual
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
WebAssembly.instantiateStreaming(fetch('fib.wasm'), importObject)
|
|
||||||
.then(app => {
|
|
||||||
// 92: 7540113804746346429
|
|
||||||
// i64: 9223372036854775807
|
|
||||||
// 93: 12200160415121876738
|
|
||||||
|
|
||||||
run_test(app, 1, '1');
|
|
||||||
run_test(app, 2, '1');
|
|
||||||
run_test(app, 3, '2');
|
|
||||||
run_test(app, 4, '3');
|
|
||||||
run_test(app, 10, '55');
|
|
||||||
run_test(app, 20, '6765');
|
|
||||||
run_test(app, 30, '832040');
|
|
||||||
run_test(app, 40, '102334155');
|
|
||||||
run_test(app, 50, '12586269025');
|
|
||||||
run_test(app, 60, '1548008755920');
|
|
||||||
run_test(app, 70, '190392490709135');
|
|
||||||
run_test(app, 80, '23416728348467685');
|
|
||||||
run_test(app, 90, '2880067194370816120');
|
|
||||||
run_test(app, 92, '7540113804746346429');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
def helper(n: u64, a: u64, b: u64) -> u64:
|
|
||||||
if n < 1:
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
return helper(n - 1, a + b, a)
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def fib(n: u64) -> u64:
|
|
||||||
if n == 0:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if n == 1:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return helper(n - 1, 0, 1)
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u64:
|
|
||||||
return fib(40)
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Examples - Fold</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Fold</h1>
|
|
||||||
|
|
||||||
<a href="index.html">List</a> - <a href="fold.py.html">Source</a> - <a href="fold.wat.html">WebAssembly</a>
|
|
||||||
|
|
||||||
<div style="white-space: pre;" id="results"></div>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="./include.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
let importObject = {};
|
|
||||||
|
|
||||||
function run_test(app, data, expected)
|
|
||||||
{
|
|
||||||
let offset = alloc_bytes(app, data);
|
|
||||||
|
|
||||||
let actual = app.instance.exports.foldl_u8_or_1(offset);
|
|
||||||
|
|
||||||
test_result(expected == actual, {
|
|
||||||
'summary': 'foldl(or, 1, [' + data + ']) == ' + expected,
|
|
||||||
'attributes': {
|
|
||||||
'data': data,
|
|
||||||
'expected': expected,
|
|
||||||
'offset': offset,
|
|
||||||
'actual': actual,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
WebAssembly.instantiateStreaming(fetch('fold.wasm'), importObject)
|
|
||||||
.then(app => {
|
|
||||||
run_test(app, [], 1);
|
|
||||||
run_test(app, [0x20], 0x21);
|
|
||||||
run_test(app, [0x20, 0x10], 0x31);
|
|
||||||
run_test(app, [0x20, 0x10, 0x08], 0x39);
|
|
||||||
run_test(app, [0x20, 0x10, 0x08, 0x04], 0x3D);
|
|
||||||
run_test(app, [0x20, 0x10, 0x08, 0x04, 0x02], 0x3F);
|
|
||||||
run_test(app, [0x20, 0x10, 0x08, 0x04, 0x02, 0x01], 0x3F);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
def u8_or(l: u8, r: u8) -> u8:
|
|
||||||
return l | r
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def foldl_u8_or_1(b: bytes) -> u8:
|
|
||||||
return foldl(u8_or, 1, b)
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
<!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" src="./include.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
let importObject = {
|
|
||||||
'imports': {
|
|
||||||
'log': log,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let log_result = [];
|
|
||||||
|
|
||||||
function log(inp)
|
|
||||||
{
|
|
||||||
log_result.push(inp);
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_test(app, a, b)
|
|
||||||
{
|
|
||||||
log_result = [];
|
|
||||||
|
|
||||||
let expected = a * b;
|
|
||||||
|
|
||||||
app.instance.exports.mult_and_log(a, b);
|
|
||||||
|
|
||||||
let actual = log_result[0];
|
|
||||||
|
|
||||||
test_result(expected == actual, {
|
|
||||||
'summary': 'mult_and_log(' + a + ', ' + b + ') == ' + expected,
|
|
||||||
'attributes': {
|
|
||||||
'a': a,
|
|
||||||
'b': b,
|
|
||||||
'expected': expected,
|
|
||||||
'actual': actual,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
WebAssembly.instantiateStreaming(fetch('imported.wasm'), importObject)
|
|
||||||
.then(app => {
|
|
||||||
run_test(app, 1, 1);
|
|
||||||
run_test(app, 3, 5);
|
|
||||||
run_test(app, 8, 19);
|
|
||||||
run_test(app, 12, 127);
|
|
||||||
run_test(app, 79, 193);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
@imported
|
|
||||||
def log(no: i32) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def mult_and_log(a: i32, b: i32) -> None:
|
|
||||||
return log(a * b)
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
function alloc_bytes(app, data)
|
|
||||||
{
|
|
||||||
let stdlib_types___alloc_bytes__ = app.instance.exports['stdlib.types.__alloc_bytes__']
|
|
||||||
|
|
||||||
if( typeof data == 'string' ) {
|
|
||||||
// TODO: Unicode
|
|
||||||
data = Uint8Array.from(data.split('').map(x => x.charCodeAt()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = stdlib_types___alloc_bytes__(data.length);
|
|
||||||
let i8arr = new Uint8Array(app.instance.exports.memory.buffer, offset + 4, data.length);
|
|
||||||
i8arr.set(data);
|
|
||||||
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_times(times, callback, tweak)
|
|
||||||
{
|
|
||||||
let sum = 0;
|
|
||||||
let max = 0;
|
|
||||||
let min = 1000000000000000000;
|
|
||||||
let values = [];
|
|
||||||
for(let idx = 0; idx < times; idx += 1) {
|
|
||||||
if( tweak ) {
|
|
||||||
tweak();
|
|
||||||
}
|
|
||||||
|
|
||||||
const t0 = performance.now();
|
|
||||||
let result = callback();
|
|
||||||
const t1 = performance.now();
|
|
||||||
let time = t1 - t0;
|
|
||||||
sum += time;
|
|
||||||
values.push({'time': time, 'result': result});
|
|
||||||
max = max < time ? time : max;
|
|
||||||
min = min > time ? time : min;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
'min': min,
|
|
||||||
'avg': sum / times,
|
|
||||||
'max': max,
|
|
||||||
'sum': sum,
|
|
||||||
'values': values,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_result(is_pass, data)
|
|
||||||
{
|
|
||||||
data = data || {};
|
|
||||||
|
|
||||||
let result_details = document.createElement('details');
|
|
||||||
|
|
||||||
let result_summary = document.createElement('summary');
|
|
||||||
result_summary.textContent =
|
|
||||||
(is_pass ? 'Test passed: ' : 'Test failed: ')
|
|
||||||
+ (data.summary ?? '(no summary)')
|
|
||||||
;
|
|
||||||
result_summary.setAttribute('style', is_pass ? 'background: green' : 'background: red');
|
|
||||||
result_details.appendChild(result_summary);
|
|
||||||
|
|
||||||
if( data.attributes ) {
|
|
||||||
result_table(data, result_details);
|
|
||||||
}
|
|
||||||
|
|
||||||
let results = document.getElementById('results');
|
|
||||||
results.appendChild(result_details);
|
|
||||||
}
|
|
||||||
|
|
||||||
function result_table(attributes, parent)
|
|
||||||
{
|
|
||||||
let table = document.createElement('table');
|
|
||||||
|
|
||||||
Object.keys(attributes).forEach(idx => {
|
|
||||||
let td0 = document.createElement('td');
|
|
||||||
td0.setAttribute('style', 'vertical-align: top;');
|
|
||||||
td0.textContent = idx;
|
|
||||||
let td1 = document.createElement('td');
|
|
||||||
if( typeof(attributes[idx]) == 'object' ) {
|
|
||||||
let result_details = document.createElement('details');
|
|
||||||
|
|
||||||
let result_summary = document.createElement('summary');
|
|
||||||
result_summary.textContent = 'Show me';
|
|
||||||
result_details.appendChild(result_summary);
|
|
||||||
|
|
||||||
result_table(attributes[idx], result_details);
|
|
||||||
|
|
||||||
td1.appendChild(result_details);
|
|
||||||
} else {
|
|
||||||
td1.textContent = attributes[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
let tr = document.createElement('tr');
|
|
||||||
tr.appendChild(td0);
|
|
||||||
tr.appendChild(td1);
|
|
||||||
|
|
||||||
table.appendChild(tr);
|
|
||||||
});
|
|
||||||
parent.append(table);
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Examples</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Examples</h1>
|
|
||||||
<h2>Standard</h2>
|
|
||||||
<ul>
|
|
||||||
<li><a href="crc32.html">CRC32</a></li>
|
|
||||||
<li><a href="fib.html">Fibonacci</a></li>
|
|
||||||
</ul>
|
|
||||||
<h2>Technical</h2>
|
|
||||||
<ul>
|
|
||||||
<li><a href="buffer.html">Buffer</a></li>
|
|
||||||
<li><a href="fold.html">Folding</a></li>
|
|
||||||
<li><a href="imported.html">Imported</a></li>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
20
func.html
Normal file
20
func.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Simple add example</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
WebAssembly.instantiateStreaming(fetch('func.wasm'))
|
||||||
|
.then(obj => {
|
||||||
|
console.log(obj.instance.exports.add(1, 2)); // "3"
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
29
log.html
Normal file
29
log.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Simple log example</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var importObject = {
|
||||||
|
console: {
|
||||||
|
log: function(arg) {
|
||||||
|
console.log(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
WebAssembly.instantiateStreaming(fetch('log.wasm'), importObject)
|
||||||
|
.then(obj => {
|
||||||
|
obj.instance.exports.logIt();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
5
log.py
Normal file
5
log.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from console import log as log
|
||||||
|
"log(i32)"
|
||||||
|
|
||||||
|
def logIt():
|
||||||
|
log(13 + 13 * 123)
|
||||||
@ -1,28 +0,0 @@
|
|||||||
"""
|
|
||||||
Functions for using this module from CLI
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from .parser import phasm_parse
|
|
||||||
from .compiler import phasm_compile
|
|
||||||
|
|
||||||
def main(source: str, sink: str) -> int:
|
|
||||||
"""
|
|
||||||
Main method
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(source, 'r') as fil:
|
|
||||||
code_py = fil.read()
|
|
||||||
|
|
||||||
our_module = phasm_parse(code_py)
|
|
||||||
wasm_module = phasm_compile(our_module)
|
|
||||||
code_wat = wasm_module.to_wat()
|
|
||||||
|
|
||||||
with open(sink, 'w') as fil:
|
|
||||||
fil.write(code_wat)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main(*sys.argv[1:])) # pylint: disable=E1120
|
|
||||||
@ -1,237 +0,0 @@
|
|||||||
"""
|
|
||||||
This module generates source code based on the parsed AST
|
|
||||||
|
|
||||||
It's intented to be a "any color, as long as it's black" kind of renderer
|
|
||||||
"""
|
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
from . import ourlang
|
|
||||||
from . import typing
|
|
||||||
|
|
||||||
def phasm_render(inp: ourlang.Module) -> str:
|
|
||||||
"""
|
|
||||||
Public method for rendering a Phasm module into Phasm code
|
|
||||||
"""
|
|
||||||
return module(inp)
|
|
||||||
|
|
||||||
Statements = Generator[str, None, None]
|
|
||||||
|
|
||||||
def type_(inp: typing.TypeBase) -> str:
|
|
||||||
"""
|
|
||||||
Render: Type (name)
|
|
||||||
"""
|
|
||||||
if isinstance(inp, typing.TypeNone):
|
|
||||||
return 'None'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeBool):
|
|
||||||
return 'bool'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeUInt8):
|
|
||||||
return 'u8'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeUInt32):
|
|
||||||
return 'u32'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeUInt64):
|
|
||||||
return 'u64'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeInt32):
|
|
||||||
return 'i32'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeInt64):
|
|
||||||
return 'i64'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeFloat32):
|
|
||||||
return 'f32'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeFloat64):
|
|
||||||
return 'f64'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeBytes):
|
|
||||||
return 'bytes'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeTuple):
|
|
||||||
mems = ', '.join(
|
|
||||||
type_(x.type)
|
|
||||||
for x in inp.members
|
|
||||||
)
|
|
||||||
|
|
||||||
return f'({mems}, )'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeStaticArray):
|
|
||||||
return f'{type_(inp.member_type)}[{len(inp.members)}]'
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeStruct):
|
|
||||||
return inp.name
|
|
||||||
|
|
||||||
raise NotImplementedError(type_, inp)
|
|
||||||
|
|
||||||
def struct_definition(inp: typing.TypeStruct) -> str:
|
|
||||||
"""
|
|
||||||
Render: TypeStruct's definition
|
|
||||||
"""
|
|
||||||
result = f'class {inp.name}:\n'
|
|
||||||
for mem in inp.members:
|
|
||||||
result += f' {mem.name}: {type_(mem.type)}\n'
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
|
|
||||||
"""
|
|
||||||
Render: Module Constant's definition
|
|
||||||
"""
|
|
||||||
return f'{inp.name}: {type_(inp.type)} = {expression(inp.constant)}\n'
|
|
||||||
|
|
||||||
def expression(inp: ourlang.Expression) -> str:
|
|
||||||
"""
|
|
||||||
Render: A Phasm expression
|
|
||||||
"""
|
|
||||||
if isinstance(inp, (
|
|
||||||
ourlang.ConstantUInt8, ourlang.ConstantUInt32, ourlang.ConstantUInt64,
|
|
||||||
ourlang.ConstantInt32, ourlang.ConstantInt64,
|
|
||||||
)):
|
|
||||||
return str(inp.value)
|
|
||||||
|
|
||||||
if isinstance(inp, (ourlang.ConstantFloat32, ourlang.ConstantFloat64, )):
|
|
||||||
# These might not round trip if the original constant
|
|
||||||
# could not fit in the given float type
|
|
||||||
return str(inp.value)
|
|
||||||
|
|
||||||
if isinstance(inp, (ourlang.ConstantTuple, ourlang.ConstantStaticArray, )):
|
|
||||||
return '(' + ', '.join(
|
|
||||||
expression(x)
|
|
||||||
for x in inp.value
|
|
||||||
) + ', )'
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.VariableReference):
|
|
||||||
return str(inp.name)
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.UnaryOp):
|
|
||||||
if (
|
|
||||||
inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS
|
|
||||||
or inp.operator in ourlang.WEBASSEMBLY_BUILDIN_BYTES_OPS):
|
|
||||||
return f'{inp.operator}({expression(inp.right)})'
|
|
||||||
|
|
||||||
if inp.operator == 'cast':
|
|
||||||
return f'{type_(inp.type)}({expression(inp.right)})'
|
|
||||||
|
|
||||||
return f'{inp.operator}{expression(inp.right)}'
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.BinaryOp):
|
|
||||||
return f'{expression(inp.left)} {inp.operator} {expression(inp.right)}'
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.FunctionCall):
|
|
||||||
args = ', '.join(
|
|
||||||
expression(arg)
|
|
||||||
for arg in inp.arguments
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(inp.function, ourlang.StructConstructor):
|
|
||||||
return f'{inp.function.struct.name}({args})'
|
|
||||||
|
|
||||||
if isinstance(inp.function, ourlang.TupleConstructor):
|
|
||||||
return f'({args}, )'
|
|
||||||
|
|
||||||
return f'{inp.function.name}({args})'
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.AccessBytesIndex):
|
|
||||||
return f'{expression(inp.varref)}[{expression(inp.index)}]'
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.AccessStructMember):
|
|
||||||
return f'{expression(inp.varref)}.{inp.member.name}'
|
|
||||||
|
|
||||||
if isinstance(inp, (ourlang.AccessTupleMember, ourlang.AccessStaticArrayMember, )):
|
|
||||||
if isinstance(inp.member, ourlang.Expression):
|
|
||||||
return f'{expression(inp.varref)}[{expression(inp.member)}]'
|
|
||||||
|
|
||||||
return f'{expression(inp.varref)}[{inp.member.idx}]'
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.Fold):
|
|
||||||
fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr'
|
|
||||||
return f'{fold_name}({inp.func.name}, {expression(inp.base)}, {expression(inp.iter)})'
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ModuleConstantReference):
|
|
||||||
return inp.definition.name
|
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp)
|
|
||||||
|
|
||||||
def statement(inp: ourlang.Statement) -> Statements:
|
|
||||||
"""
|
|
||||||
Render: A list of Phasm statements
|
|
||||||
"""
|
|
||||||
if isinstance(inp, ourlang.StatementPass):
|
|
||||||
yield 'pass'
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.StatementReturn):
|
|
||||||
yield f'return {expression(inp.value)}'
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.StatementIf):
|
|
||||||
yield f'if {expression(inp.test)}:'
|
|
||||||
|
|
||||||
for stmt in inp.statements:
|
|
||||||
for line in statement(stmt):
|
|
||||||
yield f' {line}' if line else ''
|
|
||||||
|
|
||||||
yield ''
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(statement, inp)
|
|
||||||
|
|
||||||
def function(inp: ourlang.Function) -> str:
|
|
||||||
"""
|
|
||||||
Render: Function body
|
|
||||||
|
|
||||||
Imported functions only have "pass" as a body. Later on we might replace
|
|
||||||
this by the function documentation, if any.
|
|
||||||
"""
|
|
||||||
result = ''
|
|
||||||
if inp.exported:
|
|
||||||
result += '@exported\n'
|
|
||||||
if inp.imported:
|
|
||||||
result += '@imported\n'
|
|
||||||
|
|
||||||
args = ', '.join(
|
|
||||||
f'{x}: {type_(y)}'
|
|
||||||
for x, y in inp.posonlyargs
|
|
||||||
)
|
|
||||||
|
|
||||||
result += f'def {inp.name}({args}) -> {type_(inp.returns)}:\n'
|
|
||||||
|
|
||||||
if inp.imported:
|
|
||||||
result += ' pass\n'
|
|
||||||
else:
|
|
||||||
for stmt in inp.statements:
|
|
||||||
for line in statement(stmt):
|
|
||||||
result += f' {line}\n' if line else '\n'
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def module(inp: ourlang.Module) -> str:
|
|
||||||
"""
|
|
||||||
Render: Module
|
|
||||||
"""
|
|
||||||
result = ''
|
|
||||||
|
|
||||||
for struct in inp.structs.values():
|
|
||||||
if result:
|
|
||||||
result += '\n'
|
|
||||||
result += struct_definition(struct)
|
|
||||||
|
|
||||||
for cdef in inp.constant_defs.values():
|
|
||||||
if result:
|
|
||||||
result += '\n'
|
|
||||||
result += constant_definition(cdef)
|
|
||||||
|
|
||||||
for func in inp.functions.values():
|
|
||||||
if func.lineno < 0:
|
|
||||||
# Buildin (-2) or auto generated (-1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if result:
|
|
||||||
result += '\n'
|
|
||||||
result += function(func)
|
|
||||||
|
|
||||||
return result
|
|
||||||
@ -1,669 +0,0 @@
|
|||||||
"""
|
|
||||||
This module contains the code to convert parsed Ourlang into WebAssembly code
|
|
||||||
"""
|
|
||||||
import struct
|
|
||||||
|
|
||||||
from . import codestyle
|
|
||||||
from . import ourlang
|
|
||||||
from . import typing
|
|
||||||
from . import wasm
|
|
||||||
|
|
||||||
from .stdlib import alloc as stdlib_alloc
|
|
||||||
from .stdlib import types as stdlib_types
|
|
||||||
from .wasmgenerator import Generator as WasmGenerator
|
|
||||||
|
|
||||||
LOAD_STORE_TYPE_MAP = {
|
|
||||||
typing.TypeUInt8: 'i32',
|
|
||||||
typing.TypeUInt32: 'i32',
|
|
||||||
typing.TypeUInt64: 'i64',
|
|
||||||
typing.TypeInt32: 'i32',
|
|
||||||
typing.TypeInt64: 'i64',
|
|
||||||
typing.TypeFloat32: 'f32',
|
|
||||||
typing.TypeFloat64: 'f64',
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
When generating code, we sometimes need to load or store simple values
|
|
||||||
"""
|
|
||||||
|
|
||||||
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
|
|
||||||
"""
|
|
||||||
Public method for compiling a parsed Phasm module into
|
|
||||||
a WebAssembly module
|
|
||||||
"""
|
|
||||||
return module(inp)
|
|
||||||
|
|
||||||
def type_(inp: typing.TypeBase) -> wasm.WasmType:
|
|
||||||
"""
|
|
||||||
Compile: type
|
|
||||||
"""
|
|
||||||
if isinstance(inp, typing.TypeNone):
|
|
||||||
return wasm.WasmTypeNone()
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeUInt8):
|
|
||||||
# WebAssembly has only support for 32 and 64 bits
|
|
||||||
# So we need to store more memory per byte
|
|
||||||
return wasm.WasmTypeInt32()
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeUInt32):
|
|
||||||
return wasm.WasmTypeInt32()
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeUInt64):
|
|
||||||
return wasm.WasmTypeInt64()
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeInt32):
|
|
||||||
return wasm.WasmTypeInt32()
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeInt64):
|
|
||||||
return wasm.WasmTypeInt64()
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeFloat32):
|
|
||||||
return wasm.WasmTypeFloat32()
|
|
||||||
|
|
||||||
if isinstance(inp, typing.TypeFloat64):
|
|
||||||
return wasm.WasmTypeFloat64()
|
|
||||||
|
|
||||||
if isinstance(inp, (typing.TypeStruct, typing.TypeTuple, typing.TypeStaticArray, typing.TypeBytes)):
|
|
||||||
# Structs and tuples are passed as pointer
|
|
||||||
# And pointers are i32
|
|
||||||
return wasm.WasmTypeInt32()
|
|
||||||
|
|
||||||
raise NotImplementedError(type_, inp)
|
|
||||||
|
|
||||||
# Operators that work for i32, i64, f32, f64
|
|
||||||
OPERATOR_MAP = {
|
|
||||||
'+': 'add',
|
|
||||||
'-': 'sub',
|
|
||||||
'*': 'mul',
|
|
||||||
'==': 'eq',
|
|
||||||
}
|
|
||||||
|
|
||||||
U8_OPERATOR_MAP = {
|
|
||||||
# Under the hood, this is an i32
|
|
||||||
# Implementing Right Shift XOR, OR, AND is fine since the 3 remaining
|
|
||||||
# bytes stay zero after this operation
|
|
||||||
# Since it's unsigned an unsigned value, Logical or Arithmetic shift right
|
|
||||||
# are the same operation
|
|
||||||
'>>': 'shr_u',
|
|
||||||
'^': 'xor',
|
|
||||||
'|': 'or',
|
|
||||||
'&': 'and',
|
|
||||||
}
|
|
||||||
|
|
||||||
U32_OPERATOR_MAP = {
|
|
||||||
'<': 'lt_u',
|
|
||||||
'>': 'gt_u',
|
|
||||||
'<=': 'le_u',
|
|
||||||
'>=': 'ge_u',
|
|
||||||
'<<': 'shl',
|
|
||||||
'>>': 'shr_u',
|
|
||||||
'^': 'xor',
|
|
||||||
'|': 'or',
|
|
||||||
'&': 'and',
|
|
||||||
}
|
|
||||||
|
|
||||||
U64_OPERATOR_MAP = {
|
|
||||||
'<': 'lt_u',
|
|
||||||
'>': 'gt_u',
|
|
||||||
'<=': 'le_u',
|
|
||||||
'>=': 'ge_u',
|
|
||||||
'<<': 'shl',
|
|
||||||
'>>': 'shr_u',
|
|
||||||
'^': 'xor',
|
|
||||||
'|': 'or',
|
|
||||||
'&': 'and',
|
|
||||||
}
|
|
||||||
|
|
||||||
I32_OPERATOR_MAP = {
|
|
||||||
'<': 'lt_s',
|
|
||||||
'>': 'gt_s',
|
|
||||||
'<=': 'le_s',
|
|
||||||
'>=': 'ge_s',
|
|
||||||
}
|
|
||||||
|
|
||||||
I64_OPERATOR_MAP = {
|
|
||||||
'<': 'lt_s',
|
|
||||||
'>': 'gt_s',
|
|
||||||
'<=': 'le_s',
|
|
||||||
'>=': 'ge_s',
|
|
||||||
}
|
|
||||||
|
|
||||||
def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|
||||||
"""
|
|
||||||
Compile: Any expression
|
|
||||||
"""
|
|
||||||
if isinstance(inp, ourlang.ConstantUInt8):
|
|
||||||
wgn.i32.const(inp.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantUInt32):
|
|
||||||
wgn.i32.const(inp.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantUInt64):
|
|
||||||
wgn.i64.const(inp.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantInt32):
|
|
||||||
wgn.i32.const(inp.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantInt64):
|
|
||||||
wgn.i64.const(inp.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantFloat32):
|
|
||||||
wgn.f32.const(inp.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantFloat64):
|
|
||||||
wgn.f64.const(inp.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.VariableReference):
|
|
||||||
wgn.add_statement('local.get', '${}'.format(inp.name))
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.BinaryOp):
|
|
||||||
expression(wgn, inp.left)
|
|
||||||
expression(wgn, inp.right)
|
|
||||||
|
|
||||||
if isinstance(inp.type, typing.TypeUInt8):
|
|
||||||
if operator := U8_OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i32.{operator}')
|
|
||||||
return
|
|
||||||
if isinstance(inp.type, typing.TypeUInt32):
|
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i32.{operator}')
|
|
||||||
return
|
|
||||||
if operator := U32_OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i32.{operator}')
|
|
||||||
return
|
|
||||||
if isinstance(inp.type, typing.TypeUInt64):
|
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i64.{operator}')
|
|
||||||
return
|
|
||||||
if operator := U64_OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i64.{operator}')
|
|
||||||
return
|
|
||||||
if isinstance(inp.type, typing.TypeInt32):
|
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i32.{operator}')
|
|
||||||
return
|
|
||||||
if operator := I32_OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i32.{operator}')
|
|
||||||
return
|
|
||||||
if isinstance(inp.type, typing.TypeInt64):
|
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i64.{operator}')
|
|
||||||
return
|
|
||||||
if operator := I64_OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i64.{operator}')
|
|
||||||
return
|
|
||||||
if isinstance(inp.type, typing.TypeFloat32):
|
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'f32.{operator}')
|
|
||||||
return
|
|
||||||
if isinstance(inp.type, typing.TypeFloat64):
|
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'f64.{operator}')
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp.type, inp.operator)
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.UnaryOp):
|
|
||||||
expression(wgn, inp.right)
|
|
||||||
|
|
||||||
if isinstance(inp.type, typing.TypeFloat32):
|
|
||||||
if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS:
|
|
||||||
wgn.add_statement(f'f32.{inp.operator}')
|
|
||||||
return
|
|
||||||
if isinstance(inp.type, typing.TypeFloat64):
|
|
||||||
if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS:
|
|
||||||
wgn.add_statement(f'f64.{inp.operator}')
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp.type, typing.TypeInt32):
|
|
||||||
if inp.operator == 'len':
|
|
||||||
if isinstance(inp.right.type, typing.TypeBytes):
|
|
||||||
wgn.i32.load()
|
|
||||||
return
|
|
||||||
|
|
||||||
if inp.operator == 'cast':
|
|
||||||
if isinstance(inp.type, typing.TypeUInt32) and isinstance(inp.right.type, typing.TypeUInt8):
|
|
||||||
# Nothing to do, you can use an u8 value as a u32 no problem
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp.type, inp.operator)
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.FunctionCall):
|
|
||||||
for arg in inp.arguments:
|
|
||||||
expression(wgn, arg)
|
|
||||||
|
|
||||||
wgn.add_statement('call', '${}'.format(inp.function.name))
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.AccessBytesIndex):
|
|
||||||
if not isinstance(inp.type, typing.TypeUInt8):
|
|
||||||
raise NotImplementedError(inp, inp.type)
|
|
||||||
|
|
||||||
expression(wgn, inp.varref)
|
|
||||||
expression(wgn, inp.index)
|
|
||||||
wgn.call(stdlib_types.__subscript_bytes__)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.AccessStructMember):
|
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
|
|
||||||
if mtyp is None:
|
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, inp.member)
|
|
||||||
|
|
||||||
expression(wgn, inp.varref)
|
|
||||||
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.AccessTupleMember):
|
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
|
|
||||||
if mtyp is None:
|
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, inp.member)
|
|
||||||
|
|
||||||
expression(wgn, inp.varref)
|
|
||||||
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.AccessStaticArrayMember):
|
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(inp.static_array.member_type.__class__)
|
|
||||||
if mtyp is None:
|
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of static arrays
|
|
||||||
raise NotImplementedError(expression, inp, inp.member)
|
|
||||||
|
|
||||||
if isinstance(inp.member, typing.TypeStaticArrayMember):
|
|
||||||
expression(wgn, inp.varref)
|
|
||||||
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
|
|
||||||
return
|
|
||||||
|
|
||||||
expression(wgn, inp.varref)
|
|
||||||
expression(wgn, inp.member)
|
|
||||||
wgn.i32.const(inp.static_array.member_type.alloc_size())
|
|
||||||
wgn.i32.mul()
|
|
||||||
wgn.i32.add()
|
|
||||||
wgn.add_statement(f'{mtyp}.load')
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.Fold):
|
|
||||||
expression_fold(wgn, inp)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ModuleConstantReference):
|
|
||||||
if isinstance(inp.type, typing.TypeTuple):
|
|
||||||
assert isinstance(inp.definition.constant, ourlang.ConstantTuple)
|
|
||||||
assert inp.definition.data_block is not None, 'Combined values are memory stored'
|
|
||||||
assert inp.definition.data_block.address is not None, 'Value not allocated'
|
|
||||||
wgn.i32.const(inp.definition.data_block.address)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp.type, typing.TypeStaticArray):
|
|
||||||
assert isinstance(inp.definition.constant, ourlang.ConstantStaticArray)
|
|
||||||
assert inp.definition.data_block is not None, 'Combined values are memory stored'
|
|
||||||
assert inp.definition.data_block.address is not None, 'Value not allocated'
|
|
||||||
wgn.i32.const(inp.definition.data_block.address)
|
|
||||||
return
|
|
||||||
|
|
||||||
assert inp.definition.data_block is None, 'Primitives are not memory stored'
|
|
||||||
|
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(inp.type.__class__)
|
|
||||||
if mtyp is None:
|
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, inp.type)
|
|
||||||
|
|
||||||
expression(wgn, inp.definition.constant)
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp)
|
|
||||||
|
|
||||||
def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
|
|
||||||
"""
|
|
||||||
Compile: Fold expression
|
|
||||||
"""
|
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(inp.base.type.__class__)
|
|
||||||
if mtyp is None:
|
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, inp.base)
|
|
||||||
|
|
||||||
if inp.iter.type.__class__.__name__ != 'TypeBytes':
|
|
||||||
raise NotImplementedError(expression, inp, inp.iter.type)
|
|
||||||
|
|
||||||
wgn.add_statement('nop', comment='acu :: u8')
|
|
||||||
acu_var = wgn.temp_var_u8(f'fold_{codestyle.type_(inp.type)}_acu')
|
|
||||||
wgn.add_statement('nop', comment='adr :: bytes*')
|
|
||||||
adr_var = wgn.temp_var_i32('fold_i32_adr')
|
|
||||||
wgn.add_statement('nop', comment='len :: i32')
|
|
||||||
len_var = wgn.temp_var_i32('fold_i32_len')
|
|
||||||
|
|
||||||
wgn.add_statement('nop', comment='acu = base')
|
|
||||||
expression(wgn, inp.base)
|
|
||||||
wgn.local.set(acu_var)
|
|
||||||
|
|
||||||
wgn.add_statement('nop', comment='adr = adr(iter)')
|
|
||||||
expression(wgn, inp.iter)
|
|
||||||
wgn.local.set(adr_var)
|
|
||||||
|
|
||||||
wgn.add_statement('nop', comment='len = len(iter)')
|
|
||||||
wgn.local.get(adr_var)
|
|
||||||
wgn.i32.load()
|
|
||||||
wgn.local.set(len_var)
|
|
||||||
|
|
||||||
wgn.add_statement('nop', comment='i = 0')
|
|
||||||
idx_var = wgn.temp_var_i32(f'fold_{codestyle.type_(inp.type)}_idx')
|
|
||||||
wgn.i32.const(0)
|
|
||||||
wgn.local.set(idx_var)
|
|
||||||
|
|
||||||
wgn.add_statement('nop', comment='if i < len')
|
|
||||||
wgn.local.get(idx_var)
|
|
||||||
wgn.local.get(len_var)
|
|
||||||
wgn.i32.lt_u()
|
|
||||||
with wgn.if_():
|
|
||||||
wgn.add_statement('nop', comment='while True')
|
|
||||||
with wgn.loop():
|
|
||||||
wgn.add_statement('nop', comment='acu = func(acu, iter[i])')
|
|
||||||
wgn.local.get(acu_var)
|
|
||||||
wgn.local.get(adr_var)
|
|
||||||
wgn.local.get(idx_var)
|
|
||||||
wgn.call(stdlib_types.__subscript_bytes__)
|
|
||||||
wgn.add_statement('call', f'${inp.func.name}')
|
|
||||||
wgn.local.set(acu_var)
|
|
||||||
|
|
||||||
wgn.add_statement('nop', comment='i = i + 1')
|
|
||||||
wgn.local.get(idx_var)
|
|
||||||
wgn.i32.const(1)
|
|
||||||
wgn.i32.add()
|
|
||||||
wgn.local.set(idx_var)
|
|
||||||
|
|
||||||
wgn.add_statement('nop', comment='if i >= len: break')
|
|
||||||
wgn.local.get(idx_var)
|
|
||||||
wgn.local.get(len_var)
|
|
||||||
wgn.i32.lt_u()
|
|
||||||
wgn.br_if(0)
|
|
||||||
|
|
||||||
# return acu
|
|
||||||
wgn.local.get(acu_var)
|
|
||||||
|
|
||||||
def statement_return(wgn: WasmGenerator, inp: ourlang.StatementReturn) -> None:
|
|
||||||
"""
|
|
||||||
Compile: Return statement
|
|
||||||
"""
|
|
||||||
expression(wgn, inp.value)
|
|
||||||
wgn.return_()
|
|
||||||
|
|
||||||
def statement_if(wgn: WasmGenerator, inp: ourlang.StatementIf) -> None:
|
|
||||||
"""
|
|
||||||
Compile: If statement
|
|
||||||
"""
|
|
||||||
expression(wgn, inp.test)
|
|
||||||
with wgn.if_():
|
|
||||||
for stat in inp.statements:
|
|
||||||
statement(wgn, stat)
|
|
||||||
|
|
||||||
if inp.else_statements:
|
|
||||||
raise NotImplementedError
|
|
||||||
# yield wasm.Statement('else')
|
|
||||||
# for stat in inp.else_statements:
|
|
||||||
# statement(wgn, stat)
|
|
||||||
|
|
||||||
def statement(wgn: WasmGenerator, inp: ourlang.Statement) -> None:
|
|
||||||
"""
|
|
||||||
Compile: any statement
|
|
||||||
"""
|
|
||||||
if isinstance(inp, ourlang.StatementReturn):
|
|
||||||
statement_return(wgn, inp)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.StatementIf):
|
|
||||||
statement_if(wgn, inp)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.StatementPass):
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(statement, inp)
|
|
||||||
|
|
||||||
def function_argument(inp: ourlang.FunctionParam) -> wasm.Param:
|
|
||||||
"""
|
|
||||||
Compile: function argument
|
|
||||||
"""
|
|
||||||
return (inp[0], type_(inp[1]), )
|
|
||||||
|
|
||||||
def import_(inp: ourlang.Function) -> wasm.Import:
|
|
||||||
"""
|
|
||||||
Compile: imported function
|
|
||||||
"""
|
|
||||||
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:
|
|
||||||
"""
|
|
||||||
Compile: function
|
|
||||||
"""
|
|
||||||
assert not inp.imported
|
|
||||||
|
|
||||||
wgn = WasmGenerator()
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.TupleConstructor):
|
|
||||||
_generate_tuple_constructor(wgn, inp)
|
|
||||||
elif isinstance(inp, ourlang.StructConstructor):
|
|
||||||
_generate_struct_constructor(wgn, inp)
|
|
||||||
else:
|
|
||||||
for stat in inp.statements:
|
|
||||||
statement(wgn, stat)
|
|
||||||
|
|
||||||
return wasm.Function(
|
|
||||||
inp.name,
|
|
||||||
inp.name if inp.exported else None,
|
|
||||||
[
|
|
||||||
function_argument(x)
|
|
||||||
for x in inp.posonlyargs
|
|
||||||
],
|
|
||||||
[
|
|
||||||
(k, v.wasm_type(), )
|
|
||||||
for k, v in wgn.locals.items()
|
|
||||||
],
|
|
||||||
type_(inp.returns),
|
|
||||||
wgn.statements
|
|
||||||
)
|
|
||||||
|
|
||||||
def module_data_u8(inp: int) -> bytes:
|
|
||||||
"""
|
|
||||||
Compile: module data, u8 value
|
|
||||||
|
|
||||||
# FIXME: All u8 values are stored as u32
|
|
||||||
"""
|
|
||||||
return struct.pack('<i', inp) # Should be B
|
|
||||||
|
|
||||||
def module_data_u32(inp: int) -> bytes:
|
|
||||||
"""
|
|
||||||
Compile: module data, u32 value
|
|
||||||
"""
|
|
||||||
return struct.pack('<I', inp)
|
|
||||||
|
|
||||||
def module_data_u64(inp: int) -> bytes:
|
|
||||||
"""
|
|
||||||
Compile: module data, u64 value
|
|
||||||
"""
|
|
||||||
return struct.pack('<Q', inp)
|
|
||||||
|
|
||||||
def module_data_i32(inp: int) -> bytes:
|
|
||||||
"""
|
|
||||||
Compile: module data, i32 value
|
|
||||||
"""
|
|
||||||
return struct.pack('<i', inp)
|
|
||||||
|
|
||||||
def module_data_i64(inp: int) -> bytes:
|
|
||||||
"""
|
|
||||||
Compile: module data, i64 value
|
|
||||||
"""
|
|
||||||
return struct.pack('<q', inp)
|
|
||||||
|
|
||||||
def module_data_f32(inp: float) -> bytes:
|
|
||||||
"""
|
|
||||||
Compile: module data, f32 value
|
|
||||||
"""
|
|
||||||
return struct.pack('<f', inp)
|
|
||||||
|
|
||||||
def module_data_f64(inp: float) -> bytes:
|
|
||||||
"""
|
|
||||||
Compile: module data, f64 value
|
|
||||||
"""
|
|
||||||
return struct.pack('<d', inp)
|
|
||||||
|
|
||||||
def module_data(inp: ourlang.ModuleData) -> bytes:
|
|
||||||
"""
|
|
||||||
Compile: module data
|
|
||||||
"""
|
|
||||||
unalloc_ptr = stdlib_alloc.UNALLOC_PTR
|
|
||||||
|
|
||||||
allocated_data = b''
|
|
||||||
|
|
||||||
for block in inp.blocks:
|
|
||||||
block.address = unalloc_ptr + 4 # 4 bytes for allocator header
|
|
||||||
|
|
||||||
data_list = []
|
|
||||||
|
|
||||||
for constant in block.data:
|
|
||||||
if isinstance(constant, ourlang.ConstantUInt8):
|
|
||||||
data_list.append(module_data_u8(constant.value))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(constant, ourlang.ConstantUInt32):
|
|
||||||
data_list.append(module_data_u32(constant.value))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(constant, ourlang.ConstantUInt64):
|
|
||||||
data_list.append(module_data_u64(constant.value))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(constant, ourlang.ConstantInt32):
|
|
||||||
data_list.append(module_data_i32(constant.value))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(constant, ourlang.ConstantInt64):
|
|
||||||
data_list.append(module_data_i64(constant.value))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(constant, ourlang.ConstantFloat32):
|
|
||||||
data_list.append(module_data_f32(constant.value))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(constant, ourlang.ConstantFloat64):
|
|
||||||
data_list.append(module_data_f64(constant.value))
|
|
||||||
continue
|
|
||||||
|
|
||||||
raise NotImplementedError(constant)
|
|
||||||
|
|
||||||
block_data = b''.join(data_list)
|
|
||||||
|
|
||||||
allocated_data += module_data_u32(len(block_data)) + block_data
|
|
||||||
|
|
||||||
unalloc_ptr += 4 + len(block_data)
|
|
||||||
|
|
||||||
return (
|
|
||||||
# Store that we've initialized the memory
|
|
||||||
module_data_u32(stdlib_alloc.IDENTIFIER)
|
|
||||||
# Store the first reserved i32
|
|
||||||
+ module_data_u32(0)
|
|
||||||
# Store the pointer towards the first free block
|
|
||||||
# In this case, 0 since we haven't freed any blocks yet
|
|
||||||
+ module_data_u32(0)
|
|
||||||
# Store the pointer towards the first unallocated block
|
|
||||||
# In this case the end of the stdlib.alloc header at the start
|
|
||||||
+ module_data_u32(unalloc_ptr)
|
|
||||||
# Store the actual data
|
|
||||||
+ allocated_data
|
|
||||||
)
|
|
||||||
|
|
||||||
def module(inp: ourlang.Module) -> wasm.Module:
|
|
||||||
"""
|
|
||||||
Compile: module
|
|
||||||
"""
|
|
||||||
result = wasm.Module()
|
|
||||||
|
|
||||||
result.memory.data = module_data(inp.data)
|
|
||||||
|
|
||||||
result.imports = [
|
|
||||||
import_(x)
|
|
||||||
for x in inp.functions.values()
|
|
||||||
if x.imported
|
|
||||||
]
|
|
||||||
|
|
||||||
result.functions = [
|
|
||||||
stdlib_alloc.__find_free_block__,
|
|
||||||
stdlib_alloc.__alloc__,
|
|
||||||
stdlib_types.__alloc_bytes__,
|
|
||||||
stdlib_types.__subscript_bytes__,
|
|
||||||
] + [
|
|
||||||
function(x)
|
|
||||||
for x in inp.functions.values()
|
|
||||||
if not x.imported
|
|
||||||
]
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _generate_tuple_constructor(wgn: WasmGenerator, inp: ourlang.TupleConstructor) -> None:
|
|
||||||
tmp_var = wgn.temp_var_i32('tuple_adr')
|
|
||||||
|
|
||||||
# Allocated the required amounts of bytes in memory
|
|
||||||
wgn.i32.const(inp.tuple.alloc_size())
|
|
||||||
wgn.call(stdlib_alloc.__alloc__)
|
|
||||||
wgn.local.set(tmp_var)
|
|
||||||
|
|
||||||
# Store each member individually
|
|
||||||
for member in inp.tuple.members:
|
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__)
|
|
||||||
if mtyp is None:
|
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, member)
|
|
||||||
|
|
||||||
wgn.local.get(tmp_var)
|
|
||||||
wgn.add_statement('local.get', f'$arg{member.idx}')
|
|
||||||
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
|
|
||||||
|
|
||||||
# Return the allocated address
|
|
||||||
wgn.local.get(tmp_var)
|
|
||||||
|
|
||||||
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
|
|
||||||
tmp_var = wgn.temp_var_i32('struct_adr')
|
|
||||||
|
|
||||||
# Allocated the required amounts of bytes in memory
|
|
||||||
wgn.i32.const(inp.struct.alloc_size())
|
|
||||||
wgn.call(stdlib_alloc.__alloc__)
|
|
||||||
wgn.local.set(tmp_var)
|
|
||||||
|
|
||||||
# Store each member individually
|
|
||||||
for member in inp.struct.members:
|
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__)
|
|
||||||
if mtyp is None:
|
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, member)
|
|
||||||
|
|
||||||
wgn.local.get(tmp_var)
|
|
||||||
wgn.add_statement('local.get', f'${member.name}')
|
|
||||||
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
|
|
||||||
|
|
||||||
# Return the allocated address
|
|
||||||
wgn.local.get(tmp_var)
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
"""
|
|
||||||
Exceptions for the phasm compiler
|
|
||||||
"""
|
|
||||||
|
|
||||||
class StaticError(Exception):
|
|
||||||
"""
|
|
||||||
An error found during static analysis
|
|
||||||
"""
|
|
||||||
487
phasm/ourlang.py
487
phasm/ourlang.py
@ -1,487 +0,0 @@
|
|||||||
"""
|
|
||||||
Contains the syntax tree for ourlang
|
|
||||||
"""
|
|
||||||
from typing import Dict, List, Tuple, Optional, Union
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from typing_extensions import Final
|
|
||||||
|
|
||||||
WEBASSEMBLY_BUILDIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
|
|
||||||
WEBASSEMBLY_BUILDIN_BYTES_OPS: Final = ('len', )
|
|
||||||
|
|
||||||
from .typing import (
|
|
||||||
TypeBase,
|
|
||||||
TypeNone,
|
|
||||||
TypeBool,
|
|
||||||
TypeUInt8, TypeUInt32, TypeUInt64,
|
|
||||||
TypeInt32, TypeInt64,
|
|
||||||
TypeFloat32, TypeFloat64,
|
|
||||||
TypeBytes,
|
|
||||||
TypeTuple, TypeTupleMember,
|
|
||||||
TypeStaticArray, TypeStaticArrayMember,
|
|
||||||
TypeStruct, TypeStructMember,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Expression:
|
|
||||||
"""
|
|
||||||
An expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('type', )
|
|
||||||
|
|
||||||
type: TypeBase
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeBase) -> None:
|
|
||||||
self.type = type_
|
|
||||||
|
|
||||||
class Constant(Expression):
|
|
||||||
"""
|
|
||||||
An constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
class ConstantUInt8(Constant):
|
|
||||||
"""
|
|
||||||
An UInt8 constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
value: int
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeUInt8, value: int) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class ConstantUInt32(Constant):
|
|
||||||
"""
|
|
||||||
An UInt32 constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
value: int
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeUInt32, value: int) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class ConstantUInt64(Constant):
|
|
||||||
"""
|
|
||||||
An UInt64 constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
value: int
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeUInt64, value: int) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class ConstantInt32(Constant):
|
|
||||||
"""
|
|
||||||
An Int32 constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
value: int
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeInt32, value: int) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class ConstantInt64(Constant):
|
|
||||||
"""
|
|
||||||
An Int64 constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
value: int
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeInt64, value: int) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class ConstantFloat32(Constant):
|
|
||||||
"""
|
|
||||||
An Float32 constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
value: float
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeFloat32, value: float) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class ConstantFloat64(Constant):
|
|
||||||
"""
|
|
||||||
An Float64 constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
value: float
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeFloat64, value: float) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class ConstantTuple(Constant):
|
|
||||||
"""
|
|
||||||
A Tuple constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
value: List[Constant]
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeTuple, value: List[Constant]) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class ConstantStaticArray(Constant):
|
|
||||||
"""
|
|
||||||
A StaticArray constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
value: List[Constant]
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeStaticArray, value: List[Constant]) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class VariableReference(Expression):
|
|
||||||
"""
|
|
||||||
An variable reference expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('name', )
|
|
||||||
|
|
||||||
name: str
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeBase, name: str) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
class UnaryOp(Expression):
|
|
||||||
"""
|
|
||||||
A unary operator expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('operator', 'right', )
|
|
||||||
|
|
||||||
operator: str
|
|
||||||
right: Expression
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeBase, operator: str, right: Expression) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
|
|
||||||
self.operator = operator
|
|
||||||
self.right = right
|
|
||||||
|
|
||||||
class BinaryOp(Expression):
|
|
||||||
"""
|
|
||||||
A binary operator expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('operator', 'left', 'right', )
|
|
||||||
|
|
||||||
operator: str
|
|
||||||
left: Expression
|
|
||||||
right: Expression
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeBase, operator: str, left: Expression, right: Expression) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
|
|
||||||
self.operator = operator
|
|
||||||
self.left = left
|
|
||||||
self.right = right
|
|
||||||
|
|
||||||
class FunctionCall(Expression):
|
|
||||||
"""
|
|
||||||
A function call expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('function', 'arguments', )
|
|
||||||
|
|
||||||
function: 'Function'
|
|
||||||
arguments: List[Expression]
|
|
||||||
|
|
||||||
def __init__(self, function: 'Function') -> None:
|
|
||||||
super().__init__(function.returns)
|
|
||||||
|
|
||||||
self.function = function
|
|
||||||
self.arguments = []
|
|
||||||
|
|
||||||
class AccessBytesIndex(Expression):
|
|
||||||
"""
|
|
||||||
Access a bytes index for reading
|
|
||||||
"""
|
|
||||||
__slots__ = ('varref', 'index', )
|
|
||||||
|
|
||||||
varref: VariableReference
|
|
||||||
index: Expression
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeBase, varref: VariableReference, index: Expression) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
|
|
||||||
self.varref = varref
|
|
||||||
self.index = index
|
|
||||||
|
|
||||||
class AccessStructMember(Expression):
|
|
||||||
"""
|
|
||||||
Access a struct member for reading of writing
|
|
||||||
"""
|
|
||||||
__slots__ = ('varref', 'member', )
|
|
||||||
|
|
||||||
varref: VariableReference
|
|
||||||
member: TypeStructMember
|
|
||||||
|
|
||||||
def __init__(self, varref: VariableReference, member: TypeStructMember) -> None:
|
|
||||||
super().__init__(member.type)
|
|
||||||
|
|
||||||
self.varref = varref
|
|
||||||
self.member = member
|
|
||||||
|
|
||||||
class AccessTupleMember(Expression):
|
|
||||||
"""
|
|
||||||
Access a tuple member for reading of writing
|
|
||||||
"""
|
|
||||||
__slots__ = ('varref', 'member', )
|
|
||||||
|
|
||||||
varref: VariableReference
|
|
||||||
member: TypeTupleMember
|
|
||||||
|
|
||||||
def __init__(self, varref: VariableReference, member: TypeTupleMember, ) -> None:
|
|
||||||
super().__init__(member.type)
|
|
||||||
|
|
||||||
self.varref = varref
|
|
||||||
self.member = member
|
|
||||||
|
|
||||||
class AccessStaticArrayMember(Expression):
|
|
||||||
"""
|
|
||||||
Access a tuple member for reading of writing
|
|
||||||
"""
|
|
||||||
__slots__ = ('varref', 'static_array', 'member', )
|
|
||||||
|
|
||||||
varref: Union['ModuleConstantReference', VariableReference]
|
|
||||||
static_array: TypeStaticArray
|
|
||||||
member: Union[Expression, TypeStaticArrayMember]
|
|
||||||
|
|
||||||
def __init__(self, varref: Union['ModuleConstantReference', VariableReference], static_array: TypeStaticArray, member: Union[TypeStaticArrayMember, Expression], ) -> None:
|
|
||||||
super().__init__(static_array.member_type)
|
|
||||||
|
|
||||||
self.varref = varref
|
|
||||||
self.static_array = static_array
|
|
||||||
self.member = member
|
|
||||||
|
|
||||||
class Fold(Expression):
|
|
||||||
"""
|
|
||||||
A (left or right) fold
|
|
||||||
"""
|
|
||||||
class Direction(enum.Enum):
|
|
||||||
"""
|
|
||||||
Which direction to fold in
|
|
||||||
"""
|
|
||||||
LEFT = 0
|
|
||||||
RIGHT = 1
|
|
||||||
|
|
||||||
dir: Direction
|
|
||||||
func: 'Function'
|
|
||||||
base: Expression
|
|
||||||
iter: Expression
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
type_: TypeBase,
|
|
||||||
dir_: Direction,
|
|
||||||
func: 'Function',
|
|
||||||
base: Expression,
|
|
||||||
iter_: Expression,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
|
|
||||||
self.dir = dir_
|
|
||||||
self.func = func
|
|
||||||
self.base = base
|
|
||||||
self.iter = iter_
|
|
||||||
|
|
||||||
class ModuleConstantReference(Expression):
|
|
||||||
"""
|
|
||||||
An reference to a module constant expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('definition', )
|
|
||||||
|
|
||||||
definition: 'ModuleConstantDef'
|
|
||||||
|
|
||||||
def __init__(self, type_: TypeBase, definition: 'ModuleConstantDef') -> None:
|
|
||||||
super().__init__(type_)
|
|
||||||
self.definition = definition
|
|
||||||
|
|
||||||
class Statement:
|
|
||||||
"""
|
|
||||||
A statement within a function
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
class StatementPass(Statement):
|
|
||||||
"""
|
|
||||||
A pass statement
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
class StatementReturn(Statement):
|
|
||||||
"""
|
|
||||||
A return statement within a function
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
def __init__(self, value: Expression) -> None:
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class StatementIf(Statement):
|
|
||||||
"""
|
|
||||||
An if statement within a function
|
|
||||||
"""
|
|
||||||
__slots__ = ('test', 'statements', 'else_statements', )
|
|
||||||
|
|
||||||
test: Expression
|
|
||||||
statements: List[Statement]
|
|
||||||
else_statements: List[Statement]
|
|
||||||
|
|
||||||
def __init__(self, test: Expression) -> None:
|
|
||||||
self.test = test
|
|
||||||
self.statements = []
|
|
||||||
self.else_statements = []
|
|
||||||
|
|
||||||
FunctionParam = Tuple[str, TypeBase]
|
|
||||||
|
|
||||||
class Function:
|
|
||||||
"""
|
|
||||||
A function processes input and produces output
|
|
||||||
"""
|
|
||||||
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns', 'posonlyargs', )
|
|
||||||
|
|
||||||
name: str
|
|
||||||
lineno: int
|
|
||||||
exported: bool
|
|
||||||
imported: bool
|
|
||||||
statements: List[Statement]
|
|
||||||
returns: TypeBase
|
|
||||||
posonlyargs: List[FunctionParam]
|
|
||||||
|
|
||||||
def __init__(self, name: str, lineno: int) -> None:
|
|
||||||
self.name = name
|
|
||||||
self.lineno = lineno
|
|
||||||
self.exported = False
|
|
||||||
self.imported = False
|
|
||||||
self.statements = []
|
|
||||||
self.returns = TypeNone()
|
|
||||||
self.posonlyargs = []
|
|
||||||
|
|
||||||
class StructConstructor(Function):
|
|
||||||
"""
|
|
||||||
The constructor method for a struct
|
|
||||||
|
|
||||||
A function will generated to instantiate a struct. The arguments
|
|
||||||
will be the defaults
|
|
||||||
"""
|
|
||||||
__slots__ = ('struct', )
|
|
||||||
|
|
||||||
struct: TypeStruct
|
|
||||||
|
|
||||||
def __init__(self, struct: TypeStruct) -> None:
|
|
||||||
super().__init__(f'@{struct.name}@__init___@', -1)
|
|
||||||
|
|
||||||
self.returns = struct
|
|
||||||
|
|
||||||
for mem in struct.members:
|
|
||||||
self.posonlyargs.append((mem.name, mem.type, ))
|
|
||||||
|
|
||||||
self.struct = struct
|
|
||||||
|
|
||||||
class TupleConstructor(Function):
|
|
||||||
"""
|
|
||||||
The constructor method for a tuple
|
|
||||||
"""
|
|
||||||
__slots__ = ('tuple', )
|
|
||||||
|
|
||||||
tuple: TypeTuple
|
|
||||||
|
|
||||||
def __init__(self, tuple_: TypeTuple) -> None:
|
|
||||||
name = tuple_.render_internal_name()
|
|
||||||
|
|
||||||
super().__init__(f'@{name}@__init___@', -1)
|
|
||||||
|
|
||||||
self.returns = tuple_
|
|
||||||
|
|
||||||
for mem in tuple_.members:
|
|
||||||
self.posonlyargs.append((f'arg{mem.idx}', mem.type, ))
|
|
||||||
|
|
||||||
self.tuple = tuple_
|
|
||||||
|
|
||||||
class ModuleConstantDef:
|
|
||||||
"""
|
|
||||||
A constant definition within a module
|
|
||||||
"""
|
|
||||||
__slots__ = ('name', 'lineno', 'type', 'constant', 'data_block', )
|
|
||||||
|
|
||||||
name: str
|
|
||||||
lineno: int
|
|
||||||
type: TypeBase
|
|
||||||
constant: Constant
|
|
||||||
data_block: Optional['ModuleDataBlock']
|
|
||||||
|
|
||||||
def __init__(self, name: str, lineno: int, type_: TypeBase, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None:
|
|
||||||
self.name = name
|
|
||||||
self.lineno = lineno
|
|
||||||
self.type = type_
|
|
||||||
self.constant = constant
|
|
||||||
self.data_block = data_block
|
|
||||||
|
|
||||||
class ModuleDataBlock:
|
|
||||||
"""
|
|
||||||
A single allocated block for module data
|
|
||||||
"""
|
|
||||||
__slots__ = ('data', 'address', )
|
|
||||||
|
|
||||||
data: List[Constant]
|
|
||||||
address: Optional[int]
|
|
||||||
|
|
||||||
def __init__(self, data: List[Constant]) -> None:
|
|
||||||
self.data = data
|
|
||||||
self.address = None
|
|
||||||
|
|
||||||
class ModuleData:
|
|
||||||
"""
|
|
||||||
The data for when a module is loaded into memory
|
|
||||||
"""
|
|
||||||
__slots__ = ('blocks', )
|
|
||||||
|
|
||||||
blocks: List[ModuleDataBlock]
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.blocks = []
|
|
||||||
|
|
||||||
class Module:
|
|
||||||
"""
|
|
||||||
A module is a file and consists of functions
|
|
||||||
"""
|
|
||||||
__slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',)
|
|
||||||
|
|
||||||
data: ModuleData
|
|
||||||
types: Dict[str, TypeBase]
|
|
||||||
structs: Dict[str, TypeStruct]
|
|
||||||
constant_defs: Dict[str, ModuleConstantDef]
|
|
||||||
functions: Dict[str, Function]
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.types = {
|
|
||||||
'None': TypeNone(),
|
|
||||||
'u8': TypeUInt8(),
|
|
||||||
'u32': TypeUInt32(),
|
|
||||||
'u64': TypeUInt64(),
|
|
||||||
'i32': TypeInt32(),
|
|
||||||
'i64': TypeInt64(),
|
|
||||||
'f32': TypeFloat32(),
|
|
||||||
'f64': TypeFloat64(),
|
|
||||||
'bytes': TypeBytes(),
|
|
||||||
}
|
|
||||||
self.data = ModuleData()
|
|
||||||
self.structs = {}
|
|
||||||
self.constant_defs = {}
|
|
||||||
self.functions = {}
|
|
||||||
844
phasm/parser.py
844
phasm/parser.py
@ -1,844 +0,0 @@
|
|||||||
"""
|
|
||||||
Parses the source code from the plain text into a syntax tree
|
|
||||||
"""
|
|
||||||
from typing import Any, Dict, NoReturn, Union
|
|
||||||
|
|
||||||
import ast
|
|
||||||
|
|
||||||
from .typing import (
|
|
||||||
TypeBase,
|
|
||||||
TypeUInt8,
|
|
||||||
TypeUInt32,
|
|
||||||
TypeUInt64,
|
|
||||||
TypeInt32,
|
|
||||||
TypeInt64,
|
|
||||||
TypeFloat32,
|
|
||||||
TypeFloat64,
|
|
||||||
TypeBytes,
|
|
||||||
TypeStruct,
|
|
||||||
TypeStructMember,
|
|
||||||
TypeTuple,
|
|
||||||
TypeTupleMember,
|
|
||||||
TypeStaticArray,
|
|
||||||
TypeStaticArrayMember,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import codestyle
|
|
||||||
from .exceptions import StaticError
|
|
||||||
from .ourlang import (
|
|
||||||
WEBASSEMBLY_BUILDIN_FLOAT_OPS,
|
|
||||||
|
|
||||||
Module, ModuleDataBlock,
|
|
||||||
Function,
|
|
||||||
|
|
||||||
Expression,
|
|
||||||
AccessBytesIndex, AccessStructMember, AccessTupleMember, AccessStaticArrayMember,
|
|
||||||
BinaryOp,
|
|
||||||
Constant,
|
|
||||||
ConstantFloat32, ConstantFloat64, ConstantInt32, ConstantInt64,
|
|
||||||
ConstantUInt8, ConstantUInt32, ConstantUInt64,
|
|
||||||
ConstantTuple, ConstantStaticArray,
|
|
||||||
|
|
||||||
FunctionCall,
|
|
||||||
StructConstructor, TupleConstructor,
|
|
||||||
UnaryOp, VariableReference,
|
|
||||||
|
|
||||||
Fold, ModuleConstantReference,
|
|
||||||
|
|
||||||
Statement,
|
|
||||||
StatementIf, StatementPass, StatementReturn,
|
|
||||||
|
|
||||||
ModuleConstantDef,
|
|
||||||
)
|
|
||||||
|
|
||||||
def phasm_parse(source: str) -> Module:
|
|
||||||
"""
|
|
||||||
Public method for parsing Phasm code into a Phasm Module
|
|
||||||
"""
|
|
||||||
res = ast.parse(source, '')
|
|
||||||
|
|
||||||
our_visitor = OurVisitor()
|
|
||||||
return our_visitor.visit_Module(res)
|
|
||||||
|
|
||||||
OurLocals = Dict[str, TypeBase]
|
|
||||||
|
|
||||||
class OurVisitor:
|
|
||||||
"""
|
|
||||||
Class to visit a Python syntax tree and create an ourlang syntax tree
|
|
||||||
|
|
||||||
We're (ab)using the Python AST parser to give us a leg up
|
|
||||||
|
|
||||||
At some point, we may deviate from Python syntax. If nothing else,
|
|
||||||
we probably won't keep up with the Python syntax changes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# pylint: disable=C0103,C0116,C0301,R0201,R0912
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def visit_Module(self, node: ast.Module) -> Module:
|
|
||||||
module = Module()
|
|
||||||
|
|
||||||
_not_implemented(not node.type_ignores, 'Module.type_ignores')
|
|
||||||
|
|
||||||
# Second pass for the types
|
|
||||||
|
|
||||||
for stmt in node.body:
|
|
||||||
res = self.pre_visit_Module_stmt(module, stmt)
|
|
||||||
|
|
||||||
if isinstance(res, ModuleConstantDef):
|
|
||||||
if res.name in module.constant_defs:
|
|
||||||
raise StaticError(
|
|
||||||
f'{res.name} already defined on line {module.constant_defs[res.name].lineno}'
|
|
||||||
)
|
|
||||||
|
|
||||||
module.constant_defs[res.name] = res
|
|
||||||
|
|
||||||
if isinstance(res, TypeStruct):
|
|
||||||
if res.name in module.structs:
|
|
||||||
raise StaticError(
|
|
||||||
f'{res.name} already defined on line {module.structs[res.name].lineno}'
|
|
||||||
)
|
|
||||||
|
|
||||||
module.structs[res.name] = res
|
|
||||||
constructor = StructConstructor(res)
|
|
||||||
module.functions[constructor.name] = constructor
|
|
||||||
|
|
||||||
if isinstance(res, Function):
|
|
||||||
if res.name in module.functions:
|
|
||||||
raise StaticError(
|
|
||||||
f'{res.name} already defined on line {module.functions[res.name].lineno}'
|
|
||||||
)
|
|
||||||
|
|
||||||
module.functions[res.name] = res
|
|
||||||
|
|
||||||
# Second pass for the function bodies
|
|
||||||
|
|
||||||
for stmt in node.body:
|
|
||||||
self.visit_Module_stmt(module, stmt)
|
|
||||||
|
|
||||||
return module
|
|
||||||
|
|
||||||
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, TypeStruct, ModuleConstantDef]:
|
|
||||||
if isinstance(node, ast.FunctionDef):
|
|
||||||
return self.pre_visit_Module_FunctionDef(module, node)
|
|
||||||
|
|
||||||
if isinstance(node, ast.ClassDef):
|
|
||||||
return self.pre_visit_Module_ClassDef(module, node)
|
|
||||||
|
|
||||||
if isinstance(node, ast.AnnAssign):
|
|
||||||
return self.pre_visit_Module_AnnAssign(module, node)
|
|
||||||
|
|
||||||
raise NotImplementedError(f'{node} on Module')
|
|
||||||
|
|
||||||
def pre_visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> Function:
|
|
||||||
function = Function(node.name, node.lineno)
|
|
||||||
|
|
||||||
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
|
|
||||||
|
|
||||||
for arg in node.args.args:
|
|
||||||
if not arg.annotation:
|
|
||||||
_raise_static_error(node, 'Type is required')
|
|
||||||
|
|
||||||
function.posonlyargs.append((
|
|
||||||
arg.arg,
|
|
||||||
self.visit_type(module, arg.annotation),
|
|
||||||
))
|
|
||||||
|
|
||||||
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
|
|
||||||
_not_implemented(not node.args.kwonlyargs, 'FunctionDef.args.kwonlyargs')
|
|
||||||
_not_implemented(not node.args.kw_defaults, 'FunctionDef.args.kw_defaults')
|
|
||||||
_not_implemented(not node.args.kwarg, 'FunctionDef.args.kwarg')
|
|
||||||
_not_implemented(not node.args.defaults, 'FunctionDef.args.defaults')
|
|
||||||
|
|
||||||
# Do stmts at the end so we have the return value
|
|
||||||
|
|
||||||
for decorator in node.decorator_list:
|
|
||||||
if not isinstance(decorator, ast.Name):
|
|
||||||
_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 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)
|
|
||||||
|
|
||||||
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
|
|
||||||
|
|
||||||
return function
|
|
||||||
|
|
||||||
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> TypeStruct:
|
|
||||||
struct = TypeStruct(node.name, node.lineno)
|
|
||||||
|
|
||||||
_not_implemented(not node.bases, 'ClassDef.bases')
|
|
||||||
_not_implemented(not node.keywords, 'ClassDef.keywords')
|
|
||||||
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
|
|
||||||
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
for stmt in node.body:
|
|
||||||
if not isinstance(stmt, ast.AnnAssign):
|
|
||||||
raise NotImplementedError(f'Class with {stmt} nodes')
|
|
||||||
|
|
||||||
if not isinstance(stmt.target, ast.Name):
|
|
||||||
raise NotImplementedError('Class with default values')
|
|
||||||
|
|
||||||
if not stmt.value is None:
|
|
||||||
raise NotImplementedError('Class with default values')
|
|
||||||
|
|
||||||
if stmt.simple != 1:
|
|
||||||
raise NotImplementedError('Class with non-simple arguments')
|
|
||||||
|
|
||||||
member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset)
|
|
||||||
|
|
||||||
struct.members.append(member)
|
|
||||||
offset += member.type.alloc_size()
|
|
||||||
|
|
||||||
return struct
|
|
||||||
|
|
||||||
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
|
|
||||||
if not isinstance(node.target, ast.Name):
|
|
||||||
_raise_static_error(node, 'Must be name')
|
|
||||||
if not isinstance(node.target.ctx, ast.Store):
|
|
||||||
_raise_static_error(node, 'Must be load context')
|
|
||||||
|
|
||||||
exp_type = self.visit_type(module, node.annotation)
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeInt32):
|
|
||||||
if not isinstance(node.value, ast.Constant):
|
|
||||||
_raise_static_error(node, 'Must be constant')
|
|
||||||
|
|
||||||
constant = ModuleConstantDef(
|
|
||||||
node.target.id,
|
|
||||||
node.lineno,
|
|
||||||
exp_type,
|
|
||||||
self.visit_Module_Constant(module, exp_type, node.value),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
return constant
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeTuple):
|
|
||||||
if not isinstance(node.value, ast.Tuple):
|
|
||||||
_raise_static_error(node, 'Must be tuple')
|
|
||||||
|
|
||||||
if len(exp_type.members) != len(node.value.elts):
|
|
||||||
_raise_static_error(node, 'Invalid number of tuple values')
|
|
||||||
|
|
||||||
tuple_data = [
|
|
||||||
self.visit_Module_Constant(module, mem.type, arg_node)
|
|
||||||
for arg_node, mem in zip(node.value.elts, exp_type.members)
|
|
||||||
if isinstance(arg_node, ast.Constant)
|
|
||||||
]
|
|
||||||
if len(exp_type.members) != len(tuple_data):
|
|
||||||
_raise_static_error(node, 'Tuple arguments must be constants')
|
|
||||||
|
|
||||||
# Allocate the data
|
|
||||||
data_block = ModuleDataBlock(tuple_data)
|
|
||||||
module.data.blocks.append(data_block)
|
|
||||||
|
|
||||||
# Then return the constant as a pointer
|
|
||||||
return ModuleConstantDef(
|
|
||||||
node.target.id,
|
|
||||||
node.lineno,
|
|
||||||
exp_type,
|
|
||||||
ConstantTuple(exp_type, tuple_data),
|
|
||||||
data_block,
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeStaticArray):
|
|
||||||
if not isinstance(node.value, ast.Tuple):
|
|
||||||
_raise_static_error(node, 'Must be static array')
|
|
||||||
|
|
||||||
if len(exp_type.members) != len(node.value.elts):
|
|
||||||
_raise_static_error(node, 'Invalid number of static array values')
|
|
||||||
|
|
||||||
static_array_data = [
|
|
||||||
self.visit_Module_Constant(module, exp_type.member_type, arg_node)
|
|
||||||
for arg_node in node.value.elts
|
|
||||||
if isinstance(arg_node, ast.Constant)
|
|
||||||
]
|
|
||||||
if len(exp_type.members) != len(static_array_data):
|
|
||||||
_raise_static_error(node, 'Static array arguments must be constants')
|
|
||||||
|
|
||||||
# Allocate the data
|
|
||||||
data_block = ModuleDataBlock(static_array_data)
|
|
||||||
module.data.blocks.append(data_block)
|
|
||||||
|
|
||||||
# Then return the constant as a pointer
|
|
||||||
return ModuleConstantDef(
|
|
||||||
node.target.id,
|
|
||||||
node.lineno,
|
|
||||||
exp_type,
|
|
||||||
ConstantStaticArray(exp_type, static_array_data),
|
|
||||||
data_block,
|
|
||||||
)
|
|
||||||
|
|
||||||
raise NotImplementedError(f'{node} on Module AnnAssign')
|
|
||||||
|
|
||||||
def visit_Module_stmt(self, module: Module, node: ast.stmt) -> None:
|
|
||||||
if isinstance(node, ast.FunctionDef):
|
|
||||||
self.visit_Module_FunctionDef(module, node)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(node, ast.ClassDef):
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(node, ast.AnnAssign):
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(f'{node} on Module')
|
|
||||||
|
|
||||||
def visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> None:
|
|
||||||
function = module.functions[node.name]
|
|
||||||
|
|
||||||
our_locals = dict(function.posonlyargs)
|
|
||||||
|
|
||||||
for stmt in node.body:
|
|
||||||
function.statements.append(
|
|
||||||
self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt)
|
|
||||||
)
|
|
||||||
|
|
||||||
def visit_Module_FunctionDef_stmt(self, module: Module, function: Function, our_locals: OurLocals, node: ast.stmt) -> Statement:
|
|
||||||
if isinstance(node, ast.Return):
|
|
||||||
if node.value is None:
|
|
||||||
# TODO: Implement methods without return values
|
|
||||||
_raise_static_error(node, 'Return must have an argument')
|
|
||||||
|
|
||||||
return StatementReturn(
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, function.returns, node.value)
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(node, ast.If):
|
|
||||||
result = StatementIf(
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, function.returns, node.test)
|
|
||||||
)
|
|
||||||
|
|
||||||
for stmt in node.body:
|
|
||||||
result.statements.append(
|
|
||||||
self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt)
|
|
||||||
)
|
|
||||||
|
|
||||||
for stmt in node.orelse:
|
|
||||||
result.else_statements.append(
|
|
||||||
self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt)
|
|
||||||
)
|
|
||||||
|
|
||||||
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: TypeBase, node: ast.expr) -> Expression:
|
|
||||||
if isinstance(node, ast.BinOp):
|
|
||||||
if isinstance(node.op, ast.Add):
|
|
||||||
operator = '+'
|
|
||||||
elif isinstance(node.op, ast.Sub):
|
|
||||||
operator = '-'
|
|
||||||
elif isinstance(node.op, ast.Mult):
|
|
||||||
operator = '*'
|
|
||||||
elif isinstance(node.op, ast.LShift):
|
|
||||||
operator = '<<'
|
|
||||||
elif isinstance(node.op, ast.RShift):
|
|
||||||
operator = '>>'
|
|
||||||
elif isinstance(node.op, ast.BitOr):
|
|
||||||
operator = '|'
|
|
||||||
elif isinstance(node.op, ast.BitXor):
|
|
||||||
operator = '^'
|
|
||||||
elif isinstance(node.op, ast.BitAnd):
|
|
||||||
operator = '&'
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(f'Operator {node.op}')
|
|
||||||
|
|
||||||
# Assume the type doesn't change when descending into a binary operator
|
|
||||||
# e.g. you can do `"hello" * 3` with the code below (yet)
|
|
||||||
|
|
||||||
return BinaryOp(
|
|
||||||
exp_type,
|
|
||||||
operator,
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.left),
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.right),
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(node, ast.UnaryOp):
|
|
||||||
if isinstance(node.op, ast.UAdd):
|
|
||||||
operator = '+'
|
|
||||||
elif isinstance(node.op, ast.USub):
|
|
||||||
operator = '-'
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(f'Operator {node.op}')
|
|
||||||
|
|
||||||
return UnaryOp(
|
|
||||||
exp_type,
|
|
||||||
operator,
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.operand),
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(node, ast.Compare):
|
|
||||||
if 1 < len(node.ops):
|
|
||||||
raise NotImplementedError('Multiple operators')
|
|
||||||
|
|
||||||
if isinstance(node.ops[0], ast.Gt):
|
|
||||||
operator = '>'
|
|
||||||
elif isinstance(node.ops[0], ast.Eq):
|
|
||||||
operator = '=='
|
|
||||||
elif isinstance(node.ops[0], ast.Lt):
|
|
||||||
operator = '<'
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(f'Operator {node.ops}')
|
|
||||||
|
|
||||||
# Assume the type doesn't change when descending into a binary operator
|
|
||||||
# e.g. you can do `"hello" * 3` with the code below (yet)
|
|
||||||
|
|
||||||
return BinaryOp(
|
|
||||||
exp_type,
|
|
||||||
operator,
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.left),
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.comparators[0]),
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(node, ast.Call):
|
|
||||||
return self.visit_Module_FunctionDef_Call(module, function, our_locals, exp_type, node)
|
|
||||||
|
|
||||||
if isinstance(node, ast.Constant):
|
|
||||||
return self.visit_Module_Constant(
|
|
||||||
module, exp_type, node,
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(node, ast.Attribute):
|
|
||||||
return self.visit_Module_FunctionDef_Attribute(
|
|
||||||
module, function, our_locals, exp_type, node,
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(node, ast.Subscript):
|
|
||||||
return self.visit_Module_FunctionDef_Subscript(
|
|
||||||
module, function, our_locals, exp_type, node,
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(node, ast.Name):
|
|
||||||
if not isinstance(node.ctx, ast.Load):
|
|
||||||
_raise_static_error(node, 'Must be load context')
|
|
||||||
|
|
||||||
if node.id in our_locals:
|
|
||||||
act_type = our_locals[node.id]
|
|
||||||
if exp_type != act_type:
|
|
||||||
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.id} is actually {codestyle.type_(act_type)}')
|
|
||||||
|
|
||||||
return VariableReference(act_type, node.id)
|
|
||||||
|
|
||||||
if node.id in module.constant_defs:
|
|
||||||
cdef = module.constant_defs[node.id]
|
|
||||||
if exp_type != cdef.type:
|
|
||||||
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.id} is actually {codestyle.type_(cdef.type)}')
|
|
||||||
|
|
||||||
return ModuleConstantReference(exp_type, cdef)
|
|
||||||
|
|
||||||
_raise_static_error(node, f'Undefined variable {node.id}')
|
|
||||||
|
|
||||||
if isinstance(node, ast.Tuple):
|
|
||||||
if not isinstance(node.ctx, ast.Load):
|
|
||||||
_raise_static_error(node, 'Must be load context')
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeTuple):
|
|
||||||
if len(exp_type.members) != len(node.elts):
|
|
||||||
_raise_static_error(node, f'Expression is expecting a tuple of size {len(exp_type.members)}, but {len(node.elts)} are given')
|
|
||||||
|
|
||||||
tuple_constructor = TupleConstructor(exp_type)
|
|
||||||
|
|
||||||
func = module.functions[tuple_constructor.name]
|
|
||||||
|
|
||||||
result = FunctionCall(func)
|
|
||||||
result.arguments = [
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, mem.type, arg_node)
|
|
||||||
for arg_node, mem in zip(node.elts, exp_type.members)
|
|
||||||
]
|
|
||||||
return result
|
|
||||||
|
|
||||||
_raise_static_error(node, f'Expression is expecting a {codestyle.type_(exp_type)}, not a tuple')
|
|
||||||
|
|
||||||
raise NotImplementedError(f'{node} as expr in FunctionDef')
|
|
||||||
|
|
||||||
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Call) -> Union[Fold, FunctionCall, UnaryOp]:
|
|
||||||
if node.keywords:
|
|
||||||
_raise_static_error(node, 'Keyword calling not supported') # Yet?
|
|
||||||
|
|
||||||
if not isinstance(node.func, ast.Name):
|
|
||||||
raise NotImplementedError(f'Calling methods that are not a name {node.func}')
|
|
||||||
if not isinstance(node.func.ctx, ast.Load):
|
|
||||||
_raise_static_error(node, 'Must be load context')
|
|
||||||
|
|
||||||
if node.func.id in module.structs:
|
|
||||||
struct = module.structs[node.func.id]
|
|
||||||
struct_constructor = StructConstructor(struct)
|
|
||||||
|
|
||||||
func = module.functions[struct_constructor.name]
|
|
||||||
elif node.func.id in WEBASSEMBLY_BUILDIN_FLOAT_OPS:
|
|
||||||
if not isinstance(exp_type, (TypeFloat32, TypeFloat64, )):
|
|
||||||
_raise_static_error(node, f'Cannot make {node.func.id} result in {codestyle.type_(exp_type)}')
|
|
||||||
|
|
||||||
if 1 != len(node.args):
|
|
||||||
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
|
|
||||||
|
|
||||||
return UnaryOp(
|
|
||||||
exp_type,
|
|
||||||
'sqrt',
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.args[0]),
|
|
||||||
)
|
|
||||||
elif node.func.id == 'u32':
|
|
||||||
if not isinstance(exp_type, TypeUInt32):
|
|
||||||
_raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}')
|
|
||||||
|
|
||||||
if 1 != len(node.args):
|
|
||||||
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
|
|
||||||
|
|
||||||
# FIXME: This is a stub, proper casting is todo
|
|
||||||
|
|
||||||
return UnaryOp(
|
|
||||||
exp_type,
|
|
||||||
'cast',
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, module.types['u8'], node.args[0]),
|
|
||||||
)
|
|
||||||
elif node.func.id == 'len':
|
|
||||||
if not isinstance(exp_type, TypeInt32):
|
|
||||||
_raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}')
|
|
||||||
|
|
||||||
if 1 != len(node.args):
|
|
||||||
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
|
|
||||||
|
|
||||||
return UnaryOp(
|
|
||||||
exp_type,
|
|
||||||
'len',
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, module.types['bytes'], node.args[0]),
|
|
||||||
)
|
|
||||||
elif node.func.id == 'foldl':
|
|
||||||
# TODO: This should a much more generic function!
|
|
||||||
# For development purposes, we're assuming you're doing a foldl(Callable[[u8, u8], u8], u8, bytes)
|
|
||||||
# In the future, we should probably infer the type of the second argument,
|
|
||||||
# and use it as expected types for the other u8s and the Iterable[u8] (i.e. bytes)
|
|
||||||
|
|
||||||
if 3 != len(node.args):
|
|
||||||
_raise_static_error(node, f'Function {node.func.id} requires 3 arguments but {len(node.args)} are given')
|
|
||||||
|
|
||||||
# TODO: This is not generic
|
|
||||||
subnode = node.args[0]
|
|
||||||
if not isinstance(subnode, ast.Name):
|
|
||||||
raise NotImplementedError(f'Calling methods that are not a name {subnode}')
|
|
||||||
if not isinstance(subnode.ctx, ast.Load):
|
|
||||||
_raise_static_error(subnode, 'Must be load context')
|
|
||||||
if subnode.id not in module.functions:
|
|
||||||
_raise_static_error(subnode, 'Reference to undefined function')
|
|
||||||
func = module.functions[subnode.id]
|
|
||||||
if 2 != len(func.posonlyargs):
|
|
||||||
_raise_static_error(node, f'Function {node.func.id} requires a function with 2 arguments but a function with {len(func.posonlyargs)} args is given')
|
|
||||||
|
|
||||||
if exp_type.__class__ != func.returns.__class__:
|
|
||||||
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {func.name} actually returns {codestyle.type_(func.returns)}')
|
|
||||||
|
|
||||||
if func.returns.__class__ != func.posonlyargs[0][1].__class__:
|
|
||||||
_raise_static_error(node, f'Expected a foldable function, {func.name} returns a {codestyle.type_(func.returns)} but expects a {codestyle.type_(func.posonlyargs[0][1])}')
|
|
||||||
|
|
||||||
if module.types['u8'].__class__ != func.posonlyargs[1][1].__class__:
|
|
||||||
_raise_static_error(node, 'Only folding over bytes (u8) is supported at this time')
|
|
||||||
|
|
||||||
return Fold(
|
|
||||||
exp_type,
|
|
||||||
Fold.Direction.LEFT,
|
|
||||||
func,
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, func.returns, node.args[1]),
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, module.types['bytes'], node.args[2]),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if node.func.id not in module.functions:
|
|
||||||
_raise_static_error(node, 'Call to undefined function')
|
|
||||||
|
|
||||||
func = module.functions[node.func.id]
|
|
||||||
|
|
||||||
if func.returns != exp_type:
|
|
||||||
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {func.name} actually returns {codestyle.type_(func.returns)}')
|
|
||||||
|
|
||||||
if len(func.posonlyargs) != len(node.args):
|
|
||||||
_raise_static_error(node, f'Function {node.func.id} requires {len(func.posonlyargs)} arguments but {len(node.args)} are given')
|
|
||||||
|
|
||||||
result = FunctionCall(func)
|
|
||||||
result.arguments.extend(
|
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_type, arg_expr)
|
|
||||||
for arg_expr, (_, arg_type) in zip(node.args, func.posonlyargs)
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Attribute) -> Expression:
|
|
||||||
del module
|
|
||||||
del function
|
|
||||||
|
|
||||||
if not isinstance(node.value, ast.Name):
|
|
||||||
_raise_static_error(node, 'Must reference a name')
|
|
||||||
|
|
||||||
if not isinstance(node.ctx, ast.Load):
|
|
||||||
_raise_static_error(node, 'Must be load context')
|
|
||||||
|
|
||||||
if not node.value.id in our_locals:
|
|
||||||
_raise_static_error(node, f'Undefined variable {node.value.id}')
|
|
||||||
|
|
||||||
node_typ = our_locals[node.value.id]
|
|
||||||
if not isinstance(node_typ, TypeStruct):
|
|
||||||
_raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
|
|
||||||
|
|
||||||
member = node_typ.get_member(node.attr)
|
|
||||||
if member is None:
|
|
||||||
_raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}')
|
|
||||||
|
|
||||||
if exp_type != member.type:
|
|
||||||
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}.{member.name} is actually {codestyle.type_(member.type)}')
|
|
||||||
|
|
||||||
return AccessStructMember(
|
|
||||||
VariableReference(node_typ, node.value.id),
|
|
||||||
member,
|
|
||||||
)
|
|
||||||
|
|
||||||
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Subscript) -> Expression:
|
|
||||||
if not isinstance(node.value, ast.Name):
|
|
||||||
_raise_static_error(node, 'Must reference a name')
|
|
||||||
|
|
||||||
if not isinstance(node.slice, ast.Index):
|
|
||||||
_raise_static_error(node, 'Must subscript using an index')
|
|
||||||
|
|
||||||
if not isinstance(node.ctx, ast.Load):
|
|
||||||
_raise_static_error(node, 'Must be load context')
|
|
||||||
|
|
||||||
varref: Union[ModuleConstantReference, VariableReference]
|
|
||||||
if node.value.id in our_locals:
|
|
||||||
node_typ = our_locals[node.value.id]
|
|
||||||
varref = VariableReference(node_typ, node.value.id)
|
|
||||||
elif node.value.id in module.constant_defs:
|
|
||||||
constant_def = module.constant_defs[node.value.id]
|
|
||||||
node_typ = constant_def.type
|
|
||||||
varref = ModuleConstantReference(node_typ, constant_def)
|
|
||||||
else:
|
|
||||||
_raise_static_error(node, f'Undefined variable {node.value.id}')
|
|
||||||
|
|
||||||
slice_expr = self.visit_Module_FunctionDef_expr(
|
|
||||||
module, function, our_locals, module.types['u32'], node.slice.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(node_typ, TypeBytes):
|
|
||||||
t_u8 = module.types['u8']
|
|
||||||
if exp_type != t_u8:
|
|
||||||
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}[{codestyle.expression(slice_expr)}] is actually {codestyle.type_(t_u8)}')
|
|
||||||
|
|
||||||
if isinstance(varref, ModuleConstantReference):
|
|
||||||
raise NotImplementedError(f'{node} from module constant')
|
|
||||||
|
|
||||||
return AccessBytesIndex(
|
|
||||||
t_u8,
|
|
||||||
varref,
|
|
||||||
slice_expr,
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(node_typ, TypeTuple):
|
|
||||||
if not isinstance(slice_expr, ConstantUInt32):
|
|
||||||
_raise_static_error(node, 'Must subscript using a constant index')
|
|
||||||
|
|
||||||
idx = slice_expr.value
|
|
||||||
|
|
||||||
if len(node_typ.members) <= idx:
|
|
||||||
_raise_static_error(node, f'Index {idx} out of bounds for tuple {node.value.id}')
|
|
||||||
|
|
||||||
tuple_member = node_typ.members[idx]
|
|
||||||
if exp_type != tuple_member.type:
|
|
||||||
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}[{idx}] is actually {codestyle.type_(tuple_member.type)}')
|
|
||||||
|
|
||||||
if isinstance(varref, ModuleConstantReference):
|
|
||||||
raise NotImplementedError(f'{node} from module constant')
|
|
||||||
|
|
||||||
return AccessTupleMember(
|
|
||||||
varref,
|
|
||||||
tuple_member,
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(node_typ, TypeStaticArray):
|
|
||||||
if exp_type != node_typ.member_type:
|
|
||||||
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}[{idx}] is actually {codestyle.type_(node_typ.member_type)}')
|
|
||||||
|
|
||||||
if not isinstance(slice_expr, ConstantInt32):
|
|
||||||
return AccessStaticArrayMember(
|
|
||||||
varref,
|
|
||||||
node_typ,
|
|
||||||
slice_expr,
|
|
||||||
)
|
|
||||||
|
|
||||||
idx = slice_expr.value
|
|
||||||
|
|
||||||
if len(node_typ.members) <= idx:
|
|
||||||
_raise_static_error(node, f'Index {idx} out of bounds for static array {node.value.id}')
|
|
||||||
|
|
||||||
static_array_member = node_typ.members[idx]
|
|
||||||
|
|
||||||
return AccessStaticArrayMember(
|
|
||||||
varref,
|
|
||||||
node_typ,
|
|
||||||
static_array_member,
|
|
||||||
)
|
|
||||||
|
|
||||||
_raise_static_error(node, f'Cannot take index of {node_typ} {node.value.id}')
|
|
||||||
|
|
||||||
def visit_Module_Constant(self, module: Module, exp_type: TypeBase, node: ast.Constant) -> Constant:
|
|
||||||
del module
|
|
||||||
|
|
||||||
_not_implemented(node.kind is None, 'Constant.kind')
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeUInt8):
|
|
||||||
if not isinstance(node.value, int):
|
|
||||||
_raise_static_error(node, 'Expected integer value')
|
|
||||||
|
|
||||||
if node.value < 0 or node.value > 255:
|
|
||||||
_raise_static_error(node, f'Integer value out of range; expected 0..255, actual {node.value}')
|
|
||||||
|
|
||||||
return ConstantUInt8(exp_type, node.value)
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeUInt32):
|
|
||||||
if not isinstance(node.value, int):
|
|
||||||
_raise_static_error(node, 'Expected integer value')
|
|
||||||
|
|
||||||
if node.value < 0 or node.value > 4294967295:
|
|
||||||
_raise_static_error(node, 'Integer value out of range')
|
|
||||||
|
|
||||||
return ConstantUInt32(exp_type, node.value)
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeUInt64):
|
|
||||||
if not isinstance(node.value, int):
|
|
||||||
_raise_static_error(node, 'Expected integer value')
|
|
||||||
|
|
||||||
if node.value < 0 or node.value > 18446744073709551615:
|
|
||||||
_raise_static_error(node, 'Integer value out of range')
|
|
||||||
|
|
||||||
return ConstantUInt64(exp_type, node.value)
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeInt32):
|
|
||||||
if not isinstance(node.value, int):
|
|
||||||
_raise_static_error(node, 'Expected integer value')
|
|
||||||
|
|
||||||
if node.value < -2147483648 or node.value > 2147483647:
|
|
||||||
_raise_static_error(node, 'Integer value out of range')
|
|
||||||
|
|
||||||
return ConstantInt32(exp_type, node.value)
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeInt64):
|
|
||||||
if not isinstance(node.value, int):
|
|
||||||
_raise_static_error(node, 'Expected integer value')
|
|
||||||
|
|
||||||
if node.value < -9223372036854775808 or node.value > 9223372036854775807:
|
|
||||||
_raise_static_error(node, 'Integer value out of range')
|
|
||||||
|
|
||||||
return ConstantInt64(exp_type, node.value)
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeFloat32):
|
|
||||||
if not isinstance(node.value, (float, int, )):
|
|
||||||
_raise_static_error(node, 'Expected float value')
|
|
||||||
|
|
||||||
# FIXME: Range check
|
|
||||||
|
|
||||||
return ConstantFloat32(exp_type, node.value)
|
|
||||||
|
|
||||||
if isinstance(exp_type, TypeFloat64):
|
|
||||||
if not isinstance(node.value, (float, int, )):
|
|
||||||
_raise_static_error(node, 'Expected float value')
|
|
||||||
|
|
||||||
# FIXME: Range check
|
|
||||||
|
|
||||||
return ConstantFloat64(exp_type, node.value)
|
|
||||||
|
|
||||||
raise NotImplementedError(f'{node} as const for type {exp_type}')
|
|
||||||
|
|
||||||
def visit_type(self, module: Module, node: ast.expr) -> TypeBase:
|
|
||||||
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')
|
|
||||||
|
|
||||||
if node.id in module.types:
|
|
||||||
return module.types[node.id]
|
|
||||||
|
|
||||||
if node.id in module.structs:
|
|
||||||
return module.structs[node.id]
|
|
||||||
|
|
||||||
_raise_static_error(node, f'Unrecognized type {node.id}')
|
|
||||||
|
|
||||||
if isinstance(node, ast.Subscript):
|
|
||||||
if not isinstance(node.value, ast.Name):
|
|
||||||
_raise_static_error(node, 'Must be name')
|
|
||||||
if not isinstance(node.slice, ast.Index):
|
|
||||||
_raise_static_error(node, 'Must subscript using an index')
|
|
||||||
if not isinstance(node.slice.value, ast.Constant):
|
|
||||||
_raise_static_error(node, 'Must subscript using a constant index')
|
|
||||||
if not isinstance(node.slice.value.value, int):
|
|
||||||
_raise_static_error(node, 'Must subscript using a constant integer index')
|
|
||||||
if not isinstance(node.ctx, ast.Load):
|
|
||||||
_raise_static_error(node, 'Must be load context')
|
|
||||||
|
|
||||||
if node.value.id in module.types:
|
|
||||||
member_type = module.types[node.value.id]
|
|
||||||
else:
|
|
||||||
_raise_static_error(node, f'Unrecognized type {node.value.id}')
|
|
||||||
|
|
||||||
type_static_array = TypeStaticArray(member_type)
|
|
||||||
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
for idx in range(node.slice.value.value):
|
|
||||||
static_array_member = TypeStaticArrayMember(idx, offset)
|
|
||||||
|
|
||||||
type_static_array.members.append(static_array_member)
|
|
||||||
offset += member_type.alloc_size()
|
|
||||||
|
|
||||||
key = f'{node.value.id}[{node.slice.value.value}]'
|
|
||||||
|
|
||||||
if key not in module.types:
|
|
||||||
module.types[key] = type_static_array
|
|
||||||
|
|
||||||
return module.types[key]
|
|
||||||
|
|
||||||
if isinstance(node, ast.Tuple):
|
|
||||||
if not isinstance(node.ctx, ast.Load):
|
|
||||||
_raise_static_error(node, 'Must be load context')
|
|
||||||
|
|
||||||
type_tuple = TypeTuple()
|
|
||||||
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
for idx, elt in enumerate(node.elts):
|
|
||||||
tuple_member = TypeTupleMember(idx, self.visit_type(module, elt), offset)
|
|
||||||
|
|
||||||
type_tuple.members.append(tuple_member)
|
|
||||||
offset += tuple_member.type.alloc_size()
|
|
||||||
|
|
||||||
key = type_tuple.render_internal_name()
|
|
||||||
|
|
||||||
if key not in module.types:
|
|
||||||
module.types[key] = type_tuple
|
|
||||||
constructor = TupleConstructor(type_tuple)
|
|
||||||
module.functions[constructor.name] = constructor
|
|
||||||
|
|
||||||
return module.types[key]
|
|
||||||
|
|
||||||
raise NotImplementedError(f'{node} as type')
|
|
||||||
|
|
||||||
def _not_implemented(check: Any, msg: str) -> None:
|
|
||||||
if not check:
|
|
||||||
raise NotImplementedError(msg)
|
|
||||||
|
|
||||||
def _raise_static_error(node: Union[ast.mod, ast.stmt, ast.expr], msg: str) -> NoReturn:
|
|
||||||
raise StaticError(
|
|
||||||
f'Static error on line {node.lineno}: {msg}'
|
|
||||||
)
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
"""
|
|
||||||
stdlib: Memory allocation
|
|
||||||
"""
|
|
||||||
from phasm.wasmgenerator import Generator, VarType_i32 as i32, func_wrapper
|
|
||||||
|
|
||||||
IDENTIFIER = 0xA1C0
|
|
||||||
|
|
||||||
ADR_IDENTIFIER = 0
|
|
||||||
ADR_RESERVED0 = ADR_IDENTIFIER + 4
|
|
||||||
ADR_FREE_BLOCK_PTR = ADR_RESERVED0 + 4
|
|
||||||
ADR_UNALLOC_PTR = ADR_FREE_BLOCK_PTR + 4
|
|
||||||
|
|
||||||
UNALLOC_PTR = ADR_UNALLOC_PTR + 4
|
|
||||||
|
|
||||||
# For memory initialization see phasm.compiler.module_data
|
|
||||||
|
|
||||||
@func_wrapper(exported=False)
|
|
||||||
def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
|
|
||||||
# Find out if we've freed any blocks at all so far
|
|
||||||
g.i32.const(ADR_FREE_BLOCK_PTR)
|
|
||||||
g.i32.load()
|
|
||||||
g.i32.const(0)
|
|
||||||
g.i32.eq()
|
|
||||||
|
|
||||||
with g.if_():
|
|
||||||
g.i32.const(0)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
del alloc_size # TODO
|
|
||||||
g.unreachable()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __alloc__(g: Generator, alloc_size: i32) -> i32:
|
|
||||||
result = i32('result')
|
|
||||||
|
|
||||||
# Check if the memory is already initialized
|
|
||||||
g.i32.const(ADR_IDENTIFIER)
|
|
||||||
g.i32.load()
|
|
||||||
g.i32.const(IDENTIFIER)
|
|
||||||
g.i32.ne()
|
|
||||||
with g.if_():
|
|
||||||
# Not yet initialized, or memory corruption
|
|
||||||
g.unreachable()
|
|
||||||
|
|
||||||
# Try to claim a free block
|
|
||||||
g.local.get(alloc_size)
|
|
||||||
g.call(__find_free_block__)
|
|
||||||
g.local.set(result)
|
|
||||||
|
|
||||||
# Check if there was a free block
|
|
||||||
g.local.get(result)
|
|
||||||
g.i32.const(0)
|
|
||||||
g.i32.eq()
|
|
||||||
with g.if_():
|
|
||||||
# No free blocks, increase allocated memory usage
|
|
||||||
|
|
||||||
# Put the address on the stack in advance so we can store to it later
|
|
||||||
g.i32.const(ADR_UNALLOC_PTR)
|
|
||||||
|
|
||||||
# Get the current unalloc pointer value
|
|
||||||
g.i32.const(ADR_UNALLOC_PTR)
|
|
||||||
g.i32.load()
|
|
||||||
g.local.tee(result)
|
|
||||||
|
|
||||||
# Calculate new unalloc pointer value
|
|
||||||
g.i32.const(4) # Header size
|
|
||||||
g.i32.add()
|
|
||||||
g.local.get(alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
|
|
||||||
# Store new unalloc pointer value (address was set on stack in advance)
|
|
||||||
g.i32.store()
|
|
||||||
|
|
||||||
# Store block size in the header
|
|
||||||
g.local.get(result)
|
|
||||||
g.local.get(alloc_size)
|
|
||||||
g.i32.store()
|
|
||||||
|
|
||||||
# Return address of the allocated memory, skipping the allocator header
|
|
||||||
g.local.get(result)
|
|
||||||
g.i32.const(4) # Header size
|
|
||||||
g.i32.add()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
"""
|
|
||||||
stdlib: Standard types that are not wasm primitives
|
|
||||||
"""
|
|
||||||
from phasm.wasmgenerator import Generator, VarType_i32 as i32, func_wrapper
|
|
||||||
|
|
||||||
from phasm.stdlib import alloc
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __alloc_bytes__(g: Generator, length: i32) -> i32:
|
|
||||||
"""
|
|
||||||
Allocates room for a bytes instance, but does not write
|
|
||||||
anything to the allocated memory
|
|
||||||
"""
|
|
||||||
result = i32('result')
|
|
||||||
|
|
||||||
# Allocate the length of the byte string, as well
|
|
||||||
# as 4 bytes for a length header
|
|
||||||
g.local.get(length)
|
|
||||||
g.i32.const(4)
|
|
||||||
g.i32.add()
|
|
||||||
g.call(alloc.__alloc__)
|
|
||||||
|
|
||||||
# Store the address in a variable so we can use it up
|
|
||||||
# for writing the length header
|
|
||||||
g.local.tee(result)
|
|
||||||
g.local.get(length)
|
|
||||||
g.i32.store()
|
|
||||||
|
|
||||||
# Get the address back from the variable as return
|
|
||||||
g.local.get(result)
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32:
|
|
||||||
"""
|
|
||||||
Returns an index from a bytes value
|
|
||||||
|
|
||||||
If ofs is more than the length of the bytes, this
|
|
||||||
function returns 0, following the 'no undefined behaviour'
|
|
||||||
philosophy.
|
|
||||||
|
|
||||||
adr i32 The pointer for the allocated bytes
|
|
||||||
ofs i32 The offset within the allocated bytes
|
|
||||||
"""
|
|
||||||
g.local.get(ofs)
|
|
||||||
g.local.get(adr)
|
|
||||||
g.i32.load()
|
|
||||||
g.i32.lt_u()
|
|
||||||
|
|
||||||
with g.if_():
|
|
||||||
# The offset is less than the length
|
|
||||||
|
|
||||||
g.local.get(adr)
|
|
||||||
g.i32.const(4) # Bytes header
|
|
||||||
g.i32.add()
|
|
||||||
g.local.get(ofs)
|
|
||||||
g.i32.add()
|
|
||||||
g.i32.load8_u()
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
# The offset is outside the allocated bytes
|
|
||||||
g.i32.const(0)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
202
phasm/typing.py
202
phasm/typing.py
@ -1,202 +0,0 @@
|
|||||||
"""
|
|
||||||
The phasm type system
|
|
||||||
"""
|
|
||||||
from typing import Optional, List
|
|
||||||
|
|
||||||
class TypeBase:
|
|
||||||
"""
|
|
||||||
TypeBase base class
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
"""
|
|
||||||
When allocating this type in memory, how many bytes do we need to reserve?
|
|
||||||
"""
|
|
||||||
raise NotImplementedError(self, 'alloc_size')
|
|
||||||
|
|
||||||
class TypeNone(TypeBase):
|
|
||||||
"""
|
|
||||||
The None (or Void) type
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
class TypeBool(TypeBase):
|
|
||||||
"""
|
|
||||||
The boolean type
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
class TypeUInt8(TypeBase):
|
|
||||||
"""
|
|
||||||
The Integer type, unsigned and 8 bits wide
|
|
||||||
|
|
||||||
Note that under the hood we need to use i32 to represent
|
|
||||||
these values in expressions. So we need to add some operations
|
|
||||||
to make sure the math checks out.
|
|
||||||
|
|
||||||
So while this does save bytes in memory, it may not actually
|
|
||||||
speed up or improve your code.
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
return 4 # Int32 under the hood
|
|
||||||
|
|
||||||
class TypeUInt32(TypeBase):
|
|
||||||
"""
|
|
||||||
The Integer type, unsigned and 32 bits wide
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
return 4
|
|
||||||
|
|
||||||
class TypeUInt64(TypeBase):
|
|
||||||
"""
|
|
||||||
The Integer type, unsigned and 64 bits wide
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
return 8
|
|
||||||
|
|
||||||
class TypeInt32(TypeBase):
|
|
||||||
"""
|
|
||||||
The Integer type, signed and 32 bits wide
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
return 4
|
|
||||||
|
|
||||||
class TypeInt64(TypeBase):
|
|
||||||
"""
|
|
||||||
The Integer type, signed and 64 bits wide
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
return 8
|
|
||||||
|
|
||||||
class TypeFloat32(TypeBase):
|
|
||||||
"""
|
|
||||||
The Float type, 32 bits wide
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
return 4
|
|
||||||
|
|
||||||
class TypeFloat64(TypeBase):
|
|
||||||
"""
|
|
||||||
The Float type, 64 bits wide
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
return 8
|
|
||||||
|
|
||||||
class TypeBytes(TypeBase):
|
|
||||||
"""
|
|
||||||
The bytes type
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
class TypeTupleMember:
|
|
||||||
"""
|
|
||||||
Represents a tuple member
|
|
||||||
"""
|
|
||||||
def __init__(self, idx: int, type_: TypeBase, offset: int) -> None:
|
|
||||||
self.idx = idx
|
|
||||||
self.type = type_
|
|
||||||
self.offset = offset
|
|
||||||
|
|
||||||
class TypeTuple(TypeBase):
|
|
||||||
"""
|
|
||||||
The tuple type
|
|
||||||
"""
|
|
||||||
__slots__ = ('members', )
|
|
||||||
|
|
||||||
members: List[TypeTupleMember]
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.members = []
|
|
||||||
|
|
||||||
def render_internal_name(self) -> str:
|
|
||||||
"""
|
|
||||||
Generates an internal name for this tuple
|
|
||||||
"""
|
|
||||||
mems = '@'.join('?' for x in self.members) # FIXME: Should not be a questionmark
|
|
||||||
assert ' ' not in mems, 'Not implement yet: subtuples'
|
|
||||||
return f'tuple@{mems}'
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
return sum(
|
|
||||||
x.type.alloc_size()
|
|
||||||
for x in self.members
|
|
||||||
)
|
|
||||||
|
|
||||||
class TypeStaticArrayMember:
|
|
||||||
"""
|
|
||||||
Represents a static array member
|
|
||||||
"""
|
|
||||||
def __init__(self, idx: int, offset: int) -> None:
|
|
||||||
self.idx = idx
|
|
||||||
self.offset = offset
|
|
||||||
|
|
||||||
class TypeStaticArray(TypeBase):
|
|
||||||
"""
|
|
||||||
The static array type
|
|
||||||
"""
|
|
||||||
__slots__ = ('member_type', 'members', )
|
|
||||||
|
|
||||||
member_type: TypeBase
|
|
||||||
members: List[TypeStaticArrayMember]
|
|
||||||
|
|
||||||
def __init__(self, member_type: TypeBase) -> None:
|
|
||||||
self.member_type = member_type
|
|
||||||
self.members = []
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
return self.member_type.alloc_size() * len(self.members)
|
|
||||||
|
|
||||||
class TypeStructMember:
|
|
||||||
"""
|
|
||||||
Represents a struct member
|
|
||||||
"""
|
|
||||||
def __init__(self, name: str, type_: TypeBase, offset: int) -> None:
|
|
||||||
self.name = name
|
|
||||||
self.type = type_
|
|
||||||
self.offset = offset
|
|
||||||
|
|
||||||
class TypeStruct(TypeBase):
|
|
||||||
"""
|
|
||||||
A struct has named properties
|
|
||||||
"""
|
|
||||||
__slots__ = ('name', 'lineno', 'members', )
|
|
||||||
|
|
||||||
name: str
|
|
||||||
lineno: int
|
|
||||||
members: List[TypeStructMember]
|
|
||||||
|
|
||||||
def __init__(self, name: str, lineno: int) -> None:
|
|
||||||
self.name = name
|
|
||||||
self.lineno = lineno
|
|
||||||
self.members = []
|
|
||||||
|
|
||||||
def get_member(self, name: str) -> Optional[TypeStructMember]:
|
|
||||||
"""
|
|
||||||
Returns a member by name
|
|
||||||
"""
|
|
||||||
for mem in self.members:
|
|
||||||
if mem.name == name:
|
|
||||||
return mem
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def alloc_size(self) -> int:
|
|
||||||
return sum(
|
|
||||||
x.type.alloc_size()
|
|
||||||
for x in self.members
|
|
||||||
)
|
|
||||||
199
phasm/wasm.py
199
phasm/wasm.py
@ -1,199 +0,0 @@
|
|||||||
"""
|
|
||||||
Python classes for storing the representation of Web Assembly code,
|
|
||||||
and being able to conver it to Web Assembly Text Format
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Iterable, List, Optional, Tuple
|
|
||||||
|
|
||||||
class WatSerializable:
|
|
||||||
"""
|
|
||||||
Mixin for clases that can be serialized as WebAssembly Text
|
|
||||||
"""
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
"""
|
|
||||||
Renders this object as WebAssembly Text
|
|
||||||
"""
|
|
||||||
raise NotImplementedError(self, 'to_wat')
|
|
||||||
|
|
||||||
class WasmType(WatSerializable):
|
|
||||||
"""
|
|
||||||
Type base class
|
|
||||||
"""
|
|
||||||
|
|
||||||
class WasmTypeNone(WasmType):
|
|
||||||
"""
|
|
||||||
Type when there is no type
|
|
||||||
"""
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
raise Exception('None type is only a placeholder')
|
|
||||||
|
|
||||||
class WasmTypeInt32(WasmType):
|
|
||||||
"""
|
|
||||||
i32 value
|
|
||||||
|
|
||||||
Signed or not depends on the operations, not the type
|
|
||||||
"""
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
return 'i32'
|
|
||||||
|
|
||||||
class WasmTypeInt64(WasmType):
|
|
||||||
"""
|
|
||||||
i64 value
|
|
||||||
|
|
||||||
Signed or not depends on the operations, not the type
|
|
||||||
"""
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
return 'i64'
|
|
||||||
|
|
||||||
class WasmTypeFloat32(WasmType):
|
|
||||||
"""
|
|
||||||
f32 value
|
|
||||||
"""
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
return 'f32'
|
|
||||||
|
|
||||||
class WasmTypeFloat64(WasmType):
|
|
||||||
"""
|
|
||||||
f64 value
|
|
||||||
"""
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
return 'f64'
|
|
||||||
|
|
||||||
class WasmTypeVector(WasmType):
|
|
||||||
"""
|
|
||||||
A vector is a 128-bit value
|
|
||||||
"""
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
return 'v128'
|
|
||||||
|
|
||||||
class WasmTypeVectorInt32x4(WasmTypeVector):
|
|
||||||
"""
|
|
||||||
4 Int32 values in a single vector
|
|
||||||
"""
|
|
||||||
|
|
||||||
Param = Tuple[str, WasmType]
|
|
||||||
|
|
||||||
class Import(WatSerializable):
|
|
||||||
"""
|
|
||||||
Represents a Web Assembly import
|
|
||||||
"""
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
module: str,
|
|
||||||
name: str,
|
|
||||||
intname: str,
|
|
||||||
params: Iterable[Param],
|
|
||||||
result: WasmType,
|
|
||||||
) -> None:
|
|
||||||
self.module = module
|
|
||||||
self.name = name
|
|
||||||
self.intname = intname
|
|
||||||
self.params = [*params]
|
|
||||||
self.result = result
|
|
||||||
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
return '(import "{}" "{}" (func ${}{}{}))'.format(
|
|
||||||
self.module,
|
|
||||||
self.name,
|
|
||||||
self.intname,
|
|
||||||
''.join(
|
|
||||||
f' (param {typ.to_wat()})'
|
|
||||||
for _, typ in self.params
|
|
||||||
),
|
|
||||||
'' if isinstance(self.result, WasmTypeNone)
|
|
||||||
else f' (result {self.result.to_wat()})'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Statement(WatSerializable):
|
|
||||||
"""
|
|
||||||
Represents a Web Assembly statement
|
|
||||||
"""
|
|
||||||
def __init__(self, name: str, *args: str, comment: Optional[str] = None):
|
|
||||||
self.name = name
|
|
||||||
self.args = args
|
|
||||||
self.comment = comment
|
|
||||||
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
args = ' '.join(self.args)
|
|
||||||
comment = f' ;; {self.comment}' if self.comment else ''
|
|
||||||
|
|
||||||
return f'{self.name} {args}{comment}'
|
|
||||||
|
|
||||||
class Function(WatSerializable):
|
|
||||||
"""
|
|
||||||
Represents a Web Assembly function
|
|
||||||
"""
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
exported_name: Optional[str],
|
|
||||||
params: Iterable[Param],
|
|
||||||
locals_: Iterable[Param],
|
|
||||||
result: WasmType,
|
|
||||||
statements: Iterable[Statement],
|
|
||||||
) -> None:
|
|
||||||
self.name = name
|
|
||||||
self.exported_name = exported_name
|
|
||||||
self.params = [*params]
|
|
||||||
self.locals = [*locals_]
|
|
||||||
self.result = result
|
|
||||||
self.statements = [*statements]
|
|
||||||
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
header = f'${self.name}' # Name for internal use
|
|
||||||
|
|
||||||
if self.exported_name is not None:
|
|
||||||
# Name for external use
|
|
||||||
header += f' (export "{self.exported_name}")'
|
|
||||||
|
|
||||||
for nam, typ in self.params:
|
|
||||||
header += f' (param ${nam} {typ.to_wat()})'
|
|
||||||
|
|
||||||
if not isinstance(self.result, WasmTypeNone):
|
|
||||||
header += f' (result {self.result.to_wat()})'
|
|
||||||
|
|
||||||
for nam, typ in self.locals:
|
|
||||||
header += f' (local ${nam} {typ.to_wat()})'
|
|
||||||
|
|
||||||
return '(func {}\n {}\n )'.format(
|
|
||||||
header,
|
|
||||||
'\n '.join(x.to_wat() for x in self.statements),
|
|
||||||
)
|
|
||||||
|
|
||||||
class ModuleMemory(WatSerializable):
|
|
||||||
"""
|
|
||||||
Represents a WebAssembly module's memory
|
|
||||||
"""
|
|
||||||
def __init__(self, data: bytes = b'') -> None:
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
data = ''.join(
|
|
||||||
f'\\{x:02x}'
|
|
||||||
for x in self.data
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
'(memory 1)\n '
|
|
||||||
f'(data (memory 0) (i32.const 0) "{data}")\n '
|
|
||||||
'(export "memory" (memory 0))\n'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Module(WatSerializable):
|
|
||||||
"""
|
|
||||||
Represents a Web Assembly module
|
|
||||||
"""
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.imports: List[Import] = []
|
|
||||||
self.functions: List[Function] = []
|
|
||||||
self.memory = ModuleMemory()
|
|
||||||
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
"""
|
|
||||||
Generates the text version
|
|
||||||
"""
|
|
||||||
return '(module\n {}\n {}\n {})\n'.format(
|
|
||||||
'\n '.join(x.to_wat() for x in self.imports),
|
|
||||||
self.memory.to_wat(),
|
|
||||||
'\n '.join(x.to_wat() for x in self.functions),
|
|
||||||
)
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
"""
|
|
||||||
Helper functions to quickly generate WASM code
|
|
||||||
"""
|
|
||||||
from typing import Any, Dict, List, Optional, Type
|
|
||||||
|
|
||||||
import functools
|
|
||||||
|
|
||||||
from . import wasm
|
|
||||||
|
|
||||||
#pylint: disable=C0103,C0115,C0116,R0201,R0902
|
|
||||||
|
|
||||||
class Prefix_inn_fnn:
|
|
||||||
def __init__(self, prefix: str) -> None:
|
|
||||||
self.prefix = prefix
|
|
||||||
|
|
||||||
# 6.5.5. Memory Instructions
|
|
||||||
self.load = functools.partial(wasm.Statement, f'{self.prefix}.load')
|
|
||||||
self.store = functools.partial(wasm.Statement, f'{self.prefix}.store')
|
|
||||||
|
|
||||||
# 6.5.6. Numeric Instructions
|
|
||||||
self.clz = functools.partial(wasm.Statement, f'{self.prefix}.clz')
|
|
||||||
self.ctz = functools.partial(wasm.Statement, f'{self.prefix}.ctz')
|
|
||||||
self.popcnt = functools.partial(wasm.Statement, f'{self.prefix}.popcnt')
|
|
||||||
self.add = functools.partial(wasm.Statement, f'{self.prefix}.add')
|
|
||||||
self.sub = functools.partial(wasm.Statement, f'{self.prefix}.sub')
|
|
||||||
self.mul = functools.partial(wasm.Statement, f'{self.prefix}.mul')
|
|
||||||
self.div_s = functools.partial(wasm.Statement, f'{self.prefix}.div_s')
|
|
||||||
self.div_u = functools.partial(wasm.Statement, f'{self.prefix}.div_u')
|
|
||||||
self.rem_s = functools.partial(wasm.Statement, f'{self.prefix}.rem_s')
|
|
||||||
self.rem_u = functools.partial(wasm.Statement, f'{self.prefix}.rem_u')
|
|
||||||
self.and_ = functools.partial(wasm.Statement, f'{self.prefix}.and')
|
|
||||||
self.or_ = functools.partial(wasm.Statement, f'{self.prefix}.or')
|
|
||||||
self.xor = functools.partial(wasm.Statement, f'{self.prefix}.xor')
|
|
||||||
self.shl = functools.partial(wasm.Statement, f'{self.prefix}.shl')
|
|
||||||
self.shr_s = functools.partial(wasm.Statement, f'{self.prefix}.shr_s')
|
|
||||||
self.shr_u = functools.partial(wasm.Statement, f'{self.prefix}.shr_u')
|
|
||||||
self.rotl = functools.partial(wasm.Statement, f'{self.prefix}.rotl')
|
|
||||||
self.rotr = functools.partial(wasm.Statement, f'{self.prefix}.rotr')
|
|
||||||
|
|
||||||
self.eqz = functools.partial(wasm.Statement, f'{self.prefix}.eqz')
|
|
||||||
self.eq = functools.partial(wasm.Statement, f'{self.prefix}.eq')
|
|
||||||
self.ne = functools.partial(wasm.Statement, f'{self.prefix}.ne')
|
|
||||||
self.lt_s = functools.partial(wasm.Statement, f'{self.prefix}.lt_s')
|
|
||||||
self.lt_u = functools.partial(wasm.Statement, f'{self.prefix}.lt_u')
|
|
||||||
self.gt_s = functools.partial(wasm.Statement, f'{self.prefix}.gt_s')
|
|
||||||
self.gt_u = functools.partial(wasm.Statement, f'{self.prefix}.gt_u')
|
|
||||||
self.le_s = functools.partial(wasm.Statement, f'{self.prefix}.le_s')
|
|
||||||
self.le_u = functools.partial(wasm.Statement, f'{self.prefix}.le_u')
|
|
||||||
self.ge_s = functools.partial(wasm.Statement, f'{self.prefix}.ge_s')
|
|
||||||
self.ge_u = functools.partial(wasm.Statement, f'{self.prefix}.ge_u')
|
|
||||||
|
|
||||||
def const(self, value: int, comment: Optional[str] = None) -> wasm.Statement:
|
|
||||||
return wasm.Statement(f'{self.prefix}.const', f'0x{value:08x}', comment=comment)
|
|
||||||
|
|
||||||
i32 = Prefix_inn_fnn('i32')
|
|
||||||
i64 = Prefix_inn_fnn('i64')
|
|
||||||
|
|
||||||
class Block:
|
|
||||||
def __init__(self, start: str) -> None:
|
|
||||||
self.start = start
|
|
||||||
|
|
||||||
def __call__(self, *statements: wasm.Statement) -> List[wasm.Statement]:
|
|
||||||
return [
|
|
||||||
wasm.Statement('if'),
|
|
||||||
*statements,
|
|
||||||
wasm.Statement('end'),
|
|
||||||
]
|
|
||||||
|
|
||||||
if_ = Block('if')
|
|
||||||
@ -1,220 +0,0 @@
|
|||||||
"""
|
|
||||||
Helper functions to generate WASM code by writing Python functions
|
|
||||||
"""
|
|
||||||
from typing import Any, Callable, Dict, List, Optional, Type
|
|
||||||
|
|
||||||
import functools
|
|
||||||
|
|
||||||
from . import wasm
|
|
||||||
|
|
||||||
# pylint: disable=C0103,C0115,C0116,R0902
|
|
||||||
|
|
||||||
class VarType_Base:
|
|
||||||
wasm_type: Type[wasm.WasmType]
|
|
||||||
|
|
||||||
def __init__(self, name: str) -> None:
|
|
||||||
self.name = name
|
|
||||||
self.name_ref = f'${name}'
|
|
||||||
|
|
||||||
class VarType_u8(VarType_Base):
|
|
||||||
wasm_type = wasm.WasmTypeInt32
|
|
||||||
|
|
||||||
class VarType_i32(VarType_Base):
|
|
||||||
wasm_type = wasm.WasmTypeInt32
|
|
||||||
|
|
||||||
class Generator_i32i64:
|
|
||||||
def __init__(self, prefix: str, generator: 'Generator') -> None:
|
|
||||||
self.prefix = prefix
|
|
||||||
self.generator = generator
|
|
||||||
|
|
||||||
# 2.4.1. Numeric Instructions
|
|
||||||
# ibinop
|
|
||||||
self.add = functools.partial(self.generator.add_statement, f'{prefix}.add')
|
|
||||||
self.sub = functools.partial(self.generator.add_statement, f'{prefix}.sub')
|
|
||||||
self.mul = functools.partial(self.generator.add_statement, f'{prefix}.mul')
|
|
||||||
|
|
||||||
# irelop
|
|
||||||
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
|
|
||||||
self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne')
|
|
||||||
self.lt_u = functools.partial(self.generator.add_statement, f'{prefix}.lt_u')
|
|
||||||
self.ge_u = functools.partial(self.generator.add_statement, f'{prefix}.ge_u')
|
|
||||||
|
|
||||||
# 2.4.4. Memory Instructions
|
|
||||||
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
|
|
||||||
self.load8_u = functools.partial(self.generator.add_statement, f'{prefix}.load8_u')
|
|
||||||
self.store = functools.partial(self.generator.add_statement, f'{prefix}.store')
|
|
||||||
|
|
||||||
def const(self, value: int, comment: Optional[str] = None) -> None:
|
|
||||||
self.generator.add_statement(f'{self.prefix}.const', f'0x{value:08x}', comment=comment)
|
|
||||||
|
|
||||||
class Generator_i32(Generator_i32i64):
|
|
||||||
def __init__(self, generator: 'Generator') -> None:
|
|
||||||
super().__init__('i32', generator)
|
|
||||||
|
|
||||||
class Generator_i64(Generator_i32i64):
|
|
||||||
def __init__(self, generator: 'Generator') -> None:
|
|
||||||
super().__init__('i64', generator)
|
|
||||||
|
|
||||||
class Generator_f32f64:
|
|
||||||
def __init__(self, prefix: str, generator: 'Generator') -> None:
|
|
||||||
self.prefix = prefix
|
|
||||||
self.generator = generator
|
|
||||||
|
|
||||||
# 2.4.1. Numeric Instructions
|
|
||||||
# fbinop
|
|
||||||
self.add = functools.partial(self.generator.add_statement, f'{prefix}.add')
|
|
||||||
|
|
||||||
# frelop
|
|
||||||
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
|
|
||||||
self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne')
|
|
||||||
|
|
||||||
# 2.4.4. Memory Instructions
|
|
||||||
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
|
|
||||||
self.store = functools.partial(self.generator.add_statement, f'{prefix}.store')
|
|
||||||
|
|
||||||
def const(self, value: float, comment: Optional[str] = None) -> None:
|
|
||||||
# FIXME: Is this sufficient to guarantee the float comes across properly?
|
|
||||||
self.generator.add_statement(f'{self.prefix}.const', f'{value}', comment=comment)
|
|
||||||
|
|
||||||
class Generator_f32(Generator_f32f64):
|
|
||||||
def __init__(self, generator: 'Generator') -> None:
|
|
||||||
super().__init__('f32', generator)
|
|
||||||
|
|
||||||
class Generator_f64(Generator_f32f64):
|
|
||||||
def __init__(self, generator: 'Generator') -> None:
|
|
||||||
super().__init__('f64', generator)
|
|
||||||
|
|
||||||
class Generator_Local:
|
|
||||||
def __init__(self, generator: 'Generator') -> None:
|
|
||||||
self.generator = generator
|
|
||||||
|
|
||||||
# 2.4.3. Variable Instructions
|
|
||||||
def get(self, variable: VarType_Base, comment: Optional[str] = None) -> None:
|
|
||||||
self.generator.add_statement('local.get', variable.name_ref, comment=comment)
|
|
||||||
|
|
||||||
def set(self, variable: VarType_Base, comment: Optional[str] = None) -> None:
|
|
||||||
self.generator.locals.setdefault(variable.name, variable)
|
|
||||||
|
|
||||||
self.generator.add_statement('local.set', variable.name_ref, comment=comment)
|
|
||||||
|
|
||||||
def tee(self, variable: VarType_Base, comment: Optional[str] = None) -> None:
|
|
||||||
self.generator.locals.setdefault(variable.name, variable)
|
|
||||||
|
|
||||||
self.generator.add_statement('local.tee', variable.name_ref, comment=comment)
|
|
||||||
|
|
||||||
class GeneratorBlock:
|
|
||||||
def __init__(self, generator: 'Generator', name: str) -> None:
|
|
||||||
self.generator = generator
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def __enter__(self) -> None:
|
|
||||||
self.generator.add_statement(self.name)
|
|
||||||
|
|
||||||
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
|
||||||
if not exc_type:
|
|
||||||
self.generator.add_statement('end')
|
|
||||||
|
|
||||||
class Generator:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.statements: List[wasm.Statement] = []
|
|
||||||
self.locals: Dict[str, VarType_Base] = {}
|
|
||||||
|
|
||||||
self.i32 = Generator_i32(self)
|
|
||||||
self.i64 = Generator_i64(self)
|
|
||||||
self.f32 = Generator_f32(self)
|
|
||||||
self.f64 = Generator_f64(self)
|
|
||||||
|
|
||||||
# 2.4.3 Variable Instructions
|
|
||||||
self.local = Generator_Local(self)
|
|
||||||
|
|
||||||
# 2.4.5 Control Instructions
|
|
||||||
self.nop = functools.partial(self.add_statement, 'nop')
|
|
||||||
self.unreachable = functools.partial(self.add_statement, 'unreachable')
|
|
||||||
# block
|
|
||||||
self.loop = functools.partial(GeneratorBlock, self, 'loop')
|
|
||||||
self.if_ = functools.partial(GeneratorBlock, self, 'if')
|
|
||||||
# br
|
|
||||||
# br_if - see below
|
|
||||||
# br_table
|
|
||||||
self.return_ = functools.partial(self.add_statement, 'return')
|
|
||||||
# call - see below
|
|
||||||
# call_indirect
|
|
||||||
|
|
||||||
def br_if(self, idx: int) -> None:
|
|
||||||
self.add_statement('br_if', f'{idx}')
|
|
||||||
|
|
||||||
def call(self, function: wasm.Function) -> None:
|
|
||||||
self.add_statement('call', f'${function.name}')
|
|
||||||
|
|
||||||
def add_statement(self, name: str, *args: str, comment: Optional[str] = None) -> None:
|
|
||||||
self.statements.append(wasm.Statement(name, *args, comment=comment))
|
|
||||||
|
|
||||||
def temp_var_i32(self, infix: str) -> VarType_i32:
|
|
||||||
idx = 0
|
|
||||||
while (varname := f'__{infix}_tmp_var_{idx}__') in self.locals:
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
return VarType_i32(varname)
|
|
||||||
|
|
||||||
def temp_var_u8(self, infix: str) -> VarType_u8:
|
|
||||||
idx = 0
|
|
||||||
while (varname := f'__{infix}_tmp_var_{idx}__') in self.locals:
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
return VarType_u8(varname)
|
|
||||||
|
|
||||||
def func_wrapper(exported: bool = True) -> Callable[[Any], wasm.Function]:
|
|
||||||
"""
|
|
||||||
This wrapper will execute the function and return
|
|
||||||
a wasm Function with the generated Statements
|
|
||||||
"""
|
|
||||||
def inner(func: Any) -> wasm.Function:
|
|
||||||
func_name_parts = func.__module__.split('.') + [func.__name__]
|
|
||||||
if 'phasm' == func_name_parts[0]:
|
|
||||||
func_name_parts.pop(0)
|
|
||||||
func_name = '.'.join(func_name_parts)
|
|
||||||
|
|
||||||
annot = dict(func.__annotations__)
|
|
||||||
|
|
||||||
# Check if we can pass the generator
|
|
||||||
assert Generator is annot.pop('g')
|
|
||||||
|
|
||||||
# Convert return type to WasmType
|
|
||||||
annot_return = annot.pop('return')
|
|
||||||
if annot_return is None:
|
|
||||||
return_type = wasm.WasmTypeNone()
|
|
||||||
else:
|
|
||||||
assert issubclass(annot_return, VarType_Base)
|
|
||||||
return_type = annot_return.wasm_type()
|
|
||||||
|
|
||||||
# Load the argument types, and generate instances
|
|
||||||
args: Dict[str, VarType_Base] = {}
|
|
||||||
params: List[wasm.Param] = []
|
|
||||||
for param_name, param_type in annot.items():
|
|
||||||
assert issubclass(param_type, VarType_Base)
|
|
||||||
|
|
||||||
params.append((param_name, param_type.wasm_type(), ))
|
|
||||||
|
|
||||||
args[param_name] = VarType_Base(param_name)
|
|
||||||
|
|
||||||
# Make a generator, and run the function on that generator,
|
|
||||||
# so the Statements get added
|
|
||||||
generator = Generator()
|
|
||||||
func(g=generator, **args)
|
|
||||||
|
|
||||||
# Check what locals were used, and define them
|
|
||||||
locals_: List[wasm.Param] = []
|
|
||||||
for local_name, local_type in generator.locals.items():
|
|
||||||
locals_.append((local_name, local_type.wasm_type(), ))
|
|
||||||
|
|
||||||
# Complete function definition
|
|
||||||
return wasm.Function(
|
|
||||||
func_name,
|
|
||||||
func_name if exported else None,
|
|
||||||
params,
|
|
||||||
locals_,
|
|
||||||
return_type,
|
|
||||||
generator.statements,
|
|
||||||
)
|
|
||||||
|
|
||||||
return inner
|
|
||||||
10
pylintrc
10
pylintrc
@ -1,10 +0,0 @@
|
|||||||
[MASTER]
|
|
||||||
disable=C0122,R0903,R0911,R0912,R0913,R0915,R1710,W0223
|
|
||||||
|
|
||||||
max-line-length=180
|
|
||||||
|
|
||||||
[stdlib]
|
|
||||||
good-names=g
|
|
||||||
|
|
||||||
[tests]
|
|
||||||
disable=C0116,
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
mypy==0.812
|
|
||||||
pygments==2.12.0
|
|
||||||
pylint==2.7.4
|
|
||||||
pytest==6.2.2
|
|
||||||
pytest-integration==0.2.2
|
|
||||||
pywasm==1.0.7
|
|
||||||
pywasm3==0.5.0
|
|
||||||
wasmer==1.1.0
|
|
||||||
wasmer_compiler_cranelift==1.1.0
|
|
||||||
wasmtime==0.36.0
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
from typing import Any, Dict, List, Optional, Union
|
|
||||||
|
|
||||||
from . import binary
|
|
||||||
from . import option
|
|
||||||
from . import execution
|
|
||||||
|
|
||||||
class Runtime:
|
|
||||||
store: execution.Store
|
|
||||||
|
|
||||||
def __init__(self, module: binary.Module, imps: Optional[Dict[str, Any]] = None, opts: Optional[option.Option] = None):
|
|
||||||
...
|
|
||||||
|
|
||||||
def exec(self, name: str, args: List[Union[int, float]]) -> Any:
|
|
||||||
...
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
from typing import BinaryIO
|
|
||||||
|
|
||||||
class Module:
|
|
||||||
@classmethod
|
|
||||||
def from_reader(cls, reader: BinaryIO) -> 'Module':
|
|
||||||
...
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
|
|
||||||
class Result:
|
|
||||||
...
|
|
||||||
|
|
||||||
class MemoryInstance:
|
|
||||||
data: bytearray
|
|
||||||
|
|
||||||
class Store:
|
|
||||||
memory_list: List[MemoryInstance]
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
class Option:
|
|
||||||
...
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
from typing import Any, Callable
|
|
||||||
|
|
||||||
class Module:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Runtime:
|
|
||||||
...
|
|
||||||
|
|
||||||
def load(self, wasm_bin: Module) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
def get_memory(self, memid: int) -> memoryview:
|
|
||||||
...
|
|
||||||
|
|
||||||
def find_function(self, name: str) -> Callable[[Any], Any]:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Environment:
|
|
||||||
def new_runtime(self, mem_size: int) -> Runtime:
|
|
||||||
...
|
|
||||||
|
|
||||||
def parse_module(self, wasm_bin: bytes) -> Module:
|
|
||||||
...
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
from typing import Any, Dict, Callable, Union
|
|
||||||
|
|
||||||
def wat2wasm(inp: str) -> bytes:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Store:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Function:
|
|
||||||
def __init__(self, store: Store, func: Callable[[Any], Any]) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Module:
|
|
||||||
def __init__(self, store: Store, wasm: bytes) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Uint8Array:
|
|
||||||
def __getitem__(self, index: Union[int, slice]) -> int:
|
|
||||||
...
|
|
||||||
|
|
||||||
def __setitem__(self, idx: int, value: int) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Memory:
|
|
||||||
def uint8_view(self, offset: int = 0) -> Uint8Array:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Exports:
|
|
||||||
...
|
|
||||||
|
|
||||||
class ImportObject:
|
|
||||||
def register(self, region: str, values: Dict[str, Function]) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Instance:
|
|
||||||
exports: Exports
|
|
||||||
|
|
||||||
def __init__(self, module: Module, imports: ImportObject) -> None:
|
|
||||||
...
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
pytest.register_assert_rewrite('tests.integration.helpers')
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
from phasm.codestyle import phasm_render
|
|
||||||
|
|
||||||
from . import runners
|
|
||||||
|
|
||||||
DASHES = '-' * 16
|
|
||||||
|
|
||||||
class SuiteResult:
|
|
||||||
def __init__(self):
|
|
||||||
self.returned_value = None
|
|
||||||
|
|
||||||
RUNNER_CLASS_MAP = {
|
|
||||||
'pywasm': runners.RunnerPywasm,
|
|
||||||
'pywasm3': runners.RunnerPywasm3,
|
|
||||||
'wasmtime': runners.RunnerWasmtime,
|
|
||||||
'wasmer': runners.RunnerWasmer,
|
|
||||||
}
|
|
||||||
|
|
||||||
class Suite:
|
|
||||||
"""
|
|
||||||
WebAssembly test suite
|
|
||||||
"""
|
|
||||||
def __init__(self, code_py):
|
|
||||||
self.code_py = code_py
|
|
||||||
|
|
||||||
def run_code(self, *args, runtime='pywasm3', imports=None):
|
|
||||||
"""
|
|
||||||
Compiles the given python code into wasm and
|
|
||||||
then runs it
|
|
||||||
|
|
||||||
Returned is an object with the results set
|
|
||||||
"""
|
|
||||||
class_ = RUNNER_CLASS_MAP[runtime]
|
|
||||||
|
|
||||||
runner = class_(self.code_py)
|
|
||||||
|
|
||||||
runner.parse()
|
|
||||||
runner.compile_ast()
|
|
||||||
runner.compile_wat()
|
|
||||||
runner.compile_wasm()
|
|
||||||
runner.interpreter_setup()
|
|
||||||
runner.interpreter_load(imports)
|
|
||||||
|
|
||||||
write_header(sys.stderr, 'Phasm')
|
|
||||||
runner.dump_phasm_code(sys.stderr)
|
|
||||||
write_header(sys.stderr, 'Assembly')
|
|
||||||
runner.dump_wasm_wat(sys.stderr)
|
|
||||||
|
|
||||||
# Check if code formatting works
|
|
||||||
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
|
|
||||||
|
|
||||||
wasm_args = []
|
|
||||||
if args:
|
|
||||||
write_header(sys.stderr, 'Memory (pre alloc)')
|
|
||||||
runner.interpreter_dump_memory(sys.stderr)
|
|
||||||
|
|
||||||
for arg in args:
|
|
||||||
if isinstance(arg, (int, float, )):
|
|
||||||
wasm_args.append(arg)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(arg, bytes):
|
|
||||||
adr = runner.call('stdlib.types.__alloc_bytes__', len(arg))
|
|
||||||
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(arg)}\n')
|
|
||||||
|
|
||||||
runner.interpreter_write_memory(adr + 4, arg)
|
|
||||||
wasm_args.append(adr)
|
|
||||||
continue
|
|
||||||
|
|
||||||
raise NotImplementedError(arg)
|
|
||||||
|
|
||||||
write_header(sys.stderr, 'Memory (pre run)')
|
|
||||||
runner.interpreter_dump_memory(sys.stderr)
|
|
||||||
|
|
||||||
result = SuiteResult()
|
|
||||||
result.returned_value = runner.call('testEntry', *wasm_args)
|
|
||||||
|
|
||||||
write_header(sys.stderr, 'Memory (post run)')
|
|
||||||
runner.interpreter_dump_memory(sys.stderr)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def write_header(textio, msg: str) -> None:
|
|
||||||
textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')
|
|
||||||
@ -1,323 +0,0 @@
|
|||||||
"""
|
|
||||||
Runners to help run WebAssembly code on various interpreters
|
|
||||||
"""
|
|
||||||
from typing import Any, Callable, Dict, Iterable, Optional, TextIO
|
|
||||||
|
|
||||||
import ctypes
|
|
||||||
import io
|
|
||||||
|
|
||||||
import pywasm.binary
|
|
||||||
import wasm3
|
|
||||||
import wasmer
|
|
||||||
import wasmtime
|
|
||||||
|
|
||||||
from phasm.compiler import phasm_compile
|
|
||||||
from phasm.parser import phasm_parse
|
|
||||||
from phasm import ourlang
|
|
||||||
from phasm import wasm
|
|
||||||
|
|
||||||
class RunnerBase:
|
|
||||||
"""
|
|
||||||
Base class
|
|
||||||
"""
|
|
||||||
phasm_code: str
|
|
||||||
phasm_ast: ourlang.Module
|
|
||||||
wasm_ast: wasm.Module
|
|
||||||
wasm_asm: str
|
|
||||||
wasm_bin: bytes
|
|
||||||
|
|
||||||
def __init__(self, phasm_code: str) -> None:
|
|
||||||
self.phasm_code = phasm_code
|
|
||||||
|
|
||||||
def dump_phasm_code(self, textio: TextIO) -> None:
|
|
||||||
"""
|
|
||||||
Dumps the input Phasm code for debugging
|
|
||||||
"""
|
|
||||||
_dump_code(textio, self.phasm_code)
|
|
||||||
|
|
||||||
def parse(self) -> None:
|
|
||||||
"""
|
|
||||||
Parses the Phasm code into an AST
|
|
||||||
"""
|
|
||||||
self.phasm_ast = phasm_parse(self.phasm_code)
|
|
||||||
|
|
||||||
def compile_ast(self) -> None:
|
|
||||||
"""
|
|
||||||
Compiles the Phasm AST into an WebAssembly AST
|
|
||||||
"""
|
|
||||||
self.wasm_ast = phasm_compile(self.phasm_ast)
|
|
||||||
|
|
||||||
def compile_wat(self) -> None:
|
|
||||||
"""
|
|
||||||
Compiles the WebAssembly AST into WebAssembly Assembly code
|
|
||||||
"""
|
|
||||||
self.wasm_asm = self.wasm_ast.to_wat()
|
|
||||||
|
|
||||||
def dump_wasm_wat(self, textio: TextIO) -> None:
|
|
||||||
"""
|
|
||||||
Dumps the intermediate WebAssembly Assembly code for debugging
|
|
||||||
"""
|
|
||||||
_dump_code(textio, self.wasm_asm)
|
|
||||||
|
|
||||||
def compile_wasm(self) -> None:
|
|
||||||
"""
|
|
||||||
Compiles the WebAssembly AST into WebAssembly Binary
|
|
||||||
"""
|
|
||||||
self.wasm_bin = wasmer.wat2wasm(self.wasm_asm)
|
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
|
||||||
"""
|
|
||||||
Sets up the interpreter
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
|
||||||
"""
|
|
||||||
Loads the code into the interpreter
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
|
||||||
"""
|
|
||||||
Writes into the interpreters memory
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
|
||||||
"""
|
|
||||||
Reads from the interpreters memory
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
|
||||||
"""
|
|
||||||
Dumps the interpreters memory for debugging
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def call(self, function: str, *args: Any) -> Any:
|
|
||||||
"""
|
|
||||||
Calls the given function with the given arguments, returning the result
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
class RunnerPywasm(RunnerBase):
|
|
||||||
"""
|
|
||||||
Implements a runner for pywasm
|
|
||||||
|
|
||||||
See https://pypi.org/project/pywasm/
|
|
||||||
"""
|
|
||||||
module: pywasm.binary.Module
|
|
||||||
runtime: pywasm.Runtime
|
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
|
||||||
# Nothing to set up
|
|
||||||
pass
|
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
|
||||||
if imports is not None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
bytesio = io.BytesIO(self.wasm_bin)
|
|
||||||
self.module = pywasm.binary.Module.from_reader(bytesio)
|
|
||||||
self.runtime = pywasm.Runtime(self.module, {}, None)
|
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
|
||||||
for idx, byt in enumerate(data):
|
|
||||||
self.runtime.store.memory_list[0].data[offset + idx] = byt
|
|
||||||
|
|
||||||
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
|
||||||
return self.runtime.store.memory_list[0].data[offset:length]
|
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
|
||||||
_dump_memory(textio, self.runtime.store.memory_list[0].data)
|
|
||||||
|
|
||||||
def call(self, function: str, *args: Any) -> Any:
|
|
||||||
return self.runtime.exec(function, [*args])
|
|
||||||
|
|
||||||
class RunnerPywasm3(RunnerBase):
|
|
||||||
"""
|
|
||||||
Implements a runner for pywasm3
|
|
||||||
|
|
||||||
See https://pypi.org/project/pywasm3/
|
|
||||||
"""
|
|
||||||
env: wasm3.Environment
|
|
||||||
rtime: wasm3.Runtime
|
|
||||||
mod: wasm3.Module
|
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
|
||||||
self.env = wasm3.Environment()
|
|
||||||
self.rtime = self.env.new_runtime(1024 * 1024)
|
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
|
||||||
if imports is not None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
self.mod = self.env.parse_module(self.wasm_bin)
|
|
||||||
self.rtime.load(self.mod)
|
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
|
||||||
memory = self.rtime.get_memory(0)
|
|
||||||
|
|
||||||
for idx, byt in enumerate(data):
|
|
||||||
memory[offset + idx] = byt # type: ignore
|
|
||||||
|
|
||||||
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
|
||||||
memory = self.rtime.get_memory(0)
|
|
||||||
return memory[offset:length].tobytes()
|
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
|
||||||
_dump_memory(textio, self.rtime.get_memory(0))
|
|
||||||
|
|
||||||
def call(self, function: str, *args: Any) -> Any:
|
|
||||||
return self.rtime.find_function(function)(*args)
|
|
||||||
|
|
||||||
class RunnerWasmtime(RunnerBase):
|
|
||||||
"""
|
|
||||||
Implements a runner for wasmtime
|
|
||||||
|
|
||||||
See https://pypi.org/project/wasmtime/
|
|
||||||
"""
|
|
||||||
store: wasmtime.Store
|
|
||||||
module: wasmtime.Module
|
|
||||||
instance: wasmtime.Instance
|
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
|
||||||
self.store = wasmtime.Store()
|
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
|
||||||
if imports is not None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
self.module = wasmtime.Module(self.store.engine, self.wasm_bin)
|
|
||||||
self.instance = wasmtime.Instance(self.store, self.module, [])
|
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
|
||||||
exports = self.instance.exports(self.store)
|
|
||||||
memory = exports['memory']
|
|
||||||
assert isinstance(memory, wasmtime.Memory) # type hint
|
|
||||||
|
|
||||||
data_ptr = memory.data_ptr(self.store)
|
|
||||||
data_len = memory.data_len(self.store)
|
|
||||||
|
|
||||||
idx = offset
|
|
||||||
for byt in data:
|
|
||||||
assert idx < data_len
|
|
||||||
data_ptr[idx] = ctypes.c_ubyte(byt)
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
|
||||||
exports = self.instance.exports(self.store)
|
|
||||||
memory = exports['memory']
|
|
||||||
assert isinstance(memory, wasmtime.Memory) # type hint
|
|
||||||
|
|
||||||
data_ptr = memory.data_ptr(self.store)
|
|
||||||
data_len = memory.data_len(self.store)
|
|
||||||
|
|
||||||
raw = ctypes.string_at(data_ptr, data_len)
|
|
||||||
|
|
||||||
return raw[offset:length]
|
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
|
||||||
exports = self.instance.exports(self.store)
|
|
||||||
memory = exports['memory']
|
|
||||||
assert isinstance(memory, wasmtime.Memory) # type hint
|
|
||||||
|
|
||||||
data_ptr = memory.data_ptr(self.store)
|
|
||||||
data_len = memory.data_len(self.store)
|
|
||||||
|
|
||||||
_dump_memory(textio, ctypes.string_at(data_ptr, data_len))
|
|
||||||
|
|
||||||
def call(self, function: str, *args: Any) -> Any:
|
|
||||||
exports = self.instance.exports(self.store)
|
|
||||||
func = exports[function]
|
|
||||||
assert isinstance(func, wasmtime.Func)
|
|
||||||
|
|
||||||
return func(self.store, *args)
|
|
||||||
|
|
||||||
class RunnerWasmer(RunnerBase):
|
|
||||||
"""
|
|
||||||
Implements a runner for wasmer
|
|
||||||
|
|
||||||
See https://pypi.org/project/wasmer/
|
|
||||||
"""
|
|
||||||
|
|
||||||
# pylint: disable=E1101
|
|
||||||
|
|
||||||
store: wasmer.Store
|
|
||||||
module: wasmer.Module
|
|
||||||
instance: wasmer.Instance
|
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
|
||||||
self.store = wasmer.Store()
|
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
|
||||||
import_object = wasmer.ImportObject()
|
|
||||||
if imports:
|
|
||||||
import_object.register('imports', {
|
|
||||||
k: wasmer.Function(self.store, v)
|
|
||||||
for k, v in (imports or {}).items()
|
|
||||||
})
|
|
||||||
|
|
||||||
self.module = wasmer.Module(self.store, self.wasm_bin)
|
|
||||||
self.instance = wasmer.Instance(self.module, import_object)
|
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
|
||||||
exports = self.instance.exports
|
|
||||||
memory = getattr(exports, 'memory')
|
|
||||||
assert isinstance(memory, wasmer.Memory)
|
|
||||||
view = memory.uint8_view(offset)
|
|
||||||
|
|
||||||
for idx, byt in enumerate(data):
|
|
||||||
view[idx] = byt
|
|
||||||
|
|
||||||
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
|
||||||
exports = self.instance.exports
|
|
||||||
memory = getattr(exports, 'memory')
|
|
||||||
assert isinstance(memory, wasmer.Memory)
|
|
||||||
view = memory.uint8_view(offset)
|
|
||||||
return bytes(view[offset:length])
|
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
|
||||||
exports = self.instance.exports
|
|
||||||
memory = getattr(exports, 'memory')
|
|
||||||
assert isinstance(memory, wasmer.Memory)
|
|
||||||
view = memory.uint8_view()
|
|
||||||
|
|
||||||
_dump_memory(textio, view) # type: ignore
|
|
||||||
|
|
||||||
def call(self, function: str, *args: Any) -> Any:
|
|
||||||
exports = self.instance.exports
|
|
||||||
func = getattr(exports, function)
|
|
||||||
|
|
||||||
return func(*args)
|
|
||||||
|
|
||||||
def _dump_memory(textio: TextIO, mem: bytes) -> None:
|
|
||||||
line_width = 16
|
|
||||||
|
|
||||||
prev_line = None
|
|
||||||
skip = False
|
|
||||||
for idx in range(0, len(mem), line_width):
|
|
||||||
line = ''
|
|
||||||
for idx2 in range(0, line_width):
|
|
||||||
line += f'{mem[idx + idx2]:02X}'
|
|
||||||
if idx2 % 2 == 1:
|
|
||||||
line += ' '
|
|
||||||
|
|
||||||
if prev_line == line:
|
|
||||||
if not skip:
|
|
||||||
textio.write('**\n')
|
|
||||||
skip = True
|
|
||||||
else:
|
|
||||||
textio.write(f'{idx:08x} {line}\n')
|
|
||||||
|
|
||||||
prev_line = line
|
|
||||||
|
|
||||||
def _dump_code(textio: TextIO, text: str) -> None:
|
|
||||||
line_list = text.split('\n')
|
|
||||||
line_no_width = len(str(len(line_list)))
|
|
||||||
for line_no, line_txt in enumerate(line_list):
|
|
||||||
textio.write('{} {}\n'.format(
|
|
||||||
str(line_no + 1).zfill(line_no_width),
|
|
||||||
line_txt,
|
|
||||||
))
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from .helpers import Suite, write_header
|
|
||||||
from .runners import RunnerPywasm
|
|
||||||
|
|
||||||
def setup_interpreter(phash_code: str) -> RunnerPywasm:
|
|
||||||
runner = RunnerPywasm(phash_code)
|
|
||||||
|
|
||||||
runner.parse()
|
|
||||||
runner.compile_ast()
|
|
||||||
runner.compile_wat()
|
|
||||||
runner.compile_wasm()
|
|
||||||
runner.interpreter_setup()
|
|
||||||
runner.interpreter_load()
|
|
||||||
|
|
||||||
write_header(sys.stderr, 'Phasm')
|
|
||||||
runner.dump_phasm_code(sys.stderr)
|
|
||||||
write_header(sys.stderr, 'Assembly')
|
|
||||||
runner.dump_wasm_wat(sys.stderr)
|
|
||||||
|
|
||||||
return runner
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_foldl_1():
|
|
||||||
code_py = """
|
|
||||||
def u8_or(l: u8, r: u8) -> u8:
|
|
||||||
return l | r
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry(b: bytes) -> u8:
|
|
||||||
return foldl(u8_or, 128, b)
|
|
||||||
"""
|
|
||||||
suite = Suite(code_py)
|
|
||||||
|
|
||||||
result = suite.run_code(b'')
|
|
||||||
assert 128 == result.returned_value
|
|
||||||
|
|
||||||
result = suite.run_code(b'\x80', runtime='pywasm')
|
|
||||||
assert 128 == result.returned_value
|
|
||||||
|
|
||||||
result = suite.run_code(b'\x80\x40', runtime='pywasm')
|
|
||||||
assert 192 == result.returned_value
|
|
||||||
|
|
||||||
result = suite.run_code(b'\x80\x40\x20\x10', runtime='pywasm')
|
|
||||||
assert 240 == result.returned_value
|
|
||||||
|
|
||||||
result = suite.run_code(b'\x80\x40\x20\x10\x08\x04\x02\x01', runtime='pywasm')
|
|
||||||
assert 255 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_foldl_2():
|
|
||||||
code_py = """
|
|
||||||
def xor(l: u8, r: u8) -> u8:
|
|
||||||
return l ^ r
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry(a: bytes, b: bytes) -> u8:
|
|
||||||
return foldl(xor, 0, a) ^ foldl(xor, 0, b)
|
|
||||||
"""
|
|
||||||
suite = Suite(code_py)
|
|
||||||
|
|
||||||
result = suite.run_code(b'\x55\x0F', b'\x33\x80')
|
|
||||||
assert 233 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_foldl_3():
|
|
||||||
code_py = """
|
|
||||||
def xor(l: u32, r: u8) -> u32:
|
|
||||||
return l ^ u32(r)
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry(a: bytes) -> u32:
|
|
||||||
return foldl(xor, 0, a)
|
|
||||||
"""
|
|
||||||
suite = Suite(code_py)
|
|
||||||
|
|
||||||
result = suite.run_code(b'\x55\x0F\x33\x80')
|
|
||||||
assert 233 == result.returned_value
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from .helpers import Suite
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_i32():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: i32 = 13
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return CONSTANT * 5
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 65 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ])
|
|
||||||
def test_tuple_1(type_):
|
|
||||||
code_py = f"""
|
|
||||||
CONSTANT: ({type_}, ) = (65, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(CONSTANT)
|
|
||||||
|
|
||||||
def helper(vector: ({type_}, )) -> {type_}:
|
|
||||||
return vector[0]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 65 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_tuple_6():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: (u8, u8, u32, u32, u64, u64, ) = (11, 22, 3333, 4444, 555555, 666666, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u32:
|
|
||||||
return helper(CONSTANT)
|
|
||||||
|
|
||||||
def helper(vector: (u8, u8, u32, u32, u64, u64, )) -> u32:
|
|
||||||
return vector[2]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 3333 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ])
|
|
||||||
def test_static_array_1(type_):
|
|
||||||
code_py = f"""
|
|
||||||
CONSTANT: {type_}[1] = (65, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(CONSTANT)
|
|
||||||
|
|
||||||
def helper(vector: {type_}[1]) -> {type_}:
|
|
||||||
return vector[0]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 65 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_static_array_6():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: u32[6] = (11, 22, 3333, 4444, 555555, 666666, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u32:
|
|
||||||
return helper(CONSTANT)
|
|
||||||
|
|
||||||
def helper(vector: u32[6]) -> u32:
|
|
||||||
return vector[2]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 3333 == result.returned_value
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import binascii
|
|
||||||
import struct
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from .helpers import Suite
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_crc32():
|
|
||||||
# FIXME: Stub
|
|
||||||
# crc = 0xFFFFFFFF
|
|
||||||
# byt = 0x61
|
|
||||||
# => (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ byt]
|
|
||||||
# (crc >> 8) = 0x00FFFFFF
|
|
||||||
# => 0x00FFFFFF ^ _CRC32_Table[(crc & 0xFF) ^ byt]
|
|
||||||
# (crc & 0xFF) = 0xFF
|
|
||||||
# => 0x00FFFFFF ^ _CRC32_Table[0xFF ^ byt]
|
|
||||||
# 0xFF ^ 0x61 = 0x9E
|
|
||||||
# => 0x00FFFFFF ^ _CRC32_Table[0x9E]
|
|
||||||
# _CRC32_Table[0x9E] = 0x17b7be43
|
|
||||||
# => 0x00FFFFFF ^ 0x17b7be43
|
|
||||||
|
|
||||||
code_py = """
|
|
||||||
def _crc32_f(crc: u32, byt: u8) -> u32:
|
|
||||||
return 16777215 ^ 397917763
|
|
||||||
|
|
||||||
def testEntry(data: bytes) -> u32:
|
|
||||||
return 4294967295 ^ _crc32_f(4294967295, data[0])
|
|
||||||
"""
|
|
||||||
exp_result = binascii.crc32(b'a')
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(b'a')
|
|
||||||
|
|
||||||
# exp_result returns a unsigned integer, as is proper
|
|
||||||
exp_data = struct.pack('I', exp_result)
|
|
||||||
# ints extracted from WebAssembly are always signed
|
|
||||||
data = struct.pack('i', result.returned_value)
|
|
||||||
|
|
||||||
assert exp_data == data
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from .helpers import Suite
|
|
||||||
|
|
||||||
@pytest.mark.slow_integration_test
|
|
||||||
def test_fib():
|
|
||||||
code_py = """
|
|
||||||
def helper(n: i32, a: i32, b: i32) -> i32:
|
|
||||||
if n < 1:
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
return helper(n - 1, a + b, a)
|
|
||||||
|
|
||||||
def fib(n: i32) -> i32:
|
|
||||||
if n == 0:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if n == 1:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return helper(n - 1, 0, 1)
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return fib(40)
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 102334155 == result.returned_value
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import io
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from pywasm import binary
|
|
||||||
from pywasm import Runtime
|
|
||||||
|
|
||||||
from wasmer import wat2wasm
|
|
||||||
|
|
||||||
def run(code_wat):
|
|
||||||
code_wasm = wat2wasm(code_wat)
|
|
||||||
module = binary.Module.from_reader(io.BytesIO(code_wasm))
|
|
||||||
|
|
||||||
runtime = Runtime(module, {}, {})
|
|
||||||
|
|
||||||
out_put = runtime.exec('testEntry', [])
|
|
||||||
return (runtime, out_put)
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('size,offset,exp_out_put', [
|
|
||||||
('32', 0, 0x3020100),
|
|
||||||
('32', 1, 0x4030201),
|
|
||||||
('64', 0, 0x706050403020100),
|
|
||||||
('64', 2, 0x908070605040302),
|
|
||||||
])
|
|
||||||
def test_i32_64_load(size, offset, exp_out_put):
|
|
||||||
code_wat = f"""
|
|
||||||
(module
|
|
||||||
(memory 1)
|
|
||||||
(data (memory 0) (i32.const 0) "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\10")
|
|
||||||
|
|
||||||
(func (export "testEntry") (result i{size})
|
|
||||||
i32.const {offset}
|
|
||||||
i{size}.load
|
|
||||||
return ))
|
|
||||||
"""
|
|
||||||
|
|
||||||
(_, out_put) = run(code_wat)
|
|
||||||
assert exp_out_put == out_put
|
|
||||||
|
|
||||||
def test_load_then_store():
|
|
||||||
code_wat = """
|
|
||||||
(module
|
|
||||||
(memory 1)
|
|
||||||
(data (memory 0) (i32.const 0) "\\04\\00\\00\\00")
|
|
||||||
|
|
||||||
(func (export "testEntry") (result i32) (local $my_memory_value i32)
|
|
||||||
;; Load i32 from address 0
|
|
||||||
i32.const 0
|
|
||||||
i32.load
|
|
||||||
|
|
||||||
;; Add 8 to the loaded value
|
|
||||||
i32.const 8
|
|
||||||
i32.add
|
|
||||||
|
|
||||||
local.set $my_memory_value
|
|
||||||
|
|
||||||
;; Store back to the memory
|
|
||||||
i32.const 0
|
|
||||||
local.get $my_memory_value
|
|
||||||
i32.store
|
|
||||||
|
|
||||||
;; Return something
|
|
||||||
i32.const 9
|
|
||||||
return ))
|
|
||||||
"""
|
|
||||||
(runtime, out_put) = run(code_wat)
|
|
||||||
|
|
||||||
assert 9 == out_put
|
|
||||||
|
|
||||||
assert (b'\x0c'+ b'\00' * 23) == runtime.store.mems[0].data[:24]
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from .helpers import Suite
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_bytes_index_out_of_bounds():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(f: bytes) -> u8:
|
|
||||||
return f[50]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(b'Short', b'Long' * 100)
|
|
||||||
|
|
||||||
assert 0 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_static_array_index_out_of_bounds():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT0: u32[3] = (24, 57, 80, )
|
|
||||||
|
|
||||||
CONSTANT1: u32[16] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u32:
|
|
||||||
return CONSTANT0[16]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 0 == result.returned_value
|
|
||||||
@ -1,571 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from .helpers import Suite
|
|
||||||
|
|
||||||
TYPE_MAP = {
|
|
||||||
'u8': int,
|
|
||||||
'u32': int,
|
|
||||||
'u64': int,
|
|
||||||
'i32': int,
|
|
||||||
'i64': int,
|
|
||||||
'f32': float,
|
|
||||||
'f64': float,
|
|
||||||
}
|
|
||||||
|
|
||||||
COMPLETE_SIMPLE_TYPES = [
|
|
||||||
'u32', 'u64',
|
|
||||||
'i32', 'i64',
|
|
||||||
'f32', 'f64',
|
|
||||||
]
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
|
|
||||||
def test_return(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 13
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 13 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
|
|
||||||
def test_addition(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 + 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 13 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
|
|
||||||
def test_subtraction(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 - 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 7 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u32', 'u64']) # FIXME: Support u8, requires an extra AND operation
|
|
||||||
def test_logical_left_shift(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 << 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 80 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
|
||||||
def test_logical_right_shift(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 >> 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 1 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
|
||||||
def test_bitwise_or(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 | 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 11 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
|
||||||
def test_bitwise_xor(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 ^ 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 9 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
|
||||||
def test_bitwise_and(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 & 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 2 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['f32', 'f64'])
|
|
||||||
def test_buildins_sqrt(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return sqrt(25)
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 5 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
|
|
||||||
def test_arg(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry(a: {type_}) -> {type_}:
|
|
||||||
return a
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(125)
|
|
||||||
|
|
||||||
assert 125 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('Do we want it to work like this?')
|
|
||||||
def test_i32_to_i64():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(a: i32) -> i64:
|
|
||||||
return a
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(125)
|
|
||||||
|
|
||||||
assert 125 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('Do we want it to work like this?')
|
|
||||||
def test_i32_plus_i64():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(a: i32, b: i64) -> i64:
|
|
||||||
return a + b
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(125, 100)
|
|
||||||
|
|
||||||
assert 225 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('Do we want it to work like this?')
|
|
||||||
def test_f32_to_f64():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(a: f32) -> f64:
|
|
||||||
return a
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(125.5)
|
|
||||||
|
|
||||||
assert 125.5 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('Do we want it to work like this?')
|
|
||||||
def test_f32_plus_f64():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(a: f32, b: f64) -> f64:
|
|
||||||
return a + b
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(125.5, 100.25)
|
|
||||||
|
|
||||||
assert 225.75 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('TODO')
|
|
||||||
def test_uadd():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return +523
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 523 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('TODO')
|
|
||||||
def test_usub():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return -19
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert -19 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('inp', [9, 10, 11, 12])
|
|
||||||
def test_if_simple(inp):
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(a: i32) -> i32:
|
|
||||||
if a > 10:
|
|
||||||
return 15
|
|
||||||
|
|
||||||
return 3
|
|
||||||
"""
|
|
||||||
exp_result = 15 if inp > 10 else 3
|
|
||||||
|
|
||||||
suite = Suite(code_py)
|
|
||||||
|
|
||||||
result = suite.run_code(inp)
|
|
||||||
assert exp_result == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('Such a return is not how things should be')
|
|
||||||
def test_if_complex():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(a: i32) -> i32:
|
|
||||||
if a > 10:
|
|
||||||
return 10
|
|
||||||
elif a > 0:
|
|
||||||
return a
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
return -1 # Required due to function type
|
|
||||||
"""
|
|
||||||
|
|
||||||
suite = Suite(code_py)
|
|
||||||
|
|
||||||
assert 10 == suite.run_code(20).returned_value
|
|
||||||
assert 10 == suite.run_code(10).returned_value
|
|
||||||
|
|
||||||
assert 8 == suite.run_code(8).returned_value
|
|
||||||
|
|
||||||
assert 0 == suite.run_code(0).returned_value
|
|
||||||
assert 0 == suite.run_code(-1).returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_if_nested():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(a: i32, b: i32) -> i32:
|
|
||||||
if a > 11:
|
|
||||||
if b > 11:
|
|
||||||
return 3
|
|
||||||
|
|
||||||
return 2
|
|
||||||
|
|
||||||
if b > 11:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
"""
|
|
||||||
|
|
||||||
suite = Suite(code_py)
|
|
||||||
|
|
||||||
assert 3 == suite.run_code(20, 20).returned_value
|
|
||||||
assert 2 == suite.run_code(20, 10).returned_value
|
|
||||||
assert 1 == suite.run_code(10, 20).returned_value
|
|
||||||
assert 0 == suite.run_code(10, 10).returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_call_pre_defined():
|
|
||||||
code_py = """
|
|
||||||
def helper(left: i32, right: i32) -> i32:
|
|
||||||
return left + right
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper(10, 3)
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 13 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_call_post_defined():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper(10, 3)
|
|
||||||
|
|
||||||
def helper(left: i32, right: i32) -> i32:
|
|
||||||
return left - right
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 7 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
|
|
||||||
def test_call_with_expression(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(10 + 20, 3 + 5)
|
|
||||||
|
|
||||||
def helper(left: {type_}, right: {type_}) -> {type_}:
|
|
||||||
return left - right
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 22 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('Not yet implemented')
|
|
||||||
def test_assign():
|
|
||||||
code_py = """
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
a: i32 = 8947
|
|
||||||
return a
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 8947 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
|
|
||||||
def test_struct_0(type_):
|
|
||||||
code_py = f"""
|
|
||||||
class CheckedValue:
|
|
||||||
value: {type_}
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(CheckedValue(23))
|
|
||||||
|
|
||||||
def helper(cv: CheckedValue) -> {type_}:
|
|
||||||
return cv.value
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 23 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_struct_1():
|
|
||||||
code_py = """
|
|
||||||
class Rectangle:
|
|
||||||
height: i32
|
|
||||||
width: i32
|
|
||||||
border: i32
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper(Rectangle(100, 150, 2))
|
|
||||||
|
|
||||||
def helper(shape: Rectangle) -> i32:
|
|
||||||
return shape.height + shape.width + shape.border
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 252 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_struct_2():
|
|
||||||
code_py = """
|
|
||||||
class Rectangle:
|
|
||||||
height: i32
|
|
||||||
width: i32
|
|
||||||
border: i32
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper(Rectangle(100, 150, 2), Rectangle(200, 90, 3))
|
|
||||||
|
|
||||||
def helper(shape1: Rectangle, shape2: Rectangle) -> i32:
|
|
||||||
return shape1.height + shape1.width + shape1.border + shape2.height + shape2.width + shape2.border
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 545 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
|
|
||||||
def test_tuple_simple_constructor(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper((24, 57, 80, ))
|
|
||||||
|
|
||||||
def helper(vector: ({type_}, {type_}, {type_}, )) -> {type_}:
|
|
||||||
return vector[0] + vector[1] + vector[2]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 161 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_tuple_float():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> f32:
|
|
||||||
return helper((1.0, 2.0, 3.0, ))
|
|
||||||
|
|
||||||
def helper(v: (f32, f32, f32, )) -> f32:
|
|
||||||
return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 3.74 < result.returned_value < 3.75
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
|
|
||||||
def test_static_array_module_constant(type_):
|
|
||||||
code_py = f"""
|
|
||||||
CONSTANT: {type_}[3] = (24, 57, 80, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(CONSTANT)
|
|
||||||
|
|
||||||
def helper(array: {type_}[3]) -> {type_}:
|
|
||||||
return array[0] + array[1] + array[2]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 161 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
|
|
||||||
def test_static_array_indexed(type_):
|
|
||||||
code_py = f"""
|
|
||||||
CONSTANT: {type_}[3] = (24, 57, 80, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(CONSTANT, 0, 1, 2)
|
|
||||||
|
|
||||||
def helper(array: {type_}[3], i0: u32, i1: u32, i2: u32) -> {type_}:
|
|
||||||
return array[i0] + array[i1] + array[i2]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 161 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_bytes_address():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(f: bytes) -> bytes:
|
|
||||||
return f
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(b'This is a test')
|
|
||||||
|
|
||||||
# THIS DEPENDS ON THE ALLOCATOR
|
|
||||||
# A different allocator will return a different value
|
|
||||||
assert 20 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_bytes_length():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(f: bytes) -> i32:
|
|
||||||
return len(f)
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(b'This is another test')
|
|
||||||
|
|
||||||
assert 20 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_bytes_index():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(f: bytes) -> u8:
|
|
||||||
return f[8]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(b'This is another test')
|
|
||||||
|
|
||||||
assert 0x61 == result.returned_value
|
|
||||||
|
|
||||||
@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).run_code()
|
|
||||||
|
|
||||||
assert (1, 2, 3, 0) == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_imported():
|
|
||||||
code_py = """
|
|
||||||
@imported
|
|
||||||
def helper(mul: i32) -> i32:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper(2)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def helper(mul: int) -> int:
|
|
||||||
return 4238 * mul
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(
|
|
||||||
runtime='wasmer',
|
|
||||||
imports={
|
|
||||||
'helper': helper,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert 8476 == result.returned_value
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from phasm.parser import phasm_parse
|
|
||||||
from phasm.exceptions import StaticError
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
|
|
||||||
def test_type_mismatch_function_argument(type_):
|
|
||||||
code_py = f"""
|
|
||||||
def helper(a: {type_}) -> (i32, i32, ):
|
|
||||||
return a
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), a is actually {type_}'):
|
|
||||||
phasm_parse(code_py)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
|
|
||||||
def test_type_mismatch_struct_member(type_):
|
|
||||||
code_py = f"""
|
|
||||||
class Struct:
|
|
||||||
param: {type_}
|
|
||||||
|
|
||||||
def testEntry(arg: Struct) -> (i32, i32, ):
|
|
||||||
return arg.param
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(StaticError, match=f'Static error on line 6: Expected \\(i32, i32, \\), arg.param is actually {type_}'):
|
|
||||||
phasm_parse(code_py)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
|
|
||||||
def test_type_mismatch_tuple_member(type_):
|
|
||||||
code_py = f"""
|
|
||||||
def testEntry(arg: ({type_}, )) -> (i32, i32, ):
|
|
||||||
return arg[0]
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), arg\\[0\\] is actually {type_}'):
|
|
||||||
phasm_parse(code_py)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
|
|
||||||
def test_type_mismatch_function_result(type_):
|
|
||||||
code_py = f"""
|
|
||||||
def helper() -> {type_}:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> (i32, i32, ):
|
|
||||||
return helper()
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(StaticError, match=f'Static error on line 7: Expected \\(i32, i32, \\), helper actually returns {type_}'):
|
|
||||||
phasm_parse(code_py)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_tuple_constant_too_few_values():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: (u32, u8, u8, ) = (24, 57, )
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
|
|
||||||
phasm_parse(code_py)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_tuple_constant_too_many_values():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
|
|
||||||
phasm_parse(code_py)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_tuple_constant_type_mismatch():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'):
|
|
||||||
phasm_parse(code_py)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_static_array_constant_too_few_values():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: u8[3] = (24, 57, )
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of static array values'):
|
|
||||||
phasm_parse(code_py)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_static_array_constant_too_many_values():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: u8[3] = (24, 57, 1, 1, )
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of static array values'):
|
|
||||||
phasm_parse(code_py)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_static_array_constant_type_mismatch():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: u8[3] = (24, 4000, 1, )
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'):
|
|
||||||
phasm_parse(code_py)
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from .helpers import write_header
|
|
||||||
from .runners import RunnerPywasm3 as Runner
|
|
||||||
|
|
||||||
def setup_interpreter(phash_code: str) -> Runner:
|
|
||||||
runner = Runner(phash_code)
|
|
||||||
|
|
||||||
runner.parse()
|
|
||||||
runner.compile_ast()
|
|
||||||
runner.compile_wat()
|
|
||||||
runner.compile_wasm()
|
|
||||||
runner.interpreter_setup()
|
|
||||||
runner.interpreter_load()
|
|
||||||
|
|
||||||
write_header(sys.stderr, 'Phasm')
|
|
||||||
runner.dump_phasm_code(sys.stderr)
|
|
||||||
write_header(sys.stderr, 'Assembly')
|
|
||||||
runner.dump_wasm_wat(sys.stderr)
|
|
||||||
|
|
||||||
return runner
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test___alloc___ok():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u8:
|
|
||||||
return 13
|
|
||||||
"""
|
|
||||||
|
|
||||||
runner = setup_interpreter(code_py)
|
|
||||||
|
|
||||||
write_header(sys.stderr, 'Memory (pre run)')
|
|
||||||
runner.interpreter_dump_memory(sys.stderr)
|
|
||||||
|
|
||||||
offset0 = runner.call('stdlib.alloc.__alloc__', 32)
|
|
||||||
offset1 = runner.call('stdlib.alloc.__alloc__', 32)
|
|
||||||
offset2 = runner.call('stdlib.alloc.__alloc__', 32)
|
|
||||||
|
|
||||||
write_header(sys.stderr, 'Memory (post run)')
|
|
||||||
runner.interpreter_dump_memory(sys.stderr)
|
|
||||||
|
|
||||||
assert (
|
|
||||||
b'\xC0\xA1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7C\x00\x00\x00'
|
|
||||||
b'\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
||||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
||||||
b'\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
||||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
||||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00'
|
|
||||||
) == runner.interpreter_read_memory(0, 0x60)
|
|
||||||
|
|
||||||
assert 0x14 == offset0
|
|
||||||
assert 0x38 == offset1
|
|
||||||
assert 0x5C == offset2
|
|
||||||
Loading…
x
Reference in New Issue
Block a user