メインコンテンツまでスキップ
バージョン: 0.2.0 snapshot

セルフホストのコンパイラ

selfhost/compiler.dicks は、0.2のコンパイル処理全体をDicks Languageで実装しています。バイト列の字句解析、構造の解析、シグネチャの構築、スタック・型・初期化の検査、呼び出しグラフの検証、エフェクト、到達可能性、構造化JavaScriptの生成までを行います。実行可能なソースに含めるのは 8、=、D、~ とASCIIの空白だけです。従来の命令を維持し、0.2仕様の名目的なデータ型を使い、ランタイムの上限も緩めていません。

コンパイラは、自身の完全なソースをコンパイルできます。Rustは起動用のシードと独立した比較実装です。TypeScript参照実装はテストだけで使います。生成したコンパイラがソースを処理する際に、どちらを呼び出すこともありません。

再現可能なブートストラップ​

npm ci
npm run selfhost:bootstrap -- --out target/my-bootstrap

出力には新しいディレクトリを選んでください。生成物を上書きすることはありません。このコマンドはRustシードをビルドした後、次を実行します。

  1. Rustで selfhost/compiler.dicks と selfhost/hosts.json をコンパイルし、stage1.mjs を作ります。
  2. stage 1で同じソースとABIをコンパイルし、stage2.mjs を作ります。
  3. stage 2で同じソースとABIをコンパイルし、stage3.mjs を作ります。
  4. 完全な出力ファイル3つすべてを、バイト単位で一致させます。bootstrap.json には、ソース、ABI、ランタイム、ドライバー、シードバイナリ、生成物のSHA-256、Nodeのバージョン、ステップ数、測定したリソース使用量を記録します。

バージョン0.2は記号で書かれた65関数を持ちます。正確なソース・生成物のサイズとステップ数は、マニフェストを正とします。時間はホストによって異なり、性能の優位性は主張していません。

ブートストラップ後は、Rustや参照実装を呼び出さずにコンパイルできます。

npm run selfhost:build -- examples/add.dicks \
--compiler target/my-bootstrap/stage3.mjs --out dist/selfhost-add.mjs
node --input-type=module -e 'import("./dist/selfhost-add.mjs").then(async m => console.log((await m.execute()).output))'

必要であれば先に dist/ を作成してください。--hosts ABI.json では、省略可能なアプリケーションのホストマニフェストを指定します。入力と出力のパスを明示し、既存の出力先があれば作成を拒否します。runnerはRustの実行ファイル・構文解析器・検査器・生成器に依存しません。生成するアプリケーションはシードの出力と同じく、具体的な演算、構造化した if / for、静的な呼び出しを持つ、単独動作するES2022です。インタープリター、eval、動的コンパイル、Nodeのインポートは含めません。

コンパイラとランタイムの境界​

tools/lib/compiler-host.ts は、バイト入力、出力の受け渡し、整数のビット演算、汎用のABI直列化を提供します。ソースから使うホストの宣言は selfhost/hosts.json にあります。

IDプリミティブ意味
3stream.writeTextの断片を出力ストリームへ追加
4stream.readオフセットの入力バイトを読む。ちょうどEOFなら-1
7–8integer.and / integer.orInt値のビット演算
10seed.entry不変の入力メタデータからアドレス・値の1組を読む
11seed.count不変の入力メタデータの組数

これらのプリミティブでは、トークンの認識、シグネチャの検証、呼び出し先の解決、命令の選択、JavaScript構文の生成はできません。こうした判断は記号のコードで行います。コンパイラのメモリー操作も、言語の永続リストとレコードで実装しています。FFIのメモリープリミティブを通して、ホストが実行中の状態を読み書きすることはできません。

アダプターは外部のJSON形式のABIメタデータをスカラー配列に変換し、文字列を決定的な辞書順で共通化します。記号のコード側で、宣言ID、重複、シグネチャ数、エフェクト構文を検証し、エフェクト集合を構築・結合し、出力するメタデータを選んで安全に引用します。アダプターは数値の診断イベントを人間向けのメッセージへ変換します。また、静的なランタイムのリンクと同様に、変更していない runtime/runtime.js を先頭へ付けます。生成する関数の本体や構文解析の判断を、アダプターから持ち込むことはありません。

コンパイラの各ステップは [Result<Memory,Int>] を受け取り、[Result<Memory,Int>,Int phase] を返します。最初のErrでゼロ埋めの永続ページ木を作り、以降のOkに明示的な状態を載せて受け渡します。Memoryレコードはページのリストと、メタデータ初期化用のカーソルを持ちます。リスト・レコード・Resultは通常の言語の値で、エントリー境界で検証します。ドライバーは呼び出し間の状態を不透明な値として保持し、診断の変換に必要な、文書化した少数のセルだけを読みます。

再利用可能なrunnerはクロージャーとABIの検証を保持し、呼び出しごとのfuel・ホスト呼び出し数・割り当てカウンターを新しく始めます。各ステップには100,000fuel、128ホスト呼び出し、8MiBの値割り当て上限を適用します。ソースと外部ABIの入力枠は引き続き1MiBです。アドレス空間は64Miセルで、書き込むまで共有ゼロブロックによってコンパクトに表します。出力は32MiB、1回のコンパイルは800万ステップが上限です。これらは実際に強制する制限であり、JavaScriptヒープのピーク使用量の見積もりではありません。

記号のソースを保守する​

selfhost/compiler.dicks が実行可能な実装の正本です。ビルドにPython・TypeScriptの生成器は必要ありません。dicks disasm selfhost/compiler.dicks で命令の名前と正確なソース位置を確認できます。内部のメモリー配置と関数IDはコンパイラ内部に記載しています。コンパイラ自身のソースにも明示的な型を付け、アプリケーションと同じ検査器を通します。

独立したホスト不要の selfhost/lexer.dicks は、小さな構成要素や独立した字句解析テストの対象として残します。その遷移関数はコンパイラの関数2にも組み込んでいます。npm run selfhost:lex -- file.dicks で、この構成要素を動かせます。完全なビルドはコンパイラ全体を使い、このアダプターは呼び出しません。

受け入れ検証と復旧​

npm run test:bootstrap
npm run check

ブートストラップのテストは、Git管理対象外の新しい target/selfhost-* ディレクトリを作り、検査できるようマニフェストと生成物を残します。各段階の一致を検査し、stage 2とstage 3の両方で、同じ正常・異常・ランタイムエラー・Workersのテスト群を実行します。受理した全フィクスチャを、記号で生成したモジュールの実行前に、Rust出力とバイト単位で比較します。拒否するフィクスチャは、診断コード、メッセージ、完全なバイト範囲を一致させます。加えて、構文・シグネチャの上限、ローカル状態の再利用、分岐の初期化、早期return、再帰・深さ、ホストメタデータの引用、メモリーの境界を検査します。

再現性と差分検査は有力な根拠ですが、あらゆるコンパイラの不具合やtrusting-trust攻撃への証明ではありません。Rustソース、Cargo.lock、ソース・ABI・ランタイムのハッシュ、正常動作を確認したブートストラップのマニフェストを保存してください。記号のコンパイラが壊れた場合は、レビューしたリビジョンからRustシードをビルドし、新しいディレクトリでブートストラップします。調査中に、正常動作を確認した既存の生成物を置き換えないでください。

この作業でDaaSの配備、API経路、DB、secret、Cloudflareの権限は変わりません。ローカルのDaaSビルドとの結合は、そのリポジトリで別途検証します。このリポジトリからデプロイは行いません。