Compare commits

..

5 Commits

Author SHA1 Message Date
Johan B.W. de Vries
6f3d9a5bcc First attempt at ripping out old system
This breaks test_addition[u32], which is a good thing to chase next.
2022-09-16 17:39:46 +02:00
Johan B.W. de Vries
2d0daf4b90 Fixes 2022-09-16 17:04:13 +02:00
Johan B.W. de Vries
7669f3cbca More framework stuff 2022-09-16 17:01:23 +02:00
Johan B.W. de Vries
48e16c38b9 FunctionParam is a class, more framework stuff 2022-09-16 16:43:40 +02:00
Johan B.W. de Vries
7acb2bd8e6 Framework sketch 2022-09-16 15:54:24 +02:00
136 changed files with 3801 additions and 10959 deletions

2
.gitignore vendored
View File

@ -3,6 +3,4 @@
/.coverage /.coverage
/venv /venv
/tests/integration/test_lang/test_generated_*.py
__pycache__ __pycache__

View File

@ -1,5 +1,7 @@
TEST_FILES := tests WABT_DIR := /home/johan/Sources/github.com/WebAssembly/wabt
WAT2WASM := venv/bin/python wat2wasm.py
WAT2WASM := $(WABT_DIR)/bin/wat2wasm
WASM2C := $(WABT_DIR)/bin/wasm2c
%.wat: %.py $(shell find phasm -name '*.py') venv/.done %.wat: %.py $(shell find phasm -name '*.py') venv/.done
venv/bin/python -m phasm $< $@ venv/bin/python -m phasm $< $@
@ -13,38 +15,30 @@ WAT2WASM := venv/bin/python wat2wasm.py
%.wasm: %.wat %.wasm: %.wat
$(WAT2WASM) $^ -o $@ $(WAT2WASM) $^ -o $@
%.c: %.wasm
$(WASM2C) $^ -o $@
# %.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)) 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 venv/bin/python3 -m http.server --directory examples
test: venv/.done $(subst .json,.py,$(subst /generator_,/test_generated_,$(wildcard tests/integration/test_lang/generator_*.json))) test: venv/.done
venv/bin/pytest $(TEST_FILES) $(TEST_FLAGS) venv/bin/pytest tests $(TEST_FLAGS)
lint: venv/.done lint: venv/.done
venv/bin/ruff check phasm tests venv/bin/pylint phasm
typecheck: venv/.done typecheck: venv/.done
venv/bin/mypy --strict phasm wat2wasm.py tests/integration/helpers.py tests/integration/memory.py tests/integration/runners.py venv/bin/mypy --strict phasm tests/integration/runners.py
venv/.done: requirements.txt venv/.done: requirements.txt
python3.12 -m venv venv python3.8 -m venv venv
venv/bin/python3 -m pip install wheel pip --upgrade venv/bin/python3 -m pip install wheel pip --upgrade
venv/bin/python3 -m pip install -r $^ venv/bin/python3 -m pip install -r $^
touch $@ touch $@
tests/integration/test_lang/test_generated_%.py: venv/.done tests/integration/test_lang/generator.py tests/integration/test_lang/generator.md tests/integration/test_lang/generator_%.json
venv/bin/python3 tests/integration/test_lang/generator.py tests/integration/test_lang/generator.md tests/integration/test_lang/generator_$*.json > $@
clean-examples:
rm -f examples/*.wat examples/*.wasm examples/*.wat.html examples/*.py.html
clean-generated-tests:
rm -f tests/integration/test_lang/test_generated_*.py
.SECONDARY: # Keep intermediate files .SECONDARY: # Keep intermediate files
.PHONY: examples .PHONY: examples
# So generally the right thing to do is to delete the target file if the recipe fails after beginning to change the file.
# make will do this if .DELETE_ON_ERROR appears as a target.
# This is almost always what you want make to do, but it is not historical practice; so for compatibility, you must explicitly request it.
.DELETE_ON_ERROR:

View File

@ -3,14 +3,46 @@ phasm
Elevator pitch Elevator pitch
-------------- --------------
A programming language that looks like Python, handles like Haskell, A programming language, that looks like Python, handles like Haskell,
and compiles directly to WebAssembly. 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 Example
------- -------
For more examples, see the examples directory.
From `examples/fib.py`:
```py ```py
def helper(n: u64, a: u64, b: u64) -> u64: def helper(n: u64, a: u64, b: u64) -> u64:
if n < 1: if n < 1:
@ -29,55 +61,6 @@ def fib(n: u64) -> u64:
return helper(n - 1, 0, 1) return helper(n - 1, 0, 1)
``` ```
Compile to a WebAssembly text file:
```sh
python3 -m phasm examples/fib.py examples/fib.wat
```
Generate a WebAssembly binary file:
```sh
python wat2wasm.py examples/fib.wat -o examples/fib.wasm
```
Ready for including in your WebAssembly runtime!
Run `make examples` to start a local web server with some more examples. Each example has the source listed, as well as the compiled WebAssembly text.
Project state
-------------
This is a hobby project for now. Use at your own risk.
The parser, compiler and type checker are in a reasonably usable state.
What's still lacking is support for runtimes - notably, making it easier to get values in and out of the runtime.
For example, while Phasm supports a u32 type, when you get your value out, it will probably be a signed value.
And getting strings, structs, arrays and other combined values in and out requires manual work.
How to run
----------
You should only need make and python3. Currently, we're working with python3.12,
since we're using the python ast parser, it might not work on other versions.
To compile a Phasm file:
```sh
python3.12 -m phasm source.py output.wat
```
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
```
Gotcha's Gotcha's
-------- --------
- When importing and exporting unsigned values to WebAssembly, they will become - When importing and exporting unsigned values to WebAssembly, they will become
@ -100,9 +83,6 @@ 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 interested in a minimal test case that shows what you want to achieve and how
Phasm currently fails you. Phasm currently fails you.
We're also investigating using WASI - but that's still ongoing research. If you
have tips or ideas on that, we'd be interested.
Name origin Name origin
----------- -----------
- p from python - p from python
@ -116,3 +96,4 @@ References
[3] https://webassembly.org/ [3] https://webassembly.org/
[4] https://www.w3.org/TR/wasm-core-1/ [4] https://www.w3.org/TR/wasm-core-1/
[5] https://en.wikipedia.org/w/index.php?title=WebAssembly&oldid=1103639883 [5] https://en.wikipedia.org/w/index.php?title=WebAssembly&oldid=1103639883
[6] https://github.com/WebAssembly/wabt

23
TODO.md
View File

@ -1,27 +1,8 @@
# TODO # TODO
- Decide between lineair types / uniqueness vs garbage collector
- https://borretti.me/article/how-australs-linear-type-checker-works
- Rename constant to literal
- Implement a trace() builtin for debugging - 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 - 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 - Implement a FizzBuzz example
- Also, check the codes for FIXME and TODO - Also, check the codes for FIXME and TODO
- Allocation is done using pointers for members, is this desired?
- See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental
- Implement q32? q64? Two i32/i64 divided?
- Have a set of rules or guidelines for the constraint comments, they're messy.
- calculate_alloc_size can be reworked; is_member isn't useful with TYPE_INFO_MAP
- Parser is putting stuff in ModuleDataBlock
- Surely the compiler should build data blocks
- Also put the struct.pack constants in TYPE_INFO_MAP
- Implemented Bounded: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#t:Bounded
- Try to implement the min and max functions using select
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm
- Implement type class 'inheritance'
- Rework type classes - already started on a separate dir for those, but quite a few things are still in other places.

51
examples/buffer.html Normal file
View File

@ -0,0 +1,51 @@
<!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>

7
examples/buffer.py Normal file
View File

@ -0,0 +1,7 @@
@exported
def index(inp: bytes, idx: u32) -> u8:
return inp[idx]
@exported
def length(inp: bytes) -> i32:
return len(inp)

View File

@ -2,68 +2,245 @@
<html> <html>
<head> <head>
<title>Examples - CRC32</title> <title>Examples - CRC32</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head> </head>
<body> <body>
<h1>Examples - CRC32</h1> <h1>Buffer</h1>
<div class="menu">
<a href="index.html">List</a> - <a href="crc32.py.html">Source</a> - <a href="crc32.wat.html">WebAssembly</a><br /> <a href="index.html">List</a> - <a href="crc32.py.html">Source</a> - <a href="crc32.wat.html">WebAssembly</a><br />
</div> <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>
<div class="description"> <h2>Measurement log</h2>
<p> <h3>AMD Ryzen 7 3700X 8-Core, Ubuntu 20.04, Linux 5.4.0-124-generic</h3>
This example shows a fold implementation of a <a href="https://en.wikipedia.org/wiki/Cyclic_redundancy_check">cyclic redundancy check</a>. <h4>After optimizing fold over bytes by inlineing __subscript_bytes__</h4>
There are actually many variations of these CRCs - this one is specifically know as CRC-32/ISO-HDLC. <table>
</p> <tr>
</div> <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>5.70</td>
<td>12.45</td>
</tr>
<tr>
<td>Lynx * 65536</td>
<td>Firefox 103</td>
<td>DevTools closed</td>
<td>5.16</td>
<td>5.72</td>
</tr>
<tr>
<td>Lynx * 1048576</td>
<td>Chromium 104.0.5112.101</td>
<td>DevTools closed</td>
<td>95.65</td>
<td>203.60</td>
</tr>
<tr>
<td>Lynx * 1048576</td>
<td>Firefox 103</td>
<td>DevTools closed</td>
<td>83.34</td>
<td>92.38</td>
</tr>
</table>
<h4>Before optimizing fold over bytes by inlineing __subscript_bytes__</h4>
<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>
<h3>Try it out!</h3> <tr>
<div class="example"> <td>Lynx * 1048576</td>
<textarea id="example-data" style="width: 75%; height: 4em;">The quick brown fox jumps over the lazy dog</textarea><br /> <td>Chromium 104.0.5112.101</td>
<button type="click" id="example-click" disabled>Calculate</button> <td>DevTools closed</td>
<input type="text" id="example-crc32" /> <td>149.24</td>
</div> <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>
<div class="example-list"> <h4>Notes</h4>
<ul> - Firefox seems faster than Chromium in my setup for Javascript, WebAssembly seems about the same.<br />
<li><a href="#" data-n="123456789">crc32("123456789")</a> = cbf43926</li> - 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 />
<li><a href="#" data-n="Hello, world!">crc32("Hello, world!")</a> = ebe6c6e6</li> - WebAssembly in Firefox seems to slow down when doing a recording of the page load, which makes sense, but the Javascript does not.<br />
<li><a href="#" data-n="The quick brown fox jumps over the lazy dog">crc32("The quick brown fox jumps over the lazy dog")</a> = 414fa339</li>
<li><a href="#" data-n="CRC-32/ISO-HDLC
Also referred to as ISO 3309, ITU-T V.42, CRC-32-IEEE, and many other names.
The CRC of ASCII &quot;123456789&quot; is 0xcbf43926.
Examples of formats that use CRC-32/ISO-HDLC: ZIP, PNG, Gzip, ARJ.">crc32("CRC-32/ISO-HDLC...")</a> = 126afcf</li>
</ul>
</div>
<!-- We'll need to use some interface glue - WebAssembly doesn't let us pass strings directly. -->
<script type="text/javascript" src="./include.js"></script> <script type="text/javascript" src="./include.js"></script>
<script> <script type="text/javascript">
let importObject = {}; let importObject = {};
let exampleN = document.querySelector('#example-data');
let exampleClick = document.querySelector('#example-click');
let exampleCrc32 = document.querySelector('#example-crc32');
WebAssembly.instantiateStreaming(fetch('crc32.wasm'), importObject) // Build up a JS version
.then(app => { var makeCRCTable = function(){
exampleClick.addEventListener('click', event => { var c;
let in_put = exampleN.value; var crcTable = [];
let in_put_addr = alloc_bytes(app, in_put); for(var n =0; n < 256; n++){
let result = app.instance.exports.crc32(in_put_addr); c = n;
exampleCrc32.value = i32_to_u32(result).toString(16); for(var k =0; k < 8; k++){
}); c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
exampleClick.removeAttribute('disabled'); }
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;
}); });
for(let exmpl of document.querySelectorAll('a[data-n]') ) { // Don't test speedup for small strings, it varies a lot
exmpl.addEventListener('click', event => { let speedup = (wasm_timing.min == 0 || js_timing.min == 0)
exampleN.value = exmpl.getAttribute('data-n'); ? 1
exampleClick.click(); : 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!", 65536);
});
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,5 +1,3 @@
# CRC-32/ISO-HDLC
#
# #include <inttypes.h> // uint32_t, uint8_t # #include <inttypes.h> // uint32_t, uint8_t
# #
# uint32_t CRC32(const uint8_t data[], size_t data_length) { # uint32_t CRC32(const uint8_t data[], size_t data_length) {
@ -51,7 +49,7 @@ _CRC32_Table: u32[256] = (
) )
def _crc32_f(crc: u32, byt: u8) -> u32: def _crc32_f(crc: u32, byt: u8) -> u32:
return (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ extend(byt)] return (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ u32(byt)]
@exported @exported
def crc32(data: bytes) -> u32: def crc32(data: bytes) -> u32:

View File

@ -1,62 +1,55 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Examples - Fibonacci</title> <title>Examples - Fibonacci</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head> </head>
<body> <body>
<h1>Examples - Fibonacci</h1> <h1>Fibonacci</h1>
<div class="menu">
<a href="index.html">List</a> - <a href="fib.py.html">Source</a> - <a href="fib.wat.html">WebAssembly</a> <a href="index.html">List</a> - <a href="fib.py.html">Source</a> - <a href="fib.wat.html">WebAssembly</a>
</div>
<div class="description"> <div style="white-space: pre;" id="results"></div>
<p>
This example shows a recursive implementation of the <a href="https://en.wikipedia.org/wiki/Fibonacci_sequence">Fibonacci sequence</a>.
It makes uses of WebAssembly's support for <a href="https://en.wikipedia.org/wiki/Tail_call">tail calls</a>.
</p>
</div>
<h3>Try it out!</h3>
<div class="example">
<input type="number" id="example-n" value="25" />
<button type="click" id="example-click" disabled>Calculate</button>
<input type="number" id="example-fib" />
</div>
<div class="example-list"> <script type="text/javascript" src="./include.js"></script>
<ul> <script type="text/javascript">
<li><a href="#" data-n="5">fib(5)</a> = 5</li>
<li><a href="#" data-n="10">fib(10)</a> = 55</li>
<li><a href="#" data-n="25">fib(25)</a> = 75025</li>
<li><a href="#" data-n="50">fib(50)</a> = 12586269025</li>
</ul>
</div>
<script>
let importObject = {}; let importObject = {};
let exampleN = document.querySelector('#example-n');
let exampleClick = document.querySelector('#example-click');
let exampleFib = document.querySelector('#example-fib');
WebAssembly.instantiateStreaming(fetch('fib.wasm'), importObject) function run_test(app, number, expected)
.then(app => { {
exampleClick.addEventListener('click', event => { let actual = app.instance.exports.fib(BigInt(number));
let in_put = exampleN.value; console.log(actual);
let result = app.instance.exports.fib(BigInt(in_put));
exampleFib.value = result;
});
exampleClick.removeAttribute('disabled');
});
for(let exmpl of document.querySelectorAll('a[data-n]') ) { test_result(BigInt(expected) == actual, {
exmpl.addEventListener('click', event => { 'summary': 'fib(' + number + ')',
exampleN.value = exmpl.getAttribute('data-n'); 'attributes': {
exampleClick.click(); '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> </script>
</body> </body>
</html> </html>

View File

@ -13,3 +13,7 @@ def fib(n: u64) -> u64:
return 1 return 1
return helper(n - 1, 0, 1) return helper(n - 1, 0, 1)
@exported
def testEntry() -> u64:
return fib(40)

48
examples/fold.html Normal file
View File

@ -0,0 +1,48 @@
<!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>

6
examples/fold.py Normal file
View File

@ -0,0 +1,6 @@
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)

60
examples/imported.html Normal file
View File

@ -0,0 +1,60 @@
<!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>

7
examples/imported.py Normal file
View File

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

View File

@ -1,6 +1,3 @@
/***
* Allocates the given string in the given application's memory
*/
function alloc_bytes(app, data) function alloc_bytes(app, data)
{ {
let stdlib_types___alloc_bytes__ = app.instance.exports['stdlib.types.__alloc_bytes__'] let stdlib_types___alloc_bytes__ = app.instance.exports['stdlib.types.__alloc_bytes__']
@ -17,12 +14,85 @@ function alloc_bytes(app, data)
return offset; return offset;
} }
/** function run_times(times, callback, tweak)
* WebAssembly's interface only gets you signed integers
*
* Getting unsigned values out requires some work.
*/
function i32_to_u32(n)
{ {
return n >>> 0; 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);
} }

View File

@ -1,16 +1,19 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Examples</title> <title>Examples</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head> </head>
<body> <body>
<h1>Examples</h1> <h1>Examples</h1>
<h2>Standard</h2>
<h2>Functions</h2>
<ul> <ul>
<li><a href="crc32.html">CRC32</a></li> <li><a href="crc32.html">CRC32</a></li>
<li><a href="fib.html">Fibonacci</a></li> <li><a href="fib.html">Fibonacci</a></li>
</ul> </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> </body>
</html> </html>

View File

@ -1,31 +0,0 @@
:root {
/* CSS HEX */
--seasalt: #fcfafaff;
--silver: #c8d3d5ff;
--powder-blue: #a4b8c4ff;
--slate-gray: #6e8387ff;
--dark-pastel-green: #0cca4aff;
}
body {
background-color: var(--seasalt);
color: var(--slate-gray);
}
a {
color: var(--dark-pastel-green);
text-decoration: none;
font-weight: bold;
}
.menu {
border: 1px solid var(--powder-blue);
border-width: 1px 0px;
padding: 0.2em;
margin: 0.2em 0;
}
h3 {
border-top: 1px solid var(--powder-blue);
padding-top: 0.3em;
}

View File

@ -4,11 +4,8 @@ Functions for using this module from CLI
import sys import sys
from .compiler import phasm_compile
from .optimise.removeunusedfuncs import removeunusedfuncs
from .parser import phasm_parse from .parser import phasm_parse
from .type5.solver import phasm_type5 from .compiler import phasm_compile
def main(source: str, sink: str) -> int: def main(source: str, sink: str) -> int:
""" """
@ -19,9 +16,7 @@ def main(source: str, sink: str) -> int:
code_py = fil.read() code_py = fil.read()
our_module = phasm_parse(code_py) our_module = phasm_parse(code_py)
phasm_type5(our_module, verbose=False)
wasm_module = phasm_compile(our_module) wasm_module = phasm_compile(our_module)
removeunusedfuncs(wasm_module)
code_wat = wasm_module.to_wat() code_wat = wasm_module.to_wat()
with open(sink, 'w') as fil: with open(sink, 'w') as fil:

View File

@ -1,445 +0,0 @@
"""
The base class for build environments.
Contains nothing but the explicit compiler builtins.
"""
from typing import NamedTuple, Protocol, Sequence, Type
from ..type5 import constrainedexpr as type5constrainedexpr
from ..type5 import kindexpr as type5kindexpr
from ..type5 import record as type5record
from ..type5 import typeexpr as type5typeexpr
from ..typeclass import TypeClass
from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone
from .typeclassregistry import TypeClassRegistry
from .typerouter import TypeAllocSize, TypeName
TypeInfo = NamedTuple('TypeInfo', [
# Name of the type
('typ', str, ),
# What WebAssembly type to use when passing this value around
# For example in function arguments
('wasm_type', Type[WasmType]),
# What WebAssembly function to use when loading a value from memory
('wasm_load_func', str),
# What WebAssembly function to use when storing a value to memory
('wasm_store_func', str),
# When storing this value in memory, how many bytes do we use?
# Only valid for non-constructed types, see calculate_alloc_size
# Should match wasm_load_func / wasm_store_func
('alloc_size', int),
# When storing integers, the values can be stored as natural number
# (False) or as integer number (True). For other types, this is None.
('signed', bool | None),
])
class MissingImplementationWarning(Warning):
pass
class InternalImplementedMethodProtocol[G](Protocol):
def __call__(self, g: G, tv_map: dict[str, type5typeexpr.TypeExpr]) -> None:
pass
class BuildBase[G]:
__slots__ = (
'dynamic_array_type5_constructor',
'function_type5_constructor',
'static_array_type5_constructor',
'tuple_type5_constructor_map',
'none_type5',
'unit_type5',
'bool_type5',
'u8_type5',
'u32_type5',
'bytes_type5',
'type_info_map',
'type_info_constructed',
'types',
'type_classes',
'type_class_instances',
'methods',
'operators',
'type5_name',
'type5_alloc_size_root',
'type5_alloc_size_member',
)
dynamic_array_type5_constructor: type5typeexpr.TypeConstructor
"""
Constructor for arrays of runtime deterined length.
See type5_make_dynamic_array and type5_is_dynamic_array.
"""
function_type5_constructor: type5typeexpr.TypeConstructor
"""
Constructor for functions.
See type5_make_function and type5_is_function.
"""
static_array_type5_constructor: type5typeexpr.TypeConstructor
"""
Constructor for arrays of compiled time determined length.
See type5_make_static_array and type5_is_static_array.
"""
tuple_type5_constructor_map: dict[int, type5typeexpr.TypeConstructor]
"""
Map for constructors for tuples of each length.
See type5_make_tuple and type5_is_tuple.
"""
none_type5: type5typeexpr.AtomicType
"""
The none type.
TODO: Not sure this should be a buildin (rather than a Maybe type).
"""
unit_type5: type5typeexpr.AtomicType
"""
The unit type has exactly one value and can always be constructed.
Use for functions that don't take any arguments or do not produce any result.
This only make sense for IO functions.
TODO: Is this not what Python calls None?
"""
bool_type5: type5typeexpr.AtomicType
"""
The bool type, either True or False.
Builtin since functions require a boolean value in their test.
"""
u8_type5: type5typeexpr.AtomicType
"""
The u8 type, an integer value between 0 and 255.
Builtin since we can have bytes literals - which are the same as u8[...].
"""
u32_type5: type5typeexpr.AtomicType
"""
The u32 type, an integer value between 0 and 4 294 967 295.
Builtin since we can use this for indexing arrays and since
we use this for the length prefix on dynamic arrays.
"""
bytes_type5: type5typeexpr.TypeApplication
"""
The bytes type, a dynamic array with u8 elements.
Builtin since we can have bytes literals.
"""
type_info_map: dict[str, TypeInfo]
"""
Map from type name to the info of that type
"""
type_info_constructed: TypeInfo
"""
By default, constructed types are passed as pointers
NOTE: ALLOC SIZE IN THIS STRUCT DOES NOT WORK FOR CONSTRUCTED TYPES
USE calculate_alloc_size FOR ACCURATE RESULTS
Functions count as constructed types - even though they are
not memory pointers but table addresses instead.
"""
types: dict[str, type5typeexpr.TypeExpr]
"""
Types that are available without explicit import.
"""
type_classes: dict[str, TypeClass]
"""
Type classes that are available without explicit import.
"""
type_class_instances: dict[str, TypeClassRegistry[bool]]
"""
Type class instances that are available without explicit import.
"""
methods: dict[str, tuple[type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr, TypeClassRegistry[InternalImplementedMethodProtocol[G]]]]
"""
Methods that are available without explicit import.
"""
operators: dict[str, tuple[type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr, TypeClassRegistry[InternalImplementedMethodProtocol[G]]]]
"""
Operators that are available without explicit import.
"""
type5_name: TypeName
"""
Helper router to turn types into their human readable names.
"""
type5_alloc_size_root: TypeAllocSize
"""
Helper router to turn types into their allocation sizes.
This calculates the value when allocated directly.
"""
type5_alloc_size_member: TypeAllocSize
"""
Helper router to turn types into their allocation sizes.
This calculates the value when allocated as a member, e.g. in a tuple or struct.
"""
def __init__(self) -> None:
S = type5kindexpr.Star()
N = type5kindexpr.Nat()
self.dynamic_array_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> S, name="dynamic_array")
self.function_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> (S >> S), name="function")
self.static_array_type5_constructor = type5typeexpr.TypeConstructor(kind=N >> (S >> S), name='static_array')
self.tuple_type5_constructor_map = {}
self.none_type5 = type5typeexpr.AtomicType('None')
self.unit_type5 = type5typeexpr.AtomicType('()')
self.bool_type5 = type5typeexpr.AtomicType('bool')
self.u8_type5 = type5typeexpr.AtomicType('u8')
self.u32_type5 = type5typeexpr.AtomicType('u32')
self.bytes_type5 = self.type5_make_dynamic_array(self.u8_type5)
self.type_info_map = {
'None': TypeInfo('None', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
'()': TypeInfo('()', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
'bool': TypeInfo('bool', WasmTypeInt32, 'unreachable', 'unreachable', 0, None),
'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, False),
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
}
self.type_info_constructed = TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False)
self.types = {
'None': self.none_type5,
'()': self.unit_type5,
'bool': self.bool_type5,
'u8': self.u8_type5,
'u32': self.u32_type5,
'bytes': self.bytes_type5,
}
self.type_classes = {}
self.type_class_instances = {}
self.methods = {}
self.operators = {}
self.type5_name = TypeName(self)
self.type5_alloc_size_root = TypeAllocSize(self, is_member=False)
self.type5_alloc_size_member = TypeAllocSize(self, is_member=True)
def register_type_class(self, cls: TypeClass) -> None:
assert cls.name not in self.type_classes, 'Duplicate typeclass name'
self.type_classes[cls.name] = cls
self.type_class_instances[cls.name] = TypeClassRegistry()
for mtd_nam, mtd_typ in cls.methods.items():
assert mtd_nam not in self.methods, 'Duplicate typeclass method name'
self.methods[mtd_nam] = (mtd_typ, TypeClassRegistry(), )
for opr_nam, opr_typ in cls.operators.items():
assert opr_nam not in self.operators, 'Duplicate typeclass operator name'
self.operators[opr_nam] = (opr_typ, TypeClassRegistry(), )
def instance_type_class(
self,
cls: TypeClass,
*args: type5typeexpr.TypeExpr,
methods: dict[str, InternalImplementedMethodProtocol[G]] = {},
operators: dict[str, InternalImplementedMethodProtocol[G]] = {},
) -> None:
self.type_class_instances[cls.name].add(args, True)
assert len(cls.variables) == len(args)
for mtd_nam, mtd_imp in methods.items():
mtd_typ, mtd_rtr = self.methods[mtd_nam]
if isinstance(mtd_typ, type5constrainedexpr.ConstrainedExpr):
mtd_typ = mtd_typ.expr
for var, rep_expr in zip(cls.variables, args, strict=True):
mtd_typ = type5typeexpr.replace_variable(mtd_typ, var, rep_expr)
mtd_rtr.add((mtd_typ, ), mtd_imp)
for opr_nam, opr_imp in operators.items():
mtd_typ, opr_rtr = self.operators[opr_nam]
if isinstance(mtd_typ, type5constrainedexpr.ConstrainedExpr):
mtd_typ = mtd_typ.expr
for var, rep_expr in zip(cls.variables, args, strict=True):
mtd_typ = type5typeexpr.replace_variable(mtd_typ, var, rep_expr)
opr_rtr.add((mtd_typ, ), opr_imp)
def type5_make_function(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeExpr:
if not args:
raise TypeError("Functions must at least have a return type")
if len(args) == 1:
# Functions always take an argument
# To distinguish between a function without arguments and a value
# of the type, we have a unit type
# This type has one value so it can always be called
args = [self.unit_type5, *args]
res_type5 = None
for arg_type5 in reversed(args):
if res_type5 is None:
res_type5 = arg_type5
continue
res_type5 = type5typeexpr.TypeApplication(
constructor=type5typeexpr.TypeApplication(
constructor=self.function_type5_constructor,
argument=arg_type5,
),
argument=res_type5,
)
assert res_type5 is not None # type hint
return res_type5
def type5_is_function(self, typeexpr: type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr) -> list[type5typeexpr.TypeExpr] | None:
if isinstance(typeexpr, type5constrainedexpr.ConstrainedExpr):
typeexpr = typeexpr.expr
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
return None
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):
return None
if typeexpr.constructor.constructor != self.function_type5_constructor:
return None
arg0 = typeexpr.constructor.argument
if arg0 is self.unit_type5:
my_args = []
else:
my_args = [arg0]
arg1 = typeexpr.argument
more_args = self.type5_is_function(arg1)
if more_args is None:
return my_args + [arg1]
return my_args + more_args
def type5_make_tuple(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeApplication:
if not args:
raise TypeError("Tuples must at least one field")
arlen = len(args)
constructor = self.tuple_type5_constructor_map.get(arlen)
if constructor is None:
star = type5kindexpr.Star()
kind: type5kindexpr.Arrow = star >> star
for _ in range(len(args) - 1):
kind = star >> kind
constructor = type5typeexpr.TypeConstructor(kind=kind, name=f'tuple_{arlen}')
self.tuple_type5_constructor_map[arlen] = constructor
result: type5typeexpr.TypeApplication | None = None
for arg in args:
if result is None:
result = type5typeexpr.TypeApplication(
constructor=constructor,
argument=arg
)
continue
result = type5typeexpr.TypeApplication(
constructor=result,
argument=arg
)
assert result is not None # type hint
return result
def type5_is_tuple(self, typeexpr: type5typeexpr.TypeExpr) -> list[type5typeexpr.TypeExpr] | None:
arg_list = []
while isinstance(typeexpr, type5typeexpr.TypeApplication):
arg_list.append(typeexpr.argument)
typeexpr = typeexpr.constructor
if not isinstance(typeexpr, type5typeexpr.TypeConstructor):
return None
if typeexpr not in self.tuple_type5_constructor_map.values():
return None
return list(reversed(arg_list))
def type5_make_struct(self, name: str, fields: tuple[tuple[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication], ...]) -> type5record.Record:
return type5record.Record(name, fields)
def type5_is_struct(self, arg: type5typeexpr.TypeExpr) -> tuple[tuple[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication], ...] | None:
if not isinstance(arg, type5record.Record):
return None
return arg.fields
def type5_make_dynamic_array(self, arg: type5typeexpr.TypeExpr) -> type5typeexpr.TypeApplication:
return type5typeexpr.TypeApplication(
constructor=self.dynamic_array_type5_constructor,
argument=arg,
)
def type5_is_dynamic_array(self, typeexpr: type5typeexpr.TypeExpr) -> type5typeexpr.TypeExpr | None:
"""
Check if the given type expr is a concrete dynamic array type.
The element argument type is returned if so. Else, None is returned.
"""
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
return None
if typeexpr.constructor != self.dynamic_array_type5_constructor:
return None
return typeexpr.argument
def type5_make_static_array(self, len: int, arg: type5typeexpr.TypeExpr) -> type5typeexpr.TypeApplication:
return type5typeexpr.TypeApplication(
constructor=type5typeexpr.TypeApplication(
constructor=self.static_array_type5_constructor,
argument=type5typeexpr.TypeLevelNat(len),
),
argument=arg,
)
def type5_is_static_array(self, typeexpr: type5typeexpr.TypeExpr) -> tuple[int, type5typeexpr.TypeExpr] | None:
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
return None
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):
return None
if typeexpr.constructor.constructor != self.static_array_type5_constructor:
return None
assert isinstance(typeexpr.constructor.argument, type5typeexpr.TypeLevelNat) # type hint
return (
typeexpr.constructor.argument.value,
typeexpr.argument,
)

View File

@ -1,77 +0,0 @@
"""
The default class for build environments.
Contains the compiler builtins as well as some sane defaults.
"""
from ..type5 import typeexpr as type5typeexpr
from ..wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
)
from ..wasmgenerator import Generator
from .base import BuildBase, TypeInfo
from .typeclasses import (
bits,
convertable,
eq,
extendable,
floating,
foldable,
fractional,
integral,
intnum,
natnum,
ord,
promotable,
reinterpretable,
sized,
subscriptable,
)
class BuildDefault(BuildBase[Generator]):
__slots__ = ()
def __init__(self) -> None:
super().__init__()
self.type_info_map.update({
'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2, False),
'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False),
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True),
'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True),
'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4, True),
'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8, True),
'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4, None),
'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8, None),
})
self.types.update({
'u16': type5typeexpr.AtomicType('u16'),
'u64': type5typeexpr.AtomicType('u64'),
'i8': type5typeexpr.AtomicType('i8'),
'i16': type5typeexpr.AtomicType('i16'),
'i32': type5typeexpr.AtomicType('i32'),
'i64': type5typeexpr.AtomicType('i64'),
'f32': type5typeexpr.AtomicType('f32'),
'f64': type5typeexpr.AtomicType('f64'),
})
tc_list = [
bits,
eq, ord,
extendable, promotable,
convertable, reinterpretable,
natnum, intnum, fractional, floating,
integral,
foldable, subscriptable,
sized,
]
for tc in tc_list:
tc.load(self)
for tc in tc_list:
tc.wasm(self)

View File

@ -1,214 +0,0 @@
"""
The Bits type class is defined for types that can be bit manipulated.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
u32 = build.types['u32']
Bits = TypeClass('Bits', (a, ), methods={}, operators={})
has_bits_a = TypeClassConstraint(Bits, [a])
fn_a_u32_a = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, u32, a]),
constraints=(has_bits_a, ),
)
fn_a_a_a = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, a, a]),
constraints=(has_bits_a, ),
)
Bits.methods = {
'shl': fn_a_u32_a, # Logical shift left
'shr': fn_a_u32_a, # Logical shift right
'rotl': fn_a_u32_a, # Rotate bits left
'rotr': fn_a_u32_a, # Rotate bits right
# FIXME: Do we want to expose clz, ctz, popcnt?
}
Bits.operators = {
'&': fn_a_a_a, # Bit-wise and
'|': fn_a_a_a, # Bit-wise or
'^': fn_a_a_a, # Bit-wise xor
}
build.register_type_class(Bits)
def wasm_u8_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shl()
g.i32.const(0xFF)
g.i32.and_()
def wasm_u16_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shl()
g.i32.const(0xFFFF)
g.i32.and_()
def wasm_u32_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shl()
def wasm_u64_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shl()
def wasm_u8_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shr_u()
def wasm_u16_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shr_u()
def wasm_u32_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shr_u()
def wasm_u64_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shr_u()
def wasm_u8_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u8_rotl__')
def wasm_u16_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u16_rotl__')
def wasm_u32_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.rotl()
def wasm_u64_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.rotl()
def wasm_u8_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u8_rotr__')
def wasm_u16_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u16_rotr__')
def wasm_u32_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.rotr()
def wasm_u64_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.rotr()
def wasm_u8_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.and_()
def wasm_u16_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.and_()
def wasm_u32_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.and_()
def wasm_u64_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.and_()
def wasm_u8_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.or_()
def wasm_u16_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.or_()
def wasm_u32_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.or_()
def wasm_u64_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.or_()
def wasm_u8_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.xor()
def wasm_u16_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.xor()
def wasm_u32_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.xor()
def wasm_u64_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.xor()
def wasm(build: BuildBase[WasmGenerator]) -> None:
Bits = build.type_classes['Bits']
build.instance_type_class(Bits, build.types['u8'], methods={
'shl': wasm_u8_logical_shift_left,
'shr': wasm_u8_logical_shift_right,
'rotl': wasm_u8_rotate_left,
'rotr': wasm_u8_rotate_right,
}, operators={
'&': wasm_u8_bitwise_and,
'|': wasm_u8_bitwise_or,
'^': wasm_u8_bitwise_xor,
})
build.instance_type_class(Bits, build.types['u16'], methods={
'shl': wasm_u16_logical_shift_left,
'shr': wasm_u16_logical_shift_right,
'rotl': wasm_u16_rotate_left,
'rotr': wasm_u16_rotate_right,
}, operators={
'&': wasm_u16_bitwise_and,
'|': wasm_u16_bitwise_or,
'^': wasm_u16_bitwise_xor,
})
build.instance_type_class(Bits, build.types['u32'], methods={
'shl': wasm_u32_logical_shift_left,
'shr': wasm_u32_logical_shift_right,
'rotl': wasm_u32_rotate_left,
'rotr': wasm_u32_rotate_right,
}, operators={
'&': wasm_u32_bitwise_and,
'|': wasm_u32_bitwise_or,
'^': wasm_u32_bitwise_xor,
})
build.instance_type_class(Bits, build.types['u64'], methods={
'shl': wasm_u64_logical_shift_left,
'shr': wasm_u64_logical_shift_right,
'rotl': wasm_u64_rotate_left,
'rotr': wasm_u64_rotate_right,
}, operators={
'&': wasm_u64_bitwise_and,
'|': wasm_u64_bitwise_or,
'^': wasm_u64_bitwise_xor,
})

View File

@ -1,143 +0,0 @@
"""
The Convertable type class is defined for when a value from one type can be
converted to another type - but there's no real guarantee about precision or
value loss.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
b = TypeVariable(kind=Star(), name='b')
Convertable = TypeClass('Convertable', (a, b, ), methods={}, operators={})
has_convertable_a_b = TypeClassConstraint(Convertable, [a, b])
fn_a_b = ConstrainedExpr(
variables={a, b},
expr=build.type5_make_function([a, b]),
constraints=(has_convertable_a_b, ),
)
fn_b_a = ConstrainedExpr(
variables={a, b},
expr=build.type5_make_function([b, a]),
constraints=(has_convertable_a_b, ),
)
Convertable.methods = {
'convert': fn_a_b,
'truncate': fn_b_a, # To prevent name clas with Fractional
}
build.register_type_class(Convertable)
def wasm_u32_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.convert_i32_u()
def wasm_u32_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.convert_i32_u()
def wasm_u64_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.convert_i64_u()
def wasm_u64_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.convert_i64_u()
def wasm_i32_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.convert_i32_s()
def wasm_i32_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.convert_i32_s()
def wasm_i64_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.convert_i64_s()
def wasm_i64_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.convert_i64_s()
def wasm_u32_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.trunc_f32_u()
def wasm_u32_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.trunc_f64_u()
def wasm_u64_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.trunc_f32_u()
def wasm_u64_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.trunc_f64_u()
def wasm_i32_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.trunc_f32_s()
def wasm_i32_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.trunc_f64_s()
def wasm_i64_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.trunc_f32_s()
def wasm_i64_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.trunc_f64_s()
def wasm(build: BuildBase[WasmGenerator]) -> None:
Convertable = build.type_classes['Convertable']
build.instance_type_class(Convertable, build.types['u32'], build.types['f32'], methods={
'convert': wasm_u32_f32_convert,
'truncate': wasm_u32_f32_truncate,
})
build.instance_type_class(Convertable, build.types['u32'], build.types['f64'], methods={
'convert': wasm_u32_f64_convert,
'truncate': wasm_u32_f64_truncate,
})
build.instance_type_class(Convertable, build.types['u64'], build.types['f32'], methods={
'convert': wasm_u64_f32_convert,
'truncate': wasm_u64_f32_truncate,
})
build.instance_type_class(Convertable, build.types['u64'], build.types['f64'], methods={
'convert': wasm_u64_f64_convert,
'truncate': wasm_u64_f64_truncate,
})
build.instance_type_class(Convertable, build.types['i32'], build.types['f32'], methods={
'convert': wasm_i32_f32_convert,
'truncate': wasm_i32_f32_truncate,
})
build.instance_type_class(Convertable, build.types['i32'], build.types['f64'], methods={
'convert': wasm_i32_f64_convert,
'truncate': wasm_i32_f64_truncate,
})
build.instance_type_class(Convertable, build.types['i64'], build.types['f32'], methods={
'convert': wasm_i64_f32_convert,
'truncate': wasm_i64_f32_truncate,
})
build.instance_type_class(Convertable, build.types['i64'], build.types['f64'], methods={
'convert': wasm_i64_f64_convert,
'truncate': wasm_i64_f64_truncate,
})

View File

@ -1,159 +0,0 @@
"""
The Eq type class is defined for types that can be compered based on equality.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
Eq = TypeClass('Eq', (a, ), methods={}, operators={})
has_eq_a = TypeClassConstraint(Eq, [a])
fn_a_a_bool = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, a, build.bool_type5]),
constraints=(has_eq_a, ),
)
Eq.operators = {
'==': fn_a_a_bool,
'!=': fn_a_a_bool,
# FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization?
}
build.register_type_class(Eq)
def wasm_u8_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_u16_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_u32_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_u64_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.eq()
def wasm_i8_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_i16_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_i32_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_i64_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.eq()
def wasm_f32_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.eq()
def wasm_f64_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.eq()
def wasm_u8_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_u16_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_u32_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_u64_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.ne()
def wasm_i8_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_i16_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_i32_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_i64_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.ne()
def wasm_f32_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.ne()
def wasm_f64_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.ne()
def wasm(build: BuildBase[WasmGenerator]) -> None:
Eq = build.type_classes['Eq']
build.instance_type_class(Eq, build.types['u8'], operators={
'==': wasm_u8_equals,
'!=': wasm_u8_not_equals,
})
build.instance_type_class(Eq, build.types['u16'], operators={
'==': wasm_u16_equals,
'!=': wasm_u16_not_equals,
})
build.instance_type_class(Eq, build.types['u32'], operators={
'==': wasm_u32_equals,
'!=': wasm_u32_not_equals,
})
build.instance_type_class(Eq, build.types['u64'], operators={
'==': wasm_u64_equals,
'!=': wasm_u64_not_equals,
})
build.instance_type_class(Eq, build.types['i8'], operators={
'==': wasm_i8_equals,
'!=': wasm_i8_not_equals,
})
build.instance_type_class(Eq, build.types['i16'], operators={
'==': wasm_i16_equals,
'!=': wasm_i16_not_equals,
})
build.instance_type_class(Eq, build.types['i32'], operators={
'==': wasm_i32_equals,
'!=': wasm_i32_not_equals,
})
build.instance_type_class(Eq, build.types['i64'], operators={
'==': wasm_i64_equals,
'!=': wasm_i64_not_equals,
})
build.instance_type_class(Eq, build.types['f32'], operators={
'==': wasm_f32_equals,
'!=': wasm_f32_not_equals,
})
build.instance_type_class(Eq, build.types['f64'], operators={
'==': wasm_f64_equals,
'!=': wasm_f64_not_equals,
})

View File

@ -1,216 +0,0 @@
"""
The Extendable type class is defined for types that can safely be extended to a type
that can hold strictly more values. Going back will result in some values being lost.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
b = TypeVariable(kind=Star(), name='b')
Extendable = TypeClass('Extendable', (a, b, ), methods={}, operators={})
has_extendable_a_b = TypeClassConstraint(Extendable, [a, b])
fn_a_b = ConstrainedExpr(
variables={a, b},
expr=build.type5_make_function([a, b]),
constraints=(has_extendable_a_b, ),
)
fn_b_a = ConstrainedExpr(
variables={a, b},
expr=build.type5_make_function([b, a]),
constraints=(has_extendable_a_b, ),
)
Extendable.methods = {
'extend': fn_a_b,
'wrap': fn_b_a,
}
build.register_type_class(Extendable)
def wasm_u8_u16_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# u8 and u16 are both stored as u32
pass
def wasm_u8_u32_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# u8 is already stored as u32
pass
def wasm_u8_u64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
def wasm_u16_u32_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# u16 is already stored as u32
pass
def wasm_u16_u64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
def wasm_u32_u64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
def wasm_i8_i16_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# i8 is already stored as i32
pass
def wasm_i8_i32_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# i8 is already stored as i32
pass
def wasm_i8_i64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_s()
def wasm_i16_i32_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# i16 is already stored as i32
pass
def wasm_i16_i64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_s()
def wasm_i32_i64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_s()
def wasm_u8_u16_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFF)
g.i32.and_()
def wasm_u8_u32_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFF)
g.i32.and_()
def wasm_u8_u64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
g.i32.const(0xFF)
g.i32.and_()
def wasm_u16_u32_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFFFF)
g.i32.and_()
def wasm_u16_u64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
g.i32.const(0xFFFF)
g.i32.and_()
def wasm_u32_u64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
def wasm_i8_i16_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFF)
g.i32.and_()
def wasm_i8_i32_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFF)
g.i32.and_()
def wasm_i8_i64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
g.i32.const(0xFF)
g.i32.and_()
def wasm_i16_i32_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFFFF)
g.i32.and_()
def wasm_i16_i64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
g.i32.const(0xFFFF)
g.i32.and_()
def wasm_i32_i64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
def wasm(build: BuildBase[WasmGenerator]) -> None:
Extendable = build.type_classes['Extendable']
build.instance_type_class(Extendable, build.types['u8'], build.types['u16'], methods={
'extend': wasm_u8_u16_extend,
'wrap': wasm_u8_u16_wrap,
})
build.instance_type_class(Extendable, build.types['u8'], build.types['u32'], methods={
'extend': wasm_u8_u32_extend,
'wrap': wasm_u8_u32_wrap,
})
build.instance_type_class(Extendable, build.types['u8'], build.types['u64'], methods={
'extend': wasm_u8_u64_extend,
'wrap': wasm_u8_u64_wrap,
})
build.instance_type_class(Extendable, build.types['u16'], build.types['u32'], methods={
'extend': wasm_u16_u32_extend,
'wrap': wasm_u16_u32_wrap,
})
build.instance_type_class(Extendable, build.types['u16'], build.types['u64'], methods={
'extend': wasm_u16_u64_extend,
'wrap': wasm_u16_u64_wrap,
})
build.instance_type_class(Extendable, build.types['u32'], build.types['u64'], methods={
'extend': wasm_u32_u64_extend,
'wrap': wasm_u32_u64_wrap,
})
build.instance_type_class(Extendable, build.types['i8'], build.types['i16'], methods={
'extend': wasm_i8_i16_extend,
'wrap': wasm_i8_i16_wrap,
})
build.instance_type_class(Extendable, build.types['i8'], build.types['i32'], methods={
'extend': wasm_i8_i32_extend,
'wrap': wasm_i8_i32_wrap,
})
build.instance_type_class(Extendable, build.types['i8'], build.types['i64'], methods={
'extend': wasm_i8_i64_extend,
'wrap': wasm_i8_i64_wrap,
})
build.instance_type_class(Extendable, build.types['i16'], build.types['i32'], methods={
'extend': wasm_i16_i32_extend,
'wrap': wasm_i16_i32_wrap,
})
build.instance_type_class(Extendable, build.types['i16'], build.types['i64'], methods={
'extend': wasm_i16_i64_extend,
'wrap': wasm_i16_i64_wrap,
})
build.instance_type_class(Extendable, build.types['i32'], build.types['i64'], methods={
'extend': wasm_i32_i64_extend,
'wrap': wasm_i32_i64_wrap,
})

View File

@ -1,54 +0,0 @@
"""
The Floating type class is defined for Real numbers.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
Floating = TypeClass('Floating', (a, ), methods={}, operators={})
has_floating_a = TypeClassConstraint(Floating, [a])
fn_a_a = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, a]),
constraints=(has_floating_a, ),
)
Floating.methods = {
'sqrt': fn_a_a
}
# FIXME: inherited_classes=[Fractional]
# FIXME: Do we want to expose copysign?
build.register_type_class(Floating)
def wasm_f32_sqrt(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f32.sqrt')
def wasm_f64_sqrt(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f64.sqrt')
def wasm(build: BuildBase[WasmGenerator]) -> None:
Floating = build.type_classes['Floating']
build.instance_type_class(Floating, build.types['f32'], methods={
'sqrt': wasm_f32_sqrt,
})
build.instance_type_class(Floating, build.types['f64'], methods={
'sqrt': wasm_f64_sqrt,
})

View File

@ -1,643 +0,0 @@
"""
The Foldable type class is defined for when a value iterated over.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Nat, Star
from ...type5.typeexpr import (
TypeApplication,
TypeExpr,
TypeLevelNat,
TypeVariable,
replace_variable,
)
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase, InternalImplementedMethodProtocol
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
b = TypeVariable(kind=Star(), name='b')
t = TypeVariable(kind=Star() >> Star(), name='t')
t_a = TypeApplication(constructor=t, argument=a)
NatNum = build.type_classes['NatNum']
Foldable = TypeClass('Foldable', (t, ), methods={}, operators={})
has_foldable_t = TypeClassConstraint(Foldable, [t])
has_natnum_a = TypeClassConstraint(NatNum, [a])
fn_sum = ConstrainedExpr(
variables={t, a},
expr=build.type5_make_function([t_a, a]),
constraints=(has_foldable_t, has_natnum_a, ),
)
fn_b_a_b = build.type5_make_function([b, a, b])
fn_foldl = ConstrainedExpr(
variables={t, a, b},
expr=build.type5_make_function([fn_b_a_b, b, t_a, b]),
constraints=(has_foldable_t, ),
)
fn_a_b_b = build.type5_make_function([a, b, b])
fn_foldr = ConstrainedExpr(
variables={t, a, b},
expr=build.type5_make_function([fn_a_b_b, b, t_a, b]),
constraints=(has_foldable_t, ),
)
Foldable.methods = {
'sum': fn_sum,
'foldl': fn_foldl,
'foldr': fn_foldr,
}
build.register_type_class(Foldable)
class FoldableCodeGenerator:
def __init__(self, build: BuildBase[WasmGenerator]) -> None:
self.build = build
def get_natnum(self, sa_type: TypeExpr) -> tuple[dict[str, TypeExpr], InternalImplementedMethodProtocol[WasmGenerator]]:
natnum_type, natnum_router = self.build.operators['+']
assert isinstance(natnum_type, ConstrainedExpr)
assert len(natnum_type.variables) == 1
natnum_a = next(iter(natnum_type.variables))
natnum_type = replace_variable(natnum_type.expr, natnum_a, sa_type)
impl_lookup = natnum_router.get((natnum_type, ))
assert impl_lookup is not None
return impl_lookup
def wasm_dynamic_array_sum(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
sa_type = tv_map['a']
ptr_type_info = self.build.type_info_constructed
sa_type_info = self.build.type_info_map.get(sa_type.name)
if sa_type_info is None:
sa_type_info = ptr_type_info
natnum_kwargs, natnum_impl = self.get_natnum(sa_type)
# Definitions
sum_adr = g.temp_var_t(ptr_type_info.wasm_type, 'sum_adr')
sum_stop = g.temp_var_t(ptr_type_info.wasm_type, 'sum_stop')
with g.block(params=['i32'], result=sa_type_info.wasm_type):
# Stack: [adr] -> [] ; sum_adr=ard
g.local.set(sum_adr)
# Stack: [] ; sum_stop = adr + 4 + len(adr) * sa_type_info.alloc_size
g.nop(comment='Calculate address at which to stop looping')
g.local.get(sum_adr)
g.i32.load()
g.i32.const(sa_type_info.alloc_size)
g.i32.mul()
g.local.get(sum_adr)
g.i32.add()
g.i32.const(4)
g.i32.add()
g.local.set(sum_stop)
# Stack: [] -> [sum] ; sum_adr += 4
g.nop(comment='Get the first array value as starting point')
g.local.get(sum_adr)
g.i32.const(4)
g.i32.add()
g.local.tee(sum_adr)
g.add_statement(sa_type_info.wasm_load_func)
# Since we did the first one, increase adr
# Stack: [sum] -> [sum] ; sum_adr = sum_adr + sa_type_info.alloc_size
g.local.get(sum_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.add()
g.local.set(sum_adr)
g.local.get(sum_adr)
g.local.get(sum_stop)
g.i32.lt_u()
with g.if_(params=[sa_type_info.wasm_type], result=sa_type_info.wasm_type):
with g.loop(params=[sa_type_info.wasm_type], result=sa_type_info.wasm_type):
# sum = sum + *adr
# Stack: [sum] -> [sum + *adr]
g.nop(comment='Add array value')
g.local.get(sum_adr)
g.add_statement(sa_type_info.wasm_load_func)
natnum_impl(g, natnum_kwargs)
# adr = adr + sa_type_info.alloc_size
# Stack: [sum] -> [sum]
g.nop(comment='Calculate address of the next value')
g.local.get(sum_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.add()
g.local.tee(sum_adr)
# loop if adr < stop
g.nop(comment='Check if address exceeds array bounds')
g.local.get(sum_stop)
g.i32.lt_u()
g.br_if(0)
# else: sum x[1] === x => so we don't need to loop
# End result: [sum]
def wasm_static_array_sum(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
sa_type = tv_map['a']
sa_len = tv_map['n']
assert isinstance(sa_len, TypeLevelNat)
if sa_len.value < 1:
raise NotImplementedError('Default value in case sum is empty')
ptr_type_info = self.build.type_info_constructed
sa_type_info = self.build.type_info_map.get(sa_type.name)
if sa_type_info is None:
sa_type_info = ptr_type_info
natnum_kwargs, natnum_impl = self.get_natnum(sa_type)
# Definitions
sum_adr = g.temp_var_t(ptr_type_info.wasm_type, 'sum_adr')
sum_stop = g.temp_var_t(ptr_type_info.wasm_type, 'sum_stop')
# Stack before: [adr]
# Stack after: [sum]
# adr = {address of what's currently on stack}
# Stack: [adr] -> []
g.nop(comment=f'Start sum for {sa_type.name}[{sa_len.value}]')
g.local.set(sum_adr)
# stop = adr + ar_len * sa_type_info.alloc_size
# Stack: []
g.nop(comment='Calculate address at which to stop looping')
g.local.get(sum_adr)
g.i32.const(sa_len.value * sa_type_info.alloc_size)
g.i32.add()
g.local.set(sum_stop)
# sum = *adr
# Stack: [] -> [sum]
g.nop(comment='Get the first array value as starting point')
g.local.get(sum_adr)
g.add_statement(sa_type_info.wasm_load_func)
# Since we did the first one, increase adr
# adr = adr + sa_type_info.alloc_size
# Stack: [sum] -> [sum]
g.local.get(sum_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.add()
g.local.set(sum_adr)
if sa_len.value > 1:
with g.loop(params=[sa_type_info.wasm_type], result=sa_type_info.wasm_type):
# sum = sum + *adr
# Stack: [sum] -> [sum + *adr]
g.nop(comment='Add array value')
g.local.get(sum_adr)
g.add_statement(sa_type_info.wasm_load_func)
natnum_impl(g, natnum_kwargs)
# adr = adr + sa_type_info.alloc_size
# Stack: [sum] -> [sum]
g.nop(comment='Calculate address of the next value')
g.local.get(sum_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.add()
g.local.tee(sum_adr)
# loop if adr < stop
g.nop(comment='Check if address exceeds array bounds')
g.local.get(sum_stop)
g.i32.lt_u()
g.br_if(0)
# else: sum x[1] === x => so we don't need to loop
g.nop(comment=f'Completed sum for {sa_type.name}[{sa_len.value}]')
# End result: [sum]
def wasm_dynamic_array_foldl(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
sa_type = tv_map['a']
res_type = tv_map['b']
ptr_type_info = self.build.type_info_constructed
u32_type_info = self.build.type_info_map['u32']
sa_type_info = self.build.type_info_map.get(sa_type.name)
if sa_type_info is None:
sa_type_info = ptr_type_info
res_type_info = self.build.type_info_map.get(res_type.name)
if res_type_info is None:
res_type_info = ptr_type_info
# Definitions
fold_adr = g.temp_var_t(ptr_type_info.wasm_type, 'fold_adr')
fold_stop = g.temp_var_t(ptr_type_info.wasm_type, 'fold_stop')
fold_init = g.temp_var_t(res_type_info.wasm_type, 'fold_init')
fold_func = g.temp_var_t(ptr_type_info.wasm_type, 'fold_func')
fold_len = g.temp_var_t(u32_type_info.wasm_type, 'fold_len')
with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldl a={sa_type.name} b={res_type.name}'):
# Stack: [fn*, b, sa*] -> [fn*, b]
g.local.tee(fold_adr) # Store address, but also keep it for loading the length
g.i32.load() # Load the length
g.local.set(fold_len) # Store the length
# Stack: [fn*, b] -> [fn*]
g.local.set(fold_init)
# Stack: [fn*] -> []
g.local.set(fold_func)
# Stack: [] -> [b]
g.nop(comment='No applications if array is empty')
g.local.get(fold_init)
g.local.get(fold_len)
g.i32.eqz() # If the array is empty
g.br_if(0) # Then the base value is the result
# Stack: [b] -> [b] ; fold_adr=fold_adr + 4
g.nop(comment='Skip the header')
g.local.get(fold_adr)
g.i32.const(4)
g.i32.add()
g.local.set(fold_adr)
# Stack: [b] -> [b]
g.nop(comment='Apply the first function call')
g.local.get(fold_adr)
g.add_statement(sa_type_info.wasm_load_func)
g.local.get(fold_func)
g.call_indirect([res_type_info.wasm_type, sa_type_info.wasm_type], res_type_info.wasm_type)
# Stack: [b] -> [b]
g.nop(comment='No loop if there is only one item')
g.local.get(fold_len)
g.i32.const(1)
g.i32.eq()
g.br_if(0) # just one value, don't need to loop
# Stack: [b] -> [b] ; fold_stop=fold_adr + (sa_len.value * sa_type_info.alloc_size)
g.nop(comment='Calculate address at which to stop looping')
g.local.get(fold_adr)
g.local.get(fold_len)
g.i32.const(sa_type_info.alloc_size)
g.i32.mul()
g.i32.add()
g.local.set(fold_stop)
# Stack: [b] -> [b] ; fold_adr = fold_adr + sa_type_info.alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.add()
g.local.set(fold_adr)
with g.loop(params=[res_type_info.wasm_type], result=res_type_info.wasm_type):
# Stack: [b] -> [b]
g.nop(comment='Apply function call')
g.local.get(fold_adr)
g.add_statement(sa_type_info.wasm_load_func)
g.local.get(fold_func)
g.call_indirect([res_type_info.wasm_type, sa_type_info.wasm_type], res_type_info.wasm_type)
# Stack: [b] -> [b] ; fold_adr = fold_adr + sa_type_info.alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.add()
g.local.tee(fold_adr)
# loop if adr > stop
# Stack: [b] -> [b]
g.nop(comment='Check if address exceeds array bounds')
g.local.get(fold_stop)
g.i32.lt_u()
g.br_if(0)
# Stack: [b]
def wasm_static_array_foldl(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
sa_type = tv_map['a']
sa_len = tv_map['n']
res_type = tv_map['b']
assert isinstance(sa_len, TypeLevelNat)
ptr_type_info = self.build.type_info_constructed
sa_type_info = self.build.type_info_map.get(sa_type.name)
if sa_type_info is None:
sa_type_info = ptr_type_info
res_type_info = self.build.type_info_map.get(res_type.name)
if res_type_info is None:
res_type_info = ptr_type_info
# Definitions
fold_adr = g.temp_var_t(ptr_type_info.wasm_type, 'fold_adr')
fold_stop = g.temp_var_t(ptr_type_info.wasm_type, 'fold_stop')
fold_init = g.temp_var_t(res_type_info.wasm_type, 'fold_init')
fold_func = g.temp_var_t(ptr_type_info.wasm_type, 'fold_func')
with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldl a={sa_type.name} n={sa_len.value} b={res_type.name}'):
# Stack: [fn*, b, sa*] -> [fn*, b]
g.local.set(fold_adr)
# Stack: [fn*, b] -> [fn*]
g.local.set(fold_init)
# Stack: [fn*] -> []
g.local.set(fold_func)
if sa_len.value < 1:
g.local.get(fold_init)
return
# Stack: [] -> [b]
g.nop(comment='Apply the first function call')
g.local.get(fold_init)
g.local.get(fold_adr)
g.add_statement(sa_type_info.wasm_load_func)
g.local.get(fold_func)
g.call_indirect([res_type_info.wasm_type, sa_type_info.wasm_type], res_type_info.wasm_type)
if sa_len.value > 1:
# Stack: [b] -> [b] ; fold_stop=fold_adr + (sa_len.value * sa_type_info.alloc_size)
g.nop(comment='Calculate address at which to stop looping')
g.local.get(fold_adr)
g.i32.const(sa_len.value * sa_type_info.alloc_size)
g.i32.add()
g.local.set(fold_stop)
# Stack: [b] -> [b] ; fold_adr = fold_adr + sa_type_info.alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.add()
g.local.set(fold_adr)
with g.loop(params=[res_type_info.wasm_type], result=res_type_info.wasm_type):
# Stack: [b] -> [b]
g.nop(comment='Apply function call')
g.local.get(fold_adr)
g.add_statement(sa_type_info.wasm_load_func)
g.local.get(fold_func)
g.call_indirect([res_type_info.wasm_type, sa_type_info.wasm_type], res_type_info.wasm_type)
# Stack: [b] -> [b] ; fold_adr = fold_adr + sa_type_info.alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.add()
g.local.tee(fold_adr)
# loop if adr > stop
# Stack: [b] -> [b]
g.nop(comment='Check if address exceeds array bounds')
g.local.get(fold_stop)
g.i32.lt_u()
g.br_if(0)
# else: just one value, don't need to loop
# Stack: [b]
def wasm_dynamic_array_foldr(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
sa_type = tv_map['a']
res_type = tv_map['b']
ptr_type_info = self.build.type_info_constructed
u32_type_info = self.build.type_info_map['u32']
sa_type_info = self.build.type_info_map.get(sa_type.name)
if sa_type_info is None:
sa_type_info = ptr_type_info
res_type_info = self.build.type_info_map.get(res_type.name)
if res_type_info is None:
res_type_info = ptr_type_info
# Definitions
fold_adr = g.temp_var_t(ptr_type_info.wasm_type, 'fold_adr')
fold_stop = g.temp_var_t(ptr_type_info.wasm_type, 'fold_stop')
fold_tmp = g.temp_var_t(res_type_info.wasm_type, 'fold_tmp')
fold_func = g.temp_var_t(ptr_type_info.wasm_type, 'fold_func')
fold_len = g.temp_var_t(u32_type_info.wasm_type, 'fold_len')
with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldr a={sa_type.name} b={res_type.name}'):
# Stack: [fn*, b, sa*] -> [fn*, b] ; fold_adr=fn*, fold_tmp=b, fold_func=fn*, fold_len=*sa
g.local.tee(fold_adr) # Store address, but also keep it for loading the length
g.i32.load() # Load the length
g.local.set(fold_len) # Store the length
# Stack: [fn*, b] -> [fn*]
g.local.set(fold_tmp)
# Stack: [fn*] -> []
g.local.set(fold_func)
# Stack: [] -> []
g.nop(comment='No applications if array is empty')
g.local.get(fold_tmp)
g.local.get(fold_len)
g.i32.eqz() # If the array is empty
g.br_if(0) # Then the base value is the result
g.drop() # Else drop the value for now
# Stack: [b] -> [b] ; fold_adr=fold_adr + 4
g.nop(comment='Skip the header')
g.local.get(fold_adr)
g.i32.const(4)
g.i32.add()
g.local.set(fold_adr)
# Stack: [] -> [] ; fold_stop=fold_adr
g.nop(comment='Calculate address at which to stop looping')
g.local.get(fold_adr)
g.local.set(fold_stop)
# Stack: [] -> [] ; fold_adr=fold_adr + (sa_len.value - 1) * sa_type_info.alloc_size
g.nop(comment='Calculate address at which to start looping')
g.local.get(fold_adr)
g.local.get(fold_len)
g.i32.const(1)
g.i32.sub()
g.i32.const(sa_type_info.alloc_size)
g.i32.mul()
g.i32.add()
g.local.set(fold_adr)
# Stack: [] -> [b]
g.nop(comment='Apply the first function call')
g.local.get(fold_adr)
g.add_statement(sa_type_info.wasm_load_func)
g.local.get(fold_tmp)
g.local.get(fold_func)
g.call_indirect([sa_type_info.wasm_type, res_type_info.wasm_type], res_type_info.wasm_type)
# Stack: [b] -> [b]
g.nop(comment='Check if more than one entry')
g.local.get(fold_len)
g.i32.const(1)
g.i32.eq() # If the array has only item
g.br_if(0) # Then the the first application is sufficient
# Stack: [b] -> [b] ; fold_adr = fold_adr - sa_type_info.alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.sub()
g.local.set(fold_adr)
with g.loop(params=[res_type_info.wasm_type], result=res_type_info.wasm_type):
g.nop(comment='Apply function call')
# Stack [b] since we don't have proper stack switching opcodes
# Stack: [b] -> []
g.local.set(fold_tmp)
# Stack: [] -> [a]
g.local.get(fold_adr)
g.add_statement(sa_type_info.wasm_load_func)
# Stack [a] -> [a, b]
g.local.get(fold_tmp)
# Stack [a, b] -> [b]
g.local.get(fold_func)
g.call_indirect([sa_type_info.wasm_type, res_type_info.wasm_type], res_type_info.wasm_type)
# Stack: [b] -> [b] ; fold_adr = fold_adr - sa_type_info.alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.sub()
g.local.tee(fold_adr)
# loop if adr >= stop
# Stack: [b] -> [b]
g.nop(comment='Check if address exceeds array bounds')
g.local.get(fold_stop)
g.i32.ge_u()
g.br_if(0)
# Stack: [b]
def wasm_static_array_foldr(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
sa_type = tv_map['a']
sa_len = tv_map['n']
res_type = tv_map['b']
assert isinstance(sa_len, TypeLevelNat)
ptr_type_info = self.build.type_info_constructed
sa_type_info = self.build.type_info_map.get(sa_type.name)
if sa_type_info is None:
sa_type_info = ptr_type_info
res_type_info = self.build.type_info_map.get(res_type.name)
if res_type_info is None:
res_type_info = ptr_type_info
# Definitions
fold_adr = g.temp_var_t(ptr_type_info.wasm_type, 'fold_adr')
fold_stop = g.temp_var_t(ptr_type_info.wasm_type, 'fold_stop')
fold_tmp = g.temp_var_t(res_type_info.wasm_type, 'fold_tmp')
fold_func = g.temp_var_t(ptr_type_info.wasm_type, 'fold_func')
with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldr a={sa_type.name} n={sa_len.value} b={res_type.name}'):
# Stack: [fn*, b, sa*] -> [fn*, b] ; fold_adr=fn*, fold_tmp=b, fold_func=fn*
g.local.set(fold_adr)
# Stack: [fn*, b] -> [fn*]
g.local.set(fold_tmp)
# Stack: [fn*] -> []
g.local.set(fold_func)
if sa_len.value < 1:
g.local.get(fold_tmp)
return
# Stack: [] -> [] ; fold_stop=fold_adr
g.nop(comment='Calculate address at which to stop looping')
g.local.get(fold_adr)
g.local.set(fold_stop)
# Stack: [] -> [] ; fold_adr=fold_adr + (sa_len.value - 1) * sa_type_info.alloc_size
g.nop(comment='Calculate address at which to start looping')
g.local.get(fold_adr)
g.i32.const((sa_len.value - 1) * sa_type_info.alloc_size)
g.i32.add()
g.local.set(fold_adr)
# Stack: [] -> [b]
g.nop(comment='Get the init value and first array value as starting point')
g.local.get(fold_adr)
g.add_statement(sa_type_info.wasm_load_func)
g.local.get(fold_tmp)
g.local.get(fold_func)
g.call_indirect([sa_type_info.wasm_type, res_type_info.wasm_type], res_type_info.wasm_type)
if sa_len.value > 1:
# Stack: [b] -> [b] ; fold_adr = fold_adr - sa_type_info.alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.sub()
g.local.set(fold_adr)
with g.loop(params=[res_type_info.wasm_type], result=res_type_info.wasm_type):
g.nop(comment='Apply function call')
# Stack [b] since we don't have proper stack switching opcodes
# Stack: [b] -> []
g.local.set(fold_tmp)
# Stack: [] -> [a]
g.local.get(fold_adr)
g.add_statement(sa_type_info.wasm_load_func)
# Stack [a] -> [a, b]
g.local.get(fold_tmp)
# Stack [a, b] -> [b]
g.local.get(fold_func)
g.call_indirect([sa_type_info.wasm_type, res_type_info.wasm_type], res_type_info.wasm_type)
# Stack: [b] -> [b] ; fold_adr = fold_adr - sa_type_info.alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_info.alloc_size)
g.i32.sub()
g.local.tee(fold_adr)
# loop if adr >= stop
# Stack: [b] -> [b]
g.nop(comment='Check if address exceeds array bounds')
g.local.get(fold_stop)
g.i32.ge_u()
g.br_if(0)
# else: just one value, don't need to loop
# Stack: [b]
def wasm(build: BuildBase[WasmGenerator]) -> None:
Foldable = build.type_classes['Foldable']
n = TypeVariable(kind=Nat(), name='n')
gen = FoldableCodeGenerator(build)
build.instance_type_class(Foldable, build.dynamic_array_type5_constructor, methods={
'sum': gen.wasm_dynamic_array_sum,
'foldl': gen.wasm_dynamic_array_foldl,
'foldr': gen.wasm_dynamic_array_foldr,
})
foo = TypeApplication(constructor=build.static_array_type5_constructor, argument=n)
build.instance_type_class(Foldable, foo, methods={
'sum': gen.wasm_static_array_sum,
'foldl': gen.wasm_static_array_foldl,
'foldr': gen.wasm_static_array_foldr,
})

View File

@ -1,107 +0,0 @@
"""
The Fractional type class is defined for numeric types that can be (precisely) divided.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
Fractional = TypeClass('Fractional', (a, ), methods={}, operators={})
has_fractional_a = TypeClassConstraint(Fractional, [a])
fn_a_a = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, a]),
constraints=(has_fractional_a, ),
)
fn_a_a_a = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, a, a]),
constraints=(has_fractional_a, ),
)
Fractional.methods = {
'ceil': fn_a_a,
'floor': fn_a_a,
'trunc': fn_a_a,
'nearest': fn_a_a,
}
Fractional.operators = {
'/': fn_a_a_a,
}
# FIXME: inherited_classes=[NatNum])
build.register_type_class(Fractional)
def wasm_f32_ceil(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.ceil()
def wasm_f64_ceil(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.ceil()
def wasm_f32_floor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.floor()
def wasm_f64_floor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.floor()
def wasm_f32_trunc(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.trunc()
def wasm_f64_trunc(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.trunc()
def wasm_f32_nearest(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.nearest()
def wasm_f64_nearest(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.nearest()
def wasm_f32_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.div()
def wasm_f64_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.div()
def wasm(build: BuildBase[WasmGenerator]) -> None:
Fractional = build.type_classes['Fractional']
build.instance_type_class(Fractional, build.types['f32'], methods={
'ceil': wasm_f32_ceil,
'floor': wasm_f32_floor,
'trunc': wasm_f32_trunc,
'nearest': wasm_f32_nearest,
}, operators={
'/': wasm_f32_div,
})
build.instance_type_class(Fractional, build.types['f64'], methods={
'ceil': wasm_f64_ceil,
'floor': wasm_f64_floor,
'trunc': wasm_f64_trunc,
'nearest': wasm_f64_nearest,
}, operators={
'/': wasm_f64_div,
})

View File

@ -1,88 +0,0 @@
"""
The Integral type class is defined for types that can only be approximately divided.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
Integral = TypeClass('Integral', (a, ), methods={}, operators={})
has_integral_a = TypeClassConstraint(Integral, [a])
fn_a_a_a = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, a, a]),
constraints=(has_integral_a, ),
)
Integral.operators = {
'//': fn_a_a_a,
'%': fn_a_a_a,
}
# FIXME: inherited_classes=[NatNum]
build.register_type_class(Integral)
def wasm_u32_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.div_u')
def wasm_u64_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.div_u')
def wasm_i32_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.div_s')
def wasm_i64_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.div_s')
def wasm_u32_rem(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.rem_u')
def wasm_u64_rem(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.rem_u')
def wasm_i32_rem(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.rem_s')
def wasm_i64_rem(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.rem_s')
def wasm(build: BuildBase[WasmGenerator]) -> None:
Integral = build.type_classes['Integral']
build.instance_type_class(Integral, build.types['u32'], operators={
'//': wasm_u32_div,
'%': wasm_u32_rem,
})
build.instance_type_class(Integral, build.types['u64'], operators={
'//': wasm_u64_div,
'%': wasm_u64_rem,
})
build.instance_type_class(Integral, build.types['i32'], operators={
'//': wasm_i32_div,
'%': wasm_i32_rem,
})
build.instance_type_class(Integral, build.types['i64'], operators={
'//': wasm_i64_div,
'%': wasm_i64_rem,
})

View File

@ -1,89 +0,0 @@
"""
The IntNum type class is defined for Integer Number types.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
IntNum = TypeClass('IntNum', (a, ), methods={}, operators={})
has_intnum_a = TypeClassConstraint(IntNum, [a])
fn_a_a = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, a]),
constraints=(has_intnum_a, ),
)
IntNum.methods = {
'abs': fn_a_a,
'neg': fn_a_a,
}
# FIXME: inherited_classes=[NatNum])
build.register_type_class(IntNum)
def wasm_i32_abs(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_abs__')
def wasm_i64_abs(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i64_abs__')
def wasm_f32_abs(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.abs()
def wasm_f64_abs(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.abs()
def wasm_i32_neg(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(-1)
g.i32.mul()
def wasm_i64_neg(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.const(-1)
g.i64.mul()
def wasm_f32_neg(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.neg()
def wasm_f64_neg(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.neg()
def wasm(build: BuildBase[WasmGenerator]) -> None:
IntNum = build.type_classes['IntNum']
build.instance_type_class(IntNum, build.types['i32'], methods={
'abs': wasm_i32_abs,
'neg': wasm_i32_neg,
})
build.instance_type_class(IntNum, build.types['i64'], methods={
'abs': wasm_i64_abs,
'neg': wasm_i64_neg,
})
build.instance_type_class(IntNum, build.types['f32'], methods={
'abs': wasm_f32_abs,
'neg': wasm_f32_neg,
})
build.instance_type_class(IntNum, build.types['f64'], methods={
'abs': wasm_f64_abs,
'neg': wasm_f64_neg,
})

View File

@ -1,227 +0,0 @@
"""
The NatNum type class is defined for Natural Number types.
These cannot be negative so functions like abs and neg make no sense.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
u32 = build.types['u32']
NatNum = TypeClass('NatNum', (a, ), methods={}, operators={})
has_natnum_a = TypeClassConstraint(NatNum, [a])
fn_a_a_a = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, a, a]),
constraints=(has_natnum_a, ),
)
fn_a_u32_a = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, u32, a]),
constraints=(has_natnum_a, ),
)
NatNum.operators = {
'+': fn_a_a_a,
'-': fn_a_a_a,
'*': fn_a_a_a,
'<<': fn_a_u32_a, # Arithmic shift left
'>>': fn_a_u32_a, # Arithmic shift right
}
build.register_type_class(NatNum)
## ###
## class NatNum
def wasm_u32_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.add')
def wasm_u64_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.add')
def wasm_i32_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.add')
def wasm_i64_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.add')
def wasm_f32_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f32.add')
def wasm_f64_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f64.add')
def wasm_u32_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.sub')
def wasm_u64_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.sub')
def wasm_i32_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.sub')
def wasm_i64_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.sub')
def wasm_f32_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f32.sub')
def wasm_f64_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f64.sub')
def wasm_u32_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.mul')
def wasm_u64_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.mul')
def wasm_i32_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.mul')
def wasm_i64_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.mul')
def wasm_f32_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f32.mul')
def wasm_f64_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f64.mul')
def wasm_u32_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shl()
def wasm_u64_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shl()
def wasm_i32_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shl()
def wasm_i64_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shl()
def wasm_f32_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_pow2__')
g.f32.convert_i32_u()
g.f32.mul()
def wasm_f64_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_pow2__')
g.f64.convert_i32_u()
g.f64.mul()
def wasm_u32_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shr_u()
def wasm_u64_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shr_u()
def wasm_i32_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shr_s()
def wasm_i64_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shr_s()
def wasm_f32_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_pow2__')
g.f32.convert_i32_u()
g.f32.div()
def wasm_f64_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_pow2__')
g.f64.convert_i32_u()
g.f64.div()
def wasm(build: BuildBase[WasmGenerator]) -> None:
NatNum = build.type_classes['NatNum']
build.instance_type_class(NatNum, build.types['u32'], operators={
'+': wasm_u32_add,
'-': wasm_u32_sub,
'*': wasm_u32_mul,
'<<': wasm_u32_arithmic_shift_left,
'>>': wasm_u32_arithmic_shift_right,
})
build.instance_type_class(NatNum, build.types['u64'], operators={
'+': wasm_u64_add,
'-': wasm_u64_sub,
'*': wasm_u64_mul,
'<<': wasm_u64_arithmic_shift_left,
'>>': wasm_u64_arithmic_shift_right,
})
build.instance_type_class(NatNum, build.types['i32'], operators={
'+': wasm_i32_add,
'-': wasm_i32_sub,
'*': wasm_i32_mul,
'<<': wasm_i32_arithmic_shift_left,
'>>': wasm_i32_arithmic_shift_right,
})
build.instance_type_class(NatNum, build.types['i64'], operators={
'+': wasm_i64_add,
'-': wasm_i64_sub,
'*': wasm_i64_mul,
'<<': wasm_i64_arithmic_shift_left,
'>>': wasm_i64_arithmic_shift_right,
})
build.instance_type_class(NatNum, build.types['f32'], operators={
'+': wasm_f32_add,
'-': wasm_f32_sub,
'*': wasm_f32_mul,
'<<': wasm_f32_arithmic_shift_left,
'>>': wasm_f32_arithmic_shift_right,
})
build.instance_type_class(NatNum, build.types['f64'], operators={
'+': wasm_f64_add,
'-': wasm_f64_sub,
'*': wasm_f64_mul,
'<<': wasm_f64_arithmic_shift_left,
'>>': wasm_f64_arithmic_shift_right,
})

View File

@ -1,383 +0,0 @@
"""
The Ord type class is defined for totally ordered datatypes.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
Ord = TypeClass('Ord', (a, ), methods={}, operators={})
has_ord_a = TypeClassConstraint(Ord, [a])
fn_a_a_a = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, a, a]),
constraints=(has_ord_a, ),
)
fn_a_a_bool = ConstrainedExpr(
variables={a},
expr=build.type5_make_function([a, a, build.bool_type5]),
constraints=(has_ord_a, ),
)
Ord.methods = {
'min': fn_a_a_a,
'max': fn_a_a_a,
}
Ord.operators = {
'<': fn_a_a_bool,
'<=': fn_a_a_bool,
'>': fn_a_a_bool,
'>=': fn_a_a_bool,
}
#FIXME: }, inherited_classes=[Eq])
build.register_type_class(Ord)
def wasm_u8_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_min__')
def wasm_u16_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_min__')
def wasm_u32_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_min__')
def wasm_u64_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u64_min__')
def wasm_i8_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_min__')
def wasm_i16_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_min__')
def wasm_i32_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_min__')
def wasm_i64_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i64_min__')
def wasm_f32_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.min()
def wasm_f64_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.min()
def wasm_u8_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_max__')
def wasm_u16_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_max__')
def wasm_u32_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_max__')
def wasm_u64_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u64_max__')
def wasm_i8_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_max__')
def wasm_i16_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_max__')
def wasm_i32_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_max__')
def wasm_i64_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i64_max__')
def wasm_f32_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.max()
def wasm_f64_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.max()
def wasm_u8_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_u()
def wasm_u16_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_u()
def wasm_u32_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_u()
def wasm_u64_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.lt_u()
def wasm_i8_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_s()
def wasm_i16_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_s()
def wasm_i32_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_s()
def wasm_i64_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.lt_s()
def wasm_f32_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.lt()
def wasm_f64_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.lt()
def wasm_u8_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_u()
def wasm_u16_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_u()
def wasm_u32_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_u()
def wasm_u64_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.le_u()
def wasm_i8_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_s()
def wasm_i16_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_s()
def wasm_i32_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_s()
def wasm_i64_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.le_s()
def wasm_f32_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.le()
def wasm_f64_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.le()
def wasm_u8_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_u()
def wasm_u16_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_u()
def wasm_u32_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_u()
def wasm_u64_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.gt_u()
def wasm_i8_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_s()
def wasm_i16_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_s()
def wasm_i32_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_s()
def wasm_i64_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.gt_s()
def wasm_f32_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.gt()
def wasm_f64_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.gt()
def wasm_u8_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_u()
def wasm_u16_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_u()
def wasm_u32_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_u()
def wasm_u64_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.ge_u()
def wasm_i8_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_s()
def wasm_i16_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_s()
def wasm_i32_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_s()
def wasm_i64_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.ge_s()
def wasm_f32_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.ge()
def wasm_f64_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.ge()
def wasm(build: BuildBase[WasmGenerator]) -> None:
Ord = build.type_classes['Ord']
build.instance_type_class(Ord, build.types['u8'], methods={
'min': wasm_u8_min,
'max': wasm_u8_max,
}, operators={
'<': wasm_u8_less_than,
'<=': wasm_u8_less_than_or_equal,
'>': wasm_u8_greater_than,
'>=': wasm_u8_greater_than_or_equal,
})
build.instance_type_class(Ord, build.types['u16'], methods={
'min': wasm_u16_min,
'max': wasm_u16_max,
}, operators={
'<': wasm_u16_less_than,
'<=': wasm_u16_less_than_or_equal,
'>': wasm_u16_greater_than,
'>=': wasm_u16_greater_than_or_equal,
})
build.instance_type_class(Ord, build.types['u32'], methods={
'min': wasm_u32_min,
'max': wasm_u32_max,
}, operators={
'<': wasm_u32_less_than,
'<=': wasm_u32_less_than_or_equal,
'>': wasm_u32_greater_than,
'>=': wasm_u32_greater_than_or_equal,
})
build.instance_type_class(Ord, build.types['u64'], methods={
'min': wasm_u64_min,
'max': wasm_u64_max,
}, operators={
'<': wasm_u64_less_than,
'<=': wasm_u64_less_than_or_equal,
'>': wasm_u64_greater_than,
'>=': wasm_u64_greater_than_or_equal,
})
build.instance_type_class(Ord, build.types['i8'], methods={
'min': wasm_i8_min,
'max': wasm_i8_max,
}, operators={
'<': wasm_i8_less_than,
'<=': wasm_i8_less_than_or_equal,
'>': wasm_i8_greater_than,
'>=': wasm_i8_greater_than_or_equal,
})
build.instance_type_class(Ord, build.types['i16'], methods={
'min': wasm_i16_min,
'max': wasm_i16_max,
}, operators={
'<': wasm_i16_less_than,
'<=': wasm_i16_less_than_or_equal,
'>': wasm_i16_greater_than,
'>=': wasm_i16_greater_than_or_equal,
})
build.instance_type_class(Ord, build.types['i32'], methods={
'min': wasm_i32_min,
'max': wasm_i32_max,
}, operators={
'<': wasm_i32_less_than,
'<=': wasm_i32_less_than_or_equal,
'>': wasm_i32_greater_than,
'>=': wasm_i32_greater_than_or_equal,
})
build.instance_type_class(Ord, build.types['i64'], methods={
'min': wasm_i64_min,
'max': wasm_i64_max,
}, operators={
'<': wasm_i64_less_than,
'<=': wasm_i64_less_than_or_equal,
'>': wasm_i64_greater_than,
'>=': wasm_i64_greater_than_or_equal,
})
build.instance_type_class(Ord, build.types['f32'], methods={
'min': wasm_f32_min,
'max': wasm_f32_max,
}, operators={
'<': wasm_f32_less_than,
'<=': wasm_f32_less_than_or_equal,
'>': wasm_f32_greater_than,
'>=': wasm_f32_greater_than_or_equal,
})
build.instance_type_class(Ord, build.types['f64'], methods={
'min': wasm_f64_min,
'max': wasm_f64_max,
}, operators={
'<': wasm_f64_less_than,
'<=': wasm_f64_less_than_or_equal,
'>': wasm_f64_greater_than,
'>=': wasm_f64_greater_than_or_equal,
})

View File

@ -1,58 +0,0 @@
"""
The Promotable type class is defined for types that can safely be promoted to a type
that can hold strictly more values. Going back will result in some precision being lost.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
b = TypeVariable(kind=Star(), name='b')
Promotable = TypeClass('Promotable', (a, b, ), methods={}, operators={})
has_Promotable_a_b = TypeClassConstraint(Promotable, [a, b])
fn_a_b = ConstrainedExpr(
variables={a, b},
expr=build.type5_make_function([a, b]),
constraints=(has_Promotable_a_b, ),
)
fn_b_a = ConstrainedExpr(
variables={a, b},
expr=build.type5_make_function([b, a]),
constraints=(has_Promotable_a_b, ),
)
Promotable.methods = {
'promote': fn_a_b,
'demote': fn_b_a,
}
build.register_type_class(Promotable)
def wasm_f32_f64_promote(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.promote_f32()
def wasm_f32_f64_demote(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.demote_f64()
def wasm(build: BuildBase[WasmGenerator]) -> None:
Promotable = build.type_classes['Promotable']
build.instance_type_class(Promotable, build.types['f32'], build.types['f64'], methods={
'promote': wasm_f32_f64_promote,
'demote': wasm_f32_f64_demote,
})

View File

@ -1,95 +0,0 @@
"""
The Reinterpretable type class is defined for when the data for a type can be reinterpreted
to hold a value for another type.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
b = TypeVariable(kind=Star(), name='b')
Reinterpretable = TypeClass('Reinterpretable', (a, b, ), methods={}, operators={})
has_reinterpretable_a_b = TypeClassConstraint(Reinterpretable, [a, b])
fn_a_b = ConstrainedExpr(
variables={a, b},
expr=build.type5_make_function([a, b]),
constraints=(has_reinterpretable_a_b, ),
)
Reinterpretable.methods = {
'reinterpret': fn_a_b,
}
build.register_type_class(Reinterpretable)
def wasm_i32_f32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.reinterpret_i32()
def wasm_u32_f32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.reinterpret_i32()
def wasm_i64_f64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.reinterpret_i64()
def wasm_u64_f64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.reinterpret_i64()
def wasm_f32_i32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.reinterpret_f32()
def wasm_f32_u32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.reinterpret_f32()
def wasm_f64_i64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.reinterpret_f64()
def wasm_f64_u64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.reinterpret_f64()
def wasm(build: BuildBase[WasmGenerator]) -> None:
Reinterpretable = build.type_classes['Reinterpretable']
build.instance_type_class(Reinterpretable, build.types['u32'], build.types['f32'], methods={
'reinterpret': wasm_u32_f32_reinterpret,
})
build.instance_type_class(Reinterpretable, build.types['u64'], build.types['f64'], methods={
'reinterpret': wasm_u64_f64_reinterpret,
})
build.instance_type_class(Reinterpretable, build.types['i32'], build.types['f32'], methods={
'reinterpret': wasm_i32_f32_reinterpret,
})
build.instance_type_class(Reinterpretable, build.types['i64'], build.types['f64'], methods={
'reinterpret': wasm_i64_f64_reinterpret,
})
build.instance_type_class(Reinterpretable, build.types['f32'], build.types['u32'], methods={
'reinterpret': wasm_f32_u32_reinterpret,
})
build.instance_type_class(Reinterpretable, build.types['f64'], build.types['u64'], methods={
'reinterpret': wasm_f64_u64_reinterpret,
})
build.instance_type_class(Reinterpretable, build.types['f32'], build.types['i32'], methods={
'reinterpret': wasm_f32_i32_reinterpret,
})
build.instance_type_class(Reinterpretable, build.types['f64'], build.types['i64'], methods={
'reinterpret': wasm_f64_i64_reinterpret,
})

View File

@ -1,61 +0,0 @@
"""
The Sized type class is defined for when a value can be considered to have a length.
The length is always in number of items, and never in number of bytes (unless an item is a byte).
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Nat, Star
from ...type5.typeexpr import TypeApplication, TypeExpr, TypeLevelNat, TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
t = TypeVariable(kind=Star() >> Star(), name='t')
t_a = TypeApplication(constructor=t, argument=a)
u32 = build.types['u32']
Sized = TypeClass('Sized', (t, ), methods={}, operators={})
has_sized_t = TypeClassConstraint(Sized, [t])
fn_t_a_u32 = ConstrainedExpr(
variables={t, a},
expr=build.type5_make_function([t_a, u32]),
constraints=(has_sized_t, ),
)
Sized.methods = {
'len': fn_t_a_u32,
}
build.register_type_class(Sized)
def wasm_dynamic_array_len(g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
del tv_map
# The length is stored in the first 4 bytes
g.i32.load()
def wasm_static_array_len(g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
sa_len = tv_map['n']
assert isinstance(sa_len, TypeLevelNat)
g.i32.const(sa_len.value)
def wasm(build: BuildBase[WasmGenerator]) -> None:
Sized = build.type_classes['Sized']
n = TypeVariable(kind=Nat(), name='n')
build.instance_type_class(Sized, build.dynamic_array_type5_constructor, methods={
'len': wasm_dynamic_array_len,
})
foo = TypeApplication(constructor=build.static_array_type5_constructor, argument=n)
build.instance_type_class(Sized, foo, methods={
'len': wasm_static_array_len,
})

View File

@ -1,144 +0,0 @@
"""
The Eq type class is defined for types that can be compered based on equality.
"""
from __future__ import annotations
from typing import Any
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Nat, Star
from ...type5.typeexpr import TypeApplication, TypeExpr, TypeLevelNat, TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a')
t = TypeVariable(kind=Star() >> Star(), name='t')
t_a = TypeApplication(constructor=t, argument=a)
u32 = build.types['u32']
Subscriptable = TypeClass('Subscriptable', (t, ), methods={}, operators={})
has_subscriptable_t = TypeClassConstraint(Subscriptable, [t])
fn_t_a_u32_a = ConstrainedExpr(
variables={t, a},
expr=build.type5_make_function([t_a, u32, a]),
constraints=(has_subscriptable_t, ),
)
Subscriptable.operators = {
'[]': fn_t_a_u32_a,
}
build.register_type_class(Subscriptable)
class SubscriptableCodeGenerator:
def __init__(self, build: BuildBase[WasmGenerator]) -> None:
self.build = build
def wasm_dynamic_array_getitem(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
sa_type = tv_map['a']
u32_type_info = self.build.type_info_map['u32']
sa_type_info = self.build.type_info_map.get(sa_type.name)
if sa_type_info is None:
sa_type_info = self.build.type_info_constructed
getitem_adr = g.temp_var_t(u32_type_info.wasm_type, 'getitem_adr')
getitem_idx = g.temp_var_t(u32_type_info.wasm_type, 'getitem_idx')
# Stack: [varref: *ard, idx: u32]
g.local.set(getitem_idx)
# Stack: [varref: *ard]
g.local.set(getitem_adr)
# Stack: []
# Out of bounds check based on memory stored length
# Stack: []
g.local.get(getitem_idx)
# Stack: [idx: u32]
g.local.get(getitem_adr)
# Stack: [idx: u32, varref: *ard]
g.i32.load()
# Stack: [idx: u32, len: u32]
g.i32.ge_u()
# Stack: [res: bool]
with g.if_():
g.unreachable(comment='Out of bounds')
# Stack: []
g.local.get(getitem_adr)
# Stack: [varref: *ard]
g.i32.const(4)
# Stack: [varref: *ard, 4]
g.i32.add()
# Stack: [firstel: *ard]
g.local.get(getitem_idx)
# Stack: [firstel: *ard, idx: u32]
g.i32.const(sa_type_info.alloc_size)
# Stack: [firstel: *ard, idx: u32, as: u32]
g.i32.mul()
# Stack: [firstel: *ard, offset: u32]
g.i32.add()
# Stack: [eladr: *ard]
g.add_statement(sa_type_info.wasm_load_func)
# Stack: [el]
def wasm_static_array_getitem(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
sa_type = tv_map['a']
sa_len = tv_map['n']
assert isinstance(sa_len, TypeLevelNat)
u32_type_info = self.build.type_info_map['u32']
sa_type_info = self.build.type_info_map.get(sa_type.name)
if sa_type_info is None:
sa_type_info = self.build.type_info_constructed
# OPTIMIZE: If index is a constant, we can use offset instead of multiply
# and we don't need to do the out of bounds check
getitem_idx = g.temp_var_t(u32_type_info.wasm_type, 'getitem_idx')
# Stack: [varref: *ard, idx: u32]
g.local.tee(getitem_idx)
# Stack: [varref: *ard, idx: u32]
# Out of bounds check based on sa_len.value
g.i32.const(sa_len.value)
# Stack: [varref: *ard, idx: u32, len: u32]
g.i32.ge_u()
# Stack: [varref: *ard, res: bool]
with g.if_():
g.unreachable(comment='Out of bounds')
# Stack: [varref: *ard]
g.local.get(getitem_idx)
# Stack: [varref: *ard, idx: u32]
g.i32.const(sa_type_info.alloc_size)
# Stack: [varref: *ard, idx: u32, as: u32]
g.i32.mul()
# Stack: [varref: *ard, offset: u32]
g.i32.add()
# Stack: [eladr: *ard]
g.add_statement(sa_type_info.wasm_load_func)
# Stack: [el]
def wasm(build: BuildBase[WasmGenerator]) -> None:
Subscriptable = build.type_classes['Subscriptable']
n = TypeVariable(kind=Nat(), name='n')
gen = SubscriptableCodeGenerator(build)
build.instance_type_class(Subscriptable, build.dynamic_array_type5_constructor, operators={
'[]': gen.wasm_dynamic_array_getitem,
})
foo = TypeApplication(constructor=build.static_array_type5_constructor, argument=n)
build.instance_type_class(Subscriptable, foo, operators={
'[]': gen.wasm_static_array_getitem,
})

View File

@ -1,86 +0,0 @@
from __future__ import annotations
from typing import Iterable
from ..type5.typeexpr import (
AtomicType,
TypeApplication,
TypeConstructor,
TypeExpr,
TypeVariable,
is_concrete,
)
class TypeClassRegistry[T]:
__slots__ = ('data', )
data: list[tuple[tuple[TypeExpr, ...], T]]
def __init__(self) -> None:
self.data = []
def add(self, arg_list: Iterable[TypeExpr], val: T) -> None:
self.data.append((tuple(arg_list), val))
def __contains__(self, arg_list: tuple[TypeExpr, ...]) -> bool:
for candidate, _ in self.data:
subsitutes = _matches(arg_list, candidate)
if subsitutes is not None:
return True
return False
def get(self, arg_list: tuple[TypeExpr, ...]) -> tuple[dict[str, TypeExpr], T] | None:
assert all(is_concrete(x) for x in arg_list)
for candidate, val in self.data:
subsitutes = _matches(arg_list, candidate)
if subsitutes is not None:
return (subsitutes, val)
return None
def _matches(lft_tpl: tuple[TypeExpr, ...], rgt_tpl: tuple[TypeExpr, ...]) -> dict[str, TypeExpr] | None:
subsitutes: dict[str, TypeExpr] = {}
for lft, rgt in zip(lft_tpl, rgt_tpl, strict=True):
new_substitutes = _matches_one(lft, rgt)
if new_substitutes is None:
return None
subsitutes.update(new_substitutes)
return subsitutes
def _matches_one(lft: TypeExpr, rgt: TypeExpr) -> dict[str, TypeExpr] | None:
if lft.kind != rgt.kind:
return None
if isinstance(rgt, TypeVariable):
assert not isinstance(lft, TypeVariable)
return _matches_one(rgt, lft)
if isinstance(lft, TypeVariable):
return {lft.name: rgt}
if isinstance(lft, (AtomicType, TypeConstructor, )):
if lft == rgt:
return {}
return None
if isinstance(lft, TypeApplication):
if not isinstance(rgt, TypeApplication):
return None
con_result = _matches_one(lft.constructor, rgt.constructor)
if con_result is None:
return None
arg_result = _matches_one(lft.argument, rgt.argument)
if arg_result is None:
return None
return con_result | arg_result
raise NotImplementedError(lft, rgt)

View File

@ -1,154 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from ..type5.record import Record
from ..type5.typeexpr import (
AtomicType,
TypeApplication,
TypeConstructor,
TypeExpr,
TypeLevelNat,
TypeVariable,
)
from ..type5.typerouter import TypeRouter
if TYPE_CHECKING:
from .base import BuildBase
class BuildTypeRouter[T](TypeRouter[T]):
"""
Extends the general type router with phasm builtin types.
Like functions, tuples, static and dynamic arrays.
"""
__slots__ = ('build', )
def __init__(self, build: BuildBase[Any]) -> None:
self.build = build
def when_application(self, typ: TypeApplication) -> T:
da_arg = self.build.type5_is_dynamic_array(typ)
if da_arg is not None:
return self.when_dynamic_array(da_arg)
fn_args = self.build.type5_is_function(typ)
if fn_args is not None:
return self.when_function(fn_args)
sa_args = self.build.type5_is_static_array(typ)
if sa_args is not None:
sa_len, sa_typ = sa_args
return self.when_static_array(sa_len, sa_typ)
tp_args = self.build.type5_is_tuple(typ)
if tp_args is not None:
return self.when_tuple(tp_args)
return self.when_application_other(typ)
def when_record(self, typ: Record) -> T:
return self.when_struct(typ)
def when_application_other(self, typ: TypeApplication) -> T:
raise NotImplementedError
def when_dynamic_array(self, da_arg: TypeExpr) -> T:
raise NotImplementedError
def when_function(self, fn_args: list[TypeExpr]) -> T:
raise NotImplementedError
def when_struct(self, typ: Record) -> T:
raise NotImplementedError
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> T:
raise NotImplementedError
def when_tuple(self, tp_args: list[TypeExpr]) -> T:
raise NotImplementedError
class TypeName(BuildTypeRouter[str]):
"""
Router to generate a type's name.
Also serves an example implementation.
"""
__slots__ = ()
def when_application_other(self, typ: TypeApplication) -> str:
return typ.name
def when_atomic(self, typ: AtomicType) -> str:
return typ.name
def when_constructor(self, typ: TypeConstructor) -> str:
return typ.name
def when_dynamic_array(self, da_arg: TypeExpr) -> str:
if da_arg == self.build.u8_type5:
return 'bytes'
return self(da_arg) + '[...]'
def when_function(self, fn_args: list[TypeExpr]) -> str:
return 'Callable[' + ', '.join(map(self, fn_args)) + ']'
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> str:
return f'{self(sa_typ)}[{sa_len}]'
def when_struct(self, typ: Record) -> str:
return typ.name
def when_tuple(self, tp_args: list[TypeExpr]) -> str:
return '(' + ', '.join(map(self, tp_args)) + ', )'
def when_type_level_nat(self, typ: TypeLevelNat) -> str:
return str(typ.value)
def when_variable(self, typ: TypeVariable) -> str:
return typ.name
class TypeAllocSize(BuildTypeRouter[int]):
"""
Router to generate a type's allocation size.
"""
__slots__ = ('is_member', )
is_member: bool
def __init__(self, build: BuildBase[Any], is_member: bool) -> None:
super().__init__(build)
self.is_member = is_member
def when_atomic(self, typ: AtomicType) -> int:
typ_info = self.build.type_info_map.get(typ.name)
if typ_info is None:
raise NotImplementedError(typ)
return typ_info.alloc_size
def when_dynamic_array(self, da_arg: TypeExpr) -> int:
if self.is_member:
return self.build.type_info_constructed.alloc_size
raise RuntimeError("Cannot know size of dynamic array at type level")
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> int:
if self.is_member:
return self.build.type_info_constructed.alloc_size
raise NotImplementedError
def when_struct(self, typ: Record) -> int:
if self.is_member:
return self.build.type_info_constructed.alloc_size
return sum(map(self.build.type5_alloc_size_member, (x[1] for x in typ.fields)))
def when_tuple(self, tp_args: list[TypeExpr]) -> int:
if self.is_member:
return self.build.type_info_constructed.alloc_size
return sum(map(self.build.type5_alloc_size_member, tp_args))

View File

@ -3,13 +3,12 @@ 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 It's intented to be a "any color, as long as it's black" kind of renderer
""" """
from typing import Any, Generator from typing import Generator
from . import ourlang from . import ourlang
from .type5 import typeexpr as type5typeexpr from . import typing
def phasm_render(inp: ourlang.Module) -> str:
def phasm_render(inp: ourlang.Module[Any]) -> str:
""" """
Public method for rendering a Phasm module into Phasm code Public method for rendering a Phasm module into Phasm code
""" """
@ -17,27 +16,71 @@ def phasm_render(inp: ourlang.Module[Any]) -> str:
Statements = Generator[str, None, None] Statements = Generator[str, None, None]
def type5(mod: ourlang.Module[Any], inp: type5typeexpr.TypeExpr) -> str: def type_(inp: typing.TypeBase) -> str:
""" """
Render: type's name Render: Type (name)
""" """
return mod.build.type5_name(inp) if isinstance(inp, typing.TypeNone):
return 'None'
def struct_definition(mod: ourlang.Module[Any], inp: ourlang.StructDefinition) -> str: 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 Render: TypeStruct's definition
""" """
result = f'class {inp.struct_type5.name}:\n' result = f'class {inp.name}:\n'
for mem, typ in inp.struct_type5.fields: for mem in inp.members:
result += f' {mem}: {type5(mod, typ)}\n' result += f' {mem.name}: {type_(mem.type)}\n'
return result return result
def constant_definition(mod: ourlang.Module[Any], inp: ourlang.ModuleConstantDef) -> str: def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
""" """
Render: Module Constant's definition Render: Module Constant's definition
""" """
return f'{inp.name}: {type5(mod, inp.type5)} = {expression(inp.constant)}\n' return f'{inp.name}: {type_(inp.type)} = {expression(inp.constant)}\n'
def expression(inp: ourlang.Expression) -> str: def expression(inp: ourlang.Expression) -> str:
""" """
@ -48,26 +91,28 @@ def expression(inp: ourlang.Expression) -> str:
# could not fit in the given float type # could not fit in the given float type
return str(inp.value) return str(inp.value)
if isinstance(inp, ourlang.ConstantBytes): if isinstance(inp, (ourlang.ConstantTuple, ourlang.ConstantStaticArray, )):
return repr(inp.value)
if isinstance(inp, ourlang.ConstantTuple):
return '(' + ', '.join( return '(' + ', '.join(
expression(x) expression(x)
for x in inp.value for x in inp.value
) + ', )' ) + ', )'
if isinstance(inp, ourlang.ConstantStruct):
return inp.struct_type5.name + '(' + ', '.join(
expression(x)
for x in inp.value
) + ')'
if isinstance(inp, ourlang.VariableReference): if isinstance(inp, ourlang.VariableReference):
return str(inp.variable.name) return str(inp.variable.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): if isinstance(inp, ourlang.BinaryOp):
return f'{expression(inp.left)} {inp.operator.name} {expression(inp.right)}' return f'{expression(inp.left)} {inp.operator} {expression(inp.right)}'
if isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.FunctionCall):
args = ', '.join( args = ', '.join(
@ -76,29 +121,31 @@ def expression(inp: ourlang.Expression) -> str:
) )
if isinstance(inp.function, ourlang.StructConstructor): if isinstance(inp.function, ourlang.StructConstructor):
return f'{inp.function.struct_type5.name}({args})' return f'{inp.function.struct.name}({args})'
if isinstance(inp.function, ourlang.TupleConstructor):
return f'({args}, )'
return f'{inp.function.name}({args})' return f'{inp.function.name}({args})'
if isinstance(inp, ourlang.FunctionReference): if isinstance(inp, ourlang.AccessBytesIndex):
return str(inp.function.name) return f'{expression(inp.varref)}[{expression(inp.index)}]'
if isinstance(inp, ourlang.TupleInstantiation):
args = ', '.join(
expression(arg)
for arg in inp.elements
)
return f'({args}, )'
if isinstance(inp, ourlang.Subscript):
varref = expression(inp.varref)
index = expression(inp.index)
return f'{varref}[{index}]'
if isinstance(inp, ourlang.AccessStructMember): if isinstance(inp, ourlang.AccessStructMember):
return f'{expression(inp.varref)}.{inp.member}' 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) raise NotImplementedError(expression, inp)
@ -126,7 +173,7 @@ def statement(inp: ourlang.Statement) -> Statements:
raise NotImplementedError(statement, inp) raise NotImplementedError(statement, inp)
def function(mod: ourlang.Module[Any], inp: ourlang.Function) -> str: def function(inp: ourlang.Function) -> str:
""" """
Render: Function body Render: Function body
@ -139,17 +186,12 @@ def function(mod: ourlang.Module[Any], inp: ourlang.Function) -> str:
if inp.imported: if inp.imported:
result += '@imported\n' result += '@imported\n'
assert inp.type5 is not None
fn_args = mod.build.type5_is_function(inp.type5)
assert fn_args is not None
ret_type5 = fn_args.pop()
args = ', '.join( args = ', '.join(
f'{arg_name}: {type5(mod, arg_type)}' f'{p.name}: {type_(p.type)}'
for arg_name, arg_type in zip(inp.arg_names, fn_args, strict=True) for p in inp.posonlyargs
) )
result += f'def {inp.name}({args}) -> {type5(mod, ret_type5)}:\n' result += f'def {inp.name}({args}) -> {type_(inp.returns)}:\n'
if inp.imported: if inp.imported:
result += ' pass\n' result += ' pass\n'
@ -161,29 +203,29 @@ def function(mod: ourlang.Module[Any], inp: ourlang.Function) -> str:
return result return result
def module(inp: ourlang.Module[Any]) -> str: def module(inp: ourlang.Module) -> str:
""" """
Render: Module Render: Module
""" """
result = '' result = ''
for struct in inp.struct_definitions.values(): for struct in inp.structs.values():
if result: if result:
result += '\n' result += '\n'
result += struct_definition(inp, struct) result += struct_definition(struct)
for cdef in inp.constant_defs.values(): for cdef in inp.constant_defs.values():
if result: if result:
result += '\n' result += '\n'
result += constant_definition(inp, cdef) result += constant_definition(cdef)
for func in inp.functions.values(): for func in inp.functions.values():
if isinstance(func, ourlang.StructConstructor): if func.lineno < 0:
# Auto generated # Buildin (-2) or auto generated (-1)
continue continue
if result: if result:
result += '\n' result += '\n'
result += function(inp, func) result += function(func)
return result return result

File diff suppressed because it is too large Load Diff

View File

@ -6,3 +6,6 @@ class StaticError(Exception):
""" """
An error found during static analysis An error found during static analysis
""" """
class TypingError(Exception):
pass

View File

@ -1,5 +0,0 @@
# This folder contains some basic optimisations
# If you really want to optimise your stuff,
# checkout https://github.com/WebAssembly/binaryen
# It's wasm-opt tool can be run on the wat or wasm files.
# And they have a ton of options to optimize.

View File

@ -1,49 +0,0 @@
from phasm import wasm
def removeunusedfuncs(inp: wasm.Module) -> None:
"""
Removes functions that aren't exported and aren't
called by exported functions
"""
# First make a handy lookup table
function_map = {
x.name: x
for x in inp.functions
}
# Keep a list of all functions to retain
retain_functions = set()
# Keep a queue (stack) of the funtions we need to check
# The exported functions as well as the tabled functions are the
# first we know of to keep
to_check_functions = [
x
for x in inp.functions
if x.exported_name is not None
] + [
function_map[x]
for x in inp.table.values()
]
while to_check_functions:
# Check the next function. If it's on the list, we need to retain it
to_check_func = to_check_functions.pop()
retain_functions.add(to_check_func)
# Check all functions calls by this retaining function
to_check_functions.extend(
func
for stmt in to_check_func.statements
if isinstance(stmt, wasm.StatementCall)
# The function_map can not have the named function if it is an import
if (func := function_map.get(stmt.func_name)) is not None
and func not in retain_functions
)
inp.functions = [
func
for func in inp.functions
if func in retain_functions
]

View File

@ -1,52 +1,46 @@
""" """
Contains the syntax tree for ourlang Contains the syntax tree for ourlang
""" """
from __future__ import annotations from typing import Dict, List, Tuple, Optional, Union
from typing import Dict, Iterable, List, Optional, Union import enum
from .build.base import BuildBase from typing_extensions import Final
from .type5 import constrainedexpr as type5constrainedexpr
from .type5 import record as type5record
from .type5 import typeexpr as type5typeexpr
WEBASSEMBLY_BUILDIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
WEBASSEMBLY_BUILDIN_BYTES_OPS: Final = ('len', )
class SourceRef: from .typing import (
__slots__ = ('filename', 'lineno', 'colno', ) TypeBase,
TypeNone,
TypeBool,
TypeUInt8, TypeUInt32, TypeUInt64,
TypeInt32, TypeInt64,
TypeFloat32, TypeFloat64,
TypeBytes,
TypeTuple, TypeTupleMember,
TypeStaticArray, TypeStaticArrayMember,
TypeStruct, TypeStructMember,
filename: str | None TypeVar,
lineno: int | None )
colno: int | None
def __init__(self, filename: str | None, lineno: int | None = None, colno: int | None = None) -> None:
self.filename = filename
self.lineno = lineno
self.colno = colno
def __repr__(self) -> str:
return f"SourceRef({self.filename!r}, {self.lineno!r}, {self.colno!r})"
def __str__(self) -> str:
return f"{self.filename}:{self.lineno:>4}:{self.colno:<3}"
class Expression: class Expression:
""" """
An expression within a statement An expression within a statement
""" """
__slots__ = ('type5', 'sourceref', ) __slots__ = ('type', 'type_var', )
sourceref: SourceRef type: TypeBase
type5: type5typeexpr.TypeExpr | None type_var: Optional[TypeVar]
def __init__(self, *, sourceref: SourceRef) -> None: def __init__(self, type_: TypeBase) -> None:
self.sourceref = sourceref self.type = type_
self.type5 = None self.type_var = None
class Constant(Expression): class Constant(Expression):
""" """
An constant value expression within a statement An constant value expression within a statement
# FIXME: Rename to literal
""" """
__slots__ = () __slots__ = ()
@ -56,179 +50,105 @@ class ConstantPrimitive(Constant):
""" """
__slots__ = ('value', ) __slots__ = ('value', )
value: int | float value: Union[int, float]
def __init__(self, value: int | float, sourceref: SourceRef) -> None: def __init__(self, value: Union[int, float]) -> None:
super().__init__(sourceref=sourceref)
self.value = value self.value = value
def __repr__(self) -> str: class ConstantTuple(Constant):
return f'ConstantPrimitive({repr(self.value)})'
class ConstantMemoryStored(Constant):
"""
An constant value expression within a statement
# FIXME: Rename to literal
"""
__slots__ = ('data_block', )
data_block: 'ModuleDataBlock'
def __init__(self, data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.data_block = data_block
class ConstantBytes(ConstantMemoryStored):
"""
A bytes constant value expression within a statement
"""
__slots__ = ('value', )
value: bytes
def __init__(self, value: bytes, data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
super().__init__(data_block, sourceref=sourceref)
self.value = value
def __repr__(self) -> str:
# Do not repr the whole ModuleDataBlock
# As this has a reference back to this constant for its data
# which it needs to compile the data into the program
return f'ConstantBytes({repr(self.value)}, @{repr(self.data_block.address)})'
class ConstantTuple(ConstantMemoryStored):
""" """
A Tuple constant value expression within a statement A Tuple constant value expression within a statement
""" """
__slots__ = ('value', ) __slots__ = ('value', )
value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']] value: List[ConstantPrimitive]
def __init__(self, value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']], data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None: def __init__(self, type_: TypeTuple, value: List[ConstantPrimitive]) -> None: # FIXME: Tuple of tuples?
super().__init__(data_block, sourceref=sourceref) super().__init__(type_)
self.value = value self.value = value
def __repr__(self) -> str: class ConstantStaticArray(Constant):
# Do not repr the whole ModuleDataBlock
# As this has a reference back to this constant for its data
# which it needs to compile the data into the program
return f'ConstantTuple({repr(self.value)}, @{repr(self.data_block.address)})'
class ConstantStruct(ConstantMemoryStored):
""" """
A Struct constant value expression within a statement A StaticArray constant value expression within a statement
""" """
__slots__ = ('struct_type5', 'value', ) __slots__ = ('value', )
struct_type5: type5record.Record value: List[ConstantPrimitive]
value: list[ConstantPrimitive | ConstantBytes | ConstantTuple | ConstantStruct]
def __init__( def __init__(self, type_: TypeStaticArray, value: List[ConstantPrimitive]) -> None: # FIXME: Arrays of arrays?
self, super().__init__(type_)
struct_type5: type5record.Record,
value: list[ConstantPrimitive | ConstantBytes | ConstantTuple | ConstantStruct],
data_block: 'ModuleDataBlock',
sourceref: SourceRef
) -> None:
super().__init__(data_block, sourceref=sourceref)
self.struct_type5 = struct_type5
self.value = value self.value = value
def __repr__(self) -> str:
# Do not repr the whole ModuleDataBlock
# As this has a reference back to this constant for its data
# which it needs to compile the data into the program
return f'ConstantStruct({self.struct_type5!r}, {self.value!r}, @{self.data_block.address!r})'
class VariableReference(Expression): class VariableReference(Expression):
""" """
An variable reference expression within a statement An variable reference expression within a statement
""" """
__slots__ = ('variable', ) __slots__ = ('variable', )
variable: Union['ModuleConstantDef', 'FunctionParam'] # also possibly local variable: 'FunctionParam' # also possibly local
def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam'], sourceref: SourceRef) -> None: def __init__(self, type_: TypeBase, variable: 'FunctionParam') -> None:
super().__init__(sourceref=sourceref) super().__init__(type_)
self.variable = variable self.variable = variable
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): class BinaryOp(Expression):
""" """
A binary operator expression within a statement A binary operator expression within a statement
""" """
__slots__ = ('operator', 'polytype_substitutions', 'left', 'right', ) __slots__ = ('operator', 'left', 'right', )
operator: Function | FunctionParam operator: str
polytype_substitutions: dict[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr]
left: Expression left: Expression
right: Expression right: Expression
def __init__(self, operator: Function | FunctionParam, left: Expression, right: Expression, sourceref: SourceRef) -> None: def __init__(self, type_: TypeBase, operator: str, left: Expression, right: Expression) -> None:
super().__init__(sourceref=sourceref) super().__init__(type_)
self.operator = operator self.operator = operator
self.polytype_substitutions = {}
self.left = left self.left = left
self.right = right self.right = right
def __repr__(self) -> str:
return f'BinaryOp({repr(self.operator)}, {repr(self.left)}, {repr(self.right)})'
class FunctionCall(Expression): class FunctionCall(Expression):
""" """
A function call expression within a statement A function call expression within a statement
""" """
__slots__ = ('function', 'polytype_substitutions', 'arguments', ) __slots__ = ('function', 'arguments', )
function: Function | FunctionParam
polytype_substitutions: dict[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr]
arguments: List[Expression]
def __init__(self, function: Function | FunctionParam, sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.function = function
self.polytype_substitutions = {}
self.arguments = []
class FunctionReference(Expression):
"""
An function reference expression within a statement
"""
__slots__ = ('function', )
function: 'Function' function: 'Function'
arguments: List[Expression]
def __init__(self, function: 'Function') -> None:
super().__init__(function.returns)
def __init__(self, function: 'Function', sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.function = function self.function = function
self.arguments = []
class TupleInstantiation(Expression): class AccessBytesIndex(Expression):
""" """
Instantiation a tuple Access a bytes index for reading
"""
__slots__ = ('elements', )
elements: List[Expression]
def __init__(self, elements: List[Expression], sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.elements = elements
class Subscript(Expression):
"""
A subscript, for example to refer to a static array or tuple
by index
""" """
__slots__ = ('varref', 'index', ) __slots__ = ('varref', 'index', )
varref: VariableReference varref: VariableReference
index: Expression index: Expression
def __init__(self, varref: VariableReference, index: Expression, sourceref: SourceRef) -> None: def __init__(self, type_: TypeBase, varref: VariableReference, index: Expression) -> None:
super().__init__(sourceref=sourceref) super().__init__(type_)
self.varref = varref self.varref = varref
self.index = index self.index = index
@ -240,24 +160,94 @@ class AccessStructMember(Expression):
__slots__ = ('varref', 'member', ) __slots__ = ('varref', 'member', )
varref: VariableReference varref: VariableReference
member: str member: TypeStructMember
def __init__(self, varref: VariableReference, member: str, sourceref: SourceRef) -> None: def __init__(self, varref: VariableReference, member: TypeStructMember) -> None:
super().__init__(sourceref=sourceref) super().__init__(member.type)
self.varref = varref self.varref = varref
self.member = member 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: class Statement:
""" """
A statement within a function A statement within a function
""" """
__slots__ = ("sourceref", ) __slots__ = ()
sourceref: SourceRef
def __init__(self, *, sourceref: SourceRef) -> None:
self.sourceref = sourceref
class StatementPass(Statement): class StatementPass(Statement):
""" """
@ -265,23 +255,15 @@ class StatementPass(Statement):
""" """
__slots__ = () __slots__ = ()
def __init__(self, sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
class StatementReturn(Statement): class StatementReturn(Statement):
""" """
A return statement within a function A return statement within a function
""" """
__slots__ = ('value', ) __slots__ = ('value', )
def __init__(self, value: Expression, sourceref: SourceRef) -> None: def __init__(self, value: Expression) -> None:
super().__init__(sourceref=sourceref)
self.value = value self.value = value
def __repr__(self) -> str:
return f'StatementReturn({repr(self.value)})'
class StatementIf(Statement): class StatementIf(Statement):
""" """
An if statement within a function An if statement within a function
@ -298,63 +280,41 @@ class StatementIf(Statement):
self.else_statements = [] self.else_statements = []
class FunctionParam: class FunctionParam:
""" __slots__ = ('name', 'type', 'type_var', )
A parameter for a Function
"""
__slots__ = ('name', 'type5', )
name: str name: str
type5: type5typeexpr.TypeExpr type: TypeBase
type_var: Optional[TypeVar]
def __init__(self, name: str, type5: type5typeexpr.TypeExpr) -> None:
assert type5typeexpr.is_concrete(type5)
def __init__(self, name: str, type_: TypeBase) -> None:
self.name = name self.name = name
self.type5 = type5 self.type = type_
self.type_var = None
def __repr__(self) -> str:
return f'FunctionParam({self.name!r}, {self.type5!r})'
class Function: class Function:
""" """
A function processes input and produces output A function processes input and produces output
""" """
__slots__ = ('name', 'sourceref', 'exported', 'imported', 'statements', 'type5', 'arg_names', ) __slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns', 'returns_type_var', 'posonlyargs', )
name: str name: str
sourceref: SourceRef lineno: int
exported: bool exported: bool
imported: Optional[str] imported: bool
statements: List[Statement] statements: List[Statement]
type5: type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr | None returns: TypeBase
arg_names: list[str] returns_type_var: Optional[TypeVar]
posonlyargs: List[FunctionParam]
def __init__(self, name: str, sourceref: SourceRef) -> None: def __init__(self, name: str, lineno: int) -> None:
self.name = name self.name = name
self.sourceref = sourceref self.lineno = lineno
self.exported = False self.exported = False
self.imported = None self.imported = False
self.statements = [] self.statements = []
self.type5 = None self.returns = TypeNone()
self.arg_names = [] self.returns_type_var = None
self.posonlyargs = []
class BuiltinFunction(Function):
def __init__(self, name: str, type5: type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr) -> None:
super().__init__(name, SourceRef("/", 0, 0))
self.type5 = type5
class StructDefinition:
"""
The definition for a struct
"""
__slots__ = ('struct_type5', 'sourceref', )
struct_type5: type5record.Record
sourceref: SourceRef
def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
self.struct_type5 = struct_type5
self.sourceref = sourceref
class StructConstructor(Function): class StructConstructor(Function):
""" """
@ -363,33 +323,58 @@ class StructConstructor(Function):
A function will generated to instantiate a struct. The arguments A function will generated to instantiate a struct. The arguments
will be the defaults will be the defaults
""" """
__slots__ = ('struct_type5', ) __slots__ = ('struct', )
struct_type5: type5record.Record struct: TypeStruct
def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None: def __init__(self, struct: TypeStruct) -> None:
super().__init__(f'@{struct_type5.name}@__init___@', sourceref) super().__init__(f'@{struct.name}@__init___@', -1)
self.struct_type5 = struct_type5
for mem, typ in struct_type5.fields: self.returns = struct
self.arg_names.append(mem)
for mem in struct.members:
self.posonlyargs.append(FunctionParam(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(FunctionParam(f'arg{mem.idx}', mem.type, ))
self.tuple = tuple_
class ModuleConstantDef: class ModuleConstantDef:
""" """
A constant definition within a module A constant definition within a module
""" """
__slots__ = ('name', 'sourceref', 'type5', 'constant', ) __slots__ = ('name', 'lineno', 'type', 'constant', 'data_block', )
name: str name: str
sourceref: SourceRef lineno: int
type5: type5typeexpr.TypeExpr type: TypeBase
constant: Constant constant: Constant
data_block: Optional['ModuleDataBlock']
def __init__(self, name: str, sourceref: SourceRef, type5: type5typeexpr.TypeExpr, constant: Constant) -> None: def __init__(self, name: str, lineno: int, type_: TypeBase, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None:
self.name = name self.name = name
self.sourceref = sourceref self.lineno = lineno
self.type5 = type5 self.type = type_
self.constant = constant self.constant = constant
self.data_block = data_block
class ModuleDataBlock: class ModuleDataBlock:
""" """
@ -397,16 +382,13 @@ class ModuleDataBlock:
""" """
__slots__ = ('data', 'address', ) __slots__ = ('data', 'address', )
data: List[Union[ConstantPrimitive, ConstantMemoryStored]] data: List[ConstantPrimitive]
address: Optional[int] address: Optional[int]
def __init__(self, data: Iterable[Union[ConstantPrimitive, ConstantMemoryStored]]) -> None: def __init__(self, data: List[ConstantPrimitive]) -> None:
self.data = [*data] self.data = data
self.address = None self.address = None
def __repr__(self) -> str:
return f'ModuleDataBlock({self.data!r}, {self.address!r})'
class ModuleData: class ModuleData:
""" """
The data for when a module is loaded into memory The data for when a module is loaded into memory
@ -418,32 +400,31 @@ class ModuleData:
def __init__(self) -> None: def __init__(self) -> None:
self.blocks = [] self.blocks = []
class Module[G]: class Module:
""" """
A module is a file and consists of functions A module is a file and consists of functions
""" """
__slots__ = ('build', 'filename', 'data', 'types', 'type5s', 'struct_definitions', 'constant_defs', 'functions', 'methods', 'operators', 'functions_table', ) __slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',)
build: BuildBase[G]
filename: str
data: ModuleData data: ModuleData
types: dict[str, type5typeexpr.TypeExpr] types: Dict[str, TypeBase]
struct_definitions: Dict[str, StructDefinition] structs: Dict[str, TypeStruct]
constant_defs: Dict[str, ModuleConstantDef] constant_defs: Dict[str, ModuleConstantDef]
functions: Dict[str, Function] functions: Dict[str, Function]
methods: Dict[str, type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr]
operators: Dict[str, type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr]
functions_table: dict[Function, int]
def __init__(self, build: BuildBase[G], filename: str) -> None:
self.build = build
self.filename = filename
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.data = ModuleData()
self.types = {} self.structs = {}
self.struct_definitions = {}
self.constant_defs = {} self.constant_defs = {}
self.functions = {} self.functions = {}
self.methods = {}
self.operators = {}
self.functions_table = {}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,7 @@
""" """
stdlib: Memory allocation stdlib: Memory allocation
""" """
from phasm.wasmgenerator import Generator, func_wrapper from phasm.wasmgenerator import Generator, VarType_i32 as i32, func_wrapper
from phasm.wasmgenerator import VarType_i32 as i32
IDENTIFIER = 0xA1C0 IDENTIFIER = 0xA1C0
@ -15,7 +14,7 @@ UNALLOC_PTR = ADR_UNALLOC_PTR + 4
# For memory initialization see phasm.compiler.module_data # For memory initialization see phasm.compiler.module_data
@func_wrapper() @func_wrapper(exported=False)
def __find_free_block__(g: Generator, alloc_size: i32) -> i32: def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
# Find out if we've freed any blocks at all so far # Find out if we've freed any blocks at all so far
g.i32.const(ADR_FREE_BLOCK_PTR) g.i32.const(ADR_FREE_BLOCK_PTR)
@ -27,12 +26,12 @@ def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
g.i32.const(0) g.i32.const(0)
g.return_() g.return_()
del alloc_size # TODO: Actual implement using a previously freed block del alloc_size # TODO
g.unreachable() g.unreachable()
return i32('return') # To satisfy mypy return i32('return') # To satisfy mypy
@func_wrapper(exported=True) @func_wrapper()
def __alloc__(g: Generator, alloc_size: i32) -> i32: def __alloc__(g: Generator, alloc_size: i32) -> i32:
result = i32('result') result = i32('result')

View File

@ -1,13 +1,11 @@
""" """
stdlib: Standard types that are not wasm primitives stdlib: Standard types that are not wasm primitives
""" """
from phasm.wasmgenerator import Generator, VarType_i32 as i32, func_wrapper
from phasm.stdlib import alloc from phasm.stdlib import alloc
from phasm.wasmgenerator import Generator, func_wrapper
from phasm.wasmgenerator import VarType_i32 as i32
from phasm.wasmgenerator import VarType_i64 as i64
@func_wrapper()
@func_wrapper(exported=True)
def __alloc_bytes__(g: Generator, length: i32) -> i32: def __alloc_bytes__(g: Generator, length: i32) -> i32:
""" """
Allocates room for a bytes instance, but does not write Allocates room for a bytes instance, but does not write
@ -34,400 +32,35 @@ def __alloc_bytes__(g: Generator, length: i32) -> i32:
return i32('return') # To satisfy mypy return i32('return') # To satisfy mypy
@func_wrapper() @func_wrapper()
def __u32_min__(g: Generator, x: i32, y: i32) -> i32: def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32:
g.local.get(x) """
g.local.get(y) 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() g.i32.lt_u()
with g.if_(): with g.if_():
g.local.get(x) # 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_() g.return_()
g.local.get(y) # The offset is outside the allocated bytes
g.return_() g.i32.const(0)
return i32('return') # To satisfy mypy
@func_wrapper()
def __u64_min__(g: Generator, x: i64, y: i64) -> i64:
g.local.get(x)
g.local.get(y)
g.i64.lt_u()
with g.if_():
g.local.get(x)
g.return_()
g.local.get(y)
g.return_()
return i64('return') # To satisfy mypy
@func_wrapper()
def __i32_min__(g: Generator, x: i32, y: i32) -> i32:
g.local.get(x)
g.local.get(y)
g.i32.lt_s()
with g.if_():
g.local.get(x)
g.return_()
g.local.get(y)
g.return_()
return i32('return') # To satisfy mypy
@func_wrapper()
def __i64_min__(g: Generator, x: i64, y: i64) -> i64:
g.local.get(x)
g.local.get(y)
g.i64.lt_s()
with g.if_():
g.local.get(x)
g.return_()
g.local.get(y)
g.return_()
return i64('return') # To satisfy mypy
@func_wrapper()
def __u32_max__(g: Generator, x: i32, y: i32) -> i32:
g.local.get(x)
g.local.get(y)
g.i32.gt_u()
with g.if_():
g.local.get(x)
g.return_()
g.local.get(y)
g.return_()
return i32('return') # To satisfy mypy
@func_wrapper()
def __u64_max__(g: Generator, x: i64, y: i64) -> i64:
g.local.get(x)
g.local.get(y)
g.i64.gt_u()
with g.if_():
g.local.get(x)
g.return_()
g.local.get(y)
g.return_()
return i64('return') # To satisfy mypy
@func_wrapper()
def __i32_max__(g: Generator, x: i32, y: i32) -> i32:
g.local.get(x)
g.local.get(y)
g.i32.gt_s()
with g.if_():
g.local.get(x)
g.return_()
g.local.get(y)
g.return_()
return i32('return') # To satisfy mypy
@func_wrapper()
def __i64_max__(g: Generator, x: i64, y: i64) -> i64:
g.local.get(x)
g.local.get(y)
g.i64.gt_s()
with g.if_():
g.local.get(x)
g.return_()
g.local.get(y)
g.return_()
return i64('return') # To satisfy mypy
@func_wrapper()
def __i32_abs__(g: Generator, x: i32) -> i32:
# https://stackoverflow.com/a/14194764
y = i32('y')
# z = i32('z')
# y = x >> 31
g.local.get(x)
g.i32.const(31)
g.i32.shr_s() # Must be arithmetic shift
g.local.set(y)
# abs(x) = (x XOR y) - y
# (x XOR y)
g.local.get(x)
g.local.get(y)
g.i32.xor()
# - y
g.local.get(y)
g.i32.sub()
g.return_()
return i32('return') # To satisfy mypy
@func_wrapper()
def __i64_abs__(g: Generator, x: i64) -> i64:
# https://stackoverflow.com/a/14194764
y = i64('y')
# z = i64('z')
# y = x >> 31
g.local.get(x)
g.i64.const(31)
g.i64.shr_s() # Must be arithmetic shift
g.local.set(y)
# abs(x) = (x XOR y) - y
# (x XOR y)
g.local.get(x)
g.local.get(y)
g.i64.xor()
# - y
g.local.get(y)
g.i64.sub()
g.return_()
return i64('return') # To satisfy mypy
@func_wrapper()
def __u32_pow2__(g: Generator, x: i32) -> i32:
# 2^0 == 1
g.local.get(x)
g.i32.eqz()
with g.if_():
g.i32.const(1)
g.return_()
# 2 ^ x == 2 << (x - 1)
# (when x > 1)
g.i32.const(2)
g.local.get(x)
g.i32.const(1)
g.i32.sub()
g.i32.shl()
return i32('return') # To satisfy mypy
@func_wrapper()
def __u8_rotl__(g: Generator, x: i32, r: i32) -> i32:
s = i32('s') # The shifted part we need to overlay
# Handle cases where we need to shift more than 8 bits
g.local.get(r)
g.i32.const(8)
g.i32.rem_u()
g.local.set(r)
# Now do the rotation
g.local.get(x)
# 0000 0000 1100 0011
g.local.get(r)
# 0000 0000 1100 0011, 3
g.i32.shl()
# 0000 0110 0001 1000
g.local.tee(s)
# 0000 0110 0001 1000
g.i32.const(255)
# 0000 0110 0001 1000, 0000 0000 1111 1111
g.i32.and_()
# 0000 0000 0001 1000
g.local.get(s)
# 0000 0000 0001 1000, 0000 0110 0001 1000
g.i32.const(65280)
# 0000 0000 0001 1000, 0000 0110 0001 1000, 1111 1111 0000 0000
g.i32.and_()
# 0000 0000 0001 1000, 0000 0110 0000 0000
g.i32.const(8)
# 0000 0000 0001 1000, 0000 0110 0000 0000, 8
g.i32.shr_u()
# 0000 0000 0001 1000, 0000 0000 0000 0110
g.i32.or_()
# 0000 0000 0001 110
g.return_()
return i32('return') # To satisfy mypy
@func_wrapper()
def __u8_rotr__(g: Generator, x: i32, r: i32) -> i32:
s = i32('s') # The shifted part we need to overlay
# Handle cases where we need to shift more than 8 bits
g.local.get(r)
g.i32.const(8)
g.i32.rem_u()
g.local.set(r)
# Now do the rotation
g.local.get(x)
# 0000 0000 1100 0011
g.local.get(r)
# 0000 0000 1100 0011, 3
g.i32.rotr()
# 0110 0000 0000 0000 0000 0000 0001 1000
g.local.tee(s)
# 0110 0000 0000 0000 0000 0000 0001 1000
g.i32.const(255)
# 0110 0000 0000 0000 0000 0000 0001 1000, 0000 0000 1111 1111
g.i32.and_()
# 0000 0000 0000 0000 0000 0000 0001 1000
g.local.get(s)
# 0000 0000 0000 0000 0000 0000 0001 1000, 0110 0000 0000 0000 0000 0000 0001 1000
g.i32.const(4278190080)
# 0000 0000 0000 0000 0000 0000 0001 1000, 0110 0000 0000 0000 0000 0000 0001 1000, 1111 1111 0000 0000 0000 0000 0000 0000
g.i32.and_()
# 0000 0000 0000 0000 0000 0000 0001 1000, 0110 0000 0000 0000 0000 0000 0000 0000
g.i32.const(24)
# 0000 0000 0000 0000 0000 0000 0001 1000, 0110 0000 0000 0000 0000 0000 0000 0000, 24
g.i32.shr_u()
# 0000 0000 0000 0000 0000 0000 0001 1000, 0000 0000 0000 0000 0000 0000 0110 0000
g.i32.or_()
# 0000 0000 0000 0000 0000 0000 0111 1000
g.return_()
return i32('return') # To satisfy mypy
@func_wrapper()
def __u16_rotl__(g: Generator, x: i32, r: i32) -> i32:
s = i32('s') # The shifted part we need to overlay
# Handle cases where we need to shift more than 8 bits
g.local.get(r)
g.i32.const(8)
g.i32.rem_u()
g.local.set(r)
# Now do the rotation
g.local.get(x)
# 0x0000B2C3
g.local.get(r)
# 0x0000B2C3, 3
g.i32.shl()
# 0x00059618
g.local.tee(s)
# 0x00059618
g.i32.const(0xFFFF)
# 0x00059618, 0x0000FFFF
g.i32.and_()
# 0x00009618
g.local.get(s)
# 0x00009618, 0x00059618
g.i32.const(0xFFFF0000)
# 0x00009618, 0x00059618, 0xFFFF0000
g.i32.and_()
# 0x00009618, 0x00050000
g.i32.const(16)
# 0x00009618, 0x00050000, 16
g.i32.shr_u()
# 0x00009618, 0x00000005, 16
g.i32.or_()
# 0x0000961D
g.return_()
return i32('return') # To satisfy mypy
@func_wrapper()
def __u16_rotr__(g: Generator, x: i32, r: i32) -> i32:
s = i32('s') # The shifted part we need to overlay
# Handle cases where we need to shift more than 16 bits
g.local.get(r)
g.i32.const(16)
g.i32.rem_u()
g.local.set(r)
# Now do the rotation
g.local.get(x)
# 0x0000B2C3
g.local.get(r)
# 0x0000B2C3, 3
g.i32.rotr()
# 0x60001658
g.local.tee(s)
# 0x60001658
g.i32.const(0xFFFF)
# 0x60001658, 0x0000FFFF
g.i32.and_()
# 0x00001658
g.local.get(s)
# 0x00001658, 0x60001658
g.i32.const(0xFFFF0000)
# 0x00001658, 0x60001658, 0xFFFF0000
g.i32.and_()
# 0x00001658, 0x60000000
g.i32.const(16)
# 0x00001658, 0x60000000, 16
g.i32.shr_u()
# 0x00001658, 0x00006000
g.i32.or_()
# 0x00007658
g.return_() g.return_()
return i32('return') # To satisfy mypy return i32('return') # To satisfy mypy

View File

@ -1,63 +0,0 @@
"""
Some expression have constraints - those are usually the outmost expressions.
"""
from dataclasses import dataclass
from typing import Callable, Self
from .kindexpr import KindExpr
from .typeexpr import TypeExpr, TypeVariable, instantiate
class TypeConstraint:
"""
Base class for type constraints
"""
__slots__ = ()
def __str__(self) -> str:
raise NotImplementedError
def instantiate(self, known_map: dict[TypeVariable, TypeVariable]) -> Self:
raise NotImplementedError
@dataclass
class ConstrainedExpr:
"""
Also known as a polytype.
We only have rank 1 types, so this is always on the outside.
"""
variables: set[TypeVariable]
expr: TypeExpr
constraints: tuple[TypeConstraint, ...]
def __hash__(self) -> int:
return hash((
tuple(sorted(self.variables)),
self.expr,
self.constraints,
))
# TODO: Move instantiate here? it only makes sense for polytypes
def instantiate_constrained(
constrainedexpr: ConstrainedExpr,
make_variable: Callable[[KindExpr, str], TypeVariable],
) -> tuple[ConstrainedExpr, dict[TypeVariable, TypeVariable]]:
"""
Instantiates a type expression and its constraints
"""
# Would be handier if we had the list of variables on ConstrainedExpr
# So we don't have to pass make_variable
# This also helps with FunctionCall.substitutions
known_map = {
v: make_variable(v.kind, v.name)
for v in constrainedexpr.variables
}
expr = instantiate(constrainedexpr.expr, known_map)
constraints = tuple(
x.instantiate(known_map)
for x in constrainedexpr.constraints
)
return ConstrainedExpr(constrainedexpr.variables, expr, constraints), known_map

View File

@ -1,638 +0,0 @@
from __future__ import annotations
import dataclasses
from typing import Any, Callable, Iterable, Protocol, Sequence, TypeAlias
from ..build.base import BuildBase
from ..ourlang import SourceRef
from ..wasm import WasmTypeFloat32, WasmTypeFloat64, WasmTypeInt32, WasmTypeInt64
from .kindexpr import KindExpr, Star
from .record import Record
from .typeexpr import (
AtomicType,
TypeApplication,
TypeConstructor,
TypeExpr,
TypeLevelNat,
TypeVariable,
is_concrete,
occurs,
replace_variable,
)
class ExpressionProtocol(Protocol):
"""
A protocol for classes that should be updated on substitution
"""
type5: TypeExpr | None
"""
The type to update
"""
PolytypeSubsituteMap: TypeAlias = dict[TypeVariable, TypeExpr]
class Context:
__slots__ = ("build", "placeholder_update", "ptst_update", )
build: BuildBase[Any]
placeholder_update: dict[TypeVariable, ExpressionProtocol | None]
ptst_update: dict[TypeVariable, tuple[PolytypeSubsituteMap, TypeVariable]]
def __init__(self, build: BuildBase[Any]) -> None:
self.build = build
self.placeholder_update = {}
self.ptst_update = {}
def make_placeholder(self, arg: ExpressionProtocol | None = None, kind: KindExpr = Star(), prefix: str = 'p') -> TypeVariable:
res = TypeVariable(kind, f"{prefix}_{len(self.placeholder_update)}")
self.placeholder_update[res] = arg
return res
def register_polytype_subsitutes(self, tvar: TypeVariable, arg: PolytypeSubsituteMap, orig_var: TypeVariable) -> None:
"""
When `tvar` gets subsituted, also set the result in arg with orig_var as key
e.g.
(-) :: Callable[a, a, a]
def foo() -> u32:
return 2 - 1
During typing, we instantiate a into a_3, and get the following constraints:
- u8 ~ p_1
- u8 ~ p_2
- Exists NatNum a_3
- Callable[a_3, a_3, a_3] ~ Callable[p_1, p_2, p_0]
- u8 ~ p_0
When we resolve a_3, then on the call to `-`, we should note that a_3 got resolved
to u32. But we need to use `a` as key, since that's what's used on the definition
"""
assert tvar in self.placeholder_update
assert tvar not in self.ptst_update
self.ptst_update[tvar] = (arg, orig_var)
class Success:
"""
The contsraint checks out.
Nothing new was learned and nothing new needs to be checked.
"""
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
return '(ok)'
class SkipForNow:
"""
Not enough information to resolve this constraint
"""
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
return '(skip for now)'
class ConstraintList(list['ConstraintBase']):
"""
A new list of constraints.
Sometimes, checking one constraint means you get a list of new contraints.
"""
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
return f'(got {len(self)} new constraints)'
@dataclasses.dataclass
class Failure:
"""
Both types are already different - cannot be unified.
"""
msg: str
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
return f'ERR: {self.msg}'
@dataclasses.dataclass
class ReplaceVariable:
"""
A variable should be replaced.
Either by another variable or by a (concrete) type.
"""
var: TypeVariable
typ: TypeExpr
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
return f'{{{self.var.name} := {type_namer(self.typ)}}}'
CheckResult: TypeAlias = Success | SkipForNow | ConstraintList | Failure | ReplaceVariable
def ok() -> CheckResult:
return Success()
def skip_for_now() -> CheckResult:
return SkipForNow()
def new_constraints(lst: Iterable[ConstraintBase]) -> CheckResult:
return ConstraintList(lst)
def fail(msg: str) -> CheckResult:
return Failure(msg)
def replace(var: TypeVariable, typ: TypeExpr) -> CheckResult:
return ReplaceVariable(var, typ)
class ConstraintBase:
__slots__ = ("ctx", "sourceref", )
ctx: Context
sourceref: SourceRef
def __init__(self, ctx: Context, sourceref: SourceRef) -> None:
self.ctx = ctx
self.sourceref = sourceref
def check(self) -> CheckResult:
raise NotImplementedError(self)
def complexity(self) -> int:
raise NotImplementedError
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
pass
class FromLiteralInteger(ConstraintBase):
__slots__ = ('type5', 'literal', )
type5: TypeExpr
literal: int
def __init__(self, ctx: Context, sourceref: SourceRef, type5: TypeExpr, literal: int) -> None:
super().__init__(ctx, sourceref)
self.type5 = type5
self.literal = literal
def check(self) -> CheckResult:
if not is_concrete(self.type5):
return skip_for_now()
type_info = self.ctx.build.type_info_map.get(self.type5.name)
if type_info is None:
return fail('Cannot convert from literal integer')
if type_info.wasm_type is not WasmTypeInt32 and type_info.wasm_type is not WasmTypeInt64:
return fail('Cannot convert from literal integer')
assert type_info.signed is not None # type hint
if not type_info.signed and self.literal < 0:
return fail('May not be negative')
try:
self.literal.to_bytes(type_info.alloc_size, 'big', signed=type_info.signed)
except OverflowError:
return fail(f'Must fit in {type_info.alloc_size} byte(s)')
return ok()
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.type5 = replace_variable(self.type5, var, typ)
def complexity(self) -> int:
return 100 + complexity(self.type5)
def __str__(self) -> str:
return f'FromLiteralInteger {self.ctx.build.type5_name(self.type5)} ~ {self.literal!r}'
class FromLiteralFloat(ConstraintBase):
__slots__ = ('type5', 'literal', )
type5: TypeExpr
literal: float
def __init__(self, ctx: Context, sourceref: SourceRef, type5: TypeExpr, literal: float) -> None:
super().__init__(ctx, sourceref)
self.type5 = type5
self.literal = literal
def check(self) -> CheckResult:
if not is_concrete(self.type5):
return skip_for_now()
type_info = self.ctx.build.type_info_map.get(self.type5.name)
if type_info is None:
return fail('Cannot convert from literal float')
if type_info.wasm_type is not WasmTypeFloat32 and type_info.wasm_type is not WasmTypeFloat64:
return fail('Cannot convert from literal float')
# TODO: Precision check
return ok()
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.type5 = replace_variable(self.type5, var, typ)
def complexity(self) -> int:
return 100 + complexity(self.type5)
def __str__(self) -> str:
return f'FromLiteralFloat {self.ctx.build.type5_name(self.type5)} ~ {self.literal!r}'
class FromLiteralBytes(ConstraintBase):
__slots__ = ('type5', 'literal', )
type5: TypeExpr
literal: bytes
def __init__(self, ctx: Context, sourceref: SourceRef, type5: TypeExpr, literal: bytes) -> None:
super().__init__(ctx, sourceref)
self.type5 = type5
self.literal = literal
def check(self) -> CheckResult:
if not is_concrete(self.type5):
return skip_for_now()
da_arg = self.ctx.build.type5_is_dynamic_array(self.type5)
if da_arg is None or da_arg != self.ctx.build.u8_type5:
return fail('Cannot convert from literal bytes')
return ok()
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.type5 = replace_variable(self.type5, var, typ)
def complexity(self) -> int:
return 100 + complexity(self.type5)
def __str__(self) -> str:
return f'FromLiteralBytes {self.ctx.build.type5_name(self.type5)} ~ {self.literal!r}'
class UnifyTypesConstraint(ConstraintBase):
__slots__ = ("lft", "rgt", "prefix", )
def __init__(self, ctx: Context, sourceref: SourceRef, lft: TypeExpr, rgt: TypeExpr, prefix: str | None = None) -> None:
super().__init__(ctx, sourceref)
self.lft = lft
self.rgt = rgt
self.prefix = prefix
def check(self) -> CheckResult:
lft = self.lft
rgt = self.rgt
if lft == self.rgt:
return ok()
if lft.kind != rgt.kind:
return fail("Kind mismatch")
if isinstance(lft, AtomicType) and isinstance(rgt, AtomicType):
return fail("Not the same type")
if isinstance(lft, AtomicType) and isinstance(rgt, TypeVariable):
return replace(rgt, lft)
if isinstance(lft, AtomicType) and isinstance(rgt, TypeConstructor):
raise NotImplementedError # Should have been caught by kind check above
if isinstance(lft, AtomicType) and isinstance(rgt, TypeApplication):
return fail("Not the same type" if is_concrete(rgt) else "Type shape mismatch")
if isinstance(lft, TypeVariable) and isinstance(rgt, AtomicType):
return replace(lft, rgt)
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeVariable):
return replace(lft, rgt)
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeConstructor):
return replace(lft, rgt)
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeApplication):
if occurs(lft, rgt):
return fail("One type occurs in the other")
return replace(lft, rgt)
if isinstance(lft, TypeConstructor) and isinstance(rgt, AtomicType):
raise NotImplementedError # Should have been caught by kind check above
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeVariable):
return replace(rgt, lft)
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeConstructor):
return fail("Not the same type constructor")
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeApplication):
return fail("Not the same type constructor")
if isinstance(lft, TypeApplication) and isinstance(rgt, AtomicType):
return fail("Not the same type" if is_concrete(lft) else "Type shape mismatch")
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeVariable):
if occurs(rgt, lft):
return fail("One type occurs in the other")
return replace(rgt, lft)
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeConstructor):
return fail("Not the same type constructor")
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeApplication):
## USABILITY HACK
## Often, we have two type applications in the same go
## If so, resolve it in a single step
## (Helps with debugging function unification)
## This *should* not affect the actual type unification
## It's just one less call to UnifyTypesConstraint.check
if isinstance(lft.constructor, TypeApplication) and isinstance(rgt.constructor, TypeApplication):
return new_constraints([
UnifyTypesConstraint(self.ctx, self.sourceref, lft.constructor.constructor, rgt.constructor.constructor),
UnifyTypesConstraint(self.ctx, self.sourceref, lft.constructor.argument, rgt.constructor.argument),
UnifyTypesConstraint(self.ctx, self.sourceref, lft.argument, rgt.argument),
])
return new_constraints([
UnifyTypesConstraint(self.ctx, self.sourceref, lft.constructor, rgt.constructor),
UnifyTypesConstraint(self.ctx, self.sourceref, lft.argument, rgt.argument),
])
raise NotImplementedError(lft, rgt)
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.lft = replace_variable(self.lft, var, typ)
self.rgt = replace_variable(self.rgt, var, typ)
def complexity(self) -> int:
return complexity(self.lft) + complexity(self.rgt)
def __str__(self) -> str:
prefix = f'{self.prefix} :: ' if self.prefix else ''
return f"{prefix}{self.ctx.build.type5_name(self.lft)} ~ {self.ctx.build.type5_name(self.rgt)}"
class CanBeSubscriptedConstraint(ConstraintBase):
__slots__ = ('ret_type5', 'container_type5', 'index_type5', 'index_const', )
ret_type5: TypeExpr
container_type5: TypeExpr
index_type5: TypeExpr
index_const: int | None
def __init__(
self,
ctx: Context,
sourceref: SourceRef,
ret_type5: TypeExpr,
container_type5: TypeExpr,
index_type5: TypeExpr,
index_const: int | None,
) -> None:
super().__init__(ctx, sourceref)
self.ret_type5 = ret_type5
self.container_type5 = container_type5
self.index_type5 = index_type5
self.index_const = index_const
def check(self) -> CheckResult:
if not is_concrete(self.container_type5):
return skip_for_now()
tp_args = self.ctx.build.type5_is_tuple(self.container_type5)
if tp_args is not None:
if self.index_const is None:
return fail('Must index with integer literal')
if len(tp_args) <= self.index_const:
return fail('Tuple index out of range')
return new_constraints([
UnifyTypesConstraint(self.ctx, self.sourceref, tp_args[self.index_const], self.ret_type5),
UnifyTypesConstraint(self.ctx, self.sourceref, self.ctx.build.u32_type5, self.index_type5),
])
if not isinstance(self.container_type5, TypeApplication):
return fail('Missing type class instance')
return new_constraints([
TypeClassInstanceExistsConstraint(
self.ctx,
self.sourceref,
'Subscriptable',
(self.container_type5.constructor, ),
),
UnifyTypesConstraint(self.ctx, self.sourceref, self.container_type5.argument, self.ret_type5),
UnifyTypesConstraint(self.ctx, self.sourceref, self.ctx.build.u32_type5, self.index_type5),
])
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.ret_type5 = replace_variable(self.ret_type5, var, typ)
self.container_type5 = replace_variable(self.container_type5, var, typ)
self.index_type5 = replace_variable(self.index_type5, var, typ)
def complexity(self) -> int:
return 100 + complexity(self.ret_type5) + complexity(self.container_type5) + complexity(self.index_type5)
def __str__(self) -> str:
return f"[] :: t a -> b -> a ~ {self.ctx.build.type5_name(self.container_type5)} -> {self.ctx.build.type5_name(self.index_type5)} -> {self.ctx.build.type5_name(self.ret_type5)}"
class CanAccessStructMemberConstraint(ConstraintBase):
__slots__ = ('ret_type5', 'struct_type5', 'member_name', )
ret_type5: TypeExpr
struct_type5: TypeExpr
member_name: str
def __init__(
self,
ctx: Context,
sourceref: SourceRef,
ret_type5: TypeExpr,
struct_type5: TypeExpr,
member_name: str,
) -> None:
super().__init__(ctx, sourceref)
self.ret_type5 = ret_type5
self.struct_type5 = struct_type5
self.member_name = member_name
def check(self) -> CheckResult:
if not is_concrete(self.struct_type5):
return skip_for_now()
st_args = self.ctx.build.type5_is_struct(self.struct_type5)
if st_args is None:
return fail('Must be a struct')
member_dict = dict(st_args)
if self.member_name not in member_dict:
return fail('Must have a field with this name')
return UnifyTypesConstraint(self.ctx, self.sourceref, self.ret_type5, member_dict[self.member_name]).check()
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.ret_type5 = replace_variable(self.ret_type5, var, typ)
self.struct_type5 = replace_variable(self.struct_type5, var, typ)
def complexity(self) -> int:
return 100 + complexity(self.ret_type5) + complexity(self.struct_type5)
def __str__(self) -> str:
st_args = self.ctx.build.type5_is_struct(self.struct_type5)
member_dict = dict(st_args or [])
member_typ = member_dict.get(self.member_name)
if member_typ is None:
expect = 'a -> b'
else:
expect = f'{self.ctx.build.type5_name(self.struct_type5)} -> {self.ctx.build.type5_name(member_typ)}'
return f".{self.member_name} :: {expect} ~ {self.ctx.build.type5_name(self.struct_type5)} -> {self.ctx.build.type5_name(self.ret_type5)}"
class FromTupleConstraint(ConstraintBase):
__slots__ = ('ret_type5', 'member_type5_list', )
ret_type5: TypeExpr
member_type5_list: list[TypeExpr]
def __init__(
self,
ctx: Context,
sourceref: SourceRef,
ret_type5: TypeExpr,
member_type5_list: Sequence[TypeExpr],
) -> None:
super().__init__(ctx, sourceref)
self.ret_type5 = ret_type5
self.member_type5_list = list(member_type5_list)
def check(self) -> CheckResult:
if not is_concrete(self.ret_type5):
return skip_for_now()
da_arg = self.ctx.build.type5_is_dynamic_array(self.ret_type5)
if da_arg is not None:
return new_constraints([
UnifyTypesConstraint(self.ctx, self.sourceref, da_arg, x)
for x in self.member_type5_list
])
sa_args = self.ctx.build.type5_is_static_array(self.ret_type5)
if sa_args is not None:
sa_len, sa_typ = sa_args
if sa_len != len(self.member_type5_list):
return fail('Tuple element count mismatch')
return new_constraints([
UnifyTypesConstraint(self.ctx, self.sourceref, sa_typ, x)
for x in self.member_type5_list
])
tp_args = self.ctx.build.type5_is_tuple(self.ret_type5)
if tp_args is not None:
if len(tp_args) != len(self.member_type5_list):
return fail('Tuple element count mismatch')
return new_constraints([
UnifyTypesConstraint(self.ctx, self.sourceref, act_typ, exp_typ)
for act_typ, exp_typ in zip(tp_args, self.member_type5_list, strict=True)
])
raise NotImplementedError(self.ret_type5)
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.ret_type5 = replace_variable(self.ret_type5, var, typ)
self.member_type5_list = [
replace_variable(x, var, typ)
for x in self.member_type5_list
]
def complexity(self) -> int:
return 100 + complexity(self.ret_type5) + sum(complexity(x) for x in self.member_type5_list)
def __str__(self) -> str:
args = ', '.join(self.ctx.build.type5_name(x) for x in self.member_type5_list)
return f'FromTuple {self.ctx.build.type5_name(self.ret_type5)} ~ ({args}, )'
class TypeClassInstanceExistsConstraint(ConstraintBase):
__slots__ = ('typeclass', 'arg_list', )
typeclass: str
arg_list: list[TypeExpr]
def __init__(
self,
ctx: Context,
sourceref: SourceRef,
typeclass: str,
arg_list: Sequence[TypeExpr]
) -> None:
super().__init__(ctx, sourceref)
self.typeclass = typeclass
self.arg_list = list(arg_list)
def check(self) -> CheckResult:
c_arg_list = [
x for x in self.arg_list if is_concrete(x)
]
if len(c_arg_list) != len(self.arg_list):
return skip_for_now()
if any(isinstance(x, Record) for x in c_arg_list):
# TODO: Allow users to implement type classes on their structs
return fail('Missing type class instance')
key = tuple(c_arg_list)
existing_instances = self.ctx.build.type_class_instances[self.typeclass]
if key in existing_instances:
return ok()
return fail('Missing type class instance')
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.arg_list = [
replace_variable(x, var, typ)
for x in self.arg_list
]
def complexity(self) -> int:
return 100 + sum(complexity(x) for x in self.arg_list)
def __str__(self) -> str:
args = ' '.join(self.ctx.build.type5_name(x) for x in self.arg_list)
return f'Exists {self.typeclass} {args}'
def complexity(expr: TypeExpr) -> int:
if isinstance(expr, AtomicType | TypeLevelNat):
return 1
if isinstance(expr, TypeConstructor):
return 2
if isinstance(expr, TypeVariable):
return 5
if isinstance(expr, TypeApplication):
return complexity(expr.constructor) + complexity(expr.argument)
raise NotImplementedError(expr)

View File

@ -1,290 +0,0 @@
from typing import Any, Generator
from .. import ourlang
from ..typeclass import TypeClassConstraint
from .constrainedexpr import ConstrainedExpr, instantiate_constrained
from .constraints import (
CanAccessStructMemberConstraint,
CanBeSubscriptedConstraint,
ConstraintBase,
Context,
FromLiteralBytes,
FromLiteralFloat,
FromLiteralInteger,
FromTupleConstraint,
TypeClassInstanceExistsConstraint,
UnifyTypesConstraint,
)
from .kindexpr import KindExpr
from .typeexpr import TypeApplication, TypeExpr, TypeVariable, is_concrete
ConstraintGenerator = Generator[ConstraintBase, None, None]
def phasm_type5_generate_constraints(ctx: Context, inp: ourlang.Module[Any]) -> list[ConstraintBase]:
return [*module(ctx, inp)]
def expression_constant_primitive(ctx: Context, inp: ourlang.ConstantPrimitive, phft: TypeVariable) -> ConstraintGenerator:
if isinstance(inp.value, int):
yield FromLiteralInteger(ctx, inp.sourceref, phft, inp.value)
return
if isinstance(inp.value, float):
yield FromLiteralFloat(ctx, inp.sourceref, phft, inp.value)
return
raise NotImplementedError(inp.value)
def expression_constant_bytes(ctx: Context, inp: ourlang.ConstantBytes, phft: TypeVariable) -> ConstraintGenerator:
yield FromLiteralBytes(ctx, inp.sourceref, phft, inp.value)
def expression_constant_tuple(ctx: Context, inp: ourlang.ConstantTuple, phft: TypeVariable) -> ConstraintGenerator:
member_type5_list = [
ctx.make_placeholder(arg)
for arg in inp.value
]
for arg, arg_phft in zip(inp.value, member_type5_list):
yield from expression(ctx, arg, arg_phft)
yield FromTupleConstraint(ctx, inp.sourceref, phft, member_type5_list)
def expression_constant_struct(ctx: Context, inp: ourlang.ConstantStruct, phft: TypeVariable) -> ConstraintGenerator:
member_type5_list = [
ctx.make_placeholder(arg)
for arg in inp.value
]
for arg, arg_phft in zip(inp.value, member_type5_list):
yield from expression(ctx, arg, arg_phft)
lft = ctx.build.type5_make_function([x[1] for x in inp.struct_type5.fields] + [inp.struct_type5])
rgt = ctx.build.type5_make_function(member_type5_list + [phft])
yield UnifyTypesConstraint(ctx, inp.sourceref, lft, rgt)
def expression_constant_memory_stored(ctx: Context, inp: ourlang.ConstantMemoryStored, phft: TypeVariable) -> ConstraintGenerator:
if isinstance(inp, ourlang.ConstantBytes):
yield from expression_constant_bytes(ctx, inp, phft)
return
if isinstance(inp, ourlang.ConstantTuple):
yield from expression_constant_tuple(ctx, inp, phft)
return
if isinstance(inp, ourlang.ConstantStruct):
yield from expression_constant_struct(ctx, inp, phft)
return
raise NotImplementedError(inp)
def expression_constant(ctx: Context, inp: ourlang.Constant, phft: TypeVariable) -> ConstraintGenerator:
if isinstance(inp, ourlang.ConstantPrimitive):
yield from expression_constant_primitive(ctx, inp, phft)
return
if isinstance(inp, ourlang.ConstantMemoryStored):
yield from expression_constant_memory_stored(ctx, inp, phft)
return
raise NotImplementedError(inp)
def expression_variable_reference(ctx: Context, inp: ourlang.VariableReference, phft: TypeVariable) -> ConstraintGenerator:
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.variable.type5, phft, prefix=inp.variable.name)
def expression_binary_operator(ctx: Context, inp: ourlang.BinaryOp, phft: TypeVariable) -> ConstraintGenerator:
yield from _expression_binary_operator_or_function_call(
ctx,
inp.operator,
inp.polytype_substitutions,
[inp.left, inp.right],
inp.sourceref,
f'({inp.operator.name})',
phft,
)
def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: TypeVariable) -> ConstraintGenerator:
yield from _expression_binary_operator_or_function_call(
ctx,
inp.function,
inp.polytype_substitutions,
inp.arguments,
inp.sourceref,
inp.function.name,
phft,
)
def _expression_binary_operator_or_function_call(
ctx: Context,
function: ourlang.Function | ourlang.FunctionParam,
polytype_substitutions: dict[TypeVariable, TypeExpr],
arguments: list[ourlang.Expression],
sourceref: ourlang.SourceRef,
function_name: str,
phft: TypeVariable,
) -> ConstraintGenerator:
arg_typ_list = []
for arg in arguments:
arg_tv = ctx.make_placeholder(arg)
yield from expression(ctx, arg, arg_tv)
arg_typ_list.append(arg_tv)
def make_placeholder(x: KindExpr, p: str) -> TypeVariable:
return ctx.make_placeholder(kind=x, prefix=p)
ftp5 = function.type5
assert ftp5 is not None
if isinstance(ftp5, ConstrainedExpr):
ftp5, phft_lookup = instantiate_constrained(ftp5, make_placeholder)
for orig_tvar, tvar in phft_lookup.items():
ctx.register_polytype_subsitutes(tvar, polytype_substitutions, orig_tvar)
for type_constraint in ftp5.constraints:
if isinstance(type_constraint, TypeClassConstraint):
yield TypeClassInstanceExistsConstraint(ctx, sourceref, type_constraint.cls.name, type_constraint.variables)
continue
raise NotImplementedError(type_constraint)
ftp5 = ftp5.expr
else:
assert is_concrete(ftp5)
expr_type = ctx.build.type5_make_function(arg_typ_list + [phft])
yield UnifyTypesConstraint(ctx, sourceref, ftp5, expr_type, prefix=function_name)
def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: TypeVariable) -> ConstraintGenerator:
assert inp.function.type5 is not None # Todo: Make not nullable
ftp5 = inp.function.type5
if isinstance(ftp5, ConstrainedExpr):
ftp5 = ftp5.expr
yield UnifyTypesConstraint(ctx, inp.sourceref, ftp5, phft, prefix=inp.function.name)
def expression_tuple_instantiation(ctx: Context, inp: ourlang.TupleInstantiation, phft: TypeVariable) -> ConstraintGenerator:
arg_typ_list = []
for arg in inp.elements:
arg_tv = ctx.make_placeholder(arg)
yield from expression(ctx, arg, arg_tv)
arg_typ_list.append(arg_tv)
yield FromTupleConstraint(ctx, inp.sourceref, phft, arg_typ_list)
def expression_subscript(ctx: Context, inp: ourlang.Subscript, phft: TypeVariable) -> ConstraintGenerator:
varref_phft = ctx.make_placeholder(inp.varref)
index_phft = ctx.make_placeholder(inp.index)
yield from expression(ctx, inp.varref, varref_phft)
yield from expression(ctx, inp.index, index_phft)
if isinstance(inp.index, ourlang.ConstantPrimitive) and isinstance(inp.index.value, int):
yield CanBeSubscriptedConstraint(ctx, inp.sourceref, phft, varref_phft, index_phft, inp.index.value)
else:
yield CanBeSubscriptedConstraint(ctx, inp.sourceref, phft, varref_phft, index_phft, None)
def expression_access_struct_member(ctx: Context, inp: ourlang.AccessStructMember, phft: TypeVariable) -> ConstraintGenerator:
varref_phft = ctx.make_placeholder(inp.varref)
yield from expression_variable_reference(ctx, inp.varref, varref_phft)
yield CanAccessStructMemberConstraint(ctx, inp.sourceref, phft, varref_phft, inp.member)
def expression(ctx: Context, inp: ourlang.Expression, phft: TypeVariable) -> ConstraintGenerator:
if isinstance(inp, ourlang.Constant):
yield from expression_constant(ctx, inp, phft)
return
if isinstance(inp, ourlang.VariableReference):
yield from expression_variable_reference(ctx, inp, phft)
return
if isinstance(inp, ourlang.BinaryOp):
yield from expression_binary_operator(ctx, inp, phft)
return
if isinstance(inp, ourlang.FunctionCall):
yield from expression_function_call(ctx, inp, phft)
return
if isinstance(inp, ourlang.FunctionReference):
yield from expression_function_reference(ctx, inp, phft)
return
if isinstance(inp, ourlang.TupleInstantiation):
yield from expression_tuple_instantiation(ctx, inp, phft)
return
if isinstance(inp, ourlang.Subscript):
yield from expression_subscript(ctx, inp, phft)
return
if isinstance(inp, ourlang.AccessStructMember):
yield from expression_access_struct_member(ctx, inp, phft)
return
raise NotImplementedError(inp)
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
phft = ctx.make_placeholder(inp.value)
if fun.type5 is None:
raise NotImplementedError("Deducing function type - you'll have to annotate it.")
if isinstance(fun.type5, TypeApplication):
args = ctx.build.type5_is_function(fun.type5)
assert args is not None
type5 = args[-1]
else:
type5 = fun.type5.expr if isinstance(fun.type5, ConstrainedExpr) else fun.type5
yield from expression(ctx, inp.value, phft)
yield UnifyTypesConstraint(ctx, inp.sourceref, type5, phft, prefix=f'{fun.name}(...)')
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
test_phft = ctx.make_placeholder(inp.test)
yield from expression(ctx, inp.test, test_phft)
yield UnifyTypesConstraint(ctx, inp.test.sourceref, test_phft, ctx.build.bool_type5)
for stmt in inp.statements:
yield from statement(ctx, fun, stmt)
for stmt in inp.else_statements:
yield from statement(ctx, fun, stmt)
def statement(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement) -> ConstraintGenerator:
if isinstance(inp, ourlang.StatementReturn):
yield from statement_return(ctx, fun, inp)
return
if isinstance(inp, ourlang.StatementIf):
yield from statement_if(ctx, fun, inp)
return
raise NotImplementedError(inp)
def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator:
for stmt in inp.statements:
yield from statement(ctx, inp, stmt)
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator:
phft = ctx.make_placeholder(inp.constant)
yield from expression_constant(ctx, inp.constant, phft)
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.type5, phft)
def module(ctx: Context, inp: ourlang.Module[Any]) -> ConstraintGenerator:
for cdef in inp.constant_defs.values():
yield from module_constant_def(ctx, cdef)
for func in inp.functions.values():
if func.imported:
continue
yield from function(ctx, func)
# TODO: Generalize?

View File

@ -1,57 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TypeAlias
@dataclass
class Star:
def __rshift__(self, other: KindExpr) -> Arrow:
return Arrow(self, other)
def __str__(self) -> str:
return "*"
def __hash__(self) -> int:
return 0 # All Stars are the same
@dataclass
class Nat:
def __rshift__(self, other: KindExpr) -> Arrow:
return Arrow(self, other)
def __str__(self) -> str:
return "Nat"
def __hash__(self) -> int:
return 0 # All Stars are the same
@dataclass
class Arrow:
"""
Represents an arrow kind `K1 -> K2`.
To create K1 -> K2 -> K3, pass an Arrow for result_kind.
For now, we do not support Arrows as arguments (i.e.,
no higher order kinds).
"""
arg_kind: Star | Nat
result_kind: KindExpr
def __str__(self) -> str:
if isinstance(self.arg_kind, Star):
arg_kind = "*"
else:
arg_kind = f"({str(self.arg_kind)})"
if isinstance(self.result_kind, Star):
result_kind = "*"
else:
result_kind = f"({str(self.result_kind)})"
return f"{arg_kind} -> {result_kind}"
def __hash__(self) -> int:
return hash((self.arg_kind, self.result_kind, ))
KindExpr: TypeAlias = Star | Nat | Arrow

View File

@ -1,43 +0,0 @@
from dataclasses import dataclass
from .kindexpr import Star
from .typeexpr import AtomicType, TypeApplication, is_concrete
@dataclass
class Record(AtomicType):
"""
Records are a fundamental type. But we need to store some extra info.
"""
fields: tuple[tuple[str, AtomicType | TypeApplication], ...]
def __init__(self, name: str, fields: tuple[tuple[str, AtomicType | TypeApplication], ...]) -> None:
for field_name, field_type in fields:
if field_type.kind != Star():
raise TypeError(f"Record fields must not be constructors ({field_name} :: {field_type})")
if not is_concrete(field_type):
raise TypeError("Record field types must be concrete types ({field_name} :: {field_type})")
super().__init__(name)
self.fields = fields
def __str__(self) -> str:
args = ", ".join(
f"{field_name} :: {field_type}"
for field_name, field_type in self.fields
)
return f"{self.name} {{{args}}} :: {self.kind}"
# @dataclass
# class RecordConstructor(TypeConstructor):
# """
# TODO.
# i.e.:
# ```
# class Foo[T, R]:
# lft: T
# rgt: R
# """
# name: str

View File

@ -1,122 +0,0 @@
from typing import Any
from ..ourlang import Module
from .constraints import ConstraintBase, ConstraintList, Context, Failure, ReplaceVariable, SkipForNow, Success
from .fromast import phasm_type5_generate_constraints
from .typeexpr import TypeExpr, TypeVariable, is_concrete, replace_variable
MAX_RESTACK_COUNT = 100
class Type5SolverException(Exception):
pass
def phasm_type5(inp: Module[Any], verbose: bool = False) -> None:
ctx = Context(inp.build)
constraint_list = phasm_type5_generate_constraints(ctx, inp)
assert constraint_list
placeholder_types: dict[TypeVariable, TypeExpr] = {}
error_list: list[tuple[str, str, str]] = []
if verbose:
print('Constraints')
for _ in range(MAX_RESTACK_COUNT):
if verbose:
for constraint in constraint_list:
print(f"{constraint.sourceref!s} {constraint!s}")
print("Validating")
new_constraint_list: list[ConstraintBase] = []
# Iterate using a while and pop since on ReplaceVariable
# we want to iterate over the list as well, and since on
# ConstraintList we want to treat those first.
remaining_constraint_list = sorted(constraint_list, key=lambda x: x.complexity())
while remaining_constraint_list:
constraint = remaining_constraint_list.pop(0)
result = constraint.check()
if verbose:
print(f"{constraint.sourceref!s} {constraint!s}")
print(f"{constraint.sourceref!s} => {result.to_str(inp.build.type5_name)}")
match result:
case Success():
# This constraint was valid
continue
case SkipForNow():
# We have to check later
new_constraint_list.append(constraint)
continue
case ConstraintList(items):
# This constraint was valid, but we have new once
# Do this as the first next items, so when users are reading the
# solver output they don't need to context switch.
remaining_constraint_list = items + remaining_constraint_list
if verbose:
for new_const in items:
print(f"{constraint.sourceref!s} => + {new_const!s}")
continue
case Failure(msg):
error_list.append((str(constraint.sourceref), str(constraint), msg, ))
continue
case ReplaceVariable(action_var, action_typ):
assert action_var not in placeholder_types # When does this happen?
assert not isinstance(action_typ, TypeVariable) or action_typ not in placeholder_types # When does this happen?
assert action_var != action_typ # When does this happen?
# Ensure all existing found types are updated
# if they have this variable somewhere inside them.
placeholder_types = {
k: replace_variable(v, action_var, action_typ)
for k, v in placeholder_types.items()
}
# Add the new variable to the registry
placeholder_types[action_var] = action_typ
# Also update all constraints that may refer to this variable
# that they now have more detailed information.
for oth_const in new_constraint_list + remaining_constraint_list:
old_str = str(oth_const)
oth_const.replace_variable(action_var, action_typ)
new_str = str(oth_const)
if verbose and old_str != new_str:
print(f"{oth_const.sourceref!s} => - {old_str!s}")
print(f"{oth_const.sourceref!s} => + {new_str!s}")
continue
if error_list:
raise Type5SolverException(error_list)
if not new_constraint_list:
break
if verbose:
print()
print('New round')
constraint_list = new_constraint_list
if new_constraint_list:
raise Type5SolverException('Was unable to complete typing this program')
for placeholder, expression in ctx.placeholder_update.items():
if expression is None:
continue
resolved_type5 = placeholder_types[placeholder]
assert is_concrete(resolved_type5) # When does this happen?
expression.type5 = resolved_type5
for placeholder, (ptst_map, orig_tvar) in ctx.ptst_update.items():
resolved_type5 = placeholder_types[placeholder]
assert is_concrete(resolved_type5) # When does this happen?
ptst_map[orig_tvar] = resolved_type5

View File

@ -1,201 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
from .kindexpr import Arrow, KindExpr, Nat, Star
@dataclass
class TypeExpr:
kind: KindExpr
name: str
def __str__(self) -> str:
return f"{self.name} :: {self.kind}"
@dataclass
class AtomicType(TypeExpr):
def __init__(self, name: str) -> None:
super().__init__(Star(), name)
def __hash__(self) -> int:
return hash((self.kind, self.name))
@dataclass
class TypeLevelNat(TypeExpr):
value: int
def __init__(self, nat: int) -> None:
assert 0 <= nat
super().__init__(Nat(), str(nat))
self.value = nat
def __hash__(self) -> int:
return hash((self.kind, self.name, self.value))
@dataclass
class TypeVariable(TypeExpr):
"""
A placeholder in a type expression
"""
def __hash__(self) -> int:
return hash((self.kind, self.name))
def __lt__(self, other: object) -> bool:
if not isinstance(other, TypeVariable):
raise TypeError
return self.name < other.name
@dataclass
class TypeConstructor(TypeExpr):
def __init__(self, kind: Arrow, name: str) -> None:
super().__init__(kind, name)
def __hash__(self) -> int:
return hash((self.kind, self.name))
@dataclass
class TypeApplication(TypeExpr):
constructor: TypeConstructor | TypeApplication | TypeVariable
argument: TypeExpr
def __init__(
self,
constructor: TypeConstructor | TypeApplication | TypeVariable,
argument: TypeExpr,
) -> None:
if isinstance(constructor.kind, Star):
raise TypeError("A constructor cannot be a concrete type")
if isinstance(constructor.kind, Nat):
raise TypeError("A constructor cannot be a number")
if constructor.kind.arg_kind != argument.kind:
raise TypeError("Argument does match construtor's expectations")
super().__init__(
constructor.kind.result_kind,
f"{constructor.name} ({argument.name})",
)
self.constructor = constructor
self.argument = argument
def __hash__(self) -> int:
return hash((self.kind, self.name, self.constructor, self.argument))
def occurs(lft: TypeVariable, rgt: TypeApplication) -> bool:
"""
Checks whether the given variable occurs in the given application.
"""
if lft == rgt.constructor:
return True
if lft == rgt.argument:
return True
if isinstance(rgt.argument, TypeApplication):
return occurs(lft, rgt.argument)
return False
def is_concrete(lft: TypeExpr) -> bool:
"""
A concrete type has no variables in it.
This is also known as a monomorphic type or a closed type.
TODO: I don't know the differen between them yet.
"""
if isinstance(lft, AtomicType):
return True
if isinstance(lft, TypeLevelNat):
return True
if isinstance(lft, TypeVariable):
return False
if isinstance(lft, TypeConstructor):
return True
if isinstance(lft, TypeApplication):
return is_concrete(lft.constructor) and is_concrete(lft.argument)
raise NotImplementedError
def is_polymorphic(lft: TypeExpr) -> bool:
"""
A polymorphic type has one or more variables in it.
"""
return not is_concrete(lft)
def replace_variable(expr: TypeExpr, var: TypeVariable, rep_expr: TypeExpr) -> TypeExpr:
assert var.kind == rep_expr.kind, (var, rep_expr, )
if isinstance(expr, AtomicType):
# Nothing to replace
return expr
if isinstance(expr, TypeLevelNat):
# Nothing to replace
return expr
if isinstance(expr, TypeVariable):
if expr == var:
return rep_expr
return expr
if isinstance(expr, TypeConstructor):
# Nothing to replace
return expr
if isinstance(expr, TypeApplication):
new_constructor = replace_variable(expr.constructor, var, rep_expr)
assert isinstance(new_constructor, TypeConstructor | TypeApplication | TypeVariable) # type hint
return TypeApplication(
constructor=new_constructor,
argument=replace_variable(expr.argument, var, rep_expr),
)
raise NotImplementedError
def instantiate(
expr: TypeExpr,
known_map: dict[TypeVariable, TypeVariable],
) -> TypeExpr:
"""
Make a copy of all variables.
This is need when you use a polymorphic function in two places. In the
one place, `t a` may refer to `u32[...]` and in another to `f32[4]`.
These should not be unified since they refer to a different monomorphic
version of the function.
"""
if isinstance(expr, AtomicType):
return expr
if isinstance(expr, TypeLevelNat):
return expr
if isinstance(expr, TypeVariable):
return known_map[expr]
if isinstance(expr, TypeConstructor):
return expr
if isinstance(expr, TypeApplication):
new_constructor = instantiate(expr.constructor, known_map)
assert isinstance(new_constructor, TypeConstructor | TypeApplication | TypeVariable) # type hint
return TypeApplication(
constructor=new_constructor,
argument=instantiate(expr.argument, known_map),
)
raise NotImplementedError(expr)

View File

@ -1,50 +0,0 @@
from .record import Record
from .typeexpr import (
AtomicType,
TypeApplication,
TypeConstructor,
TypeExpr,
TypeLevelNat,
TypeVariable,
)
class TypeRouter[T]:
def when_atomic(self, typ: AtomicType) -> T:
raise NotImplementedError(typ)
def when_application(self, typ: TypeApplication) -> T:
raise NotImplementedError(typ)
def when_constructor(self, typ: TypeConstructor) -> T:
raise NotImplementedError(typ)
def when_record(self, typ: Record) -> T:
raise NotImplementedError(typ)
def when_type_level_nat(self, typ: TypeLevelNat) -> T:
raise NotImplementedError(typ)
def when_variable(self, typ: TypeVariable) -> T:
raise NotImplementedError(typ)
def __call__(self, typ: TypeExpr) -> T:
if isinstance(typ, AtomicType):
if isinstance(typ, Record):
return self.when_record(typ)
return self.when_atomic(typ)
if isinstance(typ, TypeApplication):
return self.when_application(typ)
if isinstance(typ, TypeConstructor):
return self.when_constructor(typ)
if isinstance(typ, TypeLevelNat):
return self.when_type_level_nat(typ)
if isinstance(typ, TypeVariable):
return self.when_variable(typ)
raise NotImplementedError(typ)

View File

@ -1,43 +0,0 @@
from __future__ import annotations
import dataclasses
from typing import Iterable
from ..type5.constrainedexpr import ConstrainedExpr, TypeConstraint
from ..type5.typeexpr import TypeExpr, TypeVariable, instantiate
@dataclasses.dataclass
class TypeClass:
name: str
variables: tuple[TypeVariable, ...]
methods: dict[str, TypeExpr | ConstrainedExpr] = dataclasses.field(default_factory=dict)
operators: dict[str, TypeExpr | ConstrainedExpr] = dataclasses.field(default_factory=dict)
class TypeClassConstraint(TypeConstraint):
__slots__ = ('cls', 'variables', )
def __init__(self, cls: TypeClass, variables: Iterable[TypeVariable]) -> None:
self.cls = cls
self.variables = tuple(variables)
def __str__(self) -> str:
vrs = ' '.join(x.name for x in self.variables)
return f'{self.cls.name} {vrs}'
def __repr__(self) -> str:
vrs = ', '.join(str(x) for x in self.variables)
return f'TypeClassConstraint({self.cls.name}, [{vrs}])'
def instantiate(self, known_map: dict[TypeVariable, TypeVariable]) -> TypeClassConstraint:
return TypeClassConstraint(
self.cls,
[
# instantiate returns a TypeVariable if you give it a TypeVariable,
# but I can't seem to convince mypy of that.
instantiate(var, known_map) # type: ignore
for var in self.variables
]
)

115
phasm/typer.py Normal file
View File

@ -0,0 +1,115 @@
"""
Type checks and enriches the given ast
"""
from . import ourlang
from .typing import Context, TypeConstraintBitWidth, TypeConstraintPrimitive, TypeConstraintSigned, TypeVar
def phasm_type(inp: ourlang.Module) -> None:
module(inp)
def constant(ctx: 'Context', inp: ourlang.Constant) -> 'TypeVar':
if isinstance(inp, ourlang.ConstantPrimitive):
result = ctx.new_var()
if not isinstance(inp.value, int):
raise NotImplementedError('Float constants in new type system')
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT))
# Need at least this many bits to store this constant value
result.add_constraint(TypeConstraintBitWidth(minb=len(bin(inp.value)) - 2))
# Don't dictate anything about signedness - you can use a signed
# constant in an unsigned variable if the bits fit
result.add_constraint(TypeConstraintSigned(None))
result.add_location(str(inp.value))
inp.type_var = result
return result
raise NotImplementedError(constant, inp)
def expression(ctx: 'Context', inp: ourlang.Expression) -> 'TypeVar':
if isinstance(inp, ourlang.Constant):
return constant(ctx, inp)
if isinstance(inp, ourlang.VariableReference):
assert inp.variable.type_var is not None, inp
return inp.variable.type_var
if isinstance(inp, ourlang.BinaryOp):
if inp.operator not in ('+', '-', '|', '&', '^'):
raise NotImplementedError(expression, inp, inp.operator)
left = expression(ctx, inp.left)
right = expression(ctx, inp.right)
ctx.unify(left, right)
return left
if isinstance(inp, ourlang.FunctionCall):
assert inp.function.returns_type_var is not None
if inp.function.posonlyargs:
raise NotImplementedError
return inp.function.returns_type_var
raise NotImplementedError(expression, inp)
def function(ctx: 'Context', inp: ourlang.Function) -> None:
if len(inp.statements) != 1 or not isinstance(inp.statements[0], ourlang.StatementReturn):
raise NotImplementedError('Functions with not just a return statement')
typ = expression(ctx, inp.statements[0].value)
assert inp.returns_type_var is not None
ctx.unify(inp.returns_type_var, typ)
return
def module(inp: ourlang.Module) -> None:
ctx = Context()
for func in inp.functions.values():
func.returns_type_var = _convert_old_type(ctx, func.returns, f'{func.name}.(returns)')
for param in func.posonlyargs:
param.type_var = _convert_old_type(ctx, param.type, f'{func.name}.{param.name}')
for func in inp.functions.values():
function(ctx, func)
from . import typing
def _convert_old_type(ctx: Context, inp: typing.TypeBase, location: str) -> TypeVar:
result = ctx.new_var()
if isinstance(inp, typing.TypeUInt8):
result.add_constraint(TypeConstraintBitWidth(minb=8, maxb=8))
result.add_constraint(TypeConstraintSigned(False))
result.add_location(location)
return result
if isinstance(inp, typing.TypeUInt32):
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
result.add_constraint(TypeConstraintSigned(False))
result.add_location(location)
return result
if isinstance(inp, typing.TypeUInt64):
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
result.add_constraint(TypeConstraintSigned(False))
result.add_location(location)
return result
if isinstance(inp, typing.TypeInt32):
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
result.add_constraint(TypeConstraintSigned(True))
result.add_location(location)
return result
if isinstance(inp, typing.TypeInt64):
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
result.add_constraint(TypeConstraintSigned(True))
result.add_location(location)
return result
raise NotImplementedError(_convert_old_type, inp)

379
phasm/typing.py Normal file
View File

@ -0,0 +1,379 @@
"""
The phasm type system
"""
from typing import Dict, Optional, List, Type
import enum
from .exceptions import TypingError
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
)
## NEW STUFF BELOW
class TypingNarrowProtoError(TypingError):
pass
class TypingNarrowError(TypingError):
def __init__(self, l: 'TypeVar', r: 'TypeVar', msg: str) -> None:
super().__init__(
f'Cannot narrow types {l} and {r}: {msg}'
)
class TypeConstraintBase:
def narrow(self, other: 'TypeConstraintBase') -> 'TypeConstraintBase':
raise NotImplementedError('narrow', self, other)
class TypeConstraintPrimitive(TypeConstraintBase):
__slots__ = ('primitive', )
class Primitive(enum.Enum):
INT = 0
FLOAT = 1
primitive: Primitive
def __init__(self, primitive: Primitive) -> None:
self.primitive = primitive
def narrow(self, other: 'TypeConstraintBase') -> 'TypeConstraintPrimitive':
if not isinstance(other, TypeConstraintPrimitive):
raise Exception('Invalid comparison')
if self.primitive != other.primitive:
raise TypingNarrowProtoError('Primitive does not match')
return TypeConstraintPrimitive(self.primitive)
def __repr__(self) -> str:
return f'Primitive={self.primitive.name}'
class TypeConstraintSigned(TypeConstraintBase):
__slots__ = ('signed', )
signed: Optional[bool]
def __init__(self, signed: Optional[bool]) -> None:
self.signed = signed
def narrow(self, other: 'TypeConstraintBase') -> 'TypeConstraintSigned':
if not isinstance(other, TypeConstraintSigned):
raise Exception('Invalid comparison')
if other.signed is None:
return TypeConstraintSigned(self.signed)
if self.signed is None:
return TypeConstraintSigned(other.signed)
if self.signed is not other.signed:
raise TypingNarrowProtoError('Signed does not match')
return TypeConstraintSigned(self.signed)
def __repr__(self) -> str:
return f'Signed={self.signed}'
class TypeConstraintBitWidth(TypeConstraintBase):
__slots__ = ('minb', 'maxb', )
minb: int
maxb: int
def __init__(self, *, minb: int = 1, maxb: int = 64) -> None:
assert minb is not None or maxb is not None
assert maxb <= 64 # For now, support up to 64 bits values
self.minb = minb
self.maxb = maxb
def narrow(self, other: 'TypeConstraintBase') -> 'TypeConstraintBitWidth':
if not isinstance(other, TypeConstraintBitWidth):
raise Exception('Invalid comparison')
if self.minb > other.maxb:
raise TypingNarrowProtoError('Min bitwidth exceeds other max bitwidth')
if other.minb > self.maxb:
raise TypingNarrowProtoError('Other min bitwidth exceeds max bitwidth')
return TypeConstraintBitWidth(
minb=max(self.minb, other.minb),
maxb=min(self.maxb, other.maxb),
)
def __repr__(self) -> str:
return f'BitWidth={self.minb}..{self.maxb}'
class TypeVar:
def __init__(self, ctx: 'Context') -> None:
self.context = ctx
self.constraints: Dict[Type[TypeConstraintBase], TypeConstraintBase] = {}
self.locations: List[str] = []
def add_constraint(self, newconst: TypeConstraintBase) -> None:
if newconst.__class__ in self.constraints:
self.constraints[newconst.__class__] = self.constraints[newconst.__class__].narrow(newconst)
else:
self.constraints[newconst.__class__] = newconst
def add_location(self, ref: str) -> None:
self.locations.append(ref)
def __repr__(self) -> str:
return (
'TypeVar<'
+ '; '.join(map(repr, self.constraints.values()))
+ '; locations: '
+ ', '.join(self.locations)
+ '>'
)
class Context:
def new_var(self) -> TypeVar:
return TypeVar(self)
def unify(self, l: 'TypeVar', r: 'TypeVar') -> None:
newtypevar = self.new_var()
try:
for const in l.constraints.values():
newtypevar.add_constraint(const)
for const in r.constraints.values():
newtypevar.add_constraint(const)
except TypingNarrowProtoError as ex:
raise TypingNarrowError(l, r, str(ex)) from None
newtypevar.locations.extend(l.locations)
newtypevar.locations.extend(r.locations)
# Make pointer locations to the constraints and locations
# so they get linked together throughout the unification
l.constraints = newtypevar.constraints
l.locations = newtypevar.locations
r.constraints = newtypevar.constraints
r.locations = newtypevar.locations
return
def simplify(inp: TypeVar) -> Optional[str]:
tc_prim = inp.constraints.get(TypeConstraintPrimitive)
tc_bits = inp.constraints.get(TypeConstraintBitWidth)
tc_sign = inp.constraints.get(TypeConstraintSigned)
if tc_prim is None:
return None
assert isinstance(tc_prim, TypeConstraintPrimitive) # type hint
primitive = tc_prim.primitive
if primitive is TypeConstraintPrimitive.Primitive.INT:
if tc_bits is None or tc_sign is None:
return None
assert isinstance(tc_bits, TypeConstraintBitWidth) # type hint
assert isinstance(tc_sign, TypeConstraintSigned) # type hint
if tc_sign.signed is None or tc_bits.minb != tc_bits.maxb or tc_bits.minb not in (8, 32, 64):
return None
base = 'i' if tc_sign.signed else 'u'
return f'{base}{tc_bits.minb}'
return None

View File

@ -5,7 +5,6 @@ and being able to conver it to Web Assembly Text Format
from typing import Iterable, List, Optional, Tuple from typing import Iterable, List, Optional, Tuple
class WatSerializable: class WatSerializable:
""" """
Mixin for clases that can be serialized as WebAssembly Text Mixin for clases that can be serialized as WebAssembly Text
@ -105,17 +104,11 @@ class Import(WatSerializable):
else f' (result {self.result.to_wat()})' else f' (result {self.result.to_wat()})'
) )
class StatementBase(WatSerializable): class Statement(WatSerializable):
pass
class Statement(StatementBase ):
""" """
Represents a Web Assembly statement Represents a Web Assembly statement
""" """
def __init__(self, name: str, *args: str, comment: Optional[str] = None): def __init__(self, name: str, *args: str, comment: Optional[str] = None):
assert ' ' not in name, 'Please pass argument separately'
assert name != 'call', 'Please use StatementCall'
self.name = name self.name = name
self.args = args self.args = args
self.comment = comment self.comment = comment
@ -126,16 +119,6 @@ class Statement(StatementBase ):
return f'{self.name} {args}{comment}' return f'{self.name} {args}{comment}'
class StatementCall(StatementBase ):
def __init__(self, func_name: str, comment: str | None = None):
self.func_name = func_name
self.comment = comment
def to_wat(self) -> str:
comment = f' ;; {self.comment}' if self.comment else ''
return f'call ${self.func_name} {comment}'
class Function(WatSerializable): class Function(WatSerializable):
""" """
Represents a Web Assembly function Represents a Web Assembly function
@ -147,7 +130,7 @@ class Function(WatSerializable):
params: Iterable[Param], params: Iterable[Param],
locals_: Iterable[Param], locals_: Iterable[Param],
result: WasmType, result: WasmType,
statements: Iterable[StatementBase], statements: Iterable[Statement],
) -> None: ) -> None:
self.name = name self.name = name
self.exported_name = exported_name self.exported_name = exported_name
@ -202,7 +185,6 @@ class Module(WatSerializable):
""" """
def __init__(self) -> None: def __init__(self) -> None:
self.imports: List[Import] = [] self.imports: List[Import] = []
self.table: dict[int, str] = {}
self.functions: List[Function] = [] self.functions: List[Function] = []
self.memory = ModuleMemory() self.memory = ModuleMemory()
@ -210,10 +192,8 @@ class Module(WatSerializable):
""" """
Generates the text version Generates the text version
""" """
return '(module\n {}\n {}\n {}\n {}\n {})\n'.format( return '(module\n {}\n {}\n {})\n'.format(
'\n '.join(x.to_wat() for x in self.imports), '\n '.join(x.to_wat() for x in self.imports),
f'(table {len(self.table)} funcref)',
'\n '.join(f'(elem (i32.const {k}) ${v})' for k, v in self.table.items()),
self.memory.to_wat(), self.memory.to_wat(),
'\n '.join(x.to_wat() for x in self.functions), '\n '.join(x.to_wat() for x in self.functions),
) )

69
phasm/wasmeasy.py Normal file
View File

@ -0,0 +1,69 @@
"""
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')

View File

@ -1,8 +1,9 @@
""" """
Helper functions to generate WASM code by writing Python functions Helper functions to generate WASM code by writing Python functions
""" """
from typing import Any, Callable, Dict, List, Optional, Type
import functools import functools
from typing import Any, Callable, Dict, Iterable, List, Optional, Type
from . import wasm from . import wasm
@ -21,15 +22,6 @@ class VarType_u8(VarType_Base):
class VarType_i32(VarType_Base): class VarType_i32(VarType_Base):
wasm_type = wasm.WasmTypeInt32 wasm_type = wasm.WasmTypeInt32
class VarType_i64(VarType_Base):
wasm_type = wasm.WasmTypeInt64
class VarType_f32(VarType_Base):
wasm_type = wasm.WasmTypeFloat32
class VarType_f64(VarType_Base):
wasm_type = wasm.WasmTypeFloat64
class Generator_i32i64: class Generator_i32i64:
def __init__(self, prefix: str, generator: 'Generator') -> None: def __init__(self, prefix: str, generator: 'Generator') -> None:
self.prefix = prefix self.prefix = prefix
@ -40,101 +32,41 @@ class Generator_i32i64:
self.add = functools.partial(self.generator.add_statement, f'{prefix}.add') self.add = functools.partial(self.generator.add_statement, f'{prefix}.add')
self.sub = functools.partial(self.generator.add_statement, f'{prefix}.sub') self.sub = functools.partial(self.generator.add_statement, f'{prefix}.sub')
self.mul = functools.partial(self.generator.add_statement, f'{prefix}.mul') self.mul = functools.partial(self.generator.add_statement, f'{prefix}.mul')
self.div_s = functools.partial(self.generator.add_statement, f'{prefix}.div_s')
self.div_u = functools.partial(self.generator.add_statement, f'{prefix}.div_u')
self.rem_s = functools.partial(self.generator.add_statement, f'{prefix}.rem_s')
self.rem_u = functools.partial(self.generator.add_statement, f'{prefix}.rem_u')
self.and_ = functools.partial(self.generator.add_statement, f'{prefix}.and')
self.or_ = functools.partial(self.generator.add_statement, f'{prefix}.or')
self.xor = functools.partial(self.generator.add_statement, f'{prefix}.xor')
self.shl = functools.partial(self.generator.add_statement, f'{prefix}.shl')
self.shr_s = functools.partial(self.generator.add_statement, f'{prefix}.shr_s')
self.shr_u = functools.partial(self.generator.add_statement, f'{prefix}.shr_u')
self.rotl = functools.partial(self.generator.add_statement, f'{prefix}.rotl')
self.rotr = functools.partial(self.generator.add_statement, f'{prefix}.rotr')
# itestop
self.eqz = functools.partial(self.generator.add_statement, f'{prefix}.eqz')
# irelop # irelop
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq') self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne') self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne')
self.lt_s = functools.partial(self.generator.add_statement, f'{prefix}.lt_s')
self.lt_u = functools.partial(self.generator.add_statement, f'{prefix}.lt_u') self.lt_u = functools.partial(self.generator.add_statement, f'{prefix}.lt_u')
self.gt_s = functools.partial(self.generator.add_statement, f'{prefix}.gt_s')
self.gt_u = functools.partial(self.generator.add_statement, f'{prefix}.gt_u')
self.le_s = functools.partial(self.generator.add_statement, f'{prefix}.le_s')
self.le_u = functools.partial(self.generator.add_statement, f'{prefix}.le_u')
self.ge_s = functools.partial(self.generator.add_statement, f'{prefix}.ge_s')
self.ge_u = functools.partial(self.generator.add_statement, f'{prefix}.ge_u') self.ge_u = functools.partial(self.generator.add_statement, f'{prefix}.ge_u')
self.trunc_f32_s = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f32_s')
self.trunc_f32_u = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f32_u')
self.trunc_f64_s = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f64_s')
self.trunc_f64_u = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f64_u')
# 2.4.4. Memory Instructions # 2.4.4. Memory Instructions
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load') 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.load8_u = functools.partial(self.generator.add_statement, f'{prefix}.load8_u')
self.store = functools.partial(self.generator.add_statement, f'{prefix}.store') self.store = functools.partial(self.generator.add_statement, f'{prefix}.store')
def const(self, value: int, comment: Optional[str] = None) -> None: def const(self, value: int, comment: Optional[str] = None) -> None:
self.generator.add_statement(f'{self.prefix}.const', f'{value}', comment=comment) self.generator.add_statement(f'{self.prefix}.const', f'0x{value:08x}', comment=comment)
class Generator_i32(Generator_i32i64): class Generator_i32(Generator_i32i64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('i32', generator) super().__init__('i32', generator)
# 2.4.1. Numeric Instructions
self.reinterpret_f32 = functools.partial(self.generator.add_statement, 'i32.reinterpret_f32')
self.wrap_i64 = functools.partial(self.generator.add_statement, 'i32.wrap_i64')
class Generator_i64(Generator_i32i64): class Generator_i64(Generator_i32i64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('i64', generator) super().__init__('i64', generator)
# 2.4.1. Numeric Instructions
self.extend_i32_s = functools.partial(self.generator.add_statement, 'i64.extend_i32_s')
self.extend_i32_u = functools.partial(self.generator.add_statement, 'i64.extend_i32_u')
self.reinterpret_f64 = functools.partial(self.generator.add_statement, 'i64.reinterpret_f64')
class Generator_f32f64: class Generator_f32f64:
def __init__(self, prefix: str, generator: 'Generator') -> None: def __init__(self, prefix: str, generator: 'Generator') -> None:
self.prefix = prefix self.prefix = prefix
self.generator = generator self.generator = generator
# 2.4.1. Numeric Instructions # 2.4.1. Numeric Instructions
# funop
self.abs = functools.partial(self.generator.add_statement, f'{prefix}.abs')
self.neg = functools.partial(self.generator.add_statement, f'{prefix}.neg')
self.sqrt = functools.partial(self.generator.add_statement, f'{prefix}.sqrt')
self.ceil = functools.partial(self.generator.add_statement, f'{prefix}.ceil')
self.floor = functools.partial(self.generator.add_statement, f'{prefix}.floor')
self.trunc = functools.partial(self.generator.add_statement, f'{prefix}.trunc')
self.nearest = functools.partial(self.generator.add_statement, f'{prefix}.nearest')
# fbinop # fbinop
self.add = functools.partial(self.generator.add_statement, f'{prefix}.add') 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')
self.div = functools.partial(self.generator.add_statement, f'{prefix}.div')
self.min = functools.partial(self.generator.add_statement, f'{prefix}.min')
self.max = functools.partial(self.generator.add_statement, f'{prefix}.max')
self.copysign = functools.partial(self.generator.add_statement, f'{prefix}.copysign')
# frelop # frelop
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq') self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne') self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne')
self.lt = functools.partial(self.generator.add_statement, f'{prefix}.lt')
self.gt = functools.partial(self.generator.add_statement, f'{prefix}.gt')
self.le = functools.partial(self.generator.add_statement, f'{prefix}.le')
self.ge = functools.partial(self.generator.add_statement, f'{prefix}.ge')
# Other instr - convert
self.convert_i32_s = functools.partial(self.generator.add_statement, f'{prefix}.convert_i32_s')
self.convert_i32_u = functools.partial(self.generator.add_statement, f'{prefix}.convert_i32_u')
self.convert_i64_s = functools.partial(self.generator.add_statement, f'{prefix}.convert_i64_s')
self.convert_i64_u = functools.partial(self.generator.add_statement, f'{prefix}.convert_i64_u')
# 2.4.4. Memory Instructions # 2.4.4. Memory Instructions
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load') self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
@ -148,19 +80,10 @@ class Generator_f32(Generator_f32f64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('f32', generator) super().__init__('f32', generator)
# 2.4.1 Numeric Instructions
self.demote_f64 = functools.partial(self.generator.add_statement, 'f32.demote_f64')
self.reinterpret_i32 = functools.partial(self.generator.add_statement, 'f32.reinterpret_i32')
class Generator_f64(Generator_f32f64): class Generator_f64(Generator_f32f64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('f64', generator) super().__init__('f64', generator)
# 2.4.1 Numeric Instructions
self.promote_f32 = functools.partial(self.generator.add_statement, 'f64.promote_f32')
self.reinterpret_i64 = functools.partial(self.generator.add_statement, 'f64.reinterpret_i64')
self.reinterpret_i64 = functools.partial(self.generator.add_statement, 'f64.reinterpret_i64')
class Generator_Local: class Generator_Local:
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
self.generator = generator self.generator = generator
@ -180,33 +103,12 @@ class Generator_Local:
self.generator.add_statement('local.tee', variable.name_ref, comment=comment) self.generator.add_statement('local.tee', variable.name_ref, comment=comment)
class GeneratorBlock: class GeneratorBlock:
def __init__( def __init__(self, generator: 'Generator', name: str) -> None:
self,
generator: 'Generator',
name: str,
params: Iterable[str | Type[wasm.WasmType]] = (),
result: str | Type[wasm.WasmType] | None = None,
comment: str | None = None,
) -> None:
self.generator = generator self.generator = generator
self.name = name self.name = name
self.params = params
self.result = result
self.comment = comment
def __enter__(self) -> None: def __enter__(self) -> None:
stmt = self.name self.generator.add_statement(self.name)
args: list[str] = []
if self.params:
args.extend(
f'(param {typ})' if isinstance(typ, str) else f'(param {typ().to_wat()})'
for typ in self.params
)
if self.result:
result = self.result if isinstance(self.result, str) else self.result().to_wat()
args.append(f'(result {result})')
self.generator.add_statement(stmt, *args, comment=self.comment)
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
if not exc_type: if not exc_type:
@ -214,7 +116,7 @@ class GeneratorBlock:
class Generator: class Generator:
def __init__(self) -> None: def __init__(self) -> None:
self.statements: List[wasm.StatementBase] = [] self.statements: List[wasm.Statement] = []
self.locals: Dict[str, VarType_Base] = {} self.locals: Dict[str, VarType_Base] = {}
self.i32 = Generator_i32(self) self.i32 = Generator_i32(self)
@ -222,16 +124,13 @@ class Generator:
self.f32 = Generator_f32(self) self.f32 = Generator_f32(self)
self.f64 = Generator_f64(self) self.f64 = Generator_f64(self)
# Parametric Instructions
self.drop = functools.partial(self.add_statement, 'drop')
# 2.4.3 Variable Instructions # 2.4.3 Variable Instructions
self.local = Generator_Local(self) self.local = Generator_Local(self)
# 2.4.5 Control Instructions # 2.4.5 Control Instructions
self.nop = functools.partial(self.add_statement, 'nop') self.nop = functools.partial(self.add_statement, 'nop')
self.unreachable = functools.partial(self.add_statement, 'unreachable') self.unreachable = functools.partial(self.add_statement, 'unreachable')
self.block = functools.partial(GeneratorBlock, self, 'block') # block
self.loop = functools.partial(GeneratorBlock, self, 'loop') self.loop = functools.partial(GeneratorBlock, self, 'loop')
self.if_ = functools.partial(GeneratorBlock, self, 'if') self.if_ = functools.partial(GeneratorBlock, self, 'if')
# br # br
@ -239,68 +138,32 @@ class Generator:
# br_table # br_table
self.return_ = functools.partial(self.add_statement, 'return') self.return_ = functools.partial(self.add_statement, 'return')
# call - see below # call - see below
# call_indirect - see below # call_indirect
def br_if(self, idx: int) -> None: def br_if(self, idx: int) -> None:
self.add_statement('br_if', f'{idx}') self.add_statement('br_if', f'{idx}')
def call(self, function: wasm.Function | str) -> None: def call(self, function: wasm.Function) -> None:
if isinstance(function, wasm.Function): self.add_statement('call', f'${function.name}')
function = function.name
self.statements.append(wasm.StatementCall(function))
def call_indirect(self, params: Iterable[Type[wasm.WasmType] | wasm.WasmType], result: Type[wasm.WasmType] | wasm.WasmType) -> None:
param_str = ' '.join(
(x() if isinstance(x, type) else x).to_wat()
for x in params
)
if isinstance(result, type):
result = result()
result_str = result.to_wat()
self.add_statement('call_indirect', f'(param {param_str})', f'(result {result_str})')
def add_statement(self, name: str, *args: str, comment: Optional[str] = None) -> None: def add_statement(self, name: str, *args: str, comment: Optional[str] = None) -> None:
self.statements.append(wasm.Statement(name, *args, comment=comment)) self.statements.append(wasm.Statement(name, *args, comment=comment))
def temp_var[T: VarType_Base](self, var: T) -> T: def temp_var_i32(self, infix: str) -> VarType_i32:
idx = 0 idx = 0
while (varname := f'__{var.name}_tmp_var_{idx}__') in self.locals: while (varname := f'__{infix}_tmp_var_{idx}__') in self.locals:
idx += 1 idx += 1
return var.__class__(varname)
def temp_var_t(self, typ: Type[wasm.WasmType], name: str) -> VarType_Base:
idx = 0
while (varname := f'__{name}_tmp_var_{idx}__') in self.locals:
idx += 1
if typ is wasm.WasmTypeInt32:
return VarType_u8(varname)
if typ is wasm.WasmTypeInt32:
return VarType_i32(varname) return VarType_i32(varname)
if typ is wasm.WasmTypeInt64:
return VarType_i64(varname)
if typ is wasm.WasmTypeFloat32:
return VarType_f32(varname)
if typ is wasm.WasmTypeFloat64:
return VarType_f64(varname)
raise NotImplementedError(typ)
def temp_var_i32(self, infix: str) -> VarType_i32:
return self.temp_var(VarType_i32(infix))
def temp_var_u8(self, infix: str) -> VarType_u8: def temp_var_u8(self, infix: str) -> VarType_u8:
return self.temp_var(VarType_u8(infix)) idx = 0
while (varname := f'__{infix}_tmp_var_{idx}__') in self.locals:
idx += 1
def func_wrapper(exported: bool = False) -> Callable[[Any], wasm.Function]: return VarType_u8(varname)
def func_wrapper(exported: bool = True) -> Callable[[Any], wasm.Function]:
""" """
This wrapper will execute the function and return This wrapper will execute the function and return
a wasm Function with the generated Statements a wasm Function with the generated Statements
@ -342,10 +205,6 @@ def func_wrapper(exported: bool = False) -> Callable[[Any], wasm.Function]:
# Check what locals were used, and define them # Check what locals were used, and define them
locals_: List[wasm.Param] = [] locals_: List[wasm.Param] = []
for local_name, local_type in generator.locals.items(): for local_name, local_type in generator.locals.items():
if local_name in args:
# Already defined as a local by wasm itself
continue
locals_.append((local_name, local_type.wasm_type(), )) locals_.append((local_name, local_type.wasm_type(), ))
# Complete function definition # Complete function definition

View File

@ -1,5 +1,5 @@
[MASTER] [MASTER]
disable=C0103,C0122,R0902,R0903,R0911,R0912,R0913,R0915,R1710,W0223 disable=C0122,R0903,R0911,R0912,R0913,R0915,R1710,W0223
max-line-length=180 max-line-length=180
@ -7,4 +7,4 @@ max-line-length=180
good-names=g good-names=g
[tests] [tests]
disable=C0116,R0201 disable=C0116,

View File

@ -1,3 +0,0 @@
[tool.ruff.lint]
select = ["F", "E", "W", "I"]
ignore = ["E501"]

View File

@ -1,19 +1,10 @@
marko==2.1.3 mypy==0.812
mypy==1.17.1 pygments==2.12.0
pygments==2.19.1 pylint==2.7.4
pytest==8.3.5 pytest==6.2.2
pytest-integration==0.2.2 pytest-integration==0.2.2
ruff==0.12.7 pywasm==1.0.7
pywasm3==0.5.0
wasmer==1.1.0
wasmtime==31.0.0 wasmer_compiler_cranelift==1.1.0
wasmtime==0.36.0
# TODO:
# extism?
# wasmedge
# Check 2025-04-05
# wasm3: minimal maintenance phase
# py-wasm: last updated 6 years ago
# wasmer-python: Not compatible with python3.12, last updated 2 years ago
# WAVM: Last updated 3 years ago

14
stubs/pywasm/__init__.pyi Normal file
View File

@ -0,0 +1,14 @@
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:
...

6
stubs/pywasm/binary.pyi Normal file
View File

@ -0,0 +1,6 @@
from typing import BinaryIO
class Module:
@classmethod
def from_reader(cls, reader: BinaryIO) -> 'Module':
...

View File

@ -0,0 +1,10 @@
from typing import List
class Result:
...
class MemoryInstance:
data: bytearray
class Store:
memory_list: List[MemoryInstance]

2
stubs/pywasm/option.pyi Normal file
View File

@ -0,0 +1,2 @@
class Option:
...

23
stubs/wasm3.pyi Normal file
View File

@ -0,0 +1,23 @@
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:
...

39
stubs/wasmer.pyi Normal file
View File

@ -0,0 +1,39 @@
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:
...

View File

@ -1,164 +1,85 @@
from __future__ import annotations
import os
import sys import sys
from typing import Any, Callable, List, TextIO, Union
from phasm.codestyle import phasm_render from phasm.codestyle import phasm_render
from phasm.wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
)
from . import memory, runners from . import runners
DASHES = '-' * 16 DASHES = '-' * 16
class InvalidArgumentException(Exception):
pass
class SuiteResult: class SuiteResult:
def __init__(self) -> None: def __init__(self):
self.returned_value = None self.returned_value = None
RUNNER_CLASS_MAP = { RUNNER_CLASS_MAP = {
'pywasm': runners.RunnerPywasm,
'pywasm3': runners.RunnerPywasm3,
'wasmtime': runners.RunnerWasmtime, 'wasmtime': runners.RunnerWasmtime,
'wasmer': runners.RunnerWasmer,
} }
TRACE_CODE_PREFIX = """
@imported
def trace_adr(adr: i32) -> i32:
pass
@imported
def trace_i32(x: i32) -> i32:
pass
"""
def make_int_trace(prefix: str) -> Callable[[int], int]:
def trace_helper(adr: int) -> int:
sys.stderr.write(f'{prefix} {adr}\n')
sys.stderr.flush()
return adr
return trace_helper
class Suite: class Suite:
""" """
WebAssembly test suite WebAssembly test suite
""" """
def __init__(self, code_py: str) -> None: def __init__(self, code_py):
self.code_py = code_py self.code_py = code_py
def run_code( def run_code(self, *args, runtime='pywasm3', imports=None):
self,
*args: Any,
runtime: str = 'wasmtime',
func_name: str = 'testEntry',
imports: runners.Imports = None,
do_format_check: bool = True,
verbose: bool | None = None,
with_traces: bool = False,
) -> Any:
""" """
Compiles the given python code into wasm and Compiles the given python code into wasm and
then runs it then runs it
Returned is an object with the results set Returned is an object with the results set
""" """
if verbose is None:
verbose = bool(os.environ.get('VERBOSE'))
code_prefix = ''
if with_traces:
assert do_format_check is False
if imports is None:
imports = {}
imports.update({
'trace_adr': make_int_trace('ADR'),
'trace_i32': make_int_trace('i32'),
})
code_prefix = TRACE_CODE_PREFIX
class_ = RUNNER_CLASS_MAP[runtime] class_ = RUNNER_CLASS_MAP[runtime]
runner = class_(code_prefix + self.code_py) runner = class_(self.code_py)
if verbose: runner.parse()
write_header(sys.stderr, 'Phasm')
runner.dump_phasm_code(sys.stderr)
runner.parse(verbose=verbose)
runner.compile_ast() runner.compile_ast()
runner.optimise_wasm_ast()
runner.compile_wat() runner.compile_wat()
runner.compile_wasm()
if verbose:
write_header(sys.stderr, 'Assembly')
runner.dump_wasm_wat(sys.stderr)
runner.interpreter_setup() runner.interpreter_setup()
runner.interpreter_load(imports) runner.interpreter_load(imports)
allocator_generator = memory.Allocator(runner.phasm_ast.build, runner) 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 # Check if code formatting works
if do_format_check:
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
func = runner.phasm_ast.functions[func_name] wasm_args = []
assert func.type5 is not None # Type hint
func_args = runner.phasm_ast.build.type5_is_function(func.type5)
assert func_args is not None
func_ret = func_args.pop()
if len(func_args) != len(args):
raise RuntimeError(f'Invalid number of args for {func_name}')
wasm_args: List[Union[float, int]] = []
if args: if args:
if verbose:
write_header(sys.stderr, 'Memory (pre alloc)') write_header(sys.stderr, 'Memory (pre alloc)')
runner.interpreter_dump_memory(sys.stderr) runner.interpreter_dump_memory(sys.stderr)
for arg, arg_typ in zip(args, func_args, strict=True): for arg in args:
arg_typ_info = runner.phasm_ast.build.type_info_map.get(arg_typ.name) if isinstance(arg, (int, float, )):
if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeInt32 or arg_typ_info.wasm_type is WasmTypeInt64):
assert isinstance(arg, int)
wasm_args.append(arg) wasm_args.append(arg)
continue continue
if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeFloat32 or arg_typ_info.wasm_type is WasmTypeFloat64): if isinstance(arg, bytes):
assert isinstance(arg, float) adr = runner.call('stdlib.types.__alloc_bytes__', len(arg))
wasm_args.append(arg) sys.stderr.write(f'Allocation 0x{adr:08x} {repr(arg)}\n')
continue
allocator = allocator_generator(arg_typ) runner.interpreter_write_memory(adr + 4, arg)
adr = allocator(arg)
wasm_args.append(adr) wasm_args.append(adr)
continue
raise NotImplementedError(arg)
if verbose:
write_header(sys.stderr, 'Memory (pre run)') write_header(sys.stderr, 'Memory (pre run)')
runner.interpreter_dump_memory(sys.stderr) runner.interpreter_dump_memory(sys.stderr)
result = SuiteResult() result = SuiteResult()
result.returned_value = runner.call(func_name, *wasm_args) result.returned_value = runner.call('testEntry', *wasm_args)
extractor = memory.Extractor(runner.phasm_ast.build, runner)(func_ret)
result.returned_value = extractor(result.returned_value)
if verbose:
write_header(sys.stderr, 'Memory (post run)') write_header(sys.stderr, 'Memory (post run)')
runner.interpreter_dump_memory(sys.stderr) runner.interpreter_dump_memory(sys.stderr)
return result return result
def write_header(textio: TextIO, msg: str) -> None: def write_header(textio, msg: str) -> None:
textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n') textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')

View File

@ -1,551 +0,0 @@
import struct
from typing import Any, Protocol
from phasm.build.base import BuildBase
from phasm.build.typerouter import BuildTypeRouter
from phasm.type5.record import Record
from phasm.type5.typeexpr import AtomicType, TypeExpr
from phasm.wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
WasmTypeNone,
)
class MemoryAccess(Protocol):
def call(self, function: str, *args: Any) -> Any:
"""
Use for calling allocator methods inside the WASM environment.
"""
def interpreter_write_memory(self, offset: int, data: bytes) -> None:
"""
Writes bytes directly to WASM environment memory.
Addresses should be generated using allocators via call.
"""
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
"""
Reads bytes directly from WASM environment memory.
"""
class MemorySlice:
__slots__ = ('memory', 'offset', )
def __init__(self, memory: bytes, offset: int) -> None:
self.memory = memory
self.offset = offset
def __call__(self, size: int) -> bytes:
return self.memory[self.offset:self.offset + size]
def __repr__(self) -> str:
return f'MemorySlice({self.memory!r}, {self.offset!r})'
class AllocatorFunc(Protocol):
alloc_size: int
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
"""
Takes a Python value and allocaties it in the given memory
Based on the phasm type.
When the parent already has allocated memory, the store_at_adr is set.
In that case, write your value to the given address, and return it.
"""
class Allocator(BuildTypeRouter[AllocatorFunc]):
__slots__ = ('access', )
access: MemoryAccess
def __init__(self, build: BuildBase[Any], access: MemoryAccess) -> None:
super().__init__(build)
self.access = access
def when_atomic(self, typ: AtomicType) -> AllocatorFunc:
type_info = self.build.type_info_map[typ.name]
if type_info.wasm_type is WasmTypeNone:
raise NotImplementedError
if type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64:
if type_info.signed is None:
raise NotImplementedError
return IntAllocator(self.access, type_info.signed, type_info.alloc_size)
if type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64:
return FloatAllocator(self.access, type_info.alloc_size)
raise NotImplementedError(typ)
def when_dynamic_array(self, da_arg: TypeExpr) -> AllocatorFunc:
if da_arg.name == 'u8':
return BytesAllocator(self.access)
return DynamicArrayAllocator(self.access, self(da_arg))
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> AllocatorFunc:
return StaticArrayAllocator(self.access, sa_len, self(sa_typ))
def when_struct(self, typ: Record) -> AllocatorFunc:
return StructAllocator(self.access, [(x_nam, self(x_typ)) for x_nam, x_typ in typ.fields])
def when_tuple(self, tp_args: list[TypeExpr]) -> AllocatorFunc:
return TupleAllocator(self.access, list(map(self, tp_args)))
class ExtractorFunc(Protocol):
alloc_size: int
def __call__(self, wasm_value: Any) -> Any:
"""
Takes a WASM value and returns a Python value
Based on the phasm type
"""
class Extractor(BuildTypeRouter[ExtractorFunc]):
__slots__ = ('access', )
access: MemoryAccess
def __init__(self, build: BuildBase[Any], access: MemoryAccess) -> None:
super().__init__(build)
self.access = access
def when_atomic(self, typ: AtomicType) -> ExtractorFunc:
type_info = self.build.type_info_map[typ.name]
if type_info.wasm_type is WasmTypeNone:
return NoneExtractor()
if type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64:
if type_info.signed is None:
return BoolExtractor()
return IntExtractor(type_info.signed, type_info.alloc_size)
if type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64:
return FloatExtractor(type_info.alloc_size)
raise NotImplementedError(typ)
def when_dynamic_array(self, da_arg: TypeExpr) -> ExtractorFunc:
if da_arg.name == 'u8':
return BytesExtractor(self.access)
return DynamicArrayExtractor(self.access, self(da_arg))
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> ExtractorFunc:
return StaticArrayExtractor(self.access, sa_len, self(sa_typ))
def when_struct(self, typ: Record) -> ExtractorFunc:
return StructExtractor(self.access, [(x_nam, self(x_typ)) for x_nam, x_typ in typ.fields])
def when_tuple(self, tp_args: list[TypeExpr]) -> ExtractorFunc:
return TupleExtractor(self.access, list(map(self, tp_args)))
class NoneExtractor:
__slots__ = ('alloc_size', )
alloc_size: int
def __init__(self) -> None:
# Do not set alloc_size, it should not be called
# this will generate an AttributeError
pass
def __call__(self, wasm_value: Any) -> None:
assert wasm_value is None
class BoolExtractor:
__slots__ = ('alloc_size', )
def __init__(self) -> None:
self.alloc_size = 1
def __call__(self, wasm_value: Any) -> bool:
assert isinstance(wasm_value, int), wasm_value
return wasm_value != 0
class IntAllocator:
__slots__ = ('access', 'alloc_size', 'signed', )
def __init__(self, access: MemoryAccess, signed: bool, alloc_size: int) -> None:
self.access = access
self.signed = signed
self.alloc_size = alloc_size
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
if store_at_adr is None:
raise NotImplementedError
assert isinstance(py_value, int), py_value
data = py_value.to_bytes(self.alloc_size, 'little', signed=self.signed)
self.access.interpreter_write_memory(store_at_adr, data)
return store_at_adr
class IntExtractor:
__slots__ = ('alloc_size', 'signed', )
def __init__(self, signed: bool, alloc_size: int) -> None:
self.signed = signed
self.alloc_size = alloc_size
def __call__(self, wasm_value: Any) -> int:
if isinstance(wasm_value, MemorySlice):
# Memory stored int
data = wasm_value(self.alloc_size)
else:
# Int received from the wasm interface
# Work around the fact that phasm has unsigned integers but wasm does not
# Use little endian since that matches with what WASM uses internally
assert isinstance(wasm_value, int), wasm_value
data = wasm_value.to_bytes(8, 'little', signed=True)
data = data[:self.alloc_size]
return int.from_bytes(data, 'little', signed=self.signed)
class PtrAllocator(IntAllocator):
def __init__(self, access: MemoryAccess) -> None:
super().__init__(access, False, 4)
class PtrExtractor(IntExtractor):
def __init__(self) -> None:
super().__init__(False, 4)
FLOAT_LETTER_MAP = {
4: 'f',
8: 'd'
}
class FloatAllocator:
__slots__ = ('access', 'alloc_size', )
def __init__(self, access: MemoryAccess, alloc_size: int) -> None:
self.access = access
self.alloc_size = alloc_size
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
if store_at_adr is None:
raise NotImplementedError
assert isinstance(py_value, (float, int, )), py_value
data = struct.pack(f'<{FLOAT_LETTER_MAP[self.alloc_size]}', py_value)
self.access.interpreter_write_memory(store_at_adr, data)
return store_at_adr
class FloatExtractor:
__slots__ = ('alloc_size', )
def __init__(self, alloc_size: int) -> None:
self.alloc_size = alloc_size
def __call__(self, wasm_value: Any) -> float:
if isinstance(wasm_value, MemorySlice):
# Memory stored float
data = wasm_value(self.alloc_size)
wasm_value, = struct.unpack(f'<{FLOAT_LETTER_MAP[self.alloc_size]}', data)
assert isinstance(wasm_value, float), wasm_value
return wasm_value
class DynamicArrayAllocator:
__slots__ = ('access', 'alloc_size', 'sub_allocator', )
access: MemoryAccess
alloc_size: int
sub_allocator: AllocatorFunc
def __init__(self, access: MemoryAccess, sub_allocator: AllocatorFunc) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sub_allocator = sub_allocator
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
if store_at_adr is not None:
raise NotImplementedError
assert isinstance(py_value, tuple), py_value
py_len = len(py_value)
alloc_size = 4 + py_len * self.sub_allocator.alloc_size
adr = self.access.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) # Type int
PtrAllocator(self.access)(py_len, adr)
for idx, el_value in enumerate(py_value):
offset = adr + 4 + idx * self.sub_allocator.alloc_size
self.sub_allocator(el_value, offset)
return adr
class DynamicArrayExtractor:
__slots__ = ('access', 'alloc_size', 'sub_extractor', )
access: MemoryAccess
alloc_size: int
sub_extractor: ExtractorFunc
def __init__(self, access: MemoryAccess, sub_extractor: ExtractorFunc) -> None:
self.access = access
self.sub_extractor = sub_extractor
def __call__(self, wasm_value: Any) -> Any:
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
# wasm_value must be a pointer
# The first value at said pointer is the length of the array
read_bytes = self.access.interpreter_read_memory(adr, 4)
array_len, = struct.unpack('<I', read_bytes)
read_bytes = self.access.interpreter_read_memory(adr + 4, array_len * self.sub_extractor.alloc_size)
return tuple(
self.sub_extractor(MemorySlice(read_bytes, idx * self.sub_extractor.alloc_size))
for idx in range(array_len)
)
class BytesAllocator:
__slots__ = ('access', 'alloc_size', )
access: MemoryAccess
def __init__(self, access: MemoryAccess) -> None:
self.access = access
self.alloc_size = 4 # ptr
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
assert isinstance(py_value, bytes), py_value
adr = self.access.call('stdlib.types.__alloc_bytes__', len(py_value))
assert isinstance(adr, int)
self.access.interpreter_write_memory(adr + 4, py_value)
if store_at_adr is not None:
PtrAllocator(self.access)(adr, store_at_adr)
return adr
class BytesExtractor:
__slots__ = ('access', 'alloc_size', )
access: MemoryAccess
alloc_size: int
def __init__(self, access: MemoryAccess) -> None:
self.access = access
self.alloc_size = 4 # ptr
def __call__(self, wasm_value: Any) -> bytes:
if isinstance(wasm_value, MemorySlice):
wasm_value = PtrExtractor()(wasm_value)
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
# wasm_value must be a pointer
# The first value at said pointer is the length of the array
read_bytes = self.access.interpreter_read_memory(adr, 4)
array_len, = struct.unpack('<I', read_bytes)
adr += 4
return self.access.interpreter_read_memory(adr, array_len)
class StaticArrayAllocator:
__slots__ = ('access', 'alloc_size', 'sa_len', 'sub_allocator', )
access: MemoryAccess
alloc_size: int
sa_len: int
sub_allocator: AllocatorFunc
def __init__(self, access: MemoryAccess, sa_len: int, sub_allocator: AllocatorFunc) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sa_len = sa_len
self.sub_allocator = sub_allocator
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
assert isinstance(py_value, tuple), py_value
assert len(py_value) == self.sa_len
alloc_size = self.sa_len * self.sub_allocator.alloc_size
adr = self.access.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) # Type int
for idx, el_value in enumerate(py_value):
sub_adr = adr + idx * self.sub_allocator.alloc_size
self.sub_allocator(el_value, sub_adr)
if store_at_adr is not None:
PtrAllocator(self.access)(adr, store_at_adr)
return adr
class StaticArrayExtractor:
__slots__ = ('access', 'alloc_size', 'sa_len', 'sub_extractor', )
access: MemoryAccess
alloc_size: int
sa_len: int
sub_extractor: ExtractorFunc
def __init__(self, access: MemoryAccess, sa_len: int, sub_extractor: ExtractorFunc) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sa_len = sa_len
self.sub_extractor = sub_extractor
def __call__(self, wasm_value: Any) -> Any:
if isinstance(wasm_value, MemorySlice):
wasm_value = PtrExtractor()(wasm_value)
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
read_bytes = self.access.interpreter_read_memory(adr, self.sa_len * self.sub_extractor.alloc_size)
return tuple(
self.sub_extractor(MemorySlice(read_bytes, idx * self.sub_extractor.alloc_size))
for idx in range(self.sa_len)
)
class TupleAllocator:
__slots__ = ('access', 'alloc_size', 'sub_allocator_list', )
access: MemoryAccess
alloc_size: int
sub_allocator_list: list[AllocatorFunc]
def __init__(self, access: MemoryAccess, sub_allocator_list: list[AllocatorFunc]) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sub_allocator_list = sub_allocator_list
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
assert isinstance(py_value, tuple), py_value
total_alloc_size = sum(x.alloc_size for x in self.sub_allocator_list)
adr = self.access.call('stdlib.alloc.__alloc__', total_alloc_size)
assert isinstance(adr, int) # Type int
sub_adr = adr
for sub_allocator, sub_value in zip(self.sub_allocator_list, py_value, strict=True):
sub_allocator(sub_value, sub_adr)
sub_adr += sub_allocator.alloc_size
if store_at_adr is not None:
PtrAllocator(self.access)(adr, store_at_adr)
return adr
class TupleExtractor:
__slots__ = ('access', 'alloc_size', 'sub_extractor_list', )
access: MemoryAccess
alloc_size: int
sub_extractor_list: list[ExtractorFunc]
def __init__(self, access: MemoryAccess, sub_extractor_list: list[ExtractorFunc]) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sub_extractor_list = sub_extractor_list
def __call__(self, wasm_value: Any) -> tuple[Any]:
if isinstance(wasm_value, MemorySlice):
wasm_value = PtrExtractor()(wasm_value)
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
total_alloc_size = sum(x.alloc_size for x in self.sub_extractor_list)
read_bytes = self.access.interpreter_read_memory(adr, total_alloc_size)
result = []
offset = 0
for sub_extractor in self.sub_extractor_list:
result.append(sub_extractor(MemorySlice(read_bytes, offset)))
offset += sub_extractor.alloc_size
return tuple(result)
class StructAllocator:
__slots__ = ('access', 'alloc_size', 'sub_allocator_list', )
access: MemoryAccess
alloc_size: int
sub_allocator_list: list[tuple[str, AllocatorFunc]]
def __init__(self, access: MemoryAccess, sub_allocator_list: list[tuple[str, AllocatorFunc]]) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sub_allocator_list = sub_allocator_list
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
assert isinstance(py_value, dict), py_value
total_alloc_size = sum(x.alloc_size for _, x in self.sub_allocator_list)
adr = self.access.call('stdlib.alloc.__alloc__', total_alloc_size)
assert isinstance(adr, int) # Type int
sub_adr = adr
for field_name, sub_allocator in self.sub_allocator_list:
sub_value = py_value[field_name]
sub_allocator(sub_value, sub_adr)
sub_adr += sub_allocator.alloc_size
if store_at_adr is not None:
PtrAllocator(self.access)(adr, store_at_adr)
return adr
class StructExtractor:
__slots__ = ('access', 'alloc_size', 'sub_extractor_list', )
access: MemoryAccess
alloc_size: int
sub_extractor_list: list[tuple[str, ExtractorFunc]]
def __init__(self, access: MemoryAccess, sub_extractor_list: list[tuple[str, ExtractorFunc]]) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sub_extractor_list = sub_extractor_list
def __call__(self, wasm_value: Any) -> dict[str, Any]:
if isinstance(wasm_value, MemorySlice):
wasm_value = PtrExtractor()(wasm_value)
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
total_alloc_size = sum(x.alloc_size for _, x in self.sub_extractor_list)
read_bytes = self.access.interpreter_read_memory(adr, total_alloc_size)
result = {}
offset = 0
for field_name, sub_extractor in self.sub_extractor_list:
result[field_name] = sub_extractor(MemorySlice(read_bytes, offset))
offset += sub_extractor.alloc_size
return result

View File

@ -1,26 +1,29 @@
""" """
Runners to help run WebAssembly code on various interpreters Runners to help run WebAssembly code on various interpreters
""" """
import ctypes
from typing import Any, Callable, Dict, Iterable, Optional, TextIO from typing import Any, Callable, Dict, Iterable, Optional, TextIO
import ctypes
import io
import warnings
import pywasm.binary
import wasm3
import wasmer
import wasmtime import wasmtime
from phasm import ourlang, wasm
from phasm.compiler import phasm_compile from phasm.compiler import phasm_compile
from phasm.optimise.removeunusedfuncs import removeunusedfuncs
from phasm.parser import phasm_parse from phasm.parser import phasm_parse
from phasm.type5.solver import phasm_type5 from phasm.typer import phasm_type
from phasm.wasmgenerator import Generator as WasmGenerator from phasm import ourlang
from phasm import wasm
Imports = Optional[Dict[str, Callable[[Any], Any]]]
class RunnerBase: class RunnerBase:
""" """
Base class Base class
""" """
phasm_code: str phasm_code: str
phasm_ast: ourlang.Module[WasmGenerator] phasm_ast: ourlang.Module
wasm_ast: wasm.Module wasm_ast: wasm.Module
wasm_asm: str wasm_asm: str
wasm_bin: bytes wasm_bin: bytes
@ -34,12 +37,15 @@ class RunnerBase:
""" """
_dump_code(textio, self.phasm_code) _dump_code(textio, self.phasm_code)
def parse(self, verbose: bool = True) -> None: def parse(self) -> None:
""" """
Parses the Phasm code into an AST Parses the Phasm code into an AST
""" """
self.phasm_ast = phasm_parse(self.phasm_code) self.phasm_ast = phasm_parse(self.phasm_code)
phasm_type5(self.phasm_ast, verbose=verbose) try:
phasm_type(self.phasm_ast)
except NotImplementedError as exc:
warnings.warn(f'phasm_type throws an NotImplementedError on this test: {exc}')
def compile_ast(self) -> None: def compile_ast(self) -> None:
""" """
@ -47,12 +53,6 @@ class RunnerBase:
""" """
self.wasm_ast = phasm_compile(self.phasm_ast) self.wasm_ast = phasm_compile(self.phasm_ast)
def optimise_wasm_ast(self) -> None:
"""
Optimises the WebAssembly AST
"""
removeunusedfuncs(self.wasm_ast)
def compile_wat(self) -> None: def compile_wat(self) -> None:
""" """
Compiles the WebAssembly AST into WebAssembly Assembly code Compiles the WebAssembly AST into WebAssembly Assembly code
@ -69,7 +69,7 @@ class RunnerBase:
""" """
Compiles the WebAssembly AST into WebAssembly Binary Compiles the WebAssembly AST into WebAssembly Binary
""" """
raise NotImplementedError self.wasm_bin = wasmer.wat2wasm(self.wasm_asm)
def interpreter_setup(self) -> None: def interpreter_setup(self) -> None:
""" """
@ -77,7 +77,7 @@ class RunnerBase:
""" """
raise NotImplementedError raise NotImplementedError
def interpreter_load(self, imports: Imports = None) -> None: def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
""" """
Loads the code into the interpreter Loads the code into the interpreter
""" """
@ -107,6 +107,77 @@ class RunnerBase:
""" """
raise NotImplementedError 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): class RunnerWasmtime(RunnerBase):
""" """
Implements a runner for wasmtime Implements a runner for wasmtime
@ -117,48 +188,15 @@ class RunnerWasmtime(RunnerBase):
module: wasmtime.Module module: wasmtime.Module
instance: wasmtime.Instance instance: wasmtime.Instance
@classmethod
def func2type(cls, func: Callable[[Any], Any]) -> wasmtime.FuncType:
params: list[wasmtime.ValType] = []
code = func.__code__
for idx in range(code.co_argcount):
varname = code.co_varnames[idx]
vartype = func.__annotations__[varname]
if vartype is int:
params.append(wasmtime.ValType.i32())
elif vartype is float:
params.append(wasmtime.ValType.f32())
else:
raise NotImplementedError
results: list[wasmtime.ValType] = []
if func.__annotations__['return'] is None:
pass # No return value
elif func.__annotations__['return'] is int:
results.append(wasmtime.ValType.i32())
elif func.__annotations__['return'] is float:
results.append(wasmtime.ValType.f32())
else:
raise NotImplementedError('Return type', func.__annotations__['return'])
return wasmtime.FuncType(params, results)
def interpreter_setup(self) -> None: def interpreter_setup(self) -> None:
self.store = wasmtime.Store() self.store = wasmtime.Store()
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None: def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
functions: list[wasmtime.Func] = []
if imports is not None: if imports is not None:
functions = [ raise NotImplementedError
wasmtime.Func(self.store, self.__class__.func2type(f), f)
for f in imports.values()
]
self.module = wasmtime.Module(self.store.engine, self.wasm_asm) self.module = wasmtime.Module(self.store.engine, self.wasm_bin)
self.instance = wasmtime.Instance(self.store, self.module, functions) self.instance = wasmtime.Instance(self.store, self.module, [])
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None: def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
exports = self.instance.exports(self.store) exports = self.instance.exports(self.store)
@ -183,7 +221,8 @@ class RunnerWasmtime(RunnerBase):
data_len = memory.data_len(self.store) data_len = memory.data_len(self.store)
raw = ctypes.string_at(data_ptr, data_len) raw = ctypes.string_at(data_ptr, data_len)
return raw[offset:offset + length]
return raw[offset:length]
def interpreter_dump_memory(self, textio: TextIO) -> None: def interpreter_dump_memory(self, textio: TextIO) -> None:
exports = self.instance.exports(self.store) exports = self.instance.exports(self.store)
@ -202,6 +241,63 @@ class RunnerWasmtime(RunnerBase):
return func(self.store, *args) 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: def _dump_memory(textio: TextIO, mem: bytes) -> None:
line_width = 16 line_width = 16

View File

@ -0,0 +1,80 @@
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

View File

@ -0,0 +1,87 @@
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

View File

@ -0,0 +1,39 @@
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

View File

@ -1,19 +0,0 @@
import pytest
from ..helpers import Suite
@pytest.mark.slow_integration_test
def test_crc32():
with open('examples/crc32.py', 'r', encoding='ASCII') as fil:
code_py = "\n" + fil.read()
# https://reveng.sourceforge.io/crc-catalogue/legend.htm#crc.legend.params
in_put = b'123456789'
# https://reveng.sourceforge.io/crc-catalogue/17plus.htm#crc.cat.crc-32-iso-hdlc
check = 0xcbf43926
result = Suite(code_py).run_code(in_put, func_name='crc32', do_format_check=False)
assert check == result.returned_value

View File

@ -1,13 +0,0 @@
import pytest
from ..helpers import Suite
@pytest.mark.slow_integration_test
def test_fib():
with open('./examples/fib.py', 'r', encoding='UTF-8') as fil:
code_py = "\n" + fil.read()
result = Suite(code_py).run_code(40, func_name='fib')
assert 102334155 == result.returned_value

View File

@ -0,0 +1,30 @@
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

View File

@ -0,0 +1,70 @@
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]

View File

@ -1,309 +0,0 @@
# runtime_extract_value_literal
As a developer
I want to extract a $TYPE value
In order get the result I calculated with my phasm code
```py
@exported
def testEntry() -> $TYPE:
return $VAL0
```
```py
expect(VAL0)
```
# runtime_extract_value_round_trip
As a developer
I want to extract a $TYPE value that I've input
In order let the code select a value that I've predefined
```py
@exported
def testEntry(x: $TYPE) -> $TYPE:
return x
```
```py
expect(VAL0, given=[VAL0])
```
# module_constant_def_ok
As a developer
I want to define $TYPE module constants
In order to make hardcoded values more visible
and to make it easier to change hardcoded values
```py
CONSTANT: $TYPE = $VAL0
@exported
def testEntry() -> i32:
return 9
```
```py
expect(9)
```
# module_constant_def_bad
As a developer
I want to receive a type error on an invalid assignment on a $TYPE module constant
In order to make debugging easier
```py
CONSTANT: (u32, ) = $VAL0
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
expect_type_error('Tuple element count mismatch')
elif TYPE_NAME == 'bytes':
expect_type_error('Cannot convert from literal bytes')
elif TYPE_NAME == 'f32' or TYPE_NAME == 'f64':
expect_type_error('Cannot convert from literal float')
elif TYPE_NAME == 'i32' or TYPE_NAME == 'i64' or TYPE_NAME == 'u32' or TYPE_NAME == 'u64':
expect_type_error('Cannot convert from literal integer')
else:
expect_type_error('Not the same type')
```
# function_result_is_literal_ok
As a developer
I want to use return a literal from a function
In order to define constants in a more dynamic way
```py
def drop_arg_return_9(x: $TYPE) -> i32:
return 9
def constant() -> $TYPE:
return $VAL0
@exported
def testEntry() -> i32:
return drop_arg_return_9(constant())
```
```py
expect(9)
```
# function_result_is_literal_bad
As a developer
I want to receive a type error when returning a $TYPE literal for a function that doesn't return that type
In order to make debugging easier
```py
def drop_arg_return_9(x: (u32, )) -> i32:
return 9
def constant() -> (u32, ):
return $VAL0
@exported
def testEntry() -> i32:
return drop_arg_return_9(constant())
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
expect_type_error('Tuple element count mismatch')
elif TYPE_NAME.startswith('struct_'):
expect_type_error('Not the same type')
elif TYPE_NAME == 'bytes':
expect_type_error('Cannot convert from literal bytes')
elif TYPE_NAME == 'f32' or TYPE_NAME == 'f64':
expect_type_error('Cannot convert from literal float')
elif TYPE_NAME == 'i32' or TYPE_NAME == 'i64' or TYPE_NAME == 'u32' or TYPE_NAME == 'u64':
expect_type_error('Cannot convert from literal integer')
else:
expect_type_error('Not the same type')
```
# function_result_is_module_constant_ok
As a developer
I want to use return a $TYPE module constant from a function
In order to use my module constants in return statements
```py
CONSTANT: $TYPE = $VAL0
def helper(x: $TYPE) -> i32:
return 9
def constant() -> $TYPE:
return CONSTANT
@exported
def testEntry() -> i32:
return helper(constant())
```
```py
expect(9)
```
# function_result_is_module_constant_bad
As a developer
I want to receive a type error when returning a $TYPE module constant for a function that doesn't return that type
In order to make debugging easier
```py
CONSTANT: $TYPE = $VAL0
def drop_arg_return_9(x: (u32, )) -> i32:
return 9
def constant() -> (u32, ):
return CONSTANT
@exported
def testEntry() -> i32:
return drop_arg_return_9(constant())
```
```py
expect_type_error('Not the same type')
```
# function_result_is_arg_ok
As a developer
I want to use return a $TYPE function argument
In order to make it possible to select a value using a function
```py
CONSTANT: $TYPE = $VAL0
def drop_arg_return_9(x: $TYPE) -> i32:
return 9
def select(x: $TYPE) -> $TYPE:
return x
@exported
def testEntry() -> i32:
return drop_arg_return_9(select(CONSTANT))
```
```py
expect(9)
```
# function_result_is_arg_bad
As a developer
I want to receive a type error when returning a $TYPE argument for a function that doesn't return that type
In order to make debugging easier
```py
def drop_arg_return_9(x: (u32, )) -> i32:
return 9
def select(x: $TYPE) -> (u32, ):
return x
```
```py
expect_type_error('Not the same type')
```
# function_arg_literal_ok
As a developer
I want to use a $TYPE literal by passing it to a function
In order to use a pre-existing function with the values I specify
```py
def helper(x: $TYPE) -> i32:
return 9
@exported
def testEntry() -> i32:
return helper($VAL0)
```
```py
expect(9)
```
# function_arg_literal_bad
As a developer
I want to receive a type error when passing a $TYPE literal to a function that does not accept it
In order to make debugging easier
```py
def helper(x: (u32, )) -> i32:
return 9
@exported
def testEntry() -> i32:
return helper($VAL0)
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
expect_type_error('Tuple element count mismatch')
elif TYPE_NAME.startswith('struct_'):
expect_type_error('Not the same type')
elif TYPE_NAME == 'bytes':
expect_type_error('Cannot convert from literal bytes')
elif TYPE_NAME == 'f32' or TYPE_NAME == 'f64':
expect_type_error('Cannot convert from literal float')
elif TYPE_NAME == 'i32' or TYPE_NAME == 'i64' or TYPE_NAME == 'u32' or TYPE_NAME == 'u64':
expect_type_error('Cannot convert from literal integer')
else:
expect_type_error('Not the same type')
```
# function_arg_module_constant_def_ok
As a developer
I want to use a $TYPE module constant by passing it to a function
In order to use my defined value with a pre-existing function
```py
CONSTANT: $TYPE = $VAL0
def helper(x: $TYPE) -> i32:
return 9
@exported
def testEntry() -> i32:
return helper(CONSTANT)
```
```py
expect(9)
```
# function_arg_module_constant_def_bad
As a developer
I want to receive a type error when passing a $TYPE module constant to a function that does not accept it
In order to make debugging easier
```py
CONSTANT: $TYPE = $VAL0
def helper(x: (u32, )) -> i32:
return 9
@exported
def testEntry() -> i32:
return helper(CONSTANT)
```
```py
expect_type_error('Not the same type')
```

View File

@ -1,153 +0,0 @@
import functools
import json
import sys
from typing import Any
import marko
import marko.md_renderer
def get_tests(template):
test_data = None
for el in template.children:
if isinstance(el, marko.block.BlankLine):
continue
if isinstance(el, marko.block.Heading):
if test_data is not None:
yield test_data
test_data = []
test_data.append(el)
continue
if test_data is not None:
test_data.append(el)
if test_data is not None:
yield test_data
def apply_settings(settings, txt):
for k, v in settings.items():
if k in ('CODE_HEADER', 'PYTHON'):
continue
txt = txt.replace(f'${k}', v)
return txt
def generate_assertion_expect(result, arg, given=None):
given = given or []
result.append('result = Suite(code_py).run_code(' + ', '.join(repr(x) for x in given) + ')')
result.append(f'assert {repr(arg)} == result.returned_value')
def generate_assertion_expect_type_error(result, error_msg):
result.append(f'with pytest.raises(Type5SolverException, match={error_msg!r}):')
result.append(' Suite(code_py).run_code()')
def json_does_not_support_byte_or_tuple_values_fix(inp: Any):
if isinstance(inp, (int, float, )):
return inp
if isinstance(inp, str):
if inp.startswith('bytes:'):
return inp[6:].encode()
return inp
if isinstance(inp, list):
return tuple(map(json_does_not_support_byte_or_tuple_values_fix, inp))
if isinstance(inp, dict):
return {
key: json_does_not_support_byte_or_tuple_values_fix(val)
for key, val in inp.items()
}
raise NotImplementedError(inp)
def generate_assertions(settings, result_code):
result = []
locals_ = {
'TYPE': settings['TYPE'],
'TYPE_NAME': settings['TYPE_NAME'],
'expect': functools.partial(generate_assertion_expect, result),
'expect_type_error': functools.partial(generate_assertion_expect_type_error, result),
}
if 'PYTHON' in settings:
locals_.update(json_does_not_support_byte_or_tuple_values_fix(settings['PYTHON']))
if 'VAL0' not in locals_:
locals_['VAL0'] = eval(settings['VAL0'])
exec(result_code, {}, locals_)
return ' ' + '\n '.join(result) + '\n'
def generate_code(markdown, template, settings):
type_name = settings['TYPE_NAME']
print('"""')
print('AUTO GENERATED')
print()
print('TEMPLATE:', sys.argv[1])
print('SETTINGS:', sys.argv[2])
print('"""')
print('import pytest')
print()
print('from phasm.type5.solver import Type5SolverException')
print()
print('from ..helpers import Suite')
print()
print()
for test in get_tests(template):
assert len(test) == 4, test
heading, paragraph, code_block1, code_block2 = test
assert isinstance(heading, marko.block.Heading)
assert isinstance(paragraph, marko.block.Paragraph)
assert isinstance(code_block1, marko.block.FencedCode)
assert isinstance(code_block2, marko.block.FencedCode)
test_id = apply_settings(settings, heading.children[0].children)
user_story = apply_settings(settings, markdown.renderer.render(paragraph))
inp_code = apply_settings(settings, code_block1.children[0].children)
result_code = markdown.renderer.render_children(code_block2)
print('@pytest.mark.integration_test')
print(f'def test_{type_name}_{test_id}():')
print(' """')
print(' ' + user_story.strip().replace('\n', '\n '))
print(' """')
print(' code_py = """')
if 'CODE_HEADER' in settings:
for lin in settings['CODE_HEADER']:
print(lin)
print()
print(inp_code.rstrip('\n'))
print('"""')
print()
print(generate_assertions(settings, result_code))
print()
def main():
markdown = marko.Markdown(
renderer=marko.md_renderer.MarkdownRenderer,
)
with open(sys.argv[1], 'r', encoding='utf-8') as fil:
template = markdown.parse(fil.read())
with open(sys.argv[2], 'r', encoding='utf-8') as fil:
settings = json.load(fil)
if 'TYPE_NAME' not in settings:
settings['TYPE_NAME'] = settings['TYPE']
generate_code(markdown, template, settings)
if __name__ == '__main__':
main()

View File

@ -1,4 +0,0 @@
{
"TYPE": "bytes",
"VAL0": "b'ABCDEFG'"
}

View File

@ -1,5 +0,0 @@
{
"TYPE_NAME": "dynamic_array_u64",
"TYPE": "u64[...]",
"VAL0": "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, )"
}

View File

@ -1,4 +0,0 @@
{
"TYPE": "f32",
"VAL0": "1000000.125"
}

View File

@ -1,4 +0,0 @@
{
"TYPE": "f64",
"VAL0": "1000000.125"
}

View File

@ -1,4 +0,0 @@
{
"TYPE": "i32",
"VAL0": "1000000"
}

View File

@ -1,4 +0,0 @@
{
"TYPE": "i64",
"VAL0": "1000000"
}

View File

@ -1,5 +0,0 @@
{
"TYPE_NAME": "static_array_tuple_u32_u32_3",
"TYPE": "(u32, u32, )[3]",
"VAL0": "((1, 100, ), (2, 200, ), (3, 300, ), )"
}

View File

@ -1,5 +0,0 @@
{
"TYPE_NAME": "static_array_u64_32",
"TYPE": "u64[32]",
"VAL0": "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, )"
}

View File

@ -1,33 +0,0 @@
{
"TYPE_NAME": "static_array_with_structs",
"TYPE": "StructMain[3]",
"VAL0": "(StructMain(1, (StructCode(-4), 4, 4.0, ), (StructCode(-1), StructCode(-2), )), StructMain(2, (StructCode(-16), 16, 16.0, ), (StructCode(3), StructCode(14), )), StructMain(3, (StructCode(-256), 256, 256.0, ), (StructCode(-9), StructCode(-98), )), )",
"CODE_HEADER": [
"class StructCode:",
" code: i32",
"",
"class StructMain:",
" val00: u8",
" val01: (StructCode, u64, f32, )",
" val02: StructCode[2]"
],
"PYTHON": {
"VAL0": [
{
"val00": 1,
"val01": [{"code": -4}, 4, 4.0],
"val02": [{"code": -1}, {"code": -2}]
},
{
"val00": 2,
"val01": [{"code": -16}, 16, 16.0],
"val02": [{"code": 3}, {"code": 14}]
},
{
"val00": 3,
"val01": [{"code": -256}, 256, 256.0],
"val02": [{"code": -9}, {"code": -98}]
}
]
}
}

View File

@ -1,46 +0,0 @@
{
"TYPE_NAME": "struct_all_primitives",
"TYPE": "StructallPrimitives",
"VAL0": "StructallPrimitives(1, 2, 4, 8, 1, -1, 2, -2, 4, -4, 8, -8, 125.125, -125.125, 5000.5, -5000.5, b'Hello, world!')",
"CODE_HEADER": [
"class StructallPrimitives:",
" val00: u8",
" val03: u16",
" val01: u32",
" val02: u64",
" val10: i8",
" val11: i8",
" val16: i16",
" val17: i16",
" val12: i32",
" val13: i32",
" val14: i64",
" val15: i64",
" val20: f32",
" val21: f32",
" val22: f64",
" val23: f64",
" val30: bytes"
],
"PYTHON": {
"VAL0": {
"val00": 1,
"val03": 2,
"val01": 4,
"val02": 8,
"val10": 1,
"val11": -1,
"val16": 2,
"val17": -2,
"val12": 4,
"val13": -4,
"val14": 8,
"val15": -8,
"val20": 125.125,
"val21": -125.125,
"val22": 5000.5,
"val23": -5000.5,
"val30": "bytes:Hello, world!"
}
}
}

View File

@ -1,25 +0,0 @@
{
"TYPE_NAME": "struct_nested",
"TYPE": "StructNested",
"VAL0": "StructNested(4, SubStruct(8, 16), 20)",
"CODE_HEADER": [
"class SubStruct:",
" val00: u8",
" val01: u8",
"",
"class StructNested:",
" val00: u64",
" val01: SubStruct",
" val02: u64"
],
"PYTHON": {
"VAL0": {
"val00": 4,
"val01": {
"val00": 8,
"val01": 16
},
"val02": 20
}
}
}

View File

@ -1,12 +0,0 @@
{
"TYPE_NAME": "struct_one_field",
"TYPE": "StructOneField",
"VAL0": "StructOneField(4)",
"CODE_HEADER": [
"class StructOneField:",
" value: u32"
],
"PYTHON": {
"VAL0": {"value": 4}
}
}

View File

@ -1,5 +0,0 @@
{
"TYPE_NAME": "tuple_all_primitives",
"TYPE": "(u8, u32, u64, i8, i8, i32, i32, i64, i64, f32, f32, f64, f64, bytes, )",
"VAL0": "(1, 4, 8, 1, -1, 4, -4, 8, -8, 125.125, -125.125, 5000.5, -5000.5, b'Hello, world!', )"
}

View File

@ -1,5 +0,0 @@
{
"TYPE_NAME": "tuple_nested",
"TYPE": "(u64, (u32, bytes, u32, ), (u8, u32[3], u8, ), )",
"VAL0": "(1000000, (1, b'test', 2, ), (1, (4, 4, 4, ), 1, ), )"
}

Some files were not shown because too many files have changed in this diff Show More