# !> Three versions of factorial in assembly

.global main

.text

# Assembly Design Recipe

# 1. Signature / purpose

# long fact(long n)
# Computes the nth factorial

# 2. Pseudocode

# long fact(long n) {
#   if (n < 2) 
#     return 1;
#   else
#     temp = fact(n - 1);
#     return n * temp;
# }

# 3. Variable mappings 
# n -> %rdi -> %r12
# temp -> %rax

# 4. Skeleton

# 5. Complete the body

# Version 1: callee-save register

# long fact(long n)

fact:
  # PROLOGUE
  pushq %r12     # push 8 bytes
  enter $8, $0   # allocate 8 bytes to align the stack pointer

  # BODY
  #   if (n < 2) 
  cmpq $2, %rdi
  jge fact_else
  #     return 1;
  movq $1, %rax
  jmp fact_ret
fact_else:
  #   else {
  movq %rdi, %r12
  subq $1, %rdi
  call fact
  #     temp = fact(n - 1);
  #     return n * temp;
  imulq %r12, %rax

  # EPILOGUE
fact_ret:
  leave
  popq %r12
  ret
  #   }

# Version 2: Using local variables in the stack frame

# n -> %rdi -> -8(%rbp)
# temp -> %rax
fact2:
  # PROLOGUE
  enter $16, $0   # allocate 8 bytes + 8 bytes to align rsp

  # BODY
  #   if (n < 2) 
  cmpq $2, %rdi
  jge fact2_else
  #     return 1;
  movq $1, %rax
  jmp fact2_ret
fact2_else:
  #   else {
  movq %rdi, -8(%rbp)
  subq $1, %rdi
  call fact2
  #     temp = fact2(n - 1);
  #     return n * temp;
  imulq -8(%rbp), %rax

  # EPILOGUE
fact2_ret:
  leave
  ret
  #   }

# Version 3: Using a caller-save register

# n -> %r10
fact3:
  # PROLOGUE
  enter $0, $0

  # BODY
  #   if (n < 2) 
  cmpq $2, %rdi
  jge fact3_else
  #     return 1;
  movq $1, %rax
  jmp fact3_ret
fact3_else:
  #   else {
  movq %rdi, %r10
  subq $1, %rdi
  pushq %r10    # push twice to align the stack pointer
  pushq %r10
  call fact3
  #     temp = fact3(n - 1);
  #     return n * temp;
  popq %r10
  popq %r10
  imulq %r10, %rax

  # EPILOGUE
fact3_ret:
  leave
  ret
  #   }


# int main(int argc, char *argv[])
# tmp -> %rax
main:
  enter $0, $0

  # tmp = fact(5)
  movq $5, %rdi
  call fact3

  # printf("%ld\n", tmp)
  movq $format, %rdi
  movq %rax, %rsi
  movb $0, %al
  call printf

  # return 0;
  movq $0, %rax
  leave
  ret

.data
format:
  .asciz "%ld\n"

