Home > OS >  How can I use arm adcs in loop?
How can I use arm adcs in loop?

Time:12-08

In arm assembly language, the instruction ADCS will add with condition flags C and set condition flags. And the CMP instruction do the same things, so the condition flags will be recovered. How can I solve it ? This is my code, it is doing BCD adder with r0 and r1 :

    ldr r8, =#0
    ldr r9, =#15
    adds r7, r8, #0

ADDLOOP:
    and r4, r0, r9

    and r5, r1, r9

    adcs r6, r4, r5
    
    orr r7, r6, r7

    add r8, r8, #1
    mov r9, r9, lsl #4

    cmp r8, #3
    bgt ADDEND

    bl ADDLOOP

ADDEND:
    mov r0, r7

I tried to save the state of condition flags, but I don't know how to do.

CodePudding user response:

To save/restore the Carry flag, you could create a 0/1 integer in a register (perhaps with adc reg, zeroed_reg, #0?), then next iteration cmp reg, #1 or rsbs reg, reg, #1 to set the carry flag from it.

ARM can't materialize C as an integer 0/1 with a single instruction without any setup; compilers normally use movcs r0, #1 / movcc r0, #0 when not in a loop (Godbolt), but in a loop you'd probably want to zero a register once outside the loop instead of using two instructions predicated on carry-set / carry-clear.


Loop without modifying C

Use teq r8, #4 / bne ADDLOOP as the loop branch, like the bottom of a do{}while(r8 != 4).

Or count down from 4 with tst r8,r8 / bne ADDLOOP, using sub r8, #1 instead of add.

TEQ updates N and Z but not C or V flags. (Unless you use a shifted source operand, then it can update C). docs - unlike cmp, it sets flags like eors. The eq / ne conditions work the same: subtraction and XOR both produce zero when the inputs are equal, and non-zero in every other case. But teq doesn't even set C or V flags, and greater / less wouldn't be meaningful anyway.

This is what optimized BigInt code like GMP does, for example in its mpn_add_n function (source) which adds two bigint inputs (arrays of 32-bit chunks).

IDK why you were jumping forwards over a bl (branch-and-link) which sets lr as a return address. Don't do that, structure your asm loops like a do{}while() because it's more efficient, especially when the trip-count is known to be non-zero so you don't have to worry about running the loop zero times in some cases.


There are cbz/cbnz instructions (docs) that jump on a register being zero or non-zero without affecting flags, but they can only jump forwards (out of the loop, past an unconditional branch). They're also only available in Thumb mode, unlike teq which was probably specifically designed to give ARM an efficient way to write BigInt loops.


BCD adding

Your algorithm has bugs; you need base-10 carry, like 0x05 0x06 = 0x11 not 0x0b in packed BCD.

And even the binary Carry flag isn't set by something like 0x0005000 0x0007000; there's no carry-out from the high bit, only into the next nibble. Also, adc adds the carry-in at the bottom of the register, not at nibble your mask isolated.

So maybe you need to do something like subtract 0x000a000 from the sum (for that example shift position), because that will carry-out. (ARM sets C as a !borrow on subtraction, so maybe rsb reverse-subtract or swap the operands.)


NEON should make it possible to unpack to 8-bit elements (mask odd/even and interleave) and do all nibbles in parallel, but carry propagation is a problem; ARM doesn't have an efficient way to branch on SIMD vector conditions (unlike x86 pmovmskb). Just byte-shifting the vector and adding could generate further carries, as with 999999 1.

IDK if this can be cut down effectively with the same techniques hardware uses, like carry-select or carry-lookahead, but for 4-bit BCD digits with SIMD elements instead of single bits with hardware full-adders.

It's not worth doing for binary bigint because you can work in 32 or 64-bit chunks with the carry flag to help, but maybe there's something to gain when primitive hardware operations only do 4 bits at a time.

  • Related