メインコンテンツまでスキップ

OpCode

UdonAssembly の命令セット(OpCode)について説明します。

OpCode とは

OpCode(Operation Code)は、UdonEmu が実行する個々の命令を表します。

OpCode 一覧

UdonEmu が対応している OpCode について、各命令の詳細を説明します。

NOP

  • オペコード: 0
  • パラメーター数: 0
  • 説明: 何もしない命令です。デバッグやアドレス調整用に使用されます。
  • 動作: プログラムカウンタ(PC)を次の命令へ進めるだけで、他に副作用はありません。

PUSH

  • オペコード: 1
  • パラメーター数: 1
    • パラメーター1: ヒープインデックス
  • 説明: 整数値(ヒープインデックス)をスタックの最上部にプッシュします。
  • 動作:
    • 指定されたヒープインデックスをスタックに積みます。
    • UdonAssembly では値そのものではなく、値が格納されているヒープのアドレスがプッシュされる点に注意してください。

POP

  • オペコード: 2
  • パラメーター数: 0
  • 説明: スタックの最上部から整数値を取り除きます。
  • 動作: スタックトップの要素を破棄し、他に副作用はありません。

JUMP_IF_FALSE

  • オペコード: 4
  • パラメーター数: 2
    • パラメーター1: ジャンプ先のバイトコード位置
    • パラメーター2: ヒープインデックス
  • 説明: 条件付きジャンプ命令です。
  • 動作:
    • ヒープインデックスから SystemBoolean 値を読み取ります。
    • 値が false の場合、パラメーターで指定されたバイトコード位置へジャンプします。
    • 値が true の場合、次の命令へ進みます。
UdonEmu での拡張

VRChat 公式の UdonAssembly では、JUMP_IF_FALSE はパラメーター1つ(ジャンプ先)のみを取り、条件値はスタックからポップして取得します。
UdonEmu では拡張により、パラメーター2つ(ジャンプ先 + 条件値のヒープインデックス)を取ることで、より明示的な指定が可能になっています。

JUMP

  • オペコード: 5
  • パラメーター数: 1
    • パラメーター1: ジャンプ先のバイトコード位置
  • 説明: 無条件ジャンプ命令です。
  • 動作:
    • パラメーターで指定されたバイトコード位置へ無条件にジャンプします。
    • JUMP, 0xFFFFFFFC は特別な値で、Udon コードの実行を終了(return)するために使用されます。

EXTERN

  • オペコード: 6
  • パラメーター数: 1
    • パラメーター1: 外部関数名
  • 説明: 外部メソッド(C# の関数)を呼び出す命令です。Udon が有用な操作を実行する主要な手段です。
  • 動作:
    • 外部メソッド(C# の関数)を呼び出します。
UdonEmu での実装の違い

VRChat 公式の UdonAssembly では、EXTERN の引数・戻り値の受け渡しはヒープインデックスを介して行われます(スタックにプッシュされたヒープインデックスから値を読み書き)。
UdonEmu では、パラメーター数は同じですが、引数・戻り値の受け渡しにヒープを利用しません。この違いにより、実装の詳細が異なります。

extern 名の形式:

UdonTypeName.__MethodName__Parameter1Type_Parameter2Type__ReturnType

例:

UnityEngineDebug.__Log__SystemObject__SystemVoid

ANNOTATION

  • オペコード: 7
  • パラメーター数: 1
    • パラメーター1: (無視される)
  • 説明: 「長い NOP」として機能します。パラメーターは無視されます。
  • 動作: 何もしません。デバッグ情報の埋め込みなどに使用される可能性があります。

JUMP_INDIRECT

  • オペコード: 8
  • パラメーター数: 1
    • パラメーター1: ジャンプ先アドレスが格納されたヒープインデックス
  • 説明: 間接ジャンプ命令です。
  • 動作:
    • パラメーターからヒープインデックスを取得します。
    • そのヒープインデックスから SystemUInt32 値を読み取ります。
    • この値をバイトコード位置として解釈し、そこへジャンプします。
    • サブルーチンやコールバックの実装に使用できます。

COPY

  • オペコード: 9
  • パラメーター数: 2
    • パラメーター1: コピー先のヒープインデックス
    • パラメーター2: コピー元のヒープインデックス
  • 説明: ヒープ間で値をコピーします。
  • 動作:
    • パラメーター2のヒープインデックスの値を、パラメーター1のヒープインデックスへコピーします。
UdonEmu での拡張

VRChat 公式の UdonAssembly では、COPY はパラメーターを取らず、スタックから2つのヒープインデックスをポップして使用します。
UdonEmu では拡張により、パラメーター2つ(コピー先 + コピー元のヒープインデックス)を明示的に指定することで、よりシンプルな実装になっています。

UdonEmu 独自拡張命令

注意

以下の STORELOAD は VRChat 公式の UdonAssembly 命令セットには存在しない、UdonEmu の独自拡張命令です。
主に EXTERN 呼び出し時の引数・戻り値の受け渡し(内部的な変数操作)に使われます。

STORE

  • オペコード: 16
  • パラメーター数: 2
    • パラメーター1: 格納先の変数インデックス
    • パラメーター2: 格納元のヒープインデックス
  • 説明: ヒープから変数へ値を格納します。
  • 動作:
    • パラメーター2で指定されたヒープインデックスの値を、パラメーター1で指定された変数へ格納します。

LOAD

  • オペコード: 17
  • パラメーター数: 2
    • パラメーター1: 読み出し先のヒープインデックス
    • パラメーター2: 読み出し元の変数インデックス
  • 説明: 変数からヒープへ値を読み出します。
  • 動作:
    • パラメーター2で指定された変数の値を、パラメーター1で指定されたヒープインデックスへ読み出します。

OpCode の確認方法

プログラムに含まれる OpCode は、Dump 機能を使って確認できます。

using HoshinoLabs.UdonEmu.Udon;

// プログラムを Dump
var programJson = udonProgram.Dump();
Debug.Log(programJson);

出力された JSON の instructions セクションに OpCode の列が含まれます。

Udon ヒープとスタック

ヒープ(Heap)

Udon VM の「ヒープ」は、名前に反して実際には型付きの値を格納するフラット配列です。

  • 各要素は「ヒープインデックス」(整数)でアクセスされます。
  • 変数の値、一時的な計算結果、extern 名のキャッシュなどが格納されます。
  • 多くの命令は、ヒープインデックスをオペランドとして取り、ヒープへの読み書きを行います。

スタック(Stack)

Udon VM のスタックは整数(ヒープインデックス)のスタックです。

  • 主に命令への「追加パラメーター」として使用されます。
  • VRChat 公式の UdonAssembly では、PUSH でヒープインデックスを積み、POPJUMP_IF_FALSECOPYEXTERN などがスタックから値を取得します。
  • サブルーチンのコール/リターン機構を実装することも可能ですが、ローカル変数が存在しないため注意が必要です。
UdonEmu での実装

UdonEmu では、JUMP_IF_FALSECOPY がパラメーターで値を受け取るように拡張されており、また EXTERN もヒープを利用しないため、スタックの使用頻度は公式の実装と比べて少なくなっています

変数(Variables)

  • Udon プログラムには「フィールド変数」のみが存在し、ローカル変数はありません。
  • 変数は「変数インデックス」で識別されます。
  • STORELOAD(UdonEmu 独自命令)が変数とヒープ間のデータ移動を行います。

OpCode のデバッグ

実行フローの追跡

OpCode レベルで実行フローを追跡する場合:

  1. プログラムを Dump: OpCode の列とデータセクションを確認
  2. EntryPoints の確認: _start_onEnable などのイベントハンドラーの開始アドレスを特定
  3. 制御フローの追跡:
    • JUMP 命令で無条件分岐を確認
    • JUMP_IF_FALSE で条件分岐を確認
    • JUMP_INDIRECT で動的な分岐を確認
  4. EXTERN 呼び出しの確認: どの外部メソッドが呼ばれているかを確認

トラブルシューティング

無限ループ

  • 症状: プログラムが応答しなくなる
  • 確認方法:
    • JUMP 命令の飛び先を追跡
    • ループの脱出条件(JUMP_IF_FALSE)が正しく設定されているか確認
    • ループカウンターや終了フラグが更新されているか確認

予期しない動作

  • 症状: 変数の値が期待と異なる、メソッドが呼ばれない
  • 確認方法:
    • EXTERN 呼び出しを確認
    • 型の不一致がないか確認(特に SystemBoolean と整数の混同に注意)

スタックオーバーフロー/アンダーフロー

  • 症状: スタック関連のエラー
  • 確認方法:
    • PUSHPOP のバランスが取れているか確認

ヒープアクセスエラー

  • 症状: 無効なヒープインデックスへのアクセス
  • 確認方法:
    • ヒープインデックスが有効な範囲内か確認
    • 初期化されていないヒープスロットへのアクセスがないか確認

デバッグのベストプラクティス

  1. 段階的な確認: 小さな部分から始めて、徐々に範囲を広げる
  2. Dump の活用: 実行前後で Dump を取り、状態の変化を比較
  3. シンプルなテストケース: 問題を再現する最小限のコードを作成
  4. 公式ドキュメントの参照: extern の仕様や型システムを確認

より詳しい情報

VRChat 公式ドキュメント

OpCode の詳細な仕様については、VRChat の公式ドキュメントを参照してください:

Extern(外部メソッド)のリファレンス

利用可能な extern を調べるには: