Udon Decompiler 文档

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__SystemVoid
VRCDynamicsVRCConstraintSource.__set_ParentPositionOffset__UnityEngineVector3
ExternVRCEconomyIProduct.__get_Name__SystemString
UnityEngineColor.__op_Addition__UnityEngineColor_UnityEngineColor__UnityEngineColor
UnityEngineGameObject.__SetActive__SystemBoolean__SystemVoid
VRCDynamicsVRCConstraintSource.__set_ParentPositionOffset__​UnityEngineVector3
ExternVRCEconomyIProduct.__get_Name__SystemString
UnityEngineColor.__op_Addition__UnityEngineColor_UnityEngineColor__​UnityEngineColor

这些名字由两部分组成, 分别是 ModuleNameModuleNameFuncSignatureFuncSignature. 类(也即 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_0
COPY
JUMP_INDIRECT, __intnl_returnJump_SystemUInt32_0
PUSH, __intnl_returnJump_SystemUInt32_0
COPY
JUMP_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: 从栈中先后弹出 TARGETTARGETSOURCESOURCE 两个地址, 然后把堆中 TARGETTARGET 地址指向的值使用 SOURCESOURCE 地址指向的值覆盖. 所在 PC 步进 4 字节