Skip to content

算法Algorithm

k-CFA 高阶控制流分析

k-CFA · 0-CFA · Higher-order control-flow analysis

函数也是值时,怎样有限地近似每个调用点可能遇到哪些闭包。

形式陈述 ​

k-CFA是一族高阶控制流分析:函数可以作为参数、结果或捕获值流动,分析器仍要求出每个调用位置可能执行哪些闭包。参数k规定区分最近多少个调用标签;k=0把全部调用历史合并,k=1保留最近一个调用点。

固定有限的带标签程序,并先把不同绑定器的变量唯一命名,避免同名遮蔽被误当成同一绑定。为说明经典构造,可先转成续延传递风格:返回也表现为调用一个续延闭包,不再有隐含机器栈。整数等非函数值在本分析中用有限标签概括,不枚举无穷多个整数。

设调用点集为C,抽象历史为

Time^=C≤k,tick(c,κ)=suffixk(κc).

在这里的CPS模型中,历史是已经执行的调用标签序列的后缀,不是尚未返回的物理调用栈。变量绑定地址取 (x,κ)。抽象环境 ρ^ 把词法变量映到这种地址,抽象存储 σ^ 把地址映到可能闭包集合。闭包为 (λx.e,ρ^clo),所以相同函数体配上不同捕获环境仍可成为不同抽象值。

在调用标签c处,从函数表达式的值集合枚举每个候选闭包,从实参表达式得到可能值集合V,令 κ′=tick(c,κ)。进入函数体时:

a=(x,κ′),ρ^′=ρ^clo[x↦a],σ^′=σ^⊔[a↦V].

关键是沿用闭包捕获的环境,再增加形参绑定;不能用调用者环境解释被调函数的自由变量。存储更新取并,因为一个抽象地址可能代表多次具体分配。

对初始状态做工作队列可达探索:发现新闭包、地址内容或控制状态时,重新处理受影响的调用,直到没有新事实。这是对具体高阶执行的抽象解释;不是通过采样调用来猜目标。

直觉

运行时每次进入函数都会建立新绑定,递归可以建立无穷多个。k-CFA用有限调用后缀给这些绑定贴标签;后缀相同的绑定共用一个抽象槽。槽里保存多种值,读槽时便可能产生多个后继。

0-CFA像只按变量名字设邮箱:identity的形参x收到过哪些函数,全放进同一箱。1-CFA再按“从哪个调用点送来的”分箱。它与调用串上下文敏感性共享截断思想,但还必须处理高阶闭包的词法捕获。

例子与边界

两次 identity 的函数结果 ​

表面程序为

text
id = λx.x
f = id(λu.u)          // c1,闭包标签A
g = id(λv.0)         // c2,闭包标签B
pair(f, g)

两次调用在不同的源位置。CPS转换显式带上各自的返回续延,而c1、c2仍标识进入id的两次调用。

0-CFA只为形参x保留地址 (x,ε)。两次调用令它含A、B;id体读x时两者都可能返回,于是f与g的近似结果都含 {A,B}。真实结果却是f=A、g=B。

1-CFA分别分配 (x,[c1]) 与 (x,[c2]),并将相应续延参数也按调用上下文区分。在这个例子中,前者只含A,后者只含B,因此两次结果不再串线。

抽象槽 0-CFA 1-CFA
id的形参x 单槽 {A,B} c1槽 {A},c2槽 {B}
f的结果 {A,B} {A}
g的结果 {A,B} {B}

这里比较的是同一套闭包/调用语义,只改变历史截断;不能同时换掉返回建模后把全部改进归给k。

捕获环境不能在调用时重建 ​

考虑 make = λa. λb. a。分别在c1、c2调用make传入A和B,得到两个函数。它们的函数体同为 λb.a,但捕获的a地址分别来自c1与c2。若只存函数体标签而丢掉环境,两值会被合并,后续调用仍可能返回A或B。

经典正k-CFA保留这种环境组合;只给每个函数体配一个创建上下文、再复制自由变量值的某些多项式变体,是另一种近似。二者都可以可靠,但不能在定义与复杂度之间悄悄切换。

增加k并不等于保留完整历史 ​

调用链的末尾若分别为 c1,d 与 c2,d,k=1都只剩 [d]。若一个参数正是在d进入的函数中绑定,两路值仍会合并。递归重复同一个调用点更会不断复用相同后缀。

只增加k也不修复所有数值分支不精确、堆对象合并或不可达闭包的残留。抽象垃圾回收可删除已经无法再访问的旧槽内容,处理的是另一种精度损失。

推论与应用

固定k与有限程序后,抽象调用历史有限,地址、环境、闭包和存储都只从有限集合组合,因此穷尽工作队列能终止。可靠性按具体步与抽象步对应证明:真实函数值在候选集合里,真实绑定地址映到选定抽象地址,取并保留原有别名所需值,闭包环境保证自由变量指向正确绑定的近似。

这项有限性不等于统一的低次多项式时间。若调用点数为c、变量数为v,地址数至多 v∑i=0kci;但一个含r个自由变量的闭包还可有许多地址环境组合。经典正k-CFA的成本可能很高,不能只数调用串就宣布整体 O(nk+1)。0-CFA的标准约束化版本则可以用三次时间算法求解;采用不同环境抽象的工程变体应另外命名和报告界。

分析得到的函数流集合可用于间接调用图、内联候选、逃逸检查与类型恢复。若某调用点只有一个可能闭包体,优化仍要检查效果、捕获值和语言语义;“目标唯一”不自动保证随意移动或删除该调用。

练习与解答 ​

对make例子,写出1-CFA中两个 λb.a 的抽象闭包。答案为 (λb.a,{a↦(a,[c1])}) 与 (λb.a,{a↦(a,[c2])}),忽略不相关绑定。调用它们时,形参b获得新地址,a仍使用原捕获地址。

验收重点是两个层次:进入函数的当前上下文决定新形参,创建闭包时的环境决定旧自由变量。若把所有变量一律解释为当前调用上下文,词法作用域已经被错误改成动态作用域。

参考资料
  • Olin Shivers, Control-Flow Analysis of Higher-Order Languages,CMU博士论文,1991,§3.6及Chapter 4,尤其0-CFA/1-CFA的contour machinery:绑定环境与调用历史抽象
  • Anders Møller and Michael I. Schwartzbach, Static Program Analysis,2026年8月版,§§10.1–10.4;该讲义区分经典环境表示与其多项式上下文变体
关系图谱10 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系