Assembly ADC (Add with carry) to C++
ADC is the same as ADD but adds an extra 1 if processor's carry flag is set.
ADC instruction in asm
To figure this out, start by looking up what each instruction does:
MOV AH, 00H
This
MOV
instruction will set theAH
register to 0 without affecting flags.ADC AH, 00H
This
ADC
instruction will add the source operand (0), the carry flag (CF), and the destination operand (AH
), storing the result in the destination operand (AH
).Symbolically, then, it does:
AH = AH + 0 + CF
Remember that the
MOV
did not affect the flags, so the value of CF that is used by theADC
instruction is whatever was set previously by theADD
instruction (in line 2).Also,
AH
is 0 at this point, so this is really just:AH = CF
.
And now you know what the code does:
It moves
NUMBER1
into theAL
register:AL = NUMBER1
It adds
NUMBER2
to theAL
register:AL = NUMBER1 + NUMBER2
It clears
AH
:AH = 0
It sets
AH
equal to CF, as set by the addition ofNUMBER1
andNUMBER2
. Thus,AH
will be 1 if the addition required a carry, or 0 otherwise. (AH = CF
)
As for the purpose of this code, it clearly performs a 16-bit addition of two 8-bit numbers. In a pseudo-C, it would basically be:
BYTE NUMBER1;
BYTE NUMBER2;
WORD RESULT = (WORD)NUMBER1 + (WORD)NUMBER2;
where the BYTE-sized inputs are extended to WORDs and added together. Why do this? Well, to handle overflow. If you add together two 8-bit values, the result may be larger than will fit in 8 bits.
The real trick to understanding this may be that the AL
and AH
registers are the lower and upper bits, respectively, of the AX
registers. So immediately after these instructions, you may see AX
being used. This contains the 16-bit result of the addition of NUMBER1
and NUMBER2
.
ARM 7 Assembly - ADC with immediate 0
The arm32 is only 32 bits. The value 'a' is 64bits. The instructions that you are seeing are to allow computations of sizes larger than 32bits.
rsb r2, r2, r2, lsl #4 # 15*b -> b*16-b
adds r0, r2, r0 # a+(15*b) !LOW 32 bits! could carry.
adc r1, r1, #0 # add a carry bit to the high portion
lsrs r1, r1, #1 # divide high part by 2; (a+(15*b))/2
rrx r0, r0 # The opposite of add with carry flowing down.
Note: if you are confused by the adc
instruction, then the rrx
will also be confusing? It is a 'dual' of the addition/multiplication. For division you need to take care of underflow in the higher part and put it in the next lower value.
I think the important point is that you can 'carry' this logic to arbitrarily large values. It has applications in cryptography, large value finance and other high accuracy science and engineering applications.
See: Gnu Multi-precision library, libtommath, etc.
How is the carry flag being set in this assembly code?
- but how can we be sure that the carry flag is set to 1? What previous operation would guarantee this?
$80/8137 38 SEC ; SEt Carry flag
uint8_t carry = HIGH(v1) ? 1 : 0;
Why would it work this way? I read this as, "Set the carry flag if and only if the high byte is non-zero." But wouldn't the indication of an overflow be only if the high byte is zero?
The addition ADC #$0011
is using the carry from ADC $02,s
. When ADC $02,s
is performed, the accumulator is set to 8-bit (because of SEP #$20
), so the carry flag will be set if the result of ADC $02,s
would've exceeded 8 bits (i.e. if you would've got something >= $100 in 16-bit mode).
In the C version you've got a 16-bit variable (v1
) to hold the result, so your carry will be in bit 8 of v1
, which you can test with HIGH(v1) == 1
, or simply HIGH(v1)
since it will either be 1 or 0.
Carry flag addition instruction different to adc $0?
It's the best way.
The other options include using jc
(to skip an inc
), cmovc
(probably with lea
for a no-flags add-1), or setc
. With setc:
xor %edi, %edi
add %rax, %rcx
setc %dil # %rdi = CF, since we already zeroed the upper bytes
add %rdi, %rdx # no partial-register stall/extra uop from reading rdi after writing dil, because we used a recognized zeroing idiom (xor) to zero it.
These options are obviously much worse, even though adc
is a 2-uop instruction on Intel CPUs before Broadwell. (Since Haswell introduced the hardware changes for FMA to decode to a single uop, Broadwell and Skylake also handle some other 3-input instructions as a single uop, including adc
and cmov
.)
An even worse option for getting CF into a register would the slow rotate-with-carry rcl
(on a zeroed register).
Would an Instruction Set Architecture benefit from both an ADC and SBC, or could all carry instructions repeat the previous type?
It happens every once in a while. For example, a typical use case of sbc
following an add
is to materialise the carry into a register:
add r0, r1, r2 # r0 = r1 + r2
sbc r3, r3, r3 # r3 = -1 if addition produced carry, 0 otherwise
Another use case is using adc
as a left-rotate-through-carry, accumulating bits into a register. The bits to be accumulated might come from arbitrary sources, not all of which cause the instruction to be preceded by addition.
Another issue you should consider is that when an interrupt occurs, you need to remember if an addition or subtraction was just happening so you can execute the next rwc
instruction correctly. So there must be an extra status bit in the flags register keeping track of that.
My advice: do not implement this operation. Instead implement subtraction as a reverse subtraction (i.e. dest = src - dest
instead of dest = dest - src
) that does not alter flags. Then you can implement subtraction with carry by first subtracting from 1 (to compute the complement) and then adding with carry.
Another option is to implement a skip instruction that checks the condition flags for a condition and skips the next instruction if the condition is met. Then you can implement both adc
and sbc
by conditionally skipping an instruction to add or subtract 1 (respectively) before performing the addition or subtraction. But note that this will cause the carry to differ for when the input is 0
, so you might need to tweak the design. Another side effect is that you don't need conditional jumps anymore; these become skip if not condition + unconditional jump.
A third option keeping closer to your previous idea is to instead build an “apply carry in” instruction that modifies the previous instruction's result according to the old carry in. This instruction would require two additional status flags (one for add/sub, one for carry in) but seems to be more flexible. It could also be expanded into a general modifier instruction, e.g. distinguishing between ror
and rcr
and others.
If you are looking for inspiration for such a restricted instruction set, consider looking at the PDP-8, a famous computer with just 8 instructions that nevertheless elegantly provides all required operations.
How do I use ADC in assembly?
You appear to be working with decimal math, but you're not using AAA
. ADC
does what you'd expect, but with that code there is never an actual carry from the first addition (9+9 is not bigger than 255, after all).
So the solution, of course, is to use AAA
, like this (not tested):
add al,dl
aaa ; takes no arguments and works on al
add ah,dh ; adc not necessary, aaa already incremented ah if there was a carry
AAA
(ASCII Adjust for Addition) tests if al
is bigger than 9, and if so:
- adjusts
al
, - sets the carry flag, and
- increments
ah
Related Topics
Special Simple Random Number Generator
Setjmp/Longjmp: Why Is This Throwing a Segfault
How to Restart My Own Qt Application
Detach a Pointer from a Shared_Ptr
How to Deal with Global-Constructor Warning in Clang
What Is a Curly-Brace Enclosed List If Not an Intializer_List
C++ Concept That Requires a Member Function with an Outputiterator as Parameter
Load Image with Opencv Mat C++
Define Constant Variables in C++ Header
Serializing Opencv Mat_<Vec3F>
Call Destructor and Then Constructor (Resetting an Object)
C++ Abstract Class Without Pure Virtual Functions
Two Phase Name Lookup for C++ Templates - Why
What Is "Strip" (Gcc Application) Used For