想象一下,我有一个基于堆栈的玩具语言,包括Push,Pop,Jump和If操作。
我有一个程序,它的输入是玩具语言。例如,我得到序列
Push 1
Push 1
Pop
Pop
在这种情况下,最大堆栈将为2.更复杂的示例将使用分支。
Push 1
Push true
If .success
Pop
Jump .continue
.success:
Push 1
Push 1
Pop
Pop
Pop
.continue:
在这种情况下,最大堆栈将为3.但是,如本例所示,不可能通过从上到下走来获得最大堆栈,因为它实际上会导致堆栈下溢错误。
CFG拯救你可以建立一个图表并走你所拥有的基本块的每条可能路径。但是,由于n个顶点的路径数量可以快速增长(n-1)!可能的路径。
我目前的方法是尽可能简化图表并减少可能的路径。这有效,但我认为它很难看。是否有更好的(阅读:更快)方法来解决这个问题?如果算法产生的堆栈深度不是最佳的,我很好。如果正确的堆栈大小是m,那么我唯一的约束是结果n是n> = m。是否有可用的贪心算法可以产生良好的结果?
更新:我知道循环和所有controlf流合并的不变量具有相同的堆栈深度。我以为我写下了一个简单的类似玩具的语言来说明这个问题。基本上我有一个确定的基于堆栈的语言(JVM字节码),所以每个操作都有一个已知的堆栈增量。
请注意,我确实有一个解决此问题的工作方案,可以产生良好的效果(简化cfg),但我正在寻找更好/更快的方法。
答案 0 :(得分:2)
鉴于您的语言似乎没有任何用户输入,所有程序将始终以相同的方式进行计算。因此,您可以执行程序并在执行期间跟踪最大堆栈大小。可能不是你想要的。
至于你的路径论证:请注意,跳转允许循环,因此,无需进一步分析,循环可能意味着非终止和堆栈溢出(即在每个循环执行后堆栈大小增加)。 [如果存在循环,[n个节点仍然意味着无限多个路径]
而不是实际执行代码,您可以做某种形式的abstract interpretation。
关于IVlad的评论:由于存在可能的周期,仅仅计算推动是错误的。
我不确定你的if语句的语义是什么,所以这也是有用的:假设if语句的标签只能是一个正向标签(即你永远不能跳回你的代码) 。在这种情况下,你的路径计数参数重现生机。实际上,生成的CFG将是树(如果不复制代码,则为DAG)。在这种情况下,您可以通过自下而上计算推送次数来进行近似计数,然后在if语句的情况下获取两个分支的最大推送次数。它仍然不是最佳的正确结果,但产生了比简单推送语句更好的近似值。
答案 1 :(得分:0)
您通常希望堆栈深度不会超过跳转和循环。
这意味着对于每个节点,每个传入边缘应具有相同的堆栈深度。这大大简化了CFG的步行,因为后备无法再更改已计算指令的堆栈深度。
这也是有界堆栈深度的要求。如果不强制执行,您的代码中将增加循环。
您应该考虑的另一件事是使所有操作码的堆栈效果具有确定性。非确定性操作码的一个示例是:POP IF TopOfStack == 0.
修改强>
如果确实有一组确定的操作码且堆栈深度不变,则无需访问程序的每个可能路径。通过CFG执行DFS / BFS就足以确定最大堆栈深度。这可以在线性时间内完成(取决于指令的数量),但不能更快。
评估仍然需要评估当前基本块的传出边缘的基本块是否与性能无关。即使在最坏的情况下,每条指令都是IF
,只有2 * N个边缘要评估。