이 글은 필자가 수업시간에 들은 내용과 강의록을 토대로 정리한 글입니다.
수업 필기이다 보니, 오류가 있거나 설명이 부족한 부분이 있을 수 있습니다.
궁금하신 점이나 지적하실 점이 있다면 댓글로 달아주세요! 확인 후 내용을 추가하거나 답변해드리도록 하겠습니다 :)
$v0 , $v1 : 결과값 (레지스터 2, 3번)$a0 ~ $a3 : 매개변수 (레지스터 4 ~ 7번)$t0 ~ $t9 : 임시 값$s0 ~ $s7 : 저장된 값$gp : 정적 데이터(static data)를 위한 전역 포인터 (레지스터 28번)$sp : 스택 포인터 (레지스터 29번)$fp : 프레임(frame) 포인터 (레지스터 30번)$ra : 반환 주소(return address) (레지스터 31번)$sp) : 스택에서 가장 최근에 할당된 주소를 저장하는 레지스터int leaf_example (int g, h, i, j)
{
int f;
f = (g + h) - ( i + j);
return f;
}
매개변수 g, … , j는 $a0 , … , $a3 에 저장된다.
f는 $s0 에 저장된다. 따라서, $s0 은 스택에 저장해야 한다.
결과는 $v0 에 저장한다.
MIPS 코드
leaf_example:
addi $sp, $sp, -4 # Save $s0
sw $s0, 0($sp) # on stack (push)
add $t0, $a0, $a1 #
add $t1, $a2, $a3 # Procedure body
sub $s0, $t0, $t1 #
add $v0, $s0, $zero # Result
lw $s0, 0($sp) # Restore
addi $sp, $sp, 4 # $s0
jr $ra # Return
int fact (int n)
{
if (n < 1) return 1;
else return n * fact(n - 1);
}
매개변수 n은 $a0에 저장된다.
결과는 $v0 에 저장한다.
MIPS 코드
fact:
addi $sp, $sp, -8 # adjust stack for 2 items
sw $ra, 4($sp) # save return address to caller
sw $a0, 0($sp) # save argument of this procedure
slti $t0, $a0, 1 # test for n < 1
beq $t0, $zero, L1
addi $v0, $zero, 1 # if so, result is 1
addi $sp, $sp, 8 # pop 2 items from stack
jr $ra # and return
L1: addi $a0, $a0, -1 # else decrement n
jal fact # recursive call
lw $a0, 0($sp) # restore original n
lw $ra, 4($sp) # and return address
addi $sp, $sp, 8 # pop 2 items from stack
mul $v0, $a0, $v0 # multiply to get result
jr $ra # and return
Non-Leaf 프로시저의 경우, 피호출자가 호출자의 반환 주소(return address)를 스택에 저장해야 한다. 특히 재귀일 경우 더 그런데, 중첩적으로 호출이 일어날 경우 스택에 반환 주소가 있어야만 스택에서 pop을 하며 제대로 원래 위치로 돌아갈 수 있기 때문이다.


위의 그림에서 볼 수 있듯, 매개변수가 4개보다 많을 경우, 스택의 프레임(stack frame) 보다 더 아래(Bottom)쪽에 매개변수를 저장한다. 스택의 프레임은 스택에 할당되는 해당 프로시저의 메모리 블럭 정도로 생각하면 된다. 즉, 스택에 할당되는 프로시저 블록의 바깥에 있다.
지역 변수는 저장 레지스터보다 더 위(Top)쪽에 저장하도록 한다.
스택은 큰 주소값을 가지는 쪽이 Bottom이 되도록 만들어지는데, 그리하여 앞의 예제로 사용했던 팩토리얼 예제의 경우 우측 사진과 같이 스택에 할당되게 된다.

$gp 는 오프셋(offset)을 이용해서 이 부분으로 접근할 수 있게 초기화된다.lb rt, offset(rs) / lh rt, offset(rs)
lbu rt, offset(rs) / lhu rt, offset(rs)
sb rt, offset(rs) / sh rt, offset(rs)
offset(rs)에 저장한다.void strcpy (char x[], char y[])
{
int i;
i = 0;
while ((x[i]=y[i]) != '\0')
i += 1;
}
x와 y의 주소는 $a0 과 $a1 에 저장된다.
i는 $s0 에 저장된다.
MIPS 코드
strcpy:
addi $sp, $sp, -4 # adjust stack for 1 item
sw $s0, 0($sp) # save $s0
add $s0, $zero, $zero # i = 0
L1: add $t1, $s0, $a1 # $t1 = &y[i]
lbu $t2, 0($t1) # $t2 = y[i]
add $t3, $s0, $a0 # $t3 = &x[i]
sb $t2, 0($t3) # x[i] = $t2 = y[i]
beq $t2, $zero, L2 # exit loop if y[i] == 0
addi $s0, $s0, 1 # i = i + 1
j L1 # next iteration of loop
L2: lw $s0, 0($sp) # restore saved $s0
addi $sp, $sp, 4 # pop 1 item from stack
jr $ra # and return
lui rt, constant
를 사용하면 된다.
load-upper-immediate : 16비트 상수를 rt의 왼쪽(윗쪽) 16비트에 복사하고, 오른쪽 16비트는 0으로 초기화한다.
ori 를 사용하여 오른쪽(아랫쪽) 16비트를 설정해주도록 한다.(addi 를 사용하지 말것!)

예시를 보면, addi , ori 이 두 명령어가 무슨 차이가 있는가 의문이 들 수 있다. 하지만 떠올려보라, addi 는 I 포맷 명령어이다. 즉, 32비트 상수 값을 제대로 처리할 수 없다.
beq 나 bne 같은 명령들은 I 포맷 명령이기 때문에 다음 요소들에 대한 정보를 가지고 있어야 한다.

대부분의 분기의 목적지는 분기 명령 근처에 위치한다.
“PC에 상대적인 주소 지정(PC-Relative addressing)”
점프(j , jal)의 목적지는 텍스트 부분 내에서 어디든 될 수 있다.

주소를 저장할 때, 오른쪽으로 두 번 시프트 한 값이 저장되기 때문에, 결국 주소의 28비트를 기억하고 있다.
의사 직접 점프 주소 지정((Pseudo) “Direct jump addressing”)
그렇다면 28비트의 정보는 알 수 있는데, 나머지 최상위(Top) 4비트는 어떻게 처리하는가?

위의 그림에서, PC 주변의 칠해져 있는 부분이 이동 가능한 범위이다. 즉, PC의 최상위 4비트가 고정된 범위 이내에서 자유로이 이동이 가능하다.
점프 레지스터 명령(jr)
6강의 반복문 코드를 참고하자.

위의 그림에서, 화살표가 시작되는 bne 의 2는 오프셋(offset)이다. 즉, PC 상대적인 주소 지정을 사용한 것이다.
또한, j 의 Loop의 주소를 살펴보면, 20000이다. 상기했듯, 주소를 오른쪽으로 2번 시프트하여 저장하기 때문에, 실제 주소를 구하려면 4를 곱해야 하고, 목적지 주소가 80000임을 알 수 있다.
beq $s0, $s1, L1 이라는 코드가 있는데, L1이 너무나도 멀리 있다면, 어셈블러는 이 코드를 다음과 같이 재작성하여 문제를 해결한다. bne $s0, $s1, L2
j L1
L2: ...
JRLWSWBEQBNEJJAL참고로, : 는 합친다는 뜻이다.
해독하기 위해서 이를 먼저 32비트의 이진수로 변환하고, 그린카드를 참고해서 포맷에 맞추어 천천히 해결하면 된다. 참고로, opcode가 첫 6비트이기 때문에 이에 맞추어 해독하면 된다.
move $t0, $t1 은 add $t0, $zero, $t1 이 되고, blt $t0, $t1, L 은 slt $at, $t0, $t1
bne $at, $zero, L
이 되는 식이다.
$at 는 레지스터 1번으로써, 어셈블러 임시 변수이다.bgt , bge , ble 에도 똑같이 적용된다.C 프로그램들은 실행까지 다음 과정을 거친다.
