Compare commits

..

21 Commits

Author SHA1 Message Date
0e7ea508cd add memory.fill/memory.copy intrinsics 2022-06-11 23:31:02 +02:00
0a0d90c801 add some docs, allow leading zeros in literals 2022-05-08 19:21:19 +02:00
c22297ea82 fix <| not parsing 2022-05-08 00:06:38 +02:00
c59b35f9c6 fix #<= and #>= not parsing 2022-05-07 23:59:40 +02:00
2cf47085c1 add support for +:= etc. 2022-05-07 23:51:25 +02:00
01d64baaab implement binop-assignment (+= etc.) 2022-05-07 20:30:16 +02:00
a52fe53a01 fix let lazy/inline chains re-using the same variable 2022-05-07 18:33:26 +02:00
1e746be750 add support for 'if else', change hex escapes to '\ab' 2022-05-06 09:03:13 +02:00
4cfc7ae8a8 add support for escaped strings + (multi-)char literatls 2022-05-06 01:23:31 +02:00
aac7bbd878 implement _f literal suffix as shortcut for NUM as f32 2022-04-10 23:58:18 +02:00
ebc701e2f2 fix != operator failing to parse 2022-04-09 08:51:23 +02:00
557c3a8426 always return dependencies, even when hitting an error 2022-03-05 21:02:58 +01:00
896385654a return dependencies for compiling cwa source 2022-02-27 21:26:55 +01:00
cda3eb868b fix "i?SCREEN = 0" being parsed as "i?(SCREEN = 0)" 2022-02-26 21:00:01 +01:00
5f316cf17d add new features to readme 2022-02-26 12:14:31 +01:00
e608d3bb4b implement const 2022-02-26 11:46:38 +01:00
b41b7f250c add include support 2022-02-25 23:38:50 +01:00
71de622634 add source filename to errors to prepare for includes 2022-02-25 23:13:11 +01:00
f433948d4e update dependencies 2022-02-24 23:59:14 +01:00
132aea3996 add load/store intrinsics 2022-02-23 23:52:44 +01:00
1b434f6b30 clippy fixes 2022-02-22 22:40:45 +01:00
20 changed files with 1331 additions and 606 deletions

24
Cargo.lock generated
View File

@@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.44"
version = "1.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
[[package]]
name = "ariadne"
@@ -34,9 +34,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chumsky"
version = "0.5.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2d3efff85e8572b1c3fa0127706af58c4fff8458f8d9436d54b1e97573c7a3f"
checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
dependencies = [
"ahash",
]
@@ -83,9 +83,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.3"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
dependencies = [
"cfg-if",
"libc",
@@ -106,9 +106,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "libc"
version = "0.2.105"
version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]]
name = "pico-args"
@@ -139,18 +139,18 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-encoder"
version = "0.8.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0"
checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab"
dependencies = [
"leb128",
]
[[package]]
name = "wasmparser"
version = "0.81.0"
version = "0.83.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a"
[[package]]
name = "yansi"

View File

@@ -7,9 +7,9 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
wasmparser = "0.81"
wasm-encoder = "0.8"
wasmparser = "0.83"
wasm-encoder = "0.10"
anyhow = "1"
chumsky = "0.5"
chumsky = "0.8"
ariadne = "0.1"
pico-args = "0.4"

105
README.md
View File

@@ -56,6 +56,14 @@ Then run it on [MicroW8](https://exoticorn.github.io/microw8/v0.1pre2)
*/
```
### Include
Other sourcefiles can be included with the `include` top level statement:
```
include "platform_imports.cwa"
```
### Types
There are four types in WebAssembly and therefore CurlyWas:
@@ -81,16 +89,25 @@ For floating point numbers, only the most basic decimal format is currently impl
0.464, 3.141, -10.0
```
String literals exist in a very basic form. No escape character implemented yet.
String literals are used for include paths, import names and as literal strings in the data section. The following escapes are supported:
| Escape | Result | Comment |
| `\"` | `"` | |
| `\'` | `'` | |
| `\t` | 8 | |
| `\n` | 10 | |
| `\r` | 13 | |
| `\N` | 0x0N | (Can't be followed by a hex digit) |
| `\NN` | 0xNN | |
```
"env.memory", "Hello World!"
this does not work, yet:
"one line\nsecond line", "They said: \"Enough!\""
```
Character literals are enclosed in single quotes `'` and support the same escapes as strings. They can contain up to 4 characters and evaluate to the
little-endian representation of these characters. For examples: `'A'` evaluates to `0x41`, `'hi'` evaluates to 0x6968, and `'Crly'` to 0x7a6c7243.
### Imports
WebAssembly imports are specified with a module and a name. In CurlyWas you give them inside a single string literal, seperated by a dot. So a module `env` and name `printString` would be written `"env.printString"`.
@@ -138,6 +155,18 @@ can use it. However, exporting global variable is not yet supported in CurlyWas.
The type is optional, if missing it is inferred from the init value.
### Constants
Constants can be declared in the global scope:
```
const name[: type] = value;
```
`value` has to be an expression evaluating to a constant value. It may reference other constants.
The type is optional, but if given has to match the type of `value`.
### Functions
Functions look like this:
@@ -275,6 +304,9 @@ So for example this block evaluates to 12:
Blocks are used as function bodies and in flow control (`if`, `block`, `loop`), but can also used at any point inside an expression.
Variable re-assignments of the form `name = name <op> expression` can be shortened to `name <op>= expression`, for example `x += 1` to increment `x` by one. This works for all arithmetic, bit and shift operators.
The same is allowed for `name := name <op> expression`, ie. `x +:= 1` increments `x` and returns the new value.
#### Flow control
`if condition_expression { if_true_block } [else {if_false_block}]` executes the `if_true_block` if the condition evaluates to a non-zero integer and
@@ -284,6 +316,17 @@ the `if_false_block` otherwise (if it exists). It can also be used as an express
let a = if 0 { 2 } else { 3 }; // assigns 3 to a
```
If the `if_false_block` contains exactly one `if` expression or statement you may omit the curly braces, writing `else if` chains like:
```
if x == 0 {
doOneThing()
} else if x == 1 {
doThatOtherThing()
} else {
keepWaiting()
}
```
`block name { ... }` opens a named block scope. A branch statement can be used to jump to the end of the block. Currently, `block` can only be used
as a statement, returning a value from the block is not yet supported.
@@ -296,15 +339,65 @@ non-zero integer.
#### Memory load/store
To read from memory you specify a memory location as `base?offset` or `base!offset`. `?` reads a byte and `!` reads a 32bit word.
To read from memory you specify a memory location as `base?offset`, `base!offset` or `base$offset`. `?` reads a byte, `!` reads a 32bit word
and `$` reads a 32bit float.
`base` can be any expression that evaluates to an `i32` while `offset` has to be a constant `i32` value. The effective memory address is the sum of both.
Writing to memory looks just like an assignment to a memory location: `base?offset = expressoin` and `base!offset = expression`.
Writing to memory looks just like an assignment to a memory location: `base?offset = expression`, `base!offset = expression` and `base$offset = expression`.
When reading/writing 32bit words you need to make sure the address is 4-byte aligned.
These compile to `i32.load8_u`, `i32.load`, `i32.store8` and `i32.store`. Other WASM load/store instructions will be implemented as intrinsics, but aren't yet.
These compile to `i32.load8_u`, `i32.load`, `f32.load`, `i32.store8`, `i32.store` and `f32.store`.
In addition, all wasm memory instructions are available as intrinsics:
```
<load-ins>(<base-address>[, <offset>, [<align>]])
offset defaults to 0, align to the natural alignment: 0 for 8bit loads, 1 for 16bit, 2 for 32 bit and 3 for 64bit.
```
with `<load-ins>` being one of `i32.load`, `i32.load8_u`, `i32.load8_s`, `i32.load16_u`, `i32.load16_s`,
`i64.load`, `i64.load8_u`, `i64.load8_s`, `i64.load16_u`, `i64.load16_s`, `i32.load32_u`, `i32.load32_s`,
`f32.load` and `f64.load`.
```
<store-ins>(<value>, <base-address>[, <offset>, [<align>]])
offset and align defaults are the same as the load intrinsics.
```
with `<store-ins>` being one of `i32.store`, `i32.store8`, `i32.store16`, `i64.store`, `i64.store8`,
`i64.store16`, `i64.store32`, `f32.store` and `f64.store`.
#### Data
Data sections are written in `data` blocks:
```
data <address> {
...
}
```
The content of such a block is loaded at the given address at module start.
Inside the data block you can include 8, 16, 32, 64, f32 or f64 values:
```
i8(1, 255) i16(655350) i32(0x12345678) i64(0x1234567890abcdefi64) f32(1.0, 3.141) f64(0.5f64)
```
Strings:
```
"First line" i8(13, 10) "Second line"
```
And binary files:
```
file("font.bin")
```
#### Advanced sequencing

View File

@@ -0,0 +1,16 @@
import "env.memory" memory(4);
import "env.random" fn random() -> i32;
export fn upd() {
let i: i32;
loop pixels {
let inline left = i32.load8_u((i + (320*240 - 319)) % (320*240), 120);
let inline top = i32.load8_u((i + 320*239) % (320*240), 120, 0);
let inline here = i32.load16_u(i, 119);
let inline all = (left << 24) | (top << 16) | here;
let lazy r = random();
let inline new = (all #>> ((r & 3) * 8)) ^ ((r & 31) * !(r #>> 22));
i32.store8(new, i, 120);
branch_if (i := i + 1) < 320*240: pixels;
}
}

View File

@@ -1,29 +1,8 @@
import "env.memory" memory(4);
include "uw8.cwa"
import "env.pow" fn pow(f32, f32) -> f32;
import "env.sin" fn sin(f32) -> f32;
import "env.cos" fn cos(f32) -> f32;
import "env.atan2" fn atan2(f32, f32) -> f32;
import "env.tan" fn tan(f32) -> f32;
import "env.atan" fn atan(f32) -> f32;
import "env.rectangle" fn rect(f32, f32, f32, f32, i32);
const SWEETY = PALETTE + 192 * 4;
//export fn tic(time: i32) {
// let i: i32;
// loop pixels {
// let lazy x = (i % 320) as f32 - 160.5;
// let lazy y = (i / 320 - 120) as f32;
//
// let lazy dist = 4000 as f32 / sqrt(x*x + y*y + 10 as f32);
// let lazy angle = atan2(x, y) * (64.0 / 3.141);
//
// i?120 = ((((dist + time as f32 / 63 as f32) as i32 ^ angle as i32) #% 32 + 32) >> ((dist as i32 - i % 7 * 3) / 40)) + 192;
//
// branch_if (i := i + 1) < 320*240: pixels;
// }
//}
export fn tic(time: i32) {
export fn upd() {
let i: i32;
loop colors {
rect((i % 16 * 15) as f32, (i / 16 * 15) as f32, 15 as f32, 15 as f32, i);
@@ -43,7 +22,7 @@ start fn gen_palette() {
let lazy a = max(llimit, min(ulimit, c)) * (scale + 0.05);
let lazy b = scale * scale * 0.8;
let inline v = (select(i < 11*16*3, max(0 as f32, min(a + b - a * b, 1 as f32)), scale) * 255 as f32) as i32;
(i%3 + i/3*4)?(120+320*240) = v;
(i%3 + i/3*4)?PALETTE = v;
avg = (avg + c) * 0.5;
branch_if i := i - 1: gradients;
@@ -56,15 +35,15 @@ start fn gen_palette() {
let lazy first_step = index >= 32;
let inline src1 = select(first_step, index % 32 / 2, index * 2);
let inline src2 = select(first_step, (index + 1) % 32 / 2, index * 2 + 1);
let inline c1 = (src1 * 4 + channel)?(120+320*240+192*4);
let inline c2 = (src2 * 4 + channel)?(120+320*240+192*4);
i?(120+320*240+192*4) = (c1 + c2) * (3 + first_step) / 8;
let inline c1 = (src1 * 4 + channel)?SWEETY;
let inline c2 = (src2 * 4 + channel)?SWEETY;
i?SWEETY = (c1 + c2) * (3 + first_step) / 8;
branch_if (i := i - 1) >= 0: expand_sweetie;
}
}
data 120+320*240+192*4 {
data SWEETY {
i32(
0x2c1c1a,
0x5d275d,

View File

@@ -0,0 +1,24 @@
import "env.memory" memory(4);
import "env.printString" fn printString(i32);
import "env.printChar" fn printChar(i32);
export fn upd() {
printChar(12);
printChar('Test');
printChar('\1f\10\10');
printChar('abc\n');
printString(0);
let t = 32!32 / 1000 #% 3;
if t == 0 {
printChar('one');
} else if t == 1 {
printChar('two');
} else {
printChar('many');
}
}
data 0 {
"\0e\64\"Colors!!!\"\0e\1\r\n\0"
}

View File

@@ -1,24 +1,29 @@
import "env.memory" memory(4);
import "env.sin" fn sin(f32) -> f32;
import "env.time" fn time() -> f32;
import "env.setPixel" fn setPixel(i32, i32, i32);
export fn tic(time: i32) {
let i: i32;
export fn upd() {
let x: i32;
let y: i32;
loop screen {
let lazy t = time as f32 / 2000 as f32;
let lazy o = sin(t) * 0.8;
let lazy q = (i % 320) as f32 - 160.1;
let lazy w = (i / 320 - 120) as f32;
let inline t = time() / 2 as f32;
let lazy o = sin(t) * 0.75;
let inline q = x as f32 - 160.5;
let inline w = (y - 120) as f32;
let lazy r = sqrt(q*q + w*w);
let lazy z = q / r;
let lazy s = z * o + sqrt(z * z * o * o + 1 as f32 - o * o);
let lazy q2 = (z * s - o) * 10 as f32 + t;
let lazy w2 = w / r * s * 10 as f32 + t;
let lazy s2 = s * 50 as f32 / r;
i?120 = max(
let inline q2 = (z * s - o) * 10 as f32 + t;
let inline w2 = w / r * s * 10 as f32 + t;
let inline s2 = s * 100 as f32 / r;
let inline color = max(
0 as f32,
((q2 as i32 ^ w2 as i32 & ((s2 + t) * 20 as f32) as i32) & 5) as f32 *
(2 as f32 - s2) * 22 as f32
) as i32;
branch_if (i := i + 1) < 320*240: screen
((q2 as i32 ^ w2 as i32 & ((s2 + time()) * 10 as f32) as i32) & 5) as f32 *
(4 as f32 - s2) as f32
) as i32 - 32;
setPixel(x, y, color);
branch_if x := (x + 1) % 320: screen;
branch_if y := (y + 1) % 320: screen;
}
}

13
examples/microw8/uw8.cwa Normal file
View File

@@ -0,0 +1,13 @@
import "env.memory" memory(4);
import "env.pow" fn pow(f32, f32) -> f32;
import "env.sin" fn sin(f32) -> f32;
import "env.cos" fn cos(f32) -> f32;
import "env.atan2" fn atan2(f32, f32) -> f32;
import "env.tan" fn tan(f32) -> f32;
import "env.atan" fn atan(f32) -> f32;
import "env.rectangle" fn rect(f32, f32, f32, f32, i32);
const FRAMEBUFFER = 120;
const PALETTE = 0x13000;
const FONT = 0x13400;

View File

@@ -1,15 +1,16 @@
import "env.memory" memory(2);
import "env.time" fn time() -> f32;
export fn tic(time: i32) {
export fn upd() {
let i: i32;
loop pixels {
let lazy x = (i % 320) as f32 - 160.1;
let lazy y = (i / 320 - 120) as f32;
let lazy dist = 10000.0 / (x*x + y*y);
let lazy t = time as f32 / 20 as f32;
let lazy dist = 1024_f / (x*x + y*y);
let inline t = time() * 4_f;
i?120 = (x * dist + t) as i32 ^ (y * dist + t) as i32;
i?120 = (x * dist + t) as i32 ^ (y * dist + t) as i32 | -32;
branch_if (i := i + 1) < 320*240: pixels
branch_if (i +:= 1) < 320*240: pixels
}
}

View File

@@ -18,14 +18,14 @@ fn rng(state: i32) -> i32 {
}
fn set_color(color: i32) -> i32 {
?20 = color;
0?20 = color;
6
}
export fn update() {
let y: i32;
let score = pz;
let lazy pad = ?22;
let lazy pad = 0?22;
let lazy zero = 0.0;
let lazy control_speed = 0.03;
@@ -33,7 +33,7 @@ export fn update() {
f = f * 0.7;
loop lines {
?(8003-y) = (score := score / 10) % 10 + 48;
(8003-y)?0 = (score := score / 10) % 10 + 48;
let lazy z = (4000 / (y := y + 1) + pz) / 20;
let lazy x = (rng(rng(rng(rng(z)))) >> 30) as f32 - px;
let lazy w = 9 as f32 / sqrt(z as f32);

View File

@@ -1,13 +1,26 @@
use std::{fmt, path::PathBuf};
use crate::Span;
use crate::parser::Span;
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct Script {
pub imports: Vec<Import>,
pub global_vars: Vec<GlobalVar>,
pub functions: Vec<Function>,
pub data: Vec<Data>,
pub includes: Vec<Include>,
pub consts: Vec<GlobalConst>,
}
impl Script {
pub fn merge(&mut self, mut other: Script) {
self.imports.append(&mut other.imports);
self.global_vars.append(&mut other.global_vars);
self.functions.append(&mut other.functions);
self.data.append(&mut other.data);
self.consts.append(&mut other.consts);
assert!(other.includes.is_empty());
}
}
#[derive(Debug)]
@@ -16,6 +29,14 @@ pub enum TopLevelItem {
GlobalVar(GlobalVar),
Function(Function),
Data(Data),
Include(Include),
Const(GlobalConst),
}
#[derive(Debug)]
pub struct Include {
pub span: Span,
pub path: String,
}
#[derive(Debug)]
@@ -49,6 +70,14 @@ pub struct GlobalVar {
pub mutable: bool,
}
#[derive(Debug)]
pub struct GlobalConst {
pub span: Span,
pub name: String,
pub value: Expression,
pub type_: Option<Type>,
}
#[derive(Debug)]
pub struct Function {
pub span: Span,
@@ -152,7 +181,7 @@ pub enum DataType {
F64,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct MemoryLocation {
pub span: Span,
pub size: MemSize,
@@ -160,7 +189,7 @@ pub struct MemoryLocation {
pub right: Box<Expression>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Expression {
pub type_: Option<Type>,
pub expr: Expr,
@@ -195,9 +224,16 @@ impl Expression {
_ => panic!("Expected F64Const"),
}
}
pub fn is_const(&self) -> bool {
match self.expr {
Expr::I32Const(_) | Expr::I64Const(_) | Expr::F32Const(_) | Expr::F64Const(_) => true,
_ => false,
}
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Expr {
Block {
statements: Vec<Expression>,
@@ -288,7 +324,7 @@ impl Expr {
Expression {
type_: None,
expr: self,
span: span,
span,
}
}
}
@@ -337,6 +373,7 @@ pub enum BinOp {
pub enum MemSize {
Byte,
Word,
Float,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]

View File

@@ -1,35 +1,103 @@
use crate::ast;
use std::collections::HashMap;
use crate::{
ast,
parser::{Sources, Span},
typecheck::{report_duplicate_definition, report_error},
};
type Result<T> = std::result::Result<T, ()>;
pub fn fold_script(script: &mut ast::Script, sources: &Sources) -> Result<()> {
let mut context = Context {
consts: HashMap::new(),
sources,
};
fold_consts(&mut context, &mut script.consts)?;
pub fn fold_script(script: &mut ast::Script) {
for var in &mut script.global_vars {
fold_expr(&mut var.value);
fold_expr(&context, &mut var.value);
}
for func in &mut script.functions {
fold_expr(&mut func.body);
fold_expr(&context, &mut func.body);
}
for data in &mut script.data {
fold_expr(&mut data.offset);
fold_expr(&context, &mut data.offset);
for values in &mut data.data {
match values {
ast::DataValues::Array { values, .. } => {
for value in values {
fold_expr(value);
fold_expr(&context, value);
}
}
ast::DataValues::String(_) | ast::DataValues::File { .. } => (),
}
}
}
Ok(())
}
fn fold_mem_location(mem_location: &mut ast::MemoryLocation) {
fold_expr(&mut mem_location.left);
fold_expr(&mut mem_location.right);
struct Context<'a> {
consts: HashMap<String, ast::Expr>,
sources: &'a Sources,
}
fn fold_expr(expr: &mut ast::Expression) {
fn fold_consts(context: &mut Context, consts: &mut [ast::GlobalConst]) -> Result<()> {
let mut spans: HashMap<&str, Span> = HashMap::new();
for cnst in consts.iter_mut() {
if let Some(prev_span) = spans.insert(&cnst.name, cnst.span.clone()) {
report_duplicate_definition(
"Const already defined",
&cnst.span,
&prev_span,
context.sources,
)?;
}
}
while context.consts.len() < consts.len() {
let mut making_progress = false;
for cnst in consts.iter_mut() {
if !context.consts.contains_key(&cnst.name) {
fold_expr(context, &mut cnst.value);
if cnst.value.is_const() {
context
.consts
.insert(cnst.name.clone(), cnst.value.expr.clone());
making_progress = true;
}
}
}
if !making_progress {
break;
}
}
let mut result = Ok(());
for cnst in consts {
if !context.consts.contains_key(&cnst.name) {
result = report_error(
&format!("Failed to fold const '{}'", cnst.name),
&cnst.span,
context.sources,
);
}
}
result
}
fn fold_mem_location(context: &Context, mem_location: &mut ast::MemoryLocation) {
fold_expr(context, &mut mem_location.left);
fold_expr(context, &mut mem_location.right);
}
fn fold_expr(context: &Context, expr: &mut ast::Expression) {
use ast::BinOp::*;
match expr.expr {
ast::Expr::Block {
@@ -37,15 +105,15 @@ fn fold_expr(expr: &mut ast::Expression) {
ref mut final_expression,
} => {
for stmt in statements {
fold_expr(stmt);
fold_expr(context, stmt);
}
if let Some(ref mut expr) = final_expression {
fold_expr(expr);
fold_expr(context, expr);
}
}
ast::Expr::Let { ref mut value, .. } => {
if let Some(ref mut expr) = value {
fold_expr(expr);
fold_expr(context, expr);
}
}
ast::Expr::Poke {
@@ -53,12 +121,12 @@ fn fold_expr(expr: &mut ast::Expression) {
ref mut value,
..
} => {
fold_mem_location(mem_location);
fold_expr(value);
fold_mem_location(context, mem_location);
fold_expr(context, value);
}
ast::Expr::Peek(ref mut mem_location) => fold_mem_location(mem_location),
ast::Expr::Peek(ref mut mem_location) => fold_mem_location(context, mem_location),
ast::Expr::UnaryOp { op, ref mut value } => {
fold_expr(value);
fold_expr(context, value);
let result = match (op, &value.expr) {
(ast::UnaryOp::Negate, ast::Expr::I32Const(value)) => {
Some(ast::Expr::I32Const(-*value))
@@ -72,6 +140,20 @@ fn fold_expr(expr: &mut ast::Expression) {
(ast::UnaryOp::Negate, ast::Expr::F64Const(value)) => {
Some(ast::Expr::F64Const(-*value))
}
(ast::UnaryOp::Negate, ast::Expr::Cast { value, type_ }) => {
if let ast::Expr::I32Const(v) = value.expr {
Some(ast::Expr::Cast {
value: Box::new(ast::Expression {
expr: ast::Expr::I32Const(-v),
span: value.span.clone(),
type_: value.type_,
}),
type_: *type_,
})
} else {
None
}
}
(ast::UnaryOp::Not, ast::Expr::I32Const(value)) => {
Some(ast::Expr::I32Const((*value == 0) as i32))
}
@@ -90,8 +172,8 @@ fn fold_expr(expr: &mut ast::Expression) {
ref mut right,
..
} => {
fold_expr(left);
fold_expr(right);
fold_expr(context, left);
fold_expr(context, right);
match (&left.expr, &right.expr) {
(&ast::Expr::I32Const(left), &ast::Expr::I32Const(right)) => {
let result = match op {
@@ -237,24 +319,28 @@ fn fold_expr(expr: &mut ast::Expression) {
ast::Expr::I32Const(_)
| ast::Expr::I64Const(_)
| ast::Expr::F32Const(_)
| ast::Expr::F64Const(_)
| ast::Expr::Variable { .. } => (),
ast::Expr::Assign { ref mut value, .. } => fold_expr(value),
ast::Expr::LocalTee { ref mut value, .. } => fold_expr(value),
ast::Expr::Loop { ref mut block, .. } => fold_expr(block),
ast::Expr::LabelBlock { ref mut block, .. } => fold_expr(block),
| ast::Expr::F64Const(_) => (),
ast::Expr::Variable { ref name, .. } => {
if let Some(value) = context.consts.get(name) {
expr.expr = value.clone();
}
}
ast::Expr::Assign { ref mut value, .. } => fold_expr(context, value),
ast::Expr::LocalTee { ref mut value, .. } => fold_expr(context, value),
ast::Expr::Loop { ref mut block, .. } => fold_expr(context, block),
ast::Expr::LabelBlock { ref mut block, .. } => fold_expr(context, block),
ast::Expr::Branch(_) => (),
ast::Expr::BranchIf {
ref mut condition, ..
} => fold_expr(condition),
ast::Expr::Cast { ref mut value, .. } => fold_expr(value),
} => fold_expr(context, condition),
ast::Expr::Cast { ref mut value, .. } => fold_expr(context, value),
ast::Expr::FuncCall {
ref name,
ref mut params,
..
} => {
for param in params.iter_mut() {
fold_expr(param);
fold_expr(context, param);
}
use ast::Expr::*;
let params: Vec<_> = params.iter().map(|e| &e.expr).collect();
@@ -269,31 +355,31 @@ fn fold_expr(expr: &mut ast::Expression) {
ref mut if_false,
..
} => {
fold_expr(condition);
fold_expr(if_true);
fold_expr(if_false);
fold_expr(context, condition);
fold_expr(context, if_true);
fold_expr(context, if_false);
}
ast::Expr::If {
ref mut condition,
ref mut if_true,
ref mut if_false,
} => {
fold_expr(condition);
fold_expr(if_true);
fold_expr(context, condition);
fold_expr(context, if_true);
if let Some(ref mut if_false) = if_false {
fold_expr(if_false);
fold_expr(context, if_false);
}
}
ast::Expr::Return {
value: Some(ref mut value),
} => fold_expr(value),
} => fold_expr(context, value),
ast::Expr::Return { value: None } => (),
ast::Expr::First {
ref mut value,
ref mut drop,
} => {
fold_expr(value);
fold_expr(drop);
fold_expr(context, value);
fold_expr(context, drop);
}
ast::Expr::Error => unreachable!(),
}

View File

@@ -6,7 +6,11 @@ use wasm_encoder::{
MemArg, MemoryType, Module, NameMap, NameSection, StartSection, TypeSection, ValType,
};
use crate::{ast, intrinsics::Intrinsics, Options};
use crate::{
ast,
intrinsics::{Intrinsics, MemInstruction},
Options,
};
pub fn emit(script: &ast::Script, module_name: &str, options: &Options) -> Vec<u8> {
let mut module = Module::new();
@@ -65,9 +69,7 @@ pub fn emit(script: &ast::Script, module_name: &str, options: &Options) -> Vec<u
} => {
function_map.insert(name.clone(), function_map.len() as u32);
EntityType::Function(
*function_types
.get(&(params.clone(), result.clone()))
.unwrap() as u32,
*function_types.get(&(params.clone(), *result)).unwrap() as u32
)
}
};
@@ -254,9 +256,7 @@ fn collect_function_types(script: &ast::Script) -> HashMap<FunctionTypeKey, usiz
} = import.type_
{
let index = types.len();
types
.entry((params.clone(), result.clone()))
.or_insert(index);
types.entry((params.clone(), *result)).or_insert(index);
}
}
@@ -289,7 +289,7 @@ struct FunctionContext<'a> {
functions: &'a HashMap<String, u32>,
locals: &'a ast::Locals,
labels: Vec<String>,
let_values: HashMap<u32, (&'a ast::Expression, ast::LetType)>,
let_values: HashMap<u32, Vec<(&'a ast::Expression, ast::LetType)>>,
intrinsics: &'a Intrinsics,
}
@@ -346,6 +346,11 @@ fn mem_arg_for_location(mem_location: &ast::MemoryLocation) -> MemArg {
memory_index: 0,
offset,
},
ast::MemSize::Float => MemArg {
align: 2,
memory_index: 0,
offset,
},
}
}
@@ -380,7 +385,10 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
.instruction(&Instruction::LocalSet(local.index.unwrap()));
}
ast::LetType::Lazy | ast::LetType::Inline => {
ctx.let_values.insert(local_id.unwrap(), (value, *let_type));
ctx.let_values
.entry(local_id.unwrap())
.or_default()
.push((value, *let_type));
}
}
}
@@ -391,6 +399,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
ctx.function.instruction(&match mem_location.size {
ast::MemSize::Byte => Instruction::I32Load8_U(mem_arg),
ast::MemSize::Word => Instruction::I32Load(mem_arg),
ast::MemSize::Float => Instruction::F32Load(mem_arg),
});
}
ast::Expr::Poke {
@@ -403,6 +412,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
ctx.function.instruction(&match mem_location.size {
ast::MemSize::Byte => Instruction::I32Store8(mem_arg),
ast::MemSize::Word => Instruction::I32Store(mem_arg),
ast::MemSize::Float => Instruction::F32Store(mem_arg),
});
}
ast::Expr::UnaryOp { op, value } => {
@@ -457,7 +467,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
(I32, Or) => Instruction::I32Or,
(I32, Xor) => Instruction::I32Xor,
(I32, Eq) => Instruction::I32Eq,
(I32, Ne) => Instruction::I32Neq,
(I32, Ne) => Instruction::I32Ne,
(I32, Lt) => Instruction::I32LtS,
(I32, LtU) => Instruction::I32LtU,
(I32, Le) => Instruction::I32LeS,
@@ -481,7 +491,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
(I64, Or) => Instruction::I64Or,
(I64, Xor) => Instruction::I64Xor,
(I64, Eq) => Instruction::I64Eq,
(I64, Ne) => Instruction::I64Neq,
(I64, Ne) => Instruction::I64Ne,
(I64, Lt) => Instruction::I64LtS,
(I64, LtU) => Instruction::I64LtU,
(I64, Le) => Instruction::I64LeS,
@@ -503,7 +513,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
DivU | Rem | RemU | And | Or | Xor | Shl | ShrU | ShrS | LtU | LeU | GtU | GeU,
) => unreachable!(),
(F32, Eq) => Instruction::F32Eq,
(F32, Ne) => Instruction::F32Neq,
(F32, Ne) => Instruction::F32Ne,
(F32, Lt) => Instruction::F32Lt,
(F32, Le) => Instruction::F32Le,
(F32, Gt) => Instruction::F32Gt,
@@ -518,7 +528,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
DivU | Rem | RemU | And | Or | Xor | Shl | ShrU | ShrS | LtU | LeU | GtU | GeU,
) => unreachable!(),
(F64, Eq) => Instruction::F64Eq,
(F64, Ne) => Instruction::F64Neq,
(F64, Ne) => Instruction::F64Ne,
(F64, Lt) => Instruction::F64Lt,
(F64, Le) => Instruction::F64Le,
(F64, Gt) => Instruction::F64Gt,
@@ -605,17 +615,17 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
}
ast::Expr::Variable { name, local_id } => {
if let &Some(id) = local_id {
if let Some((expr, let_type)) = ctx.let_values.get(&id) {
if let Some((expr, let_type)) = ctx.let_values.get_mut(&id).and_then(|s| s.pop()) {
match let_type {
ast::LetType::Lazy => {
let expr = ctx.let_values.remove(&id).unwrap().0;
emit_expression(ctx, expr);
ctx.let_values.get_mut(&id).unwrap().clear();
ctx.function
.instruction(&Instruction::LocalTee(ctx.locals[id].index.unwrap()));
}
ast::LetType::Inline => {
let expr = *expr;
emit_expression(ctx, expr);
ctx.let_values.get_mut(&id).unwrap().push((expr, let_type));
}
_ => unreachable!(),
}
@@ -654,19 +664,45 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
}
}
ast::Expr::FuncCall { name, params, .. } => {
for param in params {
emit_expression(ctx, param);
fn mem_instruction(
inst: MemInstruction,
params: &[ast::Expression],
) -> Instruction<'static> {
let offset = params
.get(0)
.map(|e| e.const_i32() as u32 as u64)
.unwrap_or(0);
let alignment = params.get(1).map(|e| e.const_i32() as u32);
(inst.instruction)(MemArg {
offset,
align: alignment.unwrap_or(inst.natural_alignment),
memory_index: 0,
})
}
if let Some(index) = ctx.functions.get(name) {
ctx.function.instruction(&Instruction::Call(*index));
} else {
let mut types = vec![];
for param in params {
types.push(param.type_.unwrap());
}
if let Some(load) = ctx.intrinsics.find_load(name) {
emit_expression(ctx, &params[0]);
ctx.function
.instruction(&ctx.intrinsics.get_instr(name, &types).unwrap());
.instruction(&mem_instruction(load, &params[1..]));
} else if let Some(store) = ctx.intrinsics.find_store(name) {
emit_expression(ctx, &params[1]);
emit_expression(ctx, &params[0]);
ctx.function
.instruction(&mem_instruction(store, &params[2..]));
} else {
for param in params {
emit_expression(ctx, param);
}
if let Some(index) = ctx.functions.get(name) {
ctx.function.instruction(&Instruction::Call(*index));
} else {
let mut types = vec![];
for param in params {
types.push(param.type_.unwrap());
}
ctx.function
.instruction(&ctx.intrinsics.get_instr(name, &types).unwrap());
}
}
}
ast::Expr::Select {

View File

@@ -1,28 +1,31 @@
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::{collections::HashSet, fs::File};
use crate::ast;
use anyhow::{anyhow, Result};
pub fn resolve_includes(script: &mut ast::Script, path: &Path) -> Result<()> {
pub fn resolve_includes(
script: &mut ast::Script,
dependencies: &mut HashSet<PathBuf>,
path: &Path,
) -> Result<()> {
let script_dir = path.parent().expect("Script path has no parent");
for data in &mut script.data {
for values in &mut data.data {
match values {
ast::DataValues::File {
ref path,
ref mut data,
} => {
let mut full_path = script_dir.to_path_buf();
full_path.push(path);
File::open(&full_path)
.map_err(|e| {
anyhow!("Failed to load data from {}: {}", full_path.display(), e)
})?
.read_to_end(data)?;
}
_ => (),
if let ast::DataValues::File {
ref path,
ref mut data,
} = values
{
let mut full_path = script_dir.to_path_buf();
full_path.push(path);
File::open(&full_path)
.map_err(|e| {
anyhow!("Failed to load data from {}: {}", full_path.display(), e)
})?
.read_to_end(data)?;
dependencies.insert(full_path);
}
}
}

View File

@@ -1,8 +1,11 @@
use crate::ast::Type;
use enc::MemArg;
use std::collections::HashMap;
use wasm_encoder as enc;
pub struct Intrinsics(HashMap<String, HashMap<Vec<Type>, (Type, enc::Instruction<'static>)>>);
pub struct Intrinsics(
HashMap<String, HashMap<Vec<Type>, (Option<Type>, enc::Instruction<'static>)>>,
);
impl Intrinsics {
pub fn new() -> Intrinsics {
@@ -11,14 +14,11 @@ impl Intrinsics {
i
}
pub fn find_types(
&self,
name: &str,
) -> Option<HashMap<Vec<Type>, Option<Type>>> {
pub fn find_types(&self, name: &str) -> Option<HashMap<Vec<Type>, Option<Type>>> {
self.0.get(name).map(|types| {
types
.iter()
.map(|(params, (ret, _))| (params.clone(), Some(*ret)))
.map(|(params, (ret, _))| (params.clone(), *ret))
.collect()
})
}
@@ -33,87 +33,121 @@ impl Intrinsics {
fn add_instructions(&mut self) {
use enc::Instruction as I;
use Type::*;
self.inst("i32.rotl", &[I32, I32], I32, I::I32Rotl);
self.inst("i32.rotr", &[I32, I32], I32, I::I32Rotr);
self.inst("i32.clz", &[I32], I32, I::I32Clz);
self.inst("i32.ctz", &[I32], I32, I::I32Ctz);
self.inst("i32.popcnt", &[I32], I32, I::I32Popcnt);
self.inst("i32.rotl", &[I32, I32], Some(I32), I::I32Rotl);
self.inst("i32.rotr", &[I32, I32], Some(I32), I::I32Rotr);
self.inst("i32.clz", &[I32], Some(I32), I::I32Clz);
self.inst("i32.ctz", &[I32], Some(I32), I::I32Ctz);
self.inst("i32.popcnt", &[I32], Some(I32), I::I32Popcnt);
self.inst("i64.rotl", &[I64, I64], I64, I::I64Rotl);
self.inst("i64.rotr", &[I64, I64], I64, I::I64Rotr);
self.inst("i64.clz", &[I64], I64, I::I64Clz);
self.inst("i64.ctz", &[I64], I64, I::I64Ctz);
self.inst("i64.popcnt", &[I64], I64, I::I64Popcnt);
self.inst("i64.rotl", &[I64, I64], Some(I64), I::I64Rotl);
self.inst("i64.rotr", &[I64, I64], Some(I64), I::I64Rotr);
self.inst("i64.clz", &[I64], Some(I64), I::I64Clz);
self.inst("i64.ctz", &[I64], Some(I64), I::I64Ctz);
self.inst("i64.popcnt", &[I64], Some(I64), I::I64Popcnt);
self.inst("f32/sqrt", &[F32], F32, I::F32Sqrt);
self.inst("f32/min", &[F32, F32], F32, I::F32Min);
self.inst("f32/max", &[F32, F32], F32, I::F32Max);
self.inst("f32/ceil", &[F32], F32, I::F32Ceil);
self.inst("f32/floor", &[F32], F32, I::F32Floor);
self.inst("f32/trunc", &[F32], F32, I::F32Trunc);
self.inst("f32/nearest", &[F32], F32, I::F32Nearest);
self.inst("f32/abs", &[F32], F32, I::F32Abs);
self.inst("f32.copysign", &[F32, F32], F32, I::F32Copysign);
self.inst("f32/sqrt", &[F32], Some(F32), I::F32Sqrt);
self.inst("f32/min", &[F32, F32], Some(F32), I::F32Min);
self.inst("f32/max", &[F32, F32], Some(F32), I::F32Max);
self.inst("f32/ceil", &[F32], Some(F32), I::F32Ceil);
self.inst("f32/floor", &[F32], Some(F32), I::F32Floor);
self.inst("f32/trunc", &[F32], Some(F32), I::F32Trunc);
self.inst("f32/nearest", &[F32], Some(F32), I::F32Nearest);
self.inst("f32/abs", &[F32], Some(F32), I::F32Abs);
self.inst("f32.copysign", &[F32, F32], Some(F32), I::F32Copysign);
self.inst("f64/sqrt", &[F64], F64, I::F64Sqrt);
self.inst("f64/min", &[F64, F64], F64, I::F64Min);
self.inst("f64/max", &[F64, F64], F64, I::F64Max);
self.inst("f64/ceil", &[F64], F64, I::F64Ceil);
self.inst("f64/floor", &[F64], F64, I::F64Floor);
self.inst("f64/trunc", &[F64], F64, I::F64Trunc);
self.inst("f64/nearest", &[F64], F64, I::F64Nearest);
self.inst("f64/abs", &[F64], F64, I::F64Abs);
self.inst("f64.copysign", &[F64, F64], F64, I::F64Copysign);
self.inst("f64/sqrt", &[F64], Some(F64), I::F64Sqrt);
self.inst("f64/min", &[F64, F64], Some(F64), I::F64Min);
self.inst("f64/max", &[F64, F64], Some(F64), I::F64Max);
self.inst("f64/ceil", &[F64], Some(F64), I::F64Ceil);
self.inst("f64/floor", &[F64], Some(F64), I::F64Floor);
self.inst("f64/trunc", &[F64], Some(F64), I::F64Trunc);
self.inst("f64/nearest", &[F64], Some(F64), I::F64Nearest);
self.inst("f64/abs", &[F64], Some(F64), I::F64Abs);
self.inst("f64.copysign", &[F64, F64], Some(F64), I::F64Copysign);
self.inst("i32.wrap_i64", &[I64], I32, I::I32WrapI64);
self.inst("i64.extend_i32_s", &[I32], I64, I::I64ExtendI32S);
self.inst("i64.extend_i32_u", &[I32], I64, I::I64ExtendI32U);
self.inst("i32.wrap_i64", &[I64], Some(I32), I::I32WrapI64);
self.inst("i64.extend_i32_s", &[I32], Some(I64), I::I64ExtendI32S);
self.inst("i64.extend_i32_u", &[I32], Some(I64), I::I64ExtendI32U);
self.inst("i32.trunc_f32_s", &[F32], I32, I::I32TruncF32S);
self.inst("i32.trunc_f64_s", &[F64], I32, I::I32TruncF64S);
self.inst("i64.trunc_f32_s", &[F32], I64, I::I64TruncF32S);
self.inst("i64.trunc_f64_s", &[F64], I64, I::I64TruncF64S);
self.inst("i32.trunc_f32_s", &[F32], Some(I32), I::I32TruncF32S);
self.inst("i32.trunc_f64_s", &[F64], Some(I32), I::I32TruncF64S);
self.inst("i64.trunc_f32_s", &[F32], Some(I64), I::I64TruncF32S);
self.inst("i64.trunc_f64_s", &[F64], Some(I64), I::I64TruncF64S);
self.inst("i32.trunc_f32_u", &[F32], I32, I::I32TruncF32U);
self.inst("i32.trunc_f64_u", &[F64], I32, I::I32TruncF64U);
self.inst("i64.trunc_f32_u", &[F32], I64, I::I64TruncF32U);
self.inst("i64.trunc_f64_u", &[F64], I64, I::I64TruncF64U);
self.inst("i32.trunc_f32_u", &[F32], Some(I32), I::I32TruncF32U);
self.inst("i32.trunc_f64_u", &[F64], Some(I32), I::I32TruncF64U);
self.inst("i64.trunc_f32_u", &[F32], Some(I64), I::I64TruncF32U);
self.inst("i64.trunc_f64_u", &[F64], Some(I64), I::I64TruncF64U);
self.inst("f32.demote_f64", &[F64], F32, I::F32DemoteF64);
self.inst("f64.promote_f32", &[F32], F64, I::F64PromoteF32);
self.inst("f32.demote_f64", &[F64], Some(F32), I::F32DemoteF64);
self.inst("f64.promote_f32", &[F32], Some(F64), I::F64PromoteF32);
self.inst("f32.convert_i32_s", &[I32], F32, I::F32ConvertI32S);
self.inst("f32.convert_i64_s", &[I64], F32, I::F32ConvertI32S);
self.inst("f64.convert_i32_s", &[I32], F64, I::F32ConvertI32S);
self.inst("f64.convert_i64_s", &[I64], F64, I::F32ConvertI32S);
self.inst("f32.convert_i32_s", &[I32], Some(F32), I::F32ConvertI32S);
self.inst("f32.convert_i64_s", &[I64], Some(F32), I::F32ConvertI32S);
self.inst("f64.convert_i32_s", &[I32], Some(F64), I::F32ConvertI32S);
self.inst("f64.convert_i64_s", &[I64], Some(F64), I::F32ConvertI32S);
self.inst("f32.convert_i32_u", &[I32], F32, I::F32ConvertI32U);
self.inst("f32.convert_i64_u", &[I64], F32, I::F32ConvertI32U);
self.inst("f64.convert_i32_u", &[I32], F64, I::F32ConvertI32U);
self.inst("f64.convert_i64_u", &[I64], F64, I::F32ConvertI32U);
self.inst("f32.convert_i32_u", &[I32], Some(F32), I::F32ConvertI32U);
self.inst("f32.convert_i64_u", &[I64], Some(F32), I::F32ConvertI32U);
self.inst("f64.convert_i32_u", &[I32], Some(F64), I::F32ConvertI32U);
self.inst("f64.convert_i64_u", &[I64], Some(F64), I::F32ConvertI32U);
self.inst("i32.reinterpret_f32", &[F32], I32, I::I32ReinterpretF32);
self.inst("i64.reinterpret_f64", &[F64], I64, I::I64ReinterpretF64);
self.inst("f32.reinterpret_i32", &[I32], F32, I::F32ReinterpretI32);
self.inst("f64.reinterpret_i64", &[I64], F64, I::F64ReinterpretI64);
self.inst(
"i32.reinterpret_f32",
&[F32],
Some(I32),
I::I32ReinterpretF32,
);
self.inst(
"i64.reinterpret_f64",
&[F64],
Some(I64),
I::I64ReinterpretF64,
);
self.inst(
"f32.reinterpret_i32",
&[I32],
Some(F32),
I::F32ReinterpretI32,
);
self.inst(
"f64.reinterpret_i64",
&[I64],
Some(F64),
I::F64ReinterpretI64,
);
self.inst("i32.extend8_s", &[I32], I32, I::I32Extend8S);
self.inst("i32.extend16_s", &[I32], I32, I::I32Extend16S);
self.inst("i64.extend8_s", &[I64], I64, I::I64Extend8S);
self.inst("i64.extend16_s", &[I64], I64, I::I64Extend16S);
self.inst("i64.extend32_s", &[I64], I64, I::I64Extend32S);
self.inst("i32.extend8_s", &[I32], Some(I32), I::I32Extend8S);
self.inst("i32.extend16_s", &[I32], Some(I32), I::I32Extend16S);
self.inst("i64.extend8_s", &[I64], Some(I64), I::I64Extend8S);
self.inst("i64.extend16_s", &[I64], Some(I64), I::I64Extend16S);
self.inst("i64.extend32_s", &[I64], Some(I64), I::I64Extend32S);
self.inst("i32.trunc_sat_f32_s", &[F32], I32, I::I32TruncSatF32S);
self.inst("i32.trunc_sat_f32_u", &[F32], I32, I::I32TruncSatF32U);
self.inst("i32.trunc_sat_f64_s", &[F64], I32, I::I32TruncSatF64S);
self.inst("i32.trunc_sat_f64_u", &[F64], I32, I::I32TruncSatF64U);
self.inst("i64.trunc_sat_f32_s", &[F32], I64, I::I64TruncSatF32S);
self.inst("i64.trunc_sat_f32_u", &[F32], I64, I::I64TruncSatF32U);
self.inst("i64.trunc_sat_f64_s", &[F64], I64, I::I64TruncSatF64S);
self.inst("i64.trunc_sat_f64_u", &[F64], I64, I::I64TruncSatF64U);
self.inst("i32.trunc_sat_f32_s", &[F32], Some(I32), I::I32TruncSatF32S);
self.inst("i32.trunc_sat_f32_u", &[F32], Some(I32), I::I32TruncSatF32U);
self.inst("i32.trunc_sat_f64_s", &[F64], Some(I32), I::I32TruncSatF64S);
self.inst("i32.trunc_sat_f64_u", &[F64], Some(I32), I::I32TruncSatF64U);
self.inst("i64.trunc_sat_f32_s", &[F32], Some(I64), I::I64TruncSatF32S);
self.inst("i64.trunc_sat_f32_u", &[F32], Some(I64), I::I64TruncSatF32U);
self.inst("i64.trunc_sat_f64_s", &[F64], Some(I64), I::I64TruncSatF64S);
self.inst("i64.trunc_sat_f64_u", &[F64], Some(I64), I::I64TruncSatF64U);
self.inst(
"memory.copy",
&[I32, I32, I32],
None,
I::MemoryCopy { src: 0, dst: 0 },
);
self.inst("memory.fill", &[I32, I32, I32], None, I::MemoryFill(0));
}
fn inst(&mut self, name: &str, params: &[Type], ret: Type, ins: enc::Instruction<'static>) {
fn inst(
&mut self,
name: &str,
params: &[Type],
ret: Option<Type>,
ins: enc::Instruction<'static>,
) {
if let Some(slash_idx) = name.find('/') {
self.insert(name[(slash_idx + 1)..].to_string(), params, ret, &ins);
let mut full_name = name[..slash_idx].to_string();
@@ -129,7 +163,7 @@ impl Intrinsics {
&mut self,
name: String,
params: &[Type],
ret: Type,
ret: Option<Type>,
ins: &enc::Instruction<'static>,
) {
self.0
@@ -137,4 +171,65 @@ impl Intrinsics {
.or_default()
.insert(params.to_vec(), (ret, ins.clone()));
}
pub fn find_load(&self, name: &str) -> Option<MemInstruction> {
use enc::Instruction as I;
use Type::*;
let ins = match name {
"i32.load" => MemInstruction::new(I32, I::I32Load, 2),
"i32.load8_s" => MemInstruction::new(I32, I::I32Load8_S, 0),
"i32.load8_u" => MemInstruction::new(I32, I::I32Load8_U, 0),
"i32.load16_s" => MemInstruction::new(I32, I::I32Load16_S, 1),
"i32.load16_u" => MemInstruction::new(I32, I::I32Load16_U, 1),
"i64.load" => MemInstruction::new(I64, I::I64Load, 3),
"i64.load8_s" => MemInstruction::new(I64, I::I64Load8_S, 0),
"i64.load8_u" => MemInstruction::new(I64, I::I64Load8_U, 0),
"i64.load16_s" => MemInstruction::new(I64, I::I64Load16_S, 1),
"i64.load16_u" => MemInstruction::new(I64, I::I64Load16_U, 1),
"i64.load32_s" => MemInstruction::new(I64, I::I64Load32_S, 2),
"i64.load32_u" => MemInstruction::new(I64, I::I64Load32_U, 2),
"f32.load" => MemInstruction::new(F32, I::F32Load, 2),
"f64.load" => MemInstruction::new(F64, I::F64Load, 3),
_ => return None,
};
return Some(ins);
}
pub fn find_store(&self, name: &str) -> Option<MemInstruction> {
use enc::Instruction as I;
use Type::*;
let ins = match name {
"i32.store" => MemInstruction::new(I32, I::I32Store, 2),
"i32.store8" => MemInstruction::new(I32, I::I32Store8, 0),
"i32.store16" => MemInstruction::new(I32, I::I32Store16, 1),
"i64.store" => MemInstruction::new(I64, I::I64Store, 3),
"i64.store8" => MemInstruction::new(I64, I::I64Store8, 0),
"i64.store16" => MemInstruction::new(I64, I::I64Store16, 1),
"i64.store32" => MemInstruction::new(I64, I::I64Store32, 2),
"f32.store" => MemInstruction::new(F32, I::F32Store, 2),
"f64.store" => MemInstruction::new(F64, I::F64Store, 3),
_ => return None,
};
return Some(ins);
}
}
pub struct MemInstruction {
pub type_: Type,
pub instruction: fn(MemArg) -> enc::Instruction<'static>,
pub natural_alignment: u32,
}
impl MemInstruction {
fn new(
type_: Type,
instruction: fn(MemArg) -> enc::Instruction<'static>,
natural_alignment: u32,
) -> MemInstruction {
MemInstruction {
type_,
instruction,
natural_alignment,
}
}
}

View File

@@ -1,7 +1,8 @@
use anyhow::{bail, Result};
use parser::Sources;
use std::collections::HashSet;
use std::ffi::OsStr;
use std::io::prelude::*;
use std::{fs::File, path::Path};
use std::path::{Path, PathBuf};
mod ast;
mod constfold;
@@ -11,8 +12,6 @@ mod intrinsics;
mod parser;
mod typecheck;
type Span = std::ops::Range<usize>;
#[derive(Default)]
pub struct Options {
pub(crate) debug: bool,
@@ -20,40 +19,80 @@ pub struct Options {
impl Options {
pub fn with_debug(self) -> Self {
Options {
debug: true,
..self
Options { debug: true }
}
}
pub struct CompiledModule {
pub wasm: Vec<u8>,
pub dependencies: Vec<PathBuf>,
}
pub fn compile_file<P: AsRef<Path>>(path: P, options: Options) -> (Result<Vec<u8>>, Vec<PathBuf>) {
fn compile_file_inner(
path: &Path,
options: Options,
dependencies: &mut HashSet<PathBuf>,
) -> Result<Vec<u8>> {
let mut script = ast::Script::default();
let mut sources = Sources::new();
let mut pending_files = vec![(path.to_path_buf(), None)];
while let Some((path, span)) = pending_files.pop() {
match sources.add(&path) {
Ok((id, true)) => {
dependencies.insert(path.clone());
let mut new_script = match parser::parse(&sources, id) {
Ok(script) => script,
Err(_) => bail!("Parse failed"),
};
includes::resolve_includes(&mut new_script, dependencies, &path)?;
for include in std::mem::take(&mut new_script.includes) {
let mut path = path
.parent()
.expect("Script path has no parent")
.to_path_buf();
path.push(include.path);
pending_files.push((path, Some(include.span)));
}
script.merge(new_script);
}
Ok((_, false)) => (), // already parsed this include
Err(err) => {
if let Some(span) = span {
let _ = typecheck::report_error(&err.to_string(), &span, &sources);
} else {
eprintln!("Failed to load script {}: {}", path.display(), err);
}
bail!("Parse failed");
}
}
}
if constfold::fold_script(&mut script, &sources).is_err() {
bail!("Constant folding failed");
}
if typecheck::tc_script(&mut script, &sources).is_err() {
bail!("Type check failed");
}
let wasm = emit::emit(
&script,
&path
.file_stem()
.unwrap_or_else(|| OsStr::new("unknown"))
.to_string_lossy(),
&options,
);
Ok(wasm)
}
}
pub fn compile_file<P: AsRef<Path>>(path: P, options: Options) -> Result<Vec<u8>> {
let path = path.as_ref();
let mut input = String::new();
File::open(path)?.read_to_string(&mut input)?;
compile_str(&input, path, options)
}
pub fn compile_str(input: &str, path: &Path, options: Options) -> Result<Vec<u8>> {
let mut script = match parser::parse(&input) {
Ok(script) => script,
Err(_) => bail!("Parse failed"),
};
includes::resolve_includes(&mut script, path)?;
constfold::fold_script(&mut script);
if let Err(_) = typecheck::tc_script(&mut script, &input) {
bail!("Type check failed");
}
let wasm = emit::emit(
&script,
&path
.file_stem()
.unwrap_or_else(|| OsStr::new("unknown"))
.to_string_lossy(),
&options,
);
Ok(wasm)
let mut dependencies = HashSet::new();
let result = compile_file_inner(path.as_ref(), options, &mut dependencies);
(result, dependencies.into_iter().collect())
}

View File

@@ -15,7 +15,7 @@ fn main() -> Result<()> {
let mut filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let wasm = compile_file(&filename, options)?;
let wasm = compile_file(&filename, options).0?;
wasmparser::validate(&wasm)?;

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
use ariadne::{Color, Label, Report, ReportKind, Source};
use ariadne::{Color, Label, Report, ReportKind};
use std::collections::HashMap;
use crate::ast;
use crate::ast::{self, MemSize};
use crate::intrinsics::Intrinsics;
use crate::Span;
use crate::parser::{Sources, Span};
use ast::Type::*;
type Result<T> = std::result::Result<T, ()>;
@@ -15,9 +15,9 @@ struct Var {
}
type Vars = HashMap<String, Var>;
pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
pub fn tc_script(script: &mut ast::Script, sources: &Sources) -> Result<()> {
let mut context = Context {
source,
sources,
global_vars: HashMap::new(),
functions: HashMap::new(),
locals: ast::Locals::default(),
@@ -41,7 +41,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
"Global already defined",
&import.span,
span,
source,
sources,
);
} else {
context.global_vars.insert(
@@ -64,7 +64,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
"Function already defined",
&import.span,
&fnc.span,
source,
sources,
);
} else {
context.functions.insert(
@@ -83,12 +83,12 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
for v in &mut script.global_vars {
if let Some(Var { span, .. }) = context.global_vars.get(&v.name) {
result = report_duplicate_definition("Global already defined", &v.span, span, source);
result = report_duplicate_definition("Global already defined", &v.span, span, sources);
} else {
tc_const(&mut v.value, source)?;
tc_const(&mut v.value, sources)?;
if v.type_ != v.value.type_ {
if v.type_.is_some() {
result = type_mismatch(v.type_, &v.span, v.value.type_, &v.value.span, source);
result = type_mismatch(v.type_, &v.span, v.value.type_, &v.value.span, sources);
} else {
v.type_ = v.value.type_;
}
@@ -104,11 +104,26 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
}
}
for c in &mut script.consts {
tc_const(&mut c.value, sources)?;
if c.value.type_ != c.type_ {
if c.type_.is_some() {
result = type_mismatch(c.type_, &c.span, c.value.type_, &c.value.span, sources);
} else {
c.type_ = c.value.type_;
}
}
}
for f in &script.functions {
let params = f.params.iter().map(|(_, t)| *t).collect();
if let Some(fnc) = context.functions.get(&f.name) {
result =
report_duplicate_definition("Function already defined", &f.span, &fnc.span, source);
result = report_duplicate_definition(
"Function already defined",
&f.span,
&fnc.span,
sources,
);
} else {
context.functions.insert(
f.name.clone(),
@@ -132,7 +147,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
.or_else(|| context.global_vars.get(name).map(|v| &v.span))
{
result =
report_duplicate_definition("Variable already defined", &f.span, span, source);
report_duplicate_definition("Variable already defined", &f.span, span, sources);
} else {
context.local_vars.insert(
name.clone(),
@@ -160,10 +175,10 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
context.locals.locals[index].index = Some((locals_start + id) as u32);
}
f.locals = std::mem::replace(&mut context.locals, ast::Locals::default());
f.locals = std::mem::take(&mut context.locals);
if f.body.type_ != f.type_ {
result = type_mismatch(f.type_, &f.span, f.body.type_, &f.body.span, source);
result = type_mismatch(f.type_, &f.span, f.body.type_, &f.body.span, sources);
}
}
@@ -171,7 +186,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
for f in &script.functions {
if f.start {
if !f.params.is_empty() || f.type_.is_some() {
Report::build(ReportKind::Error, (), f.span.start)
Report::build(ReportKind::Error, f.span.0, f.span.1.start)
.with_message("Start function can't have params or a return value")
.with_label(
Label::new(f.span.clone())
@@ -179,7 +194,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
.with_color(Color::Red),
)
.finish()
.eprint(Source::from(source))
.eprint(sources)
.unwrap();
result = Err(());
@@ -189,7 +204,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
"Start function already defined",
&f.span,
&prev.span,
source,
sources,
);
} else {
start_function = Some(f);
@@ -198,14 +213,14 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
}
for data in &mut script.data {
tc_const(&mut data.offset, source)?;
tc_const(&mut data.offset, sources)?;
if data.offset.type_ != Some(I32) {
result = type_mismatch(
Some(I32),
&data.offset.span,
data.offset.type_,
&data.offset.span,
source,
sources,
);
}
for values in &mut data.data {
@@ -220,14 +235,14 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
ast::DataType::F64 => ast::Type::F64,
};
for value in values {
tc_const(value, source)?;
tc_const(value, sources)?;
if value.type_ != Some(needed_type) {
result = type_mismatch(
Some(needed_type),
&value.span,
value.type_,
&value.span,
source,
sources,
);
}
}
@@ -247,7 +262,7 @@ struct FunctionType {
}
struct Context<'a> {
source: &'a str,
sources: &'a Sources,
global_vars: Vars,
functions: HashMap<String, FunctionType>,
locals: ast::Locals,
@@ -294,13 +309,13 @@ impl LocalVars {
}
}
fn report_duplicate_definition(
pub fn report_duplicate_definition(
msg: &str,
span: &Span,
prev_span: &Span,
source: &str,
sources: &Sources,
) -> Result<()> {
Report::build(ReportKind::Error, (), span.start)
Report::build(ReportKind::Error, span.0, span.1.start)
.with_message(msg)
.with_label(
Label::new(span.clone())
@@ -313,7 +328,7 @@ fn report_duplicate_definition(
.with_color(Color::Yellow),
)
.finish()
.eprint(Source::from(source))
.eprint(sources)
.unwrap();
Err(())
}
@@ -323,9 +338,9 @@ fn type_mismatch(
span1: &Span,
type2: Option<ast::Type>,
span2: &Span,
source: &str,
sources: &Sources,
) -> Result<()> {
Report::build(ReportKind::Error, (), span2.start)
Report::build(ReportKind::Error, span2.0, span2.1.start)
.with_message("Type mismatch")
.with_label(
Label::new(span1.clone())
@@ -333,7 +348,7 @@ fn type_mismatch(
"Expected type {:?}...",
type1
.map(|t| format!("{:?}", t))
.unwrap_or("void".to_string())
.unwrap_or_else(|| "void".to_string())
))
.with_color(Color::Yellow),
)
@@ -343,70 +358,48 @@ fn type_mismatch(
"...but found type {}",
type2
.map(|t| format!("{:?}", t))
.unwrap_or("void".to_string())
.unwrap_or_else(|| "void".to_string())
))
.with_color(Color::Red),
)
.finish()
.eprint(Source::from(source))
.eprint(sources)
.unwrap();
Err(())
}
fn expected_type(span: &Span, source: &str) -> Result<()> {
Report::build(ReportKind::Error, (), span.start)
.with_message("Expected value but found expression of type void")
pub fn report_error(msg: &str, span: &Span, sources: &Sources) -> Result<()> {
Report::build(ReportKind::Error, span.0, span.1.start)
.with_message(msg)
.with_label(
Label::new(span.clone())
.with_message("Expected value but found expression of type void")
.with_message(msg)
.with_color(Color::Red),
)
.finish()
.eprint(Source::from(source))
.eprint(sources)
.unwrap();
Err(())
}
fn unknown_variable(span: &Span, source: &str) -> Result<()> {
Report::build(ReportKind::Error, (), span.start)
.with_message("Unknown variable")
.with_label(
Label::new(span.clone())
.with_message("Unknown variable")
.with_color(Color::Red),
)
.finish()
.eprint(Source::from(source))
.unwrap();
Err(())
fn expected_type(span: &Span, sources: &Sources) -> Result<()> {
report_error(
"Expected value but found expression of type void",
span,
sources,
)
}
fn immutable_assign(span: &Span, source: &str) -> Result<()> {
Report::build(ReportKind::Error, (), span.start)
.with_message("Trying to assign to immutable variable")
.with_label(
Label::new(span.clone())
.with_message("Trying to assign to immutable variable")
.with_color(Color::Red),
)
.finish()
.eprint(Source::from(source))
.unwrap();
Err(())
fn unknown_variable(span: &Span, sources: &Sources) -> Result<()> {
report_error("Unknown variable", span, sources)
}
fn missing_label(span: &Span, source: &str) -> Result<()> {
Report::build(ReportKind::Error, (), span.start)
.with_message("Label not found")
.with_label(
Label::new(span.clone())
.with_message("Label not found")
.with_color(Color::Red),
)
.finish()
.eprint(Source::from(source))
.unwrap();
return Err(());
fn immutable_assign(span: &Span, sources: &Sources) -> Result<()> {
report_error("Trying to assign to immutable variable", span, sources)
}
fn missing_label(span: &Span, sources: &Sources) -> Result<()> {
report_error("Label not found", span, sources)
}
fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()> {
@@ -445,11 +438,11 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&expr.span,
value.type_,
&value.span,
context.source,
context.sources,
);
}
} else if value.type_.is_none() {
return expected_type(&value.span, context.source);
return expected_type(&value.span, context.sources);
} else {
*type_ = value.type_;
}
@@ -471,23 +464,17 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
*local_id = Some(id);
context.local_vars.insert(name.clone(), id);
} else {
Report::build(ReportKind::Error, (), expr.span.start)
.with_message("Type missing")
.with_label(
Label::new(expr.span.clone())
.with_message("Type missing")
.with_color(Color::Red),
)
.finish()
.eprint(Source::from(context.source))
.unwrap();
return Err(());
return report_error("Type missing", &expr.span, context.sources);
}
None
}
ast::Expr::Peek(ref mut mem_location) => {
tc_mem_location(context, mem_location)?;
Some(I32)
let ty = match mem_location.size {
MemSize::Float => F32,
_ => I32,
};
Some(ty)
}
ast::Expr::Poke {
ref mut mem_location,
@@ -495,13 +482,17 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
} => {
tc_mem_location(context, mem_location)?;
tc_expression(context, value)?;
if value.type_ != Some(I32) {
let ty = match mem_location.size {
MemSize::Float => F32,
_ => I32,
};
if value.type_ != Some(ty) {
return type_mismatch(
Some(I32),
Some(ty),
&expr.span,
value.type_,
&value.span,
context.source,
context.sources,
);
}
None
@@ -513,7 +504,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
ast::Expr::UnaryOp { op, ref mut value } => {
tc_expression(context, value)?;
if value.type_.is_none() {
return expected_type(&value.span, context.source);
return expected_type(&value.span, context.sources);
}
use ast::Type::*;
use ast::UnaryOp::*;
@@ -526,7 +517,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&expr.span,
value.type_,
&value.span,
context.source,
context.sources,
)
}
})
@@ -545,11 +536,11 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&left.span,
right.type_,
&right.span,
context.source,
context.sources,
);
}
} else {
return expected_type(&left.span, context.source);
return expected_type(&left.span, context.sources);
}
use ast::BinOp::*;
match op {
@@ -561,7 +552,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&left.span,
left.type_,
&left.span,
context.source,
context.sources,
);
} else {
left.type_
@@ -575,7 +566,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&left.span,
left.type_,
&left.span,
context.source,
context.sources,
);
} else {
Some(I32)
@@ -593,7 +584,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
} else if let Some(&Var { type_, .. }) = context.global_vars.get(name) {
Some(type_)
} else {
return unknown_variable(&expr.span, context.source);
return unknown_variable(&expr.span, context.sources);
}
}
ast::Expr::Assign {
@@ -607,7 +598,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
*local_id = Some(id);
let local = &context.locals[id];
if local.index.is_none() {
return immutable_assign(&expr.span, context.source);
return immutable_assign(&expr.span, context.sources);
}
(local.type_, &local.span)
} else if let Some(&Var {
@@ -617,15 +608,15 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
}) = context.global_vars.get(name)
{
if !mutable {
return immutable_assign(&expr.span, context.source);
return immutable_assign(&expr.span, context.sources);
}
(type_, span)
} else {
return unknown_variable(&expr.span, context.source);
return unknown_variable(&expr.span, context.sources);
};
if value.type_ != Some(type_) {
return type_mismatch(Some(type_), span, value.type_, &value.span, context.source);
return type_mismatch(Some(type_), span, value.type_, &value.span, context.sources);
}
None
}
@@ -640,7 +631,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
let local = &context.locals[id];
if local.index.is_none() {
return immutable_assign(&expr.span, context.source);
return immutable_assign(&expr.span, context.sources);
}
if value.type_ != Some(local.type_) {
@@ -649,13 +640,13 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&local.span,
value.type_,
&value.span,
context.source,
context.sources,
);
}
Some(local.type_)
} else {
return unknown_variable(&expr.span, context.source);
return unknown_variable(&expr.span, context.sources);
}
}
ast::Expr::Loop {
@@ -676,13 +667,13 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
context.block_stack.pop();
if block.type_ != None {
// TODO: implement, requires branches to optionally provide values
return type_mismatch(None, &expr.span, block.type_, &block.span, context.source);
return type_mismatch(None, &expr.span, block.type_, &block.span, context.sources);
}
None
}
ast::Expr::Branch(ref label) => {
if !context.block_stack.contains(label) {
return missing_label(&expr.span, context.source);
return missing_label(&expr.span, context.sources);
}
None
}
@@ -697,11 +688,11 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&expr.span,
condition.type_,
&condition.span,
context.source,
context.sources,
);
}
if !context.block_stack.contains(label) {
return missing_label(&expr.span, context.source);
return missing_label(&expr.span, context.sources);
}
None
}
@@ -711,7 +702,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
} => {
tc_expression(context, value)?;
if value.type_.is_none() {
return expected_type(&expr.span, context.source);
return expected_type(&expr.span, context.sources);
}
Some(type_)
}
@@ -722,10 +713,30 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
for param in params.iter_mut() {
tc_expression(context, param)?;
if param.type_.is_none() {
return expected_type(&param.span, context.source);
return expected_type(&param.span, context.sources);
}
}
if let Some(type_map) = context
if let Some(load) = context.intrinsics.find_load(name) {
tc_memarg(context, params.as_mut_slice(), &expr.span)?;
Some(load.type_)
} else if let Some(store) = context.intrinsics.find_store(name) {
if let Some(value) = params.first_mut() {
tc_expression(context, value)?;
if value.type_ != Some(store.type_) {
type_mismatch(
Some(store.type_),
&expr.span,
value.type_,
&value.span,
context.sources,
)?;
}
} else {
return report_error("Missing parameters", &expr.span, context.sources);
}
tc_memarg(context, &mut params[1..], &expr.span)?;
None
} else if let Some(type_map) = context
.functions
.get(name)
.map(|fnc| HashMap::from_iter([(fnc.params.clone(), fnc.type_)]))
@@ -736,8 +747,9 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
{
*rtype
} else {
let mut report = Report::build(ReportKind::Error, (), expr.span.start)
.with_message("No matching function found");
let mut report =
Report::build(ReportKind::Error, expr.span.0, expr.span.1.start)
.with_message("No matching function found");
for (params, rtype) in type_map {
let param_str: Vec<_> = params.into_iter().map(|t| t.to_string()).collect();
let msg = format!(
@@ -752,24 +764,15 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
);
report = report.with_label(Label::new(expr.span.clone()).with_message(msg));
}
report
.finish()
.eprint(Source::from(context.source))
.unwrap();
report.finish().eprint(context.sources).unwrap();
return Err(());
}
} else {
Report::build(ReportKind::Error, (), expr.span.start)
.with_message(format!("Unknown function {}", name))
.with_label(
Label::new(expr.span.clone())
.with_message(format!("Unknown function {}", name))
.with_color(Color::Red),
)
.finish()
.eprint(Source::from(context.source))
.unwrap();
return Err(());
return report_error(
&format!("Unknown function {}", name),
&expr.span,
context.sources,
);
}
}
ast::Expr::Select {
@@ -786,7 +789,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&condition.span,
condition.type_,
&condition.span,
context.source,
context.sources,
);
}
if if_true.type_.is_some() {
@@ -796,11 +799,11 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&if_true.span,
if_false.type_,
&if_false.span,
context.source,
context.sources,
);
}
} else {
return expected_type(&if_true.span, context.source);
return expected_type(&if_true.span, context.sources);
}
if_true.type_
}
@@ -819,7 +822,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&if_true.span,
if_false.type_,
&if_false.span,
context.source,
context.sources,
);
} else {
if_true.type_
@@ -837,7 +840,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
&expr.span,
value.type_,
&value.span,
context.source,
context.sources,
);
}
}
@@ -861,14 +864,14 @@ fn tc_mem_location<'a>(
mem_location: &mut ast::MemoryLocation,
) -> Result<()> {
tc_expression(context, &mut mem_location.left)?;
tc_const(&mut mem_location.right, context.source)?;
tc_const(&mut mem_location.right, context.sources)?;
if mem_location.left.type_ != Some(I32) {
return type_mismatch(
Some(I32),
&mem_location.left.span,
mem_location.left.type_,
&mem_location.left.span,
context.source,
context.sources,
);
}
if mem_location.right.type_ != Some(I32) {
@@ -877,32 +880,53 @@ fn tc_mem_location<'a>(
&mem_location.right.span,
mem_location.right.type_,
&mem_location.right.span,
context.source,
context.sources,
);
}
Ok(())
}
fn tc_const(expr: &mut ast::Expression, source: &str) -> Result<()> {
fn tc_const(expr: &mut ast::Expression, sources: &Sources) -> Result<()> {
use ast::Expr::*;
expr.type_ = Some(match expr.expr {
I32Const(_) => I32,
I64Const(_) => I64,
F32Const(_) => F32,
F64Const(_) => F64,
_ => {
Report::build(ReportKind::Error, (), expr.span.start)
.with_message("Expected constant value")
.with_label(
Label::new(expr.span.clone())
.with_message("Expected constant value")
.with_color(Color::Red),
)
.finish()
.eprint(Source::from(source))
.unwrap();
return Err(());
}
_ => return report_error("Expected constant value", &expr.span, sources),
});
Ok(())
}
fn tc_memarg(context: &mut Context, params: &mut [ast::Expression], span: &Span) -> Result<()> {
if params.is_empty() || params.len() > 3 {
let msg = if params.is_empty() {
"Missing base address parameter"
} else {
"Too many MemArg parameters"
};
return report_error(msg, span, context.sources);
}
for (index, param) in params.iter_mut().enumerate() {
tc_expression(context, param)?;
if param.type_ != Some(I32) {
return type_mismatch(Some(I32), &span, param.type_, &param.span, context.sources);
}
if index > 0 {
tc_const(param, context.sources)?;
}
if index == 2 {
let align = param.const_i32();
if align < 0 || align > 4 {
return report_error(
&format!("Alignment {} out of range (0-4)", align),
&param.span,
context.sources,
);
}
}
}
Ok(())
}

15
test/xorshift.cwa Normal file
View File

@@ -0,0 +1,15 @@
// simple test to see whether lazy/inline chains with the same variable compile correctly
fn xorshift(x: i32) -> i32 {
let lazy x = x ^ (x << 13);
let lazy x = x ^ (x #>> 17);
let inline x = x ^ (x << 5);
x
}
fn xorshift2(x: i32) -> i32 {
x ^= x << 13;
x ^= x #>> 17;
x ^= x << 5;
x
}