바이트마다
세 버그가 고쳐지고, 두 출력은 마침내 비교할 값이 있었다: 레퍼런스 컴파일러가 컴파일한 프로그램과, 자기 자신에 의해 컴파일된 컴파일러가 컴파일한 같은 프로그램. 그것들은 동일했다 —— 가깝지도 등가도 아니라, 같은 바이트가 같은 순서로 —— 그리고 어느 것을 돌려도 올바른 답을 낳았다. 그것이 bootstrap fixpoint이고, 언어가 자기 자신에 대해 말할 수 있는 가장 강한 것이다. 여기가, 당분간, 지도의 끝이다: 약속이 아니라, 거울을 들여다보고 자신이 변하지 않았음을 찾아낸 컴파일러로.
테스트는 이 Part의 처음부터 같았고, 이제 마침내 완전히 돌려질 수 있었다. 프로그램을 취한다. 레퍼런스 컴파일러 —— interpreter 아래에서 도는, 신뢰된, 올바른 것 —— 로 컴파일하고, 출력을 간직한다. 그런 뒤 자기 자신에 의해 WebAssembly 바이너리로 컴파일되어, 이제 그 바이너리로 돌고 있는 컴파일러로 같은 프로그램을 컴파일하고, 그 출력도 간직한다. 둘을 나란히 놓는다. Part 통째가 맴돌던 물음은, 그것들이 일치하는가, 였다.
동일
했다 —— 그리고 그 말이 때로 띠는 느슨한 의미로가 아니라. 두 출력은 같은 순서의 같은 바이트로, 마지막 한 글자까지 분간되지 않고, 차는 꼭 영이었다. 그리고 그것은 빈 것의 일치가 아니었다: 저마다가 실질적인 프로그램, 수천 줄의 생성 코드로, 완전히 일치했다. 그런 뒤 마지막 체크, 호기심을 사실로 바꾸는 것: 출력 자신이 돌려지고, 올바른 답을 낳았다. 그래서 사슬은 모든 고리에서 버텼다 —— 컴파일러, 자기 자신에 의해 컴파일된 컴파일러, 그리고 그 두 번째 컴파일러가 낳은 프로그램, 셋 다 올바르고, 가운데 둘이 동일. 그것이 bootstrap fixpoint다. 자기 자신에 의해 컴파일되어 바이너리로 돌고, 자기 자신의 레퍼런스 출력을 정확히 재생하는 컴파일러는, 단지 자기 자신을 emit할 수 있다는 약한 의미로 self-hosting인 게 아니다. 강한 의미로 self-hosting이다: 복제는 충실하고, 그리고 그것을 증명할 수 있다, 바이트마다.
왜 강한 의미가 세는 의미인가
왜 이것이 가질 값이 있는 주장인지를 명확히 할 값이 있다, 왜냐하면 약한 쪽이 그것과 헷갈리기 쉽기에. 자기 자신의 valid한 바이너리를 emit하는 컴파일러는, 그 코드 생성기가 자기 자신의 소스를 다룰 수 있음을 보였다 —— 실의 결과이나, 구조적인 것, validator가 받아들이는 형태에 대한. 그것은, 그 바이너리가 돌면 컴파일러가 거동하듯 거동하는가에 대해 아무것도 말하지 않는다. 강한 주장은 바로 그 gap을 닫는다. 그것은 emit된 컴파일러가 단지 well-formed할 뿐 아니라 컴파일러로서 올바르다, 그 출력을 원래의 것과 분간할 수 없을 만큼 충실하다고 말한다. 이 Part의 모든 것은 그 두 주장 사이의 공간에 살았다 —— overflow, 빠진 말단 호출, 거울만이 보여줄 수 있는 세 버그 —— 그리고 그 어느 것도, 언어가 완전히 자기 자신으로 서도록, interpreter의 관대한 스택도 타입 추론기의 환경적 지식도 받쳐 줌 없이, 되기까지 보이지 않았다. fixpoint는 그 받쳐 줌이 완전히 치워지고, 언어가 쓰러지지 않는 순간이다.
회귀 테스트, 그것이 참인 채이도록
한 번 증명된 사실은, 조용히 참이 아니게 될 수 있는 사실이다. 그래서 fixpoint는 테스트 스위트 자신에 쓰여 넣어졌다: 매 실행에서, 스위트는 레퍼런스 컴파일러와 자기 컴파일한 컴파일러로 프로그램을 컴파일하고, 두 출력이 바이트 동일이라고 assert한다. 증명은 더는 일어난 것이 아니다; 그것은 체크되는 성질이고, 충실한 복제를 깨뜨리는 미래의 어떤 변경도, 조용히 어긋나는 게 아니라 큰 소리로 실패한다. 마일스톤은 릴리스가 되었고, 릴리스는 이 Part가 세운 것을 날랐다: 보증된 말단 호출, 세 자기 컴파일 수정, 그리고 그것들 모두를 지키는 체크. 언어가 할 수 있는 가장 강한 주장이, 이제 지키기에 가장 싸다.
통째가 무엇에 대해서였나
여기가 지도의 가장자리이고, 거기에 다다르기 위해 건넌 땅을 돌아보기에 좋은 곳이다. 열 Part, 그리고 그 모두의 아래에 하나의 방법. 어려운 물음이 지상에서 정밀한 조각으로 narrowed되고, 신뢰되기 전에 작은 실험에서 시도되고, 그런 뒤 그 거친 가장자리가 나타날 때까지 실의 무언가에 대해 dogfood되었다. 새 문제가 나타났을 때, 첫 수는 더 발명하는 게 아니라 이미 세운 기구에 기대는 것 —— native가 된 메모리 모델, 가족을 기른 컴파일 타임 특수화, 한 번에 하나의 구문을 통해 threaded된 말단 위치 비트. 절제는 뒤에 증거가 있는 행위였다: 세워지지 않은 락, 투자되지 않은 LLVM backend, 서둘러지는 게 아니라 지평에 이름 지어져 남겨진 feature. 그리고 보증은 양방향으로 정직하게 지켜졌다 —— 거기 없는 건전성을 결코 주장하지 않고, 거기 있는 가장자리를 결코 숨기지 않고.
bootstrap fixpoint는 한 매듭에 어울리는 곳이다, 왜냐하면 그것은 그 방법 통째가 언어 자신을 향한 것이기에. 연재의 대부분에서 언어는 테스트하는 쪽, web 앱과 CLI와 문서를 세우고 그것들의 아픔을 찾는 도구였다. fixpoint에서 그것은 테스트되는 쪽이 된다 —— 자기 자신의 소스를 건네받고, 자기 자신을 정확히 재생하도록 요구받고, 그 가장 요구가 엄한 부하 아래에서, 더 부드러운 무엇도 결코 surface하지 않은 결함을 밝히도록 된다. 그것은 통과했다. 사상과 최소 코어로 시작한 언어가, 자기 자신을, 충실하게, 컴파일하고, 그렇게 함을 증명할 수 있다. 지도에는 아직 가장자리가 있고, 개발은 그것을 다시 그려 나갈 것이다. 하지만 이것은 실의 summit이고, 거짓의 것이 아니다 —— 컴파일러는 거울을 들여다보고 자신이 변하지 않았음을 찾아냈다 —— 그리고 여기가, 이 건설의 기록이 끝나는 곳이다.