Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ include "platform_imports.cwa"

### Types

There are four types in WebAssembly and therefore CurlyWas:
There are five types in WebAssembly and therefore CurlyWas:

* `i32`: 32bit integer
* `i64`: 64bit integer
* `f32`: 32bit float
* `f64`: 64bit float
* `v128`: 128bit vector

There are no unsigned types, but there are unsigned operators where it makes a difference.

Expand All @@ -83,6 +84,12 @@ Integer numbers can be given either in decimal or hex:
123, -7878, 0xf00
```

64 or 128-bit integers are denoted using `i64` or `i128`:

```
0xabc0i64, 8i128
```

For floating point numbers, only the most basic decimal format is currently implemented (no scientific notation or hex-floats, yet):

```
Expand Down Expand Up @@ -399,6 +406,27 @@ And binary files:
file("font.bin")
```

#### SIMD

Intrinsics are available for all WASM SIMD instructions, except for the relaxed SIMD extensions.
For instructions that refer to lanes, i.e. `*.extract_lane*`, `*.replace_lane`, `v128.store*_lane`, and
`v128.load*_lane`, the lane number follows the vector argument. For example:

```
v128.store32_lane(<v128_value>, <lane>, <base-address>[, <offset>, [<align>]]);
v128.load32_splat(<v128_value>, <lane>, <base-address>[, <offset>, [<align>]]);
i32x4.extract_lane(<v128_value>, <lane>);
i32x4.replace_lane(<v128_value>, <lane>, <i32_value>);
```

The format for `i8x16.shuffle` is:

```
i8x16.shuffle(<v128_a>, <v128_b>, [<lane_0>[, <lane_1>[, ... <lane_16>]]])
```

Omitted lane arguments default to their index, so providing no lane arguments simply returns the value of `<v128_a>`.

#### Advanced sequencing

Sometimes when sizeoptimizing it helps to be able to execute some side-effecty code in the middle an expression.
Expand Down Expand Up @@ -430,7 +458,6 @@ The idea of CurlyWas is to be able to hand-craft any valid WASM program, ie. hav

This goal is not yet fully reached, with the following being the main limitations:

* CurlyWas currently only targets MVP web assembly + non-trapping float-to-int conversions. No other post-MVP features are currently supported. Especially "Multi-value" will be problematic as this allows programs that don't map cleanly to an expression tree.
* Memory intrinsics are still missing, so only (unsigned) 8 and 32 bit integer reads and writes are possible.
* CurlyWas currently only targets MVP web assembly + non-trapping float-to-int conversions + SIMD. No other post-MVP features are currently supported. Especially "Multi-value" will be problematic as this allows programs that don't map cleanly to an expression tree.
* `block`s cannot return values, as the branch instructions are missing syntax to pass along a value.
* `br_table` and `call_indirect` are not yet implemented.
* `br_table` and `call_indirect` are not yet implemented.
19 changes: 15 additions & 4 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ pub enum DataType {
I16,
I32,
I64,
I128,
F32,
F64,
}
Expand Down Expand Up @@ -211,6 +212,13 @@ impl Expression {
}
}

pub fn const_v128(&self) -> i128 {
match self.expr {
Expr::V128Const(v) => v,
_ => panic!("Expected V128Const"),
}
}

pub fn const_f32(&self) -> f32 {
match self.expr {
Expr::F32Const(v) => v,
Expand Down Expand Up @@ -243,6 +251,7 @@ pub enum Expr {
I64Const(i64),
F32Const(f32),
F64Const(f64),
V128Const(i128),
Variable {
name: String,
local_id: Option<u32>,
Expand Down Expand Up @@ -382,15 +391,17 @@ pub enum Type {
I64,
F32,
F64,
V128
}

impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Type::I32 => write!(f, "i32"),
Type::I64 => write!(f, "i64"),
Type::F32 => write!(f, "f32"),
Type::F64 => write!(f, "f64"),
Type::I32 => write!(f, "i32"),
Type::I64 => write!(f, "i64"),
Type::F32 => write!(f, "f32"),
Type::F64 => write!(f, "f64"),
Type::V128 => write!(f, "v128"),
}
}
}
3 changes: 2 additions & 1 deletion src/constfold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,8 @@ fn fold_expr(context: &Context, expr: &mut ast::Expression) {
ast::Expr::I32Const(_)
| ast::Expr::I64Const(_)
| ast::Expr::F32Const(_)
| ast::Expr::F64Const(_) => (),
| ast::Expr::F64Const(_)
| ast::Expr::V128Const(_) => (),
ast::Expr::Variable { ref name, .. } => {
if let Some(value) = context.consts.get(name) {
expr.expr = value.clone();
Expand Down
82 changes: 80 additions & 2 deletions src/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use wasm_encoder::{

use crate::{
ast,
intrinsics::{Intrinsics, MemInstruction},
intrinsics::{Intrinsics, LaneInstruction, MemInstruction, MemLaneInstruction, ShuffleInstruction},
Options,
};

Expand Down Expand Up @@ -146,6 +146,7 @@ pub fn emit(script: &ast::Script, module_name: &str, options: &Options) -> Vec<u
ast::DataType::I16 => 2,
ast::DataType::I32 => 4,
ast::DataType::I64 => 8,
ast::DataType::I128 => 16,
ast::DataType::F32 => 4,
ast::DataType::F64 => 8,
};
Expand All @@ -161,6 +162,8 @@ pub fn emit(script: &ast::Script, module_name: &str, options: &Options) -> Vec<u
.extend_from_slice(&(value.const_i32() as u32).to_le_bytes()),
ast::DataType::I64 => segment_data
.extend_from_slice(&(value.const_i64() as u64).to_le_bytes()),
ast::DataType::I128 => segment_data
.extend_from_slice(&(value.const_v128() as u128).to_le_bytes()),
ast::DataType::F32 => {
segment_data.extend_from_slice(&value.const_f32().to_le_bytes())
}
Expand Down Expand Up @@ -279,6 +282,7 @@ fn const_instr(expr: &ast::Expression) -> Instruction {
ast::Expr::F32Const(v) => Instruction::F32Const(v),
ast::Expr::I64Const(v) => Instruction::I64Const(v),
ast::Expr::F64Const(v) => Instruction::F64Const(v),
ast::Expr::V128Const(v) => Instruction::V128Const(v),
_ => unreachable!(),
}
}
Expand Down Expand Up @@ -437,6 +441,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
emit_expression(ctx, value);
ctx.function.instruction(&Instruction::F64Neg);
}
(V128, Negate) => unreachable!(),
(I32, Not) => {
emit_expression(ctx, value);
ctx.function.instruction(&Instruction::I32Eqz);
Expand Down Expand Up @@ -533,6 +538,8 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
(F64, Le) => Instruction::F64Le,
(F64, Gt) => Instruction::F64Gt,
(F64, Ge) => Instruction::F64Ge,

(V128, _) => unreachable!(),
});
}
ast::Expr::Branch(label) => {
Expand Down Expand Up @@ -572,6 +579,9 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
ast::Expr::F64Const(v) => {
ctx.function.instruction(&Instruction::F64Const(*v));
}
ast::Expr::V128Const(v) => {
ctx.function.instruction(&Instruction::V128Const(*v));
}
ast::Expr::Assign {
name,
value,
Expand Down Expand Up @@ -657,7 +667,8 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
(F32, F64) => Some(Instruction::F64PromoteF32),
(F64, F32) => Some(Instruction::F32DemoteF64),

(I32, I32) | (I64, I64) | (F32, F32) | (F64, F64) => None,
(I32, I32) | (I64, I64) | (F32, F32) | (F64, F64) | (V128, V128) => None,
(V128, _) | (_, V128) => unreachable!(),
};
if let Some(inst) = inst {
ctx.function.instruction(&inst);
Expand All @@ -679,15 +690,81 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
memory_index: 0,
})
}
fn mem_lane_instruction(
inst: MemLaneInstruction,
lane: u8,
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,
}, lane)
}
fn lane_instruction(
inst: LaneInstruction,
lane: u8
) -> Instruction<'static> {
(inst.instruction)(lane)
}
fn shuffle_instruction(
inst: ShuffleInstruction,
instr_lanes: &[ast::Expression],
) -> Instruction<'static> {
let mut lanes: [u8; 16] = [0; 16];
for (elem, i) in lanes.iter_mut().zip(0..16) {
*elem = instr_lanes.get(i).map(|e| e.const_i32() as u8).unwrap_or(i as u8);
}
(inst.instruction)(lanes)
}
if let Some(load) = ctx.intrinsics.find_load(name) {
emit_expression(ctx, &params[0]);
ctx.function
.instruction(&mem_instruction(load, &params[1..]));
} else if let Some(load_lane) = ctx.intrinsics.find_load_lane(name) {
emit_expression(ctx, &params[2]);
emit_expression(ctx, &params[0]);
let lane = params
.get(1)
.map(|e| e.const_i32() as u8)
.unwrap();
ctx.function
.instruction(&mem_lane_instruction(load_lane, lane, &params[3..]));
} 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 if let Some(store_lane) = ctx.intrinsics.find_store_lane(name) {
emit_expression(ctx, &params[2]);
emit_expression(ctx, &params[0]);
let lane = params
.get(1)
.map(|e| e.const_i32() as u8)
.unwrap();
ctx.function
.instruction(&mem_lane_instruction(store_lane, lane, &params[3..]));
} else if let Some(lane_instr) = ctx.intrinsics.find_lane(name) {
emit_expression(ctx, &params[0]);
let lane = params
.get(1)
.map(|e| e.const_i32() as u8)
.unwrap();
if let Some(_) = lane_instr.param_type {
emit_expression(ctx, &params[2]);
}
ctx.function
.instruction(&lane_instruction(lane_instr, lane));
} else if let Some(shuffle) = ctx.intrinsics.find_shuffle(name) {
emit_expression(ctx, &params[0]);
emit_expression(ctx, &params[1]);
ctx.function
.instruction(&shuffle_instruction(shuffle, &params[2..]));
} else {
for param in params {
emit_expression(ctx, param);
Expand Down Expand Up @@ -762,6 +839,7 @@ fn map_type(t: ast::Type) -> ValType {
ast::Type::I64 => ValType::I64,
ast::Type::F32 => ValType::F32,
ast::Type::F64 => ValType::F64,
ast::Type::V128 => ValType::V128,
}
}

Expand Down
Loading