Search code examples
cassemblyarm

Unable to implement C Logic in ARM assembly


I have a C code in my mind which I want to implement in ARM Programming Language. The C code I have in my mind is something of this sort:

int a;
scanf("%d",&a);
if(a == 0 || a == 1){
   a = 1;
}
else{
   a = 2;
}

What I have tried:

//arm equivalent of taking input to reg r0

//check for first condition
cmp r0,#1
moveq r0,#1

//if false 
movne r0,#2

//check for second condition
cmp r0,#0
moveq r0,#1

Is this the correct way of implementing it?


Solution

  • Your code is broken for a=0 - single step through it in your head, or in a debugger, to see what happens.


    Given this specific condition, it's equivalent to (unsigned)a <= 1U (because negative integer convert to huge unsigned values). You can do a single cmp and movls / movhi. Compilers already spot this optimization; here's how to ask a compiler to make asm for you so you can learn the tricks clever humans programmed into them:

    int foo(int a) {
        if(a == 0 || a == 1){
           a = 1;
        }
        else{
           a = 2;
        }
        return a;
    }
    

    With ARM GCC10 -O3 -marm on the Godbolt compiler explorer:

    foo:
            cmp     r0, #1
            movls   r0, #1
            movhi   r0, #2
            bx      lr
    

    See How to remove "noise" from GCC/clang assembly output? for more about making functions that will have useful asm output. In this case, r0 is the first arg-passing register in the calling convention, and also the return-value register.

    I also included another C version using if (a <= 1U) to show that it compiles to the same asm. (1U is an unsigned constant, so C integer promotion rules implicitly convert a to unsigned so the types match for the <= operator. You don't need to explicitly do (unsigned)a <= 1U.)


    General case: not a single range

    For a case like a==0 || a==3 that isn't a single range-check, you can predicate a 2nd cmp. (Godbolt)

    foo:
            cmp     r0, #3             # sets Z if a was 3
            cmpne   r0, #0             # leaves Z unmodified if it was already set, else sets it according to a == 0
            moveq   r0, #1
            movne   r0, #2
            bx      lr
    

    You can similarly chain && like a==3 && b==4, or for checks like a >= 3 && a <= 7 you can sub / cmp, using the same unsigned-compare trick as the 0 or 1 range check after sub maps a values into the 0..n range. See the Godbolt link for that.