[컴퓨터구조] 2. Instructions: Language of the Computer (6)
The jr Instruction
jr $ra // $ra(return address)에 저장된 주소로 jump
- Returns control to the caller.
- Copies the contents of $ra into the PC.
Calling Convention
jal ProcedureLabel // 서브루틴을 호출하고, 현재의 호출 주소를 $ra에 넣는다.
jr $ra // The subroutine returns to its caller.
Main Calling Mysub Example
move $a0, $t1 // $t1 값을 $a0로 move
li $a1, 4 // $a1에 4를 load_integer
jal mysub // mysub 서브루틴을 호출하고, 현재의 주소를 $ra에 넣는다.
nop
jr $ra // mysub 서브루틴이 caller로 돌아온다.
Example 5
# Bo Cheng -- 02/08/05
# ex5.asm -- A program that exercises the function calls
.data
in_main_msg1: .asciiz "Before The Call\n"
in_sub_msg: .asciiz "In Sub Program\n"
in_main_msg2: .asciiz "After the Call\n"
.text
main:
# Print the "before" message
la $a0, in_main_msg1
li $v0, 4 // print string
syscall
# Call the subroutine sub_pro
jal sub_pro
nop // no operation
# Print "after" message
la $a0, in_main_msg2
li $v0, 4
syscall
# Exit the program
li $v0, 10 // exit
syscall
# End of add.asm
# the subroutine body // subroutine
sub_pro:
la $a0, in_sub_msg
li $v0, 4
syscall
# Return the call
jr $ra
nop
- nop (no operation)
: 'jal subroutine' 다음에 쓰는 것
- 사용 목적: jal과 같이 돌아와서 수행할 다음 instruction이 있는 경우, PC <- PC+4 로 먼저 수행된 상태에서, 그 다음 instruction 주소를 $ra에 저장하게 되기 때문에, jal 바로 다음에 뭔가 place hoder로 넣어두는 것이 필요하기 때문이다.
Example 6 - Sum
main:
li $s0, 0x06 // load_integer 6 to $s0
li $s1, 0x10 // 16
move $a0, $s0 // move from $s0 to $a0
move $a1, $s1
jal sum_it // call subroutine sum_it
nop
# the subroutine sum_it // subroutine
sum_it:
add $t1, $a0, $a1 // $t1 = $a0 + $a1
move $v0, $t1 // move from $t1 to $v0
jr $r1
nop
# Get the result
move $s3, $v0 // move from $v0 to $s3
# Print the sum
move $a0, $s3 // move from $s3 to $a0
li $v0, 1 // $a0의 값 print
syscall
# Exit the program
li $v0, 10
syscall
# End of sum_example.asm
Pushing the Return Address ($ra)
: $ra의 값을 stack에 저장하고, 필요할 때 꺼내 쓴다.
Chain of Subroutine Calls
- Push the return address in the stack.
- When it returns to its caller, it pops the stack to get the return address.
Stack
- LIFO (Last In First Out)
- Grows from larger memory address to smaller memory address.
- Use stack pointer ($sp = $29) to point the top of stack.
Push
- Push 연산을 수행할 때, 현재 주소보다 4 byte만큼 낮은 주소로 push하고, 메모리의 $sp를 4만큼 줄여서, 스택포인터가 push한 데이터를 가리키도록 한다.
- $ra 레지스터에 저장된 데이터로 메모리의 $sp가 가리키는 주소로 sw한다.
subi $sp, $sp, 4 // subtract_immediate $sp = $sp - 4
sw $ra, ($sp) // store_word $ra to ($sp), $ra 값을 스택 포인터에 저장하는 것
Pop
- 메모리의 $sp가 가리키는 곳에 있는 데이터를 $ra 레지스터로 lw한다.
- 메모리의 $sp 값을 4만큼 늘려서, $sp가 Push하기 전의 주소를 가리키도록 한다.
lw $ra, ($sp) // load_word ($sp) to $ra
addi $sp, $sp, 4 // add_integer $sp = $sp + 4
Leaf Procedure Example
// C code
int leaf_example (int g, h, i, j)
{ int f;
f = (g+h) - (i+j);
return f;
}
// Argument g~j in $a0~$a3
// f in $s0 --> Need to save $s0 on stack
// Return in $v0
// MIPS code
leaf_example:
addi $sp, $sp, -4 // $sp = $sp-4
sw $s0, 0($sp) // $sp에 $s0에 저장된 데이터를 sw (push)
add $t0, $a0, $a1
add $t1, $a2, $a3
sub $s0, $t0, $t1
add $v0, $s0, $zero // 결과를 $v0에 저장한다.
lw $s0, 0($sp) // $sp에 저장된 데이터를 $s0에 lw (pop)
addi $sp, $sp, 4
jr $ra // caller에게 return (서브루틴 수행을 마치고 돌아온다.)
Nested Procedure Calls (중첩된 프로시저 호출)
Stack-based Linkage Convention
▶ Subroutine Call
- Prolog of Subroutine
- 서브루틴이 call되면: $sp값을 4만큼 줄이고, push $ra onto the stack ($sp). - Subroutine Body
- 서브루틴을 수행한다.
- If the soubroutine calls another subroutine, then it does by following rules. - Subroutine Epilog (done by the subroutine just before it returns to the caller).
- Put return value in $v0~$v1.
▶ Regaining control from a subroutine
- Pop from the stack
: $sp가 가리키는 주소에 저장되어 있던 $ra(return address)를 pop하고, $sp를 4만큼 늘려 원래 자리로 돌려놓는다.
- caller에 의해 수행된다.
Q. Regaining control from a subroutine == Pop from the stack이 caller에 의해 수행된다? Pop 연산은 caller가 아닌 callee에 의해 수행되며, 이 pop 연산까지 마무리되었을 때 서브루틴에서 메인 함수로 돌아가게 되고, 비로소 caller가 다시 주도권을 갖게 되는 거 아닌가요?
A. 중요한 부분 잘 질문했습니다. 우선 push한 쪽이 꺼낼 수도 있겠지요? 어디서 push했나요? caller가 push했지요? 그러니, 빼 쓰고 싶은데... 무엇을 넣었는지는 caller가 알죠. Caller가 넣은 순서대로 빼내서 사용할 수 있으므로 caller가 제어권을 갖고, stack으로부터 pop하는 주체도 caller라고 할 수 있습니다.
Pushing and Popping Registers
- Push: If a subroutine is expected to alter any of the "S" registers, it must first push their values onto the stack.
- Pop: Just before returning to the caller, it must pop these values from the stack back into the registers they came from.
The Call Chain Example
subB:
subi $sp, $sp, 4 // push $ra into $sp
sw $ra, ($sp)
...
jal subC // call subC
nop
...
lw $ra, ($sp) // pop $ra from $sp
addi $sp, $sp, 4
jr $ra // return to caller
nop
// SubC expects to use $s0 and $s1
// SubC does not call another subroutine
SubC:
subi $sp, $sp, 4 // push $s0
sw $s0, ($sp) // $sp에 $s0 sw
subi $sp, $sp, 4 // push $s0
sw $s1, ($sp) // $sp에 $s1 sw
...
lw $s1, ($sp) // pop $s1
addi $sp, $sp, 4
lw $s0, ($sp)
addi $sp, $sp, 4
jr $ra // return to subB
nop
Example 7 - Find Min
main:
li $a0, 3 // $a0 = 3
li $a1, 4
li $a2, 5
jal findMin3
move $t0, $v0 // $v0값을 $t0로 move
// Print out the min
move $a0, $t0 // $t0값을 $a0로 move
li $v0, 1
syscall // $a0에 저장된 값($t0: min)을 print
// Exit the program
li $v0, 10
syscall
findMin3:
move $t0, $a0 // $a0값을 $t0로 move
bge $a1, $t0, IF2 // if($a1 >= $t0), branch to IF2
move $t0, $a1 // if($a1 < $t0), $a1값을 $t0로 move ($t0에는 min을 저장함)
IF2:
bge $a2, $t0, END // if($a2 >= $t0), branch to END
move $t0, $a2 // if($a2 < $t0), $a2값을 $t0로 move
END:
move $v0, $t0 // $t0값을 $v0로 move (return address 저장하는 레지스터
jr $ra
Non-Leaf Procedure Example
// C code (팩토리얼 함수; 재귀함수)
int fact(int n)
{
if(n < 1)
return f;
else
return n * fact(n-1);
}
// Argument n in $a0
// Result in $v0
// MIPS code:
fact:
addi $sp, $sp, -8
sw $ra, 4($sp)
sw $a0, 0($sp)
slti $t0, $a0, 1 // Set on Less Than Immediate, if($a0 < 1), $t0 = 1; else $t0 = 0;
beq $t0, $zero, L1 // if($t0 == 0) 즉 if($a0 >= 1), branch to L1
addi $v0, $zero, 1 // if($t0 == 1) 즉 if($a0 < 1), result is 1
addi $sp, $sp, 8
jr $ra // 끝!
// 서브루틴 L1
L1:
addi $a0, $a0, -1
jal fact // recursive call (재귀 호출)
lw $a0, 0($sp) // restore orignal n
lw $ra, 4($sp)
addi $sp, $sp, 8
mul $v0, $a0, $v0 // $v0 = $a0 * $v0 to get result
jr $ra
Local Data on the Stack
Memory Layout
- Stack: automatic storage
- Dynamic data: heap
- 예) malloc in C - Static data: Global variables
- Text: Program code
Byte/Halfword Operations
▶ MIPS byte/halfword load/store
// rs에 offset을 더한 레지스터 주소에서 1 byte 데이터를 load하고, 이를 rt에 저장한다.
// Sign extended to 32-bit in rt
lb rt, offset(rs)
1h rt, offset(rs)
// Zero extended to 32-bit in rt
lbu rt, offset(rs)
lhu rt, offset(rs)
// Store just rightmost byte/halfword
sb rt, offset(rs)
sh rt, offset(rs)
String Copy Example
// C code
void strcpy(char x[], char y[])
{
int i;
i = 0;
while((x[i] == y[i]) != '\0')
i += 1;
}
// Addresses of x, y in $a0, $a1
// i in $s0
// MIPS code
strcpy:
addi $sp, $sp, -4
sw $s0, 0($sp) // save s[0]
add $s0, $zero, $zero // i = 0
L1:
add $t1, $s0, $a1 // address of y[i] in $t1
lbu $t2, 0($t1) // $t0 = y[i]
add $t3, $s0, $a0 // address of x[i] in $t3
sb $t2, 0($t3) // store_byte, $t2의 가장 하위 byte 값을 $t3에 저장한다.
beq $t2, $zero, Eixt // if(y[i]==0), branch to Exit
addi $s0, $s0, 1 // i = i+1
j L1
L2:
lw $s0, 0($sp) // restore saved $s0
addi $sp, $sp, 4 // pop 1 item from stack
jr $ra
Brach Addressing
- beq $t1, $t2, L1
- bne $t1, $t2, L1
Most branch targets are near branch. Forward or backword.
▶ PC-relative addressing
- Target address = PC + offset*4
- PC already incremented by 4 by this time.
Jump Addressing
- jal subProcedure
- j $ra
Jump targets could be anywhere in text segment.
▶ Direct jump addressing
- Target address = PC31...28 : (address * 4)
Target Addressing Example
// Assume Loop at location 80000
Loop:
sll $t1, $s3, 2 // shift left logical, $t1 = $s3을 왼쪽으로 2번 shift
add $t1, $t1, $s6 // $t1 = $t1 + $s6
lw $t0, 0($t1) // load $t1 값을 $t0로
bne $t0, $s5, Exit // if($t0 != $s5), branch to Exit
addi $s3, $s3, 1 // $s3 = $s3+1
j Loop
Exit: ...
SBY 5-1
1. When you program with a chain of subroutine calls in MIPS assembly, what is the most important thing that you should take care of?
: We should remember to save the return address in register $ra in order to go back to the main function.
- Place parameters(매개변수) in registers. --> $a0, $a1, $a2...
- Transfer control to the subroutine. --> jal sum
- Acquire storage for the subroutine. (Push)
--> addi $sp, $sp, -4
--> sw $ra, 0($sp) - Perform operations in the subroutine.
- Place results in register for the caller.
--> $v0에 저장 - Pull
--> lw $ra 0($sp)
--> addi $sp, $sp, 4 - Perfrom the caller.
--> jr $ra
2. Can you convert the following C code to a MIPS code?
// C code
void main(){
int a=3; // a in $s0
int b=4; // b in $s1
int c;
c = sum(a,b);
}
// MIPS code
main:
li $s0, 3
li $s1, 4
move $a0, $s0 // $s0 값을 $a0로 move
move $a1, $s1
jal sum
nop
move $s2, $v0 // Get the result
li $v0, 10 // Exit
syscall
sum:
add $v0, $a0, $a1
jr $ra
nop