编译器语义分析过程中的条件依赖性

时间:2014-01-17 19:51:29

标签: compiler-theory

想象一下,我们已经获得了一个包含三列的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语句的每个分支中都不同。

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

2 个答案:

答案 0 :(得分:1)

为什么标准优化技术不合适?

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

如果你只是通过解除它来传播条件(类编译器优化),我们从你的原始方程开始,其中每个计算以任何顺序WRT评估给其他人,这样结果计算dag-like(即“anyorder”) “是一个打算对其进行建模的运营商:”

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; }

如此传统的转换和关于任何顺序 - 如果 - daglike知识的知识 似乎给出了正确的效果。

如果根据细胞计算COND,我不确定你做了什么。

我怀疑这样做的方法是生成计算的依赖图 与依赖关系的条件。您可能必须在弧上传播/分组这些条件,而不是像语法一样。

答案 1 :(得分:1)

是的,文学存在,抱歉我不能引用任何内容,我只是不记得了,它会像你一样只是谷歌...

依赖和循环分析的基本算法非常简单。即检测表达式中的符号,以表格形式构建一组表达式和依赖项:

    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

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

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

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

当然,在分析基于Bools的条件时,您可以分析,分组和消除(a & b & !a)等冲突条件。

..但是如果你的输入值和条件包括NON-BOOL数据,比如整数或浮点数或字符串,只要想象你的条件有一个条件执行一些外部奇怪的统计函数并检查其结果。忽略&# 39;怪异&#39;部分并专注于外部&#39;。如果你遇到一些使用AVG或MAX等复杂函数的表达式,你就无法通过静态(*)来咀嚼这些东西。即使是简单的算法也很难分析:(a+b)*(c+d) - 你可以得出一个事实,即当一个+ b == 0时c+d可以被忽略,但这是一个非常艰巨的任务来完全覆盖..

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

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

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

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

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

然后你可以扩展编译器&#39;试图提供帮助的方面,即初步部分分析依赖关系并尝试重新排序规则,以便稍后检查它们的更好的顺序&#34;,或者通过预先计算常数,或者内联一些表达式等等,但是我说,这是所有的优化,而不是核心功能。

可悲的是,我无法真正告诉你任何关于&#34;功能语言的重要信息,但是因为他们的运行时通常是非常有活力的&#39;有时它们甚至可以用符号和变换来执行代码,它可以降低编译器的复杂性。和&#39;引擎&#39;部分。这里最有价值的资产是活力。因此,即使是Ruby也会比C做得更好 - 但绝不是一个&#34;编译的&#34;你说的语言。

例如,您可以尝试将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

这是动态平台的强大功能,没有什么非常实用的功能&#39;这里。动态平台可以轻松填充/覆盖位。但是,一旦用户提供了一些位,并且一旦程序被动态更正,那么你会先调用哪一个函数?

答案有点难过......你不知道。

如果您的动态语言内置了一些规则引擎,您可以尝试生成规则而不是函数,然后依赖该引擎来填充&#34;一切都可以计算出来。

但如果它没有规则引擎,那么你又回到了第一点......

事后:

对不起,对不起,我想我写的太多,太模糊/健谈。如果您认为它有帮助,请给我发表评论。否则我会在几天或一周后删除它。