草庐IT

java - 用Java编写的编译器: Peephole optimizer implementation

coder 2023-05-19 原文

我正在为Pascal的子集编写编译器。编译器为一台组装好的机器生成机器指令。我想为此机器语言编写一个窥孔优化器,但是我无法替换一些更复杂的模式。

窥孔优化器规格

我研究了几种编写窥视孔优化器的方法,并且选择了后端方法:

  • 每次要生成机器指令时,编码器都会调用emit()函数。
  • emit(Instruction currentInstr)检查窥视孔优化表:
  • 如果当前指令与模式的尾部匹配:
  • 检查先前发出的指令以匹配
  • 如果所有指令都与该模式匹配,则应用优化,修改代码存储区
  • 的尾端
  • 如果未找到优化,则照常发出指令

  • 当前的设计方法

    该方法很容易,这是我遇到的麻烦。在我的编译器中,机器指令存储在Instruction类中。我编写了一个InstructionMatch类,该类存储用于匹配机器指令每个组件的正则表达式。如果模式与某些机器指令equals(Instruction instr)相匹配,则其true方法返回instr

    但是,我无法完全应用我的规则。首先,我觉得按照当前的方法,最终会得到一堆不必要的物体。鉴于窥孔优化编号的完整列表可以编号约400个模式,因此这将很快变得一发不可收拾。此外,使用这种方法实际上无法获得更困难的替代方法(请参阅“我的问题”)。

    替代方法

    我读过的一篇论文将前面的指令折叠为一个长字符串,使用正则表达式进行匹配和替换,并将字符串转换回机器指令。这对我来说似乎是一种不好的方法,如果我错了,请纠正我。

    模式示例,模式语法

    x: JUMP x+1; x+1: JUMP y  -->  x: JUMP y
    LOADL x; LOADL y; add     -->  LOADL x+y
    LOADA d[r]; STOREI (n)    -->  STORE (n) d[r]
    

    请注意,这些示例模式中的每一个都是以下机器指令模板的人类可读的表示形式:
    op_code register n d
    

    (n通常表示字数,d通常表示地址位移)。语法x: <instr>表示指令存储在代码存储区中的地址x处。

    因此,当LOADL 17操作码为5时,指令5 0 0 17等同于完整的机器指令LOADL(此指令未使用nr)

    我的问题

    因此,在这样的背景下,我的问题是:当我需要在替换中包括先前指令的一部分作为变量时,如何有效地匹配和替换模式?例如,我可以简单地用增量机器指令替换LOADL 1; add的所有实例-我不需要前面的指令的任何部分来执行此操作。但是我对如何在替换模式中有效使用第二个示例的“x”和“y”值感到困惑。

    编辑:我应该提到Instruction类的每个字段只是一个整数(对于机器指令来说是正常的)。模式表中对“x”或“y”的任何使用都是代表任何整数值的变量。

    最佳答案

    一种简单的方法是将窥视孔优化器实现为有限状态机。

    我们假设您有一个原始代码生成器,该生成器生成指令但不发出指令,还有一个发出例程,该例程将实际代码发送到对象流。

    状态机捕获代码生成器生成的指令,并通过在状态之间进行转换来记住0个或多个生成的指令的序列。因此,状态会隐式记住已生成但未发出的指令的(简短)序列;它还必须记住已捕获指令的关键参数,例如寄存器名称,常量值和/或寻址模式以及抽象目标存储位置。特殊的开始状态会记住指令的空字符串。在任何时候,您都需要能够发出未被忽略的指令(“刷新”);如果您一直这样做,则窥孔生成器会捕获下一条指令,然后发出该指令,而不会做任何有用的工作。

    为了完成有用的工作,我们希望机器捕获尽可能长的序列。由于通常有多种类型的机器指令,因此实际上您不会连续记住太多,否则状态机将变得巨大。但是记住最常见的机器指令(加载,添加,cmp,分支,存储)的最后两三个是实用的。机器的大小实际上取决于我们要进行的最长窥孔优化的长度,但是如果长度是P,则整个机器不必处于P状态深。

    根据您的代码生成器生成的“下一条”指令,每个状态都将转换为下一状态。想象一个状态代表N条指令的捕获。
    过渡选项包括:

  • 刷新该状态表示的最左边的0个或更多(称为k)指令,并转换到表示N-k + 1的下一个状态,该指令表示对机器指令I的附加捕获。
  • 刷新该状态表示的最左边的k条指令,转换到该状态
    代表剩余的N-k条指令,然后重新处理指令I。
  • 完全刷新状态,并发出指令I。 [你实际上可以
    仅在开始状态下执行此操作]。

  • 刷新k条指令时,实际发出的是这些k的窥孔优化版本。您可以计算发出此类指令所需的任何内容。您还需要记住“移位”其余指令的参数。

    使用窥孔优化器状态变量和在代码生成器生成其下一条指令的每个点处的case语句,可以很容易地实现这些功能。 case语句更新窥视孔优化器状态并实现过渡操作。

    假设我们的机器是增强堆叠机器,具有
     PUSHVAR x
     PUSHK i
     ADD
     POPVAR x
     MOVE x,k
    

    指令,但是原始代码生成器仅生成纯堆栈机器指令,例如,它根本不发出MOV指令。我们希望窥孔优化器能够做到这一点。

    我们关心的窥视孔案例是:
     PUSHK i, PUSHK j, ADD ==> PUSHK i+j
     PUSHK i, POPVAR x ==> MOVE x,i 
    

    我们的状态变量是:
     PEEPHOLESTATE (an enum symbol, initialized to EMPTY)
     FIRSTCONSTANT (an int)
     SECONDCONSTANT (an int)
    

    我们的案例陈述:
    GeneratePUSHK:
        switch (PEEPHOLESTATE) {
            EMPTY: PEEPHOLESTATE=PUSHK;
                   FIRSTCONSTANT=K;
                   break;
            PUSHK: PEEPHOLESTATE=PUSHKPUSHK;
                   SECONDCONSTANT=K;
                   break;
            PUSHKPUSHK:
            #IF consumeEmitLoadK // flush state, transition and consume generated instruction
                   emit(PUSHK,FIRSTCONSTANT);
                   FIRSTCONSTANT=SECONDCONSTANT;
                   SECONDCONSTANT=K;
                   PEEPHOLESTATE=PUSHKPUSHK;
                   break;
            #ELSE // flush state, transition, and reprocess generated instruction
                   emit(PUSHK,FIRSTCONSTANT);
                   FIRSTCONSTANT=SECONDCONSTANT;
                   PEEPHOLESTATE=PUSHK;
                   goto GeneratePUSHK;  // Java can't do this, but other langauges can.
            #ENDIF
         }
    
      GenerateADD:
        switch (PEEPHOLESTATE) {
            EMPTY: emit(ADD);
                   break;
            PUSHK: emit(PUSHK,FIRSTCONSTANT);
                   emit(ADD);
                   PEEPHOLESTATE=EMPTY;
                   break;
            PUSHKPUSHK:
                   PEEPHOLESTATE=PUSHK;
                   FIRSTCONSTANT+=SECONDCONSTANT;
                   break:
         }  
    
      GeneratePOPX:
        switch (PEEPHOLESTATE) {
            EMPTY: emit(POP,X);
                   break;
            PUSHK: emit(MOV,X,FIRSTCONSTANT);
                   PEEPHOLESTATE=EMPTY;
                   break;
            PUSHKPUSHK:
                   emit(MOV,X,SECONDCONSTANT);
                   PEEPHOLESTATE=PUSHK;
                   break:
         }
    
    GeneratePUSHVARX:
        switch (PEEPHOLESTATE) {
            EMPTY: emit(PUSHVAR,X);
                   break;
            PUSHK: emit(PUSHK,FIRSTCONSTANT);
                   PEEPHOLESTATE=EMPTY;
                   goto GeneratePUSHVARX;
            PUSHKPUSHK:
                   PEEPHOLESTATE=PUSHK;
                   emit(PUSHK,FIRSTCONSTANT);
                   FIRSTCONSTANT=SECONDCONSTANT;
                   goto GeneratePUSHVARX;
         }
    

    #IF显示了两种不同的过渡样式,一种消耗了生成的样式
    指示,没有的;都适用于此示例。
    当您最终得到几百个case语句时,
    您会发现两种类型都很方便,“不消耗”版本有助于
    您可以使代码更小。

    我们需要一个例程来冲洗窥视孔优化器:
      flush() {
        switch (PEEPHOLESTATE) {
            EMPTY: break;
            PUSHK: emit(PUSHK,FIRSTCONSTANT);
                   break;
            PUSHKPUSHK:
                   emit(PUSHK,FIRSTCONSTANT),
                   emit(PUSHK,SECONDCONSTANT),
                   break:
          }
          PEEPHOLESTATE=EMPTY;
          return; }
    

    考虑此窥孔优化器对以下生成的代码执行的操作很有趣:
          PUSHK  1
          PUSHK  2
          ADD
          PUSHK  5
          POPVAR X
          POPVAR Y
    

    整个FSA方案所做的就是在状态转换中隐藏模式匹配,并在情况下隐藏对匹配模式的响应。您可以手工编写此代码,它快速且相对容易编码和调试。但是,当案例数量增加时,您不想手动构建这种状态机。您可以编写一个工具来为您生成此状态机。良好的背景是FLEX或LALR解析器状态机的生成。我不在这里解释:-}

    关于java - 用Java编写的编译器: Peephole optimizer implementation,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10542082/

    有关java - 用Java编写的编译器: Peephole optimizer implementation的更多相关文章

    1. ruby - 在 Ruby 中编写命令行实用程序 - 2

      我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

    2. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    3. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

      我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

    4. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    5. ruby - Sinatra set cache_control to static files in public folder编译错误 - 2

      我不知道为什么,但是当我设置这个设置时它无法编译设置:static_cache_control,[:public,:max_age=>300]这是我得到的syntaxerror,unexpectedtASSOC,expecting']'(SyntaxError)set:static_cache_control,[:public,:max_age=>300]^我只想将“过期”header设置为css、javaascript和图像文件。谢谢。 最佳答案 我猜您使用的是Ruby1.8.7。Sinatra文档中显示的语法似乎是在Ruby1.

    6. java - 我的模型类或其他类中应该有逻辑吗 - 2

      我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

    7. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

      什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

    8. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

      这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

    9. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

      HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

    10. 【Java入门】使用Java实现文件夹的遍历 - 2

      遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

    随机推荐