There is no "if" in Assembly

If there was, it would be spelled "Assemblify", which is now the name of your next startup.

So, the cool thing about Assembly is that whenever you perform an instruction, the CPU runs a bunch of comparisons automatically! They are stored in "condition flags", which you can use to branch (goto) or do other stuff if the right conditions are met.

Consider this code:

    add.w   d1,d0   ; Add register d1 to d0
    cmp.w   #0,d0   ; Is d0 equal to 0?
    beq     .zero   ; If so, branch to .zero

The above should be pretty readable, even if you're not used to looking at Assembly. We're adding register d1 to d0, then checking if d0 is equal to zero. If it is, we branch to the label .zero.

However, we are not yet leveraging the magic of condition flags. We can accomplish the same thing like this:

    add.w   d1,d0   ; Add register d1 to d0
    beq     .zero   ; If zero, branch to .zero

We saved a whole CPU instruction for free! Congrats, you're now entitled to add "blazing fast" to your GitHub project description.

The reason this works is that whenever you do things like move data or perform arithmetic, the CPU automatically sets condition flags based on the result. Some examples of condition flags are "Zero", "Negative", "Carry".

When we do our add in the above code, the CPU will set the "Zero" condition flag if the result is zero—otherwise, it will clear the flag.

beq stands for "Branch if equal", but it actually checks for the Zero flag, not for equality to some other value. That's why we don't have to explicitly compare to 0 first.

That gets interesting in an example like this:

    add.w   d1,d0   ; Add register d1 to d0
    cmp.w   #10,d0  ; Is d0 equal to 10?
    beq     .ten    ; If so, branch to .ten

This time, instead of comparing to zero, we're comparing to 10, and we're branching if equal.

But hang on—if beq is checking for the Zero flag, how does this make sense? Aren't we comparing to 10?

The Zero flag

As it turns out, the cmp instruction performs a subtraction under the hood. Unlike sub, the real subtraction operand, cmp throws away the result. However, it still sets all the condition flags.

So, when we run cmp.w #10,d0, we are subtracting 10 from d0. Well, if d0 is also 10, then we get 10 - 10 = 0, and the CPU sets the Zero flag. That's why beq is called "branch if equal" but checks for zero!

Checking other conditions

All the human-readable mnemonics like bge (greater or equal), blt (less than), etc. are actually checking for a set of condition flags that do what we expect based on the mnemonic.

For example, bge respects signed values, so it has to check some complicated conditions:

  1. Negative flag is clear AND Overflow flag is clear, OR
  2. Negative flag is set AND Overflow flag is set

The Overflow flag gets set if you move from "positive" (0x0000-0x7fff) to "negative" (0x8000-0xffff) or vice-versa. (Only if you cross the 0x8000 barrier; going from 0xffff to 0x0000 does not set Overflow, because that's like a signed integer going from -1 to 0, which is not an overflow.)

So, 3 ≥ 2 because 0x0003 - 0x0002 = 0x0001, which is not negative, and didn't overflow.

Likewise, 3 ≥ -2 because 0x0003 - 0xfffe = 0x0005, which is not negative, and didn't overflow.

But also, 127 ≥ -16, because 0x7fff - 0xfff0 = 0x8010, which IS negative, and it DID overflow.

We also have bhi and blo (branch if higher/lower), which don't use the Overflow flag. That means you can use these to compare unsigned values. (Assembly has no concept of signed/unsigned values—all that matters is which condition flags you check for in your comparisons!)

Also popular are bcs and bcc (branch if Carry set/clear). The Carry flag gets set when an unsigned overflow happens (like incrementing from 0xff to 0x00), or when you're bit shifting and a 1 bit leaves the edge.

Here's a straightforward example where bcc is used to clamp a value to a maximum of 255:

    move.w      (a2)+,d2    ; Get color value to d2
    add.b       #$80,d2     ; Add 128
    bcc         .1          ; If no carry, move on
    moveq       #-1,d2      ; Otherwise, set d2 to -1 (255)
.1: jsr         cfx_hsv2rgb ; Apply color effect

Other uses of the condition flags

Usually, CPUs have various other instructions that use the condition flags, not just branch instructions. m68k Assembly doesn't have many, but one example is sxx, eg. "set on a certain condition".

    add.w   d1,d0   ; Add register d1 to d0
    cmp.w   #10,d0  ; Is d0 equal to 10?
    seq     d1      ; If so, set d1 to $ff

In the above, d1 gets "set" (0xff) if d0 is 10. Otherwise, it gets cleared (0x00).

You can sometimes use this to avoid branching completely. Here is an alternative to my previous example that used bcc to clamp to 255, but instead using scs to do the same thing without branching.

    move.w      (a2)+,d2    ; Get color value to d2
    add.b       #$80,d2     ; Add 128
    scs         d3          ; If carry, set d3
    or.b        d3,d2       ; Combine result with d2
    jsr         cfx_hsv2rgb ; Apply color effect

If there was a carry, d3 gets set to 0xff and or'd with d2 (which just makes d2 0xff). If there was NOT a carry, d3 gets cleared to 0x00, and the or does nothing.

The old example using bcc took 12-14 cycles, while this one only takes 8-10 cycles. We're blazing fast again!

Imagine not having condition flags and being forced to nest your code in a bunch of "if" statements (this post was written by the m68k Assembly gang)