WebAssembly execution is defined in terms of a stack machine. Some types of instructions pushe a number to a stack. Some instructions pops one number or numbers from the stack, execute and push the result to the stack or not.
For example, if you want to do addition, usei32.const
to pushe a 32-bit integer onto the stack twice. i32.add
pops two i32
numbers , computes their sum and pushes the resulting i32
number.
Of course, all processed data are bytes. What you push to the stack are bytes. What you pop from the stack are bytes. What the point of view you take for these bytes is the concept of data types. Once you have a data type, you can use concrete concepts to deal with data, not drop into 0 and 1 directly.
WebAssembly currently has four available types:
i32
: 32-bit integeri64
: 64-bit integerf32
: 32-bit floatf64
: 64-bit float
You might ask: are i32
and i64
signed or unsigned integers? The number type i32
and i64
are not inherently signed or unsigned. What you push or pop are bytes. The interpretation of these types is determined by individual operators.
For example, you can use i32.const
to push a 32-bit number onto to the stack.
(module
(func $main
i32.const 2147483647
drop
)
(start $main)
)
The binary representation of 2147483647 is 1111111 11111111 11111111 11111111
. i32.const
view these bytes as a 32-bit integers and use the little-endian order to push 01111111 11111111 11111111 11111111
to the stack.
The drop
operator can be used to explicitly pop a number from the stack and drop the number directly. You use it here because a function is one type of block (explained in later documents). The $main
function returns nothing so you have to empty the stack before leaving the function. If not, an error happens.
How about the number 2147483648?
(module
(func $main
i32.const 2147483648
drop
)
(start $main)
)
The binary representation of 2147483648 is 10000000 00000000 00000000 00000000
. i32.const
view these bytes as a 32-bit integers and use the little-endian order to push 10000000 00000000 00000000 00000000
to the stack. So, the above code has the same result as the following.
(module
(func $main
i32.const -2147483648
drop
)
(start $main)
)
WebAssembly uses 2's complement as a method of signed number representation so the binary representation of -2147483648 is also 10000000 00000000 00000000 00000000
.
If you change i32
to i64
:
(module
(func $main
i64.const 2147483648
drop
)
(start $main)
)
The binary representation of 2147483648 is 10000000 00000000 00000000 00000000
. i64.const
view these bytes as a 64-bit integers and use the little-endian order to push 00000000 00000000 00000000 00000000 10000000 00000000 00000000 00000000
to the stack.
When communicating with JavaScript, one thing you should note is, JavaScript stores numbers in double-precision 64-bit binary format IEEE 754. When representing integers, the maximum safe integer is 253 - 1 and the minimum safe integer is -(253 - 1). You cannot declare i64
when communicating with JavaScript. For example, the following code will has an error.
(module
(import "env" "log" (func $log (param i64)))
(func $main
i64.const 2147483648
call $log
)
(start $main)
)
Changing i64
to f64
solves the problem.
(module
(import "env" "log" (func $log (param f64)))
(func $main
f64.const 2147483648
call $log
)
(start $main)
)
Numbers can be written as decimals and hexidecimal. To input in hexdecimal notation, prefix with 0x
. The floating point types can also be expressed using E
or e
for scientific notation. inf
means infinity. nan
means NaN (Not a number).