Instructions

Instructions are classified by stack types [t1][t2] that describe how instructions manipulate the operand stack.

stacktype::=[opdtype][opdtype]opdtype::=valtype | 

The types describe the required input stack with operand types t1 that an instruction pops off and the provided output stack with result values of types t2 that it pushes back. Stack types are akin to function types, except that they allow individual operands to be classified as (bottom), indicating that the type is unconstrained. As an auxiliary notion, an operand type t1 matches another operand type t2, if t1 is either or equal to t2. This is extended to stack types in a point-wise manner.

ttt
(tt)[t][t]

Note

For example, the instruction i32.add has type [i32 i32][i32], consuming two i32 values and producing one.

Typing extends to instruction sequences instr. Such a sequence has a stack type [t1][t2] if the accumulative effect of executing the instructions is consuming values of types t1 off the operand stack and pushing new values of types t2.

For some instructions, the typing rules do not fully constrain the type, and therefore allow for multiple types. Such instructions are called polymorphic. Two degrees of polymorphism can be distinguished:

  • value-polymorphic: the value type t of one or several individual operands is unconstrained. That is the case for all parametric instructions like drop and select.

  • stack-polymorphic: the entire (or most of the) stack type [t1][t2] of the instruction is unconstrained. That is the case for all control instructions that perform an unconditional control transfer, such as unreachable, br, br_table, and return.

In both cases, the unconstrained types or type sequences can be chosen arbitrarily, as long as they meet the constraints imposed for the surrounding parts of the program.

Note

For example, the select instruction is valid with type [t t i32][t], for any possible number type t. Consequently, both instruction sequences

(i32.const 1)  (i32.const 2)  (i32.const 3)  select

and

(f64.const 1.0)  (f64.const 2.0)  (i32.const 3)  select

are valid, with t in the typing of select being instantiated to i32 or f64, respectively.

The unreachable instruction is valid with type [t1][t2] for any possible sequences of operand types t1 and t2. Consequently,

unreachable  i32.add

is valid by assuming type [][i32 i32] for the unreachable instruction. In contrast,

unreachable  (i64.const 0)  i32.add

is invalid, because there is no possible type to pick for the unreachable instruction that would make the sequence well-typed.

The Appendix describes a type checking algorithm that efficiently implements validation of instruction sequences as prescribed by the rules given here.

Numeric Instructions

t.const c

  • The instruction is valid with type [][t].

Ct.const c:[][t]

t.unop

  • The instruction is valid with type [t][t].

Ct.unop:[t][t]

t.binop

  • The instruction is valid with type [t t][t].

Ct.binop:[t t][t]

t.testop

  • The instruction is valid with type [t][i32].

Ct.testop:[t][i32]

t.relop

  • The instruction is valid with type [t t][i32].

Ct.relop:[t t][i32]

t2.cvtop_t1_sx?

  • The instruction is valid with type [t1][t2].

Ct2.cvtop_t1_sx?:[t1][t2]

Reference Instructions

ref.null t

  • The instruction is valid with type [][t].

Cref.null t:[][t]

Note

In future versions of WebAssembly, there may be reference types for which no null reference is allowed.

ref.is_null

  • The instruction is valid with type [t][i32], for any reference type t.

t=reftypeCref.is_null:[t][i32]

ref.func x

  • The function C.funcs[x] must be defined in the context.

  • The function index x must be contained in C.refs.

  • The instruction is valid with type [][funcref].

C.funcs[x]=functypexC.refsCref.func x:[][funcref]

Vector Instructions

Vector instructions can have a prefix to describe the shape of the operand. Packed numeric types, i8 and i16, are not value types. An auxiliary function maps such packed type shapes to value types:

unpacked(i8x16)=i32unpacked(i16x8)=i32unpacked(txN)=t

The following auxiliary function denotes the number of lanes in a vector shape, i.e., its dimension:

dim(txN)=N

v128.const c

  • The instruction is valid with type [][v128].

Cv128.const c:[][v128]

v128.vvunop

  • The instruction is valid with type [v128][v128].

Cv128.vvunop:[v128][v128]

v128.vvbinop

  • The instruction is valid with type [v128 v128][v128].

Cv128.vvbinop:[v128 v128][v128]

v128.vvternop

  • The instruction is valid with type [v128 v128 v128][v128].

Cv128.vvternop:[v128 v128 v128][v128]

v128.vvtestop

  • The instruction is valid with type [v128][i32].

Cv128.vvtestop:[v128][i32]

i8x16.swizzle

  • The instruction is valid with type [v128 v128][v128].

Ci8x16.swizzle:[v128 v128][v128]

i8x16.shuffle laneidx16

  • For all laneidxi, in laneidx16, laneidxi must be smaller than 32.

  • The instruction is valid with type [v128 v128][v128].

(laneidx<32)16Ci8x16.shuffle laneidx16:[v128 v128][v128]

shape.splat

  • Let t be unpacked(shape).

  • The instruction is valid with type [t][v128].

Cshape.splat:[unpacked(shape)][v128]

shape.extract_lane_sx? laneidx

  • The lane index laneidx must be smaller than dim(shape).

  • The instruction is valid with type [v128][unpacked(shape)].

laneidx<dim(shape)CtxN.extract_lane_sx? laneidx:[v128][unpacked(shape)]

shape.replace_lane laneidx

  • The lane index laneidx must be smaller than dim(shape).

  • Let t be unpacked(shape).

  • The instruction is valid with type [v128 t][v128].

laneidx<dim(shape)Cshape.replace_lane laneidx:[v128 unpacked(shape)][v128]

shape.vunop

  • The instruction is valid with type [v128][v128].

Cshape.vunop:[v128][v128]

shape.vbinop

  • The instruction is valid with type [v128 v128][v128].

Cshape.vbinop:[v128 v128][v128]

shape.vrelop

  • The instruction is valid with type [v128 v128][v128].

Cshape.vrelop:[v128 v128][v128]

ishape.vishiftop

  • The instruction is valid with type [v128 i32][v128].

Cishape.vishiftop:[v128 i32][v128]

shape.vtestop

  • The instruction is valid with type [v128][i32].

Cshape.vtestop:[v128][i32]

shape.vcvtop_half?_shape_sx?_zero?

  • The instruction is valid with type [v128][v128].

Cshape.vcvtop_half?_shape_sx?_zero?:[v128][v128]

ishape1.narrow_ishape2_sx

  • The instruction is valid with type [v128 v128][v128].

Cishape1.narrow_ishape2_sx:[v128 v128][v128]

ishape.bitmask

  • The instruction is valid with type [v128][i32].

Cishape.bitmask:[v128][i32]

ishape1.dot_ishape2_s

  • The instruction is valid with type [v128 v128][v128].

Cishape1.dot_ishape2_s:[v128 v128][v128]

ishape1.extmul_half_ishape2_sx

  • The instruction is valid with type [v128 v128][v128].

Cishape1.extmul_half_ishape2_sx:[v128 v128][v128]

ishape1.extadd_pairwise_ishape2_sx

  • The instruction is valid with type [v128][v128].

Cishape1.extadd_pairwise_ishape2_sx:[v128][v128]

Parametric Instructions

drop

  • The instruction is valid with type [t][], for any operand type t.

Cdrop:[t][]

Note

Both drop and select without annotation are value-polymorphic instructions.

select (t)?

  • If t is present, then:

    • The length of t must be 1.

    • Then the instruction is valid with type [t t i32][t].

  • Else:

Cselect t:[t t i32][t]tnumtypeCselect:[t t i32][t]tvectypeCselect:[t t i32][t]

Note

In future versions of WebAssembly, select may allow more than one value per choice.

Variable Instructions

local.get x

  • The local C.locals[x] must be defined in the context.

  • Let t be the value type C.locals[x].

  • Then the instruction is valid with type [][t].

C.locals[x]=tClocal.get x:[][t]

local.set x

  • The local C.locals[x] must be defined in the context.

  • Let t be the value type C.locals[x].

  • Then the instruction is valid with type [t][].

C.locals[x]=tClocal.set x:[t][]

local.tee x

  • The local C.locals[x] must be defined in the context.

  • Let t be the value type C.locals[x].

  • Then the instruction is valid with type [t][t].

C.locals[x]=tClocal.tee x:[t][t]

global.get x

  • The global C.globals[x] must be defined in the context.

  • Let mut t be the global type C.globals[x].

  • Then the instruction is valid with type [][t].

C.globals[x]=mut tCglobal.get x:[][t]

global.set x

  • The global C.globals[x] must be defined in the context.

  • Let mut t be the global type C.globals[x].

  • The mutability mut must be var.

  • Then the instruction is valid with type [t][].

C.globals[x]=var tCglobal.set x:[t][]

Table Instructions

table.get x

  • The table C.tables[x] must be defined in the context.

  • Let limits t be the table type C.tables[x].

  • Then the instruction is valid with type [i32][t].

C.tables[x]=limits tCtable.get x:[i32][t]

table.set x

  • The table C.tables[x] must be defined in the context.

  • Let limits t be the table type C.tables[x].

  • Then the instruction is valid with type [i32 t][].

C.tables[x]=limits tCtable.set x:[i32 t][]

table.size x

  • The table C.tables[x] must be defined in the context.

  • Then the instruction is valid with type [][i32].

C.tables[x]=tabletypeCtable.size x:[][i32]

table.grow x

  • The table C.tables[x] must be defined in the context.

  • Let limits t be the table type C.tables[x].

  • Then the instruction is valid with type [t i32][i32].

C.tables[x]=limits tCtable.grow x:[t i32][i32]

table.fill x

  • The table C.tables[x] must be defined in the context.

  • Let limits t be the table type C.tables[x].

  • Then the instruction is valid with type [i32 t i32][].

C.tables[x]=limits tCtable.fill x:[i32 t i32][]

table.copy x y

  • The table C.tables[x] must be defined in the context.

  • Let limits1 t1 be the table type C.tables[x].

  • The table C.tables[y] must be defined in the context.

  • Let limits2 t2 be the table type C.tables[y].

  • The reference type t1 must be the same as t2.

  • Then the instruction is valid with type [i32 i32 i32][].

C.tables[x]=limits1 tC.tables[y]=limits2 tCtable.copy x y:[i32 i32 i32][]

table.init x y

  • The table C.tables[x] must be defined in the context.

  • Let limits t1 be the table type C.tables[x].

  • The element segment C.elems[y] must be defined in the context.

  • Let t2 be the reference type C.elems[y].

  • The reference type t1 must be the same as t2.

  • Then the instruction is valid with type [i32 i32 i32][].

C.tables[x]=limits tC.elems[y]=tCtable.init x y:[i32 i32 i32][]

elem.drop x

  • The element segment C.elems[x] must be defined in the context.

  • Then the instruction is valid with type [][].

C.elems[x]=tCelem.drop x:[][]

Memory Instructions

t.load memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must not be larger than the bit width of t divided by 8.

  • Then the instruction is valid with type [i32][t].

C.mems[0]=memtype2memarg.align|t|/8Ct.load memarg:[i32][t]

t.loadN_sx memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must not be larger than N/8.

  • Then the instruction is valid with type [i32][t].

C.mems[0]=memtype2memarg.alignN/8Ct.loadN_sx memarg:[i32][t]

t.store memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must not be larger than the bit width of t divided by 8.

  • Then the instruction is valid with type [i32 t][].

C.mems[0]=memtype2memarg.align|t|/8Ct.store memarg:[i32 t][]

t.storeN memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must not be larger than N/8.

  • Then the instruction is valid with type [i32 t][].

C.mems[0]=memtype2memarg.alignN/8Ct.storeN memarg:[i32 t][]

v128.loadNxM_sx memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must not be larger than N/8M.

  • Then the instruction is valid with type [i32][v128].

C.mems[0]=memtype2memarg.alignN/8MCv128.loadNxM_sx memarg:[i32][v128]

v128.loadN_splat memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must not be larger than N/8.

  • Then the instruction is valid with type [i32][v128].

C.mems[0]=memtype2memarg.alignN/8Cv128.loadN_splat memarg:[i32][v128]

v128.loadN_zero memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must not be larger than N/8.

  • Then the instruction is valid with type [i32][v128].

C.mems[0]=memtype2memarg.alignN/8Cv128.loadN_zero memarg:[i32][v128]

v128.loadN_lane memarg laneidx

  • The lane index laneidx must be smaller than 128/N.

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must not be larger than N/8.

  • Then the instruction is valid with type [i32 v128][v128].

laneidx<128/NC.mems[0]=memtype2memarg.align<N/8Cv128.loadN_lane memarg laneidx:[i32 v128][v128]

v128.storeN_lane memarg laneidx

  • The lane index laneidx must be smaller than 128/N.

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must not be larger than N/8.

  • Then the instruction is valid with type [i32 v128][v128].

laneidx<128/NC.mems[0]=memtype2memarg.align<N/8Cv128.storeN_lane memarg laneidx:[i32 v128][]

memory.size

  • The memory C.mems[0] must be defined in the context.

  • Then the instruction is valid with type [][i32].

C.mems[0]=memtypeCmemory.size:[][i32]

memory.grow

  • The memory C.mems[0] must be defined in the context.

  • Then the instruction is valid with type [i32][i32].

C.mems[0]=memtypeCmemory.grow:[i32][i32]

memory.fill

  • The memory C.mems[0] must be defined in the context.

  • Then the instruction is valid with type [i32 i32 i32][].

C.mems[0]=memtypeCmemory.fill:[i32 i32 i32][]

memory.copy

  • The memory C.mems[0] must be defined in the context.

  • Then the instruction is valid with type [i32 i32 i32][].

C.mems[0]=memtypeCmemory.copy:[i32 i32 i32][]

memory.init x

  • The memory C.mems[0] must be defined in the context.

  • The data segment C.datas[x] must be defined in the context.

  • Then the instruction is valid with type [i32 i32 i32][].

C.mems[0]=memtypeC.datas[x]=okCmemory.init x:[i32 i32 i32][]

data.drop x

  • The data segment C.datas[x] must be defined in the context.

  • Then the instruction is valid with type [][].

C.datas[x]=okCdata.drop x:[][]

Atomic Memory Instructions

t.atomic.load memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must be equal to the width of t divided by 8.

  • Then the instruction is valid with type [i32][t].

C.mems[0]=memtype2memarg.align=|t|/8Ct.atomic.load memarg:[i32][t]

t.atomic.loadN_u memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must be equal to N/8.

  • Then the instruction is valid with type [i32][t].

C.mems[0]=memtype2memarg.align=N/8Ct.atomic.loadN_u memarg:[i32][t]

t.atomic.store memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must be equal to the width of t divided by 8.

  • Then the instruction is valid with type [i32 t][].

C.mems[0]=memtype2memarg.align=|t|/8Ct.atomic.store memarg:[i32 t][]

t.atomic.storeN memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must be equal to N/8.

  • Then the instruction is valid with type [i32 t][].

C.mems[0]=memtype2memarg.align=N/8Ct.atomic.storeN memarg:[i32 t][]

t.atomic.rmw.atop memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must be equal to the width of t divided by 8.

  • Then the instruction is valid with type [i32 t][t].

C.mems[0]=memtype2memarg.align=|t|/8Ct.atomic.rmw.atop memarg:[i32 t][t]

t.atomic.rmwN.atop_u memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must be equal to N/8.

  • Then the instruction is valid with type [i32 t][t].

C.mems[0]=memtype2memarg.align=N/8Ct.atomic.rmwN.atop_u memarg:[i32 t][t]

t.atomic.rmw.cmpxchg memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must be equal to the width of t divided by 8.

  • Then the instruction is valid with type [i32 t t][t].

C.mems[0]=memtype2memarg.align=|t|/8Ct.atomic.rmw.cmpxchg memarg:[i32 t t][t]

t.atomic.rmwN.cmpxchg_u memarg

  • The memory C.mems[0] must be defined in the context.

  • The alignment 2memarg.align must be equal to N/8.

  • Then the instruction is valid with type [i32 t t][t].

C.mems[0]=memtype2memarg.align=N/8Ct.atomic.rmwN.cmpxchg_u memarg:[i32 t t][t]

memory.atomic.notify memarg

  • The memory C.mems[0] must be defined in the context.

  • Let limits share be the memory type C.mems[0].

  • The alignment 2memarg.align must be equal to 4.

  • Then the instruction is valid with type [i32 i32][i32].

C.mems[0]=memtype2memarg.align=4Cmemory.atomic.notify memarg:[i32 i32][i32]

memory.atomic.waitN memarg

  • The memory C.mems[0] must be defined in the context.

  • Let limits share be the memory type C.mems[0].

  • The alignment 2memarg.align must be equal to N divided by 8.

  • Then the instruction is valid with type [i32 iN i64][i32].

C.mems[0]=memtype2memarg.align=N/8Cmemory.atomic.waitN memarg:[i32 iN i64][i32]

atomic.fence

  • The instruction is valid with type [][].

Catomic.fence:[][]

Note

The atomic.fence instruction may occur in modules which declare no memory without causing a validation error.

Control Instructions

nop

  • The instruction is valid with type [][].

Cnop:[][]

unreachable

  • The instruction is valid with type [t1][t2], for any sequences of operand types t1 and t2.

Cunreachable:[t1][t2]

Note

The unreachable instruction is stack-polymorphic.

block blocktype instr end

  • The block type must be valid as some function type [t1][t2].

  • Let C be the same context as C, but with the result type [t2] prepended to the labels vector.

  • Under context C, the instruction sequence instr must be valid with type [t1][t2].

  • Then the compound instruction is valid with type [t1][t2].

Cblocktype:[t1][t2]C,labels[t2]instr:[t1][t2]Cblock blocktype instr end:[t1][t2]

Note

The notation C,labels[t] inserts the new label type at index 0, shifting all others.

loop blocktype instr end

  • The block type must be valid as some function type [t1][t2].

  • Let C be the same context as C, but with the result type [t1] prepended to the labels vector.

  • Under context C, the instruction sequence instr must be valid with type [t1][t2].

  • Then the compound instruction is valid with type [t1][t2].

Cblocktype:[t1][t2]C,labels[t1]instr:[t1][t2]Cloop blocktype instr end:[t1][t2]

Note

The notation C,labels[t] inserts the new label type at index 0, shifting all others.

if blocktype instr1 else instr2 end

  • The block type must be valid as some function type [t1][t2].

  • Let C be the same context as C, but with the result type [t2] prepended to the labels vector.

  • Under context C, the instruction sequence instr1 must be valid with type [t1][t2].

  • Under context C, the instruction sequence instr2 must be valid with type [t1][t2].

  • Then the compound instruction is valid with type [t1 i32][t2].

Cblocktype:[t1][t2]C,labels[t2]instr1:[t1][t2]C,labels[t2]instr2:[t1][t2]Cif blocktype instr1 else instr2 end:[t1 i32][t2]

Note

The notation C,labels[t] inserts the new label type at index 0, shifting all others.

br l

  • The label C.labels[l] must be defined in the context.

  • Let [t] be the result type C.labels[l].

  • Then the instruction is valid with type [t1 t][t2], for any sequences of operand types t1 and t2.

C.labels[l]=[t]Cbr l:[t1 t][t2]

Note

The label index space in the context C contains the most recent label first, so that C.labels[l] performs a relative lookup as expected.

The br instruction is stack-polymorphic.

br_if l

  • The label C.labels[l] must be defined in the context.

  • Let [t] be the result type C.labels[l].

  • Then the instruction is valid with type [t i32][t].

C.labels[l]=[t]Cbr_if l:[t i32][t]

Note

The label index space in the context C contains the most recent label first, so that C.labels[l] performs a relative lookup as expected.

br_table l lN

  • The label C.labels[lN] must be defined in the context.

  • For all li in l, the label C.labels[li] must be defined in the context.

  • There must be a sequence t of operand types, such that:

    • For each operand type tj in t and corresponding type tNj in C.labels[lN], tj matches tNj.

    • For all li in l, and for each operand type tj in t and corresponding type tij in C.labels[li], tj matches tij.

  • Then the instruction is valid with type [t1 t i32][t2], for any sequences of operand types t1 and t2.

([t]C.labels[l])[t]C.labels[lN]Cbr_table l lN:[t1 t i32][t2]

Note

The label index space in the context C contains the most recent label first, so that C.labels[li] performs a relative lookup as expected.

The br_table instruction is stack-polymorphic.

return

  • The return type C.return must not be absent in the context.

  • Let [t] be the result type of C.return.

  • Then the instruction is valid with type [t1 t][t2], for any sequences of operand types t1 and t2.

C.return=[t]Creturn:[t1 t][t2]

Note

The return instruction is stack-polymorphic.

C.return is absent (set to ϵ) when validating an expression that is not a function body. This differs from it being set to the empty result type ([ϵ]), which is the case for functions not returning anything.

call x

  • The function C.funcs[x] must be defined in the context.

  • Then the instruction is valid with type C.funcs[x].

C.funcs[x]=[t1][t2]Ccall x:[t1][t2]

call_indirect x y

  • The table C.tables[x] must be defined in the context.

  • Let limits t be the table type C.tables[x].

  • The reference type t must be funcref.

  • The type C.types[y] must be defined in the context.

  • Let [t1][t2] be the function type C.types[y].

  • Then the instruction is valid with type [t1 i32][t2].

C.tables[x]=limits funcrefC.types[y]=[t1][t2]Ccall_indirect x y:[t1 i32][t2]

Instruction Sequences

Typing of instruction sequences is defined recursively.

Empty Instruction Sequence: ϵ

  • The empty instruction sequence is valid with type [t][t], for any sequence of operand types t.

Cϵ:[t][t]

Non-empty Instruction Sequence: instr instrN

  • The instruction sequence instr must be valid with type [t1][t2], for some sequences of operand types t1 and t2.

  • The instruction instrN must be valid with type [t][t3], for some sequences of operand types t and t3.

  • There must be a sequence of operand types t0, such that t2=t0 t where the type sequence t is as long as t.

  • For each operand type ti in t and corresponding type ti in t, ti matches ti.

  • Then the combined instruction sequence is valid with type [t1][t0 t3].

Cinstr:[t1][t0 t][t][t]CinstrN:[t][t3]Cinstr instrN:[t1][t0 t3]

Expressions

Expressions expr are classified by result types of the form [t].

instr end

Cinstr:[][t][t][t]Cinstr end:[t]

Constant Expressions

  • In a constant expression instr end all instructions in instr must be constant.

  • A constant instruction instr must be:

    • either of the form t.const c,

    • or of the form ref.null,

    • or of the form ref.func x,

    • or of the form global.get x, in which case C.globals[x] must be a global type of the form const t.

(Cinstrconst)Cinstr endconst
Ct.const cconstCref.null tconstCref.func xconst
C.globals[x]=const tCglobal.get xconst

Note

Currently, constant expressions occurring in globals, element, or data segments are further constrained in that contained global.get instructions are only allowed to refer to imported globals. This is enforced in the validation rule for modules by constraining the context C accordingly.

The definition of constant expression may be extended in future versions of WebAssembly.