理论计算机科学电子笔记147(2006)31-55www.elsevier.com/locate/entcsMumbo:一种基于规则的运行时程序生成语言酒吧酒吧Aktemur1,2伊利诺伊大学计算机科学系关闭IL,USA萨姆·卡明3伊利诺伊大学计算机科学系关闭IL,USA摘要我们描述了我们的eeports使用基于规则的编程产生一个模型的珍宝,运行时程序生成(RTPG)系统的Java。Jumbo结合了RTPG,遵循一个简单的原则,即常规编译器(或者更确切地说,它的后端)既可以用于普通的静态编译,也可以用于运行时编译。这往往会产生一个运行时编译器,这是效率低下的,但可能会受到部分评估的改善然而,语言和编译器的复杂性使得我们很难实现实际的优化。这个模型是用Maude编写的,保留了Jumbo的所有基本成分,但使用了一种简化的语言,称为Mumbo。语言的简化以及Maude我们将详细讨论该模型,我们所获得的各种优化,以及对Jumbo项目的影响保留字: 运行时程序生成,基于规则的编程1这项工作得到了美国国家科学基金会CCR- 0306221的部分支持。2 电子邮件地址:aktemur@cs.uiuc.edu3 电子邮件地址:kamin@cs.uiuc.edu1571-0661 © 2006 Elsevier B. V.在CC BY-NC-ND许可下开放访问。doi:10.1016/j.entcs.2005.06.03632B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)311介绍我们描述了我们的eeports使用基于规则的编程产生一个模型的运行时程序生成(RTPG)系统的Java。我们的团队已经开发了这样一个系统,称为Jumbo [12,6,10,11],它是用Java编写的。然而,我们在Jumbo中优化RTPG的尝试受到了编译器整体复杂性的阻碍。我们在这里描述的模型是用Maude [7]编写的,遵循与Jumbo相同的方法,但针对Java的一个子集;我们称系统为Mumbo,意思是 Maude支持的简化语言和基于规则的编程方法的结合使我们在优化方面取得了巨大的成功。我们从这个模型中学到了一些经验,现在我们正在将其应用于Jumbo。本文介绍了Mumbo,并讨论了我们从中吸取的教训运行时程序生成是程序优化的一种方法,其中仅在运行时发现的数据用于产生更高效的程序版本。有许多系统[8,16,19,3,4]通过允许在源语言中指定动态生成的代码来促进运行时程序生成器的产生。所有这些系统至少在表面上是相似的。它们包括代码引用语法-我们<这段代码可能有未知的部分或漏洞,这些部分或漏洞将在运行时使用某种计算的结果来填充,而这种计算无论出于何种原因都不能静态执行。漏洞由一段引用代码中的反引用表达式表示;我们将使用符号下面是一个例子:$obj.foo这里我们有一个洞,它将被一个名为param的Code对象填充,如下所示:代码param = $x>$;代码c = $obj.fooRTPG在固定的静态生成代码上实现加速的能力取决于生成代码的速度以及代码生成过程的速度。事实上,如果生成的代码只执行一次,那么几乎可以肯定的是,不值得花时间来生成它。在一定数量的重复中,代码生成的成本最终被支付,效率红利开始支付;这个数字被称为交叉点。最小化RTPG的成本,从而最小化交叉B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)3133这是所有RTPG系统的目标。我们为Java开发了一个RTPG系统,叫做Jumbo。Jumbo与上面提到的系统的区别在于,它编译了Java 1.4的所有内容,更重要的是,它的构造方法以及它可以在运行时生成的代码的通用性为了优化RTPG,其他系统做了两件事:限制生成代码中可能发生的可变性程度,并编写一个运行时编译器,专门用于在这种受约束的环境中快速发出机器指令。例如,它们中的大多数要求出现在引用代码中的任何变量必须在同一段引用代码中声明;变量捕获,其中一个变量在一个引用片段中使用,然后用于填充另一个包含变量声明的引用片段中的漏洞(This要求也是允许生成代码的静态类型检查所必需的。)知道变量的类型,即使不知道变量的确切使用方式,也可以让运行时编译器快速发出代码。我们在Jumbo采取了不同的策略。我们相信,基于大量的例子,这样的限制使运行时程序生成器变得不那么有用,而且比它们本来可以的更难编写。我们的方法是基于这样的观察,如果编译器是以组合形式编写的-这意味着任何代码段的编译都是其子组件编译的函数-那么我们可以使用静态编译器的后端-它根本不处理代码中的“漏洞”-作为RTPG机制。(Here,这实现了非常高的通用性,因为几乎任何语法片段-包括例如声明-都可以从片段中抽象出来。另一方面,它几乎没有优化的空间。毕竟,编译器的后端可能已经像它的程序员所能做的那样高效了。但是,事实上,这种方法允许一种不同的优化途径:对编译器后端的部分评估。考虑这样一个程序片段:$
$我们无法生成代码,因为我们不知道x的类型;在Java中,它甚至可以是一个字符串,+表示串联。但是我们可以部分地生成代码:我们知道会有一个对println的调用,并且运算符是加法或连接,而不是乘法或其他任何东西。粗略地说,我们可以生成这样的代码:if(x,类型为String)发出代码将“1”连接到x34B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)31if(a){t=x和int类型之上的最小类型;如果需要,发出代码将x强制转换为 t发出代码以强制1到t,如果需要,发出适当类型的add指令}发出对println的请记住,我们开始使用的静态编译器并不是为了处理RTPG而编写的。它没有特殊的机制来将类型的查找推迟到运行时,就像我们在这里所做的那样。相反,我们的工作是接受给定的编译器,并对它进行部分评估以获得此代码。总之,Jumbo的工作原理是编写一个组合风格的编译器,这个想法是使用引用的代码片段提供给我们的任何信息来优化后端。那些“更静态”的片段但更多的动态用途仍将被容纳。程序员为他所需要的动态量但这一切都取决于部分评估编译器后端的能力这就是本文的主题一段时间以来,我们一直试图以这种方式对Jumbo进行部分评估。我们已经取得了部分成功(建立在Lars Clausen作为其博士论文[6]的一部分编写的系统上我们已经编写了许多优化,可以转换编译器最初生成的代码生成器但是我们还没有产生像上面描述的那样的代码,其中运行时代码生成器已经减少到其必需品,我们还没有产生真正显著的加速。这部分是因为Java很复杂(许多出于这个原因,我们着手用Maude语言编写一个Jumbo模型,使用一种简化的语言。使用Mumbo,我们已经能够实现我们预期的代码本文介绍了Mumbo,列出了我们已经实现的优化,展示了它们如何导致RTPG的显著优化,讨论了优化规则的局限性,并总结了我们学到的经验教训我们正在把它应用到珍宝身上。关于Mumbo的完整细节可以在[2]中找到,源代码可以在http://pinatubo.cs.uiuc.edu/上找到。2Maude我们在Maude实现了Mumbo [7]。Maude是一个支持重写逻辑的强大工具。在Maude,人们可以定义方程或规则。使用规则B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)3135来描述系统中的并发转换。在Mumbo中,我们没有并发性。因此,我们只使用方程,它也可以是条件的。Maude支持模块化开发,它可以执行给定的方程4。它还包括验证和证明工具,但我们在Mumbo中不使用这些工具。3JumboJumbo [6,12,11,10]是一个Java的阶段式编译系统,允许运行时程序生成。它提供了高度的程序员控制、源代码级规范和二进制级操作。使用Jumbo,可以在运行时不调用编译器的情况下生成代码。由于许多计算机有Java运行时但没有编译器,这是一个重要的实用功能。如前所述,Jumbo程序员通过将代码放在引号中来指定要在运行时生成的代码:$和>$。从程序员因此,所包含的程序片段不是任意的,但它几乎可以是任何可解析的片段。引用的Java片段内部可能有漏洞-将用代码编写时未知的Code值填充的点。孔的语法是反引号('),后面是语法类别,后面是括号中的Code类型的Java表达式。考虑public int findDuplicate(int findDuplicate){ return $while(int findDuplicate){'Stmt(主体)}>$;}这个方法可以被称为:$if(i== 3)break; i++;>$);这个调用将给我们代码等价于:while(true){if(i==3)return;i++;}这段代码现在可以在定义了i的上下文中使用对于基元类型的表达式,还有第二种类型的反引号,4为此,方程必须是可执行的。有关可执行性要求的详细信息,请参见[7]。36B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)31一个在程序生成时计算表达式,然后将值作为常量插入到生成的代码中。例如,Code是Jumbo实现中的主类。Code值表示程序片段的部分编译版本,并表示为方法。它的参数是关于该片段的使用上下文的信息,该信息是将片段完全编译为虚拟机代码所需的;它的结果是由此计算的虚拟机代码。因为它是一个方法,所以这个程序片段被表示为虚拟机代码,而不是源代码或语法树。Jumbo的这一特性有利于安全性和效率。由于方法不是第一类公民,我们将Code值表示为函数对象,它提供eval作为“函数应用程序”方法。有关Jumbo的更多信息,请参见[6,10,11,12]。在本文的最后,我们 将 讨 论 Mumbo 项 目 对 Jumbo 编 译 器 的 影 响 。 Jumbo 可 以 在http://loome.cs.uiuc.edu/Jumbo/index.php上获得。4MumboMumbo是一种支持运行时程序生成的类型化、面向对象的语言。它可以被认为是Jumbo的简化版本。它由五个主要部分组成:编译器,预处理,语义,编译器和优化(即分析器和转换器)。每个部分都在Maude中定义,除了编译器,它是在Mumbo中实现我们在将Mumbo程序编译成虚拟机代码(称为LowLevel)后执行它们。这使得Mumbo模型Jumbo更加真实,因为Jumbo也被编译为虚拟机代码-JVM字节码。此外,在运行时,Mumbo生成低级代码,类似于Jumbo生成JVM字节码。LowLevel是一种3地址代码语言,我们在大约400行Maude中定义。它的语法和语义受到LLVM(低级虚拟机)的启发[13]。它提供了寄存器操作,如加法,等式测试,(非)条件分支等。在LowLevel中,可以定义和调用函数。程序员可以定义vtables和structs。这使得LowLevel适合作为Mumbo这样的面向对象语言的虚拟机代码.我们没有给出LowLevel的细节,因为它不是本文的重点。Mumbo程序由Mumbo编译器编译为LowLevel的B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)3137编译器在Mumbo中实现。在Maude中,Mumbo的语义是可执行的,我们使用这种可执行语义来执行Mumbo编译器。这是可能的,因为从语义的角度来看,编译器是一个普通的Mumbo程序。这样,Mumbo编译器就可以自己编译我们将在4.4节中更详细地解释编译器。在接下来的章节中,我们将解释Mumbo的语法、预处理阶段、语义、编译器和优化规则。4.1语法Mumbo是一种面向表达式的语言;语句和表达式之间没有区别。和Jumbo一样,Mumbo使用$<和>$括号来定义程序片段。插 入 符号(^(...))是表达式的反引号字符5除此之外,我(),^B(.)和^S(...)可以分别用于提升整数、布尔值和字符串^F(.)和^M(...)分别用于反引用字段和方法(需要F和M字符来解析封闭的程序片段)。下面是语法的一些重要部分。• 字符串的定义是将内置的Maude字符串或引用标识符(Qids)放在方括号之间,如[“abc”]或• Qid用作名称。self是一个特殊的名称,它指向当前对象。NIL是null指针。• #是字符串连接运算符。 有四种算术运算:++、--、**、-:-表示加、减、乘、除,表示-。我们不使用像+和-这样的常用运算符,因为它们在Maude中被定义为可交换的由于可能的副作用,这在Mumbo中不成立• 一个对象的字段可以用->访问,比如• 方法具有以下语法:op[_]_:_:MEthodFlag MName MParamListMVarDecls MName MExp-> MEthod.op:_:MEthodFlag MName MParamList MName MExp-> MEthod.方法标记可以是方法或最终方法。然后是方法的名称,后面是参数列表。 参数列表的格式为:('a 1:'t 1,...,’a的第n个参数,tn是它的类型。方法体中使用的局部变量被声明为方法头的一部分如果有5我们不能像在Jumbo中那样使用反引号38B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)31没 有 局 部 变 量 , 用 户 可 以 自 由 地 省 略 声明 , 或 者 简 单 地 输 入[noVars]。变量声明列表的格式为:'a 1:'t 1,..., ’a最后是方法的返回类型,后跟一个冒号,然后是方法体。• 类的语法如下:op延伸:MClassFlagMName MName MMethods-> MClass。opextends:MClassFlag MName MName MFields MMethods-> MClass。类标记可以是class或final class。 然后是类的名称,然后是关键字extends,然后是超类的名称。在类头之后是字段列表和方法。如果类没有字段,用户可以简单地忽略字段• 一个对象的方法是通过发送一个消息来调用的opsend:MExp MName MExpList -> MExp.在这种语法中,第一个参数是目标,它预计将评估为对象,第二个参数是消息(方法)的名称,后跟参数列表。• 程序是由零个或多个类和一个主方法组成的可执行单元。main类似于方法,但它没有参数列表或返回类型。op _main'[_]_:MClasses MVarDecls MExp -> MProgram。我们相信,其余的句法问题将清楚的例子,在整个本文。4.2预处理在Mumbo中,和许多实际系统一样,代码引用不是在语言的抽象语法中,而是被预处理掉了。Mumbo中的预处理阶段有两个相互递归的功能:预处理和代码。预处理是除代码引用之外的所有语法单元的恒等函数。它只是递归地调用那些语法单元的子组件。当它遇到引用时,它调用代码。下面是为预处理定义的一些方程。eq预处理(X)= X。-X是一个名字eq预处理(如果BE,则 E,否则如果预处理(BE),则预处理(E),否则预处理(E ')。eq preprocess($$)=code(E)。code将一个片段转换为一个表示该片段的Code对象。当看到反引号时,代码简化为预处理。下面是一些定义的方程。B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)3139eq code(X)= new 'getNameCode([X])。-X是一个名字eq代码(如果是那么Eelsenew'ifThenElseCode(code(Be),code(E),code(E')).eq code(^(E))= preprocess(E)。因此,括号中给出的每个程序片段都被转换为表达式- 代码6对象的实例化,例如• set<' a =$2>$ becomesset'a = new 'integerConstan t C o de(2)• $sendselfnewnew• $^('c)>$变成4.3语义语言的语义是通过Maude方程定义Mumbo在调用对象的方法时应用动态调度。当使用new命令创建对象时,会自动调用其'initialize是Mumbo中的一个特殊方法,不允许显式调用它(就像Java中的构造函数一样)。 子类可以覆盖它们的超类的方法。对类、方法和字段的final标记的解释遵循Java的解释:final类不能被子类化,final方法不能被覆盖,final每个变量都必须有一个声明,可以是字段、参数或局部变量。 参数和局部变量可以隐藏字段。方法的参数和局部变量必须具有不同的名称。在Mumbo中,语句和表达式之间没有区别。块的计算结果是它们最后一个表达式的计算结果。变量赋值和循环的值为0。方法返回其主体计算的结果。new命令返回对它实例化的对象的引用Mumbo没有静态字段/方法。当需要生成唯一的数字时,这会导致问题,这发生在Mumbo编译器中。另一方面,我们希望保持语言简单。因此,我们选择向语言中添加特殊的关键字,而不是静态字段。在计算时,GENSYM以(%类似地,GENLABEL返回一个新的6在MumboQids中,Qids被用作名字。 所以7在目标低级代码中,引用标识符用作标签,引用标识符前缀为百分比符号用作名称。40B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)31形式为('lX)的虚拟机标签这两个特殊表达式作为语义的一部分进行处理如上所述,预处理阶段删除了所有引用。因此,引号和反引号不是内核的一部分,我们不定义它们的语义。我们主要遵循[18,14]中提供的风格来定义语义。我们为每个表达式类型定义一个eval操作。在最高层,op eval:MProgram MState -> MValue。其评估以初始状态开始的Mumbo程序。State包含所有必要的信息,包括Environment(将名称映射到位置)和Store(将位置映射到值)。作为其结果,eval返回程序的值由于篇幅限制,我们没有给出语义的完整源代码。4.4编译器在本节中,我们将解释Mumbo编译器。该编译器是在Mumbo中实现的,并产生LowLevel代码。在用编译器编译程序之后执行低级代码的结果与执行具有等式语义的程序相同。在第3节中,我们说过每个Code类型的对象都在Mumbo中’Env 这包括(1)封闭方法的局部变量和参数列表,(2)封闭类的字段列表,(3)这个类的名字,(4)这个类的超类的名字The 它是一个包含两个元素的元组:下面是表示整数常量的final class方法[’sym{set8回想一下,LowLevel是一种3地址代码语言。所以,名字是明确的。如果它是堆栈代码,比如JVM字节码,我们就不需要B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)3141set}编译器由每个语法元素的“Code”类、"Closed-Code“、”Env“和”LinkedList“以及几个辅助类组成编译器完全在Mumbo中实现。像Jumbo一样,它是组成的。正如我们在第4节中提到的,我们利用可执行语义来执行编译器-从语义的角度来看,它只是一个Mumbo程序。编译器是一组类,零元函数Mclasser表示这组类(op Mclasser:->Mclasses . ).该编译器最重要的特性是它是无边带的。换句话说,它是以功能性风格实现的。我们通过将每个领域定义为最终领域来实现这一目标。当需要改变一个对象的状态时,它返回一个具有新状态的新对象。例如,当一个新的field被添加到field列表时,一个--是Env方法newThe 当我们有一个'Code对象代表一个完整的程序或类时生成从一个空的环境开始。final方法{set 'env = new 'Env(new 'LinkedList(NIL,NIL),-var. 列表new[""],-封闭类名称[""],---superclassname newsend(sendself}下面是一个简单的例子,展示了如何生成一个类。class$class123>$main [noVars]send(send(new对于静态时间编译,可以使用编译操作。 编译用于获取给定程序的相应LowLevel代码。 是42B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)31定义如下。op compile:MProgram ->MValue。eq compile(P)= eval(M)main [noVars]send(preprocess($$))'generate(),initialState)。换句话说,这个操作用于将Mumbo程序传递给Mumbo编译器。图1中给出了说明。用括号括起来预处理new defineClass(“c”,.new defineField(“f”,“int”),new defineMethod(“m”,...))使用编译器类课程介绍eval发送generate()低空Fig. 1. 编译一个Mumbo类。4.5分析仪和变压器对于普通程序,编译器执行的源代码级优化很少是有效的。然而,在Mumbo中,它是优化运行时代码生成器的主要方法。这些由编译器的代码('Code类等)组成 简而言之,这是一个源代码级优化可以有效的环境在本节中,我们将解释Mumbo的源代码级优化器通过等式逻辑和匹配,Maude使遍历程序的语法树变得容易。有两种基本的遍历:分析器和转换器。分析器遍历语法树并收集信息;转换器修改树9.为了方便地定义这些遍历,我们首先定义了一个叫做VISIT的模块,它定义了一个叫做visit的操作:op访问:MRewRule MExp MRewData -> MExpListDataPair。在[20]中,变压器被分为纯变压器和收集信息的变压器我们不做区分。$C类field f:intmethod m. >$C类field f:intmethod m.B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)3143这个操作的第一个参数是我们希望在语法树节点上应用的操作的名称(例如:constantPropagation)。第二个参数是要遍历的节点。最后一个参数是数据。当我们想在节点之间传递信息时,会用到这个参数。visit返回一对表达式和数据。返回的表达式可用于替换现有表达式;返回的数据可用于各种目的。访问操作足够通用,可以用来转换树、分析树,或者同时进行转换和分析它类似于[20]的遍历函数和[9]的访问者模式。遍历的默认行为在IDENTITY模块中定义 对于每个语法成分,我们定义访问递归地应用于节点的子树。默认情况下,它以自上而下、从左到右的顺序遍历树,而不更改树中的任何内容(因此命名为IDENTITY)。为了说明,让我们看看它是如何定义加法的:var R:MRewRule。变量E E ' E1 E2:MExp.变量D D1 D2:MRewData。ceq访视(R, E ++如果{E1,D1}:=访视(R,E,D)/\{E2,D2}:=访视(R,首先,通过向左操作数传递传入数据来访问左操作数。然后遍历右操作数,但这次我们传递从左操作数获得的信息这些数据可能与原始数据不同。在右操作数也被访问之后,从该遍历获得的最终数据与新操作数一起返回,新操作数可能由于变换而不同于原始操作数visit的默认行为以同样的方式定义其他语法单元。请注意,默认的遍历行为具有[otherwise]属性。这是一个关键点,当我们想让一个遍历函数在树上做一些特定的工作时,我们只需要为我们感兴趣的节点定义它的行为。对于其他节点,将应用默认行为。下面是一个例子:eqvisit(replace,E,replaceData(E,{E',replaceData(E,E')}。此操作在模块REPLACE中定义,它将一个表达式替换为另一个表达式。要替换的表达式是replaceData的第一个元素。它的第二个参数是将替换旧参数的表达式。我们只需要定义上面的等式(以及常量replace和replaceData)就可以让这个Transformer工作。当我们遍历我们想要替换的表达式E时,Maude应用这个等式并返回E对于其他表达式,应用恒等函数。这种遍历策略是通过使用[otherwise]44B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)31属性优化器需要关于程序的信息以便能够转换程序。例如,需要use-def分析来传播常量。因此,我们希望能够在节点上存储信息。为此,我们定义一个新的语法树节点:操作信息:MExp MInfoData-> MExp.info是一个闭包,它包含一个表达式和相关数据,并替换表达式。各种元素可以作为封装数据的一部分存储,包括use-def、gen-kill和类型信息。因此,分析器在遍历树时,只在访问过的节点上留下一些信息,然后继续遍历。例如,标记分析器为程序中的循环分配唯一的编号。然后我们使用这些标签来区分展开时的循环。我们在TAG模块中定义以下等式:varInfD:MInfoData. 我不知道。varsE BE E ' BE ' :MExp.变量D1 D2:MRewData。ceqvisit(tag, info(doE while BE,InfD),tagData(N))={info(doE/\{这个等式匹配一个循环,从传入的数据中获取当前的数字,递增数字并递归地遍历主体和条件,最后将标记作为封装信息的一部分。我们定义了与程序分析相关的各种遍历器:• FreeVars:在方法体中查找自由变量• Info:将表达式封装在信息包中。需要在节点上保存信息。• 标记:为名称、循环和方法调用分配唯一的编号。标签用于在展开循环、内联方法和使用定义分析时进行识别。• 取消标签:取消标签。• 类型:在表达式上显示类型信息。尽可能缩小类型。• IsLocal:确定变量是局部变量还是字段。• IsFinal:确定所访问的字段是否最终。• CollectDefs:收集树中的所有定义。• GetDefs:查找特定变量的定义。• MayHaveAUse:确定一个变量在程序中是否有用。B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)3145• Escapes:确定特定变量是否从当前方法中转义• GenKill:计算表达式的gen-kill集。看《易经》的时候,就可以知道易经是怎样炼成的,怎样炼成的,怎样炼成的。• UD:计算use-def链。 能做may或must分析。• 重置:重新计算到目前为止收集的分析结果,并通过按给定顺序运行Tag、Type、IsLocal、IsFinal、GenKill和UD与分析仪类似,我们定义变压器。变压器主要有两种:需要分析结果的变压器和其他变压器。需要分析的变压器列表如下。• Replace:用给定的表达式替换特定的表达式• 内联:如果可能,内联一个特定的方法调用。• 展开:展开一个特定的循环。• CopyAssignment:如果可能,将一个变量赋值给另一个变量.• ConstantPropagation:删除常量。• NilCheck:如果保证E不是NIL,则将E等于NIL替换为False。• UselessDef:无用的定义。• UselessDecl:从方法头中删除无用的变量声明• FieldValue:如果可能,从对象中提取最终字段的值• UselessNew:如果一个未使用的对象的构造函数是无侧边引用的,则禁止创建该对象。回想一下,编译器是以一种无侧边引用的风格实现的。因此,UselessNew可以在优化方面提供很大• 在 一 个 固 定 点 迭 代 中 , 按 照 给 定 的 顺 序 , 执 行 Reset 、ConstantPropagation 、 CopyAssignment 、 NilCheck 、 FieldValue 、UselessDef、UselessNew和UselessDecl这些都不是代码扩展。因此,清理是保证终止。• Auto:在固定点迭代中自动内联方法和展开循环。• SpecializeClass:使用Auto和Rename创建一个优化版本的'Code类,它专门用于特定的SpecializeClass是顶级操作。它直接或间接地使用其他操作来生成优化的类。其他变压器不需要分析结果(即,它们不需要上下文外的信息)。删除冗余块并减少-46B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)31声明就是这样的例子由于Maude的匹配能力我们不提供完整的集合,但提供其中一些如下:等式{Elb;{Elb 2} }={Elb;Elb 2}。eqifTruethenEelse E ' = E.如果是,则E = {BE;E}。等式E和True = E。等式E ** 1 = E。在变形金刚中,汽车似乎很危险。它会自动内联方法并展开循环,直到代码不再更改。为了防止陷入无限循环,我们使用了一些技巧。例如,如果循环已经展开并且没有减少,我们就不 当一个循环被展开时,它会嵌套在一个if语句中:做E而做BE成为如果是,则执行E,否则执行0。因此,如果一个循环直接在if语句中,我们可以断定它是展开的,我们不展开它。内联的启发式如下:如果方法是递归的,我们内联它,只有当目标和参数是可定义的。一个表达式被称为可定义的,如果它包含的所有变量都是可定义的。一个变量是可定义的,如果它的定义存在于我们所处的方法中,并且如果这个定义的右边也是可定义的。例如,我们说一个变量是不可定义的,如果它是一个参数,并且它在传递给它的方法中没有赋值在第5节中,我们将看到,使用给定的优化规则集,可以将代码生成器简化为第1节中描述的形式。5优化我们在4.5节中提供的分析和转换是众所周知的,有很好的文档记录[1,15],并且存在于许多编译器中。我们认为,给定的设置是足够的程序生成器在源代码级别的大幅优化。我们展示了几个例子。回想一下我们的目标:获得看起来像第1节中手写的程序生成器的代码。我们的第一个例子是一个程序片段,它定义了一个没有自由变量的方法。$method>$预处理阶段将其转换为以下表达式:B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)3147newnewnew'LinkedList(NIL,NIL),-没有声明的变量[’int],-下面的方法的主体:mult。对于两个名为“ x ” 和 “ xn e w ” 的 变 量 b i n O p C o d e ( [ “ m u l ” ] ,newnew此构造函数调用创建“defineMethodCode "类(”Code“的子类)的对象当表示的引用代码要转换为低级代码时,调用“defineMethodCode "对象的 因 此 , 我 们 需 要 优 化 的 正 是 这 种 方 法 。 下 面 是 'eval in'defineMethodCode的定义-[’lowlevel{-为此方法setsetset-遍历paramList 以在LowLevelset'param = 'paramList中形成参数列表while(sendsetset}的情况;- 以'entry basic block set 'lowlevel = 'lowlevel #[”){ 'entry:“]开始函数体-评估身体set-连接所有内容并返回set#(send}我们无法优化这种方法本身。 但它将发生在一个新的创建的类(由SpecializeClass模块生成),其中一些final字段已被赋予值-特别是,...)). 在这个特殊类的上下文中,“eval”代码可以是基本的48B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)31经过优化。具体来说,SpecializeClass创建了一个名为-2786与“defineMethodCode”相同,上面对“defineMethodCode "构造函数的调用newdefineMethodCode-2786()的方法[’lowlevel{setset“)set#[”br}这是当此对象将被编译为低级代码这就是我们在第一节中所要达到的目标。 请注意,它还没有准备好发出代码:我们仍然需要知道封闭类的名称来给出函数但这是我们能得到的最好的。以重写次数衡量,这段代码的速度大约是未优化版本的四倍如果我们在代码中有一个漏洞,我们将不得不离开它的'eval'方法,如。我们的下一个例子是一个带孔的引用表达式$^我们省略了乘法运算符的eval方法的定义优化后方法[’tempCode{setsetset#B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)3149#[",“]#new}在这种方法中,我们在前面提到,通过对普通代码进行源代码级优化来获得显著的性能改进是困难的,原因很简单,程序员已经对代码进行了优化,使其满意。那么,为什么我们能够如此有效地优化这个源代码呢?我们正在优化的代码在程序员操作的语义级别--编写引用的代码片段--不可能优化代码。我们通过这些优化所实现的是允许程序员在那个级别上操作,而不是在优化代码所代表的级别上操作-机器指令的级别。5.1优化的局限性在前面的例子中,我们成功地获得了我们所期望的代码。现在,我们处理一个现有优化无法产生理想代码的示例。下面是一个例子:$此表达式的优化编译代码如下(伪代码):if’leftelseif’left其他误差然而,我们可以使用现有的优化集生成的代码看起来像这样:if’left =’left误差if50B. Aktemur,S.Kamin/Electronic Notes in Theoretical Computer Science 147(2006)31’right =’right误差’result = emit code to multiply ’left and在这个代码中,我们有两倍于理想代码中的条件。这表明,在当前的优化中,例如,我们不能使用这样的信息:如果有了下面这样的等式,我们就能够实现理想的代码。[[如果be则e1elsee2;eb;如果be则e3elsee4]][[ifbethen{e1;eb;e3}else{e2;eb;e4}]]if(be)sidel