十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

Slang 序列化机制深度解析:AST/IR 模块的编码格式、RIFF 容器与跨版本兼容 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 定义了贯穿全局的通用序列化 APIslang-serialize-types.cpp/slang-serialize-types.h 定义各模块共用的 FourCC 标识与数据结构如SerialBinary常量表、ModuleChunk、EntryPointChunk。从容器实现看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规定后端必须支持的原语操作标量handleBool、handleInt8/16/32/64、handleUInt8/16/32/64、handleFloat32/64、handleString容器配对的begin/endArray、begin/endOptional、begin/endDictionary、begin/endTuple、begin/endStruct、begin/endVariant以及读取侧的hasElements()和字段标识handleFieldKey(name, index)指针handleUniquePtr逻辑上等价 optional与handleSharedPtr写时对已出现过的指针只写额外引用读时直接复用已读对象、handleDeferredObjectContents延迟序列化对象内容用于打断对象图潜在的死循环递归。与后端交互时客户端通过模板智能指针SerializerImpl, Contextslang-serialize.h同时持有后端实现指针与上下文指针上下文常用于提供工厂对象以支持去重/缓存式构造。框架层面提供了大量serialize()重载标量、List、定长数组、ShortList、std::optional、KeyValuePair、Dictionary、OrderedDictionary等以及成对的 RAII 作用域类型与宏如SLANG_SCOPED_SERIALIZER_STRUCT(serializer)保证 begin/end 配对正确。用户自定义类型只需实现一个读写共用的serialize()即可接入例如templatetypename 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); }只要被引用的成员类型如OtherThing、SomeObject已经实现序列化支持新类型即自动可序列化。指针序列化是整个体系中最复杂的部分代码采用分层定制点设计slang-serialize.hserialize(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如Bool、Int8、Int32、Ptr等写侧SerialWriter::handleXxx会以_writeSimpleValue(kind, v)落盘读侧SerialReader对称还原。类型映射通过SLANG_DECLARE_FOSSILIZED_TYPE(T, Fossilized_T)宏与FossilizedTypeTraitsT特化完成例如 IR 侧Fossilized_IRModule记录模块名、模块版本号与FlatInstTable扁平化指令表字段slang-serialize-ir.cppAST 侧则定义了Fossilized_$T模板来为每个Decl子类自动生成化石化表示并利用Fossil::ReadContext维护旧指针到新对象的映射slang-serialize-ast.cpp。枚举值经serializeEnumRawType Int32以中间整数编码slang-serialize.hAST 侧显式声明serializeEnum()将枚举编码为FossilUIntslang-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。核心概念是FourCCFour Character Code一个 32 位值、由四个 ASCII 字符构成充当可扩展枚举不同开发者可独立新增取值而几乎不会碰撞同时可被高效比较或用于 switchsource/core/slang-riff.h。容器由三种块组成Chunk基类、DataChunk携带载荷、ListChunk包含子块列表块之间存在对齐与大小校验BoundsCheckedChunkPtr在遍历子块时做边界检查source/core/slang-riff.cpp。序列化容器slang-serialize-types.h使用如下关键 FourCCFourCC含义SLmc顶层容器container对应SerialBinary::kContainerFourCcSLml翻译单元/模块列表SLst字符串表epts/EPnt入口点列表 / 单个入口点arry/dict/pair/obj通用 JSON 式结构数组、字典、键值对、对象i32/u32/i64/u64/f32/f64/str/true/false/null通用标量ast模块中的 AST 块PropertyKeysModule::ASTModuleir模块中的 IR 块PropertyKeysIRModule::IRModuleSHA1模块内容的 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把相对位置映射回内存中的SourceLocslang-serialize-source-loc.h。写入与否是可配置的SerialContainerUtil::WriteOptions中的sourceManagerToUseWhenSerializingSourceLocs若为空则产物不包含源码位置信息若非空则必须是生成输入模块中所有SourceLoc的那个SourceManagerslang-serialize-container.h。读侧对应的ReadOptions则携带SourceManager、NamePool、SharedASTBuilder、Linkage等用于重建环境的对象。Versioning and backwards compatibility序列化 IR 模块的跨版本兼容是平台级能力权威设计文档为 docs/design/backwards-compat-for-ir-modules.md此处给出要点。双版本追踪模块版本IRModule::m_version语义化版本表示 IR 指令集的版本范围在k_minSupportedModuleVersion与k_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 映射到稳定 IDgetStableNameOpcode(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打印当前编译器支持的模块版本区间。APIISession::loadModuleInfoFromIRBlob允许程序化读取模块元数据而不做完整反序列化。Round-trip 与 repro 机制Round-trip写后读对称性是整个系统的重要设计目标Serializer读/写共用同一serialize()函数IR 侧递归遍历在写入与读取两个方向都使用同一套代码路径traverseInstsInSerializationOrderslang-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.mdslangc的-dump-repro与-dump-ast、-dump-intermediates、-serial-ir等并列被列为不推荐使用的选项而-load-repro与-extract-repro则被明确保留为专门的 repro 处理工具其输入在使用前会经过校验。因此相关序列化产物若出现在受关注路径中读者应知道存在这一机制但新工作不应依赖-dump-repro。为类型增加序列化支持的实操路径综合上述机制在 Slang 仓库中为一种新类型接入序列化遵循的路径是为类型实现templatetypename S void serialize(S const, T)内部用SLANG_SCOPED_SERIALIZER_STRUCT/ARRAY/DICTIONARY/...宏包裹字段序列化指针成员走默认的serializePtr → serializeSharedPtr链路即可获得引用去重与环支持需要定制构造时特化serializeObject读取侧分配并把其余成员放入deferSerializeObjectContents若要写入容器产物确认SerialContainerUtil::WriteOptions是否携带源码位置信息与ReadOptions重建所需的Session、SourceManager、NamePool等配置正确若涉及 IR 指令在 source/slang/slang-ir-insts.lua 末尾追加定义并运行 extras/check-ir-stable-names.lua 的 update 模式生成/更新稳定 ID用verifyIRSerialize()或slangc -get-module-info验证往返结果与版本信息正确。这套读写共形 分层指针处理 稳定 ID RIFF 打包的设计使 Slang 的模块产物既能高效编码复杂对象图又能跨编译器版本稳定加载是理解其缓存、增量编译与模块互操作能力的关键一环。【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表