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
d0, then checking if
d0 is equal to
zero. If it is, we branch to the label
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.
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
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),
than), etc. are actually checking for a set of condition flags that do what we
expect based on the mnemonic.
bge respects signed values, so it has to check some complicated
- 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" (
0xffff) or vice-versa. (Only if you cross the
barrier; going from
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
0x0001, which is not negative, and
Likewise, 3 ≥ -2 because
0x0005, which is not negative,
and didn't overflow.
But also, 127 ≥ -16, because
0x8010, which IS negative,
and it DID overflow.
We also have
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
bcc (branch if Carry set/clear). The Carry flag
gets set when an unsigned overflow happens (like incrementing from
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" (
d0 is 10. Otherwise, it gets cleared
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
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
or'd with d2 (which just
0xff). If there was NOT a carry,
d3 gets cleared to
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)