news 2026/9/19 13:22:01

Slang 编译器重载决议(Overload Resolution)机制全解:候选过滤管线与转换成本排序

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Slang 编译器重载决议(Overload Resolution)机制全解:候选过滤管线与转换成本排序
  • 编译器
  • 图形学
  • 编程语言

【免费下载链接】slang

Making it easier to work with shaders

项目地址:https://gitcode.com/GitHub_Trending/sl/slang
点击查看免费下载

本篇文章深入剖析 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,依次推进为ArityCheckedFixityCheckedTypeCheckedDirectionCheckedVisibilityCheckedApplicable;若泛型实参推断失败,则落为GenericArgumentInferenceFailed。这一状态机正是后续"每步过滤器只在前一步通过后执行"的基石。

Flags位集合目前只有一个位:IsPartiallyAppliedGeneric(见下文"部分泛型应用")。

此外,候选还携带:

  • item——被应用的声明引用(LookupResultItem);
  • exprVal/funcType——当 Flavor 为Expritem不适用时的函数类型;
  • 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,取值GeneralAssignmentArgumentReturnInitializerExplicitCoercion,表示转换发生处的源级上下文——同一转换在不同CoercionSite下可能被允许或被禁止,成本计算必须携带这一上下文。

算法:候选过滤管线(Algorithm)

重载决议主体位于 slang-check-overload.cpp,候选按固定顺序经过逐级过滤,任一步失败即被剔除(silent rejection)或记录失败原因。主驱动位置(约 L1430-L1450 与 L1629-L1647)依次调用下列函数。

各步骤要点如下:

  1. TryCheckOverloadCandidateClassNewMatchUp(约 L107):检查候选的 class-new 模式是否与调用形态匹配,即"以new形式调用类"这一类候选的匹配检查;不匹配即静默剔除。
  2. TryCheckOverloadCandidateArity(约 L145):实参数量检查。默认参数在此生效——形参若声明了默认值,实参数量可少于形参总数;该步失败通常产生"实参数量不匹配"类诊断(如overloadCandidateArityMismatch家族)。
  3. TryCheckOverloadCandidateFixity(约 L225):运算符的"固定性"(fixity)检查,确认候选的运算符位置(前缀/中缀/后缀)与使用方式一致。
  4. TryCheckOverloadCandidateTypes(约 L807):按实参逐位检查类型。对Generic/UnspecializedGeneric候选,这一步同时进行泛型实参推断(底层调用 slang-check-impl.h 的inferGenericArguments,并产生一个baseCost以在歧义时偏向更特化的泛型候选);失败时记录argMismatchArgIndex等字段,为NoApplicableOverloadForNameWithArgs诊断(slang-check-overload.cpp)提供精确的实参级错误定位。
  5. TryCheckOverloadCandidateDirections(约 L1083):in/out/inout参数方向检查,实参的可写性必须与形参方向匹配。
  6. TryCheckOverloadCandidateConstraints(约 L1157):对泛型候选校验接口一致性(conformance)与where子句约束。从源码注释可见它运行于普通泛型推断之后,失败时Status置为GenericArgumentInferenceFailed,并把genericInferenceFailure记录为约束失败类原因,从而在诊断中给出"约束不满足"而非笼统的"无法特化"。
  7. TryCheckOverloadCandidateVisibility(约 L265):可见性过滤,判定候选在当前作用域是否可见;详细规则属于名称解析的 visibility 设计文档范畴,本步骤只做引用判定(不可见候选通常被静默剔除,避免在报错中泄露私有声明)。

诊断族小结:步骤 2/3/4/5/6 可能发射各自家族的诊断(数量、类型、方向、泛型约束),步骤 1 与 7 多数情形是静默拒绝(no diagnostic; silent rejection),最终"无候选存活"或"成本平局"才在汇总阶段发射NoApplicableOverloadForNameWithArgsAmbiguousOverloadForNameWithArgs(slang-check-overload.cpp)/AmbiguousOverloadWithArgs(同文件 L3814)。

转换成本(Conversion Costs)

成本等级:从最便宜到最昂贵

以下常量在 slang-ast-support-types.h 中按数值顺序声明,数值即排序依据:

成本常量语义
kConversionCost_None0无需任何转换
kConversionCost_GenericParamUpcast1泛型参数向上转型(upcast)
kConversionCost_LambdaToFunc1lambda 到函数类型
kConversionCost_OneVectorToScalar1一元素向量降为标量(附加成本)
kConversionCost_ScalarToVector2标量提升为向量(附加成本)
kConversionCost_MatrixLayout5矩阵布局互转
kConversionCost_GetRef5缓冲区到其承载类型的解引用附加成本
kConversionCost_ImplicitDereference10隐式解引用
kConversionCost_ScalarToMatrix10标量提升为矩阵
kConversionCost_UnconstraintGenericParam20无约束泛型参数
kConversionCost_MutablePtrToConstPtr20可变指针到 const 指针
kConversionCost_InRangeIntLitConversion23范围内整型字面量转换
kConversionCost_SizedArrayToUnsizedArray30定长数组到不定长数组
kConversionCost_InRangeIntLitSignedToUnsignedConversion32范围内 int 字面量 signed→unsigned
kConversionCost_CastToInterface50显式子类型关系到接口(最便宜的"类型关系"转换)
kConversionCost_InRangeIntLitUnsignedToSignedConversion81范围内字面量 unsigned→signed
kConversionCost_BoolToInt120bool→int(刻意低于普通整型转换以避免歧义)
kConversionCost_RankPromotion150秩提升(lossless,保持"类别")
kConversionCost_NoneToOptional150无值到 Optional
kConversionCost_ValToOptional150值到 Optional
kConversionCost_NullPtrToPtr150空指针到指针
kConversionCost_PtrToVoidPtr150指针到 void 指针
kConversionCost_FailedOptionalConstraint150失败的 Optional 约束
kConversionCost_UnsignedToSignedPromotion200无符号到有符号提升(lossless 但改变"类别")
kConversionCost_SignedToUnsignedConversion250同尺寸或更大尺寸 signed→unsigned
kConversionCost_SameSizeUnsignedToSignedConversion300同尺寸 unsigned→signed(可能损失,但通常静默允许)
kConversionCost_IntegerToFloatConversion400整型到浮点
kConversionCost_PtrToBool400指针到 bool
kConversionCost_IntegerTruncate450整型截断(到 int16 等)
kConversionCost_IntegerToHalfConversion500整型到 half
kConversionCost_ParameterPack500具体实参包
kConversionCost_Default500默认情况(可用于用户自定义转换)
kConversionCost_LValueCast800左值转换附加成本
kConversionCost_GeneralConversion900应被劝阻的一般转换(捕获所有不应隐式进行的转换)
kConversionCost_TypeCoercionConstraint1000由类型强制约束定义的成本
kConversionCost_Explicit90000显式转换的成本(实际上不应被隐式执行)
kConversionCost_Impossible0xFFFFFFFF转换不可能

另有若干组合常量:kConversionCost_ScalarIntegerToFloatMatrix(整型→浮点矩阵)、kConversionCost_TypeCoercionConstraintPlusScalarToVector(约束 + 标量到向量)、kConversionCost_ScalarToCoopVector(标量到协同向量)。

跨实参求和

每个实参在给定CoercionSite下计算出一个转换成本(计算函数位于 slang-check-conversion.cpp,如 L3210/L3222/L3231 附近对"成本是否为Impossible"的判定),候选各实参的成本累加到OverloadCandidate::conversionCostSum(初始为kConversionCost_None),作为排序的唯一直观度量。任何实参成本达到kConversionCost_ExplicitkConversionCost_Impossible都意味着该候选不可隐式采用。

平局处理

  • 实参级优先级:成本模型本身按实参独立计算,低成本的转换(如RankPromotionCastToInterface)在求和时天然优于高成本转换;源码注释(slang-check-impl.h 的inferGenericArguments)明确指出:泛型推断时会计算baseCost,以便在歧义时优先更特化的泛型候选——例如对f(Derived()),同时存在f1<T:IBase>f2<T:IDerived>时,f2DerivedIDerived的上转型步数更少而胜出。
  • 候选间部分序:从源码结构看,候选比较以conversionCostSum为键;若某候选成本严格更低则胜出,若多个候选成本并列最低,则全部保留在bestCandidates中,最终由汇总逻辑发射AmbiguousOverloadForNameWithArgs/AmbiguousOverloadWithArgs诊断(slang-check-overload.cpp)。

部分泛型应用(Partial Generic Application)

当泛型候选无法在当前调用中完成完整特化——例如只推断出部分泛型实参,或存在需要延迟求解的约束/witness 实参——决议结果不是直接产生一个特化的DeclRef,而是返回部分应用形态。其标志即OverloadCandidate::Flags中的IsPartiallyAppliedGeneric1 << 0),对应的 AST 节点为PartiallyAppliedGenericExpr(声明于 slang-ast-expr.h)。

从候选字段的设计(subst在约束校验前可能只含由调用推断出的普通泛型实参,校验成功后才替换为含 witness 实参的完整替换)可以推断:部分应用是解析器的一种中间产物/降级结果——当后续上下文(如高阶函数、接口方法约束)需要补全缺失实参时,该表达式会被继续特化;若调用点信息不足以完全解析,解析器返回PartiallyAppliedGenericExpr而不是报错,把收尾交给下游阶段。

运算符重载(Operator Overloading)

内置运算符快速路径:convertToBuiltinArithmeticOp

对内置的标量/向量/矩阵算术、比较、位运算与逻辑运算符,Slang 提供了一条绕过通用重载决议的快速路径:convertToBuiltinArithmeticOp(声明于 slang-check-impl.h 附近,定义于 slang-check-expr.cpp)。

该函数将内建运算符调用转换为携带BuiltinOperationKindBuiltinOperatorExpr,直接用于 IR 生成——从源码注释(slang-check-expr.cpp L4763-L4766)可知,"BuiltinOperationKind只在此处解析一次,下游一切逻辑都以该 kind 为键"。getBuiltinOperationKindFromString负责把运算符名称(如AddSubMulNegEqlLessBitAndLsh等)映射为枚举值(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_ImplicitDereferenceinnerCost + kConversionCost_RankPromotiontagCost + kConversionCost_Explicit等模式在 slang-check-conversion.cpp 中大量出现)。成本封顶由kConversionCost_Explicit(90000)与kConversionCost_Impossible(0xFFFFFFFF)承担:一旦链式累计达到Explicit,该转换即被视为不应隐式发生;达到Impossible则直接判死。上下文还设有disallowNestedConversions开关,禁止嵌套转换的场所会提前终止链条。
  • 泛型实参推断失败:候选Status置为GenericArgumentInferenceFailedgenericInferenceFailure记录聚焦原因(约束失败 vs 推断失败);若失败原因是接口一致性或where子句不满足,状态式剪枝(status-based pruning)通常已将其淘汰,只有被选中的失败候选才会把聚焦原因升级为精确诊断(GenericArgumentInferenceFailure结构本身在 slang-check-impl.h 附近定义,并通过static_assert保证其平凡可复制性)。

总结

重载决议是 Slang 语义检查中把"多个可能"收敛为"一个确定"的关键机制:OverloadCandidate用 Flavor/Status/Flags 三个维度刻画候选形态与进度,OverloadResolveContextJustTrying/ForReal双模式保证试探无副作用、正式检查才落笔;七步过滤管线(class-new 匹配 → arity → fixity → 类型与泛型推断 → 方向 → 约束 → 可见性)按序剔除不合格候选;最终以ConversionCost的逐实参求和与平局保留原则决出唯一最佳,或给出AmbiguousOverload/NoApplicableOverload级别的精确诊断。理解这套管线,是扩展新候选类型、调整成本策略或修复歧义重载诊断的起点。

  • 编译器
  • 图形学
  • 编程语言

【免费下载链接】slang

Making it easier to work with shaders

项目地址:https://gitcode.com/GitHub_Trending/sl/slang
点击查看免费下载

相关推荐

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 13:19:07

Windows重装深度解析:UEFI/GPT分区、驱动注入与四阶段可控安装

1. 项目概述&#xff1a;这不是一次简单的“点下一步”&#xff0c;而是一场系统级的精准手术重装Windows&#xff0c;三个字在普通用户嘴里是“电脑卡了就重装”&#xff0c;在IT支持人员耳中是“客户又把系统搞崩了”&#xff0c;而在真正懂行的从业者听来&#xff0c;它是一…

作者头像 李华
网站建设 2026/9/19 13:16:35

多平台电影院票务系统设计与实现:从架构到并发选座全解析

想起个事儿&#xff0c;我最近被问了好几次“电影院票务系统怎么做”&#xff0c;问的人里有做毕设的学生&#xff0c;也有准备接小影院外包项目的朋友。仔细聊下来发现&#xff0c;大家纠结的点其实很一致&#xff1a;不是不知道怎么写代码&#xff0c;而是不知道怎么把“小程…

作者头像 李华