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:
- Negative flag is clear AND Overflow flag is clear, OR
- 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)