思想

位置づけ

Zax が解くべき問題は「頂点数ぶん繰り返し評価される小さな数式を、ユーザーがインスペクタの 1 行に書けるようにする」ことである。この用途からくる要求は次の 3 つ。

  • 入力は 1 個の値を返す純粋な式である。文も宣言も要らない。
  • 評価は数万回単位で繰り返される。したがって実行時の型判定・分岐・確保は避けたい。
  • 失敗はビルド時までに分かってほしい。頂点 12345 番で初めて型エラーが出るのは論外。

結果として Zax は「コンパイル時にすべての型を解決し、実行時は型タグ付きの固定長スタックを 舐めるだけ」という形に落ち着いている。

設計判断

判断 理由
RPN を採用 パーサが要らない。優先順位・結合性・括弧の設計を丸ごと回避でき、字句解析器の出力をそのまま線形に走査すればコード生成が終わる
式のみ (文なし) プログラム全体が「変数の組から 1 値への写像」になり、要素ごとの独立評価 = 並列化が自明になる
静的型付け 実行時型ディスパッチを消せる。命令が自分の引数型・結果型を持つため、評価器の内側ループに型検査が一切ない
ホストが変数を宣言 式の側が新しい名前を作れない。どの属性が参照されたかがコンパイル結果から静的に分かるので、ホストは必要な入力だけを用意できる
スカラ・4 次元までのベクトルのみ すべての値が float4 1 個に収まり、値表現が固定長になる。SIMD 演算をそのまま使える
最適化しない 式は数トークンから数十トークン。コンパイル時間と実装の単純さを優先し、定数畳み込みも共通部分式除去も行わない (定数プールの重複排除のみ)

意図的に持たない機能

分岐・ループ・ユーザー定義関数・変数への代入・局所束縛・再帰・行列型・真偽型・文字列型・ 配列添字アクセスはいずれも存在しない。

この帰結として、プログラムの実行時間は命令数に比例する定数であり、停止しない式は書けない。 スタックの必要段数もコンパイル時に確定するため、実行時のメモリ確保も起こらない。

分岐がないことの帰結

条件分岐は値の重み付けで表現する。比較演算子が 1.0 / 0.0 を返すのはこのためである。

; x > 0.5 なら a、そうでなければ b
@a @b  @x 0.5 >  lerp

lerp の第 3 引数が 0 か 1 のときは選択そのものになる。閾値判定は step でも書ける。

0.5 @x step        ; x >= 0.5 で 1.0

論理演算も同様に算術で代用する。

論理 書き方
AND * または min
OR max
NOT ! または 1.0 swap -
XOR !=

両辺とも常に評価される (短絡しない) ため、0 除算や定義域外を含む枝を条件で回避することは できない。maxclamp で入力側を潰しておく必要がある。

頻出パターン

@position dup dot sqrt                       ; length と同じ (dup で 2 回使う)
@color #rgb 0.2126 0.7152 0.0722 vec3 dot    ; 輝度
@normal 0.5 * 0.5 +                          ; -1..1 を 0..1 へリマップ
@uv #xy ... swap vec2                        ; xy の入れ替え
@color #rgb srgb2linear ... @color #a vec4   ; アルファを保ったまま色空間変換
@a @b - abs EPSILON <                        ; 誤差を許した等値比較