类型系统、数据表示与 ABI
1. 编译期有类型,VM 槽位没有逐值标签#
编译器以 scrType 及其子类表示 BOOL、INT、FLOAT、字符串、向量、枚举、数组、结构体、引用、Native 句柄和函数类型。一般兼容性比较类型对象身份,特殊类型覆写规则。生成后,VM 使用 union scrValue:同一槽位可按 Int、Float、String、Reference 或 Any 解释。操作码及编译结果负责告知 VM 应采用哪种解释。[S1][S2]
因此这不是动态类型语言。也不能因 scrValue 的辅助 ValueType 枚举存在,就认为每个运行时值都携带类型标签。调试元数据、Native 模板、可变参数类型描述与槽位本体是不同层。
2. 主要类型#
| 类型 | 编译/执行特性 | 设计含义 |
|---|---|---|
| INT | 使用 C++ int/32 位操作,通常一槽 | 64 位槽不自动提供 64 位算术 |
| FLOAT | 单精度 float,一槽 | 不应推定双精度或跨平台确定性 |
| BOOL | 独立静态类型,运行以整数槽表示 | 条件类型检查的 warning 路径也会阻止正常生成产物 |
| VECTOR | 三个 FLOAT 槽,字段 X/Y/Z | 与引擎 SIMD Vector3 不一定同布局 |
| STRING | 字符串指针/编码引用,一槽 | 不拥有通用可增长字符串对象 |
| TEXT_LABEL_N | 固定可写缓冲区;编译大小 (N+1)/4 槽 | 容量、槽位和字节不能混用 |
| ENUM / STRICT_ENUM | 命名枚举;严格变体抑制若干整数提升 | 位标志与普通状态枚举应区分 |
| HASH_ENUM / STRICT_HASH_ENUM | 未显式赋值时取枚举名哈希 | 哈希不是序号;COUNT_OF 不等于项数 |
| STRINGHASH | 整数表示的语义类型 | 字面量可编译期哈希,变量可能运行期哈希 |
| NATIVE 类型 | 引擎句柄,可声明父类型 | 静态 API 类型安全,不等于脚本类继承 |
| POOLINDEX | 特殊池索引,默认 -1 | “全部默认零”也有例外 |
| STRUCT、数组 | 内联聚合值、固定布局 | 复制与栈消耗可预测,但大对象代价直接 |
| TYPEDEF FUNC/PROC | 带签名的函数/过程指针 | 不等同捕获环境的闭包 |
类型与默认初始化见 type.h/type.cpp。[S1] 局部槽通常先清零,再补数组头和非零默认字段;Native 句柄一般以 NULL 为零值。ENUM 的 COUNT_OF 按实现维护的枚举计数规则理解,手册说明为最后枚举值加一,不宜用于稀疏或 HASH_ENUM 的成员数量统计。
3. 数值转换:强类型不是绝对禁止转换#
INT/FLOAT 变量直接赋值不应按 C 的自动转换来写,通常使用 TO_FLOAT、ROUND、FLOOR、CEIL。但生成器对常量和算术存在提升特例。VECTOR 与 FLOAT 算术可广播 scalar。[S3]
ENUM_TO_INT、INT_TO_ENUM、NATIVE_TO_INT、INT_TO_NATIVE 提供显式通道,若干转换仅产生 OP_NOP 或改变编译期类型。显式写出转换不代表运行时验证了枚举成员或句柄存活。
scrTypeDefaultValue::IsCompatibleWith 比较基本类型,宽于精确枚举类型身份;引用参数又有 -strictreferencetypechecking 选项。报告将其描述为“有静态类型检查”,而不声称它已经形式化证明健全或拒绝一切不安全转换。[S1][S3] 诊断名字中的 warning 不代表能带警告正常生成产物,具体失败机制见前端报告。
数值边界也要单独定义。当前整数加减乘及除法多直接使用宿主 C++ int 运算,不能仅凭常见机器结果认定语言已规范“有符号溢出总是回绕”;INT_MIN / -1 也需要单独验证。FLOAT 采用单精度,但 NaN、负零、舍入及不同平台优化不由“使用 float”自动保证一致。已经能静态确认的零除数路径差异见虚拟机报告,其余边界本次没有运行验证。
4. 内存布局:用槽位计算#
当前模型可用下列公式理解:
SIZE_OF(INT) = SIZE_OF(FLOAT) = 1
SIZE_OF(VECTOR) = 3
SIZE_OF(STRUCT) = 所有字段槽位数之和
SIZE_OF(T[N]) = 1 + N × SIZE_OF(T)
运行时占用字节 = 槽位数 × sizeof(scrValue)数组多出的一个槽保存元素数量,嵌套数组的每一层都有自己的长度头。例如:
STRUCT AgentState
VECTOR position // 3 槽
INT health // 1 槽
BOOL active // 1 槽
ENDSTRUCT // 总计 5 槽
AgentState agents[10] // 1 + 10×5 = 51 槽该例在四字节槽目标为 204 字节,在八字节槽目标为 408 字节。这里是布局推导,不是编译器实测;不包含线程、资源对象及分配器额外开销。
SIZE_OF 返回槽数,不是 C++ sizeof 的字节数。跨语言 API 在边界转换,例如网络广播将 Size 乘 sizeof(scrValue);漏乘、重复乘或按固定四字节写接口都会产生兼容问题。[S4]
5. 字符串的三种不同成本#
- 字符串字面量放进程序字符串池,可以复用;不是每次执行都创建字符串对象。
- STRING 是借用式访问形态,不能从类型本身推导其指向数据的寿命。
- TEXT_LABEL 是可写的定长缓冲区,赋值、拼接和整数格式化按容量处理,调试路径可诊断过长,快速路径有截断行为。
TEXT_LABEL_15 在编译类型系统里是四槽;64 位目标的物理存储可能比命名暗示的 16 字节更大。C++ 桥接定义也按 sizeof(scrValue) 扩展。不能简单把这个历史名字视为所有平台一致的物理容量规范;应同时核对生成的指令长度、平台转换和调用端缓冲区。[S2][S5]
字符串处理以 char 字节为中心。当前证据没有提供完整 Unicode 字符索引、规范化或按字素截断规则。中文等多字节内容不应依照 TEXT_LABEL 后缀直接推定可存“字符数”。
6. 数组和引用不是动态堆#
普通数组大小参与编译期类型与布局。PROC F(INT &values[]) 的空维度表示参数可以借用不同长度的数组,实际长度由数组头保存;这不等于支持 new、resize、自动增长列表。固定长度数组实参通常要求匹配,开放长度引用在兼容性检查中有特殊规则。[S1]
数组访问有编译期常量下标检查和 VM 运行期边界检查。然而边界检查依赖数组长度头可信,Native 写入与裸引用仍是可信边界;这不能单独组成对恶意字节码的安全沙箱。
结构体按值传参会复制多个槽,引用传参则借用原区域。编译器没有展示通用所有权/借用寿命系统;将局部地址注册给长期使用的原生系统,必须遵守具体接口的生命周期契约。
7. 句柄子类型与抽象边界#
游戏定义 ENTITY_INDEX : ITEM_INDEX,VEHICLE_INDEX、PED_INDEX、OBJECT_INDEX : ENTITY_INDEX。[S6] 这允许实体 API 接受车辆或角色,防止在编译期明显混用类型。它没有赋予脚本对象方法、虚函数、用户类构造或内存继承布局。
“类型正确”和“对象仍然存在”是两个条件。对象销毁、流式卸载、网络所有权变化后,类型仍是 VEHICLE_INDEX,但对应引擎实体可能不能操作。新语言应考虑带世代编号的句柄、可空返回值和查询结果类型。
8. 对新语言的建议#
建议分离三种布局:VM 执行布局、Native ABI、网络/存档 schema;不要令平台指针宽度自动改变序列化协议。标量采用明确宽度,字符串采用明确编码,数组提供长度和容量语义,避免把可变参数签名当成动态容器。
对引用,引入只读借用、可写借用与是否跨帧保留的契约;对句柄,表达失效和所有权检查。保留编译期强类型与运行期紧凑表示的优点,同时建立加载验证器和 Native 参数验证。
源码证据#
- S1 — type.h:类型、默认值、数组兼容及大小;type.cpp:结构体、向量与 TEXT_LABEL 大小。
- S2 — value.h:槽位与值表示;thread.h:scrVector / scrTextLabel 桥接。
- S3 — node.cpp:数值提升与参数检查。
- S4 — 广播槽数转字节。
- S5 — TEXT_LABEL 快速执行路径。
- S6 — types.sch:游戏句柄层级。