자신의 출력을 읽는다

세 번째 도구는 바이너리 파일을 읽었고, 향해진 바이너리는 컴파일러 자신의 WebAssembly 출력이었다 —— 언어가 스스로 emit하는 바로 그 바이트를 읽기. 드러내려던 gap은 바이너리 처리였고, 그것을 정밀히 재는 것이 핵심이었다:버퍼는 이미 모든 바이트를 안전히 보유했고, C의 문자열 길이에 선 단 하나의 길이 함수만이, 첫 박힌 영에서 잘라내고 있었다. 수정은 builtin 하나, 통째로 새 타입이 아니라.

meredogfoodbinarywasmlanguage-design

세 번째 도구는 바이너리 파일을 읽었고, 그리고 읽기에 특히 어울리는 바이너리가 하나 있었다: 컴파일러 자신의 WebAssembly 출력이다. 컴파일러를 어떤 모드에서 자신에 향하면 .wasm 파일을 emit한다;이 새 도구 —— mwasm —— 를 그 파일에 향하면, magic number, version, 그리고 안의 섹션 목록을 알린다. 언어가 스스로 emit하는 바로 그 바이트를 읽게 된다. 그것은 멈춰 설 가치가 있는 순환을 닫는다:자신의 출력을 읽을 수 있는 컴파일러는, 원리상 자신의 일을 검사할 수 있는 컴파일러이고, 그 reader를 세우는 것이 그리로의 첫걸음이다. 공언된 gap은 바이너리 처리 —— string 타입은 늘 텍스트를 위해 세워졌다 —— 였고, 그리고 형식 parser는 그리로 걸어 들어가는 정직한 방식이었다.

이름 짓기 전에 gap을 잰다

여기서 추측이 아니라 재는 규율이 가장 날카롭게 보답했다, 왜냐하면 명백한 추측이 틀렸기에. 「언어는 바이너리를 다룰 수 없다」는 큰 프로젝트를 시사했을 것이다:새 bytes 타입, 새 연산자, 통째로 병행하는 기능 한 벌. 하지만 이 파일들의 하나를 읽고, 실제로 무엇이 일어났는지를 probe하자, 훨씬 좁은 이야기가 말해졌다. interpreter는 이미 완전히 binary-safe였다:파일의 진짜 길이를 알렸고, 박힌 영을 넘은 개별 바이트의 읽기는 옳은 값을 반환했다. native build도 안전했고, 단 하나의 예외가 있었다 —— 파일의 길이를 묻자 영이 반환되었다, 그 한 조작이 C의 문자열 길이 개념에 서고, 첫 영 바이트에서 세기를 그만두기에. 버퍼는 모든 바이트를 보유하고 있었다;망가진 것은 자뿐이었다. 그래서 gap은 「바이너리 처리가 없다」가 아니라, 훨씬 작은 「하나의 길이 함수가 바이너리 데이터에서 길이를 잃는다」였다. 그것을 그만큼 정밀히 이름 짓는 것이, 수정을 작게 유지했다.

새 타입이 아니라 builtin 하나

유혹적인 응답은 대물을 세우는 것이었을 것이다:string과 별개의, 자신의 조작을 가진 본격적인 bytes 타입. 그것은 실의 정직한 가장자리이고, 그리고 의도해 deferred되었다 —— 프로그램이 실제로 그것을 요하는 날까지 남겨졌다 —— 왜냐하면 훨씬 작은 수정이 길을 열었기에. 디스크 상의 파일 크기를 OS에 묻는 builtin을 하나, 기존의 「이 파일은 언제 변경되었는가」의 바로 옆에 더하는 것이, 프로그램에 신뢰할 수 있는 길이를 주었다. 거기서 규율은 단순했다:버퍼와 그 진짜 크기를 짝으로 함께 나르고, 모든 바이트 walk가 이미 binary-safe였던 바이트 읽기 조작으로 돈다. 새 타입 없음, 새 연산자 없음, builtin 하나 —— 그리고 도구 전체의 막힘이 풀렸다. 더 큰 기계를 세우지 않는다는 선택은, 세워진 작은 것과 똑같이 중요했다;프런티어는 빠진 것을 더하는 것만이 아니라, 눈앞의 프로그램이 실제로 요하는 이상을 더하고 싶은 충동에 저항하는 것이기도 하다.

두 번째 빠진 기능을 우회한다

섹션 크기의 읽기는 가변 길이 정수 형식을 요했다, 수를 7 비트씩, 각 바이트에 계속 플래그를 붙여 저장하는 종류다. 그것을 디코드하는 교과서적 방식은 비트 연산자를 쓴다 —— 하위 7 비트를 마스크, 시프트, 반복 —— 그리고 언어는 비트 연산자도 갖지 않았다. 멈춰 서서 그것들을 더하는 게 아니라, 디코더는 평명한 산술로 쓰였다:하위 7 비트는 128로 나눈 나머지이고, 계속 플래그는 바이트가 그 값 이상인지 여부다. 같은 수가 떨어져 나온다, 언어가 이미 가지고 있던 연산자로. 이것은 gap을 메우는 게 아니라 우회한다는 의도한 선택이고, 앞 결정들의 거울상이다:프런티어에서 옳은 한 수가 능력을 기르지 않는 것인 경우가 있다, 왜냐하면 손안의 한 프로그램이 그것 없이 목표에 닿고, 아무도 막히지 않은 빠진 기능은 아직 세울 가치가 없기에. 부재는 수정이 아니라 기록되었다.

도구가 증명한 것

신뢰할 수 있는 길이를 얻자, 나머지는 새 아픔 없이 왔다:섹션을 walk하고, 그 크기를 디코드하고, 길이 접두의 이름과 종별을 가진 export 테이블을 읽는다. 출력은 표준의 WebAssembly 검사 도구에 대해 대조 되었고, 그것에 완전 일치했다 —— 가장 강한 종류의 확인이다, 왜냐하면 언어가 자기 자신에 동의하는 게 아니라 외부의 오라클이기에. 그리고 이 도구는 앞 둘에서 배웠다:빌드 생성물의 무시 목록이 바로 첫 커밋에 쓰여, 선행자들과 달리 공개 전에 浄化할 이력이 없었다. 실의 결과는 「언어는 이제 바이너리를 다룰 수 있다」보다 작고 날카로운 것이었다:파일 읽기와 바이트 읽기 조작은 처음부터 binary-safe였고, 단 하나의 빠진 길이 함수가 gap의 전부였다. builtin 하나, meta 데모 하나 —— 컴파일러가 자신이 emit한 바이트를 읽는다 —— 그리고 bytes 타입은 무언가가 그것을 요하는 날까지 정직히 미건으로 남겨졌다.

세 도구 완료, 세 능력이 존재로 강제되었고, 각각이 언어의 다른 구석에. 네 번째는 커맨드라인을 완전히 떠난다:브라우저에서 도는 게임, frontend bindings로 그려지고 구동되는 —— 그리고 그것은 빠진 builtin이 아니라, 줄곧 두 backend에 걸쳐 컴파일러에 숨어 있던 버그를 surface하게 된다.

← Back to Mere: 언어를 만들다