바이트가 입구를 통과한다

30줄의 CRC-32 도구는 새 bitwise 연산자로 자명했다. 발견은 입력 쪽에 있었다: 파일을 읽으면 문자열이 돌아오고, C backend에서 문자열은 NUL 종단이라, 제로 바이트를 포함한 바이너리 파일은 거기서 말없이 멈춘다. 25바이트 파일이 2바이트로 읽혀, 자신만만한 잘못된 체크섬을 뱉었다 —— 한편 NUL을 보존하는 문자열을 가진 interpreter는 올바로 읽었다. 「문자열은 그저 바이트열」이라는 약속은, 하나의 backend에서만 참이었다.

merebytesbackendsiolanguage-design

bitwise 연산자는 한 무리의 프로그램을 갑자기 싸게 쓸 수 있게 했으므로, 다음 probe는 CRC-32 체크섬 도구였다 —— 30줄, 알고리즘은 그 다항식에서 거의 베껴 쓸 수 있다. 표준 테스트 문자열의 올바른 체크섬을 한 번에 계산했다. 흥미로운 실패는 산술 안에 없었다. 파일을 애초에 프로그램에 넣는 곳에 있었다.

하나의 backend에서만 지켜진 약속

Mere의 문자열은 늘 바이트열로 문서화되어 왔다 —— 메모리 모델 노트가 그렇게 말하고, Unicode 이야기가 거기 기댔다. 하지만 「문자열은 바이트열」은, 의미를 가지려면 모든 backend에서 참이어야 하는 주장이고, 그렇지 않았다. 파일을 읽으면 문자열이 돌아오고, C backend에서 문자열은 NUL 종단 char*다. 가운데쯤에 제로 바이트가 있는 바이너리 파일을 건네면, 거기서 말없이 읽기를 멈춘다. probe의 테스트 파일은 앞쪽 가까이에 NUL이 있는 25바이트였고, C 빌드는 그중 2바이트를 읽고, 내부 정합적이고, 그럴듯하고, 완전히 잘못된 체크섬을 뱉었다. 불평 없이 NUL을 나르는 진짜 OCaml 문자열을 가진 interpreter는, 25바이트 전부를 읽고 올바른 답을 얻었다. 즉 두 backend가 어긋났다 —— 에러가 아니라, 두 개의 다른 자신만만한 수로 —— 그리고 테스트가 도는 쪽이, 마침 옳은 쪽이었다. 이것은 정수 폭 이야기와 똑같은 형태다: 언어가 한 약속은 테스트된 곳에서만 지켜졌고, 그리고 바이너리 파일이, 그것이 깨지는 곳 너머에 닿은 첫 입력이었다.

바이트 타입이 아니라, 바이트의 vector

수정에는 설계의 갈림길이 있었다. 정직한 바이너리 입력 경로는 어디서나 같은 의미를 가지는 값이고, 유혹은 전용 바이트 타입을 도입하는 것이다. 더 싼 수 —— 그리고 택한 수 —— 는, 파일을 정수의 vector로 읽는 것이었다. 1바이트당 1요소, 각각 0에서 255의 범위, 언어가 이미 가진 vector 기구의 재사용. 파일 1바이트당 8바이트의 저장은 볼품없는 비율이고, 정직한 비용으로 적어 두었다. 프로그램이 그 비율을 아프게 하는 날이, 진짜 바이트 타입이 forcing program을 얻는 날이다. interpreter와 C backend는 reader를 얻었다. wasm과 LLVM은 위치 정보 붙은 compile 에러로 거부한다. 그것들의 host 파일 인터페이스가 아직 NUL 종단 문자열을 다루기 때문이다. 한 미묘함이 전용 디버깅 세션을 벌었다: 새 reader는 vector를 반환하고, 그 vector의 region을 지목하는 타입 변수는 reader가 불리는 지점에서 고정되어야 한다 —— vector 생성자가 하는 것과 똑같이 —— 그러지 않으면 region은 미해결로 남고, C backend는 반환된 vector를 받는 함수를 말없이 emit하지 않게 된다. probe는 그것도 밟았다. 어울리는 일이다: 값이 말없이 도착하지 않는 것에 대한 이야기가, 컴파일러의 한 단 아래에서, 말없이 도착하지 않는 두 번째 값을 찾았으니. 체크섬 도구는 이제 텍스트와 NUL이 든 파일 양쪽에서 시스템 zlib과 일치한다. 다음 이야기는 완전히 다른 파일 형식을 집어 든다 —— 그리고 실재하는 언어보다 가난한 언어를 기술하는 문서를 찾는다.

← Back to Mere: 언어를 만들다