목록을 지우다
예약어 목록은 여섯 번 틀렸고, 목록 자신의 주석이 또 틀릴 것이라고 인정하고 있었다. robust 한 수정 —— 모든 사용자 값 · 함수 이름에 접두사를 붙여 C 와 충돌 불가능하게 만드는 것 —— 은 크고 위험한 변경으로 몇 달이나 미뤄져 있었다. 조사가 그 리스크를 녹였다. 셀프호스트의 fixpoint 는 완전히 WAT backend 위에서 돌고 C 의 명명에 전혀 닿지 않는다. 그리고 sanitizer 는 이미 모든 이름이 지나는 단일한 관문이었다. 그래서 접두사를 넣고 목록을 지웠다. 균일한 접두사는 자기 검증적임이 판명된다 —— sanitizer 를 건너뛰는 emission 경로는 운 나쁜 이름만이 아니라 모든 함수에서 실패한다 —— 그리고 그 성질이 목록이 조용히 가리고 있던 세 개의 잠재적 불일치를 즉시 드러냈다. 하나의 후퇴: 형 이름은 raw 그대로. 재귀 variant 의 기계가 그것을 키로 삼기 때문이다. 예약 이름 문제는 양쪽 절반 모두 구조적으로 닫혔다.
예약어 목록 옆의 주석은 몇 달째 정직했다. “이 목록은 본질적으로 불완전하다 —— robust 한 수정은 모든 사용자 최상위 함수 이름의 이름공간화, 더 큰 byte-stream 변경으로서 보류”. 여섯 번의 충돌을 지나, 그 문장에서 아직 일하고 있는 부분은 “보류” 뿐이었다. 보류를 지속시킨 것은 특정한 형태의 두려움이다. 컴파일러는 셀프호스트하고, 셀프호스트 출력은 byte 일치로 검증된다. 모든 이름의 철자 방식을 바꾸는 것은 fixpoint 를 깨는 부류의 일로 들렸다.
거기 없었던 리스크
그래서 첫수는 코드가 아니라 조사였다. 두려움을 녹이는 두 가지 사실이 돌아왔다. 첫째, byte 일치의
fixpoint 는 완전히 WebAssembly 경로에 산다 —— Mere 로 쓰인 컴파일러는 WAT 를 emit 하고, 함수를
index 로 명명하며, C backend 를 아예 갖지 않는다. C 의 name mangler 와 fixpoint 는 아무것도 공유하지
않는다. 둘째, C backend 에는 이미 단일한 관문이 있었다. 모든 사용자 이름이 C 식별자가 되는 길에
지나는 하나의 sanitizer 함수, 약 마흔 곳에서 불린다. “너무 커서 미뤄진 변경” 은 그 핵심에서 하나의
함수를 고치는 일이었다. 목록을 확인해 운 나쁜 이름에 밑줄을 붙이는 대신, 모든 것에 접두사를 붙인다.
사용자의 foo 는 mu_foo 가 되고, 어떤 사용자 이름도 C 키워드와도 POSIX 심볼과도 Bessel 함수와도
충돌할 수 없다. 그 접두사로 시작하는 C 의 이름은 존재하지 않기 때문이다. 목록 —— 쌓여 온 조심의
예순몇 항목 —— 은 삭제된다.
자기 일을 검사하는 접두사
이 변경을 단지 올바를 뿐 아니라 안전하게 만든 성질은, 균일한 접두사가 자기 검증적이라는 것이다. 옛
방식에서는 sanitizer 에 상담을 잊은 emission 경로가 거의 모두에게 올바른 C 를 만들고, 매개변수를
y0 이라 이름 붙인 사람만을 깼다 —— 운이 나빠질 때까지 침묵한다. 새 방식에서는 sanitizer 를 건너뛴
경로가 접두사 없는 이름을 emit 하고 그 참조는 모두 접두사가 붙어 있어, 그 불일치는 닿는 모든
함수에서 컴파일에 실패한다. 테스트 스위트 전체가 검출기가 된다. 그리고 검출했다. 변경 후 몇 분 안에
세 개의 잠재적 def/use 불일치가 떠올랐다 —— raw 로 선언되고 참조는 새니타이즈되어 있던 패턴 매치의
binder, lifted 호출에서 raw 그대로 넘겨지던 포획 인자, 그리고 접두사를 두 번 적용해 mu_mu_ 를 낳는
하나의 경로. 모두 여섯 번의 충돌을 낳은 것과 정확히 같은 종류의 버그로, 줄곧 코드 안에 앉아 있었고,
옛 sanitizer 가 평범한 이름에는 no-op 이라는 사실에 가려져 있었다. 이행이 자신의 낙오자를 스스로
드러냈다.
발견이기도 했던 후퇴
계획의 일부는 접촉을 견디지 못했다. 첫 시도는 형 이름에도 접두사를 붙였고, record 는 깨끗이 받아들 였다 —— 그러나 variant 가 미묘한 방식으로 깨졌다. 재귀 variant 의 기계는 raw 형 이름을 키로 검색하고, 그 검색이, 특히, 값에 점으로 접근할지 포인터 너머로 접근할지를 정하고 있다. 어떤 자리에서는 이름에 접두사를 붙이고 다른 자리에서는 붙이지 않는 것이, 힙에 할당된 closure 를 값 전달 코드 경로로 보냈다. 그 기계를 구석구석 쫓는 대신, 변경은 더 깨끗한 선까지 후퇴했다. 값과 함수는 접두사를 붙인다. 형 이름은 별개의 C 이름공간이며 raw 그대로, 고유의 mangler 로 분리한다. 형 이름이 C 와 충돌한 일은 실적으로 한 번도 없고, 만약 일어나도 그것은 닫힌 별개의 수정이다. 변경의 경계가 어디에 앉아야 하는지 정확히 아는 것은 변경 자체만큼의 가치가 있다.
스위트는 초록으로 끝났다 —— 2202 개의 테스트, emit 된 C 를 검사하는 약 마흔 개의 assertion 은 새 철자로 갱신되었고, 아무것도 shadow 하지 않은 프로그램은 모두 byte 일치. 두 편 전, 가드는 사용자 정의를 언어 자신의 내장에 이기게 했다. 이 릴리스는 사용자 이름이 C 와 충돌하는 것 자체를 불가능하게 만든다. dogfood 의 거의 격주로 버그를 낳던 예약 이름 문제는 양쪽에서 닫혔다 —— 더 긴 목록도, 더 주의 깊은 감사도 아니라, 충돌을 표현 불가능하게 만듦으로써. 재발하는 버그에 대한 최선의 수정은, 충돌할 상대가 존재하지 않는 세계다.