Slang 序列化机制深度解析:AST/IR 模块的编码格式、RIFF 容器与跨版本兼容
2026/9/18 10:40:24 网站建设 项目流程

Slang 序列化机制深度解析:AST/IR 模块的编码格式、RIFF 容器与跨版本兼容

【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang

本篇技术指南围绕 Slang 编译器仓库中的序列化(Serialization)基础设施展开,系统讲解 AST 模块、IR 模块与容器三种序列化形态分别处理什么数据、采用何种编码格式,以及 round-trip(序列化后再次反序列化)如何保持一致。文章适用于想要为序列化增加新字段、调试反序列化失败,或从事 IR 模块跨版本稳定性工作的开发者;读完你将掌握从slang-serialize.h的通用 API 设计,到 Fossil 后端、RIFF 容器、源码位置编码,再到稳定指令名与版本兼容约束的完整知识链路。

What is serialized:三种序列化形态

序列化设施覆盖编译管线中的两类核心中间表示,以及将它们打包在一起的容器层,分别由独立源文件实现:

  • AST 模块:由 slang-serialize-ast.cpp(及其头文件 slang-serialize-ast.h)处理,负责把前端的抽象语法树(Decl 声明节点等)编码为字节流,并能在读取时"复活"(revitalize)出内存中的Decl*对象。
  • IR 模块:由 slang-serialize-ir.cpp 处理主体逻辑,配合 slang-serialize-ir-types.cpp(及 slang-serialize-ir-types.h)定义 IR 类型如何映射为可序列化表示。
  • 容器:由 slang-serialize-container.cpp 负责 RIFF 式分块打包,将 AST/IR 模块、入口点、目标程序、文件依赖等组织进一个容器文件(即.slang-module之类的产物)。

此外还有两个支撑文件: slang-serialize.cpp 与 slang-serialize.h 定义了贯穿全局的通用序列化 API,slang-serialize-types.cpp/slang-serialize-types.h 定义各模块共用的 FourCC 标识与数据结构(如SerialBinary常量表、ModuleChunkEntryPointChunk)。

从容器实现看(slang-serialize-container.cpp),一个FrontEndCompileRequest会被编码为一个 RIFF 列表块(FourCC 为SLmc),内部再为每个编译单元(translation unit)编码一个模块;若涉及代码生成(未开启-skip-codegen),还会追加目标程序数组块(arry)与入口点列表块(epts),每个入口点记录 mangled 名、入口名与 profile。

Backends:两套编码后端

序列化框架的核心设计是"读与写共用同一套serialize()函数",避免读写逻辑分叉导致不一致。目前仓库中存在两个编码后端:

通用序列化(Generic serialize)

定义在 slang-serialize.h 中,是面向框架使用者的抽象层。它以ISerializerImpl虚接口(slang-serialize.h)规定后端必须支持的原语操作:

  • 标量:handleBoolhandleInt8/16/32/64handleUInt8/16/32/64handleFloat32/64handleString
  • 容器:配对的begin/endArraybegin/endOptionalbegin/endDictionarybegin/endTuplebegin/endStructbegin/endVariant,以及读取侧的hasElements()和字段标识handleFieldKey(name, index)
  • 指针:handleUniquePtr(逻辑上等价 optional)与handleSharedPtr(写时对已出现过的指针只写额外引用,读时直接复用已读对象)、handleDeferredObjectContents(延迟序列化对象内容,用于打断对象图潜在的死循环递归)。

与后端交互时,客户端通过模板智能指针Serializer<Impl, Context>(slang-serialize.h)同时持有后端实现指针与上下文指针(上下文常用于提供工厂对象以支持去重/缓存式构造)。框架层面提供了大量serialize()重载(标量、List、定长数组、ShortListstd::optionalKeyValuePairDictionaryOrderedDictionary等),以及成对的 RAII 作用域类型与宏(如SLANG_SCOPED_SERIALIZER_STRUCT(serializer)),保证 begin/end 配对正确。

用户自定义类型只需实现一个读写共用的serialize()即可接入,例如:

template<typename S> void serialize(S const& serializer, MyThing& value) { SLANG_SCOPED_SERIALIZER_STRUCT(serializer); serialize(serializer, value.a); serialize(serializer, value.otherThings); serialize(serializer, value.object); }

只要被引用的成员类型(如OtherThingSomeObject)已经实现序列化支持,新类型即自动可序列化。

指针序列化是整个体系中最复杂的部分,代码采用分层定制点设计(slang-serialize.h):

serialize(s, T*&) → serializePtr → serializeSharedPtr / serializeUniquePtr → ISerializerImpl::handleSharedPtr / handleUniquePtr(回调) → serializeObject(读取时默认 new T(),可定制构造) → deferSerializeObjectContents(延迟调度) → serializeObjectContents(默认序列化 *value,可定制)

这套分层同时解决了三件事:多引用对象与对象图环(依赖共享指针的引用去重与延迟内容序列化)、多态类型(通过额外传参的serializePtr重载拦截整个类型层级)、以及需要工厂函数创建的对象。

Fossil 后端

slang-serialize-fossil.cpp 与 slang-serialize-fossil.h 实现名为 Fossil 的具体后端,是 AST 与 IR 序列化实际使用的编码器。从实现看:

  • 输出是一个 blob,顶层结构由**头部块(header chunk)根值块(root value chunk)**组成(slang-serialize-fossil.cpp);头部手工写入魔数Fossil::Header::kMagic、标志位、总 blob 大小(写完后回填)以及指向根值块的相对指针。
  • 每个值都带有类型标签FossilizedValKind(如BoolInt8Int32Ptr等),写侧SerialWriter::handleXxx会以_writeSimpleValue(kind, v)落盘,读侧SerialReader对称还原。
  • 类型映射通过SLANG_DECLARE_FOSSILIZED_TYPE(T, Fossilized_T)宏与FossilizedTypeTraits<T>特化完成;例如 IR 侧Fossilized_IRModule记录模块名、模块版本号与FlatInstTable(扁平化指令表)字段(slang-serialize-ir.cpp),AST 侧则定义了Fossilized_$T模板来为每个Decl子类自动生成"化石化"表示,并利用Fossil::ReadContext维护旧指针到新对象的映射(slang-serialize-ast.cpp)。
  • 枚举值经serializeEnum<RawType = Int32>以中间整数编码(slang-serialize.h),AST 侧显式声明"serializeEnum()将枚举编码为FossilUInt"(slang-serialize-ast.cpp)。

需要说明的是:该通用抽象与 Fossil 后端的关系是"概念契约"而非强制继承——ISerializerImpl在头文件注释中明确写明"具体后端不需要继承自该类型,它仅用于定义需求"(slang-serialize.h),这样序列化函数才能针对特定数据格式做静态特化以获得最佳性能。

RIFF 容器格式

序列化产物以 RIFF 风格的分块容器组织。RIFF 的读写实现位于 source/core/slang-riff.h 与 source/core/slang-riff.cpp(注意:虽然序列化文档常把该实现记为slang-serialize-riff.cpp,当前仓库中的实际路径是source/core/slang-riff.cpp)。

核心概念是FourCC(Four Character Code):一个 32 位值、由四个 ASCII 字符构成,充当"可扩展枚举",不同开发者可独立新增取值而几乎不会碰撞,同时可被高效比较或用于 switch(source/core/slang-riff.h)。容器由三种块组成:Chunk(基类)、DataChunk(携带载荷)、ListChunk(包含子块列表),块之间存在对齐与大小校验(BoundsCheckedChunkPtr在遍历子块时做边界检查,source/core/slang-riff.cpp)。

序列化容器(slang-serialize-types.h)使用如下关键 FourCC:

FourCC含义
SLmc顶层容器(container),对应SerialBinary::kContainerFourCc
SLml翻译单元/模块列表
SLst字符串表
epts/EPnt入口点列表 / 单个入口点
arry/dict/pair/obj通用 JSON 式结构:数组、字典、键值对、对象
i32/u32/i64/u64/f32/f64/str/true/false/null通用标量
ast模块中的 AST 块(PropertyKeys<Module>::ASTModule
ir模块中的 IR 块(PropertyKeys<IRModule>::IRModule
SHA1模块内容的 SHA1 摘要
fdep文件依赖列表
Sdeb/Sdst/Sdln/Sdal/Sdso调试与源码位置信息块(见下文)

模块编码上下文中(slang-serialize-container.cpp)还持有跨整个容器的字符串池(StringSlicePool)与可选的SerialSourceLocWriter,最终通过RIFF::Builder落盘。

Source-location 序列化

源码位置信息由 slang-serialize-source-loc.cpp 与 slang-serialize-source-loc.h 处理。其核心数据结构SerialSourceLocData定义了多种 FourCC 块:Sdeb(调试信息根块)、Sdst(调试字符串)、Sdln(行信息)、Sdal(调整后的行信息)、Sdso(源码信息)。

SourceRange以相对偏移(m_start/m_end)记录一段源码区间,读取时通过SourceView把相对位置映射回内存中的SourceLoc(slang-serialize-source-loc.h)。写入与否是可配置的:SerialContainerUtil::WriteOptions中的sourceManagerToUseWhenSerializingSourceLocs若为空,则产物不包含源码位置信息;若非空,则必须是生成输入模块中所有SourceLoc的那个SourceManager(slang-serialize-container.h)。读侧对应的ReadOptions则携带SourceManagerNamePoolSharedASTBuilderLinkage等用于重建环境的对象。

Versioning and backwards compatibility

序列化 IR 模块的跨版本兼容是平台级能力,权威设计文档为 docs/design/backwards-compat-for-ir-modules.md,此处给出要点。

双版本追踪

  • 模块版本(IRModule::m_version:语义化版本,表示 IR 指令集的版本,范围在k_minSupportedModuleVersionk_maxSupportedModuleVersion之间,随每个模块写入产物。
  • 序列化版本(IRModuleInfo::serializationVersion:表示序列化格式本身的版本,当前为 0,用于未来允许序列化结构自身演进。

另外每个模块记录创建它的编译器精确版本(SLANG_TAG_VERSION),为将来按版本做兼容处理预留依据。

稳定指令名机制

其核心是稳定指令名表source/slang/slang-ir-insts-stable-names.lua:这是一个由机器生成的lua表,把指令名映射为永久整数 ID,例如["nop"] = 0["Unrecognized"] = 1["Type.BasicType.Void"] = 2……ID 一旦分配永久有效、绝不复用,新增指令只会拿到新 ID。运行时通过getOpcodeStableName(IROp)把运行时 opcode 映射到稳定 ID,getStableNameOpcode(UInt)反向映射,未知 ID 一律映射为kIROp_Unrecognized,保证老模块遇到新编译器不认识的新指令时能安全降级。

slang-ir-insts.lua插入的硬约束

指令定义源文件是 source/slang/slang-ir-insts.lua,新增指令时只能在其末尾追加,绝不能在中间插入导致既有指令的相对顺序变化——因为稳定 ID 由check-ir-stable-names.lua依据指令顺序生成,只有尾插才能保证既有指令 ID 不漂移,老模块才能继续反序列化。

校验与查询工具

仓库提供了一整套配套工具:

  • extras/check-ir-stable-names.lua 加载slang-ir-insts.lua并与稳定名表对比,校验"所有指令都有稳定名、无重复 ID、稳定名表与当前指令构成双射";GitHub Actions 工作流会以 check 模式拦截不合规提交,或以 update 模式自动为新指令分配稳定 ID。
  • 命令行工具:slangc -get-module-info <module-file>不加载模块的情况下打印其名称、模块版本与创建它的编译器版本;slangc -get-supported-module-versions打印当前编译器支持的模块版本区间。
  • API:ISession::loadModuleInfoFromIRBlob允许程序化读取模块元数据而不做完整反序列化。

Round-trip 与 repro 机制

Round-trip(写后读)对称性是整个系统的重要设计目标:Serializer读/写共用同一serialize()函数,IR 侧递归遍历在写入与读取两个方向都使用同一套代码路径(traverseInstsInSerializationOrder,slang-serialize-ir.h),并且与 IR 特化的深度预算保持一致——kMaxIRSerializationDepth = 512对两个方向同时生效,保证往返对称。序列化遍历还开启一个轻微优化kReorderInstructionsForSerialization = true:模块指令下先递归处理普通指令,把 bool/int/float/ptr/void 字面量(以及 string/blob 字面量)挪到末尾,语义不变但让读取时的控制流更易预测(slang-serialize-ir.h)。

此外 slang-serialize-container.h 提供verifyIRSerialize(),可对一个IRModule做序列化-反序列化验证,直接用于确认 round-trip 正确性。

关于 repro(复现)机制,请以 CLAUDE.md 的说明为准(CLAUDE.md):slangc-dump-repro-dump-ast-dump-intermediates-serial-ir等并列被列为"不推荐使用"的选项;而-load-repro-extract-repro则被明确保留为专门的 repro 处理工具,其输入在使用前会经过校验。因此相关序列化产物若出现在受关注路径中,读者应知道存在这一机制,但新工作不应依赖-dump-repro

为类型增加序列化支持的实操路径

综合上述机制,在 Slang 仓库中为一种新类型接入序列化,遵循的路径是:

  1. 为类型实现template<typename S> void serialize(S const&, T&),内部用SLANG_SCOPED_SERIALIZER_STRUCT/ARRAY/DICTIONARY/...宏包裹字段序列化;
  2. 指针成员走默认的serializePtr → serializeSharedPtr链路即可获得引用去重与环支持;需要定制构造时,特化serializeObject(读取侧分配)并把其余成员放入deferSerializeObjectContents
  3. 若要写入容器产物,确认SerialContainerUtil::WriteOptions(是否携带源码位置信息)与ReadOptions(重建所需的SessionSourceManagerNamePool等)配置正确;
  4. 若涉及 IR 指令,在 source/slang/slang-ir-insts.lua 末尾追加定义,并运行 extras/check-ir-stable-names.lua 的 update 模式生成/更新稳定 ID;
  5. verifyIRSerialize()slangc -get-module-info验证往返结果与版本信息正确。

这套"读写共形 + 分层指针处理 + 稳定 ID + RIFF 打包"的设计,使 Slang 的模块产物既能高效编码复杂对象图,又能跨编译器版本稳定加载,是理解其缓存、增量编译与模块互操作能力的关键一环。

【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询