【问题标题】:Conditional Dependencies in Compiler Semantic Analysis Passes编译器语义分析过程中的条件依赖
【发布时间】:2014-02-07 07:11:07
【问题描述】:

假设我们有一个 Excel 电子表格,其中包含三列,分别标记为 COND、X 和 Y。

COND = TRUE or FALSE (user input)
X = if(COND == TRUE) then 0 else Y
Y = if(COND == TRUE) then X else 1;

这些公式在 Excel 中计算得非常好,并且 Excel 不会产生循环依赖错误。

我正在编写一个编译器,试图将这些 Excel 公式转换为 C 代码。在我的编译器中,这些公式确实会产生循环依赖错误。问题是(天真地)X 的表达式取决于 Y,而 Y 的表达式取决于 X,我的编译器无法在逻辑上继续。

Excel 能够完成这一壮举,因为它是一种懒惰的解释型语言。 Excel 只会在运行时懒惰地评估公式(使用用户输入),并且由于在运行时不会发生循环依赖,因此 Excel 在评估这种逻辑时没有问题。

不幸的是,我需要将这些公式转换为编译语言(而不是解释语言)。实际电子表格中的实际公式在多个单元格/变量之间具有更复杂的依赖关系(涉及多达六个不同的单元格)。这意味着我的编译器必须对公式执行某种复杂的静态语义分析,并且如果我们“查看”条件分支,则必须足够聪明地检测到没有循环引用。然后编译器必须从上述 Excel 公式生成以下 C 代码:

bool COND;
int X, Y;
if(COND) { X = 0; Y = X; } else { Y = 1; X = Y; }

请注意,在 C 中 if 语句的每个分支中,赋值指令的顺序是不同的。

我的问题是,是否有任何关于编译器的既定算法或文献解释了如何在编译器中实现这种类型的分析?函数式编程语言编译器必须解决这个问题吗?

【问题讨论】:

    标签: compiler-theory


    【解决方案1】:

    为什么标准的优化技术不够用?

    据推测,Excel 公式形成了一个 DAG,其中叶子是原始值,节点是计算/分配。 (如果Excel计算形成一个循环,那么你需要 某种迭代求解器,假设您需要一个固定点)。

    如果您只是通过提升条件来传播条件(类编译器优化),我们从您的原始方程开始,其中每个计算都以 WRT 的任何顺序评估给其他计算,这样结果计算类似于 dag(即“任何顺序" 是打算对其建模的运算符):

    X = if(COND == TRUE) then 0 else Y;
         anyorder
    Y = if(COND == TRUE) then X else 1;
    

    然后解除条件:

    if (COND)  { X=0; } else { X = 1; }
          anyorder
    if (COND)   { Y=X; } else { Y = 1; }
    

    然后

    if (COND)  { X=0; anyorder Y=X; } else { X = Y; anyorder Y = 1; }
    

    每个手臂都必须像匕首一样。 第一臂是 daglike 首先评估 X=0 分配。 第二个手臂是 daglike 首先评估 Y = 1。所以,我们得到了您想要的答案:

    if (COND)  { X=0; Y=X; } else { Y = 1; X = Y; }
    

    因此,关于 anyorder-if-daglike 知识的常规转换和知识 似乎产生了正确的效果。

    如果 COND 被计算为单元格的函数,我不确定你会做什么。

    我怀疑这样做的方法是生成一个计算依赖图 以依赖为条件。您可能必须在弧上传播/分组这些条件,就像我在语法上所做的那样少。

    【讨论】:

    • 我认为“通过提升条件来传播条件(类编译器优化)”是我正在寻找的。任何参考文献都会有所帮助。你确实描述了我正在尝试做的算法,所以我确实接受了答案。
    • 这个“提升”实际上是一个分配定律:ab+ac ==> a*(b+c),使用您的编程语言中的运算符。跨度>
    【解决方案2】:

    是的,文学作品是存在的,抱歉我不能引用任何内容,我只是不记得了,你可以用谷歌搜索一下吗..

    依赖和循环分析的基本算法非常简单。 IE。检测表达式中的符号,在form中构建一组表达式和依赖:

        inps             expr      outs
    cell_A6, cell_B7 -> expr3 -> cell_A7
    cell_A1, cell_B4 -> expr1 -> cell_A5
    cell_A1, cell_A5 -> expr2 -> cell_A6
    

    然后通过比较和迭代扩展/替换输入/输出集:

    step0:
    cell_A6, cell_B7 -> expr3 -> cell_A7
    cell_A1, cell_B4 -> expr1 -> cell_A5   <--1 note that cell_A5 ~ (A1,B4)
    cell_A1, cell_A5 -> expr2 -> cell_A6      <--1 apply that knowledge here
    
    so dependency
    cell_A1, cell_A5 -> expr2 -> cell_A6
    morphs into
    cell_A1, cell_B4 -> expr2 -> cell_A6   <--2 note that cell_A6 ~ (A1,B4) and so on
    

    最后,您将获得一组完整的依赖关系,您可以在其中轻松检测循环依赖关系,例如:

    cell_A1, cell_D6, cell_F7 -> exprN -> cell_D6
    

    或者,如果没有找到 - 您将能够确定一个安全的增量执行顺序。

    如果表达式包含除“返回值”之外的分支或副作用,您可以应用各种转换将表达式缩减/扩展为新的表达式,或将具有上述形式的新表达式组。例如:

    B5 = { if(A5 + A3 > 0) A3-1 else A5+1 }
    
    so
    
       inps    ...       outs
    A3, A5 -> theExpr -> B5
    
    the condition can be 'lifted' and form two conditional rules:
    
    A5 + A3 > 0  : A3 -> reducedexpr "A3-1" -> B5
    A5 + A3 <= 0 : A5 -> reducedexpr "A5-1" -> B5
    

    但是现在,您的执行/分析还必须在应用规则之前注意条件。提升只是可能的转变之一。

    但是,您仍然需要更多的东西,至少需要一些“扩展”。你的问题的难点在于你的表达式很复杂,有分支,你需要包含用户随机输入来解析分支,以消除死分支并打破死依赖。

    由于关键是消除死依赖,您必须以某种方式检测死分支。条件可以是任意复杂的,用户输入是随机的,所以你不能完全静态地解决它,真的。在使用转换之后,您仍然需要分析条件并相应地生成代码。为此,您需要为条件结果的所有可能组合以及所有产生的分支和规则组合生成代码,除了一些琐碎的情况外,这根本不可行。随着未知的数量,叶子的数量可以呈指数增长(2 ^ N),这在超过某个阈值后是一个巨大的膨胀。

    当然,在基于布尔值分析条件时,您可以分析、分组和消除冲突条件,例如(a &amp; b &amp; !a)..

    ..但是如果您的输入值和条件包括非布尔数据,如整数或浮点数或字符串,请想象您的条件是有一个执行某些外部奇怪统计函数并检查其结果的条件..忽略“奇怪” '部分并专注于'外部'。如果您遇到一些使用复杂函数(如 AVG 或 MAX)的表达式,则无法静态地咀嚼类似的东西(*)。即使是简单的算术也很难分析:(a+b)*(c+d) - 你可以推导出一个事实,当 a+b==0 时c+d 可以忽略,但要完全覆盖这是一项非常艰巨的任务..

    IIRC,使用基本运算符对布尔表达式进行可满足性分析 (SAT) 是一个 NP 难题,更不用说整数或浮点及其所有数学运算了。计算表达式的结果比告诉哪些​​值更容易这真的取决于!

    因此,由于输入值可能是硬编码(很酷)或在运行时由用户提供(doh!),因此您的编译器很可能无法预先对其进行全面分析。现在将它与标记为 (*) 的事实联系起来,很明显您可以包含一些静态分析并尝试在“编译时”消除一些分支,但仍然可能有一些部分必须延迟到用户提供实际输入。

    所以,如果部分分析必须在运行时完成,所有的分支消除只是一个可选的优化,我认为你现在应该专注于运行时部分。

    在最小的未优化版本中,您生成的程序可以简单地记住所有 excel 表达式并等待输入数据。一旦程序运行并给出输入,程序就必须在表达式中替换输入,然后尝试迭代地将它们简化为输出值。

    用命令式语言编写这样的算法是完全可能的。实际上,您需要编写一次,然后将其与从单元公式派生的不同规则集合并并完成。程序的运行时部分是相同的,公式会改变。

    然后您可以扩展“编译器”端以尝试提供帮助,即初步部分分析依赖关系并尝试重新排序规则,以便稍后以“更好的顺序”检查它们,或通过预先计算常量或内联一些表达式等等,但正如我所说,这都是优化,而不是核心功能。

    遗憾的是,我无法真正告诉您有关“函数式语言”的任何严肃内容,但由于它们的运行时通常是“非常动态的”,有时它们甚至会根据符号和转换来执行代码,它可以降低您的“编译器”和“引擎”部分。这里最宝贵的资产是活力。因此,即使是 Ruby 也会比 C 做得更好——但它绝不是你所说的“编译”语言。

    例如,您可以尝试将 excel 规则直接转换为函数:

    def cell_A5 = expr1(cell_A1, cell_B4)
    def cell_A7 = expr3(cell_A6, cell_B7)
    def cell_A6 = expr2(cell_A1, cell_A5)
    

    把它写成程序的一部分,然后在运行时当用户提供一些值时,你会重新定义程序的一些部分

    cell_B7 = 11.2  // filling up undefined variable
    cell_A1 = 23    // filling up undefined variable
    cell_A5 = 13    // overwriting the function with a value
    

    这就是动态平台的力量,这里没有什么非常“实用”的东西。动态平台使填充/覆盖位变得容易。但是,一旦用户提供了一些位并且程序已经“即时修正”,你会先调用哪个函数?

    答案有些悲哀。。你不知道。

    如果您的动态语言内置了一些规则引擎,您可以尝试生成规则而不是函数,然后依靠该引擎“填充”所有可能计算的内容。

    但如果它没有规则引擎,你就回到第一点..

    三思而后行:

    嗯..对不起,我想我写的太多了,太含糊/闲聊了。如果您认为它有帮助,请给我评论。否则我会在几天或一周后删除它。

    【讨论】:

      猜你喜欢
      • 2012-02-01
      • 1970-01-01
      • 2011-06-15
      • 1970-01-01
      • 2012-11-13
      • 2014-09-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多