1

I'm creating a compiler (source code is a C-like language, and I'm translating it to x86 NASM).

I've tested my compiler with programs that have recursive functions, and they all have the expected result except this Fibonacci program.

I know my code is not optimized at all, I will fix that later.

Can someone tell me what is wrong with the ASM code? If I print the Fibonacci numbers from 0 to 13 this should be the output:

1123581321345589144233377

but I'm getting: 1123354759611713

or this, if I format it: 1,1,2,3,3,5,4,7,5,9,6,11,7,13

Can someone tell what's wrong? The source code is:

int f
//global var f
 
int fibonacci( int n ){
    if ((n=0) or (n=1)){
        return 1
    }
    else{
        return fibonacci(n-1) + fibonacci(n-2)
    }
}
 
 
 
main(){
    f = fibonacci(6)
    print(f)
}

ASM code is:

;fibonacci (not optimized)
extern printf
global main
section .data
format db '%d', 0
 
f dd 0
 
temp0 dd 0
temp1 dd 0
temp2 dd 0
temp3 dd 0
temp4 dd 0
temp5 dd 0
temp6 dd 0
temp7 dd 0
temp8 dd 0
temp9 dd 0
temp10 dd 0
temp11 dd 0
temp12 dd 0
 
section .text
fibonacci:
push ebp
mov ebp, esp
 
;eq expr
mov dword[temp0], 0
mov eax, dword[ebp + 8]
cmp eax, dword[temp0]
mov eax, 0
sete al
mov dword[temp0], eax
 
;eq expr
mov dword[temp1], 1
mov eax, dword[ebp + 8]
cmp eax, dword[temp1]
mov eax, 0
sete al
mov dword[temp1], eax
 
;or expr
cmp dword[temp0], 1
je label1
cmp dword[temp1], 1
je label1
mov dword[temp2], 0
jmp label2
label1:
mov dword[temp2], 1
label2:
 
;ifStmt
cmp dword[temp2], 0
je label3
;return statement
mov dword[temp3], 1
mov eax, dword[temp3]
;jmp to epilogue
jmp label0
 
jmp label4
;else label
label3:
 
;return statement
 
mov dword[temp5], 1
mov esi, dword[ebp + 8]
sub esi, dword[temp5]
mov dword[temp6], esi
 
push dword[temp6]
;subprogram call
call fibonacci
add esp, 4
mov dword[temp4], eax
 
 
mov dword[temp8], 2
mov esi, dword[ebp + 8]
sub esi, dword[temp8]
mov dword[temp9], esi
 
push dword[temp9]
;subprogram call
call fibonacci
add esp, 4
mov dword[temp7], eax
 
mov esi, dword[temp4]
add esi, dword[temp7]
mov dword[temp10], esi
 
mov eax, dword[temp10]
;jmp to epilogue
jmp label0
 
label4:
 
label0:
pop esi
mov esp, ebp
pop ebp
ret
 
main: 
mov dword[temp12], 6
push dword[temp12]
;subprogram call
call fibonacci
add esp, 4
mov dword[temp11], eax
 
 
mov eax, dword[temp11]
mov dword[f], eax
 
push dword[f]
push format
call printf
add esp, 8
 
ret

Can somebody also explain why is my compiler working for this gcd function and not for the Fibonacci?

int a, b, c

// greatest common divisor
int gcd(int x, int y){
    if(b = 0){
        return x
    }else{
        return gcd(y, x mod y)
    }
}

main(){
    a = 51492
    b = 20636
    c = gcd(a, b)
    print c
}

extern printf
global main
section .data
format db '%d', 0

a dd 0
b dd 0
c dd 0

temp0 dd 0
temp1 dd 0
temp2 dd 0
temp3 dd 0
temp4 dd 0
temp5 dd 0

section .text
gcd:
push ebp
mov ebp, esp
;eq expr

mov dword[temp0], 0
mov eax, dword[ebp + 12]
cmp eax, dword[temp0]
mov eax, 0
sete al
mov dword[temp0], eax

;ifStmt
cmp dword[temp0], 0
je label1
;return statement

mov eax, dword[ebp + 8]
;jmp to epilogue
jmp label0

;elseStmt
label1:
;return
mov eax, dword[ebp + 8]
mov edx, 0
div dword[ebp + 12]
mov dword[temp2], edx

push dword[temp2]

push dword[ebp + 12]
;call to subprogram
call gcd
add esp, 8
mov dword[temp1], eax

mov eax, dword[temp1]


label0:
mov esp, ebp
pop ebp
ret

main: 
mov dword[temp3], 51492

mov eax, dword[temp3]
mov dword[a], eax
mov dword[temp4], 20636

mov eax, dword[temp4]
mov dword[b], eax

push dword[b]

push dword[a]
;call to subprogram
call gcd
add esp, 8
mov dword[temp5], eax


mov eax, dword[temp5]
mov dword[c], eax

push dword[c]
push format
call printf
add esp, 8

ret

irxnwolf
  • 11
  • 3
  • You seem to be missing a `push esi` from the prologue. However that does not seem to directly affect the result. Use a debugger to see where things go wrong. – Jester Mar 25 '21 at 23:58
  • forgot to push it, but even if I push it I still get the same result – irxnwolf Mar 26 '21 at 00:02
  • 2
    The direct cause is that your `temp` variables are global so of course they get overwritten during recursion. E.g. you use `temp4` to save the result of the first recursive call but that will be overwritten in deeper levels. You should allocate temporaries from the stack. – Jester Mar 26 '21 at 00:03
  • 1
    Automatic storage in C maps to registers and/or stack space in asm. Labels on space in `.bss` or `.data` is C static storage class (global vars, and `static` vars). – Peter Cordes Mar 26 '21 at 01:09
  • Some of those variables don't even make any sense. Why would you do something like `mov dword[temp5], 1`/ `sub esi, dword[temp5]` instead of just `sub esi,1`? – Michael Mar 26 '21 at 10:40
  • I know there's a lot of redundancy with the temps but that's just how my compiler is working for now – irxnwolf Mar 26 '21 at 14:21
  • re: your edit: pretty obviously because your GCD is tail-recursive, so no local vars or temporaries are ever needed after a call. So using global temporaries to make a non-re-entrant function happens to work, because it's basically already done executing before it makes another call. (IDK why you have global `int a,b` with names that just get shadowed by function args inside `gcd`, that seems harder to read for no benefit, especially vs. using locals in main.) – Peter Cordes Mar 27 '21 at 01:41
  • thanks for the explanation, and yes... the variable names on the function can make it a little confusing. Time to edit the example :) – irxnwolf Mar 27 '21 at 05:52

1 Answers1

-2

You are doing integer arithmetic using dwords which are 32 bits on Intel. The answer you seek is 1123581321345589144233377 which requires 80 bits for an unsigned number, or 81 bits for a signed number or 11 bytes. If you need multiplication you will need a 22 byte number for the product, or you could put two 11 byte numbers next to each other in memory. It is possible to do higher bit arithmetic on Intel using software you would have to write. Perhaps there is software you can purchase, or in the public domain.

Marichyasana
  • 2,966
  • 1
  • 19
  • 20
  • Extended-precision integer math is pretty straightforward in x86 asm, with widening `mul` and `adc` (add-with-carry) being directly available to support it. For an existing open-source library, see https://gmplib.org/. It's not public domain, instead being LGPLv3. Some answers on [128x128 bit multiplication on for x86 CPU](https://stackoverflow.com/q/29658024) show the algorithm for multiplication of a big number by a single register. [How can I multiply two 64-bit numbers using x86 assembly language?](https://stackoverflow.com/q/87771) shows asm for 64x64-bit => 64-bit mul – Peter Cordes Oct 18 '22 at 02:29
  • Err wait, Fibonacci not factorial. A chain of `add`/`adc`/`adc` will do 96-bit addition easily. You don't need a library for this, it's one of the simplest extended-precision things you can do in assembly. Printing as a decimal string is harder, requiring 96-bit division by 10. – Peter Cordes Oct 18 '22 at 02:52