Conditional Branching

So far our programs have been pretty simple. The instructions execute in sequence, one after another, until the end is reached and the program stops. In order to do more complicated things, we have to introduce conditional branching. Conditional branching just means choosing which code to execute based on the state of the program (usually the value of a register). Primarily, conditional branching is used to do variations of the following:

  • Execute code only if a register or memory has a certain value
  • Run the same code over and over (looping)

Branching generally takes at least 2 instructions:

  1. First, a comparison instruction compares two values.
  2. Second, a jump instruction decides whether or not to jump to a particular instruction based on the result of the previous comparison.

Here's a full example program that demonstrates a conditional branch. All it does is set rbx to a value, compare that value to the number 10, and print a different message depending on whether rbx was 10 or not.

%define sys_exit 60
%define success 0

%define sys_write 1
%define stdout 1

%define newline 10

section .data

; Message to be shown if rbx is equal to 10
    msg_equal: db "rbx is 10", newline
    msg_equal_len: equ $-msg_equal

; Message to be shown if rbx is not equal to 10
    msg_unequal: db "rbx is not 10", newline
    msg_unequal_len: equ $-msg_unequal

section .text

global _start
_start:

; Set the input value
    mov rbx, 7

; Compare the input value to 10
    cmp rbx, 10

; Jump to the 'unequal:' label if rbx is not equal to 10
    jne unequal

; Print a message saying that rbx is equal to 10
    mov rax, sys_write
    mov rdi, stdout
    mov rsi, msg_equal
    mov rdx, msg_equal_len
    syscall

unequal:

; Print a message saying that rbx is not equal to 10
    mov rax, sys_write
    mov rdi, stdout
    mov rsi, msg_unequal
    mov rdx, msg_unequal_len
    syscall

; Exit the program
    mov rax, sys_exit
    mov rdi, success
    syscall

The high level overview of this program is:

  1. Set rbx to 7.
  2. Compare rbx to 10.
  3. Jump to unequal: if rbx is not equal to 10.
  4. Print the corresponding message.
  5. End the program.

This is why it's called conditional branching. The first message, "rbx is 10", is only printed if rbx is actually 10. If it's not, that message is skipped. This means that different code executes depending on the result of the comparison.

Now let's look at the program in more detail:

section .data

; Message to be shown if rbx is equal to 10
    msg_equal: db "rbx is 10", newline
    msg_equal_len: equ $-msg_equal

; Message to be shown if rbx is not equal to 10
    msg_unequal: db "rbx is not 10", newline
    msg_unequal_len: equ $-msg_unequal

This is where the output strings are declared, along with their accompanying character counts:

  1. msg_equal is what will be printed if rbx equals 10.
  2. msg_unequal is what will be printed if rbx does not equal 10.
section .text

global _start
_start:

This is the beginning of the program code.

; Set the input value
    mov rbx, 7

Here is where we give rbx its value of 7, which will be compared against 10 in the next instruction.

; Compare the input value to 10
    cmp rbx, 10

This is the cmp instruction, which is short for "compare". This instruction compares two values. In this case, we're comparing the value of rbx against the integer 10. The possibilities are:

  • rbx is greater than 10
  • rbx is equal to 10
  • rbx is less than 10

This instruction does not act on the result of the comparison by itself, but it sets things up so the next instruction can.

; Jump to the 'unequal:' label if rbx is not equal to 10
    jne unequal

This is a conditional jump. Normally, code executes sequentially: one instruction after another. A jump interrupts the normal flow of code execution, jumping somewhere else instead of executing the next instruction.

There are several different kinds of jumps. Some examples are given below:

Instruction Meaning
jmp Unconditional jump (always jump)
je Jump if equal
jne Jump if not equal
jl Jump if less than
jle Jump if less than or equal
jg Jump if greater than
jge Jump if greater than or equal

In this case, we're using the jne instruction, which stands for "jump if not equal". The jump is performed conditionally, based on the result of the previous cmp instruction: if rbx is not equal to 10, execution will jump ahead to the unequal: label. If rbx is equal to 10, the jump is not performed.

You may be wondering how the jne instruction acts on the outcome of the cmp instruction. The cmp instruction compares two values and then stores the result of that comparison in a special-purpose register called rflags. The conditional jump instructions use the state of rflags to decide whether or not to jump.

So the cmp instruction writes to the rflags register, then conditional jump instructions like jl and jge use the values in rflags to decide whether to jump.

In this case, because rbx is 7, and 7 is not equal to 10, the jne instruction will jump ahead to the unequal: label. If rbx was set to 10, it would not jump. In that case, execution would continue sequentially like normal.

; Print a message saying that rbx is equal to 10
    mov rax, sys_write
    mov rdi, stdout
    mov rsi, msg_equal
    mov rdx, msg_equal_len
    syscall

This is the code that runs if rbx happened to be equal to 10. It prints out a message informing the user that rbx is equal to 10. With rbx set to 7, this code will be skipped.

unequal:

This is called a label. A label lets you name a section of code so that it can be referred to elsewhere. Labels can be jumped to, meaning that you can tell the computer to run the first instruction after the specified label instead of running the next instruction in order like normal.

This is where the jne instruction jumps to in the event that rbx is not equal to 10. The previous instructions will be skipped and execution will resume after this label.

You may notice that this looks a lot like the _start: line in every program we've written so far. _start: is also an example of a label. What makes _start: unique is that when nasm assembles your program, it looks for a special label named _start for where the program begins.

While _start: marks the beginning of the program, unequal: marks the beginning of the code which handles the situation where rbx is not equal to 10.

In higher-level languages, jumping is often referred to as goto, and you may have heard that goto is bad programming practice and should be avoided. In higher-level languages, that's mostly true: you should only use a goto in a few very specific situations. But this is assembly language, baby! There's no other game in town. This is the only way to implement conditionals and loops in assembly, so get your fill of goto here!

; Print a message saying that rbx is not equal to 10
    mov rax, sys_write
    mov rdi, stdout
    mov rsi, msg_unequal
    mov rdx, msg_unequal_len
    syscall

This is the code which runs when rbx is not equal to 10. It prints out a message saying something to that effect.

; Exit the program
    mov rax, sys_exit
    mov rdi, success
    syscall

Here is where the program ends. Type the previous program into a file called "conditional.asm" and run it. You should see the following output:

rbx is not 10

Success! Or is it? Try changing the line:

mov rbx, 7

To this:

mov rbx, 10

With rbx set to 10, this should change the result of the cmp instruction, which should in turn change the behavior of the jne instruction, causing the jump to unequal: not to happen. Run it and see what happens:

rbx is 10
rbx is not 10

Whoops. Both messages printed. Let's step through the code and see what's going on.

mov rbx, 10

First, rbx is set to 10.

cmp rbx, 10

Next, rbx is compared to 10. rbx is equal to 10.

jne unequal

Here, execution skips ahead to the unequal: label if rbx is not equal to 10. Since it is equal to 10, the jump will not occur. Execution will continue to the next line sequentially.

; Print a message saying that rbx is equal to 10
    mov rax, sys_write
    mov rdi, stdout
    mov rsi, msg_equal
    mov rdx, msg_equal_len
    syscall

Here's the first message that printed: "rbx is 10". So far so good.

unequal:

; Print a message saying that rbx is not equal to 10
    mov rax, sys_write
    mov rdi, stdout
    mov rsi, msg_unequal
    mov rdx, msg_unequal_len
    syscall

Here's where the second message is printed: "rbx is not 10". This is the erroneous message.

And that's the problem: the unequal: code runs in both cases. If rbx is not equal to 10, the first message is skipped, printing only the second message. But if rbx is equal to 10, the second message is printed as well. Execution falls through from the first message to the second.

To get around this, we need a second jump. After the first message is printed, we need to jump ahead, skipping the second message. That way, only one message will ever print. Take a look at the fixed program in its entirety:

%define sys_exit 60
%define success 0

%define sys_write 1
%define stdout 1

%define newline 10

section .data

; Message to be shown if rbx is equal to 10
    msg_equal: db "rbx is 10", newline
    msg_equal_len: equ $-msg_equal

; Message to be shown if rbx is not equal to 10
    msg_unequal: db "rbx is not 10", newline
    msg_unequal_len: equ $-msg_unequal

section .text

global _start
_start:

; Set the input value
    mov rbx, 10

; Compare the input value to 10
    cmp rbx, 10

; Jump to the 'unequal:' label if rbx is not equal to 10
    jne unequal

; Print a message saying that rbx is equal to 10
    mov rax, sys_write
    mov rdi, stdout
    mov rsi, msg_equal
    mov rdx, msg_equal_len
    syscall

; Skip ahead to the done label
    jmp done

unequal:

; Print a message saying that rbx is not equal to 10
    mov rax, sys_write
    mov rdi, stdout
    mov rsi, msg_unequal
    mov rdx, msg_unequal_len
    syscall

done:

; Exit the program
    mov rax, sys_exit
    mov rdi, success
    syscall

It's almost identical. We've added a new jmp instruction and a new label called done:. Let's take a look at these in more detail:

; Skip ahead to the done label
    jmp done

After the first message, which is printed only when rbx equals 10, we come to this unconditional jump instruction. This instruction always jumps to the given label, regardless of any conditions. This causes the computer to skip the second message if it printed the first message.

done:

This is the jump target for when the first message is finished printing.

Make the changes above or create a new file and run it. Try with rbx set to a few different values. If rbx is 10, you should see:

rbx is 10

If it's not 10, you should see:

rbx is not 10

But you should never see both messages at once.

Next section: Looping