自分の出力を読む

三つ目のツールはバイナリファイルを読み、向けられたバイナリはコンパイラ自身の 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: 言語を作る