ヿ(koto)ドキュメント

開発

ヿ製コンパイラと生成コード

compiler/compiler.kotoを中心とするヿ製コンパイラが、構文解析、型・移動・所有権・借用の検査、Wasm生成を担当します。check-sourceは成功時にCheckedProgramを返し、コード生成とソース整理で共有します。診断はCompilerDiagnosticとしてfailsとtryで伝播し、公開APIの境界でcatchして最初の一件を返します。整理に必要なアクセス権限の情報も検査時に記録します。src/compiler-sources.jsのCOMPILER_SOURCESに指定したソースを順に連結してコンパイルします。

共有定義の正本はヿで記述します。definitions/language.kotoに型・演算・予約語、definitions/abi.kotoに権限・診断・テスト・ソース整理の共有定義、definitions/runtime.kotoにランタイムのWasmバイトコードを組み立てる処理、definitions/tables.kotoにコンパイラ内部のレコードとIDの対応を置きます。

生成スクリプトは、これらをWasmコンパイラseedでコンパイルし、実行結果からcompiler/generated-runtime.koto、compiler/generated-constants.koto、src/generated-definitions.js、compiler/nodes.kotoを生成します。束縛表・関数表のGENERATED STORE区間も、ノード表と同じ索引の拡張処理から生成します。ホストも同じ正本からの生成済み定義を使います。変更は正本へ加え、生成コードは直接編集しません。

構文ノードと解析レコードは種別ごとの表に格納します。IDは種別を区別する不透明な値で、生成元の表と組み合わせて使います。走査用の整数からIDを得る操作は登録済みの範囲を検査します。索引は必要に応じて倍増するPtr配列で、拡張後もレコード本体の格納先は変わりません。未使用欄を初期化する番兵セルは変更できません。型の登録にはハッシュ索引を使い、候補の完全な比較で同一性を判定します。

npm run generate
npm run generate -- --check

generateはseed本体と現在のホストABIの整合性を確認します。編集したコンパイラソースからも生成できるよう、コンパイラソースのハッシュ一致は要求しません。--checkは全生成物を再計算して比較し、更新漏れをエラーにします。--from compiler.wasmで移行用コンパイラを明示でき、--checkと併用できます。

ホストはソースのUTF-8バイト列と3語の診断配列を呼出しregionに準備し、ヿ製コンパイラのWasmを呼び出します。生成したWasm・WGSL、metadata、編集案はヿ側の可変長バッファで組み立て、必要な長さの配列として同じregionに確保し、そのオフセットを返します。ホストは返されたオフセットを呼出しregionのbaseと使用量(__koto_region_base・__koto_region_used)で検証して読みます。構文解析・検査・コード生成にJavaScriptコンパイラへのフォールバックは使いません。各操作は独立したWasmインスタンスで実行します。

seedの更新は明示的に実行します。

npm run seed:check
npm run seed:refresh
node scripts/seed.mjs refresh --from /path/to/transition-compiler.wasm

seed:checkは現在のソースとホストABIのハッシュ、Git履歴上の全系譜のprovenance・Wasmハッシュ・revisionの先祖関係を検査し、seedによる自己再コンパイルが同じWasmになることを確認します。完全なGit履歴が必要です。ブラウザでの読込みはprovenanceの形式と配布ファイルの整合性を検査し、Git履歴は参照しません。

seed:refreshは現在のseedを起点に再生成し、記録済みの系譜を引き継ぎます。--fromは現在のホストABIに対応する移行用Wasmコンパイラを明示的な起点として選びます。--fromを指定した場合も系譜は引き継ぎます。新しい構文をコンパイラ自身に採用するときは、まず従来の構文で新構文を実装した移行用コンパイラを生成し、そのWasmを--fromで指定できます。

共有定義の更新でホスト入力のハッシュが変わる場合は、変更前のseedを保存し、生成とseed更新の両方に同じWasmを指定します。この手順はそのWasmの呼出し契約を維持する変更に使えます。

cp seed/compiler.wasm /tmp/koto-transition.wasm
npm run generate -- --from /tmp/koto-transition.wasm
npm run seed:refresh -- --from /tmp/koto-transition.wasm
npm run check

ABI自体を変える場合は、新ABIのホストと生成物(KERNEL_SIGNATURESなど)では旧Wasmを読めず、generateもホストを通してコンパイラを読むため、次の順に進めます。

  1. ホストを変更する前に、旧seedでnpm run generateを実行して生成物を更新します。
  2. 変更前のrevisionをgit archiveで展開し、そのホストで旧seedを実行して、現在のソースから新ABIのコンパイラを生成します。コンパイラ自身のランタイムが変わる場合、こうして生成したコンパイラは旧seedが組み込んだ旧ランタイムで動きます。同じホストの生のカーネル呼出し(metadataは解釈しない)で、そのコンパイラに現在のソースをもう一度コンパイルさせ、その出力を新ABIのコンパイラとします。
  3. 作業ツリーのホストを新ABIに切り替えます。
  4. 生成したWasmをnpm run generate -- --fromとnpm run seed:refresh -- --fromの両方に指定し、npm run checkを実行します。

更新コマンドは生成した3世代の共通テスト・コンパイラ内テスト・検査と整理APIの契約、および後続世代のバイト一致を確認してからseedとprovenanceを置き換えます。provenanceはソースとホストABI入力のハッシュ、生成したWasmのハッシュ、生成に使ったWasmのハッシュ(producer)と、起点を前進させた系譜(lineage)を記録します。形式と整合規則はsrc/seed.jsに定義しています。ハッシュは配布物の取り違えや更新漏れを検出するためのもので、配布元の認証ではありません。日常の信頼根は固定したseedです。系譜はprovenanceに記録しますが、バイナリでない起点からの再導出は日常の保証に含めません。

Wasm seedからのセルフホスト検証は次の世代を作ります。

  1. 検証済みのWasm seedで、ヿのソースからstage 1を生成する。
  2. stage 1で同じソースをコンパイルし、stage 2を生成する。
  3. stage 2で同じソースをコンパイルし、stage 3を生成する。

stage 1・2・3のすべてに同じ受理・拒否・実行テストを適用し、stage 2とstage 3のWasmがバイト単位で一致することも確認します。共通のヿソースはtest/fixturesに置き、成功・trapの検証はdeftestで実行します。テスト名と期待する成否(trapの場合はその原因)は独立したmanifestで、テスト宣言・assert・trapの位置はコンパイラを呼ばないテスト用のソース走査で照合します。コンパイラ自身のソース内にあるdeftestも各世代でコンパイル・実行します。検査・整理APIも各世代で同じソースと期待結果を使って検証します。ホストからのメモリ・ABIの観測はJavaScriptで検証します。この検証はNode.jsと実際のChromiumの両方で実行します。

bootstrapの再構築

日常の再構築は、seed/provenance.jsonのlineageの最後の到達点に記録したrevisionのseedを信頼根とします。起点以降に必要な移行revisionはscripts/bootstrap.mjsで管理します。

npm run bootstrap:check

完全なGit履歴、Git、tar、Node.jsとnpmが必要です。一時ディレクトリに起点を展開し、記録と照合したseedを当時のホストアダプタで読み込みます。そこから移行revisionを順に再構築し、それぞれのホストABIで3世代の検証と記録されたWasmとのバイト一致を確認します。最後に現行ソースを構築し、現行の3世代の契約テストとバイト一致、配布seedとの一致を確認します。作業ツリーのseedは更新しません。

新機能をコンパイラ自身に採用するときは、従来の言語でその機能を実装した移行用コンパイラを先に構築し、必要な移行revisionを再構築経路に追加します。コンパイラ自身のランタイムを変えた変更の直前のrevisionは、移行revisionに加えてruntimeBridgeRevisionsにも登録します。そのrevisionのコンパイラが後のソースから作るコンパイラは旧ランタイムで動くため、再構築ではそのrevisionのホストで同じソースをもう一度コンパイルしてから検証します。登録したrevisionが再構築の経路上にあることは起動時に確認します。移行revisionが3段を超えたとき、または言語の節目には、開発者が起点を前進させます。到達点となるソース・seed・provenanceを先にcommitし、変更のない作業ツリーで検証します。そのcommitを指す系譜の追記は後のcommitで行い、provenanceが自身のcommitを参照する循環を避けます。

起点の前進は次の手順で行います。

  1. bootstrap:checkで現在の起点からcommit済みの到達点まで再構築し、可能なら次のアーカイブ経路でも確認します。
  2. 確認した起点・経由revision・到達点とWasmのハッシュ、成功した検証方法、その実行に使ったNode.jsの版をlineageへ一件追記します。Node.jsの版は検証コマンドの出力または同じ環境のnode --versionで確認します。アーカイブ経路が実行環境の非互換で動かない場合は、成功した日常経路をfrom-seedとして記録します。既存の系譜は書き換えず、到達点のprovenanceにある系譜をそのまま先頭に保持します。
  3. 到達点までの移行revisionを日常の経路から除き、seed:checkとbootstrap:checkで新しい起点から検証して、系譜と経路の更新をcommitします。
node scripts/bootstrap.mjs --from-js

アーカイブ経路は、Git履歴に固定したJavaScript版と対応するソース・ホスト・テストを展開し、その配布seedとprovenanceを削除してJavaScriptから再構築します。固定版の単体テストと記録されたWasmとの一致を確認した後、系譜に記録した全段と現在の移行revisionを再生し、現行seedまで検証します。起点を前進させる際の確認に使い、日常の検証には含めません。

アーカイブ経路の実行に使ったNode.jsの版は系譜に記録します。実行環境を固定するコンテナや追加のNode.js版指定は設けません。Node.jsの非互換などでこの経路が動かなくなっても修理せず、確認済みの系譜を履歴として残します。

検証

npm ci
npx playwright install chromium
npm run check

npm run checkはpackage.jsonのcheckに並べた段を順に実行し、失敗した段で止まります。コンパイラを使う段は、どれもWasmコンパイラseedを起点にします。各段は単独でも実行でき、それぞれ次を検証します。

npm run docsはdocs/からサイトをsite/に生成し、http://127.0.0.1:8001で配信します。ファイルの変更は監視しないため、編集後は起動し直します。生成だけを行う場合はnpm run docs:buildを使います。インラインコードは、描画規則に従って常にヿとして色付けします。

システムにインストールされたChromeを使う場合は KOTO_BROWSER_CHANNEL=chrome npm run test:browser を実行します。ブラウザを起動できない場合は失敗とし、Node.jsの検証で代用しません。現時点のブラウザ検証対象はChromiumです。

性能の計測

npm run benchmarkは、Chromiumで小さなプログラムとコンパイラ自身を入力し、compile・check・analyze-cleanupを計測します。検証済みseedを使い、操作ごとに3回のウォームアップと20回の測定を行います。--samples・--warmupで回数を指定できます。

変更前後を同じ入力で比較する場合は、保存したWasmとソースを指定します。JSONだけを保存するときはスクリプトを直接実行します。

node scripts/benchmark-compiler.mjs --compiler before.wasm --source input.koto > before.json
node scripts/benchmark-compiler.mjs --compiler after.wasm --source input.koto > after.json

--host nodeで同じ呼出し境界をNode.js上でも計測できます。自己コンパイルだけを調べる場合は、保存したコンパイラソースに--operations compile,checkを指定します。--syntheticは合成入力を選び、--sizesは規模を昇順で指定します(省略時は1,000・2,000・4,000)。合成入力では既定でcheckを計測します。

node scripts/benchmark-compiler.mjs --host node --compiler before.wasm --source compiler.koto --operations compile,check
node scripts/benchmark-compiler.mjs --host node --compiler before.wasm --synthetic A,B,C,D,E --sizes 1000,2000,4000

合成入力のAは独立した関数、Bは直前の関数への呼出し、Cは独立したlet、Dは直前の束縛を使うlet、Eはvarを更新する逐次のifを増やします。結果のscalingは同じ形・操作について、隣り合う規模の中央値からlog(時間比) / log(規模比)を計算します。指数1は線形、2は二次の増加に対応します。中央値がタイマーの分解能未満で0になった組はexponent: nullになります。

結果にはホスト・ブラウザの情報、入力とWasmのハッシュ、各標本、中央値・p95・最大値、線形メモリ容量が入ります。時間は各操作の新しいWasmインスタンスの作成、入力の変換・転送、Wasm内の処理、ABI境界の検証を含みます。Wasmコンパイラ自体のロードや、公開APIの結果復元・編集案の再検査は含みません。メモリはソース・診断・結果の配列を含む線形メモリの容量で、使用中のオブジェクト量やプロセス全体の使用量ではありません。観測時間は時間上限の保証ではなく、同じ環境・入力で比較するための値です。

公開

ドキュメントのサイトはhttps://koto.intsuc.dev/にあります。mainへのpushで.github/workflows/deploy.ymlがnpm ci、npm run docs:check、npm run docs:buildを実行し、生成したsite/をwrangler.jsoncの設定でCloudflare Workersの静的アセットとしてkoto.intsuc.devへ配信します。検査に通らない文書は公開されません。このworkflowは文書の検査と生成だけを行い、npm run checkは実行しません。各ページはhttps://koto.intsuc.dev/<ページ名>で配信され、.html付きの要求はそこへリダイレクトされます。

配信にはリポジトリのActions secretsとして、CloudflareのアカウントIDのCLOUDFLARE_ACCOUNT_IDと、そのアカウントのWorkersスクリプトの編集とintsuc.devゾーンのWorkers Routesの編集を許可したAPIトークンのCLOUDFLARE_API_TOKENが必要です。custom domainのkoto.intsuc.devは最初の配信時にWorkerに付き、同名の既存のDNSレコードがあると作成できません。