Chapter 16: Condition Flags, Carry & Borrow
Back in Chapter 10, you've learned about Compare instructions. At this point, the way they work may seem like Black Magic. What Compare instructions actually do is modify what is known as the Condition Flags. These flags reside in a Special Purpose Register called NZCV. Conditional Branch instructions will analyze these flags to determine if the execution flow of the CPU will take the branch. Here are all the Condition Flags...
Generally speaking, the condition flags can only be set by an instruction that is appended with an "s" (like the subs instruction that was covered back in Chapter 12: Loops). The cmp instruction is *actually* a subs instruction. There is also an inverse instruction for cmp, which is cmn (compare negative). The cmn instruction is actually an adds instruction.
The Negative Flag is set whenever any S-appended instruction has a result that is in the negative signed range. For a zero or positive result, the Negative Flag is cleared.
The Zero Flag is set when a result is 0. Otherwise it is cleared.
For addition operations, the Carry Flag gets set if the result exceeded 0xFFFFFFFF/0xFFFFFFFFFFFFFFFF. If not, the flag gets cleared. For subtraction operations, the Carry Flag gets set if the result is 0 or higher. Otherwise, the flag gets cleared.
The Overflow Flag gets set when a result went from <=0x7FFFFFFF to >=0x80000000 (or <=0x7FFFFFFFFFFF to >=0x800000000000). It will also get set when a result does the opposite action (aka underflow i.e. going from >=0x80000000 to <= 0x7FFFFFFF). Otherwise, it will be cleared.
Due to how the Carry Flag operates, this allows us to utilize true carry and borrow. For example, a Carry can occur from an Addition operation where the unsigned upper limit has been breached. Let's say we have the following instruction...
adds w3, w3, #5 //Remember, we need the appended s so the Condition Flags will be set.
...And w3 (before the instruction executes) contains the value of 0xFFFFFFFF. When the instruction does execute, w3 will result as 0x00000004, due to the unsigned upper limit being surpassed. Also, the Carry Flag will now be set.
Let's go over an example of the Carry Flag being cleared from a sub instruction, which signals a Borrow has occurred.
Example instruction:
subs w4, w4, #8 //Remember, we need the appended s so the Condition Flags will be set.
w4 (before instruction executes) = 0x00000002
When the instruction gets executed, w4's result is 0xFFFFFFFA. The result occurred due to going below past zero. Because of this, the Carry Flag has been cleared to indicate a Borrow has occurred.
Knowing this allows us to implement something such as 64-bit addition that is capable of working with 128-bit results. Obviously, there are no 128-bit sized GPRs, so we will need our result to use a pair of GPRs. Here's a snippet of code that has the 128-bit input in x0 and x1. It will be added with a 64-bit 2nd input value that's in x2. The result will be placed back into x0 and x1. x0 holds the upper 64-bits, x1 holds lower 64-bits...
adds x1, x1, x2
adc x0, x0, wzr
The first instruction is to add up the lower 64-bits (x1) of the current number to the 64-bit adder (x2). The Condition flags need to be set for Carry to work, hence why "s" is appended in the add instruction (adds). Therefore if a situation arises where something such as 0xFFFFFFFFFFFFFFE + 0xB occurs, the Carry will be set.
The second instruction is called "Add with Carry". The instruction can only use two source registers, no Immediate Value usage allowed. The adc instruction adds the two Source Registers together, and then adds the Carry Flag to that sub-total to come up with the final total to place in the Destination Register. Therefore the following is performed...
x0 = x0 + wzr + Carry Flag
The wzr register is used because the adc instruction requires two source registers to be provided. We don't require a 2nd source register in our calculations, but we need to provided something for the instruction to be valid, so the wzr register is perfect in this scenario.
To prove the above snippet of code works, let's plug some numbers into it.
Before code is executed...
x0 = 0x0000000000000000
x1 = 0xFFFFFFFFFFFFFFFE
x2 = 0x000000000000000B
The first instruction (adds) will add x1 and x2 together. The result is 0x0000000000000009. The Carry Flag is set as well. Thus it has the value of 1. Now the 2nd instruction (adc) gets executed. The following 3-value addition is performed...
0 + 0 + 1
The first 0 is what was in x0. The second 0 is the wzr register. The 1 is the Carry Flag. The result of this entire addition is 1 and is placed back into x0. Thus x0 is now 0x0000000000000001. Now connect x0 and x1 for a 128-bit value and you have... 0x00000000000000010000000000000009. As you can see the addition was performed correctly and we have our 128-bit value.
Let's quickly go over a subtraction example. 128-bit current input (x0 & x1) that will use 64-bit subtraction (x1 & x2). Here's the code..
subs x1, x1, x2 //Perform the 64-bit subtraction, update Carry Flag. If a borrow occurs, Carry Flag will be cleared. If not, it will be set high.
sbc x0, x0, wzr //Perform x0 = x0 - wzr - !CarryFlag
Alright so the term "!CarryFlag" in the notes of the 2nd instruction means the opposite value of whatever the Carry Flag currently contains. Let's plug in some numbers to show this snippet of code works.
Before code is executed..
x0 = 0x0000000000000002
x1 = 0x0000000000000001
x2 = 0x0000000000000003
1st instruction will do 1 minus 3. Result is 0xFFFFFFFFFFFFFFFE. Carry Flag is cleared because a borrow occurred. 2nd instruction will do... x0 = x0 - wzr - 1 Remember the sbc instruction uses "!CarryFlag" (the opposite/flipped value of what the Carry Flag currently is). Carry Flag is 0 so a 1 is used in the sbc instruction. The result of the sbc instruction is that x0 now equals 0x0000000000000001. Now connect x0 and x1 together to see the 128-bit result.
0x0000000000000001FFFFFFFFFFFFFFFE
Voila! It works.