リストを消す

予約語リストは六度間違い、リスト自身のコメントがまた間違うだろうと認めていた。robust な修正 —— すべてのユーザ値・関数名に前置して C と衝突不能にする —— は、大きくて危険な変更として何ヶ月も先送りされていた。調査がそのリスクを溶かした。セルフホストの fixpoint は完全に WAT backend の上で走り、C の命名に一切触れない。そして sanitizer は既に、すべての名前が通る単一の関門だった。だから前置を入れ、リストを消した。一律の前置は自己検証的だと判明する —— sanitizer を飛ばす emission パスは、運の悪い名前だけでなくすべての関数で失敗する —— そしてその性質が、リストが静かに覆い隠していた三つの潜在的な不一致を即座に炙り出した。一つの撤退: 型名は raw のまま。再帰 variant の機構がそれをキーにしているからだ。予約名問題は両半分とも構造的に閉じた。

merecompilername-resolutioncodegenlanguage-design

予約語リストの隣のコメントは何ヶ月も正直だった。「このリストは本質的に不完全 —— robust な修正は 全ユーザ top-level 関数名の名前空間化、より大きな byte-stream 変更として先送り」。六つの衝突を経て、 その文でまだ仕事をしているのは「先送り」の部分だけだった。先送りさせ続けていたのは、特定の形をした 恐れだ。コンパイラはセルフホストし、セルフホストの出力は byte 一致で検証されている。すべての名前の 綴り方を変えることは、fixpoint を壊す類のことに聞こえた。

そこに無かったリスク

だから最初の一手はコードでなく調査だった。恐れを溶かす二つの事実が返ってきた。第一に、byte 一致の fixpoint は完全に WebAssembly の経路に住んでいる —— Mere 製コンパイラは WAT を emit し、関数を index で命名し、C backend をそもそも持たない。C の name mangler と fixpoint は何も共有しない。 第二に、C backend には既に単一の関門があった。すべてのユーザ名が C 識別子になる途中で通る一つの sanitizer 関数、およそ四十箇所から呼ばれる。「大きすぎるとして先送りされた変更」はその核心において、 一つの関数の編集だった。リストを調べて運の悪い名前にアンダースコアを付ける代わりに、すべてに前置する。 ユーザの foomu_foo になり、どのユーザ名も C キーワードとも POSIX シンボルとも Bessel 関数とも 衝突できない。その前置で始まる C の名前は存在しないからだ。リスト —— 積み上がった用心の六十余項 —— は削除される。

自分の仕事を検査する前置

この変更を単に正しいだけでなく安全にした性質は、一律の前置が自己検証的であることだ。旧方式では、 sanitizer への相談を忘れた emission パスは、ほぼ全員に正しい C を生成し、パラメータを y0 と名づけた 人だけを壊した —— 運が悪くなるまで沈黙する。新方式では、sanitizer を飛ばすパスは前置なしの名前を emit し、その参照はすべて前置済みで、その不一致は触れるすべての関数でコンパイルに失敗する。 テストスイート全体が検出器になる。そして検出した。変更から数分のうちに、三つの潜在的な def/use 不一致が浮かび上がった —— raw で宣言され参照は sanitize 済みだったパターンマッチの binder、lifted 呼び出しで raw のまま渡されていた capture 引数、そして前置を二度適用して mu_mu_ を生む一つのパス。 どれも六つの衝突を生んだのと正確に同じ種類のバグで、ずっとコードの中に座っていて、旧 sanitizer が 普通の名前には no-op だという事実に覆い隠されていた。移行が自分の落伍者を自分で炙り出した。

発見でもあった撤退

計画の一部は接触に耐えなかった。最初の試みは型名にも前置し、record は綺麗に受け入れた —— だが variant が微妙な形で壊れた。再帰 variant の機構は raw の型名をキーに検索し、その検索が、とりわけ、 値にドットでアクセスするかポインタ越しにアクセスするかを決めている。ある場所では名前に前置し別の 場所ではしないことが、ヒープ確保された closure を値渡しのコードパスに送り込んだ。その機構を隅々まで 追いかける代わりに、変更はより綺麗な線まで撤退した。値と関数は前置する。型名は別の C 名前空間であり raw のまま、独自の mangler に分離する。型名が C と衝突したことは実績として一度もなく、もし起きても それは閉じた別個の修正だ。変更の境界がどこに座るべきかを正確に知ることは、変更そのものと同じだけの 価値がある。

スイートは緑で終わった —— 二千二百二のテスト、emit された C を検査するおよそ四十の assertion は 新しい綴りに更新され、何も shadow しなかったプログラムはすべて byte 一致。二話前、ガードはユーザ定義を 言語自身の組み込みに勝たせた。このリリースは、ユーザ名が C と衝突すること自体を不可能にする。 dogfood のほぼ隔週でバグを生んでいた予約名問題は、両側から閉じた —— より長いリストでも、より注意深い 監査でもなく、衝突を表現不能にすることによって。再発するバグへの最良の修正は、衝突する相手が存在 しない世界だ。

← Back to Mere: 言語を作る