ADR-0021: User-Defined Structs, Payload Enums, and Flattened Aggregate Storage
이 콘텐츠는 아직 번역되지 않았습니다.
Status
Section titled “Status”Accepted
Context
Section titled “Context”Sobakasu には複数の値を一つの型として扱うユーザー定義型がなく、関連する state や配列要素を個別の変数として管理する必要があった。一方、対象である Udon VM には、コンパイラが任意に追加できる struct / union ABI 型がない。そのため source 上の値のまとまりと Udon heap 上の表現を分離する必要がある。
この決定は ADR-0003 の Parser、Binder、IR Lowerer、UASM backend の責務分離、ADR-0007 の let / mut、ADR-0013 の共有可能な参照と shallow-copy、ADR-0014 の top-level state / pub / sync、ADR-0016 の impl と extern 境界、ADR-0018 の module / visibility、ADR-0019 の object、ADR-0020 の typed array と評価順序を維持しなければならない。初期値は ADR-0006 の post-assemble heap patch を引き続き使用する。
Decision
Section titled “Decision”Compile-time aggregate type
Section titled “Compile-time aggregate type”非 generic のユーザー定義 struct と Rust 風 payload enum を nominal type として導入する。同じ field 構成でも宣言 identity が異なる型は互換ではなく、aggregate 間の structural implicit conversion は行わない。
aggregate は Sobakasu の Parser / Binder / Bound tree では一つの型と値である。Binder は宣言、field、variant、payload、tag、visibility、型を解決する。Parser や Binder で複数の独立変数へ早期分解せず、UASM backend に field / variant の探索や型検査を行わせない。
型宣言 shell を field 型の binding より先に収集して、同一 module 内および既存の use / pub use / qualified module access による前方参照を可能にする。pub struct と pub enum は ADR-0018 の通常の型 export と同じ規則を使う。v1 では field ごとの visibility は導入しない。Named Type Symbol を対象とする既存の通常 impl を aggregate にも再利用し、aggregate 専用 dispatch は作らない。
Struct syntax and semantics
Section titled “Struct syntax and semantics”宣言、構築、field access は次の形とする。
struct Foo { score: i32, finished: bool,}
state foo = Foo { finished: false, score: 0,};foo.score = 100;末尾 comma を許可する。v1 に field default はなく、構築時は全 field が必要である。記述順は宣言順と異なってよい。Binder は unknown / duplicate / missing field と型不一致、struct でない対象への initializer を診断する。layout は initializer 順でなく、宣言側の決定的な field 順に従う。
aggregate 全体および field の再代入には root binding の mut が必要である。ただし mut は deep immutability ではない。immutable foo でも foo.values[0] = value のような参照先配列の変更は ADR-0013 / ADR-0020 に従って許可し、foo.values = replacement は拒否する。
struct の代入、引数、戻り値は field-wise shallow copy へ lower する。primitive leaf は値を、配列、string、Unity / VRChat object 等の参照 leaf は既存の参照をコピーする。deep clone、ownership、move は導入しない。
Payload enum syntax and semantics
Section titled “Payload enum syntax and semantics”一つの enum で次の三形式を混在可能とする。
enum WebEvent { PageLoad, KeyPress(char), Ip(u8, u8, u8, u8), Click { x: i64, y: i64, },}
let load = WebEvent.PageLoad;let key = WebEvent.KeyPress('A');let ip = WebEvent.Ip(127u8, 0u8, 0u8, 1u8);let click = WebEvent.Click { x: 10i64, y: 20i64, };Unit variant は payload を持たない。Tuple variant は 0 個に特殊化せず、複数の位置付き payload を持てる。Struct variant は名前付き payload を持つ。Binder は解決済み variant symbol と payload field symbol を Bound node に保存し、unknown / duplicate variant、tuple arity / type、named payload の unknown / duplicate / missing / type mismatch を診断する。
variant の tag は宣言順に 0 から割り当てる i32 である。明示的 discriminant、C / C# 風整数 enum、flags enum は v1 では導入しない。
Recursive leaf layout
Section titled “Recursive leaf layout”ユーザー定義 aggregate は独立した Udon ABI 型を持たない。意味解析後、logical aggregate storage を Udon で表現可能な leaf storage へ宣言順で再帰的に flatten する。
Player { score: i32, position: Position { x: f32, y: f32 } }
player__score: i32player__position__x: f32player__position__y: f32実際の symbol 名は決定的で、source field path を追跡でき、既存 symbol と衝突しない名前にする。logical storage identity と physical leaf storage identity は区別する。Lowerer は aggregate value / storage と field path を解決済み leaf operation へ変換し、Assembler が受け取る命令は既存の scalar COPY、typed array extern、branch 等だけにする。
有限に flatten できない直接・間接・struct / enum 横断の dependency cycle は Binder で拒否し、cycle path を診断する。配列 field を介する cycle も例外にしない。
enum storage は tag leaf と全 variant の全 payload leaf を持つ。
event__tag: i32event__KeyPress__0: charevent__Click__x: i64event__Click__y: i64現在有効な payload は tag だけで決まる。variant を変更しても inactive payload は clear せず、stale storage を保持してよい。source から inactive payload を直接読む機能は提供しない。既存 location へ enum を書くときは、各 source expression を左から右へ一度だけ評価し、新しい payload leaf を先に書き、tag を最後に書く。
Aggregate arrays use Structure of Arrays
Section titled “Aggregate arrays use Structure of Arrays”通常の [T] は ADR-0020 のとおり単一の Udon ABI T[] である。ユーザー定義 aggregate Foo に限り [Foo] は Foo[] へ変換せず、各 leaf の typed array からなる Structure of Arrays (SoA) とする。
[Foo { score: i32, finished: bool }]
foos__score: i32[]foos__finished: bool[]全 leaf array は常に同じ length を持つ。array literal は各要素を source 順で一度だけ評価して各 leaf setter へ展開する。repeat construction は length を一度だけ評価し、同じ値で全 leaf array を構築する。値 repeat の operand、長さ 0、左から右の評価順序は ADR-0020 を維持する。
foos[i] の read / write は index を一度だけ評価して各 leaf getter / setter へ展開する。foos[i].score は対応する foos__score[i] に直接 lower でき、aggregate 全体の不要な materialize / write-back は行わない。compound assignment でも array、index、右辺を既存規則どおり一度だけ評価する。enum array は tag array と payload arrays を使い、element assignment でも payload setter を tag setter より先に emit する。
aggregate の中に aggregate がある場合および enum payload 内の aggregate にも同じ recursive layout を使う。aggregate 内の通常配列 leaf はその参照を一つの leaf とし、aggregate array に含めるには対応する typed array-of-array ABI が SDK catalog に存在しなければならない。
State, public variables, synchronization, and heap patches
Section titled “State, public variables, synchronization, and heap patches”top-level aggregate state は同じ UdonBehaviour 内の複数 leaf heap slots に展開し、どの関数・イベントから参照しても同じ slots を使用する。pub aggregate はすべての leaf を deterministic / collision-free な public symbols として export する。既存の単純 state の名前と ABI は変更しない。
宣言構文と mutability は ADR-0025 により state へ置き換えられ、aggregate state も常に再代入可能である。本 ADR の recursive flattening、SoA、leaf export/sync、heap patch semantics は変更しない。
sync aggregate はすべての leaf に同じ sync mode を付け、一つの logical synchronization group とみなす。同一 Behaviour の通常 serialization を使用し、version counter、二重 buffer、複数 Behaviour への分割は導入しない。pub と sync は ADR-0014 の独立した性質を保つ。
Binder は各 physical leaf に ADR-0014 / ADR-0020 の storage、public typed-array、sync mode 互換性を適用する。一つでも非対応 leaf があれば logical field path を含む診断で aggregate 全体を拒否する。
定数 top-level initializer は logical valueを宣言順の leaf constant に評価する。各 physical state を既存 ADR-0006 の GlobalInitializer heap patch entry として保存するため、primitive、nested aggregate、payload enum、SoA aggregate array の runtime 型情報と初期値は ProgramAsset refresh 後も復元される。aggregate 専用の第二の保存形式は作らない。
Functions and extern boundary
Section titled “Functions and extern boundary”通常の Sobakasu 関数の aggregate 引数 / 戻り値 / local は logical aggregate のまま型検査し、既存 inline lowering の parameter / result storage を leaf bindings へ展開する。Udon ABI に aggregate parameter type は生成しない。
Sobakasu aggregate 自体を Udon extern の引数、戻り値、operator receiver 等として渡すことは禁止する。object への暗黙 boxing で境界を迂回しない。必要な leaf は source で明示的に渡す。これは ADR-0016 / ADR-0019 の解決済み extern 境界を維持する。
Out of scope
Section titled “Out of scope”generic struct / enum、generic instantiation、monomorphization、match、pattern matching、if let、destructuring、明示的 discriminant、recursive aggregate、inheritance / trait / interface、aggregate runtime reflection、dynamic dispatch、packed / user-specified layout、C ABI、inactive payload access はこの決定に含めない。
将来 optimizer が内部 layout を最適化する余地は残すが、source semantics、評価順序、logical storage identity を変更してはならない。
Alternatives
Section titled “Alternatives”- user-defined aggregate を Udon ABI 型として生成する。Udon に任意の struct / union ABI がないため却下した。
object[]に AoS として詰め込む。primitive の boxing / unboxing、static type 情報の消失、operator / assignment / sync の複雑化、既存 typed array との不整合が生じるため却下した。- 固定長 aggregate array の全要素を scalar slots に展開する。runtime length に対応できず一般の
[Foo]を表現できないため却下した。 - 同型 leaf を
[x0, y0, x1, y1, ...]のように interleave する。型構成によって stride と layout が変わり、field access、sync、patch が複雑になるため v1 では却下した。 - backend が struct / enum を再解釈して flatten する。型解決、variant 検査、layout 判断、
pub/sync意味判定が backend に漏れ、ADR-0003 の責務分離に反するため却下した。
採用案は recursive leaf flattening for aggregate values and Structure of Arrays for aggregate arrays である。
Rationale
Section titled “Rationale”一つの layout traversal を struct、enum、nested aggregate、local、function、state、array に共有することで、Sobakasu の nominal value semantics と Udon の既存 typed storage を両立できる。Binder が意味と ABI 適合性を確定し、Lowerer が leaf operation を生成すれば、UASM Assembler は source-level aggregate を知らずに既存命令と heap patch を処理できる。
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- Udon に独自 runtime 型を要求せず、typed Udon storage を維持できる。
- struct、payload enum、nested aggregate、aggregate array が一つの recursive layout 原則を共有する。
pub/sync/ heap patch を既存の leaf 規則へ自然に展開できる。- tag + payload representation を将来の
match実装で再利用できる。 - source-level 型解決や aggregate layout 判断を backend に持ち込まない。
Negative
Section titled “Negative”- aggregate field 数に比例して Udon variable と aggregate array extern operation が増える。
- enum には inactive payload storage が残る。
- SoA の物理 layout は source 上の AoS イメージと異なる。
- recursive aggregate は表現できない。
- aggregate ABI を要求する extern へ直接渡せない。
- public aggregate は複数の Udon public symbols として見える。
- deterministic naming、aggregate layout、heap patch manifest の連携が複雑になる。