Assembly Adc (Add with Carry) to C++

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 the AH 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 the ADC instruction is whatever was set previously by the ADD 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:

  1. It moves NUMBER1 into the AL register: AL = NUMBER1

  2. It adds NUMBER2 to the AL register: AL = NUMBER1 + NUMBER2

  3. It clears AH: AH = 0

  4. It sets AH equal to CF, as set by the addition of NUMBER1 and NUMBER2. 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?

  1. 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


  1. 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



Leave a reply



Submit