Wasm Introduction (Part 2): Instruction Set & Operand Stack
Written by the CoinEx Chain lab, this article is the second one of the Wasm Introduction series and introduces instruction set & operand stack. CoinEx Chain is the world’s first public chain exclusively designed for DEX, and will also include a Smart Chain supporting smart contracts and a Privacy Chain protecting users’ privacy.
Wasm Instruction Set
Like the real machine code, the code in the Wasm binary file is also composed of instructions. Similarly, the Wasm instruction also contains two pieces of information: the opcode and operands. The Wasm opcode is fixed to one byte, so it can represent up to 256 instructions, which is the same as the Java bytecode. The Wasml.0 specification defines a total of 172 instructions. These instructions can be divided into 5 categories by function:
- Control Instructions, a total of 13.
- Parametric Instructions, a total of 2.
- Variable Instructions, a total of 5.
- Memory Instructions, a total of 25.
- Numeric Instructions, a total of 127.
It is clear that more than two thirds of the defined instructions are numeric instructions. For the convenience of human writing and understanding, the Wasm specification also defines a mnemonic for each opcode. For example, the mnemonic of the opcode
i32.const. The following is a schematic diagram of the opcode distribution of the defined instructions (the grey area represents undefined opcodes):
Some instructions need to carry information, which is coded immediately after the opcode, called static immediate arguments(hereinafter referred to as immediate arguments). Take the
i32.constinstruction as an example. The opcode
0x41is followed by an encoded 32-bit integer. In the following examples, we will use a schematic diagram similar to the following to represent the encoded instructions:
Like stack machinessuch as JVM, most Wasm instructions also use the operand stack(hereinafter referred to as the stack). These instructions pop one or more numbers from the top of the stack, perform calculations, and then push the results to the top of the stack. The top stack elements that are operated by instructions are called dynamic operands(hereinafter referred to as operands). In the following examples, we will use a schematic diagram similar to the following to indicate the state of the stack before and after the instruction is executed (small arrows indicate pop or push operations):
Parametric and numeric instructions only operate on the stack, and their behavior is relatively simple, which will be introduced in this article. Other instructions will be introduced in subsequent articles.
There are two parametric instructions:
drop (whose opcode is
select (whose opcode is
drop instruction pops an operand of any type from the top of the stack. There is no immediate arguments for the
drop instruction. Its schematic diagram is shown as below:
select instruction successively pops 3 operands from the top of the stack. If the first popped operand is zero, the second popped operand is pushed onto the stack, otherwise the third popped operand is pushed onto the stack. The
select instruction does not have immediate arguments either. Here is a diagram of it:
Note that the operand at the top of the stack must be of type
i32, and the remaining two operands must come in the same type. When we need to emphasize the specific type of the operand, we will mark the type with parentheses in the diagram.
selectare two special instructions, because only these two instructions do not completely limit the type of the operands. For other instructions, all operand types are fully qualified.
Numeric instructions can be divided into four groups of
f64 according to the operand type. Each group of instructions can be further divided into five groups according to the operation.
- Constant Instructions
- Test Instructions
- Comparison Instructions
- Arithmetic Instructions
- Unary Arithmetic Instructions
- Binary Arithmetic Instructions
- Conversion Instructions
Except constant instructions, all other numeric instructions have no immediate arguments.
The constant instruction pushes the immediate argument to the top of the stack. Take the
i32.const instruction (opcode
0x41) as an example. The following is its diagram:
There are four constant instructions, and the other three are:
0x44). It is not difficult to find the naming rule of the Wasm opcode mnemonic: If the type of the top stack element after the instruction execution is
t, then the mnemonic starts with
The test instruction pops an operand from the top of the stack and tests whether it is 0. If it is, it pushes 1 (as
i32) onto the stack; otherwise it pushes 0 (as
i32) onto the stack. There are only two test instructions:
0x50). Take the
i64.eqz instruction as an example. Here is a diagram of it:
As you can see, the result of the test instruction is actually a boolean value, but Wasm does not define a
booltype and uses the
i32type to represent it instead (1 means true, and 0 means false)
The comparison instruction pops two operands of the same type from the top of the stack, compares them, and then pushes the result onto the stack. Like the test instruction, the result of the comparison instruction is also a boolean value (i.e.,
i32 type). Take the
i64.lt_s instruction (Opcode
0x53) as an example. The following is its diagram:
In addition to less than (le), there are instructions for such comparisons as being equal to (eq), not equal (ne), greater than (gt), less than or equal to (le), and greater than or equal to (ge). Instructions are not introduced here one by one. It should be noted that for some instructions operate on integers, it is necessary to clearly indicate how to interpret the operands: whether it is a signednumber (mnemonic ends with
_s) or unsignednumber (mnemonic ends with
_u) . These instructions usually appear in pairs, such as the
i64.lt_sinstruction in the above example, and the corresponding
Unary Arithmetic Instructions
Unary arithmetic instructions pop an operand from the top of the stack, perform calculations, and then push the result to the top of the stack. Take
0x8C) instruction as an example. The following is a schematic diagram of it:
Binary Arithmetic Instructions
Binary arithmetic instructions pop two operands from the top of the stack, perform calculations, and then push the result to the top of the stack. Take
f32.sub instruction (Opcode
0x93) as an example. Its schematic diagram is shown as below:
The conversion instruction pops an operand from the top of the stack, performs type conversion, and then pushes the result to the top of the stack. If the type of the operand before conversion is
t, the type after conversion is
t', and the conversion operation is
conv, then the mnemonic of the instruction is
t'.conv_t. Take the
0xA7) instruction as an example. The following is a schematic diagram of it:
There are too many comparison, arithmetic and conversion instructions to introduce one by one in this article. Please refer to the Wasm specification.