Udon VM
Udon VM 是一个简单的栈式虚拟机.
- 堆: 是一个
IStrongBox[]IStrongBox[], 地址就是数组索引, 使用程序中的常量段初始化 - 栈: 一个
u32u32栈 - PC 寄存器: 单位是字节
Udon VM 的外部函数委托是 UdonExternDelegateUdonExternDelegate, 具体定义为
delegate void UdonExternDelegate(IUdonHeap heap, Span<uint> parameterAddresses);
delegate void UdonExternDelegate(IUdonHeap heap, Span<uint> parameterAddresses);
也即传入
- 堆用于获取参数和写入结果
- 一系列参数地址(在堆中的)用于获取参数
在此基础上封装了 CachedUdonExternDelegateCachedUdonExternDelegate, 具体定义为
class CachedUdonExternDelegate{ public readonly string externSignature; public readonly UdonExternDelegate externDelegate; public readonly int parameterCount;}
class CachedUdonExternDelegate{ public readonly string externSignature; public readonly UdonExternDelegate externDelegate; public readonly int parameterCount;}
CachedUdonExternDelegateCachedUdonExternDelegate 可以完全通过一个 stringstring 获取, 也即 externSignatureexternSignature.
这个 externSignatureexternSignature 是 Udon Node 的方法签名, 相关生成代码在 UdonSharp.Compiler.Udon.CompilerUdonInterfaceUdonSharp.Compiler.Udon.CompilerUdonInterface 中, 一些签名的例子如
UnityEngineGameObject.__SetActive__SystemBoolean__SystemVoidVRCDynamicsVRCConstraintSource.__set_ParentPositionOffset__UnityEngineVector3ExternVRCEconomyIProduct.__get_Name__SystemStringUnityEngineColor.__op_Addition__UnityEngineColor_UnityEngineColor__UnityEngineColor
UnityEngineGameObject.__SetActive__SystemBoolean__SystemVoidVRCDynamicsVRCConstraintSource.__set_ParentPositionOffset__UnityEngineVector3ExternVRCEconomyIProduct.__get_Name__SystemStringUnityEngineColor.__op_Addition__UnityEngineColor_UnityEngineColor__UnityEngineColor
这些名字由两部分组成, 分别是 ModuleNameModuleName 和 FuncSignatureFuncSignature. 类(也即 ModuleModule)通过实现 IUdonWrapperModuleIUdonWrapperModule, 将自己的 ModuleNameModuleName 和所有 FuncSignatureFuncSignature 及其对应的参数数量注册到 UdonWrapperUdonWrapper 中, 供其使用完整的 externSignatureexternSignature 获取.
除了入口点表一节中提到的入口点表外, Udon Sharp 在生成函数时候还做了其他的处理.
大多数(包括非公开的)函数有两个入口: 公开入口和内部入口. 公开入口用于外部调用, 从公开入口进入函数, 其执行结果是 Udon VM 停机. 从内部入口进入函数, 其执行结果是跳回到调用函数的 JUMPJUMP 之后.
两者的区别是, 公开入口比内部入口多了一句 PUSH __const_SystemUInt32_0PUSH __const_SystemUInt32_0, 这里 __const_SystemUInt32_0__const_SystemUInt32_0 的地址不固定, 但是其值永远是 42949672954294967295, 也即 0xFFFFFFFF0xFFFFFFFF. 当这个值被写入 PC(也即 JUMPJUMP 到这个地址) 时, Udon VM 会停机.
函数的返回被编译为
PUSH, __intnl_returnJump_SystemUInt32_0COPYJUMP_INDIRECT, __intnl_returnJump_SystemUInt32_0
PUSH, __intnl_returnJump_SystemUInt32_0COPYJUMP_INDIRECT, __intnl_returnJump_SystemUInt32_0
当通过公开入口进入时, 这里的 __intnl_returnJump_SystemUInt32_0__intnl_returnJump_SystemUInt32_0 也就被写入了 0xFFFFFFFF0xFFFFFFFF, 最终使 Udon VM 停机. 而当内部入口进入时, 则是由调用者负责在调用前在堆中压入返回地址(也即 JUMPJUMP 指令的下一条指令的地址).
不断读取当前 PC 处的指令并执行, 直到停机或 PC 超出当前程序有效指令空间或 PC 为 0xFFFFFFFF0xFFFFFFFF. 不同指令的执行策略为
NOPNOP: PC 步进 4 字节ANNOTATIONANNOTATION: PC 步进 8 字节PUSHPUSH: 把OPERANDOPERAND作为立即数压栈, PC 步进 8 字节POPPOP: 弹栈, 丢弃栈顶值, PC 步进 4 字节JUMPJUMP: 设置 PC 为OPERANDOPERAND-
JUMP_IF_FALSEJUMP_IF_FALSE: 栈顶是堆地址, 弹栈, 读该地址对应的堆元素(boolbool)的值- 若为
truetrue, PC 步进 8 字节 - 若为
falsefalse, 设置 PC 为OPERANDOPERAND
- 若为
JUMP_INDIRECTJUMP_INDIRECT: 设置 PC 为OPERANDOPERAND作为堆地址指向的u32u32值-
EXTERNEXTERN: 调用外部函数. 尝试读取OPERANDOPERAND作为堆地址指向的对象- 若为
stringstring, 通过UdonWrapperUdonWrapper获取该stringstring对应的CachedUdonExternDelegateCachedUdonExternDelegate(这是通常的情况) - 若为
CachedUdonExternDelegateCachedUdonExternDelegate, 也得到了CachedUdonExternDelegateCachedUdonExternDelegate
从栈中连续弹出
CachedUdonExternDelegate.parameterCountCachedUdonExternDelegate.parameterCount个参数地址, 按与弹栈相反的顺序(也即最初的栈顶为最后一个地址)组装成Span<uint> parameterAddressesSpan<uint> parameterAddresses, 并调用UdonExternDelegateUdonExternDelegate. PC 步进 8 字节在调用者(对于非静态函数)或返回值存在的情况下, 调用者和返回值分别作为第一个和最后一个参数传入.
- 若为
COPYCOPY: 从栈中先后弹出TARGETTARGET和SOURCESOURCE两个地址, 然后把堆中TARGETTARGET地址指向的值使用SOURCESOURCE地址指向的值覆盖. 所在 PC 步进 4 字节