1

I am writing a main.s program in ARM assembly and running it together with a checkPrimeNumber.c program . The program prompts the user to enter two positive numbers and output the prime numbers between them. For example if you enter 2 and 6 the prime numbers that would output would be 3 and 5. The checkPrimeNumber function is called in main to check whether a given integer n is a prime number or not. I am running into an issue were if I enter two numbers, I get the value of 1 each time I try and run the program.

I ran the program in the debugger and fixed some of the registers so that they were not taking up space in the program, but that did not change the result. I am unsure if this is a memory allocation issue or if I did not do the comparison correctly in my program. For reference, I have provided my main.s code and the checkPrimeNumber.c code

main.s

.cpu cortex-a53
.fpu neon-fp-armv8

@ Data section
.data

inp1:   .asciz "Enter two positive numbers"
inp2:   .asciz "%d %d"
outp:   .asciz "Prime numbers between %d and %d are: %d\n "

.balign             @ Align the data section to 4 bytes

n1:   .word 0
n2:   .word 0
i:    .word 0
flag: .word 0

.text
.align 2               @ Align the code section to 2 bytes
.global main           @ Declare 'main' as a global symbol
.type main, %function   @ Specify the type of the 'main' symbol as a function

main:
    push {fp, lr}      @ Save the frame pointer (fp) and link register (lr) to the stack
    add fp, sp, #4     @ Set the frame pointer (fp) to the current stack pointer (sp)

    @ Print the prompt "Enter two positive numbers"
    ldr r0, =inp1
    bl printf

    @ Scanf ("%d %d", &n1, &n2)
    ldr r0, =inp2
    ldr r1, =n1        @ Load the memory address of n1 into r1
    ldr r2, =n2        @ Load the memory address of n2 into r2
    bl scanf

    @ Initialize flag = 1 (to assume that numbers are prime)
    ldr r0, =flag
    mov r5, #1
    str r5, [r0]

    @ Load the input values from memory into r1 and r2
    ldr r1, =n1        @ Load the value of n1 into r1
    ldr r8, [r1]       @ Load the content of the memory address pointed by r1 into r1
    ldr r2, =n2        @ Load the value of n2 into r2
    ldr r6, [r2]       @ Load the content of the memory address pointed by r2 into r2

    @ Initialize i = n1 + 1
    add r3, r8, #1

forloop:              @ Loop label (start of the loop)
    @Check if i < n2
    cmp r3, r2         @ Compare i with n2
    bgt done           @ If i is greater than or equal to n2, exit the loop

    @ Call the function checkPrimeNumber(i) and store the result in 'flag'
    mov r0, r3         @ Move the current value of i to r0 (the function argument)
    bl checkPrimeNumber
    

    @ Check if flag == 1 (if the number is prime)
    cmp r0, #1
    bne increment_num  @ If not equal to 1, go to increment_num label and proceed to the next iteration

    
   @ Print the output statement "Prime numbers between %d and %d are: "
    ldr r0, =outp     @ print out end prompt 
    mov r1, r8        @ Move the first range value (n1) to r1 (for printf)
    mov r2, r6         @ Move the second range value (n2) to r2 (for printf)
    mov r4, r3        @move the resulting prime numbers (i) to r4 (for printf)
    bl printf
   
 increment_num:    @ Increment the loop variable (i) and continue to the next iteration
    add r3, r3, #1
    b forloop          @ Branch back to the forloop label

done:
    sub sp, fp, #4     @ Restore the stack pointer (sp) to its original value
    pop {fp, pc}       @ Restore the frame pointer (fp) and program counter (pc) to return from the function


checkPrimeNumber.c

int checkPrimeNumber(int n);
int main(void) {
    int n1, n2, i, flag;
    printf("Enter two positive integers: ");
    scanf("%d %d", &n1, &n2);
    printf("Prime numbers between %d and %d are: ", n1, n2);
    for (i = n1 + 1; i < n2; ++i) {
        // i is a prime number, flag will be equal to 1
        flag = checkPrimeNumber(i);
        if (flag == 1)
            printf("%d ", i);
    }
    return 0;
}
Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
Niyant
  • 11
  • 1

1 Answers1

0

Your code isn't respecting the register usage rules of the calling conventions set out by the AAPCS ABI:

A subroutine must preserve the contents of the registers r4-r8, r10, r11 and SP (and r9 in PCS variants that designate r9 as v6).

So:

  • You have to assume that r0-r3 are overwritten by any function call (unless to a function that you wrote yourself in pure asm). They are "call-clobbered registers". In your code, you use r2 and r3 for the loop indexing, but they are potentially overwritten by the calls to checkPrimeNumber and printf.

  • r4-r8 and r10 will not be overwritten by a function call (that is, if the function uses those registers, it will restore their original values before returning). They are "call-preserved". So they are suitable for values like your loop indexes. However, your main function itself is subject to the ABI rules, so you must ensure that when you use any of those registers, you restore their original values before main returns. The simplest way is to just add them to the register lists of the push and pop instructions at the entry and exit of your function.

One other note is that the stack pointer must be aligned to 8 bytes whenever you make a call to a function. It is guaranteed to be aligned when main is entered, so an easy way to ensure this is to make sure that you push/pop an even number of registers on entry/exit to your function (provided that you don't modify sp anywhere else). Right now your code satisfies this, but just pay attention to this as you add to the push/pop lists to preserve the call-preserved registers that you use. If you end up with an odd number, you can simply add an additional register to the list, such as r1.

A couple other comments:

  • Your asm code doesn't include the return 0 which is in the C code. Add mov r0, #0 after done:.

  • There isn't really any need to keep i and flag in memory. In fact your code already doesn't really use them; you store values there which are never loaded back. You can just pick a call-preserved register to be used for i and use it throughout. And flag doesn't need to exist at all since it's purely temporary. You get the return value of checkPrimeNumber in r0 and then simply do cmp r0, #1 and that's fine. It's the analogue of writing your C code as if (checkPrimeNumber(i) == 1) without the unnecessary flag variable.

  • The variables n1, n2 in your C code were local to main, but in your asm code you have made them global. It works fine either way, but having them local is a little cleaner, so you might try changing your asm version to keep them on the stack instead of in the data section. You'll probably find this also shortens your code a bit. For instance, if n1 is at sp+4, then instead of ldr r1, =n1 you would simply do add r1, sp, 4; and you can replace ldr r1, =n1 ; ldr r8, [r1] with just ldr r8, [sp, #4].

  • Remember to use proper indentation in your C code! I fixed this in your post.

  • In C, it's better to declare main as int main(void) instead of int main(). See Difference between int main() and int main(void)?. In C++, int main() is fine. I fixed this in your post too.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • Thank you so much for this detailed explanation, I was struggling understanding the register usage rules and you explained it in a very concise manner. I have attempted applying what you said about using "call Preserved registers" for my i variable as well as forgoing using the flag variable. Even with these changes, my program still outputs 1. Is my r0 comparison interfering with my r4 register? (replaced r3 with r4) – Niyant Aug 07 '23 at 20:28
  • @Niyant: I'd have to see your new code. You could edit your question and add the new code at the end. (Please leave the original code there as well, since otherwise my answer wouldn't make sense to future readers.) – Nate Eldredge Aug 08 '23 at 14:34