Slang 编译器诊断系统深入解析:DiagnosticSink、Lua 驱动诊断定义与富诊断渲染
2026/9/18 8:58:38 网站建设 项目流程

Slang 编译器诊断系统深入解析:DiagnosticSink、Lua 驱动诊断定义与富诊断渲染

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

本文聚焦 Shader 编译器 Slang 内部贯穿所有编译管线阶段的诊断(Diagnostics)系统:从中央接收接口DiagnosticSink,到以 Lua 表声明诊断、构建期生成 C++ 枚举与消息表的完整链路,再到严重级别、源码位置渲染、错误码命名空间与内部编译器错误(ICE)处理。读者读完可掌握"如何新增一条诊断"、"如何把 Slang 集成进消费其诊断的工具链"以及"如何定制错误格式化"所需的全部知识。

DiagnosticSink:所有管线阶段共用的诊断中枢

Slang 编译器从词法/预处理、解析、语义检查、AST 到 IR 的降级,直到最终代码生成,每个阶段都可能产生错误(error)、警告(warning)与附注(note)。这些信息统一汇入一个中心接口DiagnosticSink,其声明位于 source/compiler-core/slang-diagnostic-sink.h。

子系统的 sink 获取与贯穿方式

从源码结构看,各编译子系统通过以下方式获得 sink:

  • 编译请求(编译入口)持有自己的 sink,贯穿到解析器、语义检查器、IR 优化等各级对象;
  • sink 之间可以通过setParentSink()/getParentSink()(slang-diagnostic-sink.h)建立父子关系,子 sink 会把格式化后的诊断转发给父 sink;
  • 新创建的 sink 可以从父 sink 拷贝显示与设置状态(flags、颜色模式、Unicode、已启用的警告分组、严重级别覆盖表),见构造函数 slang-diagnostic-sink.h。

sink 的输出有两种去向:若设置了ISlangWriter* writer,诊断写入该 writer;否则写入内部的StringBuilder outputBuffer(slang-diagnostic-sink.h)。工具可通过getBlobIfNeeded()把累积的诊断取为 blob,getErrorCount()则随时给出当前累计错误数,编译流程常用它做提前退出或最终成败判定。

diagnose 模板与参数打印

sink->diagnose(pos, info, args...)是核心入口(模板版本见 slang-diagnostic-sink.h)。它把参数打包成DiagnosticArg数组,并依据是否设置AlwaysGenerateRichDiagnostics标志分流到旧式diagnoseImpl或富诊断diagnoseRichImpl。参数通过一组printDiagnosticArg重载完成格式化,覆盖int32/uint32/int64/uint64/doubleStringUnownedStringSliceName*TokenTypeTokenIRInst*Modifier*等类型(slang-diagnostic-sink.h),因此诊断消息里可以安全地嵌入各类编译器对象。

此外,diagnoseRaw(severity, message)用于直接以纯文本追加诊断(典型场景是转发下游编译器报错),diagnoseWithoutSourceView()则用于给已有诊断追加 note 时避免重复打印同一行源码。

行为控制 Flags

sink->setFlag/resetFlag/isFlagSet控制的Flags位(slang-diagnostic-sink.h)包括:

Flag作用
VerbosePath显示更详细(规范/绝对)路径
SourceLocationLine有源码时显示定位行
HumaneLoc显示 file/line 形式的人类可读位置
TreatWarningsAsErrors把(覆盖后的)警告提升为错误
LanguageServer以适合语言服务器的格式输出
AlwaysGenerateRichDiagnostics强制旧式诊断走新式富诊断路径
MachineReadableDiagnostics以机器可读 TSV 格式输出

另有若干渲染选项:setSourceLineMaxLength(默认 120 字符,0 表示不限)、setDiagnosticColorModeSLANG_DIAGNOSTIC_COLOR_AUTO/ALWAYS/NEVER,AUTO 时依据 writer 是否控制台决定是否着色)、setEnableUnicode(未显式设置时按控制台输出自动探测)。

诊断定义:Lua 表驱动的声明式体系

Slang 的全部诊断集中声明在 Lua 文件中,构建期经代码生成转为 C++ 结构。主文件是 source/slang/slang-diagnostics.lua(约 6000+ 行),按错误码段组织(0xxxx命令行/宿主平台、15xxx预处理、2xxxx解析、3xxxx语义等);另有独立子文件,如类型错误集中放在 source/slang/diagnostics/type-errors.lua。

表结构(schema)

每条诊断在 Lua 中用辅助函数声明,核心字段为:

  • name:唯一小驼峰/连字符名称,如cannot-open-filefunction-redefinition
  • code(id):整数错误码,构建期映射为 C++ 枚举值;
  • severityerrorwarningnoteinternalfatal之一;
  • message:消息模板,其中~param为插值参数。

以 slang-diagnostics.lua 头部注释中的示例为准:

err( "function return type mismatch", 30007, "expression type ~expression.type does not match function's return type ~returnType:Type", span({loc = "expression:Expr", message = "expression type"}), span({loc = "function:Decl", message = "function return type"}) )

插值语法有三类:

  • ~param:默认 String 参数;
  • ~param:Type:带类型的参数(TypeDeclExprStmtValNameint等);
  • ~param.member:成员访问,如~expression.type会自动生成expression->typeDecl直接插值时自动使用.name

位置参数语法为"location"(普通SourceLoc变量)或"location:Type"(类型化位置,如Decl->getNameLoc()Expr->loc),类型化位置也可作为插值参数复用。

辅助函数:span、note 与变长结构

  • span(loc, message?):标记一个主/次跨度,message可选,默认为空;
  • note(message, span...):附注,必须至少带一个 span,第一个为主跨度、其余为次跨度,不可嵌套;
  • variadic_span(struct_name, loc, message):生成嵌套结构体与List<Error> errors形式的变长跨度列表(见"multiple type errors"示例);
  • variadic_note(struct_name, message, span...):变长附注,如候选列表List<Candidate> candidates
  • standalone_note:无主诊断的独立 note(如编译耗时提示)。

位置函数err(name, code, message, [primary_span], ...)warning(...)的 primary_span 是可选的——无源码位置的诊断(如命令行参数错误)可以不挂跨度。

构建期代码生成

整个链路记录在 slang-diagnostics.lua 头部注释:

  1. slang-diagnostics.lua 用err()/warning()等辅助函数定义诊断;
  2. source/slang/slang-diagnostics-helpers.lua 处理这些定义:先提取span()/note()中的位置,再从~插值提取参数(自动去重),成员访问按位置类型解析;
  3. slang-rich-diagnostics.h.lua 加载处理结果,FIDDLE 模板在 slang-rich-diagnostics.h 中生成每个诊断的 C++ 结构体(直接参数成为成员变量,位置成为SourceLoc或类型化指针),并在slang-rich-diagnostics.cpp中生成toGenericDiagnostic():展开成员访问、插值参数拼装消息、设置主跨度/次跨度与附注。

消费侧头文件是 source/slang/slang-diagnostics.h,它明确注释"所有诊断现在都由slang-diagnostics.lua定义,并经slang-rich-diagnostics.h生成,旧的slang-diagnostic-defs.h已移除"。该头文件还导出findDiagnosticByName()getDiagnosticsLookup()overrideDiagnostic(s),供按名称查询和覆盖诊断严重级别使用。运行时查找由DiagnosticsLookup承担(slang-diagnostic-sink.h):按精确/宽松名称查找、按 id 查找(允许同 id 多条,返回先加入者)、addAlias别名注册,并用MemoryArena管理名称生命周期。

新一代声明式语法

source/slang/diagnostics/type-errors.lua 展示了一种更结构化的声明形式("guinea pig"原型诊断),以diagnostic "..."块描述完整结构:

diagnostic "argument_type_mismatch" { code = "E30019", severity = "error", flag = "type-mismatch", message = "cannot convert argument of type `{found}` to parameter of type `{expected}`", params = { { name = "func_name", type = "String" }, { name = "param_name", type = "String" }, { name = "param_index", type = "int" }, { name = "expected", type = "Type" }, { name = "found", type = "Type" }, }, primary_label = { loc = "arg_loc", message = "expected `{expected}`, found `{found}`", }, secondary_labels = { { loc = "param_loc", message = "parameter `{param_name}` declared as `{expected}` here" }, { loc = "func_loc", message = "in call to function `{func_name}`" }, }, notes = { "no implicit conversion exists from `{found}` to `{expected}`" }, helps = { "add explicit cast: `({expected}){found_expr}`" }, }

与旧式err(...)相比,它显式区分主标签、次标签、notes 与 helps,语义更接近"面向 IDE 与 LLM 消费"的富诊断模型,也印证了富诊断的内部表示(GenericDiagnostictoGenericDiagnostic()getInfo(),见 slang-rich-diagnostics.h)。

严重级别

诊断系统的严重级别在Severity枚举中定义(slang-diagnostic-sink.h),并通过static_assert与公开 API 的SLANG_SEVERITY_*常量保持同步(slang-diagnostic-sink.h):

级别名称(getSeverityName输出)语义
Disableignored被禁用/覆盖掉的诊断
Notenote附注,提供上下文
Warningwarning可编译但有隐患
Errorerror常规编译错误
Fatalfatal error致命错误,通常直接终止
Internalinternal error内部编译器错误(ICE)

Lua 侧与之对应:helper 的add_diagnostic校验 severity 只能是errorwarningnoteinternalfatal之一(slang-diagnostics-helpers.lua),并提供internal()/fatal()便捷函数;负数或特殊码(如-199999internal-compiler-error catch-all)是内部哨兵,不参与编号校验。

警告分组与覆盖

警告还带有WarningLevel分组(仿 clang/gcc 的-Wall/-Wextra/-Wpedantic),分组相互独立、非嵌套(slang-diagnostic-sink.h):

  • Default:始终输出,无需开启;
  • Extra:默认开启(m_enabledWarningLevels初始只置该位,slang-diagnostic-sink.h);
  • AllPedantic:默认关闭,需显式enableWarningLevel()开启。

enableWarningLevel()对位索引做边界检查防止越界移位(slang-diagnostic-sink.h);overrideDiagnosticSeverity(id, severity, info)支持按诊断 id 单独覆盖(例如-Wno-xxx抑制、-werror提升),覆盖表m_severityOverrides可按get/setSeverityOverrides在父子 sink 间拷贝。

源码位置与消息渲染

诊断必须能定位到file:line:column。Slang 用SourceLoc表示抽象源码位置(声明于 source/compiler-core/slang-source-loc.h),由SourceManager负责映射:SourceManager::getHumaneLoc(loc, type)返回HumaneSourceLoc(slang-source-loc.h),把名义位置(SourceLocType::Nominal)解析为文件路径、行、列。

sink通过setSourceManager()绑定SourceManager(slang-diagnostic-sink.h),渲染时结合SourceLocationLexer(可选回调,用于对定位行的 token 做词法高亮,未设置时只在SourceLoc处显示单字符 caret)与SourceLineMaxLength等选项输出类似 clang 风格的行内定位。设置Flag::SourceLocationLine会显示带 caret 的源码行;Flag::HumaneLoc控制是否显示 file/line 人类可读位置。

富诊断是更完整的渲染层,声明于 source/slang/slang-rich-diagnostics.h:每个生成的诊断结构体通过toGenericDiagnostic()转成GenericDiagnostic,携带主跨度、次跨度、notes、helps 与消息;sink->diagnoseRichImpl()依据覆盖表与位置做最终渲染,并支持颜色(SlangDiagnosticColor)、Unicode(shouldEnableUnicode()setEnableUnicode())、源行长度上限等选项。SLANG_INTERNAL_ERROR/SLANG_UNIMPLEMENTED/SLANG_DIAGNOSE_UNEXPECTED宏(slang-diagnostics.h)直接调用富诊断:debug 构建下先在诊断前追加note: internal error triggered at 文件:行的原始 note(因为主诊断可能中止编译),再输出Diagnostics::InternalCompilerError等富诊断对象。

错误码命名空间

诊断 id 的来源即 slang-diagnostics.lua 各条目的code字段,构建期生成 C++ 枚举(Diagnostics::*)与消息表,同一 id 可被DiagnosticsLookup::getDiagnosticById反查。代码按段分配命名空间:0xxxx命令行与宿主平台交互、15xxx预处理、2xxxx解析、3xxxx语义/类型,子段内再细分(如150xx条件、151xx指令解析、153xxinclude、154xx宏定义、155xx宏展开、156xxpragma)。

关于诊断的书写规范与文档化约定,仓库提供了 docs/diagnostic-guidelines.md:完整诊断采用error[E00000]: 主消息+--> file.slang:LL:CC+ 源码片段 + 主/次标签 += note/= help的 Rust/Clang 风格结构;错误码建议字母前缀加 5 位数字(如E0308W00001);消息要求小写开头、无句末标点、代码元素用反引号、主动语态与牛津逗号;并提供-error-format=json-show-error-codes-explain E00001-max-errors=N-show-type-aliases=always|helpful|never-color=auto|always|never-verbose-diagnostics等命令行选项说明,以及错误级联抑制、优先级排序(语法错误 > 模块错误 > 类型定义 > 接口实现 > 类型不匹配 > 其他语义 > 警告 > remark)与 IDE/LSP 集成(DiagnosticRelatedInformationCodeAction、severity 映射)的指导。

内部编译器错误(ICE)

编译器自身的断言失败会以Severity::Internalinternal error)上报,最终落到 catch-all 诊断internal-compiler-error(Lua 侧码99999)。

断言宏与 sink 的交互

核心断言宏定义在 source/core/slang-common.h:

  • SLANG_ASSERT(VALUE):debug 构建(_DEBUG)下调用::Slang::handleAssert(#VALUE, __FILE__, __LINE__, false);release 构建下退化为SLANG_ASSUME(VALUE)(对编译器提示假设,不执行检查);
  • SLANG_RELEASE_ASSERT(VALUE):任何构建下都调用handleAssert(..., true)

SLANG_ASSERT_FAILURE(msg)宏则定义在 source/core/slang-signal.h,同样转发到handleAssert

handleAssert的实现位于 source/core/slang-signal.cpp,其关键行为:

  • 读取环境变量SLANG_ASSERTgetenv("SLANG_ASSERT"),见 slang-signal.cpp)来调节行为;读取时刻意不构建StringBuilder,避免断言输出路径内部再次触发断言导致重入;
  • 依据SLANG_ASSERT的值决定是触发调试器断点、打印失败消息还是直接exit(-1)(slang-signal.cpp)。

与 sink 的正式衔接则是通过 source/slang/slang-diagnostics.h 的宏完成:SLANG_INTERNAL_ERROR(sink, pos)在 debug 下先diagnoseRaw(Severity::Note, "note: internal error triggered at 文件:行"),再sink->diagnose(Diagnostics::InternalCompilerError{...});release 下只输出富诊断。SLANG_UNIMPLEMENTED(sink, pos, what)SLANG_DIAGNOSE_UNEXPECTED(sink, pos, message)结构相同。从源码结构看,SLANG_INTERNAL_ERROR等宏要求包含 slang-rich-diagnostics.h 以获得完整的Diagnostics::*结构体定义。

新增一个诊断:操作检查清单

在 Slang 中新增诊断的标准路径(结合 slang-diagnostics.lua 头部示例与 docs/diagnostic-guidelines.md 的最佳实践):

  1. 选定 Lua 文件与编号段:通用诊断写入 source/slang/slang-diagnostics.lua,类型相关诊断写入 source/slang/diagnostics/type-errors.lua;从对应错误码段(如3xxxx)取未占用的 id,避免与既有码冲突(可用DiagnosticsLookup::getDiagnosticById验证);
  2. 写消息文本:小写开头、无句末标点、代码元素用反引号;通过~param~param:Type~param.member描述参数;Decl直接插值会自动取.name
  3. 声明参数与位置:能用span({loc = "expr:Expr", message = "..."})声明主跨度,用note(...)/variadic_span/variadic_note补充次跨度与附注;primary_span 可选(无位置的命令行类诊断可省略);
  4. 选择严重级别err()warning();需要分组管控的警告可传extra/pedantic哨兵使其默认受-W组开关控制;特殊场景用internal()/fatal()/standalone_note()
  5. 在合适位置调用 sink:找到持有DiagnosticSink的编译站点,调用sink->diagnose(pos, Diagnostics::YourDiagnostic{...})(富诊断结构体)或sink->diagnose(pos, info, args...)(旧式路径);无源码位置的用diagnoseRaw
  6. 考虑覆盖与级联:如需支持-Wno-xxx抑制,确保诊断注册进DiagnosticsLookup(富诊断由getRichDiagnosticsInfo()自动注册);遵循错误级联抑制原则,标记受污染符号、限制传播;
  7. 补测试:按 docs/diagnostic-guidelines.md 的约定为诊断编写至少一个测试(正例与负例),验证消息文本、跨度与 fix-it 建议。

延伸阅读

  • docs/diagnostic-guidelines.md:诊断消息书写规范、错误码格式、JSON 输出与 IDE 集成约定;
  • source/compiler-core/slang-diagnostic-sink.h:sink 完整接口、DiagnosticInfoDiagnosticsLookup
  • source/slang/slang-diagnostics.lua 与 source/slang/slang-diagnostics-helpers.lua:诊断定义 DSL 与构建期处理逻辑;
  • source/slang/slang-rich-diagnostics.h:富诊断结构体的 FIDDLE 生成模板与消费接口;
  • source/core/slang-common.h、source/core/slang-signal.cpp:断言与 ICE 上报的底层实现。

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

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

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

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

立即咨询