Code Reordering (명령어 스케줄링, Instruction Scheduling)은 하드웨어가 어쩔 수 없이 발생시키는 파이프라인의 거품(Stall)을 소프트웨어(컴파일러)가 똑똑하게 재배치하여 없애버리는 마법입니다.
앞서 우리는 하드웨어에 아무리 훌륭한 Forwarding 회로를 달아도, Load-Use Hazard 앞에서는 타임머신이 없어서 무조건 1사이클을 쉬어야(Stall) 한다는 것을 확인했습니다. 컴파일러는 이 1사이클을 그냥 놀리면서 버리는 것을 참지 못합니다.
그래서 컴파일러는 코드를 훑어보고 "어차피 1클럭 쉴 바에, 결과에 아무런 영향을 안 주면서 당장 실행할 수 있는 다른 명령어를 저 빈칸(Stall 위치)에 땡겨와서 실행하자!"라고 결정합니다. 마치 테트리스 빈칸을 채우는 것과 같습니다.
LD R2, 0(R1) // Load B into R2
LD R3, 4(R1) // Load E into R3
ADD R4, R2, R3 // Stall! (Needs R3 from previous LD)
ST R4, 12(R1) // Store A
LD R5, 8(R1) // Load F into R5
ADD R6, R2, R5 // Stall! (Needs R5 from previous LD)
ST R6, 16(R1) // Store C
LD R2, 0(R1) // Load B into R2
LD R3, 4(R1) // Load E into R3
LD R5, 8(R1) // Load F into R5 (Moved up!)
ADD R4, R2, R3 // No stall! (R3 was loaded 2 cycles ago)
ST R4, 12(R1) // Store A
ADD R6, R2, R5 // No stall! (R5 was loaded 2 cycles ago)
ST R6, 16(R1) // Store C
[해결된 원리 (왜 스톨이 사라졌을까?)]

앞서 우리가 BNEZ (Branch if Not Equal to Zero) 명령어를 풀면서 겪었던 '점프 페널티'나 '플러시(Flush)'의 근본적인 원인이 바로 이 녀석입니다
"If the branch is taken (condition is true) next instruction should be fetched from the target address"
일반적인 명령어들은 바로 다음 줄(PC + 4)을 순서대로 실행하면 됩니다. 하지만 if문이나 for, while 루프를 만드는 조건부 분기 명령어(Branch)는 다릅니다.
"We have necessary data at the end of ID stage or EX stage (depending on where the target address is calculated)"
우리가 다음 명령어를 제대로 가져오려면 두 가지 정보가 필요합니다.
문제는 이 정보들이 너무 늦게 계산된다는 것입니다.
"The next instruction needs the data at the beginning of IF stage"
이것이 파이프라인의 치명적인 모순입니다.
분기 명령어의 바로 다음에 실행될 명령어는, 자신의 첫 번째 단계인 IF (Instruction Fetch) 단계를 시작하자마자 "내가 어느 주소에서 명령어를 가져와야 하는지(PC 값)"를 알아야 합니다.
자동차 운전(내비게이션)에 비유해 보겠습니다.
분기 명령어가 ID나 EX 단계에서 "어디로 갈지" 열심히 계산하고 있는 동안, 바로 뒤따라오는 다음 명령어는 이미 파이프라인의 IF 단계로 진입하려고 대기 중입니다. 하지만 앞 명령어가 아직 계산을 안 끝냈으니, 다음 명령어는 도대체 메모리의 어디(어떤 주소)로 가서 명령어를 가져와야 할지 알 수가 없습니다.
이 엇박자로 인해 파이프라인이 멍을 때리거나 엉뚱한 길로 들어서게 되는 현상을 Control Hazard라고 부릅니다.
"그래서 이번에 시험 볼 과목(테스트 기준)이 뭐야?"
"테스트 채점 결과, 합격(O)이야 불합격(X)이야?"
"합격(Taken)했으면, 이제 어디로 가야 해? 정확한 주소 줘!"
다음 명령어는 파이프라인의 IF 단계를 당장 시작해야 하는데, CPU는 저 세 가지 정보가 하나도 없습니다.
어디로 갈지(Target Address) 덧셈도 해봐야 하고,
점프할지 말지(Condition) 비교도 해봐야 하고,
최종 결정(Outcome)을 내려야만 비로소 다음 명령어를 올바르게 가져올 수 있습니다.
이 계산들이 끝날 때까지 걸리는 시간이 바로 우리가 표에 그렸던 스톨(Stall) 1칸의 정체입니다.