Programming - Assembly Stack and Recursive Algorithms

in #programming7 years ago

    Hello its'a me again! Today we will continue our talk with functions, but this time we will call functions inside of functions, storing the return address inside of a stack (to don't lose it). I have 3 recursive algorithm codes for you, that use this principle, cause they call themselves again and again... So without further do, let's get started with how we use a Stack and afterwards let's talk about recursive algorithms!


Assembly Stack:

    When calling functions we want to store the $ra register's return value, that contains the position/line in the code that called the function, to don't lose it. Sometimes we even want to store register values, so they don't get lost. To do this Assembly offers a program stack, where we can store those values, so that they don't get lost after calling a new function. 

Stack pointer:

    Assembly offers a register called $sp (stack pointer) that points to a specific memory location of our program, that points at the stack. We will increment/decrease the address value using add/sub instruction, to save/load values inside of it. The logic is opposite and we will have to decrease it to make room for new ones and increment it to delete the old ones. So, this pointer's value has the top of the stack memory when we first start to code, and afterwards we will do calculations on it to store/load values from it.

    To load/store values we will use the same memory instructions we already know, having as parameters the $sp register and an offset. The register value that we always need to save is the one of $ra! The rest ones can be of other registers.

Store values:

For example, if we want to make room for 3 register integer values and afterwards store them into the stack we will use the following code:

 # decrease stack pointer
addi $sp, $sp, -12 # 12/4 = 3 integers
# store values
sw $r1, 0($sp) # store at $sp + 0 address
sw $r2, 4($sp) # store at $sp + 4 address
sw $r3, 8($sp) # store at $sp + 8 address
# never on top of the stack!


Load values:

    To load these values we will use the same logic. This time we first load from each of those locations and afterwards increase the $sp value like that:

# load values
lw $r1, 0($sp) # load from $sp + 0 address
lw $r2, 4($sp) # load from $sp + 4 address
lw $r3, 8($sp) # load from $sp + 8 address
# increase stack pointer
addi $sp, $sp, 12 # 12/4 = 3 integers


Code layout:

    So, our code layout, when we want to call a function inside of a function looks like this:

function1:
# code before calling
# decrease stack to make room for N Bytes
addi $sp, $sp, -N
# store into stack
sw $r1, 0($sp)
sw $r2, 4($sp)
...
sw $r(N-1), (N-4)($sp)
# call function
jal function2
# load from stack
lw $r1, 0($sp)
lw $r2, 4($sp)
...
lw $r(N-1), (N-4)($sp)
# increment stack pointer to previous value
addi $sp, $sp, N
# code after calling
# return to main
jr $ra

Recursive functions:

    Recursive functions, are those that call themselves many times to solve a problem. So, they will be some register values that we will need to store inside of a stack, so they don't get lost when the function calls itself again. We talked about them in C some posts ago. Here you can check it out.

    So, an recursive function needs to store the $ra value and values of registers that get lost when the function calls itself again (like return values). 

    I have 3 example codes for you. The first one is a recursive factorial function, the second one a recursive fibonacci function and the last one a tower of hanoi recursive function.

Code 1(Factorial):

C Code:

int fact(int n)
{
    int f;
    if(n<=0)
        f=1;
    else
        f=n*fact(n-1);
    return f;
}

Assembly Code:

fact:
    addi $sp,$sp, -8 $ space for two words
    sw $ra, 4($sp) #save return address
    sw $ao,0($sp) $temporary variable to hold n

    li $v0, 1
    ble $a0, $zero, fact_return

    addi $a0, $a0, -1
    jal fact
    lw $a0, 0($sp) #retrieve original n
    mul $v0, $v0, $a0 # n*fact(n-1)

    fact_return:
    lw $ra 4($sp) #restore $ra
    addi $sp,$sp, 8 #restore $sp
    jr $ra #back to caller

Code 2(Fibonacci):

C Code:

int fib(int n)
{
    int f;
    if(n<2)
        f=n;
    else
        f=fib(n-1)+fib(n-2)
    return f;
}

Assembly Code:

fib:
	#a0=y
	#if (y==0) return 0;
	#if (y==1) return 1;
	#return( fib(y-1)+fib(y-2) );
	addi $sp,$sp,-12 #save in stack
	sw $ra,0($sp)
	sw $s0,4($sp)
	sw $s1,8($sp)
	
	add $s0,$a0,$zero
	addi $t1,$zero,1
	beq $s0,$zero,return0
	beq $s0,$t1,return1
	
	addi $a0,$s0,-1
	jal fib
	add $s1,$zero,$v0 #s1=fib(y-1)
	
	addi $a0,$s0,-2
	jal fib #v0=fib(n-2)
	add $v0,$v0,$s1 #v0=fib(n-2)+$s1
	
	exitfib:
	lw $ra,0($sp) #read registers from stack
	lw $s0,4($sp)
	lw $s1,8($sp)
	addi $sp,$sp,12 #bring back stack pointer
	jr $ra
	
	return1:
	li $v0,1
	j exitfib
	
	return0 : li $v0,0
	j exitfib


Code 3(Tower of Hanoi):

C Code:

void towers(int num, int frompeg, int topeg, int auxpeg)
{
    if(num == 1)
    {
        printf("\nMove disk 1 from peg %d to peg %d", frompeg, topeg);
        return;
    }
    towers(num - 1, frompeg, auxpeg, topeg);
    printf("\n Move disk %d from peg %d to peg %d, num, frompeg, topeg);
    towers(num -1, auxpeg, topeg, frompeg);
}

Assembly Code:

.text must contain:

disk1_1: .asciiz "\nMove disk 1 from peg "

disk1_2: .asciiz " to peg "

diskx_1: .asciiz "\nMove disk "

diskx_2: .asciiz " from peg "

diskx_3: .asciiz " to peg "

and the function itself is:

towers:
    #decrease stack to store $ra and parameters
    sub $sp,$sp,20
    sw $ra,0($sp)
    sw $a0,4($sp)
    sw $a1,8($sp)
    sw $a2,12($sp)
    sw $a3,16($sp)

    #check if n==1 to do if or notif
    lw $s0,4($sp)
    bne $s0,1,notif
    if:
        # print message for moving disk and return to main
	li $v0,4
	la $a0,disk1_1
	syscall
		
	lw $s1,8($sp)
	li $v0,1
	add $a0,$s1,$0
	syscall
		
	li $v0,4
	la $a0,disk1_2
	syscall
		
	lw $s2,12($sp)
	li $v0,1
	add $a0,$s2,$0
	syscall
		
	lw $ra,0($sp)
	add $sp,$sp,20
	jr $ra	
	
    notif:
        #call towers with swapped parameters (aux and to)
	lw $s0,4($sp)
	sub $s0,$s0,1
	move $a0,$s0
	lw $s1,8($sp)
	move $a1,$s1
	lw $s3,16($sp)
	move $a2,$s3
	lw $s2,12($sp)
	move $a3,$s2	
	jal towers
		
         #print message for moving disk
	li $v0,4
	la $a0,diskx_1
	syscall
		
	lw $s0,4($sp)
	li $v0,1
	move $a0,$s0
	syscall
		
	li $v0,4
	la $a0,diskx_2
	syscall
			
	lw $s1,8($sp)
	li $v0,1
	move $a0,$s1
	syscall
		
	li $v0,4
	la $a0,diskx_3
	syscall
		
	lw $s2,12($sp)
	li $v0,1
	move $a0,$s2
	syscall
		
        #call towers with swapped parameters (aux and from)
	lw $s0,4($sp)
	sub $s0,$s0,1
	move $a0,$s0
	lw $s3,16($sp)
	move $a1,$s3
	lw $s2,12($sp)
	move $a2,$s2
	lw $s1,8($sp)
	move $a3,$s1
	jal towers
	
	lw $ra,0($sp)
	add $sp,$sp,20
	jr $ra	


    This is the end of today's post! Hope you learned something new and enjoyed it. We are finished with the most stuff that I wanted to show you! Only some little things remain and I will then upload more advanced codes from time to time! 

    Next post will be about Dynamic Memory Allocation and we will create an real dynamic array (and not an pseudodynamic like last time)!

Until next time...Bye!

Coin Marketplace

STEEM 0.17
TRX 0.14
JST 0.028
BTC 58607.22
ETH 2616.94
USDT 1.00
SBD 2.43