前面讲过语法的解析之后,代码生成方面就简单很多了。虽然myc是一个简单的示例编译器,但是它还是在解析的过程中生成了一个小的语法树,这个语法树将会用在生成exe可执行文件和il源码的过程中。

编译器在解析时,使用emit类来产生中间的语法树,语法树的数据结构和操作方法在iasm这个类型里完成,源程序的语法解析完毕后,Exe和Asm两个类分别遍历生成的语法树产生最终的代码。

我们来看几个代码的例子,下表的函数 Parser.program 里,在函数开始和结束的地方分别调用了 prolog 和 epilog 两个函数,这两个函数的目的就是在语法解析的前后执行一些准备和扫尾工作。如在编译过程的开始阶段,根据.net assembly的要求创建好模块(module)和类(class),虽然c语言是一个面向过程的语言,但是在.net是一个面向对象的环境,所有的代码都应该保存在一个类里。

public void program(){prolog();while (tok.NotEOF()){outerDecl();}if (Io.genexe && !mainseen)io.Abort("Generating executable with no main entrypoint");epilog();}void prolog(){emit = new Emit(io);emit.BeginModule();        // need assembly module
  emit.BeginClass();}

而在emit里,BeginModule和BeginClass这两个函数的代码如下:

public void BeginModule(){// 委托给Exe类来创建这个module,虽然在.net里,一个assembly可以由// 多个module组成,但是在C程序里,只要一个module就足够了,因此// 下面的代码并没有在生成IL源码的时候产生module。
  exe.BeginModule(io.GetInputFilename());}public void BeginClass(){// 委托给Exe类来创建class,再进一步跟踪代码的时候,会发现它其实// 是根据反射技术来创建类型的。
  exe.BeginClass(Io.GetClassname(), TypeAttributes.Public);// 如果在执行程序的命令行里,启用了生成源码的开关,那么// 将会输出IL class的源码定义。if (Io.genlist)io.Out(".class " + Io.GetClassname() + "{\r\n");}

.NET里,可以使用反射技术来生成assembly、类型和函数,下表就是Exe类的BeginModule函数的源码:

public void BeginModule(string ifile){// .net的动态assembly创建功能,要求跟appdomain绑定appdomain = System.Threading.Thread.GetDomain();appname = getAssemblyName(filename);// 调用AppDomain.DefineDynamiceAssembly创建一个Assembly,以这个为// 起点,可以创建类型,创建函数并执行。实际上,.net上的IronPython等// 动态语言的实现就非常依赖这个技术。appbuild = appdomain.DefineDynamicAssembly(appname,AssemblyBuilderAccess.Save,Io.genpath);// 在.net里,所有的代码实际上都应该保存在一个module里。emodule = appbuild.DefineDynamicModule(filename+"_module",Io.GetOutputFilename(),Io.gendebug);Guid g = System.Guid.Empty;if (Io.gendebug)srcdoc = emodule.DefineDocument(ifile, g, g, g);}

准备工作做好了以后,就可以生成语法树了,编译器在解析语法的过程当中,不停的往语法树里添加元素,如在编译函数的过程中,以处理while循环为例(其中一个调用路径是:program -> outerDecl -> declFunc -> blockOuter -> fcWhile)

void fcWhile(){// 在一般的il或者汇编语言里,循环和判断语句一般都是在不同路径的入口// 出定义好标签(label),再通过判断条件的方式跳转到指定的label实现的String label1 = newLabel();String label2 = newLabel();// 记录当前源码的位置,以便生成IL源码的时候可以把源代码和IL代码对照生成CommentHolder();    /* mark the position in insn stream */// 一般来说,循环语句至少有两个分支代码块,一个是继续循环的代码块,// 一个是跳出循环的代码块,看后面的代码,这个label是循环中执行的代码块// 开始的地方,以便满足条件的时候跳到开头继续执行
  emit.Label(label1);tok.scan();// 做一些错误判断if (tok.getFirstChar() != '(')io.Abort("Expected '('");// 处理循环条件的判断语句相关代码
  boolExpr();CommentFillPreTok();// 跳出循环的labelemit.Branch("brfalse", label2);// 循环内部的代码块,进入blockInner进行循环里面的编译blockInner(label2, label1);    /* outer label, top of loop */// 如果满足循环条件,跳转到代码块开头继续执行emit.Branch("br", label1);// 循环结束,跳出循环的地方
  emit.Label(label2);}

而在emit类型里,各个方法只是将解析出来的语法元素添加到语法树里,语法树的节点、数据结构和操作方法都在IAsm这个类里定义,如下表是 Branch 的源码:

public void Branch(String s, String lname){                // this is the branch sourceNextInsn(1);// 往语法树里添加一个类别为 Branch 的元素
  icur.setIType(IAsm.I_BRANCH);// 指令名称
  icur.setInsn(s);// 指令参数
  icur.setLabel(lname);}

当程序编译完成后,Exe类和Asm类则分别遍历语法树生成最终的结果,在myc编译器的源码里,Parser.declFunc函数通过调用Emit.IL函数来完成程序的生成:

// 因为C程序大部分都是由函数组成的,而且函数使用到的变量或者其他函数,
// 都必须在函数之前定义,所以只需要在解析函数的时候实时生成代码即可
void declFunc(Var e){
#if DEBUGConsole.WriteLine("declFunc token=["+tok+"]\n");
#endifCommentHolder();    // start new comment// 记录解析出来的函数名e.setName(tok.getValue()); /* value is the function name */// 如果函数名是main,则设置一个标识位 - mainseen为true// 在外层的函数里,会通过判断这个标志来确定程序是否有语义错误if (e.getName().Equals("main")){if (Io.gendll)io.Abort("Using main entrypoint when generating a DLL");mainseen = true;}// 函数名也是一个全局变量,放到全局变量表里,以便做语义分析// 例如要调用的函数之前没有定义,则应该报错,在后文我们将// 看到语义方面的处理staticvar.add(e);        /* add function name to static VarList */paramvar = paramList();    // track current param liste.setParams(paramvar);    // and set it in func var// 记录函数里面定义的局部变量localvar = new VarList();    // track new local parameters
  CommentFillPreTok();// 开始生成函数的prolog,例如参数传递,this对象等
  emit.FuncBegin(e);if (tok.getFirstChar() != '{')io.Abort("Expected ‘{'");// 递归分析函数里面的源码blockOuter(null, null);emit.FuncEnd();// 解析完整个函数后,执行代码生成操作
  emit.IL();// 如果需要生成IL源码,则调用LIST函数生成IL源码if (Io.genlist)emit.LIST();emit.Finish();}

而emit.IL函数就是用Exe类型遍历整个语法树,生成结果程序:

public void IL(){IAsm a = iroot;IAsm p;// 循环遍历整个语法树while (a != null){// 根据语法树里各个节点的类型来执行对应的操作switch (a.getIType()){case IAsm.I_INSN:exe.Insn(a);break;case IAsm.I_LABEL:exe.Label(a);break;case IAsm.I_BRANCH:exe.Branch(a);break;// 省略一些代码default:io.Abort("Unhandled instruction type " + a.getIType());break;}p = a;a = a.getNext();}}

而Exe类型执行真正的代码生成,如前面IL函数,在碰到I_BRANCH类型的节点时,调用Exe.Branch函数在动态Assembly (DynamicAssemby) 里生成代码:

public void Branch(IAsm a){Object o = opcodehash[a.getInsn()];if (o == null)Io.ICE("Instruction branch opcode (" + a.getInsn() + ") not found in hash”);// 使用 ILGenerator 类生成跳转IL指令。
  il.Emit((OpCode) o, (Label) getILLabel(a));}

而Asm类也采用类似的方法生成IL源码。

最后,myc编译器里也有一些语义方面的处理,如前面讲到的函数调用时,如果被调用的函数没有定义的话,应该抛出异常的情况,在Parser.statement(即编译实际的C语句的函数)中就有所体现

void statement(){Var e;String vname = tok.getValue();CommentHolder();    /* mark the position in insn stream */switch (io.getNextChar()){case '(':            /* this is a function call */// 省略一些语法处理方面的代码tok.scan();        /* move to next token */// 下面这一行即在生成函数调用代码之前,在全局变量列表里// 查找要调用的函数是否已经定义了,如果没有定义,则应该报告此错误e = staticvar.FindByName(vname); /* find the symbol (e cannot be null) */emit.Call(e);// 省略后面的代码
   }if (tok.getFirstChar() != ';')io.Abort("Expected ';'");tok.scan();}

转载于:https://www.cnblogs.com/vowei/p/4439629.html

MYC编译器源码之代码生成相关推荐

  1. MYC编译器源码之词法分析

    前文  .NET框架源码解读之MYC编译器 和 MYC编译器源码分析之程序入口 分别讲解了 SSCLI 里示例编译器的架构和程序入口,本文接着分析它的词法分析部分的代码. 词法解析的工作都由Tok类处 ...

  2. MYC编译器源码分析之程序入口

    前文.NET框架源码解读之MYC编译器讲了MyC编译器的架构,整个编译器是用C#语言写的,上图列出了MyC编译器编译一个C源文件的过程,编译主路径如下: 首先是入口Main函数用来解析命令行参数,读取 ...

  3. MYC编译器源码之语法分析

    MyC编译器采用自顶向下的方法进行语法解析,这种语法解析方式,一般是从最左边的Token开始,然后自顶向下看哪一条语法规则可能包含这个Token,如果包含这个Token,则自左向右根据这条语法规则逐一 ...

  4. 华为鸿蒙系统学习笔记4-方舟编译器源码下载及安装

    2019华为全球开发者大会将在8月9日-11日在华为松山湖基地召开.本次开发者大会邀请了1500位合作伙伴.5000名全球开发者,将是华为历来规模最大的一次会议.在这次大会上,华为方舟编译器也是关注的 ...

  5. 尝试自动批量翻译方舟编译器源码中的标识符

    在对方舟编译器源码中的近百个标识符/字符串常量进行手工汉化后, 尝试用批量替换+字典的方式对源码标识符进行自动翻译, 目标是自动翻译后达到与手工相同的效果. 字典来源于之前的手动提交. 批量替换之前基 ...

  6. 各个编程语言编译器源码收集

    心血来潮在 Github 收集了各个主流编程语言的编译器源码,下面列出了各个编译器文件链接以及实现语言(可能会有错误). GCC 系列 官网 官方仓库 Github镜像 The GNU Compile ...

  7. 编译方舟编译器源码教程

    前言:本博客主要是对华为开源平台的官方编译文档,做进一步的讲解,以及解决在编译时可能会到的问题.现在,先把编译成功的流程分享出来,后续再对各个工具和术语,以及如何使用编译出来的编译器,做进一步的讲解. ...

  8. 方舟编译器编写鸿蒙软件,华为鸿蒙系统学习笔记4-方舟编译器源码下载及安装...

    2019华为全球开发者大会将在8月9日-11日在华为松山湖基地召开.本次开发者大会邀请了1500位合作伙伴.5000名全球开发者,将是华为历来规模最大的一次会议.在这次大会上,华为方舟编译器也是关注的 ...

  9. 「JVM 编译优化」javac 编译器源码解读

    Java 的编译过程 前端编译: 编译器的前端,将 Java 文件转变成 Class 文件的过程:如 JDK 的 javac.Eclipse JDT 中的增量式编译器 ECJ: 即使编译: JIT,J ...

最新文章

  1. 2021年春季学期-信号与系统-第十三次作业参考答案-第八小题
  2. F2etest+UIRecorder(环境搭建)【1】
  3. python电话号码对应的字符组合_Python3 在字符串中提取字母+数字组合微信账号、电话等 - pytorch中文网...
  4. OS- -进程详详解
  5. oracle 打印值,oracle – 在SQL Developer中打印变量的值
  6. PS网页设计教程XXIX——如何在PS中设计一个画廊布局
  7. 腾讯花85亿买岛;微信发原图或泄露位置信息?高通逼因特尔把Modem芯片业务卖给苹果?小米9官网正式下架……...
  8. STM32的学习记录--2.WiFi模块的使用
  9. linux 免密安装
  10. 从爬取的文章 HTML 中提取出中文关键字
  11. void类型及void指针
  12. 在PyQt中构建 Python 菜单栏、菜单和工具栏
  13. php涉及数据库操作时响应很慢。
  14. Mac OS 文件、文件夹重命名的方法
  15. php mysql 任务队列_PHP+MySQL实现消息队列步骤详解
  16. 我能读懂的NLP技术科普书,可能也就只有它了(T ^ T)
  17. NX/UG二次开发—其他—NX中C++调用C#工具并传参
  18. 在麒麟桌面操作系统编译安装postgresql的经历
  19. 《黑白团团队》第八次团队作业:Alpha冲刺 第三天
  20. 梁冬一席演讲:人生的最高境界是“不二”

热门文章

  1. 这可能是最全面的Java学习路线了
  2. powerpoint无法加载宏mathtype
  3. VBA 更新自定义安装的加载宏文件版本
  4. 新闻软文写作_软文写作网_产品软文写作_活动软文写作|Giiso智搜
  5. 适合前端学习的设计模式有哪些?
  6. 博彦科技面试—20190802—周五 14:00
  7. 青少年心理健康知识PPT模板
  8. 专访集智俱乐部创始人张江:冲破藩篱,敢想敢为
  9. PowerDNS - Realtime Update 的 DNS 伺服器介紹
  10. 使用递归查看所有子目录及文件