Introduction to WebAssembly Text
Introduction to WebAssembly Text
Previous  Next  

Arrangement of WAT Instructions  Up  Loops 
7: Conditions
WebAssembly includes the highlevel flow control statements if/then/else/end
for conditional branching. Unless you explicitly state otherwise, the if
statement must complete without leaving any values behind on the stack.
Simple Value Test
Using sequential notation, we can test if the local variable $my_value
equals zero like this. Place the i32
value in question on top of the stack, then simply invoke the if
statement:
local.get $my_value ;; Stack = [5]
if
;; True if the top of the stack contains a nonzero i32
else
;; False if the top of the stack contains a zero i32
end
The point here is that as long as an expression leaves an i32
value on top of the stack, that value can be treated as a Boolean describing the outcome of a condition. The if
statement then interprets the value as false
if it is zero, and true
for anything nonzero.
It’s as simple as that.
We could also write the same condition as an Sexpression. But notice some important syntactical differences:
(if (local.get $my_value)
(then
;; True if the top of the stack contains a nonzero i32
)
(else
;; False if the top of the stack contains a zero i32
)
)
In sequential notation, the then
keyword is not used but the end
keyword is. However, if you fold an if
statement into an Sexpression then:
 The entire
if
statement must be enclosed in parentheses  The
then
branch is mandatory, theelse
branch is optional  The
then
andelse
branches must be enclosed in parentheses  The
end
keyword is redundant now because its place has been taken by the closing parenthesis
Simple Comparison Tests
Simply testing whether a local i32
variable contains a nonzero value is not a very realistic usecase for if
. More realistically, we will need to compare two values: for instance, whether some counter has reached a particular limit.
The following code^{1} sample is part of a larger loop construct, but at the moment, the condition is the part that interests us. Here, we want to repeat a particular set of instructions 5 times; so our counter starts at zero and each time around the loop (not shown in this code), we check that the hard coded limit (i32.const 5
) remains greater than our counter.
(local $counter i32)
;; Is the limit greater than the counter?
(if (i32.gt_u (i32.const 5) (local.get $counter))
(then
;; Yes, the top of stack is nonzero, so the loop continues
;; Do something here
;; Increment the counter
(local.set $counter (i32.add (local.get $counter) (i32.const 1)))
)
(else
;; Nope, the top of the stack is zero, so the loop terminates
)
)
Remember, we have a choice over how integer values are to be interpreted.
Consequently, all integer comparison statements must identify not only the type of comparison to be performed (lt
, gt
, le
, ge
etc), but must additionally specify whether the i32
is to be treated as a signed or unsigned value — hence the comparison operations end with the additional suffix of _s
or _u
for signed or unsigned respectively.
In this case, it makes no sense to check whether we’ve gone round a loop a negative number of times, so there is no need to treat $counter
as a signed value: hence we test for i32.gt_u
(greater than, unsigned) as opposed to i32.gt_s
(greater than, signed)
Using if
as an Expression
Up til now, the way we have used the if
statement assumes that it does not leave a value behind on the stack.
But what if we do want to leave a value on the stack; say, if we’re performing a conditional assignment?
Consider this little block of JavaScript code that is actually used to optimize performance when calculating the Mandelbrot Set. For some pixel location on the screen (given by x
and y
), we can avoid the expensive call to function mjEscapeTime()
by checking whether this particular pixel is located within certain areas of the Mandelbrot Set.
let iters = isInMainCardioid(x, y)  isInPeriod2Bulb(x,y)
? max_iters
: mjEscapeTime(x, y)
The important point to understand here is that the value assigned to the variable iters
is determined by the outcome of a condition. Here, we are checking whether the current pixel at location (x,y)
falls within the Mandelbrot Set’s main cardioid (the big heartshaped blob in the centre) or within the period2 bulb (the smaller circle to the left). If it does, then we can bypass the expensive call to mjEscapeTime()
and can arbitrarily set the value of iters
to the maximum iteration value.
Q: That’s nice, but how do we replicate this construct in WebAssembly?
A: We can transform if
from a statement into an expression by assigning it a return type
The implementation of functions $is_in_main_cardioid
and $is_in_period_2_bulb
is not important here, suffice it to say that these functions both return i32
values that can be treated as Booleans.
;; Set $iters to whatever i32 value is returned from the if expression
(local.set $iters
;; The "(result i32)" clause declares that the if statement will leave an i32 value on the stack
(if (result i32)
;; We can avoid running the escape time calculation if the point lies either
;; in the main cardioid or the period2 bulb
(i32.or
;; Main cardioid check returns an i32
(call $is_in_main_cardioid (local.get $x) (local.get $y))
;; Period2 bulb check returns an i32
(call $is_in_period_2_bulb (local.get $x) (local.get $y))
)
(then
;; Yup, so no need to run the escape time algorithm
(local.get $max_iters)
)
(else
;; Nope, we need to run the calculation
;; snip
)
)
)
Whatever i32
value the if
expression leaves on the stack, is then assigned to the local variable $iters
.
Selecting Between Different Values
A very useful variation on the if
statement is select
. This expression consumes the top three values from the stack. It returns the value pushed either first or second, based on the value pushed third (the outcome of a comparison).
For example:
(module
(func (export "do_select")
(result i32)
(local $lower_limit i32)
(local $upper_limit i32)
(local $test_val i32)
(local.set $lower_limit (i32.const 20))
(local.set $upper_limit (i32.const 100))
(local.set $test_val (i32.const 40))
(select
(i32.const 111) ;; Return this value if true. Stack = [111]
(i32.const 999) ;; Return this value if false. Stack = [999,111]
;; Is $test_val greater than $lower_limit? Stack = [1,999,111]
(i32.gt_u (local.get $test_val) (local.get $lower_limit))
) ;; Stack = [111]
)
)
Running this WAT file using wasmer
gives:
wasmer 07select.wat i do_select
111

From here on, we will always use the Sexpression notation, because this is easier to read. ↩