모든 이야기

precise exception: 먼저 계산한 9를 예외 처리기가 볼 수 없는 이유

예외가 발생한 프로그램을 다시 시작하려면 어디까지 실행되었는지 알아야 한다. 그런데 빠른 뒤쪽 명령이 먼저 끝나는 프로세서에서는 “끝난 명령”을 모으는 것만으로 그 지점을 정할 수 없다. precise exception은 예외 시점의 상태가 프로그램 순서의 한 지점에 정확히 대응하도록 만드는 성질이다. 계산의 완료와 상태의 확정을 나누는 이유가 여기에 있다.

먼저 끝난 결과를 모두 보여 주면 무슨 일이 생길까

가상의 프로그램에서 처음 x1=0, x3=0이라고 하자. 아래 주소는 4바이트 명령을 가정한 시작 주소다.

0x100  I0: x1 = 5
0x104  I1: x2 = load[접근할 수 없는 주소]
0x108  I2: x3 = 9

I2는 앞의 load 결과를 사용하지 않으므로 계산을 빨리 끝낼 수 있다. I0도 끝났지만 I1의 접근 오류는 나중에 판명되었다고 해 보자. 실행이 끝나는 즉시 프로그램의 레지스터를 바꾼다면, 예외 처리기는 x1=5와 x3=9를 보게 된다. 앞의 I0은 반영되었고 뒤의 I2도 반영되었는데, 그 사이 I1은 수행하지 못한 상태다.

단순한 동기적 접근 예외에서 기대하는 정확한 경계는 I1 직전이다. I0까지는 확정되고 I1의 목적지 변경과 I2의 변경은 보이지 않아야 한다. 따라서 예외 처리에 보이는 상태는 x1=5, x3=0이며, 문제가 발생한 명령의 주소는 0x104다. I2가 내부적으로 9를 계산해 둔 사실과, 프로그램이 x3=9를 보아도 된다는 판단은 구별된다.

이 예는 특정 장치의 측정 결과가 아니라, 한 명령에서 중단되는 동기적 예외 모델을 설명하기 위한 것이다. 명령이 부분 진행을 허용하는 특별한 경우나 비동기 이벤트의 세부 규칙은 해당 명령 집합의 계약을 따로 확인해야 한다.

순서가 남아 있는 기록이 필요하다

BOOM의 reorder buffer는 명령을 프로그램 순서로 추적한다. 명령을 보내면 아직 실행이 끝나지 않았다는 상태를 기록하고, 실행이 끝나면 그 대기를 해제한다. 그러나 architectural state에 변경을 확정하는 commit은 가장 오래된 쪽에서 진행한다. 앞의 명령이 아직 끝나지 않았다면 뒤의 명령이 먼저 끝나도 그 경계를 뛰어넘지 않는다. BOOM, The Reorder Buffer and the Dispatch Stage

위 명령열에서 I2가 먼저 완료되면 ROB에는 I2가 완료되었다는 사실이 남는다. I0은 조건이 맞으면 commit할 수 있다. I1에 예외가 기록되어 가장 오래된 명령이 되는 시점에는, I1을 정상적으로 commit하는 대신 예외를 처리하고 뒤의 명령을 버린다. I2의 빠른 완료가 예외 처리기의 x3를 바꾸지는 않는다.

따라서 예외를 처음 발견한 순간에 무조건 모든 앞쪽 명령까지 버리는 설명도 정확하지 않다. 더 오래된 명령이 아직 남아 있다면 그 명령들의 상태를 먼저 정해야 한다. 뒤에서 발견한 오류가 가장 먼저 처리할 오류라는 보장도 없다. BOOM 문서는 각 명령의 예외 여부를 표시하되, 세부 예외 정보는 현재 가장 오래된 예외에 대해 보관하는 구성을 설명한다. BOOM, Exception State

데이터를 어디에 두느냐와 정확한 경계는 별개다

ROB를 결과가 모두 들어 있는 큰 레지스터 파일처럼 상상하기 쉽다. 그런 구현 방식도 있지만 BOOM의 설명은 다르다. BOOM은 물리 레지스터 파일에 확정된 값과 추측 중인 값을 함께 보관한다. ROB에는 완료 여부와 예외, 이름 변환 등 commit을 판단하고 상태를 복구할 정보가 들어간다. BOOM, The Explicit Renaming Design

I2의 9가 물리 저장 공간에 남아 있어도, 확정된 매핑이 그것을 프로그램의 x3로 지정하지 않으면 예외 처리에 보이는 값이 아니다. 복구 과정은 확정된 상태에 맞는 매핑으로 돌아가야 한다. 이를 별도의 committed map으로 빠르게 복원하거나, 기록을 되감아 복원하는 선택은 지연과 저장 비용이 다르다. 정확한 상태를 얻는 성질이 언제나 한 사이클의 복구를 뜻하지는 않는다. BOOM, Resets on Exceptions and Flushes

레지스터를 되돌리는 것으로 끝나지 않는다

뒤쪽 I2가 x3 변경 대신 메모리 쓰기였다면 문제는 더 분명하다. 다른 관찰자가 이미 본 쓰기를 레지스터 매핑 복구만으로 없앨 수는 없다. BOOM의 store는 큐에서 주소와 데이터를 준비할 수 있지만, commit된 뒤에 메모리로 보낼 수 있다. 메모리에 내보내는 순서 역시 별도로 관리한다. BOOM, Store Instructions

이는 commit이 모든 메모리 접근의 물리적 완료와 같은 뜻이라는 말은 아니다. 확정된 store가 큐에서 더 기다릴 수 있고, 정상 메모리의 가시성은 메모리 시스템의 규칙도 따른다. 여기서 필요한 조건은 버려질 뒤쪽 명령의 변경을 되돌릴 수 없는 형태로 먼저 노출하지 않는 것이다. 실행 완료 비트만 검사해서는 이 조건을 보장할 수 없다.

precise exception의 답은 “모든 명령을 순서대로 계산한다”가 아니다. I2는 먼저 계산해도 된다. 다만 I1에서 멈춰야 한다는 사실이 정해졌을 때, 외부에 보이는 상태를 I0까지의 결과로 설명할 수 있어야 한다. 프로그램 순서의 확정 경계를 지키면 빠르게 실행한 결과를 보관하면서도, 예외 처리기가 어디서 다시 시작해야 할지 알 수 있다.