ヿ(koto)ドキュメント

ホストとAPI

コンパイラAPI

src/seed.jsで検証済みのコンパイラを読み込み、ブラウザのES Moduleから利用できます。

import { loadSeed } from "./src/seed.js";
import { requireWasmFeatures } from "./src/wasm-features.js";

const compile = await loadSeed();
const result = compile(source, { sourceName: "example.koto" });
if (result.ok) {
  requireWasmFeatures(result.features); // 必要な機能がなければWasmFeatureError
  const { instance } = await WebAssembly.instantiate(result.wasm);
  console.log(instance.exports.main()); // 引数なしの main をexportした例
} else {
  console.log(JSON.stringify(result.diagnostics, null, 2));
}

loadSeedはseed/compiler.wasmとprovenance.jsonを読み込み、Wasm、コンパイラソース、ホストABIのSHA-256を照合します。欠落・不一致はエラーです。独自に生成・検証したコンパイラWasmを扱う場合は、src/selfhost.jsのawait loadCompiler(wasm)で同じAPIを得られます。

コンパイラソースはsrc/compiler-sources.jsのfetchCompilerSource()で取得できます。自己再コンパイルもWasmコンパイラで行います。固定した起点から現行seedを再構築する手順はbootstrapの再構築を参照してください。

入力はソース文字列です。moduleとimportはこの入力に含まれる定義を解決し、ファイルの読込みは行いません。複数ファイルを使う場合は、ホストがその内容を改行で連結して渡します。モジュールの構文を参照してください。オプションは診断用の空でない文字列sourceNameと、"program"または"test"を指定するmodeです。省略時はそれぞれ<input>、"program"となります。どちらのモードでもテスト本体を検査しますが、テストの実行用エントリは"test"で生成します。未知のオプションや不正なAPI引数はTypeErrorとして拒否します。

成功時は{ ok: true, wasm, exports, features, tests, testSites, diagnostics: [] }を返します。wasmはUint8Arrayです。featuresは生成したWasmの実行に必要な任意のWasm機能名の配列で、名前と順序はdefinitions/abi.kotoのDefinition-wasm-featureに定義します。生成したコードが名前にrelaxed_を含む演算を使う場合は"relaxed-simd"が入り、それ以外は空です。"program"モードで生成しないテスト本体の演算は含めません。実行前の確認はWasmとホストの境界を参照してください。exportsは{ name, regions, params, result }の配列です。regionsには宣言順のregionパラメータ名、paramsには通常引数の型、resultには戻り値の型が入ります。paramsにはunitの引数も残ります。型は次の形で表します。

レコードの型はさらに、Wasm上の表現(言語仕様のホスト境界)をrepresentation: "scalar"または"address"で示します。"scalar"のレコードは、引数と結果に使うWasmの値型もvalueType: "i32"または"f32"で示します。たとえば(defrecord Id (fields (index i32)))を受け取り返す関数は、引数・戻り値ともに整数1個です。

ベクトル型・マスク型とそれを唯一のフィールドに持つレコード、参照型、借用パラメータを持つ名前付き型は、exportの規則によりexportする関数の型に配列要素・ポインタの先を含めて使えないため、export metadataには現れません。

ソースに問題がある場合は { ok: false, diagnostics } を返し、Wasmを返しません。現在は最初のエラーを一つ返します。各診断は次の情報を持ちます。

{
  severity: "error",
  code: "E_MOVED",
  message: 'Binding "x" has already been moved.',
  source: "example.koto",
  range: {
    start: { offset: 10, line: 1, column: 11 },
    end:   { offset: 11, line: 1, column: 12 }
  }
}

offset は0始まりのUTF-16コード単位位置で、終端は範囲に含みません。行・列は1始まり、列もUTF-16コード単位です。CRLF、LF、CRを改行として扱います。生成物またはmetadataが実行時の配列上限を超えるソースは、ソース全体を範囲とするE_LIMITの診断になります。その上限より先に線形メモリの確保に失敗した場合はメモリ枯渇のtrapで、診断ではなく例外になります。内部実装の不具合はソースの診断に変換せず例外として送出します。診断コードの網羅は各検査の定義、代表的なソースと期待結果はtest/fixturesとmanifestを参照してください。

テストAPI

mode: "test"でコンパイルした成功結果をsrc/testing.jsのrunTestsへ渡します。

import { runTests } from "./src/testing.js";

const result = compile(source, { sourceName: "example.koto", mode: "test" });
if (result.ok) {
  const report = await runTests(result);
  console.log(report.passed, report.failed, report.results);
} else {
  console.log(result.diagnostics);
}

コンパイル結果のtestsは{ name, exportName, range, trapSite }の配列です。trapSiteはexpect-trapの位置を識別するIDで、通常のテストではnullです。testSitesは{ id, kind, range }の配列で、kindは"assert"、"trap"または"runtime"です。"runtime"は、表の行でtrapしうる実行時検査を担う式で、そのtrapで__koto_trap_siteに書かれるIDを持ちます(実行時trap)。位置は式ごとに一つで、assert・expect-trapの式では"assert"・"trap"の位置がその式の検査も表します。rangeは診断と同じ形式です。"program"モードではtestsは空になり、testSitesには通常の関数に含まれるassertと"runtime"の位置だけが残ります。

runTestsは{ passed, failed, results }を返します。各結果は{ name, status, range, trap }で、statusは成功の"passed"、検証失敗の"assertion-failed"、想定外のtrapの"unexpected-trap"、期待したtrapが起きなかった"missing-trap"です。trapは、テストがassertの失敗以外のtrapで終わった結果("unexpected-trap"と、expect-trapの対象がtrapした"passed")では{ code, message }、それ以外ではnullです。codeは実行時trapの表の行またはネイティブtrapの名前、messageはその文言で、位置はrangeが示します。"unexpected-trap"のrangeは、表の行によるtrapではその検査を担う"runtime"の位置、それ以外ではテスト全体です。テストは一つずつ新しいインスタンスで実行します。エンジンのスタック枯渇(V8ではRangeError)もtrapとして判定し、残りのテストを続けます。コンパイル・Wasm検証・インスタンス作成の失敗は期待したtrapには含めません。表にないtrapコードを残すなどの不正な成果物や実行基盤のエラーは例外として送出します。成果物のfeaturesにホストが対応しない機能があれば、テストを実行する前にnameが"WasmFeatureError"の例外を送出します(Wasmとホストの境界)。終了しないテストを中断する場合は、ブラウザ画面と同様にWorkerで実行してください。

deftestはfailsを持ちません。失敗可能な呼出しにはcatchを使い、成功結果や失敗値を検証します。処理された失敗はtrapではなく、assertの失敗をcatchで回復することもできません。

検査・ソース整理API

src/refactor.jsは、検査と構造化された編集案をLLMや編集ツールへ提供します。モジュールの初期化時に検証済みseedを読み込み、その後の各操作は同期的に呼び出せます。構文解析、型・所有権・借用の検査、編集対象の解析はヿ製コンパイラのWasm内で実行します。

import { check, analyzeCleanup, applyCleanup } from "./src/refactor.js";

const options = { sourceName: "example.koto" };
console.log(check(source, options));
const plan = analyzeCleanup(source, options);
if (plan.ok) {
  console.log(plan.edits, plan.requirements, plan.skipped);
  const result = applyCleanup(source, plan.edits, options);
  if (result.ok) {
    source = result.source;
  } else {
    console.log(result.candidate, result.diagnostics);
  }
}

checkはWasmを生成せずにソースを検査し、{ ok, diagnostics }を返します。テスト本体も検査しますが、実行はしません。各APIのオプションはsourceNameだけです。独自のコンパイラWasmを使う場合は、src/tooling.jsのawait loadTools(wasm)で同じ三つの操作を取得できます。

整理の対象は、呼出し先まで解析して明示されたrequiresを狭めることです。提案全体を検査に通してから返し、適用時にも再検査します。テキストの置換はホストで行い、ファイルには書き込みません。

analyzeCleanupの成功結果は{ ok: true, edits, requirements, skipped, diagnostics: [] }です。各編集は{ kind, module, function, span: { start, end }, before, after }で、kindはnarrow-requiresです。spanはUTF-16の半開区間、beforeとafterは置換前後の文字列です。requirementsは関数ごとに{ module, function, regions: [{ region, declared, required }] }を返し、権限はsrc/capabilities.jsの名前で表します。moduleはモジュール名の文字列、ルートの関数ではnullです。functionは修飾していない関数名で、同名関数を区別するにはmoduleも使います。

コメントを含むrequires節は書き換えず、skippedに{ kind, module, function, span, reason }を返します。保持した契約はimport越しの呼出し元にも反映します。分岐、名前のシャドーイング、ループの条件と本体それぞれの移動規則を保って解析します。入力ソースが不正な場合は{ ok: false, edits: [], diagnostics }を返します。

applyCleanupには提案の全部または一部を渡せます。moduleを含めて現在の提案と一致しない編集はRangeError、必須フィールドの欠落など不正な引数はTypeErrorで拒否します。成功結果は{ ok: true, source, diagnostics: [] }です。選択した編集だけでは検査に通らない場合は{ ok: false, candidate, diagnostics }を返し、診断の位置はcandidateを指します。関数の契約を狭める編集は、呼出し先の契約変更と一緒に適用する必要がある場合があります。

複数ファイルとMCP

LLMからはscripts/mcp.mjsのstdio MCPサーバーを起動します。npm ciとnpx playwright install chromiumを実行済みの環境で、MCPクライアントの起動コマンドをnode /絶対パス/koto/scripts/mcp.mjsに設定してください。サーバーは専用Chromiumとツール画面を起動し、検査・コンパイル・テストをブラウザのWorkerへ転送します。Node.jsは接続とブラウザの起動を担当します。

koto_openへfiles: [{ name, text }]を順番付きで渡すとセッション内の作業領域を置き換えます。ディスクのファイルを読み書きする操作ではありません。koto_readで{ ok: true, version, files }を取得し、変更後の内容を持ち帰れます。ファイル間は改行で連結し、モジュールの自動探索は行いません。

koto_check・koto_test・koto_cleanup_analyzeには現在のversionを渡します。一般編集のkoto_editには、さらにedits: [{ file, span: { start, end }, before, after }]を渡します。位置は各ファイル内のUTF-16半開区間です。重複・重なり・Unicode文字の途中・before不一致を拒否し、候補全体の検査が成功したときだけ一括適用します。整理提案はkoto_cleanup_applyへ必要なものをそのまま返せます。各操作の正確な引数はMCPのtool schemaを参照してください。

versionは順番・名前・本文から計算します。古い版はE_WORKSPACE_VERSIONで拒否し、現在の版を返します。検査に失敗した編集は作業領域を変えず、candidateVersionと候補内の診断を返します。診断とテスト結果のrangesは元ファイルごとの{ file, range }です。複数ファイルに跨る範囲も全体を保持し、合成改行はseparator: { after, before }で区別します。ファイルを跨ぐ整理編集はE_WORKSPACE_CROSS_FILE_EDITになります。

koto_testのokは処理の完走を表し、テストの合否はreport.failedと各結果のstatus、trapの原因は各結果のtrapで判断します。必要なWasm機能にブラウザが対応しない場合はcode: "E_WASM_FEATURE"の失敗を返し、テストの成功としては扱いません。型・所有権・権限の診断はコンパイラのコードと位置を返します。ホスト側で推測した借用経路は付加しません。

実行中は読取りと停止を受け付け、別の実行・編集はE_WORKSPACE_BUSYで拒否します。koto_stopまたはMCPのリクエスト取消しでWorkerを破棄します。各Worker操作の制限時間は30秒です。停止・時間切れ・内部エラーをテスト成功として扱わず、作業領域を保持して新しいWorkerで再実行できます。接続終了時は専用ブラウザとセッションを終了します。

GPU計算と描画

npm run devで起動し、GPUの実行画面を開くと、配列を2倍にする例と三角形を描く例を実行できます。WebGPUを提供するブラウザと安全なコンテキスト(localhostを含む)が必要です。利用できるadapterがなければ実行エラーを返します。

GPU向けソースはgpu-moduleで記述します。src/gpu.jsのloadGpuCompilerはヿ製Wasmのcompile-gpuを呼び、型検査済みのWGSL・entry・buffer配置・ソースマップを返します。CPU向けのcompileやMCPの作業領域とは別のコンパイル入力です。

import { readSeed } from "./src/seed.js";
import { loadGpuCompiler } from "./src/gpu.js";
import { createGpuSession } from "./src/gpu-session.js";

const compileGpu = await loadGpuCompiler(await readSeed());
const compiled = compileGpu(source, { sourceName: "gpu-compute.koto" });
if (!compiled.ok) throw new Error(JSON.stringify(compiled.diagnostics));
const session = await createGpuSession(compiled, {
  buffers: [{ name: "values", data: new Int32Array([1, 2, 3, 4]) }],
  canvas: null,
});
try {
  await session.compute("double-values", [1, 1, 1]);
  console.log(await session.read("values"));
} finally {
  session.close();
}

buffer宣言ごとに同名の入力を一つ渡します。i32・u32・f32はそれぞれInt32Array・Uint32Array・Float32Arrayに対応します。要素数はセッション内で固定し、入力は転送前にコピーします。write(name, data)で同じ型・長さの値を書き込み、read(name)で独立した配列を取得します。Wasm線形メモリへのビューを渡す場合も、その時点の有効なビューを用意してください。

compute(entry, [x, y, z])はworkgroup数を指定します。宣言内のworkgroup sizeとの積がinvocation数になります。draw(vertex, fragment, vertexCount)は作成時に渡したcanvasへ三角形リストを描きます。GPUのentry名・型・buffer配置はコンパイラのmetadataから読み取り、転送とpipeline作成に使用します。device limits、入力型・長さ、entryのstageを検証してからGPU操作を行います。

セッションはdeviceとbufferを所有し、完了を待った操作間では再利用できます。実行中の再入・読書きは拒否します。通常終了はclose()、外部処理のtrapなどによる致命的失敗はabort(error)で終了します。作成オプションのsignalへAbortSignalを渡すと、準備中と実行中の両方を取消せます。device lossやGPUエラーでも資源を破棄し、失敗したセッションを再開しません。status()でready・busy・closed・lostと終了理由を確認できます。解放やGPU完了の時間上限は保証しません。

WGSLコンパイルのエラーは、対応する生成範囲があればヿのソース位置へ戻します。対応する範囲がない場合は生成コードの位置を返します。言語機能・制限は仕様、使い方はチュートリアルを参照してください。

Wasmとホストの境界

生成物をJavaScriptから呼び出すときの規則(exportの引数と戻り値の表現、__koto_操作、ランタイムの検証とホストの義務、ビューとbaseの読み直し、trap後の扱い)は言語仕様のホスト境界と実行時trap、メモリ管理の操作と配置の定義はdefinitions/runtime.koto、trapの原因の表はdefinitions/abi.kotoにあります。

src/wasm-features.jsのrequireWasmFeatures(features)は、各機能を使う小さなWasmを呼出しのたびにWebAssembly.validateで検証し、対応しない機能があればnameが"WasmFeatureError"でfeaturesに不足した機能名を持つ例外を送出します。runTestsはWasmのコンパイル前に、ブラウザの実行画面は実行前にこの確認を行います。実行画面は対応しない機能名を表示し、生成したWasmの保存だけを許します。MCPのkoto_testはE_WASM_FEATUREを返します。