Slang 编译器开发实践指南:构建、测试、调试与工程规范(基于 CLAUDE.md)
【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang
CLAUDE.md 是 Slang 编译器仓库面向开发者(及 AI 编码助手)编写的核心工作手册,系统定义了构建系统用法、CMake 预设配置、测试运行方式、IR 调试工具链、编译器架构总览以及一套严格的"根因修复"工程方法论。阅读本文后,你将掌握如何从零配置 Slang 的构建环境并编译出slangc/slang-test、如何在不依赖 GPU 的情况下编写与运行测试、如何用 IR dump 与 InstTrace 追踪编译器内部问题,以及如何遵守该仓库在 ABI 兼容、PR 描述格式与代码评审上沉淀下来的工程规范。
仓库定位与构建系统
Slang(shader-slang/slang)是 Khronos 主导的 GPU 着色语言项目,主体以 C++ 实现,并配套一种自定义的 Slang 语言作为着色语言本身。CLAUDE.md 明确标注了项目的基本属性:
- 仓库:shader-slang/slang —— 面向 GPU 编程的着色语言
- 主要语言:C++ 与自定义 Slang 语言
平台化构建入口
在 Windows 沙箱环境中,推荐直接运行 extras/win-sandbox-build.bat。该脚本会自动发现 Visual Studio、执行vcvarsall.bat、使用vs2022-dev预设配置,并优先使用本地缓存的依赖而非联网拉取,默认构建slangc、slang-test与slangi三个目标,也可传入额外目标名覆盖默认集合。
在非 Windows 平台(Linux/macOS)上,直接用 CMake 构建。仓库根目录的 CMakePresets.json(版本 6,要求 CMake ≥ 3.25)定义了一套预设体系,其中default预设使用Ninja Multi-Config生成器,输出目录为${sourceDir}/build,并预设了Debug;Release;RelWithDebInfo;MinSizeRel四种配置:
# 使用默认设置配置(Ninja Multi-Config) cmake --preset default # Windows 上优先使用 VS2022 预设。 # -DSLANG_IGNORE_ABORT_MSG=ON:抑制无人值守/LLM 驱动构建中的模态中断对话框 # -DSLANG_EMBED_CORE_MODULE=OFF:将 core 模块编译与 C++ 编译解耦, # 使 *.meta.slang(如 hlsl.meta.slang)的错误不会中断 C++ 编译, # 而是由独立的 slang-bootstrap -compile-core-module 步骤报告。 cmake.exe --preset vs2022 -DSLANG_IGNORE_ABORT_MSG=ON -DSLANG_EMBED_CORE_MODULE=OFF # 构建 Release/Debug 二进制,耗时取决于机器,约 5 到 20 分钟 cmake --build --preset debug # Debug 二进制 cmake --build --preset release # Release 二进制 # 或者用 workflow 预设一步完成 configure + build cmake --workflow --preset debug # 构建特定目标 cmake --build --preset debug --target slangc cmake --build --preset debug --target slang-test两个值得注意的 CMake 选项:
- sccache:配置时传
-DSLANG_USE_SCCACHE=ON(或设置环境变量SLANG_USE_SCCACHE=1)即可把 sccache 用作编译器启动器以加速增量构建;它会自动禁用预编译头(二者存在已知不兼容),且要求sccache在 PATH 中。在 CMakeLists.txt 中可以确认该选项同时支持命令行开关与环境变量两种形式。 SLANG_IGNORE_ABORT_MSG:该 CMake 选项控制 Windows 上异常抛出后的行为,CLAUDE.md 建议 LLM 工作流下强烈开启,因为它在编译期就把行为固化进所有可执行文件。
对于自动化或 LLM 驱动的构建,CLAUDE.md 还给出了一条省 token 的技巧:首次构建把全部输出重定向到空设备,仅在失败时重跑并打印日志:
# 仅在初次尝试失败时才打印构建日志 cmake --build --preset debug >/dev/null 2>&1 || cmake --build --preset debug格式化与代码风格
提交前必须运行./extras/formatting.sh,PR 必须符合项目代码风格;用./extras/formatting.sh --check-only可以只校验而不修改文件。extras/formatting.sh 支持按语言过滤(--cpp、--yaml、--md、--sh、--cmake)、只格式化相对某个 revision 的变更(--since <rev>)、只处理工作区修改过的文件(--modified),并要求 Bash ≥ 3.2(兼容 macOS 自带的/bin/bash)。格式规则遵循.clang-format与.editorconfig:四空格缩进、Allman 大括号、100 列限制、左对齐指针、文件末尾换行。
抑制未使用变量警告的惯用写法
当if条件中声明的变量在条件体内不被使用(条件本身只起类型检查的副作用)时,应使用C++17 if-init-statement写法,而不是SLANG_UNUSED:
// 推荐:C++17 if-init 模式 if (auto foo = as<IRFoo>(inst); foo) { // foo 在体内用不到 —— 类型检查本身就是目的 } // 避免:在体内写 SLANG_UNUSED if (auto foo = as<IRFoo>(inst)) { SLANG_UNUSED(foo); }对于"被赋值但从不读取"的普通局部变量,则使用带注释说明理由的SLANG_UNUSED(var)。
问题解决方法论:走"原则之路"而非最小改动
CLAUDE.md 用相当篇幅规定了该仓库的调试哲学——遵循 principled path(原则性路径),而非 minimal-edit-distance path(最小编辑距离路径),目标是构建出"结构上正确、天然健壮"的表示,即使代价是更大的重构:
- 修根因,不修症状。一个出现在 emit/codegen 的 bug,根因通常在上游——某个 IR pass、类型合法化、特化、lowering,或 AST/IR 表示本身。要追到那一层去修。
- 质疑每一次改动。保留一个改动之前先回答:这个改动为什么必要?没有它哪个测试会挂?这是正确的修复,还是问题在暗示你的方向/表示本身有缺陷?说不出"没有它会失败的测试",这个改动大概率不该存在。
- 不要用补丁掩盖。用守卫、空检查或特判去糊弄一个畸形 AST/IR/witness-table,是掩盖表示层 bug 的创可贴;在正确输入下永远不会命中的守卫就是死代码。优先让表示本身正确,消费者保持简单。
- 审视输入形状。凡是处理某种输入形状(AST 节点、IR inst、witness、类型)的代码,都要问:这个输入形状本身是否正确、是否原则化?还是应该去修上游生产者?如果形状是错误或偶然的,修生产者;只有形状确实属于合法输入时才在本地处理。这个答案必须写进 PR 描述(见下文 Process report)。
- 正确的表示优先于编辑距离。如果两种表面形式本应等价,就应该用相同方式建模;如果消费者按位置/索引/身份读取概念上无序的 key→value 数据(如 witness-table / 接口需求条目),就应改成按角色/键访问。
- 全程维护工作日志。用一份临时 markdown 记录问题与动机示例、遇到的阻塞、问题如何级联(一个修复暴露下一个)、每个修复的选择与"为什么它原则化"(附具体代码追踪)、被否决的替代方案及原因。这份日志是 PR 描述的素材,但本身不进入提交。
对"不原则化改动"的自检
在完成一个非平凡的编译器改动之前,要审查 diff 中是否存在"在替糟糕的 AST/IR/Val/witness 表示买单"的迹象。以下模式在你能证明层次正确之前一律视为危险信号:
- 自定义语义等价:新写的对
DeclRef、Val、Type、Witness或 IR 形状的递归辅助函数(如are...Equivalent、does...Match、try...Match)往往意味着两套本不该并存的表示。先问:为什么substitute、resolve、getCanonicalType、equals或既有的规范化构造器不能直接让两个值相等? - 未经审计的辅助函数膨胀:每个新 helper、fallback、
try...函数都是审查对象。只为让一个失败测试通过、或重复实现了替换/解析/AST 拷贝/泛型求解/查找/lowering 一部分逻辑的 helper,往往就是藏不原则化修复的地方。 - 从语义到语法的重建:把已检查的语义数据(
Val、Type、DeclRef、witness、lowered IR 值)再翻译回语法(Expr、TypeExp、parser 形状的 AST)是强烈异味——已检查的语义字段才应该是 source of truth。 - 靠图遍历重新发现上下文:遍历任意操作数图、替换链、witness 链、查找路径或 IR users 来恢复泛型参数、需求键、规范路径或父声明,通常是下游补救。应在生产者侧直接存储或构造规范形式。
- 消费者侧打补丁:lowering、emit、特化、typeflow 与后端代码不应去修补早期阶段产生的畸形 AST/IR 形状;如果它们需要知道前端的偶然表示细节,应追溯并修复生产者。
- 硬编码的表示细节:针对特定
DeclRef子类、内建魔数类型名、泛型参数索引、witness-table 条目顺序、嵌套 vs 扁平特化形状的特判,需要强不变量支撑,且通常应放在规范化构造边界。 - 静默处理不可能形状:对超出契约的形状静默返回默认值会藏 bug。应断言不可能形状;只有能说清"为什么这是合法输入"时才处理。
自检从"可疑 helper 清单"开始:列出每个新 helper/fallback/特判、它覆盖的既有机制、没有它会失败的测试,以及它最终幸存/被回滚/被生产者侧修复取代。对每个被标记的改动执行审计:指出确切的输入形状与生产函数、判断形状是规范还是偶然、先尝试生产者侧修复、指明已存在的语义 source of truth、指名没有该改动会失败的测试,并做"revert drill"(删掉 helper,跑最小失败测试,用失败信息追踪真正的生产者-消费者断点)。不要仅仅因为某改动让测试通过就保留它;如果确有必要,PR 的 Process report 必须论证该输入形状为何合法、该层为何拥有这段逻辑,并给出从生产者到消费者的代码追踪。
代码风格与评审约定
这些是从反复出现的评审反馈中提炼的规则,遵守它们可以避免评审往返(它们管"代码如何读、如何组织",与上文方法论管"改什么"相区分):
- 函数注释写成完整句子:先 what,再 why。先说函数做什么;若存在理由不明显,再简短说明为什么。非平凡行为要附具体示例。例如
substituteElementOfCompositeType应写成"Returntargetwith its element type replaced bynewElementType, preserving shape: scalar → newElementType,vector<T,N>→vector<newElementType,N>,matrix<T,R,C>→matrix<newElementType,R,C>."而不是 "element coerce target"。 - 用对话式示例写注释与 PR 说明。解释微妙编译器路径时,先写 "Consider this example:" 再贴相关用户代码;不要用 "Full source shape"、"AST trace"、"IR trace" 之类的抽象标签代替解释。代码之后用自然语言逐步说明:哪个 parser、checker、copier、lowering pass 或 IR pass 创建了该形状,本地代码保持什么不变量,哪个下游消费者依赖它。
- 先复用再新写;把非平凡逻辑提取为具名、有文档的 helper。写新 helper 前先搜共享头文件——AST/IR helper 在
slang-ast-type.h、slang-ir-util.h及各个*-util.h里。例如判断某类型是否为特定声明的DeclRefType,应使用现成的isDeclRefTypeOf<T>(type)。真正新的逻辑不要埋进 inline lambda 或长内联块,要给出表达意图的名字(如coerceOperandsOfBuiltinBinaryExpr、substituteElementOfCompositeType、unifyBaseType)和文档注释。 - 保持单一 source of truth;重构后删除死代码。一个映射/分类只放在一处——例如"操作符名 → operation kind"的映射只存在于
getBuiltinOperationKindFromString,不在调用点重复实现。改动使某分支/fallback/helper 不可达后就删掉。 - 每个值只有一种规范表示;断言不变量。不要为已有表示的东西引入第二种 AST/IR/
Val表示——同一逻辑值的多种形态会破坏equals/恒等检查与去重。当不变量保证某些输入永不产出某表示时(如+/-/*永远是PolynomialIntVal,绝不会是BuiltinOperationIntVal),在构造点用SLANG_ASSERT断言,让违规当场暴露。 - 对超出契约的输入大声失败。helper 只对受限输入集有效时,对集合外的输入用
SLANG_RELEASE_ASSERT,而不是静默返回默认值——例如substituteElementOfCompositeType就断言其操作数必须是内建 scalar/vector/matrix。
PR 工作流与提交规范
- 格式化:提交前运行
./extras/formatting.sh; - PR 标签:默认用 "pr: non-breaking";ABI/语言破坏性改动用 "pr: breaking change";
- 附带测试:回归测试以
.slang文件放在tests/下; - PR 描述必须遵循五段式格式:
- Motivation—— 要解决的问题,附具体示例/动机测试用例;
- Proposed solution—— 方案与"为什么它是原则化选择";
- Change summary—— 触及的文件/区域表格或列表;
- Concepts and vocabulary—— 置于 change summary 与 process report 之间的短词汇表,只重述 codebase 特有或微妙的术语(如 witness /
getSub、facet /getInheritanceInfo、fixpoint solver),不解释interface、associated type 这类常识; - Process report—— 用逻辑理由解释每一处改动;对级联问题要给出动机测试用例与代码追踪(确切涉及的函数/inst),而不仅是描述;对任何处理、守卫或特判特定输入形状的改动,必须回答方法论中的"输入形状检查"——该形状是否正确原则化,还是应修生产者——让评审者能确认修复位于正确的层。
写作要求:面向脑中不持有完整上下文的评审者,采用与代码注释相同的对话式风格——从具体用户代码示例出发,贴完整相关片段而非只报类型/函数名,按顺序解释逻辑步骤,说明编译器构建了什么、该表示如何流经具名函数或 IR 指令、为什么选定的修复保持了不变量。
提交信息另有一条约定:不要在 commit message 中提及 Claude。配套的 AGENTS.md 补充了提交主题用短祈使句(如Reject invalid descriptor heap access)、PR 保持小颗粒且基于master、格式失败可运行./extras/install-git-hooks.sh安装的钩子或请求/format等细则。
测试体系
slang-test必须从仓库根目录运行:
# 多 server 并行跑全部测试(10 到 30 分钟) ./build/Release/bin/slang-test -use-test-server -server-count 8 # 运行单个测试(测试文件必须位于 tests/ 目录之下) ./build/Release/bin/slang-test tests/path/to/test.slang # 运行单元测试 ./build/Release/bin/slang-test slang-unit-test-tool/无 GPU 环境写测试
- 用 CPU compute:
//TEST:COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -output-using-type - 用解释器:
//TEST:INTERPRET(filecheck=CHECK): - 参考示例结构见 tests/language-feature/lambda/lambda-0.slang,其中同一用例同时声明了
-vk与-cpu两条COMPARE_COMPUTE指令,正好演示了"一个测试在 GPU 与 CPU 双路径上验证"的写法:
//TEST:COMPARE_COMPUTE(filecheck-buffer=CHECK):-vk -output-using-type //TEST:COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -output-using-type诊断测试
诊断系统本身是 Lua 驱动的:诊断定义在source/slang/slang-diagnostics.lua,构建期生成 C++ 结构,完整文档见 docs/diagnostics.md。测试中使用//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK):指令验证编译器发出预期诊断;注释里的注解按消息文本、严重度或错误码与编译器输出匹配,^符号对齐到前一行源码的列:
//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK):-target spirv int foo = undefined; //CHECK: E01234 //CHECK: ^^^^^^^^^ errorSPIR-V 校验
- 使用
slangc -target spirv时设置SLANG_RUN_SPIRV_VALIDATION=1启用静态校验; - 不要使用系统自带的
spirv-val工具(可能过期),应使用仓库集成的校验路径。
slangc 命令行约定(重要)
Slang 使用单破折号表示多字符选项(不同于大多数工具的--):
- 用
-help(不是--help) - 用
-target spirv(不是--target spirv) - 用
-dump-ir(不是--dump-ir) - 用
-stage compute(不是--stage compute)
同时 CLAUDE.md 明确要求避免以下无人维护、不可靠或不必要的调试选项:slangc 的-dump-ast、-dump-intermediate-prefix、-dump-intermediates、-dump-ir-ids、-serial-ir、-dump-repro,以及 slang-test 的-category与-api。
-load-repro与-extract-repro是专门用于 repro 处理的工具,仅在处理 repro 机制时使用,输入在使用前会经过校验。
调试工具箱
IR Dump(-dump-ir)
# 在每个 pass 处 dump IR(配合 -target 与 -o,避免输出混杂) slangc -dump-ir -target spirv-asm -o tmp.spv test.slang | python extras/split-ir-dump.py # 只 dump 某个 pass 前后 slangc -dump-ir-before lowerGenerics -dump-ir-after lowerGenerics -target spirv-asm -o tmp.spv test.slang > pass.dump- 始终将
-dump-ir与-target(否则编译提前停止)和-o <file>(否则目标代码与 IR 混在 stdout)组合使用; - 大 dump 用 extras/split-ir-dump.py 拆分成每 pass 一个文件,工具说明见 extras/split-ir-dump.md;
- 也可以在 C++ 代码中插入
dumpIRToString(),用File::writeAllText()写文件做临时检查; - 调试时聚焦 IR pass(特化、内联、类型合法化、buffer lowering)中的根因,而不是在 emit 逻辑里打补丁。编译器的哲学是保持 emit 简单,把重变换都放在 IR pass 里。
InstTrace:追踪 IR 指令的来源
给定一个出问题的 IR 指令的 debugUID,可以追踪它是被哪个 pass 创建的:
python3 ./extras/insttrace.py <debugUID> ./build/Debug/bin/slangc tests/my-test.slang -target spirv脚本为 extras/insttrace.py。
SPIRV 相关工具
slangc -target spirv-asm:编译到 SPIR-V 汇编;SLANG_RUN_SPIRV_VALIDATION=1开启静态校验;校验失败时加-skip-spirv-validation仍可看到 SPIR-V 输出;slangc -target spirv-asm -emit-spirv-via-glsl:经 GLSL 生成参考 SPIR-V 用于对比。
断言行为(SLANG_ASSERT)
Windows 上断言失败默认弹出阻塞执行的模态对话框。用环境变量SLANG_ASSERT控制:
| 取值 | 行为 |
|---|---|
system | 使用系统assert(),弹出模态对话框并允许附加调试器 |
debugbreak | 调试器已附加时触发 debug-break;未附加时回退到system行为 |
release-assert-only | 跳过 debug-only 断言(SLANG_ASSERT、SLANG_ASSERT_FAILURE)继续执行;SLANG_RELEASE_ASSERT仍然触发 |
| 未设置 | 抛出异常 |
Windows 上异常抛出后的行为由 CMake 选项SLANG_IGNORE_ABORT_MSG控制,对 LLM 无人值守工作流强烈推荐(该选项在编译期把行为固化进所有构建产物,见 CMakeLists.txt)。
架构总览
编译器管线核心组件
- Lexer(source/compiler-core/slang-lexer.cpp):词法分析
- Preprocessor(source/slang/slang-preprocessor.cpp):处理
#include、宏、条件编译 - Parser(source/slang/slang-parser.cpp):递归下降解析,产出 AST
- Semantic Checker(source/slang/slang-check.cpp):类型检查、名字解析、语义验证
- IR Generation(source/slang/slang-lower-to-ir.cpp):AST 到 Slang IR 的转换
- IR Passes(
source/slang/slang-ir-*.cpp):优化与 lowering - Code Emission(
source/slang/slang-emit-*.cpp):目标相关的代码生成
关键目录
| 目录 | 职责 |
|---|---|
source/core/ | 核心工具(字符串、容器、文件系统、平台抽象) |
source/compiler-core/ | 编译器基础设施(诊断、下游编译器集成) |
source/slang/ | 主编译器实现(前端、IR、后端) |
source/slangc/ | 命令行编译器工具 |
source/slang-core-module/、source/slang-glsl-module/、source/standard-modules/ | 标准库模块 |
source/slang-wasm/ | WebAssembly 绑定 |
source/slang-record-replay/ | API 调用录制/回放 |
source/slang-rt/ | 运行时库 |
tools/ | 开发与测试工具 |
include/ | 公共 API 头文件(slang.h) |
external/ | 第三方依赖与子模块 |
prelude/ | 内建语言定义与标准库 |
tests/ | 综合测试套件 |
docs/ | 项目文档(用户指南在docs/user-guide/) |
build/source/slang/fiddle/ | FIDDLE 宏在构建期生成的代码 |
编译模型的关键概念
- CompileRequest:捆绑选项、输入文件与代码生成请求;
- TranslationUnit:源文件集合(HLSL 每文件一个,Slang 全部文件合并);
- EntryPoint:函数名 + 要编译的管线阶段;
- Target:输出格式(DXIL、SPIR-V 等)+ 能力配置。
支持的目标:Direct3D 11/12(HLSL 输出)、Vulkan(SPIR-V、GLSL 输出)、Metal(MSL,实验性)、WebGPU(WGSL,实验性)、CUDA/OptiX(C++ 输出)、CPU(C++ 输出,可执行文件/库)。
IR 系统与生成代码
- Slang 使用自研的 SSA IR(不是 LLVM);IR 指令在
slang-ir-insts.h中定义,该头文件由 Lua 生成(定义在 source/slang/slang-ir-insts.lua); - 有完善的 IR pass 框架用于优化与 lowering,代码生成前执行目标相关的合法化 pass;
- 以
kIROp_开头的枚举值定义在生成文件build/source/slang/fiddle/slang-ir-insts-enum.h.fiddle中; - AST 节点声明中的
FIDDLE()语句表示会从build/source/slang/fiddle生成并包含额外源码,提供静态类型系统/反射元数据、visitor 支持与序列化支持。
此外还有:Language Server(source/slang/slang-language-server.cpp,支持 IntelliSense、补全、诊断、格式化,供 VS Code/Visual Studio 扩展使用);模块系统(支持单独编译、模块可编译为 IR 并在运行时链接、可对分发的模块做可选混淆、核心语言特性以prelude/下的模块形式定义)。
常见开发任务
添加新语言特性的六步流程
- 为新 token 更新 lexer(
source/compiler-core/slang-lexer.cpp) - 扩展 parser(
source/slang/slang-parser.cpp) - 添加语义分析(
source/slang/slang-check-*.cpp) - 实现 IR 生成(
source/slang/slang-ir-*.cpp) - 为每个目标后端添加代码生成(
source/slang/slang-emit-*.cpp) - 在
tests/下编写完备测试
其他常见任务:
- 新增 IR 指令:更新
source/slang/slang-ir-insts.lua定义文件后重新生成; - 新增内建函数:加到
prelude/中相应模块; - 新增目标:在
source/slang/slang-emit-*.cpp中实现新 emitter。
Capability Atoms 文档生成(勿手改)
docs/user-guide/a4-02-reference-capability-atoms.md 是自动生成的,永远不要直接编辑。它由slang-capability-generator(源码在 tools/slang-capability-generator)从 source/slang/slang-capabilities.capdef 产生。更新某个 capability atom 的描述:
在
slang-capabilities.capdef中def或alias前紧邻处添加/更新///文档注释:/// My description here. alias myatom = ...;重新生成文档:
cmake --build --preset debug --target slang-capability-generator mkdir -p build/capgen-out ./build/generators/Debug/bin/slang-capability-generator \ source/slang/slang-capabilities.capdef \ --target-directory build/capgen-out \ --doc docs/user-guide/a4-02-reference-capability-atoms.md把更新后的
slang-capabilities.capdef与重新生成的.md一起提交。
注意:///注释必须写在公共 alias上(如alias node = _node;),而不是内部def _node : stage;atom 上,否则描述不会出现在公共名字下。
修改公共头文件(include/):ABI 兼容性铁律
include/下所有文件都是公共 API,改动必须对"针对旧版头文件编译的调用方"保持二进制(ABI)与源码兼容。
枚举:
- 绝不能在现有枚举中部插入新成员——插入会移动其后所有整数值,静默破坏任何存储或比较该值的调用方;
- 始终追加到终止计数/哨兵成员(如
CountOf、Count、NUM_*)之前,并赋显式整数值(前一成员之后的下一个连续整数); - 删除成员:改名为
REMOVED_<Name>并保留原整数值,永不复用已退役的整数。
虚表(COM 接口):Slang 的公共接口(ISession、IModule、IComponentType等)是在 include/slang.h 中以virtual方法声明的 COM 风格虚表,虚表布局由声明顺序固定。违反以下规则会损坏虚表,导致针对旧头文件编译的调用方静默崩溃或派发错方法:
- 绝不重排接口内的虚方法;
- 绝不改变虚方法签名(返回类型、参数类型、调用约定或
SLANG_MCALL修饰); - 绝不在接口中部插入新虚方法——只允许在接口末尾、右花括号之前追加;
- 绝不删除虚方法——把方法体替换为返回
SLANG_E_NOT_IMPLEMENTED的 stub,声明保留原位; - 若客户端可能按 UUID 实现或查询某接口,避免就地扩展现有公共 COM 接口;优先新增带自己 UUID 的派生/版本化接口,同时保持原接口声明与 UUID 继续受支持。
修改 core 模块源(hlsl.meta.slang/core.meta.slang)后的重建
core 模块源码(source/slang/hlsl.meta.slang、core.meta.slang等)在编译期被嵌入slang-bootstrap二进制。修改这些文件后,需要让 CMake 感知新的时间戳、经构建图重新生成 core-module 头文件、再重编slangc:
cmake -E touch source/slang/hlsl.meta.slang # 或实际改动的 meta 文件 cmake --build --preset <preset> --target generate_core_module_headers cmake --build --preset <preset> --target slangc<preset>与你实际使用的构建一致(debug、release、releaseWithDebugInfo等)。从 source/slang-core-module/CMakeLists.txt 与 source/slang/CMakeLists.txt 的构建图可以看到:generate_core_module会调用对应宿主/配置的slang-bootstrap完成引导编译,generate_core_module_headers依赖它,而slangc又REQUIRES generate_core_module_cache(一个 ALL 目标)——所以即使只构建--target slangc也会触发该步骤。若跳过cmake -E touch这一步,缓存中的 bootstrap 二进制可能静默嵌入旧源码,bootstrap 步骤的诊断与当前源文件对不上,即是二进制过期的明确信号。
HLSL 命名常量输出规则
绝不把 HLSL 枚举/命名常量的值以硬编码整数输出。DXC 在解析期映射命名常量(属性字符串、标志标识符等);一旦把数字烘焙进生成的 HLSL,而 DXC 日后内部映射发生变化,输出会静默失效。正确模式:
- 为每组概念值定义一个 Slang 枚举(或一组由内建支撑的命名常量,如
NodeLaunch模式、Barrier 标志集); - IR 中存名字而非整数:使用
IRStringLit操作数(如NodeLaunchDecoration的做法),或保留标识符到输出阶段的Ref<T>/内建访问器; - 在 HLSL emitter(
slang-emit-hlsl.cpp或slang-emit-c-like.cpp)中提供映射函数,把存储的枚举/字符串值转回 HLSL 源名,使输出形如[NodeLaunch("broadcasting")]而非[NodeLaunch(0)]。
代码库中已有该模式的实例:NodeLaunchDecoration以IRStringLit("broadcasting")存储模式、emitter 原样重发字符串;work-graph 输出记录的Get()返回由__intrinsic_asm ".Get"支撑的Ref<T>,使输出 HLSL 得到.Get(i)(HLSL 中的 l-value)而非整数偏移。
若一个 Slang 枚举必须以命名常量而非整数形式输出(如UAV_MEMORY而不是1),按如下五步:
把 C++ 枚举定义在
slang-type-system-shared.h中(namespace Slang内,用普通enum而非enum class,使值可隐式转int;该头被 core-module 源码与 emitter 共同包含);在相应
*.meta.slang中镜像为 Slang 枚举,用$(...)拼接从 C++ 取值,保持两边同步:enum MyFlags : uint { FlagA = $(MyFlags::FlagA), FlagB = $(MyFlags::FlagB) }在
.meta.slang中声明__intrinsic_op转换器,在 IR 中表示"枚举到字符串"的转换——传给__intrinsic_op(...)的助记符必须与第 4 步 Lua 键完全一致:__intrinsic_op(getEnumMyFlags) int GetEnumMyFlags(MyFlags f);在 source/slang/slang-ir-insts.lua 注册新 IR op,并在
slang-ir-insts-stable-names.lua添加稳定 ID;在目标 emitter(如
slang-emit-hlsl.cpp的tryEmitInstExprImpl)中输出命名常量字符串:IR 操作保持绑定到符号化枚举或内建值,把每个被接受的位/值映射到其 HLSL 命名常量字符串后用m_writer->emit(...)写出。不要实现或记录"从原始整数位置反推 HLSL 源名"的方案。
跨平台注意事项
- 支持平台:Windows(x64/ARM64)、Linux(x64/ARM64)、macOS(x64/ARM64)、WebAssembly;
- 平台抽象:文件系统、进程管理、平台检测统一使用
source/core/中的工具; - 图形 API:代码生成支持所有主流 API,但运行时测试需要相应驱动/SDK;
- WSL on Windows:在 WSL 环境下运行,尽量给可执行文件追加
.exe以避免误用 Linux 二进制——用cmake.exe而非cmake、python.exe而非python、gh.exe而非gh。配套的 AGENTS.md 进一步细化:WSL 下默认使用 Windows 原生工具(git.exe等,因为 WSL Git 可能损坏 worktree 状态),跨文件系统传路径用wslpath -w/wslpath -u转换;原生 Linux/macOS 上则直接使用无.exe后缀的平台工具。
延伸阅读
- 用户面向文档:
docs/user-guide/(语言规范见仓库外部 spec 仓库,按 CLAUDE.md 指示克隆到external/后位于external/spec/specification/,特性提案在external/spec/proposals/) - 诊断系统完整说明:docs/diagnostics.md
- 代码规范:docs/design/coding-conventions.md
- 通用构建参考:docs/building.md
- 编译器 IR 设计:docs/design/ir.md
CLAUDE.md 的价值不仅在于罗列命令,更在于它把一套"表示层正确性优先"的编译器工程文化——从 C++17 if-init 的小技巧到 PR 五段式描述、从 ABI 铁律到"revert drill"式自检——沉淀为可执行的规则。对任何参与 Slang 编译器开发或希望为大型 C++ 编译器仓库配置 AI 协作规范的团队,这份文档都是高参考价值的范本。
【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考