pub mod matcher;
use crate::dot::Dot;
use crate::encode::Encoder;
use crate::module::{DisplayExpr, DotExpr};
use crate::{DataId, FunctionId, GlobalId, MemoryId, TableId, TypeId, ValType};
use id_arena::Id;
use std::fmt;
use std::mem;
use walrus_macro::walrus_expr;
pub type LocalId = Id<Local>;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Local {
id: LocalId,
ty: ValType,
pub name: Option<String>,
}
impl Local {
pub fn new(id: LocalId, ty: ValType) -> Local {
Local { id, ty, name: None }
}
pub fn id(&self) -> LocalId {
self.id
}
pub fn ty(&self) -> ValType {
self.ty
}
}
pub type ExprId = Id<Expr>;
impl Dot for ExprId {
fn dot(&self, out: &mut String) {
out.push_str(&format!("expr_{}", self.index()))
}
}
pub trait Ast: Into<Expr> {
type Id: Into<ExprId>;
fn new_id(id: ExprId) -> Self::Id;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BlockKind {
Block,
Loop,
IfElse,
FunctionEntry,
}
#[walrus_expr]
#[derive(Clone, Debug)]
pub enum Expr {
#[walrus(display_name = display_block_name, dot_name = dot_block_name)]
Block {
#[walrus(skip_visit)] kind: BlockKind,
#[walrus(skip_visit)] params: Box<[ValType]>,
#[walrus(skip_visit)] results: Box<[ValType]>,
exprs: Vec<ExprId>,
},
Call {
func: FunctionId,
args: Box<[ExprId]>,
},
CallIndirect {
ty: TypeId,
table: TableId,
func: ExprId,
args: Box<[ExprId]>,
},
LocalGet {
local: LocalId,
},
LocalSet {
local: LocalId,
value: ExprId,
},
LocalTee {
local: LocalId,
value: ExprId,
},
GlobalGet {
global: GlobalId,
},
GlobalSet {
global: GlobalId,
value: ExprId,
},
Const {
value: Value,
},
#[walrus(display_name = display_binop_name, dot_name = dot_binop_name)]
Binop {
#[walrus(skip_visit)]
op: BinaryOp,
lhs: ExprId,
rhs: ExprId,
},
#[walrus(display_name = display_unop_name, dot_name = dot_unop_name)]
Unop {
#[walrus(skip_visit)]
op: UnaryOp,
expr: ExprId,
},
Select {
condition: ExprId,
consequent: ExprId,
alternative: ExprId,
},
Unreachable {},
#[walrus(display_extra = display_br)]
Br {
#[walrus(skip_visit)] block: BlockId,
args: Box<[ExprId]>,
},
#[walrus(display_extra = display_br_if)]
BrIf {
condition: ExprId,
#[walrus(skip_visit)] block: BlockId,
args: Box<[ExprId]>,
},
IfElse {
condition: ExprId,
consequent: BlockId,
alternative: BlockId,
},
#[walrus(display_extra = display_br_table)]
BrTable {
which: ExprId,
#[walrus(skip_visit)] blocks: Box<[BlockId]>,
#[walrus(skip_visit)] default: BlockId,
args: Box<[ExprId]>,
},
Drop {
expr: ExprId,
},
Return {
values: Box<[ExprId]>,
},
MemorySize {
memory: MemoryId,
},
MemoryGrow {
memory: MemoryId,
pages: ExprId,
},
MemoryInit {
memory: MemoryId,
data: DataId,
memory_offset: ExprId,
data_offset: ExprId,
len: ExprId,
},
DataDrop {
data: DataId,
},
MemoryCopy {
src: MemoryId,
dst: MemoryId,
dst_offset: ExprId,
src_offset: ExprId,
len: ExprId,
},
MemoryFill {
memory: MemoryId,
offset: ExprId,
value: ExprId,
len: ExprId,
},
Load {
memory: MemoryId,
#[walrus(skip_visit)]
kind: LoadKind,
#[walrus(skip_visit)]
arg: MemArg,
address: ExprId,
},
Store {
memory: MemoryId,
#[walrus(skip_visit)]
kind: StoreKind,
#[walrus(skip_visit)]
arg: MemArg,
address: ExprId,
value: ExprId,
},
AtomicRmw {
memory: MemoryId,
#[walrus(skip_visit)]
op: AtomicOp,
#[walrus(skip_visit)]
width: AtomicWidth,
#[walrus(skip_visit)]
arg: MemArg,
address: ExprId,
value: ExprId,
},
Cmpxchg {
memory: MemoryId,
#[walrus(skip_visit)]
width: AtomicWidth,
#[walrus(skip_visit)]
arg: MemArg,
address: ExprId,
expected: ExprId,
replacement: ExprId,
},
AtomicNotify {
memory: MemoryId,
#[walrus(skip_visit)]
arg: MemArg,
address: ExprId,
count: ExprId,
},
AtomicWait {
memory: MemoryId,
#[walrus(skip_visit)]
arg: MemArg,
address: ExprId,
expected: ExprId,
timeout: ExprId,
#[walrus(skip_visit)]
sixty_four: bool,
},
WithSideEffects {
before: Vec<ExprId>,
value: ExprId,
after: Vec<ExprId>,
},
TableGet {
table: TableId,
index: ExprId,
},
TableSet {
table: TableId,
index: ExprId,
value: ExprId,
},
TableGrow {
table: TableId,
amount: ExprId,
value: ExprId,
},
TableSize {
table: TableId,
},
RefNull {},
RefIsNull {
value: ExprId,
},
V128Bitselect {
mask: ExprId,
v1: ExprId,
v2: ExprId,
},
V128Shuffle {
#[walrus(skip_visit)]
indices: ShuffleIndices,
lo: ExprId,
hi: ExprId,
},
}
pub type ShuffleIndices = [u8; 16];
#[derive(Debug, Clone, Copy)]
pub enum Value {
I32(i32),
I64(i64),
F32(f32),
F64(f64),
V128(u128),
}
impl Value {
pub(crate) fn emit(&self, encoder: &mut Encoder) {
match *self {
Value::I32(n) => {
encoder.byte(0x41); encoder.i32(n);
}
Value::I64(n) => {
encoder.byte(0x42); encoder.i64(n);
}
Value::F32(n) => {
encoder.byte(0x43); encoder.f32(n);
}
Value::F64(n) => {
encoder.byte(0x44); encoder.f64(n);
}
Value::V128(n) => {
encoder.raw(&[0xfd, 0x02]); for i in 0..16 {
encoder.byte((n >> (i * 8)) as u8);
}
}
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Value::I32(i) => i.fmt(f),
Value::I64(i) => i.fmt(f),
Value::F32(i) => i.fmt(f),
Value::F64(i) => i.fmt(f),
Value::V128(i) => i.fmt(f),
}
}
}
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug)]
pub enum BinaryOp {
I32Eq,
I32Ne,
I32LtS,
I32LtU,
I32GtS,
I32GtU,
I32LeS,
I32LeU,
I32GeS,
I32GeU,
I64Eq,
I64Ne,
I64LtS,
I64LtU,
I64GtS,
I64GtU,
I64LeS,
I64LeU,
I64GeS,
I64GeU,
F32Eq,
F32Ne,
F32Lt,
F32Gt,
F32Le,
F32Ge,
F64Eq,
F64Ne,
F64Lt,
F64Gt,
F64Le,
F64Ge,
I32Add,
I32Sub,
I32Mul,
I32DivS,
I32DivU,
I32RemS,
I32RemU,
I32And,
I32Or,
I32Xor,
I32Shl,
I32ShrS,
I32ShrU,
I32Rotl,
I32Rotr,
I64Add,
I64Sub,
I64Mul,
I64DivS,
I64DivU,
I64RemS,
I64RemU,
I64And,
I64Or,
I64Xor,
I64Shl,
I64ShrS,
I64ShrU,
I64Rotl,
I64Rotr,
F32Add,
F32Sub,
F32Mul,
F32Div,
F32Min,
F32Max,
F32Copysign,
F64Add,
F64Sub,
F64Mul,
F64Div,
F64Min,
F64Max,
F64Copysign,
I8x16ReplaceLane { idx: u8 },
I16x8ReplaceLane { idx: u8 },
I32x4ReplaceLane { idx: u8 },
I64x2ReplaceLane { idx: u8 },
F32x4ReplaceLane { idx: u8 },
F64x2ReplaceLane { idx: u8 },
I8x16Eq,
I8x16Ne,
I8x16LtS,
I8x16LtU,
I8x16GtS,
I8x16GtU,
I8x16LeS,
I8x16LeU,
I8x16GeS,
I8x16GeU,
I16x8Eq,
I16x8Ne,
I16x8LtS,
I16x8LtU,
I16x8GtS,
I16x8GtU,
I16x8LeS,
I16x8LeU,
I16x8GeS,
I16x8GeU,
I32x4Eq,
I32x4Ne,
I32x4LtS,
I32x4LtU,
I32x4GtS,
I32x4GtU,
I32x4LeS,
I32x4LeU,
I32x4GeS,
I32x4GeU,
F32x4Eq,
F32x4Ne,
F32x4Lt,
F32x4Gt,
F32x4Le,
F32x4Ge,
F64x2Eq,
F64x2Ne,
F64x2Lt,
F64x2Gt,
F64x2Le,
F64x2Ge,
V128And,
V128Or,
V128Xor,
I8x16Shl,
I8x16ShrS,
I8x16ShrU,
I8x16Add,
I8x16AddSaturateS,
I8x16AddSaturateU,
I8x16Sub,
I8x16SubSaturateS,
I8x16SubSaturateU,
I8x16Mul,
I16x8Shl,
I16x8ShrS,
I16x8ShrU,
I16x8Add,
I16x8AddSaturateS,
I16x8AddSaturateU,
I16x8Sub,
I16x8SubSaturateS,
I16x8SubSaturateU,
I16x8Mul,
I32x4Shl,
I32x4ShrS,
I32x4ShrU,
I32x4Add,
I32x4Sub,
I32x4Mul,
I64x2Shl,
I64x2ShrS,
I64x2ShrU,
I64x2Add,
I64x2Sub,
F32x4Add,
F32x4Sub,
F32x4Mul,
F32x4Div,
F32x4Min,
F32x4Max,
F64x2Add,
F64x2Sub,
F64x2Mul,
F64x2Div,
F64x2Min,
F64x2Max,
}
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug)]
pub enum UnaryOp {
I32Eqz,
I32Clz,
I32Ctz,
I32Popcnt,
I64Eqz,
I64Clz,
I64Ctz,
I64Popcnt,
F32Abs,
F32Neg,
F32Ceil,
F32Floor,
F32Trunc,
F32Nearest,
F32Sqrt,
F64Abs,
F64Neg,
F64Ceil,
F64Floor,
F64Trunc,
F64Nearest,
F64Sqrt,
I32WrapI64,
I32TruncSF32,
I32TruncUF32,
I32TruncSF64,
I32TruncUF64,
I64ExtendSI32,
I64ExtendUI32,
I64TruncSF32,
I64TruncUF32,
I64TruncSF64,
I64TruncUF64,
F32ConvertSI32,
F32ConvertUI32,
F32ConvertSI64,
F32ConvertUI64,
F32DemoteF64,
F64ConvertSI32,
F64ConvertUI32,
F64ConvertSI64,
F64ConvertUI64,
F64PromoteF32,
I32ReinterpretF32,
I64ReinterpretF64,
F32ReinterpretI32,
F64ReinterpretI64,
I32Extend8S,
I32Extend16S,
I64Extend8S,
I64Extend16S,
I64Extend32S,
I8x16Splat,
I8x16ExtractLaneS { idx: u8 },
I8x16ExtractLaneU { idx: u8 },
I16x8Splat,
I16x8ExtractLaneS { idx: u8 },
I16x8ExtractLaneU { idx: u8 },
I32x4Splat,
I32x4ExtractLane { idx: u8 },
I64x2Splat,
I64x2ExtractLane { idx: u8 },
F32x4Splat,
F32x4ExtractLane { idx: u8 },
F64x2Splat,
F64x2ExtractLane { idx: u8 },
V128Not,
I8x16Neg,
I8x16AnyTrue,
I8x16AllTrue,
I16x8Neg,
I16x8AnyTrue,
I16x8AllTrue,
I32x4Neg,
I32x4AnyTrue,
I32x4AllTrue,
I64x2Neg,
I64x2AnyTrue,
I64x2AllTrue,
F32x4Abs,
F32x4Neg,
F32x4Sqrt,
F64x2Abs,
F64x2Neg,
F64x2Sqrt,
I32x4TruncSF32x4Sat,
I32x4TruncUF32x4Sat,
I64x2TruncSF64x2Sat,
I64x2TruncUF64x2Sat,
F32x4ConvertSI32x4,
F32x4ConvertUI32x4,
F64x2ConvertSI64x2,
F64x2ConvertUI64x2,
I32TruncSSatF32,
I32TruncUSatF32,
I32TruncSSatF64,
I32TruncUSatF64,
I64TruncSSatF32,
I64TruncUSatF32,
I64TruncSSatF64,
I64TruncUSatF64,
}
#[derive(Debug, Copy, Clone)]
#[allow(missing_docs)]
pub enum LoadKind {
I32 { atomic: bool },
I64 { atomic: bool },
F32,
F64,
V128,
I32_8 { kind: ExtendedLoad },
I32_16 { kind: ExtendedLoad },
I64_8 { kind: ExtendedLoad },
I64_16 { kind: ExtendedLoad },
I64_32 { kind: ExtendedLoad },
}
#[derive(Debug, Copy, Clone)]
#[allow(missing_docs)]
pub enum ExtendedLoad {
SignExtend,
ZeroExtend,
ZeroExtendAtomic,
}
impl LoadKind {
pub fn width(&self) -> u32 {
use self::LoadKind::*;
match self {
I32_8 { .. } | I64_8 { .. } => 1,
I32_16 { .. } | I64_16 { .. } => 2,
I32 { .. } | F32 | I64_32 { .. } => 4,
I64 { .. } | F64 => 8,
V128 => 16,
}
}
pub fn atomic(&self) -> bool {
use self::LoadKind::*;
match self {
I32_8 { kind }
| I32_16 { kind }
| I64_8 { kind }
| I64_16 { kind }
| I64_32 { kind } => kind.atomic(),
I32 { atomic } | I64 { atomic } => *atomic,
F32 | F64 | V128 => false,
}
}
}
impl ExtendedLoad {
pub fn atomic(&self) -> bool {
match self {
ExtendedLoad::SignExtend | ExtendedLoad::ZeroExtend => false,
ExtendedLoad::ZeroExtendAtomic => true,
}
}
}
#[derive(Debug, Copy, Clone)]
#[allow(missing_docs)]
pub enum StoreKind {
I32 { atomic: bool },
I64 { atomic: bool },
F32,
F64,
V128,
I32_8 { atomic: bool },
I32_16 { atomic: bool },
I64_8 { atomic: bool },
I64_16 { atomic: bool },
I64_32 { atomic: bool },
}
impl StoreKind {
pub fn width(&self) -> u32 {
use self::StoreKind::*;
match self {
I32_8 { .. } | I64_8 { .. } => 1,
I32_16 { .. } | I64_16 { .. } => 2,
I32 { .. } | F32 | I64_32 { .. } => 4,
I64 { .. } | F64 => 8,
V128 => 16,
}
}
pub fn atomic(&self) -> bool {
use self::StoreKind::*;
match self {
I32 { atomic }
| I64 { atomic }
| I32_8 { atomic }
| I32_16 { atomic }
| I64_8 { atomic }
| I64_16 { atomic }
| I64_32 { atomic } => *atomic,
F32 | F64 | V128 => false,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct MemArg {
pub align: u32,
pub offset: u32,
}
#[derive(Debug, Copy, Clone)]
#[allow(missing_docs)]
pub enum AtomicOp {
Add,
Sub,
And,
Or,
Xor,
Xchg,
}
#[derive(Debug, Copy, Clone)]
#[allow(missing_docs)]
pub enum AtomicWidth {
I32,
I32_8,
I32_16,
I64,
I64_8,
I64_16,
I64_32,
}
impl AtomicWidth {
pub fn bytes(&self) -> u32 {
use self::AtomicWidth::*;
match self {
I32_8 | I64_8 => 1,
I32_16 | I64_16 => 2,
I32 | I64_32 => 4,
I64 => 8,
}
}
}
impl Expr {
pub fn following_instructions_are_unreachable(&self) -> bool {
match *self {
Expr::Unreachable(..) | Expr::Br(..) | Expr::BrTable(..) | Expr::Return(..) => true,
Expr::Block(..)
| Expr::Call(..)
| Expr::LocalGet(..)
| Expr::LocalSet(..)
| Expr::LocalTee(..)
| Expr::GlobalGet(..)
| Expr::GlobalSet(..)
| Expr::Const(..)
| Expr::Binop(..)
| Expr::Unop(..)
| Expr::Select(..)
| Expr::BrIf(..)
| Expr::IfElse(..)
| Expr::MemorySize(..)
| Expr::MemoryGrow(..)
| Expr::MemoryInit(..)
| Expr::DataDrop(..)
| Expr::MemoryCopy(..)
| Expr::MemoryFill(..)
| Expr::CallIndirect(..)
| Expr::Load(..)
| Expr::Store(..)
| Expr::AtomicRmw(..)
| Expr::Cmpxchg(..)
| Expr::AtomicNotify(..)
| Expr::AtomicWait(..)
| Expr::WithSideEffects(..)
| Expr::TableGet(..)
| Expr::TableSet(..)
| Expr::TableGrow(..)
| Expr::TableSize(..)
| Expr::RefNull(..)
| Expr::RefIsNull(..)
| Expr::V128Bitselect(..)
| Expr::V128Shuffle(..)
| Expr::Drop(..) => false,
}
}
}
impl Block {
pub fn new(kind: BlockKind, params: Box<[ValType]>, results: Box<[ValType]>) -> Block {
let exprs = vec![];
Block {
kind,
params,
results,
exprs,
}
}
}
pub trait Visit<'expr> {
fn visit<V>(&self, visitor: &mut V)
where
V: Visitor<'expr>;
}
pub trait VisitMut {
fn visit_mut<V>(&mut self, visitor: &mut V)
where
V: VisitorMut;
}
impl<'expr> Visit<'expr> for ExprId {
fn visit<V>(&self, visitor: &mut V)
where
V: Visitor<'expr>,
{
visitor.visit_expr(&visitor.local_function().get(*self))
}
}
impl VisitMut for ExprId {
fn visit_mut<V>(&mut self, visitor: &mut V)
where
V: VisitorMut,
{
let mut expr = mem::replace(
visitor.local_function_mut().get_mut(*self),
Expr::Unreachable(Unreachable {}),
);
visitor.visit_expr_mut(&mut expr);
*visitor.local_function_mut().get_mut(*self) = expr;
}
}
fn display_block_name(block: &Block, out: &mut DisplayExpr) {
match block.kind {
BlockKind::Loop => out.f.push_str("loop"),
_ => out.f.push_str("block"),
}
}
fn dot_block_name(block: &Block, out: &mut DotExpr<'_, '_>) {
match block.kind {
BlockKind::Loop => out.out.push_str("loop"),
BlockKind::IfElse => out.out.push_str("if_else"),
BlockKind::FunctionEntry => out.out.push_str("entry"),
BlockKind::Block => out.out.push_str("block"),
}
}
fn display_br(e: &Br, out: &mut DisplayExpr) {
out.f
.push_str(&format!(" (;e{};)", ExprId::from(e.block).index()))
}
fn display_br_if(e: &BrIf, out: &mut DisplayExpr) {
out.f
.push_str(&format!(" (;e{};)", ExprId::from(e.block).index()))
}
fn display_br_table(e: &BrTable, out: &mut DisplayExpr) {
let blocks = e
.blocks
.iter()
.map(|b| format!("e{}", ExprId::from(*b).index()))
.collect::<Vec<_>>()
.join(" ");
out.f.push_str(&format!(
" (;default:e{} [{}];)",
ExprId::from(e.default).index(),
blocks
))
}
fn display_binop_name(e: &Binop, out: &mut DisplayExpr) {
out.f.push_str(&format!("{:?}", e.op))
}
fn dot_binop_name(e: &Binop, out: &mut DotExpr<'_, '_>) {
out.out.push_str(&format!("{:?}", e.op))
}
fn display_unop_name(e: &Unop, out: &mut DisplayExpr) {
out.f.push_str(&format!("{:?}", e.op))
}
fn dot_unop_name(e: &Unop, out: &mut DotExpr<'_, '_>) {
out.out.push_str(&format!("{:?}", e.op))
}