본문 바로가기

문과 코린이의, [컴퓨터 구조] 기록/컴퓨터구조 2강

[문과 코린이의 IT기록장] 컴퓨터 구조 - 2.(7) ~ 2.(9) [ 판단을 위한 명령어, 하드웨어의 프로시저 지원, MIPS의 32bit 수치를 위한 주소지정 및 복잡한 주소지정 방식 ]

반응형

< 2강. 명령어 : 컴퓨터 언어 >

[문과 코린이의 IT기록장] 컴퓨터 구조 - 2.(7) ~ 2.(9) 

[ 판단을 위한 명령어, 하드웨어의 프로시저 지원, MIPS의 32bit 수치를 위한 주소지정 및 복잡한 주소지정 방식 ]


 

 7. 판단을 위한 명령어 * if문, go to문

1) 조건부 분기

(1) beq register1, register2, L1

: register1과 register2의 값이 같으면, L1에 해당하는 문장으로 가라

: branch if equal

 

(2) bne register1, register2, L1

: register1과 register2의 값이 같지 않으면, L1에 해당하는 문장으로 가라

: branch if not equal

[ If-then-else를 조건부 분기로 번역 ]

ex )다음 코드에서 f,g,h,i,j는 변수이고, 각각은 레지스터 $s0부터 $s4까지에 해당한다. 아래의 C언어 if문장을 컴파일한 코드는 무엇인가?

if (i==j) f = g+h; else f = g - h;

 
해설 )
 MIPS 코드

bne $s3, $s4, Else

// bne로 $s3와 $s4가 같지 않으면 Else로, 같으면 그냥 넘어가면 됨.

(beq를 써도됨. 그러나 Esle를 마지막에 쓰는게 효율적일 것이라 판단)

 

add $s0, $s1, $s2

// f=g+h 연산 수행

j Exit

// 연산 수행 후, if문장의 끝 부분으로 가야함. (=무조건 분기)

// MIPS에서는 jump라는 이름을 붙이고, 간략하게 j로 사용한다. (Exit는 나중에 정의)

 

Else : sub $s0, $s1, $s2

// Else의 경우 f=g-h 연산 수행

 

Exit :

// 문장의 끝 표시


2) 순환문

[ While 순한문의 번역 ]

ex )아래에 전형적인 C 순환문이 있다.

while(save[i] == k)

I += 1;

// I는 레지스터 $s3, k는 레지스터 $s5에 할당되었다고 가정.

// 배열 save의 시작주소가 $s6에 저장되어 있다고 가정

 
풀이 )

1. save[i]를 임시 레지스터로 가져오기.

(1) save[i]를 임시 레지스터에 적재하려면 먼저 그주소를 구해야 함.

// 바이트 주소 문제 때문에 인덱스 I에 4를 곱해서 save의 시작주소에 더해야 주소가 만들어짐.

// 2비트씩 조측 자리이동을 하면 4를 곱한 것과 같으므로 sll연산을 할 수 있음

// 순환의 끝에서 처음 명령어로 되돌아갈 수 있도록 Loop라는 레이블을 추가함.

-> 범위지정

Loop : sll $t1, $s3, 2 // $t1 = I * 4

 

(2) save[i]의 주소를 계산하기 위해 $t1값에다 $s6에 있는 save의 베이스 주소 값을 더할 필요가 있음.

add $t1, $t1, $s6
// $t1 = save[i]의 주소

 

(3) 이제 이 주소를 이용해서, save[i]를 임시 레지스터에 넣을 수 있다.

lw $t0, 0($t1)
// $t0 = save[i]

 

2. 반복 검사를 수행해서 save[i] != k이면 순환에서 빠져나가는 부분이다.

bne $t0, $s5, Exit

 

3. save[i] = k 일 때 I+1을 하는 명령어

addi $s3, $s3, 1

 

4. 순한문의 끝에서는 맨 앞의 while 조건 검사로 되돌아가야 한다. 그리고 이의 다음에 Exit의 레이블을 두면 번역이 끝난다.

j Loop
// Loop로 다시 가라

Exit: 
// 기본 블록

* 기본 블록

: 분기 명령을 포함하지 않으며(맨 끝에는 있을 수 있다) 분기 목적지나 분기 레이블도 없는(맨 앞에 있는 것은 허용된다) 명령어 시퀀스


3) 대소관계 비교 Slt / Slti (set on less than)

- MIPS에서는 두 개의 근원지 레지스터의 값을 비교한 후 목적지 레지스터 값을 설정하는 명령어가 있음.

 

- 즉 [첫 번째 근원지 레지스터 < 두 번째 목적지 레지스터] 이면 1로, 아니면 0으로 하는 명령어로 일을 처리함

ex ) slt $t0, $s3, $s4 // $s3 < $s4이면 $t0이 1, 아니면 0으로 처리한다.

 

- 상수 피연산자 대소 비교의 경우는 slti를 사용함

ex ) slti $t0, $s2, 10

 

cf )

* MIPS 컴파일러 slt, slti, beq, bne와 레지스터 $s0에 있는 상수 0을 이용해서 모든 비교 조거(같다, 다르다, 작다, 작거나 같다, 크다, 크거나 같다)를 만들 수 있다. (레지스터 $zero는 0번 레지스터를 가르킨다.)

* 하드웨어는 간단해야 좋다는 von Neumann의 경고를 준수하여, MIPS의 구조에서는 구현하기에 너무 복잡한 blt명령어를 제외시켰다. (클럭속도가 느려지거나, 클럭사이클이 더 필요함)


[ 비교 명령은 부호있는 수와 부호없는 수 사이의 이분법도 다루어야 한다. ]

- 어떤 때는 MSB가 1인 수가 음수를 나타내며, 이때는 당연히 MSB가 0인 어떤 양수보다도 작다. 그러나 부호없는 정수인 경우에는 1인 수가 0인 어떤 수보다도 더 크다. 이와 같이 대소 비교가 가능할 경우도, 안될 경우도 있는데 이와 같은 부분에서 필요한 배열 검사 비용이 존재한다.

- MIPS는 이러한 두 경우를 처리할 수 있도록, 부호있는 정수에는 slt와 slti로, 부호 없는 정수에는 sltu와 sltiu를 사용한다.

[ 부호 있는 수와 없는 수의 비교 ]

ex )
레지스터 $s0에는

1111 1111 1111 1111 1111 1111 1111 1111 1111 two

$s1에는

0000 0000 0000 0000 0000 0000 0000 0000 0001 two

가 있을 때, 다음 두 명령어의 실행 후 $t0와 $t1의 값은 얼마인가?

 
풀이 )

slt $t0, $s0, $s1 // 부호있는 정수의 비교 = 1

slt $t1, $s0, $s1 // 부호없는 정수의 비교 = 0


[ 빠른 경계 검사 방법 ]

- 부호 있는 정수를 부호 없는 정수처럼 다루면 0<=x<y 검사 비용을 낮출 수 있는데, 이 검사는 인덱스가 배열의 한계를 벗어났는지 확인하는 검사에 딱 맞는다.

- 핵심은 2의 보수로 표현된 음수가, 부호없는 정수에서의 큰 수처럼 보인다는 것이다.

- 따라서 부호없는 비교 x<y를 하면, x가 y보다 작은지뿐만 아니라 x가 음수인지도 검사할 수 있다.

[ 빠른 경계 검사 방법 ]

ex ) 위의 방법을 이용하여 다음에서 인덱스가 경계를 넘는지 검사하는데 필요한 명령어 수를 줄여라. $s1 >= $t2이거나 $s1이 음수이면 IndexOutOfBounds로 분기하라.

풀이 )

sltu $t0, $s1, $t2 // 만약 $s1 >= $t2 또는 $s1<0이면 $t0 = 0이다.

beq $t0, $zero, IndexOutOfBounds

 


4) Case / Switch 문장

- 대부분의 프로그래밍 언어는 특정 변수의 값에 따라 여러 가지 중 하나를 선택하는 case나 switch 문장을 갖고 있음

- Switch를 구현하는 가장 간단한 방법계속적인 조건 검사를 통해 switch를 if-then-else(조건부 분기)의 연속으로 바꾸는 것임

 

[ 점프 테이블, 점프주소 테이블 ]

- but, 여러 코드의 시작 주소를 표로 만들면 더 효율적으로 구현할 수 있음.

 = 점프 주소 테이블, 점프 테이블 * 여러 명령어 시퀀스의 주소를 가지고 있는 표(배열)

= jr(jump register)명령어 : 레지스터에 명시된 주소로 무조건 점프함

 

 

 

 


 

 

 

 8. 하드웨어의 프로시저 지원  * 프로시저 : 제공되는 인수(파라미터)에 따라서 특정 작업을 수행하는 서브루틴

- 프로시저나 함수는 이해하기 쉽고 재사용이 가능하도록 프로그램을 구조화하는 방법 중의 하나이다.

- 프로시저는 프로그래머가 한 번에 한 부분 씩 집중하여 처리할 수 있게 해준다.

- 인수프로시저에 값을 보내고 결과를 받아오는 역할을 하므로, 프로그램의 다른 부분 및 데이터와 프로시저 사이의 인터페이스 역할을 한다.

- 프로시저는 소프트웨어에서 추상화를 하는 방법이다.


[ 프로그램이 프로시저를 실행할 때 거치는 6단계 ]

1. 프로시저가 접근할 수 있는 곳에 인수를 넣는다.

2. 프로시저로 제어를 넘긴다.

3. 프로시저가 필요로 하는 메모리 자원을 획득한다.

4. 필요한 작업을 수행한다.

5. 호출한 프로그램이 접근할 수 있는 장소에 결과 값을 넣는다.

6. 프로시저는 프로그램 내의 여러 곳에서 호출될 수 있으므로 원래 위치로 제어를 돌려준다.


[ MIPS의 호출 관례 : 레지스터 32개 할당 ]

* 레지스터는 데이터를 저장하는 가장 빠른 장소이므로 가능한 한 많이 사용하는 것이 바람직함.

1. $a0-$a3 : 전달할 인수를 가지고 있는 인수 레지스터 4개 (호출)

2. $v0-$v1 : 반환되는 값을 갖게 되는 값 레지스터 2개 (반환)

3. $ra : 호출한 곳으로 되돌아가기 위한 복귀 주소를 가지고 있는 레지스터 1개 (호출)

 

[ MIPS는 레지스터를 할당할 뿐만 아니라, 프로시저를 위한 명령어도 제공함 ]

1. jal 명령어 (프로시저를 위한 명령어)

: 저장된 주소로 점프하면서 +동시에 다음 명령어의 주소를 레지스터($ra)에 저장하는 명령어

 ex ) jal ProcedureAddress

2. link (복귀 주소)

: 프로시저 종료 후 제자리로 돌아갈 수 있게 하는 호출 위치에 대한 링크. MIPS에서는 $ra에 저장된다.

* 한 프로시저가 여러 곳에서 호출될 수 있으므로 복귀 주소는 꼭 필요함

: 이러한 것을 지원하기 위해 MIPS는 case문 구현에 사용했던 jr명령을 이용함.

 ex ) jr $ra

* 호출 프로그램 : 프로시저 실행을 시작시키고 필요한 인수 값을 제공하는 프로그램

* 피호출 프로그램 : 호출 프로그램이 제공하는 인수 값을 이용해서 일련의 명령어를 실행한 후, 호출 프로그램으로 제어를 넘기는 프로시저


[ 내장 프로그램 개념은 현재 수행 중인 명령어의 주소를 기억하는 레지스터를 필요로 한다. ]

- 이 레지스터의 이름 : 명령어 주소 레지스터( 프로그램 카운터(PC) ) 

- jal 명령은 프로시저에서 복귀할 때 다음 명령어부터 시작하도록 PC+4를 $ra에 저장.


1) 더 많은 레지스터의 사용

컴파일러가 프로시저를 번역하는 데 인수 레지스터 4개, 결과 값 레지스터 2개만으로는 부족한 경우는 어떻게 해야 할까?

: 프로시저 호출이 다른 부분에 영향을 미쳐서는 안 되므로, 호출 프로그램이 사용하는 모든 레지스터는 복귀하기 전에 프로시저 호출 전의 상태로 되돌려 놓아야 한다.

 * 이는 레지스터 스필링이 필요한 한 예가 됨. ( // 레지스터 스필링 : 자주 사용하지 않는 변수를 메모리에 넣는 것. )

 

- 레지스터 스필링에 이상적인 자료구조는 스택(stack)임.

* 스택 : 스필된 레지스터 저장(메모리에 함)을 위해 후입선출큐로 구성된 자료구조

* 스택 포인터 : 가장 최근에 스택에 할당된 주소를 가리키는 값. 레지스터가 스필 될 장소(아래로 스필될 수 있음) 또는 레지스터의 옛날 값을 찾을 수 있는 장소(위로 찾을 수 있음)를 표시한다. MIPS에서는 $sp이다.

 

- 스택에는 다음 프로시저가 스필할 레지스터를 저장할 장소레지스터의 옛날 값이 저장된 장소를 표시하기 위해 최근에 할당 된 주소를 가리키는 포인터가 필요하다. 이 스택 포인터레지스터 값 하나가 스택되거나 스택에서 복구될 때마다 한 워드씩 조정된다.

 

- MIPS 소프트웨어는 스택 포인터를 위해 레지스터 29를 할당해 놓고 있는데, 이름은 $sp

* 푸시 : 스택에 원소를 추가하기.

* 팝 : 스택에서 원소를 제거하기.

 

메모리 , 레지스터 스필링 , 스택 포인터

 

[ 다른 프로시저를 호출하지 않는 C 프로시저의 컴파일 ]

ex ) 
int leaf_example (int g, int h, int I, int j)

{ int f;

f = (g+h) - (i+j);

return f;

}

위 프로그램을 번역한 MIPS 어셈블리 코드를 보여라.

 

풀이 )  

1) 인수 g,h,i,j는 인수 레지스터 $a0, $a1, $a2, $a3에 해당하고, f는 $s0에 해당한다.

컴파일된 프로그램은 다음과 같은 프로시저 레이블로부터 시작된다.

leaf_example:


2) 프로시저가 사용할 레지스터 값을 저장해야 한다. // 임시레지스터 2개 사용

$t0,$t1,$s0 세 개 저장. (=스택에 세 워드를 저장할 자리를 만든 후 값 저장)

addi $sp, $sp, -12 // 3개의 방을 만들어줌 (프로시저이기 때문에 이 과정이 필요)

sw $t1, 8($sp)

sw $t0, 4($sp)

sw $s0, 0($sp)

 

3) 계산

add $t0, $a0, $a1

add $t1, $a2, $a3

sub $s0, $t0, $t1

 

4) 계산 결과 f를 보내주기 위해, f를 결과 값 레지스터에 복사한다.

add $v0, $s0, $zero // $v0는 보낼 값, $zero는 복사해주는 역할

 

5) 호출 프로그램으로 돌아가기 전에, 저장해 두었던 값을 스택에서 꺼내 레지스터를 원상 복구한다.

lw $s0, 0($sp)

lw $t0, 4($sp)

lw $t1, 8($sp)

addi $sp, $sp, 12

 

6) 이 프로시저는 복귀 주소를 사용하는 jr 명령으로 끝난다.

jr $ra

* 위의 예제에서 임시레지스터 값도 저장했다가 원상복구해야한다고 가정함. 그러나 사용하지도 않는 레지스터 값을 쓸데없이 저장했다 복구하는 일이 생길 수 있음.

* 이를 예방하기 위해, MIPS 소프트웨어는 레지스터 18개를 두 종류로 나눔

$t0-$t9 : 프로시저 호출 시, 피호출 프로그램이 값을 보존해 주지 않는 임시 레지스터

$s0-$s7 : 프로시저 호출 전과 후의 값이 같게 유지되어야 하는 변수 레지스터 8개 (피호출 프로그램이 이 레지스터를 사용하면 원래 값을 저장했다 원상 복구한다.)

 

- 이러한 간단한 관례를 정함으로서 레지스터 스필링을 많이 줄일 수 있음.


2) 중첩된 프로시저

[ 재귀 프로시저의 컴파일 ]

ex )
n 계승을 계산하는 다음 재귀 프로시저에 해당하는 MIPS 어셈블리 코드를 보여라.

int fact (int n){

if(n<1) return(1)

else return(n*fact(n-1)); }

 
풀이 )

1) 인수 n은 레지스터 $a0에 해당. 번역된 프로그램은 프로시저 레이블로 시작하며, 뒤이어 복귀 주소와 $a0를 스택에 저장하는 명령어가 나옴.

fact:

addi $sp, $sp, -8  //이정도 공간을 할당하겠다. (지역성)

sw $ra, 4($sp)

sw $a0, 0($sp)

 

2) fact가 처음 호출되었을 때, sw는 fact(호출한 프로그램의 주소)를 저장한다.

이후 n<1인지 검사해서, n>=1이면 L1으로 가도록 명령어 지정.

slti $t0, $a0, 1 // n<1에 대해 테스트 한 후, 결과값을 $t0에 넣기

beq $t0, $zero, L1 // 만약 n>=1이면($t0와, $zero가 같으면) L1으로 가라.

 

3) n<1이면 1을 결과 값 레지스터에 넣는다.

이때 0에다 1을 더해서 $v0에 넣는다.

복귀하기 전에 스택에 저장된 값 두 개를 버리고 복귀 주소로 점프한다.

addi $v0, $zero, 1 // return 1;

addi $sp, $sp, 8 // 공간을 제거하라

jr $ra

 

4) n>=1이면, 인수 n을 감소시키고 이 감소된 값으로 다시 fact를 호출한다.

L1 : addi $a0, $a0, -1 // n-1

jal fact // n-1을 가지고 fact로 간다. (점프 기능 + return 주소 지님)

 

5) 호출한 프로그램으로 되돌아가기.

스택 포인터를 사용해서 이전의 복귀 주소와 값을 복구한다.

lw $a0, 0($sp)

lw $ra, 4($sp)

addi $sp, $sp 8

 

6) 인수 $a0과 결과 값 레지스터의 현재 값을 곱해서 $v0에 넣는다.

mul $v0, $a0, $v0 // return n*fact(n-1)

 

7) 마지막으로 복귀 주소를 이용해 되돌아간다.

jr $ra

 

[ 프로시저 호출 전후의 값 보존 관계 ]

- 피호출 프로그램이 $sp보다 위쪽에는 값을 쓰지 못하게 함으로써, $sp 윗부분의 스택을 원상태로 유지한다.

- $sp 자체는 뺀 값만큼을 피호출 프로그램이 도로 더해서 원래 값을 유지하고, 다른 레지스터는(만일 프로시저 내에서 사용되면) 스택에 저장했다가 다시 꺼내서 원래 값을 유지하게 된다.`

보존O

보존X

Saved registers : $s0 - $s7

Temporary registers : $t0 - $t6

Stack pointer register : $sp

Argument registers :$a0 - $a3 (인자)

Return address register : $ra

Return value registers:$v0 - $v1 (반환)

Stack above the stack pointer

stack below the stack pointer


3) 새 데이터를 위한 스택 공간의 할당

레지스터에 들어가지 못할 만큼 큰 배열이나 구조체 같은 지역변수를 저장하는 데도 스택이 사용되기 때문에 문제가 복잡해진다.

 

- 프로시저 프레임 (= 액티베이션 레코드)

: 프로시저의 저장된 레지스터들과 지역 변수를 가지고 있는 스택 부분

 

- 프레임 포인터 ($fp)

: 프로시저의 저장된 레지스터와 지역 변수의 위치를 표시하는 값

* 프로시저 프레임의 첫 번째 값을 가르킴 ( = 모든 저장 단위를 가르킴 )

* 스택 포인터 값은 프로시저 내에서 변화할 수도 있는데, 메모리 내 지역 변수에 대한 변위는 변수가 프로시저 어느 부분에서 사용되느냐에 따라 달라질 수 있다. 이 문제점 때문에 프로시저가 더 이해하기 어려워진다.

* 프레임 포인터($fp)를 사용하면, 프레임 포인터가 변하지 않는 베이스 레지스터 역할을 하므로 지역 변수 참조가 간단해진다.

 ** 별도의 프레임 포인터 사용 여부와 상관 없이 액티베이션 레코드는 항상 스택에 존재함.

 


4) 새 데이터를위한 힙 공간의 할당

C 프로그래머는 프로시저에만 국한되는 자동 변수 외에도 정적 변수와 동적 자료구조를 위한 메모리 공간이 필요하다.

 

 

cf )
* 정적 변수(static) : 공간에 대한 정보 기억해서 다시 가져옴
* 자동 변수 : 공간을 초기화 시킴
* 정적 변수에 대한 접근을 단순화시키기 위해 MIPS는 전역 포인터($gp)라 불리는 레지스터를 에약해 놓고 있다.
  ** 전역 포인터($gp) : 정적 영역을 가르키도록 예약된 레지스터

C는 함수를 사용해서 힙의 공간을 할당받기도 하고, 사용하지 않는 공간은 되돌려 주기도 한다.

ex) malloc() : 힙에 공간을 할당한 후 이 공간을 가리키는 포인터를 결과 값으로 반환.

free() : 포인터가 가리키는 힙 공간을 반납.

 

C에서는 이러한 메모리 할당을 프로그램이 통제한다.

- 그러나 이 부분이 흔하고도 까다로운 여러 버그의 근원이다.

(1) 사용이 끝난 공간을 반납하는 것을 잊어버리면 <메모리 누출>이 발생하여, 결국은 <메모리 부족>으로, 운영체제가 붕괴될 수 있다.

(2) 반면 공간을 너무 일찍 반납하면 프로그램 의도와 관계없이 엉뚱한 것을 가리키는 <메달린 포인터>가 발생한다.

* Java에서는 이러한 버그를 피하기 위해 자동 메모리 할당과 가비지 컬렉션(garbage collection)을 사용한다.

 

 

 

[ MIPS 레지스터 사용 관례 ]

Name

Register Number

Usage

Preserved on call?

$zero

0

The constant value 0

n.a.

$v0-$v1

2-3

Values for results and expression evaluation

no

$a0-$a3

4-7

Arguments

no

$t0-$t7

8-15

Temporaries

no

$s0-$s7

16-23

Saved

yes

$t8-$t9

24-25

More temporaries

no

$gp

28

Global pointer

yes

$sp

29

Stack pointer

yes

$fp

30

Frame pointer

yes

$ra

31

Return address

yes

* $at라고 부르는 1번 레지스터는 어셈블러 전용으로 예약되어 있음

* $k0-$k1이라고 부르는 26,27번 레지스터는 운영체제 전용으로 예약되어 있음.

 

 


 

 

 9. MIPS의 32비트 수치를 위한 주소지정 및 복잡한 주소지정 방식

- MIPS 명령어의 길이를 32비트로 고정한 덕택에 하드웨어가 간단해지기는 했지만, 32bit 상수나, 32bit 주소를 사용하게 되면 편한 경우가 많다. 이 절에서는 32bit 상수를 지원하는 방법과 분기 명령어나 점프 명령어에서 사용되는 명령어 주소 적화를 알아본다.


1) 32bit 수치 피연산자

- 프로그램에서 사용하는 상주는 대체로 크기가 작다. 대부분 16bit면 충분함

- 그러나 때에 따라서는 더 큰 상수가 필요한 경우도 있음

- 이럴 때를 위해 MIPS는 레지스터의 상위 16bit에 상수를 넣는 lui(load upper immediate) 명령어를 제공한다. 하위 16bit는 ori명령어를 사용한다.

[ 32bit 상수의 로딩 ]

ex )
레지스터 $s0에 다음 32bit 상수를 채우는 MIPS 어셈블리 코드를 작성하라

0000 0000 0011 1101 0000 1001 0000 0000


풀이 )

1) 먼저 lui를 이용해서 상위 16bit를 채운다. 상위 16bit의 값은 십진수로 61이다.

lui $s0, 61 // 61 = 0000 0000 0011 1101 binary

* lui의 명령어 동작 : lui 명령어는 16bit 수치 상수 필드의 값을 레지스터 왼쪽 16bit에 넣고 오른쪽 16bit는 0으로 채운다.

2) 이 명령어를 실행한 후 레지스터 $s0의 값은 다음과 같다.

0000 0000 0011 1101 0000 0000 0000 0000

3) 다음은 하위 16bit를 더하면 된다. 하위 16bit의 값은 십진수로 2304이다.

ori $s0, $s0, 2304 // 2304 = 0000 1001 0000 0000

4) 원하는 대로 레지스터 $s0에는 다음 값이 들어간다.

0000 0000 0011 1101 0000 1001 0000 0000

* 32bit 상수를 만드는 방법에서 주의할 점

- addi 명령어는 명령어의 16bit 상수 필드의 비트 15 값을 레지스터의 상위 16bit에 복사함

- 논리 또는 수치 명령어는 상위 16bit를 0으로 만들기 때문에, 어셈블러는 이 명령어를 lui와 함께 사용하여 32bit 상수를 만든다.

 

 

- 컴파일러나 어셈블러는 큰 숫자를 직접 다룰 수 없기 때문에 나눈 후 재조립이 필요

* 수치 명령어의 상수는 물론 load나 store의 메모리 주소도 상수 필드 크기의 제약이 문제가 됨.

MIPS 소프트웨어처럼 이 문제를 어셈블러가 해결하도록 하려면 큰 값을 만드는데 사용할 임시 레지스터를 제공할 필요가 잇음. $at(더 큰 수치의 주소를 확인하도록)

* MIPS 기계어의 기호 표현은 하드웨어에 의해 제한되기보다는 어셈블러를 만든 사람이 어떤 것을 포함시키기로 했느냐에 달려 있다.

 


2) 분기와 점프 명령에서의 주소지정

점프 명령(j)6bit의 op코드와, 26bit의 주소 필드로 구성되는 j타입 명령어 형식을 사용

 

(1) 점프 명령

2

10000

ex ) j 10000 // go to location 10000

 

(2) 조건부 분기 명령

ex ) bne $s0, $s1, Exit // go to Exit if $s0 <> $s1

이는 다음과 같이 어셈블되어 분기 주소로 16bit만 쓸 수 있다.

5

16

17

Exit

그러나 만일 프로그램에서 사용하는 모든 주소가 16bit 필드에 들어가야 한다면, 어떤 프로그램도 2^16보다 더 커질 수는 없다. 그러나 이것은 현실적으로 너무 작은 크기이다.

 

- 이에 대한 해결방안

PC = 레지스터 + 분기주소(갈 주소)

// 이 방식은 프로그램의 크기가 32bit, 즉 2^32까지 커지는 것을 허용하면서 조건부 분기도 지원함으로써 분기 주소의 크기 제한을 극복한다.

 

- 조건부분기는 주로 순환문이나 if문이 사용되므로, 가까이 있는 명령어로 분기하는 경향이 있다.

ex) SPEC 벤치마크에서 사용된 조건부 분기의 절반가량이 16개 명령어 이상 떨어지지 않은 위치로 분기한다.

 

- PC(Program Counter)는 현 명령어의 주소를 가지고 있으므로, 분기 주소를 더할 레지스터로 PC를 선택하면 현 위치에서 +-2^15워드 이내 떨어진 곳은 어디든지 분기가 가능하다.

 

- 이러한 분기 주소지정 방식을 PC 상대 주소지정 방식이라 한다.

: PC와 명령어 내의 상수의 합이, 실제 주소가 되는 주소지정

: 실제 MIPS 주소는 현재 명령어주소의 다음 명령어 주소를 기준으로 함 (PC+4)

: 이는 자주 생기는 일을 빠르게라는 원칙의 또 다른 예

: 조건부 분기의 목적지는 대체로 가까운 곳에 있기 때문에 PC 상대 주소를 사용하며, 프로시저들은 가까이 붙어 있어야 할 이유가 없으므로 jal 명령은 다른 주소지정 방식을 사용한다.

 

- MIPS 명령어의 길이는 항상 4byte이므로, MIPS의 PC 상대 주소지정 방식에서는 분기할 거리르 바이트 수가 아니라 워드 수로 나타내면 약 분기 거리를 4배 정도로 늘릴 수 있다.


3) MIPS 주소지정 방식 요약

1. 수치(immediate) 주소지정 : 피연산자는 명령어 내에 있는 상수이다.

2. 레지스터 주소지정 : 피연산자는 레지스터이다.

3. 베이스(base) 또는 변위(displacement) 주소지정 : 메모리 내용이 피연산자이다. 메모리 주소는 레지스터와 명령어 내의 상수를 더해서 구한다.

4. PC 상대 주소지정 : PC 값과 명령어 내의 상수의 합을 더해서 주소를 구한다.

5. 의사직접(pseudodirect) 주소지정 : 명령어 내의 26비트를 PC의 상위 비트들과 연접하여 점프 주소를 구한다.

 


* 유의사항
- 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다.
- 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다.
- 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :)
반응형