To get familiar with stack-oriented operations, you can start from number operations. Let's see how to push a constant number onto the stack. You can append .const
to i32
, i64
, f32
and f64
.
i32.const
i64.const
f32.const
f64.const
After pushing enough numbers, you can use the operators listed below.
Integer operators
i32
and i64
have the same operators. For example, add
is available on 32-bit (i32.add
) and 64-bit integers (i64.add
). Take i32
for example. The operators in mathematics are:
i32.add
: sign-agnostic additioni32.sub
: sign-agnostic subtractioni32.mul
: sign-agnostic multiplication (lower 32-bits)i32.div_s
: signed division (result is truncated toward zero)i32.div_u
: unsigned division (result is floored)i32.rem_s
: signed remainder (result has the sign of the dividend)i32.rem_u
: unsigned remainder
The bitwise operators include:
i32.and
: sign-agnostic bitwise andi32.or
: sign-agnostic bitwise inclusive ori32.xor
: sign-agnostic bitwise exclusive ori32.shl
: sign-agnostic shift lefti32.shr_u
: zero-replicating (logical) shift righti32.shr_s
: sign-replicating (arithmetic) shift righti32.rotl
: sign-agnostic rotate lefti32.rotr
: sign-agnostic rotate right
The comparison operators are listed below. In WebAssembly, all comparison operators yield 32-bit integer results with 1 representing true
and 0 representing false
.
i32.eq
: sign-agnostic compare equali32.ne
: sign-agnostic compare unequali32.lt_s
: signed less thani32.le_s
: signed less than or equali32.lt_u
: unsigned less thani32.le_u
: unsigned less than or equali32.gt_s
: signed greater thani32.ge_s
: signed greater than or equali32.gt_u
: unsigned greater thani32.ge_u
: unsigned greater than or equal
All the above are binary operators so you have to push two numbers before executing the instruction. For example, if you want to do 1 + 2:
(module
(import "env" "log" (func $log (param i32)))
(func $main
i32.const 1
i32.const 2
i32.add
call $log ;; show 3 in conole
)
(start $main)
)
You can see a single line comment here. The text after ;;
will be ignored when compiling.
If you've got familiar with stack-oriented operations, you can write concisely as follows:
(module
(import "env" "log" (func $log (param i32)))
(func $main
(i32.add (i32.const 1) (i32.const 2))
call $log
)
(start $main)
)
The instruction inside the parentheses will be executed first. Take a look at Compiling C to WebAssembly. The Wat generated automatically is written the same way.
If you observe the generated code carefully, you can rewrite the above code as follows:
(module
(import "env" "log" (func $log (param i32)))
(func $main
(call $log
(i32.add (i32.const 1) (i32.const 2))
)
)
(start $main)
)
Simply speaking, do some basic operations first and watch what the automatically-generated Wat is when compiling C to Wat. You will be able to write code more clearly and concisely. In later documents, I'll keep writing basic operations first and use the above style if it's helpful for readability.
Unary operators require one number from the stack.
i32.clz
: sign-agnostic count leading zero bits (All zero bits are considered leading if the value is zero)i32.ctz
: sign-agnostic count trailing zero bits (All zero bits are considered trailing if the value is zero)i32.popcn
: sign-agnostic count number of one bitsi32.eqz
: compare equal to zero (return 1 if operand is zero, 0 otherwise)
Floating point operators
f32
and f64
have the same operators. Take f32
for example. The binary operators include:
f32.add
: additionf32.sub
: subtractionf32.mul
: multiplicationf32.div
: divisionf32.copysign
: copysignf32.eq
: compare ordered and equalf32.ne
: compare unordered or unequalf32.lt
: compare ordered and less thanf32.le
: compare ordered and less than or equalf32.gt
: compare ordered and greater thanf32.ge
: compare ordered and greater than or equalf32.min
: minimum (binary operator); if either operand isnan
, returnsnan
f32.max
: maximum (binary operator); if either operand isnan
, returnsnan
Unary operators are:
f32.abs
: absolute valuef32.neg
: negationf32.ceil
: ceiling operatorf32.floor
: floor operatorf32.trunc
: round to nearest integer towards zerof32.nearest
: round to nearest integer, ties to evenf32.sqrt
: square root
Datatype operators
There are operators for datatype conversions, truncations, reinterpretations, promotions, and demotions.
i32
has the following operators for wrapping, truncating or reinterpreing:
i32.wrap/i64
: wrap a 64-bit integer to a 32-bit integeri32.trunc_s/f32
: truncate a 32-bit float to a signed 32-bit integeri32.trunc_s/f64
: truncate a 64-bit float to a signed 32-bit integeri32.trunc_u/f32
: truncate a 32-bit float to an unsigned 32-bit integeri32.trunc_u/f64
: truncate a 64-bit float to an unsigned 32-bit integeri32.reinterpret/f32
: reinterpret the bits of a 32-bit float as a 32-bit integer
One example is shown below. If you remove i32.wrap/i64
, one error happens due to data type mismatch.
(module
(import "env" "log" (func $log (param i32)))
(func $main
i32.const 1
i64.const 2
i32.wrap/i64
i32.add
call $log
)
(start $main)
)
i64
has the following operators for extending, truncating or reinterpreing:
i64.extend_s/i32
: extend a signed 32-bit integer to a 64-bit integeri64.extend_u/i32
: extend an unsigned 32-bit integer to a 64-bit integeri64.trunc_s/f32
: truncate a 32-bit float to a signed 64-bit integeri64.trunc_s/f64
: truncate a 64-bit float to a signed 64-bit integeri64.trunc_u/f32
: truncate a 32-bit float to an unsigned 64-bit integeri64.trunc_u/f64
: truncate a 64-bit float to an unsigned 64-bit integeri64.reinterpret/f64
: reinterpret the bits of a 64-bit float as a 64-bit integer
f32
has the following operators for demoting, converting or reinterpreing:
f32.demote/f64
: demote a 64-bit float to a 32-bit floatf32.convert_s/i32
: convert a signed 32-bit integer to a 32-bit floatf32.convert_s/i64
: convert a signed 64-bit integer to a 32-bit floatf32.convert_u/i32
: convert an unsigned 32-bit integer to a 32-bit floatf32.convert_u/i64
: convert an unsigned 64-bit integer to a 32-bit floatf32.reinterpret/i32
: reinterpret the bits of a 32-bit integer as a 32-bit float
f64
has the following operators for promoting, converting or reinterpreing:
f64.promote/f32
: promote a 32-bit float to a 64-bit floatf64.convert_s/i32
: convert a signed 32-bit integer to a 64-bit floatf64.convert_s/i64
: convert a signed 64-bit integer to a 64-bit floatf64.convert_u/i32
: convert an unsigned 32-bit integer to a 64-bit floatf64.convert_u/i64
: convert an unsigned 64-bit integer to a 64-bit floatf64.reinterpret/i64
: reinterpret the bits of a 64-bit integer as a 64-bit float