Appearance
ShirasUdonAssembly JSON v1 仕様書
生成AIでテストJSONを作成する場合は、実践的な生成手順、検証項目、プロンプト雛形をまとめた AI向け作成ガイドも参照すること。
1. 概要
ShirasUdonAssembly JSON は、ShirasUdonVM が実行するための中間表現である。
ShirasUdonAssembly JSON は UdonAssembly そのものを表現するものではない。
特に以下の実行時・生成時の詳細は JSON から排除する。
- Udon の Heap アドレス
- Udon の命令アドレス
- UdonAssembly のラベル解決後のアドレス
- ExternBridge の内部配置
ShirasUdonAssembly ではコードブロックにラベルを付け、ジャンプはラベルを使用する。
現在のランタイムでは、EXTERN の実行を次のコンポーネントに分離する。
text
ShirasUdonVM
↓ EXTERN要求
ShirasUdonExternBridge
↓ 引数・Signature・実行先を設定
ShirasUdonExternExecutor
↓
Udon EXTERN命令ShirasUdonVMは ShirasUdonAssembly の命令、Stack、Heapを管理する。ShirasUdonExternBridgeは論理Extern Index、Executor bank、キャッシュおよび障害復旧を管理する。ShirasUdonExternExecutorは実際のUdonEXTERN命令だけを実行する。
この分離はランタイムの実装詳細であり、ShirasUdonAssembly JSONの形式には含めない。
text
ShirasUdonAssembly JSON
↓
ShirasUdonVM
↓
実行
または
ShirasUdonAssembly JSON
↓
UdonAssembly Compiler
↓
UdonAssembly2. トップレベル構造
json
{
"version": 1,
"data": [],
"code": []
}| プロパティ | 型 | 必須 | 内容 |
|---|---|---|---|
version | Number | ○ | JSON仕様のバージョン |
data | Array | ○ | Heap初期値 |
code | Array | ○ | コードブロック |
3. data
data は VM の Heap を初期化するためのデータを定義する。
json
{
"name": "__value_0",
"type": "SystemInt32",
"value": 123
}3.1 プロパティ
| プロパティ | 型 | 必須 | 内容 |
|---|---|---|---|
name | String | ○ | Heap Symbol |
type | String | ○ | 実際に保持する型 |
value | JSON Value | ○ | 初期値 |
4. JSON値とHeap値
VRCJson では JSON の数値は TokenType.Double として読み込まれる。
したがって、例えば、
json
{
"type": "SystemInt32",
"value": 123
}はロード時に、
text
JSON Double
↓
data.type == SystemInt32
↓
(int)123
↓
new DataToken((object)123)と変換する。
現在想定する数値型:
text
SystemByte
SystemSByte
SystemInt16
SystemUInt16
SystemInt32
SystemUInt32
SystemInt64
SystemUInt64
SystemSingle
SystemDoubleBoolean、String についても JSON から読み込んだ値を new DataToken((object)value) として再ラップする。
これにより Heap 上の実値は Reference として扱える。
4.1 enum の表現
ShirasUdonAssembly JSON の data で enum 型の初期値を定義する場合、value には enum の要素名ではなく、その基になる整数値を JSON Number として記述する。
json
{
"name": "__ease_0",
"type": "VRCSDK3ComponentsVRCTweenEase",
"value": 1
}上記の 1 は VRCSDK3ComponentsVRCTweenEase.Linear を表す。type には Udon の型名を記述し、value に "Linear" のような文字列を記述してはならない。
これは ShirasUdonAssembly JSON を生成する外部コンパイラの責務である。外部コンパイラは コンパイル元の enum 定数を、その enum の基になる数値へ正規化して value に出力する 必要がある。Flags enum の組み合わせも、要素名の配列ではなく合成後の整数値を出力する。
ShirasUdonVM はロード時に type と数値 value を使って、実際のUdon enum値へ変換する。 対応するenum型または数値がランタイムのenumテーブルに存在しない場合、ロードは失敗する。
この表現は標準のUdonAssemblyと異なる。標準のUdonAssemblyでは型付きHeap変数として enumを保持できるが、ShirasUdonAssembly JSONではenumオブジェクトをJSONへ直接格納せず、 型名と数値の組として表現する。したがって、標準UdonAssemblyからShirasUdonAssembly JSONを 生成するコンパイラも、enum初期値を数値へ変換しなければならない。
COPY では、コピー元とコピー先の宣言型に応じて次の変換を行う。
text
enum -> SystemInt32
SystemInt32 -> enumこれにより、次のような処理ではenumを一度 SystemInt32 として計算し、結果をenumへ 書き戻せる。
csharp
e = (EnumType)((int)e + 1);ただし、外部コンパイラが生成する data のenum初期値については、常に前述の数値表現を 使用しなければならない。
4.2 null と値型のデフォルト値
data.value が null の場合、宣言された type に応じて次のように扱う。
text
参照型 -> null
値型 -> default(T)値型の null は、値そのものをShirasUdonAssembly JSONへシリアライズするための表現では なく、APIの戻り値を後から格納するHeap Symbolの安全な初期値として使用する。
json
{
"name": "__hit_0",
"type": "UnityEngineRaycastHit",
"value": null
}ShirasUdonVMはロード時に、UdonでHeap変数として使用可能な各値型について事前生成された default(T) を取得し、boxed objectとして論理Heapへ格納する。これにより、APIの戻り値が 書き込まれる前にHeap Symbolが参照されても、値型引数へnullが渡されることを防ぐ。
コンストラクターが公開されていない値型について、任意の非デフォルト値をJSONから 復元する仕様はv1に含めない。そのような値はAPIの戻り値として取得して使用する。
enum初期値は4.1の数値表現を使用する。enumのAPI戻り値スロットを初期化する目的に限り、 value: null を default(EnumType) として使用できる。
5. Stack
ShirasUdonVM の Stack には、実値そのものではなく Heap Symbol を格納する。
例えば、
text
PUSH __value_0を実行すると、
text
Stack
┌──────────────┐
│ "__value_0" │
└──────────────┘となる。
実際の値は Heap から取得する。
text
Stack
│
│ "__value_0"
▼
Heap
│
│ Reference → int 123
▼
123したがって Stack の基本的な型は String である。
6. code
code はコードブロックの配列。
json
{
"name": "_start",
"export": true,
"instructions": []
}6.1 コードブロック
| プロパティ | 型 | 必須 | 内容 |
|---|---|---|---|
name | String | ○ | コードブロックのラベル |
export | Boolean | ○ | 外部から呼び出し可能か |
instructions | Array | ○ | 命令列 |
すべてのコードブロックはラベルを持つ。
ShirasUdonAssembly JSON ではジャンプ先をアドレスではなくラベルで指定する。
7. export
export は、そのコードブロックを外部からエントリポイントとして実行できるかを表す。
json
{
"name": "_start",
"export": true,
"instructions": []
}Run("_start") のような外部実行では export: true が必要。
一方、
text
JUMP "_loop"では export の値に関係なくジャンプできる。
_start を特別扱いする仕様ではない。
8. 命令
v1 で現在使用している命令は以下。
text
PUSH
POP
COPY
EXTERN
JUMP
JUMP_INDIRECT
JUMP_IF_FALSE8.1 PUSH
Heap Symbol を Stack へ積む。
json
{
"op": "PUSH",
"operand": "__value_0"
}9. POP
Stack の最上位を破棄する。
json
{
"op": "POP"
}主に、不要な Extern 戻り値を破棄するために使用する。
10. COPY
Stack 上の 2 つの Heap Symbol 間で値をコピーする。
json
{
"op": "COPY"
}Stack:
text
[
"__source",
"__destination"
]実行すると、
text
Heap["__destination"]
=
Heap["__source"]となり、Stack から 2 つの Symbol が取り除かれる。
11. EXTERN
Udon の Extern を実行する。
json
{
"op": "EXTERN",
"signature": "SystemInt32.__op_Addition__SystemInt32_SystemInt32__SystemInt32",
"argumentCount": 2,
"return": "__sum_0"
}プロパティ
| プロパティ | 型 | 必須 | 内容 |
|---|---|---|---|
op | String | ○ | "EXTERN" |
signature | String | ○ | Udon Extern Signature |
argumentCount | Number | ○ | Udon EXTERN へPUSHする引数数。戻り値格納先は含まない |
refArguments | Array | × | 実行後に書き戻す引数の0始まりインデックス。省略時は空配列 |
return | String | 条件付き | 非Void Signatureでは必須。Void Signatureでは指定不可 |
hasReturn と pushCount は存在しない
戻り値の有無はVMがロード時にSignatureから判断する。原則として末尾が __SystemVoidなら戻り値なし、それ以外なら戻り値ありとして扱う。
ただし、Udonの一部のプロパティsetter Signatureは末尾の__SystemVoidを省略する。 .__set_を含むSignatureは、末尾にかかわらず戻り値なしとして扱う。
json
{
"signature": "UnityEngineDebug.__Log__SystemObject__SystemVoid",
"argumentCount": 1
}→ 戻り値なし。
json
{
"signature": "SystemInt32.__TryParse__SystemString_SystemInt32Ref__SystemBoolean",
"argumentCount": 2,
"refArguments": [1],
"return": "__result_0"
}→ 戻り値あり。
非Void Signatureで return が省略されている場合、またはVoid Signatureで return が指定されている場合、ロードは失敗する。戻り値を破棄する場合も 上位コンパイラが一時Heap Symbolを return に指定し、直後に POP を生成する。
12. refArguments
Extern の引数について、実行後にHeapへ値を書き戻す必要がある引数の 0始まりインデックスを表す。C#の ref と out の両方を含む。
例えば、
text
SystemInt32.TryParse(
string,
ref int
)なら、
json
"refArguments": [1]となる。
インデックスは argumentCount 未満でなければならず、重複してはならない。 refArguments を省略した場合は空配列として扱う。
13. argumentCount と内部Push数
argumentCount は上位コンパイラが確定した、Udon EXTERNへPUSHする 引数数である。インスタンスメソッドのreceiverを含み、戻り値格納先は含まない。
VMはロード時に次を計算し、内部情報として命令へキャッシュする。
text
hasReturn = Signatureが値を返すか(プロパティsetterは常にfalse)
pushCount = argumentCount + (hasReturn ? 1 : 0)例えば、
text
TryParse(
string,
ref int
)
↓
引数 2
戻り値 1
↓
argumentCount = 2
pushCount = 3(VM内部)戻り値なしの Debug.Log(object) なら、
text
引数 1
戻り値 0
↓
argumentCount = 1
pushCount = 1(VM内部)となる。
argumentCount は0以上の整数でなければならない。計算された pushCount が ABI上限を超える場合、ロードは失敗する。実行時はロード時にキャッシュした値を使う。
14. EXTERN の Stack 動作
例えば、
json
{
"op": "EXTERN",
"signature": "SystemInt32.__op_Addition__SystemInt32_SystemInt32__SystemInt32",
"argumentCount": 2,
"return": "__sum_0"
}実行前:
text
Stack:
[
"__value_0",
"__constant_10"
]結果:
text
Heap["__sum_0"] → -113Stack:
text
[
"__sum_0"
]戻り値がある Extern は、戻り値の Heap Symbol を Stack へ残す。
14.1 Extern BridgeとExecutor
ShirasUdonExternBridge と ShirasUdonExternExecutor の責務は分離する。
| コンポーネント | 責務 |
|---|---|
ShirasUdonExternBridge | Signatureから論理Extern Indexへの割り当て、Executor bankの選択・生成・交換、引数と戻り値の橋渡し |
ShirasUdonExternExecutor | 生成済みUdonAssemblyを使用した、実際のUdon EXTERN命令の実行 |
ShirasUdonExternBridge は常駐する。
ShirasUdonExternExecutor はPrefabから必要に応じてInstantiateし、Bridgeの子GameObjectとして保持する。
Executorの1インスタンスは256個の物理Extern slotを持つ。
Signatureへ割り当てる論理Extern Indexには256の上限を設けない。Bridgeは次の式で実行先を決定する。
text
executorBankIndex = logicalExternIndex / 256
physicalExternIndex = logicalExternIndex % 256例えば、論理Extern Index 1500 は次のように配置する。
text
Executor bank = 5
Physical slot = 220必要なExecutor bankだけを遅延生成する。
論理Extern Indexはランタイム内部のキャッシュであり、ShirasUdonAssembly JSONのEXTERN命令には保存しない。JSON上の識別情報は引き続きsignatureとする。
14.2 Externキャッシュ
BridgeはExtern Signatureごとに安定した論理Extern Indexを割り当てる。
text
Extern Signature
↓ cache
Logical Extern Index
↓ / 256, % 256
Executor bank + Physical slot同じSignatureを再度実行するときは、同じ論理Extern Indexと物理slotを使用する。
各実行時にSignatureを対象の物理slotへ設定する。このため、Executorが交換された場合でも、使用されたExternから順にslotの内容を遅延再構築できる。交換時にbank内の全Signatureを一括登録し直す必要はない。
14.3 Executorの例外と交換
Udonは実行中に例外が発生すると、そのUdonBehaviourのenabledをfalseにして停止する。停止したShirasUdonExternExecutorは、その後の命令を実行できない。
BridgeはExtern呼び出しの直前と直後にExecutorのenabledを確認する。
Extern実行後にExecutorが無効になっていた場合は、次のように処理する。
- 失敗したExecutorから戻り値および
ref/out値を読み取らない。 - 失敗したExecutor bankのGameObjectを破棄する。
- 同じbank位置へ新しい
ShirasUdonExternExecutorをInstantiateする。 - Signatureから論理Extern Indexへのキャッシュは維持する。
- 現在の
EXTERN命令を失敗としてVMへ返し、VMの現在の実行を停止する。
例外を発生させたExtern命令は、副作用が途中まで実行されている可能性がある。そのため自動的には再実行しない。次回のVM実行では、交換済みExecutorを使用する。
他のExecutor bankは破棄せず、そのbankに属するキャッシュと実行状態を維持する。
15. JUMP
指定されたラベルへジャンプする。
json
{
"op": "JUMP",
"operand": "_loop"
}_loop が存在すれば、その命令列の先頭から実行する。
export は確認しない。
16. JUMP_INDIRECT
Heap に格納されたラベルへ間接ジャンプする。
json
{
"op": "JUMP_INDIRECT",
"operand": "__jump_0"
}Heap:
text
__jump_0
↓
Reference("_loop")の場合、
text
JUMP_INDIRECT __jump_0
↓
"_loop"
↓
JUMP "_loop"として実行する。
ShirasUdonAssembly JSON では、間接ジャンプにも Udon の生アドレスを使用しない。
17. JUMP_IF_FALSE
Stack から条件の Heap Symbol を取得し、その Heap 値が false の場合に指定ラベルへジャンプする。
json
{
"op": "JUMP_IF_FALSE",
"operand": "_end"
}例えば、
text
Stack:
[
"__condition_0"
]
Heap:
__condition_0 → trueならジャンプしない。
text
__condition_0 → falseなら、
text
JUMP "_end"を実行する。
18. 命令列の終了
現在の仕様では、
現在のコードブロックの命令列末尾に到達した場合、正常終了する。
例えば _end の命令列を最後まで実行した場合、その後に次の命令が存在しないため VM は正常終了する。
19. v1 の設計思想
ShirasUdonAssembly JSON v1 では、上位コンパイラだけが確定できる意味情報をJSONに持たせ、派生情報はロード時に一度だけ計算する。
例えば EXTERN では、
text
signature
argumentCount
refArguments(該当する場合)
returnを持つ。
一方、
text
hasReturn
pushCount
ref引数のbool配列への展開などはVMがロード時に検証・計算し、実行時に再計算しない。
設計としては、
text
コンパイル時
↓
解析・計算
↓
ShirasUdonAssembly JSON
↓
実行時
↓
できるだけそのまま実行とする。
20. ShirasUdonAssembly と UdonAssembly の境界
ShirasUdonAssembly で扱う
text
ラベル
Heap Symbol
Extern Signature
Stack
Heap
制御フロー
型情報
WriteBack 情報ShirasUdonAssembly では扱わない
text
Udon Heap Address
Udon Instruction Address
JUMP の実アドレス
ExternBridge の内部配置
UdonAssembly の stride / offsetこれらは将来的な ShirasUdonAssembly → UdonAssembly Compiler の責務とする。
21. 設計上の基本原則
ShirasUdonAssembly は、Udon VM の低レベルなアドレス表現を直接記述するのではなく、コンパイル可能な意味を表現する中間言語として設計する。
特に、
text
ラベル → 制御フロー
Heap Symbol → 値への参照
Extern Signature → Udon Extern
export → 外部公開という責務を持たせる。
その後の UdonAssembly 生成時に、
text
Shiras Label
↓
Udon address
Heap Symbol
↓
Udon heap variable
Extern
↓
ShirasUdonExternBridge
↓
ShirasUdonExternExecutor / UdonAssemblyへ変換する。