On this page

Conditions

You can control the path a program takes using conditions and the if then else end keywords. This section will help you understand how to use them within WAT and what instructions are available. Controlling the path a program takes depending on different conditions is a fundamental part of designing an application and it is important to learn how to use the instructions WAT contains.

IF...THEN

In your program you will want to do something only if a condition is met, if age is over 65 then add a discount, for example. Let's look at how the if keyword works.

;; Push 1 onto stack
i32.const 1
if
  ;; Because the stack had a non-zero value
  ;; this part will be processed.
end

;; Push 0 onto stack
i32.const 0
if
  ;; Because the stack had a zero value this
  ;; part will NOT be processed.
end

The if instruction will pop the last item off the stack and see if it is 0 or not. Therefore, before the if instruction is used, you need to make sure you have added the condition value onto the stack. This condition value is how the if statements work and how they are controlled. If the condition value is 0 then the code inside the if block is not executed. If the condition value is something other than 0 then it will continue to execute whatever is inside the block.

The condition value can only be an i32 data type. We will look into condition instructions used for different data types later. But, the if instruction can only work when an i32 value is on the stack before it is called.

In the example above, in the first part, we are pushing the conditional value 1 onto the stack. Next the if instruction will pop that value off the stack, see that it is not zero, and then move the execution inside the block.

In the second part of the example, we push the condition value 0 onto the stack. This time the if instruction, after popping it off the stack, will see that it is zero, therefore the condition has not been met and the execution is moved past it, not touching the block of code inside.

IF...THEN...ELSE

But what happens if the condition is not met, can we do something else? This type of “if condition met then do A otherwise do B” can be done using the else keyword.

;; Push 1 onto stack
i32.const 1
if
  ;; Because the stack had a non-zero value
  ;; this part will be processed.
else
  ;; But, if it was a zero then this part
  ;; would have been processed.
end

In this example we are using the else keyword to split the if...end block in two. If the condition value is non-zero then the execution path is moved to the top part, but if the condition value is zero (the condition was not met) then the execution path moves to the bottom block, skipping the top block. Either the top section is executed or the bottom section.

In the example above, because we are pushing the condition value 1 onto the stack, the condition is met and therefore the top section is processed. If we change the code so that we push the value 0 instead, then the condition would not be met, and therefore the bottom section would be executed.

Conditions

The if instruction only needs an i32 value on the stack for it to work. This can get there in a number of different ways. There are different condition instructions that allow you to compare two numbers. We can see if one number is equal to another number for example.

;; Push i32 value 42 onto the stack
i32.const 42

;; Push i32 value 101 onto the stack
i32.const 101

;; Compare the first number with the second one (42 == 101)
i32.eq

;; If 42 is equal to 101
if
  ;; Yes 42 is equal to 101
else
  ;; No 42 is not equal to 101
end

In this example we are pushing two i32 values onto the stack and calling the i32.eq instruction. This “equal” instruction pops the last two values off the stack and compares them, seeing if they are equal or not, and then pushes the result onto the stack. In this case, 42 does not equal 101, and therefore the i32 value 0 is pushed onto the stack. This then allows the if instruction to process the result of the condition and execute the code required, which in this case is the bottom block, the 42 is not equal to 101 section.

;; Push f64 value 3.142 onto the stack
f64.const 3.142

;; Push f64 value 2.718 onto the stack
f64.const 2.718

;; Compare the first number with the second one (3.142 == 2.718)
f64.eq

;; If first number was equal to second
if
  ;; Yes first number was equal to second
else
  ;; No first number was not equal to second
end

This time we are comparing f64 numbers and using its f64.eq instruction. This pops the two f64 values off the stack and compares them, checking if they are both the same, and then pushes the result, an i32 value, onto the stack. The result is not of the same type, it is not a f64 value, but the same i32 data type as before. All comparison instructions return an i32 data type value.

Equal to Zero

This tests if a single value on the stack is zero or not. It is only available to the i32 and i64 data types, there aren't any for floating point numbers. To perform this you use the i32.eqz instruction. It is only looking at one number, so only one item needs to be on the stack before calling the compare instruction.

;; Add i32 value 0 onto the stack
i32.const 0

;; Compare to see if it is zero
i32.eqz

;; The result on the stack is an i32 value of 1

The last value on the stack is 0, which is zero and therefore the compare instruction will push the result 1 onto the stack.

Equal

This compares two numbers and sees if they are the same value. It is available for all data types. To check both numbers are equal you use the i32.eq instruction (or whichever data types you are comparing). This needs two values, of the same data type, pushed onto the stack before you can call the instruction.

;; Add f64 value 3.142 onto the stack
f64.const 3.142

;; Add another f64 value 3.142 onto the stack
f64.const 3.142

;; Compare to see if both are the same
f64.eq

;; The result on the stack is an i32 value of 1

Two f64 values are pushed onto the stack, both of which have the same value. The order you push the values onto the stack does not matter, the result will be the same. Then we call the f64.eq instruction, which pops the two values off the stack, compares them to see if they are equal, and pushes the result onto the stack. In the example above, the result is an i32 data type with the value 1, telling us that both the f64 values are the same.

Not Equal

With this instruction we can compare two numbers and check that they do not have the same value. This can be used with all data types. To perform the check you use the i32.ne instruction. This will need two values pushed onto the stack. The data type needs to be the same as the calling instruction before we call it.

;; Add f64 value 3.142 onto the stack
f64.const 3.142

;; Add another f64 value 2.718 onto the stack
f64.const 2.718

;; Compare to see if both are not the same
f64.ne

;; The result on the stack is an i32 value of 1  

We push two f64 values onto the stack, each containing a different value. The values can be pushed onto the stack in any order, it will not change the result. Afterwards the f64.ne instruction is called, which pops off the two values from the stack, compares to see if they are different to one another, and then pushes the result value onto the stack. The result in the example above is an i32 value of 1, stating that both numbers are not the same.

Greater, Less, or Equal

Comparing if one number is greater or lesser (or equal) than another is done differently between integers and floating point numbers. The f32 and f64 data types are natural numbers and can be positive or negative. But the i32 and i64 data types can be seen as signed (can be negative) or unsigned (positive only) and therefore there are different versions for each comparison instruction.

First let's look at the floating point number comparison.

Instruction Details
gt Greater Than
ge Greater Than or Equal To
lt Less Than
le Less Than or Equal To
;; Add f64 value 3.142 onto the stack
f64.const 3.142

;; Add another f64 value 2.718 onto the stack
f64.const 2.718

;; Compare to see the first > second value
f64.gt

;; The result on the stack is an i32 value of 1  

The first number pushed onto the stack is checked against the second number. The order this is done is important and will affect the result. You are checking if the first value is greater than the second. The f64.gt instruction pops the two values off the stack, compares them, seeing if the first is greater than the second, and pushing the result onto the stack.

Now when it comes to integers we have signed and unsigned versions.

Instruction Details
gt_s Greater Than (signed)
gt_u Greater Than (unsigned)
ge_s Greater Than or Equal To (signed)
ge_u Greater Than or Equal To (unsigned)
lt_s Less Than (signed)
lt_u Less Than (unsigned)
le_s Less Than or Equal To (signed)
le_u Less Than or Equal To (unsigned)
;; Compare the numbers 0xFE000001 and 0x000000AB (signed)
i32.const 0xFE000001
i32.const 0x000000AB
i32.gt_s

;; Result 0xFE000001 (-33554431) > 0x000000AB (171) is 0

;; Compare the numbers 0xFE000001 and 0x000000AB (unsigned)
i32.const 0xFE000001
i32.const 0x000000AB
i32.gt_u

;; Result 0xFE000001 (4261412865) > 0x000000AB (171) is 1  

Here we are comparing two numbers twice. The first time we are looking at the number as being signed (can be negative), and then a second time with them being unsigned (positive only). They are the same two numbers, but with the first number, the first time it looks negative and the second it looks positive.

The first part we are using the i32.gt_s instruction, which is the signed version, and looks at the values 0xFE000001 and 0x00000AB as -33554431 and 171. Because the first number is smaller than the second the result will be 0, it is not greater than. In the second part we use the i32.gt_u instruction, the unsigned version, which looks at the values 0xFE000001 and 0x00000AB as 4261412865 and 171. This time the first number is greater than the second and therefore sets the result as 1.

You need to be careful how you see the integers you are using, signed or unsigned, and which comparison functions you are using. Get it wrong and the results will not be what you expect.

Nested

So far we have looked at making a single condition but how would we handle two or more conditions at the same time. For example, if the age is over 18 and under 65, how do we do this in WAT?

;; Compare $age is over 18
local.get $age
i32.const 18
i32.gt_s

;; If $age > 18
if
  ;; Compare $age is under 65
  local.get $age
  i32.const 65
  i32.lt_s

  ;; If $age < 65
  if
    ;; Age is within range...
  end
end  

In this example we have one condition inside another. We first check if the $age variable is greater or equal to 18. If it is then move inside the if block and then we perform the second condition, which is checking if the $age variable is less than or equal to 65. Here we have nested the second condition check inside the first.

;; Compare $age is over 18
local.get $age
i32.const 18
i32.gt_s

;; Compare $age is under 65
local.get $age
i32.const 65
i32.lt_s

;; Perform bitwise AND on both compare results
i32.and

;; If $age > 18 AND $age < 65
if
  ;; Age is within range...
end

Another way of doing this is to perform the conditions one after the other and then use a logical AND operation on the results. We need to walk through the above example to understand what is happening.

  1. We compare $age to the value 18. We want to know if it is greater than or equal to it.
  2. The result of this condition is pushed on the stack.
  3. We then compare $age again but this time to 65. We want to know if it is less than or equal to it.
  4. The result is pushed onto the stack.
  5. At this point we have the result of the first and second conditions on the stack. Each will be either 1 (for true) or 0 (for false).
  6. Now, we only want to perform something if the age is within range. So we want the age to be greater than 16 AND less than 65. This means we want both the results of the conditions to be 1.
  7. Therefore if we perform a logical AND operation at this point, it will use the two values currently on the stack, and will push the result on the stack.
  8. If both age check conditions are 1 (true), then after performing the AND operation, the result will be 1 also, otherwise it will be 0.
  9. This can then be used with the if instruction. It will function just like before, moving the execution inside if the stack contains a non-zero value.

You can chain condition functions and logical operations together to create more complex condition statements. Choosing which approach to use will depend on what your needs are and what seems best at the time.

Formatting

So far we have used a flat style of formatting, but with S-Expressions you can write them differently, in a more compact way. This can be useful if you have a lot of if statements close together.

;; Push 1 onto stack
i32.const 1

;; If statement with S-Expression
(if
  (then
    ;; Process if true
  )
  (else
    ;; Process of false
  )
)    

The if statement is being used with an S-Expression starting and ending bracket. Also the then and else parts have surrounding brackets. The condition is being set outside, but it can be performed inside.

;; If statement with S-Expression
(if
  (
    ;; Push 1 onto stack
    i32.const 1
  )
  (then
    ;; Process if true
  )
  (else
    ;; Process of false
  )
)  

The condition part has been moved inside the if S-Expressing. It is not labelled, so it must be the first block after the if statement. We can now be able to put everything onto a single line.

;; Check the $n value and set $result
(if (i32.eq (local.get $n)(i32.const 1)) (then (f64.const 1.0) (local.set $result)))
(if (i32.eq (local.get $n)(i32.const 2)) (then (f64.const 2.0) (local.set $result)))
(if (i32.eq (local.get $n)(i32.const 3)) (then (f64.const 6.0) (local.set $result)))    

Everything we want to do, for each condition, now easily fits onto the one line. Our code would start to look bulky if we did the same thing using the previous approach.

Stack

Inside the if statement block we may want to use something that was placed on the stack before, or we may want to add something to the stack that will be used after. Let us look at both with an example.

;; Push i32 value 10 onto the stack
i32.const 10

;; Perform condition
i32.const 1
if
  ;; Add 2 to the 10 value already on the stack
  i32.const 10
  i32.add
end    

In this example we want to add 10 to whatever value was pushed onto the stack before the if statement. Seems perfectly normal, but it will not compile, the wat2wasm tool will output an error message.

;; Perform condition
i32.const 1
if
  ;; Push 20 onto the stack
  i32.const 20
end

Here we want to push the value 20 onto the stack if the condition is met. But what if the condition is not met, nothing will be put on the stack. You will also get an error message when compiling this.

The result for these issues is because we need to see the if statement as its own block, which can have parameter inputs and result outputs. These are just like function's parameter list and result statements. First we need to look at the result part.

;; Perform condition
i32.const 1
if (result i32)
  ;; Push 10 to stack
  i32.const 10
else
  ;; Push 20 to stack
  i32.const 20
end

The if statement block has a new part added, which is saying that the block will add a single i32 value onto the stack when it has finished. If there was no else section then it would not compile and would give an error message. This is because both the true and false parts of the if block need to add a single i32 value onto the stack. If the condition is not met then nothing would be added to the stack which would be unbalanced.

;; Add value to stack
i32.const 42

;; Perform condition
i32.const 1
if (param i32) (result i32)
  ;; Add 10 to parameter and push result onto stack
  i32.const 10
  i32.add
else
  ;; Add 20 to parameter and push result onto stack
  i32.const 20
  i32.add
end

This adds either 10 or 20 to the parameter placed on the stack before the if statement is processed. The block has had a list of parameters added to it. This states that the block is expecting a single i32 value on the stack.

You need to make the if statement balanced when you are pushing and popping items on and off the stack, so that the true part of the block matches the false part. If they do not then the compiler will output an error message about it.