- 编译器
- 图形学
- 编程语言
【免费下载链接】slang
Making it easier to work with shaders
本篇文章深入剖析 Slang 编译器语义检查器中名称解析(Name Resolution)的核心环节——重载决议(Overload Resolution):当一个LookupResult携带多个候选(candidate)时,编译器如何通过逐级过滤管线将其收窄为唯一最佳候选,或在无法消除歧义时给出明确诊断。文中所有概念、函数名、枚举值均以当前仓库 source/slang 中的实现为准,适合正在修改重载决议逻辑、新增候选类型或过滤步骤、以及排查"歧义重载"(ambiguous overload)诊断的开发者阅读。读完本文,你将掌握候选的数据结构、JustTrying/ForReal双模式语义检查、完整的七步过滤管线、转换成本(ConversionCost)排序与平局处理、部分泛型应用以及内置运算符快速路径的底层原理。
核心概念(Concepts)
OverloadCandidate:一个待决议候选的完整画像
候选在 slang-check-impl.h 中由struct OverloadCandidate表示,它承载了重载决议过程中一个候选从产生、逐级检查到最终胜出的全部状态。
Flavor枚举标识候选的来源形态:
Func——普通函数/方法候选;Generic——泛型候选(已可进行泛型实参推断);UnspecializedGeneric——尚未特化的泛型候选;Expr——候选本身就是表达式(例如重载的构造器表达式)。
Status枚举记录候选通过过滤管线到达的阶段,初始为Unchecked,依次推进为ArityChecked→FixityChecked→TypeChecked→DirectionChecked→VisibilityChecked→Applicable;若泛型实参推断失败,则落为GenericArgumentInferenceFailed。这一状态机正是后续"每步过滤器只在前一步通过后执行"的基石。
Flags位集合目前只有一个位:IsPartiallyAppliedGeneric(见下文"部分泛型应用")。
此外,候选还携带:
item——被应用的声明引用(LookupResultItem);exprVal/funcType——当 Flavor 为Expr或item不适用时的函数类型;resultType——若该候选胜出,调用结果的类型;subst——泛型候选的替换集合(substitution),约束校验成功后会被替换为包含编译器求解出的 witness 实参的完整替换;conversionCostSum——用于候选排序的累计转换成本;explicitGenericArgCount——显式提供的普通泛型实参数(默认 -1 表示尚未计算,供约束求解器处理默认值与 witness 实参);genericInferenceFailure——泛型候选失败时求解器记录的聚焦失败原因,让报错不再是笼统的"无法特化";argMismatchArgIndex/argMismatchExpectedType/argMismatchActualType——首个类型检查失败的实参信息(对应 issue #7857),使"没有可用重载"的诊断能精确指向出错实参。
OverloadResolveContext:决议现场与 JustTrying/ForReal 双模式
所有候选共享一个OverloadResolveContext(slang-check-impl.h),它相当于一次重载决议的"现场":保存触发调用的原始表达式originalExpr、函数位置的源码位置funcLoc、用于可见性测试的sourceScope、实参列表与实参类型,以及当前尝试的mode。
mode字段的Mode枚举区分两种语义检查阶段:
JustTrying——仅试探:对候选执行与正式检查完全相同的过滤步骤,但不发射任何诊断,只返回"可用/不可用"。这样检查器可以安全地依次探测多种调用形态(call shape),例如泛型推断失败、类型不匹配等都不会打扰用户;ForReal——动真格:当选出最佳候选后,以该模式真正更新 AST、记录类型并允许发射诊断。
上下文内部还直接内嵌一个bestCandidateStorage以避免每次动态分配,并保留bestCandidates列表用于歧义情形下的报错。matchArgumentsToParams负责把实参按位置匹配到形参(泛型场景下还会预计算类型以避免跨阶段重复工作)。
ConversionCost 与 CoercionSite:成本模型的载体
ConversionCost虽在 slang-ast-support-types.h 中定义(类型为unsigned int的常量枚举),其计算逻辑集中在 slang-check-conversion.cpp;CoercionSite则定义于 slang-check-impl.h,取值General、Assignment、Argument、Return、Initializer、ExplicitCoercion,表示转换发生处的源级上下文——同一转换在不同CoercionSite下可能被允许或被禁止,成本计算必须携带这一上下文。
算法:候选过滤管线(Algorithm)
重载决议主体位于 slang-check-overload.cpp,候选按固定顺序经过逐级过滤,任一步失败即被剔除(silent rejection)或记录失败原因。主驱动位置(约 L1430-L1450 与 L1629-L1647)依次调用下列函数。
各步骤要点如下:
TryCheckOverloadCandidateClassNewMatchUp(约 L107):检查候选的 class-new 模式是否与调用形态匹配,即"以new形式调用类"这一类候选的匹配检查;不匹配即静默剔除。TryCheckOverloadCandidateArity(约 L145):实参数量检查。默认参数在此生效——形参若声明了默认值,实参数量可少于形参总数;该步失败通常产生"实参数量不匹配"类诊断(如overloadCandidateArityMismatch家族)。TryCheckOverloadCandidateFixity(约 L225):运算符的"固定性"(fixity)检查,确认候选的运算符位置(前缀/中缀/后缀)与使用方式一致。TryCheckOverloadCandidateTypes(约 L807):按实参逐位检查类型。对Generic/UnspecializedGeneric候选,这一步同时进行泛型实参推断(底层调用 slang-check-impl.h 的inferGenericArguments,并产生一个baseCost以在歧义时偏向更特化的泛型候选);失败时记录argMismatchArgIndex等字段,为NoApplicableOverloadForNameWithArgs诊断(slang-check-overload.cpp)提供精确的实参级错误定位。TryCheckOverloadCandidateDirections(约 L1083):in/out/inout参数方向检查,实参的可写性必须与形参方向匹配。TryCheckOverloadCandidateConstraints(约 L1157):对泛型候选校验接口一致性(conformance)与where子句约束。从源码注释可见它运行于普通泛型推断之后,失败时Status置为GenericArgumentInferenceFailed,并把genericInferenceFailure记录为约束失败类原因,从而在诊断中给出"约束不满足"而非笼统的"无法特化"。TryCheckOverloadCandidateVisibility(约 L265):可见性过滤,判定候选在当前作用域是否可见;详细规则属于名称解析的 visibility 设计文档范畴,本步骤只做引用判定(不可见候选通常被静默剔除,避免在报错中泄露私有声明)。
诊断族小结:步骤 2/3/4/5/6 可能发射各自家族的诊断(数量、类型、方向、泛型约束),步骤 1 与 7 多数情形是静默拒绝(no diagnostic; silent rejection),最终"无候选存活"或"成本平局"才在汇总阶段发射NoApplicableOverloadForNameWithArgs与AmbiguousOverloadForNameWithArgs(slang-check-overload.cpp)/AmbiguousOverloadWithArgs(同文件 L3814)。
转换成本(Conversion Costs)
成本等级:从最便宜到最昂贵
以下常量在 slang-ast-support-types.h 中按数值顺序声明,数值即排序依据:
| 成本常量 | 值 | 语义 |
|---|---|---|
kConversionCost_None | 0 | 无需任何转换 |
kConversionCost_GenericParamUpcast | 1 | 泛型参数向上转型(upcast) |
kConversionCost_LambdaToFunc | 1 | lambda 到函数类型 |
kConversionCost_OneVectorToScalar | 1 | 一元素向量降为标量(附加成本) |
kConversionCost_ScalarToVector | 2 | 标量提升为向量(附加成本) |
kConversionCost_MatrixLayout | 5 | 矩阵布局互转 |
kConversionCost_GetRef | 5 | 缓冲区到其承载类型的解引用附加成本 |
kConversionCost_ImplicitDereference | 10 | 隐式解引用 |
kConversionCost_ScalarToMatrix | 10 | 标量提升为矩阵 |
kConversionCost_UnconstraintGenericParam | 20 | 无约束泛型参数 |
kConversionCost_MutablePtrToConstPtr | 20 | 可变指针到 const 指针 |
kConversionCost_InRangeIntLitConversion | 23 | 范围内整型字面量转换 |
kConversionCost_SizedArrayToUnsizedArray | 30 | 定长数组到不定长数组 |
kConversionCost_InRangeIntLitSignedToUnsignedConversion | 32 | 范围内 int 字面量 signed→unsigned |
kConversionCost_CastToInterface | 50 | 显式子类型关系到接口(最便宜的"类型关系"转换) |
kConversionCost_InRangeIntLitUnsignedToSignedConversion | 81 | 范围内字面量 unsigned→signed |
kConversionCost_BoolToInt | 120 | bool→int(刻意低于普通整型转换以避免歧义) |
kConversionCost_RankPromotion | 150 | 秩提升(lossless,保持"类别") |
kConversionCost_NoneToOptional | 150 | 无值到 Optional |
kConversionCost_ValToOptional | 150 | 值到 Optional |
kConversionCost_NullPtrToPtr | 150 | 空指针到指针 |
kConversionCost_PtrToVoidPtr | 150 | 指针到 void 指针 |
kConversionCost_FailedOptionalConstraint | 150 | 失败的 Optional 约束 |
kConversionCost_UnsignedToSignedPromotion | 200 | 无符号到有符号提升(lossless 但改变"类别") |
kConversionCost_SignedToUnsignedConversion | 250 | 同尺寸或更大尺寸 signed→unsigned |
kConversionCost_SameSizeUnsignedToSignedConversion | 300 | 同尺寸 unsigned→signed(可能损失,但通常静默允许) |
kConversionCost_IntegerToFloatConversion | 400 | 整型到浮点 |
kConversionCost_PtrToBool | 400 | 指针到 bool |
kConversionCost_IntegerTruncate | 450 | 整型截断(到 int16 等) |
kConversionCost_IntegerToHalfConversion | 500 | 整型到 half |
kConversionCost_ParameterPack | 500 | 具体实参包 |
kConversionCost_Default | 500 | 默认情况(可用于用户自定义转换) |
kConversionCost_LValueCast | 800 | 左值转换附加成本 |
kConversionCost_GeneralConversion | 900 | 应被劝阻的一般转换(捕获所有不应隐式进行的转换) |
kConversionCost_TypeCoercionConstraint | 1000 | 由类型强制约束定义的成本 |
kConversionCost_Explicit | 90000 | 显式转换的成本(实际上不应被隐式执行) |
kConversionCost_Impossible | 0xFFFFFFFF | 转换不可能 |
另有若干组合常量:kConversionCost_ScalarIntegerToFloatMatrix(整型→浮点矩阵)、kConversionCost_TypeCoercionConstraintPlusScalarToVector(约束 + 标量到向量)、kConversionCost_ScalarToCoopVector(标量到协同向量)。
跨实参求和
每个实参在给定CoercionSite下计算出一个转换成本(计算函数位于 slang-check-conversion.cpp,如 L3210/L3222/L3231 附近对"成本是否为Impossible"的判定),候选各实参的成本累加到OverloadCandidate::conversionCostSum(初始为kConversionCost_None),作为排序的唯一直观度量。任何实参成本达到kConversionCost_Explicit或kConversionCost_Impossible都意味着该候选不可隐式采用。
平局处理
- 实参级优先级:成本模型本身按实参独立计算,低成本的转换(如
RankPromotion、CastToInterface)在求和时天然优于高成本转换;源码注释(slang-check-impl.h 的inferGenericArguments)明确指出:泛型推断时会计算baseCost,以便在歧义时优先更特化的泛型候选——例如对f(Derived()),同时存在f1<T:IBase>与f2<T:IDerived>时,f2因Derived到IDerived的上转型步数更少而胜出。 - 候选间部分序:从源码结构看,候选比较以
conversionCostSum为键;若某候选成本严格更低则胜出,若多个候选成本并列最低,则全部保留在bestCandidates中,最终由汇总逻辑发射AmbiguousOverloadForNameWithArgs/AmbiguousOverloadWithArgs诊断(slang-check-overload.cpp)。
部分泛型应用(Partial Generic Application)
当泛型候选无法在当前调用中完成完整特化——例如只推断出部分泛型实参,或存在需要延迟求解的约束/witness 实参——决议结果不是直接产生一个特化的DeclRef,而是返回部分应用形态。其标志即OverloadCandidate::Flags中的IsPartiallyAppliedGeneric(1 << 0),对应的 AST 节点为PartiallyAppliedGenericExpr(声明于 slang-ast-expr.h)。
从候选字段的设计(subst在约束校验前可能只含由调用推断出的普通泛型实参,校验成功后才替换为含 witness 实参的完整替换)可以推断:部分应用是解析器的一种中间产物/降级结果——当后续上下文(如高阶函数、接口方法约束)需要补全缺失实参时,该表达式会被继续特化;若调用点信息不足以完全解析,解析器返回PartiallyAppliedGenericExpr而不是报错,把收尾交给下游阶段。
运算符重载(Operator Overloading)
内置运算符快速路径:convertToBuiltinArithmeticOp
对内置的标量/向量/矩阵算术、比较、位运算与逻辑运算符,Slang 提供了一条绕过通用重载决议的快速路径:convertToBuiltinArithmeticOp(声明于 slang-check-impl.h 附近,定义于 slang-check-expr.cpp)。
该函数将内建运算符调用转换为携带BuiltinOperationKind的BuiltinOperatorExpr,直接用于 IR 生成——从源码注释(slang-check-expr.cpp L4763-L4766)可知,"BuiltinOperationKind只在此处解析一次,下游一切逻辑都以该 kind 为键"。getBuiltinOperationKindFromString负责把运算符名称(如Add、Sub、Mul、Neg、Eql、Less、BitAnd、Lsh等)映射为枚举值(L2560/L4783/L4858)。
注意:早期的ResolvedOperatorOverload记忆化缓存已不再存在——它已被上述convertToBuiltinArithmeticOp及其辅助函数取代(当前 slang-check-impl.h 中残留的struct ResolvedOperatorOverload仅为跨会话无效的候选缓存占位,不承担运算符决议的 memoization 职责)。内置运算符决议不依赖缓存命中。
运算符查找如何从通用算法特化
内置运算符优先走快速路径:先尝试convertToBuiltinArithmeticOp,成功则产生BuiltinOperatorExpr(对比较/移位等类别还有细分逻辑,见 L4858-L4877 的isArithmetic/isComparison/isBitwise/isEquality/isShift分组);失败(kind 为Unknown)或涉及用户定义运算符时,才回落到通用重载决议管线,按 Flavor 过滤与成本排序处理。
二元/一元运算符重载的隐式 this
对类内定义的运算符重载,决议时隐式注入this实参:二元运算符的左侧操作数匹配this,一元运算符仅this参与。这意味着运算符重载决议中的"实参数量"与"第一个实参类型"检查都会把this计算在内——这也是TryCheckOverloadCandidateFixity与方向检查在运算符场景下必须与通用函数区分处理的原因之一。
边界情况与失败模式(Edge Cases and Failure Modes)
- 两个候选转换成本完全相等:两者都被保留在
bestCandidates中,汇总时发射AmbiguousOverloadForNameWithArgs诊断(slang-check-overload.cpp),附带候选清单;若是表达式场景则用AmbiguousOverloadWithArgs(同文件 L3814)。诊断标识符在 slang-diagnostics.h 中定义。 - 没有任何候选匹配:发射
NoApplicableOverloadForNameWithArgs(slang-check-overload.cpp),并利用候选记录的首个失败实参信息(argMismatchArgIndex/ 期望类型 / 实际类型)报告"哪个实参、期望什么类型、实参是什么类型"的近匹配信息(issue #7857),帮助用户快速定位。 - 候选除可见性外全部匹配:
TryCheckOverloadCandidateVisibility失败通常导致静默剔除——不可见候选不会出现在"无可用重载"的候选列表里,避免向用户泄露私有/内部声明;这是"filtered with a hint"与"silently dropped"两种策略中 Slang 采用的保守做法(除非诊断框架另有显式提示路径)。 - 隐式转换爆炸(conversion chain):每个实参允许一条转换链,成本逐级累加(如
subCost + kConversionCost_ImplicitDereference、innerCost + kConversionCost_RankPromotion、tagCost + kConversionCost_Explicit等模式在 slang-check-conversion.cpp 中大量出现)。成本封顶由kConversionCost_Explicit(90000)与kConversionCost_Impossible(0xFFFFFFFF)承担:一旦链式累计达到Explicit,该转换即被视为不应隐式发生;达到Impossible则直接判死。上下文还设有disallowNestedConversions开关,禁止嵌套转换的场所会提前终止链条。 - 泛型实参推断失败:候选
Status置为GenericArgumentInferenceFailed,genericInferenceFailure记录聚焦原因(约束失败 vs 推断失败);若失败原因是接口一致性或where子句不满足,状态式剪枝(status-based pruning)通常已将其淘汰,只有被选中的失败候选才会把聚焦原因升级为精确诊断(GenericArgumentInferenceFailure结构本身在 slang-check-impl.h 附近定义,并通过static_assert保证其平凡可复制性)。
总结
重载决议是 Slang 语义检查中把"多个可能"收敛为"一个确定"的关键机制:OverloadCandidate用 Flavor/Status/Flags 三个维度刻画候选形态与进度,OverloadResolveContext以JustTrying/ForReal双模式保证试探无副作用、正式检查才落笔;七步过滤管线(class-new 匹配 → arity → fixity → 类型与泛型推断 → 方向 → 约束 → 可见性)按序剔除不合格候选;最终以ConversionCost的逐实参求和与平局保留原则决出唯一最佳,或给出AmbiguousOverload/NoApplicableOverload级别的精确诊断。理解这套管线,是扩展新候选类型、调整成本策略或修复歧义重载诊断的起点。
- 编译器
- 图形学
- 编程语言
【免费下载链接】slang
Making it easier to work with shaders
相关推荐
Slang 重载决议机制全解析:候选筛选管线、转换成本与歧义诊断的实现细节
Slang 重载决议机制全解析:候选筛选管线、转换成本与歧义诊断的实现细节 本篇技术指南以 Slang 编译器文档缺口审计报告(gap intake repor
编译器图形学编程语言Slang 重载决议(Overload Resolution)算法与测试体系全解析
Slang 重载决议(Overload Resolution)算法与测试体系全解析 本篇技术指南以 Slang 着色器语言编译器中「重载决议(overload
编译器图形学编程语言Slang 编译器类型转换体系解析:as、dynamicCast 与 canonical type 机制
Slang 编译器类型转换体系解析:as、dynamicCast 与 canonical type 机制 本文聚焦 Slang 着色器编译器( GitHub_T
编译器图形学编程语言
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考