#5. Procedure Calling(1)(함수 호출, Leaf Procedure인 경우)
컴퓨터구조론 2023. 12. 20. 13:53명령어에 대해서 알아보았으니, 이제 실제로 컴퓨터가 함수를 호출할때, 작동하는 방식에 대해서 알아본다.
여기서는 프로시저(procedure)라고 하는데, 함수(function)과 같은 말이니 겁먹지는 말자
1. procedure calling 절차
레지스터는 32개가 끝이다. 따라서, 동시에 여러 함수를 호출하고 실행하기는 어렵다. 만약A라는 함수가 B라는 함수를 호출해야 한다면, 레지스터에는 A함수의 값들이 잠시 다른 곳에 저장되고, B함수에 관련한 값들이 레지스터로 올라와서 연산을 하고, 결과값을 A에게 리턴 한다. 그리고, A는 리턴받은 값을 토대로, 다시 레지스터 위에 올라간다.
32개의 레지스터 중에서 8~15번, 16~23번은 각각 t0~t7, s0~s7 이렇게 명명되어 있는데, 함수에 관계에 따라 저장되는 위치가 달라진다.
함수 호출은 다음과 같은 순서로 이루어진다. A함수에서 B함수를 호출한다면
1. input 파라미터들을 레지스터에 입력
2. A 함수의 관련값들을 Stack에 저장
3. B 함수를 호출하여 레지스터에 관련값들 입력(A에 있던 값들은 Stack에 저장하고, 덮어씌우기)
4. B함수 연산 뒤, 리턴 값을 저장
5. Stack에 있던 값들을 다시 레지스터로 복원
이렇게 이야기 해서는 이해가 어려우니, 예시를 통해 생각해보자
2. Leaf Procedure
먼저 함수 안에서 다른 함수를 호출하지 않는 끝단의(Leaf) 프로시저를 예시로 생각해보자
int leaf_example (int g,h,i,j)
{
int f;
f= (g+h) - (i+j);
return f;
}
이렇게 C언어가 작성되어 있다고 생각하자.
이걸 어셈블리어로 작성한다 생각해보자, 입력값 g,h,i,j 는 이미 레지스터 $a0~$a3에 들어온 상태이다.
먼저, leaf_example 이라는 함수를 작성하고, 입력 변수 g,h,i,j 값이 레지스터에 들어왔을 텐데, 이것을 Stack으로 옮겨야 한다.
leaf_example :
addi $sp, $sp, -12; //stack에 저장
sw $t1, 8($sp)
sw $t0, 4($sp)
sw $s0, 0($sp)
addi $sp, $sp, -12 : $sp 레지스터(stack pointer)에서 -12를 하여 메모리에 Stack 공간을 마련한다. 아래 그림을 보면, $sp 포인터가 원래 주소에서 12만큼 내려가 3개의 변수를 저장할 Stack을 만들었음을 알 수 있다.
sw $t1, 8($sp) : 1개의 word(32bit)를 레지스터에서 메모리로 저장, 함수 호출전 $t1레지스터에 있던 값을 Stack으로 잠시 갖다 놓기sw $t0, 4($sp) : 함수 호출전 $t0레지스터에 있던 값을 Stack으로 잠시 갖다 놓기
sw $s0, 0($sp) : 함수 호출전 $s0레지스터에 있던 값을 Stack으로 잠시 갖다 놓기
여기서 주의 할점은, $sp의 주소가 현재 -12 되어서 가장 아래쪽을 가르키는 상태이다. MIPS는 하향식 스택성장방식을 사용하기 때문에, 주소가 내려간 상태에서, 낮은 주소부터 데이터가 입력된다는 것에 주의하자. 따라서 sw $t1, 12($sp) 이런식으로 +12해서 저장을 시작하면 안된다.
연산과정(Procedure Body)
add $t0, $a0, $a1 // $t0 = g + h
add $t1, $a2, $a3 // $t1 = i + j
sub $s0, $t0, $t1 // $s0 = (g + h) - (i + j)
add $v0, $s0, $zero // $v0에 최종 결과 넘기기
add $t0, $a0, $a1 :g+h 값을 $t0 레지스터에 저장
add $t1, $a2, $a3 :i+j 값을 $t1 레지스터에 저장
sub $s0, $t0, $t1 : $t0-$t1 한 값을 $s0에 저장
add $v0, $s0, $zero : $v0 에 최종 결과 넘기기
크게 어려운 부분은 없고, 레지스터의 사용만 확인해보면 된다. $s레지스터와 $t레지스터의 사용할 때에, temporary한 값들은 $t레지스터들을 주로 사용해서 저장하는 것을 알아두자
Restore
lw $s0, 0($sp)
lw $t0, 4($sp)
lw $t1, 8($sp)
addi $sp, $sp, 12
jr $ra
Stack에 저장되어 있던 값들을 다시 register로 불러오는 과정이다. lw명령어 주소 그대로 가져오면 되기 때문에 어렵지는 않고, 마지막에 포인터의 위치를 다시 +12 하여, 원래 자리로 돌아간다는 것만 확인한다.
마지막 줄에 ja 명령어의 경우 정해진 주소로 바로 건너뛰게 되는 명령어인데, 이 함수를 불러왔던 원래 함수의 주소로 다시 돌아가는 것이다.
전체 코드는 다음과 같다.
leaf_example :
//Save
addi $sp, $sp, -12; //stack에 저장
sw $t1, 8($sp)
sw $t0, 4($sp)
sw $s0, 0($sp)
//Procedure body
add $t0, $a0, $a1 // $t0 = g + h
add $t1, $a2, $a3 // $t1 = i + j
sub $s0, $t0, $t1 // $s0 = (g + h) - (i + j)
add $v0, $s0, $zero // $v0에 최종 결과 넘기기
//restore
lw $s0, 0($sp)
lw $t0, 4($sp)
lw $t1, 8($sp)
addi $sp, $sp, 12
jr $ra
#4. MIPS R, I, J-format Instructions, 명령어 포맷 (1) | 2023.12.17 |
---|---|
#3. Big Endian, Little Endian (0) | 2023.12.14 |
#2. MIPS ISA에 대해(명령어 집합)(1) (0) | 2023.11.17 |
#1. CPU time 과 CPI에 대해서, CPU의 성능측정 척도 (0) | 2023.11.16 |