3. MIPS
Now we are going to study MIPS specifically!
Data Format
Most things are 32 bits!
- 명령어와 데이터 주소
- signed, unsigned integer
Floating Point Number (디시설 ON)
\[(-1)^S \times (M+1)\times2^e\]Big Endian vs Little Endian
예를 들어 4 byte의 정수 0x12345678를 저장한다고 하자.
Big endian은 하나의 32비트 저장소에 0x12, 0x34 … 순으로 저장된다.
Little endian은 하나의 저장소에 0x78, 0x56…으로 저장된다. 즉, Big endian과 little endian모두 1byte 단위로 나눠서 저장하나 big endian은 MSB쪽 조각을 먼저(주소가 작은 곳에), little endian은 LSB쪽 조각을 먼저(주소가 작은 곳에) 넣는다.
- Big Endian (MIPS)
- 크기 비교에 유리
- sign 여부 판단에 유리
- human-friendly함. 4byte를 적절히 자르기만 하면 읽는 순서가 그대로임
- Little Endian (x86)
- 홀수 짝수 판단에 유리
- 추가적인 조작 안해줘도 됨
Instruction Format
3가지가 있다. R-type
, I-type
, and J-type
.
- 4-byte per insturction (CISC는 아닌가봄)
- must be 4-byte aligned (2 LSB of PC should be 2’b00)
→ 하드웨어 구조 단순화. fetch를 4-byte 단위로 해서 한번에 가져올 수 있음. 메모리 접근 횟수 감소
1. ALU Instructions (ADD, SUB, …)
(1)
Assembly: ADD rd rs rt
(R-type)
Machine Code:
000000 sssss ttttt ddddd 00000 (ADD - 6 bits)
- 앞자리 000000으로 special instruction임을 확인
- GPR[rd] ← GRP[rt] + GPR[rs], PC ← PC + 4
Exceptions on overflow.
(2)
Assembly: ADDI rs rt immediate
(I-type)
Machine code
ADDI rs rt (immediate 16-bit)
- ADDI 라는 OPCODE 인식
- immediate를 가져와서, sign-extension.
- GPR[rt] ← GRP[rs] + immediate
→ There is no SUB
in I-type ALU instruction! (compiler가 부호를 바꾼 값을 ADDI하면 되기 때문)
2. Load Instructions
Assembly: LW rt offset base
Machine code:
LW (6-bit) base (5-bit) rt(5-bit) offset (16-bit)
- 실제 address 계산: GPR[base] + offset (sign extenstion)
- GPR[rt] ← MEM[addr]
- PC ← PC + 4
Data Alignment
load와 store는 WORD 단위로 이루어짐.
만약 두 word에 따로 떨어진 것을 이어서 가져오려면 LW/SW를 2번 써야함.
ex) 7 6 5 4
1
3 2 1 0
의 순으로 되어있을때, 4-3-2-1 을 가져오고싶다.
LWL rd 3(r0)
LWR rd 6(r0)
을 각각 해야한다. → Alignment가 강요된다. (MIPS 등, 인텔 머신은 아무데서나 가능)
3. Store Instructions
Assembly: SW rt offset(base)
Machine code:
SW (base 5-bit) (rt 5-bit) offset (16-bit)
→ 즉, 어느 한 주소에서 2^16 이상 넘어가는게 불가능하다. 이는 뒤에서 설명!
이때, offset(16-bit)과 base의 값 (32-bit)를 sign-extenston한다.
Load Delay Slots
1
2
3
LW ra --
addi r- ra r-
addi r- ra r-
LW가 정말 오래걸릴 것 같으니… 성능은 하드웨어의 영역인데 프로그래머가 이렇게 짜버렸다.
4. Branch Insturctions
Assembly: BEQ rs rt immediate
Machine code
BEQ (6-bit) rs rt immediate(16-bit)
(I-type)
if GPR[rs] === GPR[rt], PC ← PC + target
else, PC ← PC +4
5. Jump Instruction
Assembly: J immediate (26)
Machine code
J (6-bit) immediate (26-bit)
target = {PC[31:28], immediate}
즉, PC의 앞 4비트를 읽는다는 것은, 전체 4GB 메모리 영역을 16등분한 뒤, 해당 block에서만 움직이겠다는 것으로 이해해도 좋다. 따라서, 내 공간의 첫자리 내부에서 이동이 가능하다.
Branch Delay Slots
- branch instruction은 architectural latency가 1만큼 존재한다.
→ 그래서 옛날에는 nop을 넣어줘서 컴파일러가 이렇게 해버린다.
1
2
3
4
5
6
bne r1 r2 L1
add r3 r4 r0
j L2
...
1
2
3
bne r1 r2 L1
j L2
add r3 r4 r0
branch 조건이 만족하는지 체크할 때 까지 nop 으로 채워버린다. → pipe-lining의 성능 최적화를 위함.
independent한 명령어가 있기 때문에 순서를 바꿔버린다.
→ 아키텍쳐 위배
Function Call and Return
Jump and Link
- JAL offset (26-bit)
→ 그 다음에 실행할 명령어 주소를 r31에 저장하고 jump한다.
Jump Indirect
JR rs_reg
- 아무데나 register 주소에 있는 값 위치로 점프한다.
→ usually used by r31 (function return)
Caller-save Callee-save
- Caller-saved register.
- 함수가 호출되며 들어가기 전에 stack에 register 상황을 모두 저장하고 rsp 늘리고 rbp 옮기고를 한다.
- r31
- Callee-saved register
- jump한 후 쓰기 전에, register 상태 저장. 나가기 전에 복구하고 return
Calling convention
- Caler saves caller-saved register
- caller loads arguments into r4~r7 → 함수 argument
- caller jumps to callee using JAL (call하면서 r31에 다음 명령어를 집어넣음.)
—prologue—-
- Callee allocates space on stack (rbp값에 rsp옮기고 rsp값 줄이기)
- Callee saves callee-saved registers to stack (r4~r7, old rbp, r31 → callee save)
–epilogue—
- Callee loads results to r2, r3
- Callee restores callee-saved registers
- JR r31
—
- Caller restores caller-saved
- caller continues with return value r2, r3 (x86에서는 rax…였던걸로 기억)