상위 수준 언어 프로그램과는 달리 산술 명령어의 피연산자에는 제약이 있다. 레지스터(register)라고 하는 하드웨어로 직접 구현된 특수 위치 몇 곳에 있는 것만을 사용할 수 있다. MIPS 구조에서 레지스터의 크기는 32비트이다. MIPS에서는 32비트가 한 덩어리로 처리되는 일이 매우 빈번하므로 이것을 워드(word)라고 부른다.
변수와 하드웨어 레지스터의 큰 차이점 하나는 레지스터는 개수가 한정되어 있다는 점이다. 기호 형태로 표현된 MIPS 언어를 단계적으로 구체화할 때 산술 명령어의 각 피연산자는 32개의 32비트 레지스터 중 하나여야 하는 제약이 있다.
레지스터 개수를 32개로 제한하는 이유는 하드웨어 기술의 바탕이 되는 세 가지 설계 원칙 중 두 번째 원칙에서 찾을 수 있다.
설계 원칙 2: 작은 것이 더 빠르다.
메모리 피연산자
프로그래밍 언어에는 값 하나만 기억하는 단순 변수 외에도 배열(array)이나 구조체(structure)같은 복잡한 자료구조가 있다. 이런 복잡한 자료구조 하나에는 레지스터 개수보다 훨씬 많은 데이터 원소가 있을 수 있다. 그렇다면 이런 큰 구조는 컴퓨터 내에서 어떻게 표현되고 사용되는가?
프로세서는 소량의 데이터만을 레지스터에 저장할 수 있지만, 컴퓨터 메모리는 수십억 개의 데이터를 저장할 수 있다. 그러므로 배열이나 구조체 같은 자료구조는 메모리에 보관한다.
MIPS의 산술 연산은 레지스터에서만 실행되므로 메모리와 레지스터 간에 데이터를 주고받는 명령어가 있어야 한다. 이런 명령어를 데이터 전송 명령어(data transfer instruction)라 한다. 메모리에 기억된 데이터 워드에 접근하려면 명령어가 메모리 주소(memory address)를 지정해야 한다. 메모리는 주소가 인덱스 역할을 하는 큰 1차원 배열이다. 주소는 0부터 시작한다.
예를 들어 위 그림의 세 번째 데이터 원소의 주소는 2이고, Memory [2]의 값은 10이다.
Load word
메모리에서 레지스터로 데이터를 복사해 오는 데이터 전송 명령을 적재(load)라 한다. 적재 명령은 연산자 이름과 메모리에서 읽어 온 값을 저장할 레지스터, 메모리 접근에 사용할 상수와 레지스터로 구성된다. 메모리 주소는 명령어의 상수 부분과 두 번째 레지스터 값의 합으로 구해진다. MIPS에서 이 명령어의 실제 이름은 lw(load word)이다.
예제: 메모리 피연산자를 사용하는 치환문의 번역
Problem
A는 100 워드 배열이고, 변수 g, h는 레지스터 $s1, $s2에 할당되었다고 가정한다. 또 배열 A의 시작 주소(base address)가 $s3에 기억되고 있다고 할 때 다음 C문장을 컴파일하라.
g = h + A[8];
Solution
이 치환문에 연산은 하나밖에 없지만, 피연산자 중 하나가 메모리에 있으므로 먼저 A[8]을 레지스터로 옮긴 후 연산을 시작해야 한다. 이 배열 원소의 주소는 $s3에 있는 배열의 시작 주소에 인덱스 8을 더한 값이다. 이 데이터는 다음 명령어가 사용할 수 있도록 임시 레지스터에 넣어야 한다.
lw $t0, 8($s3) # Temporary reg $t0 gets A[8]
이제 필요한 값인 A[8]을 레지스터 $t0에 넣었으므로 덧셈을 수행할 수 있다.
add $s1, $s2, $t0 # g = h + a[8]
데이터 전송 명령어의 상수 부분(8)을 변위(offset)라 하고, 주소 계산을 위해 여기에 더해지는 레지스터($s3)를 베이스 레지스터(base register)라고 한다.
Byte Ordering
변수를 레지스터와 연관 짓는 일뿐 아니라 배열이나 구조체 같은 자료구조를 메모리에 할당하는 것도 컴파일러의 임무이다. 그런 다음 컴파일러는 자료구조의 시작 주소를 데이터 전송 명령에 넣을 수가 있다.
프로그램에서 8비트로 구성된 바이트를 많이 사용하므로 대부분의 컴퓨터는 바이트 단위의 주소를 지정한다. 워드 주소는 워드를 구성하는 4바이트 주소 중 하나를 사용한다. 그러므로 연속된 워드의 주소는 4씩 차이가 난다.
MIPS에서 워드의 시작 주소는 항상 4의 배수이어야 한다. 이러한 요구 사항을 정렬 제약(alignment restriction)이라고 하며, 많은 컴퓨터에서 이 방법을 사용한다.
컴퓨터는 제일 왼쪽, 즉 최상위(big end)바이트 주소를 워드 주소로 사용하는 것과 제일 오른쪽, 즉 최하위(little end) 바이트 주소를 워드 주소로 사용하는 것 두 종류로 나누어진다. MIPS는 최상위 주소를 사용하는 빅 엔디안(big endian) 계열에 속한다. 이런 저장 순서는 동일 데이터를 워드로 혹은 4바이트로 접근할 때만 문제가 되기에 이 엔디안에 대해서는 몇몇 사람들만 알면 된다.
- Big Endian : Least significant byte has highest address
Sun, PPC Mac, Internet - Little Endian : Least significant byte has lowest address
x86, ARM processors running Android, iOS, and Windows
Example
- Variable x has 4-byte value of 0x01234567
- Address given by &x is 0x100
바이트 주소의 사용은 배열의 인덱스에도 영향을 미친다. 앞의 코드에서 바이트 주소를 제대로 구하려면 베이스 레지스터 $s3에 offset 4 * 8, 즉 32를 더해야 한다. 그래야 A[8/4]이 아닌 A[32/4]의 주소가 구해진다.
Store Word
적재와 반대로 레지스터에서 메모리로 데이터를 보내는 명령을 저장(store)이라고 한다. 저장 명령의 생김새는 적재와 같다. 즉 연산자 이름, 저장할 데이터를 갖고 있는 레지스터, 배열 원소 선택에 사용할 변위, 베이스 레지스터로 구성된다. 앞에서와 마찬가지로 주소의 일부는 상수 형태로 명령어에 포함되어 있고 일부는 레지스터에 기억되어 있다. MIPS에서 이 명령어의 실제 이름은 sw(store word)이다.
예제: 적재와 저장을 사용한 번역
Problem
변수 h가 레지스터 $s2에 할당되어 있으며 배열 A의 시작 주소는 $s3에 들어 있다고 가정하자. 다음 C문장을 MIPS 어셈블리 프로그램으로 바꾸어라.
A[12] = h + A[8];
Solution
연산자는 하나이지만 피연산자 2개가 메모리에 있기 때문에 MIPS 명령어가 더 필요하다. 적재 명령어의 offset이 바이트 주소에 맞는 적절한 값(32)로 바뀐 것과 add 명령어가 합을 $t0에 넣는 것을 제외하면 처음 두 명령어는 앞의 예와 같다.
lw $t0, 32($s3) # Temporaryy reg $t0 gets A[8]
add $t0, $s2, $t0 # Temporaryy reg $t0 gets h + A[8]
마지막 명령어는 48(4 * 12)을 offset으로, $s3를 베이스 레지스터로 사용하여 합을 A[12]에 저장한다.
sw $t0, 48($s3) # Stores h + A[8] back into A[12]
Registers vs. Memory
컴퓨터가 갖고 있는 레지스터보다 프로그램에서 사용하는 변수가 더 많은 경우가 자주 있다. 그러므로 컴파일러는 자주 사용되는 변수를 가능한 많이 레지스터에 넣고 나머지 변수는 메모리에 저장했다가 필요할 때 꺼내서 레지스터에 넣는다. 자주 사용하지 않는 변수를 메모리에 넣는 일을 레지스터 스필링(spilling register)이라고 말한다.
레지스터에 저장된 데이터는 메모리 데이터보다 사용하기도 편리하다. MIPS의 산술 연산 명령은 레지스터 2개를 읽어서 연산한 다음 결과를 레지스터에 쓴다. 하지만 데이터 전송 명령은 피연산자 하나를 읽거나 쓰는 일만 할 뿐 데이터에 대한 연산은 하지 못한다.
레지스터는 메모리보다 접근시간이 짧고 처리량도 많으므로, 레지스터에 저장된 데이터를 사용하면 시간도 절약되고 사용하기도 간편하다. 그뿐만 아니라 레지스터 접근은 메모리 접근보다 에너지도 적게 든다.
상수 또는 수치 피연산자
적재 명령을 사용하지 않고 연산할 수 있는 방법은 피연산자 중 하나가 상수인 산술 연산 명령어를 제공하는 것이다. 이 상수를 수치(immediate) 피연산자라고 한다. 수치 피연산자를 갖는 덧셈 명령어는 addi인데 레지스터 $s3에 4를 더하려면 다음과 같이 쓰면된다.
addi $3, $s3, 4 # $s3 = $s3 + 4
상수 피연산자는 자주 사용되므로, 상수 필드를 갖는 산술 명령어를 사용하면 매번 메모리에서 상수를 가져오는 것보다 연산이 훨씬 빨라지고 에너지를 덜 소모하게 된다.
상수 중에서도 0은 또 다른 역할이 있다. 명령어에 여러 가지 유용한 변형을 제공함으로써 명령어 집합을 단순하게 하는 것이다. 예를 들면 복사(move) 연산은 피연산자 중 하나가 0인 add 명령어이다.
따라서 MIPS에서는 레지스터 $zero를 값 0으로 묶어 두도록 회로가 구현되어 있다.