自制编译器:后端代码生成(三)

自制编译器:后端代码生成(一) 后端早就已经弄的差不多了,因为学校论文的事情耽搁的比较久,一直到现在才发博客。 所谓的编译器后端的作用就是将语法树翻译成目标机器码。所谓目标机器码,考虑到直接翻译成具体平台(如X86,ARM等)过于复杂,因此先设计一个虚拟机,并翻译成这个虚拟机的机器码。 对于虚拟机以及其指令格式可参考这篇文章http://blog.youkuaiyun.com/roger__wong/article/details/89 阅读详情

(14)expr

/*                         Type
* expr -->  (expr)           0
                ids|             1
              number|           2
              literal|           3
             func-call|         4
            expr  ops expr|      5
*/

public void genCode() {
		
		ArrayList<Code> al=BackendClassManager.tFunc.codes;
		if(Type==0)
		{
			expr1.genCode();
		}
		else if(Type==1)
		{
			ids.genCode();
		}
		else if(Type==2)
		{
			Code code;
			if(number.isInt==true)
			{
				code=new Code(0x01);
				code.Operands.add(number.toString());
			}
			else
			{
				code=new Code(0x02);
				code.Operands.add(number.toString());
			}
			al.add(code);
		}
		else if(Type==3)
		{
			literal.genCode();
		}
		else if(Type==4)
		{
			fc.genCode();
		}
		else if(Type==5)
		{
			expr1.genCode();
			Code code=new Code(0x05);
			int pos=BackendClassManager.nameSlot.keySet().size();
			BackendClassManager.nameSlot.put(this.toString(), pos);
			code.Operands.add(String.valueOf(pos));
			al.add(code);
			expr2.genCode();
			code=new Code(0x03);
			code.Operands.add(String.valueOf(pos));
			al.add(code);
			BackendClassManager.expr1=expr1;
			BackendClassManager.expr2=expr2;
			op.genCode();
		}
	}

expr的genCode方法产生的效果是把此expr的值放到栈顶。

如果type=0,则递归调用括号里的expr的genCode;如果type=1,则把ids的值放到栈顶;如果type=2,先判断立即数的类型之后再压入立即数;如果type=3,把字符串的句柄压入栈顶;type=4调用funccall的genCode;type=5,先生成expr1的字节码,然后将结果暂存到局部变量表,之后生成expr2的字节码,再把之前的结果压栈,调用op的genCode代码为运算符生成字节码。

值得注意的是,调用op的genCode之前首先要把参与运算的expr放到全局变量里,op的字节码生成过程需要使用这两个expr的信息。


(15)funccall

func-call --> ids . func-name(NUL|args)

public void genCode() {
		// TODO Auto-generated method stub
		ArrayList<Code> al=BackendClassManager.tFunc.codes;
		memberfuncdeclare mfc=(memberfuncdeclare)SyntaxTreeGenerator.getFunctions().get(fn.toString()).value;
		ArrayList<ids> ags=ag.getidsList();
		ArrayList<Integer> poses=new ArrayList<Integer>();
		for(int i=0;i<=ags.size()-1;i++)
		{
			ags.get(i).genCode();
			poses.add(CodeGenHelper.StoreToLocalTable(al));
		}
		for(int i=0;i<=poses.size()-1;i++)
		{
			CodeGenHelper.LoadToStack(al, poses.get(i));
		}
		if(mfc.isstatic==true)
		{
			Code code=new Code(0x1B);
			code.Operands.add(IDS.toString()+"."+fn.toString());
			al.add(code);
		
		}
		else
		{
			IDS.genCode();
			Code code=new Code(0x1A);
			code.Operands.add(fn.toString());
			al.add(code);
		}
	}
函数调用的过程如下:

首先所有参数压栈,然后虚拟机执行call指令,给新函数分配执行环境(栈帧),把之前压栈的参数放到局部变量表中(如果有this参数的话this也要以参数的形式放到局部变量表中);函数执行完之后虚拟机释放资源,并把堆栈顶元素(返回值)放到其调用者的堆栈顶。

首先先从符号表中找到这个函数(封装在了SyntaxTreeGenerator中),然后得到所有参数,并存储到局部变量表中(之所以这么做因为参数的获取过程可能会把堆栈顺序打乱),接着把之前存过的所有参数压入堆栈,再判断该函数是否是个静态函数来使用不同的字节码进行调用。


(16)ops

ops --> bitop | logiop | artmop | cprop

public void genCode() {
		if(type==TYPE_BITOP)
		{
			bo.genCode();
		}
		else if(type==TYPE_LOGIOP)
		{
			lo.genCode();
		}
		else if(type==TYPE_ARMTOP)
		{
			ao.genCode();
		}
		else if(type==TYPE_CPROP)
		{
			co.genCode();
		}
		
	}

根据不同的类型调用不同op的genCode方法。


(17)cprop

public void genCode() {
		expr expr1,expr2;
		ArrayList<Code> al=BackendClassManager.tFunc.codes;
		expr1=BackendClassManager.expr1;
		expr2=BackendClassManager.expr2;
		boolean intint=true;
		if(expr1.tp.toString().equals("int") && expr2.tp.toString().equals("int"))
		{
			intint=true;
		}
		if(expr1.tp.toString().equals("double") && expr2.tp.toString().equals("double"))
		{
			intint=false;
		}
		if(expr1.tp.toString().equals("int") && expr2.tp.toString().equals("double"))
		{
			CodeGenHelper.i2d(al);
			intint=false;
			
		}
		if(expr1.tp.toString().equals("double") && expr2.tp.toString().equals("int"))
		{
			int pos=CodeGenHelper.StoreToLocalTable(al);
			Code code=new Code(0x23);
			al.add(code);
			CodeGenHelper.i2d(al);
			CodeGenHelper.LoadToStack(al, pos);
			intint=false;
			
		}
		if(value.equals(">"))
		{
			if(intint==true)
			{
				Code code=new Code(0x16);//比较
				al.add(code);
				code=new Code(0x01);
				code.Operands.add("1");//压入1
				al.add(code);
				code=new Code(0x16);//和1比较
				al.add(code);
				code=new Code(0x24);//取非
				al.add(code);
			}
			else
			{
				Code code=new Code(0x17);//比较
				al.add(code);
				code=new Code(0x02);
				code.Operands.add("1");//压入1
				al.add(code);
				code=new Code(0x17);//和1比较
				al.add(code);
				code=new Code(0x24);//取非
				al.add(code);
			}
		}
		if(value.equals("<"))
		{
			if(intint==true)
			{
				Code code=new Code(0x16);//比较
				al.add(code);
				code=new Code(0x01);
				code.Operands.add("-1");//压入1
				al.add(code);
				code=new Code(0x16);//和1比较
				al.add(code);
				code=new Code(0x24);//取非
				al.add(code);
			}
			else
			{
				Code code=new Code(0x17);//比较
				al.add(code);
				code=new Code(0x02);
				code.Operands.add("-1");//压入1
				al.add(code);
				code=new Code(0x17);//和1比较
				al.add(code);
				code=new Code(0x24);//取非
				al.add(code);
			}
		}
		if(value.equals(">="))
		{
			if(intint==true)
			{
				Code code=new Code(0x16);//比较
				al.add(code);
				code=new Code(0x01);
				code.Operands.add("-1");//压入1
				al.add(code);
				code=new Code(0x16);//和1比较
				al.add(code);
				
			}
			else
			{
				Code code=new Code(0x17);//比较
				al.add(code);
				code=new Code(0x02);
				code.Operands.add("-1");//压入1
				al.add(code);
				code=new Code(0x17);//和1比较
				al.add(code);
				code=new Code(0x24);//取非
				al.add(code);
			}
		}
		if(value.equals("<="))
		{
			if(intint==true)
			{
				Code code=new Code(0x16);//比较
				al.add(code);
				code=new Code(0x01);
				code.Operands.add("1");//压入1
				al.add(code);
				code=new Code(0x16);//和1比较
				al.add(code);
				
			}
			else
			{
				Code code=new Code(0x17);//比较
				al.add(code);
				code=new Code(0x02);
				code.Operands.add("1");//压入1
				al.add(code);
				code=new Code(0x17);//和1比较
				al.add(code);
				
			}
		}
		if(value.equals("=="))
		{
			if(intint==true)
			{
				Code code=new Code(0x16);//比较
				al.add(code);
				code=new Code(0x01);
				code.Operands.add("0");//压入1
				al.add(code);
				code=new Code(0x16);//和0比较
				al.add(code);
				code=new Code(0x24);//取非
				al.add(code);
			}
			else
			{
				Code code=new Code(0x17);//比较
				al.add(code);
				code=new Code(0x02);
				code.Operands.add("0");//压入1
				al.add(code);
				code=new Code(0x17);//和0比较
				al.add(code);
				code=new Code(0x24);//取非
				al.add(code);
			}
		}
		if(value.equals("!="))
		{
			if(intint==true)
			{
				Code code=new Code(0x16);//比较
				al.add(code);
				code=new Code(0x01);
				code.Operands.add("0");//压入1
				al.add(code);
				code=new Code(0x16);//和0比较
				al.add(code);
			
			}
			else
			{
				Code code=new Code(0x17);//比较
				al.add(code);
				code=new Code(0x02);
				code.Operands.add("0");//压入1
				al.add(code);
				code=new Code(0x17);//和0比较
				al.add(code);
			
			}
		}
		
	}
比较运算符的genCode虽然代码比较长但是逻辑很简单。

首先需要根据待比较的expr来决定是否进行类型转换,目前在语言的设计中,只有double和int是可比的,其它类型的比较虽然在编译器不会报错但在运行时中会进行报错。

其次根据类型来调用不同的比较指令,再次根据逻辑需要进行二次比较(因为比较指令会根据大于小于等于返回1、0或-1,和运算符并不等价,需要通过比较两次使之和运算符等价)。


大体上代码就这么多,目前的编译器已经可以编译出正确的字节码了,但目前代码还无法执行,需要等到Runtime写好之后才能运行。


因此本系列博客到这里暂时告一段落,若给语言再添加一些高级点的特性,也需要等到我把Runtime写好再说了。



深度学习编译器后端和运行时 编译器前端将用户代码解析得到计算图 IR,并且做了一些和计算设备无关的通用优化。编译器后端做的优化就和具体的设备有关了(不同设备有不同的 allocator,不同的编程模型,比如英伟达的 CUDA),后端优化更加贴合硬件,会针对硬件特点为 IR 中的计算节点选择在硬件上的算子,然后为每个算子的输入输出分配硬件内存,最终生成一个可以在硬件上执行的任务序列。如上图所示,编译器后端位于前端和硬件驱动层之间,主要做:计算图优化、算子选择、内存分配等工作。 阅读详情

相关推荐

C#编译器的代码生成与后端技术:从源代码到机器码的“变形记”全解析

摘要: 本文深入解析C#代码从高级语言到机器码的完整编译过程,揭秘Roslyn语法解析、IL生成、JIT编译及后端优化的核心技术。通过代码示例演示语法树构建、语义分析、IL反编译和机器码生成,详解循环展开、寄存器分配等优化策略,并提供对象池、LINQ等实战优化技巧。对比JIT与AOT编译的优劣,最后解答常见性能优化误区,帮助开发者深入理解编译器工作原理,写出更高效的C#代码。(149字) 关键词: C#编译原理、Roslyn、IL中间语言、JIT优化、性能调优

java专栏 2086

编译器之后端原理(三十六)

一、编译器的后端技术 1. 编译器的前端技术,重点是让编译器能够读懂程序,无结构的代码文本经过前端的处理以后,就变成了Token、AST和语义属性、符号表等结构化的信息,基于这些信息,可以实现简单的脚本解释器。但很多情况下,需要继续把程序编译成机器能读懂的代码,并高效运行。这时就面临了三个问题: (1)必须了解计算机运行一个程序的原理(也就是运行期机制),只有这样才知道如何生成这样的程序。 (2)要能利用前端生成的AST和属性信息,将其正确翻译成目标代码。 (3)需要对程序做尽可能多的优化,比如让程序

Android系统攻城狮 6082

【编译、链接、装载五】编译器后端——gcc生成的汇编代码

编译器后端——gcc生成的汇编代码

junxuezheng的博客 2164

编译型语言后端原理笔记

一、编译器的后端技术 1. 编译器的前端技术,重点是让编译器能够读懂程序,无结构的代码文本经过前端的处理以后,就变成了Token、AST和语义属性、符号表等结构化的信息,基于这些信息,可以实现简单的脚本解释器。但很多情况下,需要继续把程序编译成机器能读懂的代码,并高效运行。这时就面临了三个问题: (1)必须了解计算机运行一个程序的原理(也就是运行期机制),只有这样才知道如何生成这样的程序。 (2)要能利用前端生成的AST和属性信息,将其正确翻译成目标代码。 (3)需要对程序做尽可能多的优化,比如让程

书忆江南的IT博客 4816

DESeq2差异分析结果不会看?用这个R脚本5分钟生成发表级火山图和热图

本文详细介绍了如何使用R语言快速可视化DESeq2差异分析结果,5分钟内生成可直接用于论文发表的火山图和热图。通过解析DESeq2数据结构、展示ggplot2和EnhancedVolcano包的绘图技巧,以及pheatmap包的热图定制方法,帮助研究者高效呈现差异表达基因分析结果。

weixin_30326515的博客 340

经典编译器组成(前端+优化器+后端)以及LLVM和Clang简介

LLVM简介原文,有兴趣的同学可以直接读原文:LLVMLLVM是开源的编译器(compiler)架构,以C++编写而成,用于优化以随意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、执行时间(run-time)以及空暇时间(idle-time),对开发人员保持开放,并兼容已有脚本。LLVM的设计就采用了上述前端+优化器+后端的设计。如下图所示:基于这个认知,我们可以认为 LLVM 包括了两个概念:一个广义的 LLVM 和一个狭义的 LLVM。

DayDayUp 1万+

allegro 约束管理Constraint Manager(一)

介绍Cadence allegro 约束管理器CM的使用方法。

weixin_38975819的博客 8711

自制编译器:后端代码生成(二)

(8)stmts stmts逻辑上代表一个语句块或一组语句块,对应的生成式和在节点中使用的type如下: stmts --> NUL|            type-->0                   stmt stmts|        type-->1                   if(expr) { stmts} stmts|   type-->2

锐之锋芒 2850

Mac用户福音:三步搞定PyMol开源版,告别许可证弹窗(附Homebrew/MacPorts安装指南)

本文为Mac用户提供三步安装PyMol开源版的详细指南,帮助科研人员告别许可证弹窗烦恼。通过Homebrew或MacPorts包管理器安装开源核心,并与商业版图形界面整合,实现无干扰的分子可视化研究体验。特别适合MacOS用户快速部署PyMol进行结构生物学分析。

weixin_28743405的博客 194

Python程序—定子故障三相永磁同步电动机振动与电流数据集

1.0 kW 电机匝间短路故障数据: 正常匝间短路振动数据: 匝间短路故障,严重度为2.26%: 匝间短路故障,严重度为2.70%: 匝间短路故障,严重度为3.35%: 匝间短路故障,严重度为4.41%: 匝间短路故障,严重度为6.48%: 匝间短路故障,严重度为12.17%: 匝间短路故障,严重度为21.69%1.5 kW 电机匝间短路故障数据: 正常匝间短路振动数据: 匝间短路故障,严重度为1.57%: 匝间短路故障,严重度为1.88%: 匝间短路故障,严重度为2.34%

m0_66465741的博客 1316

Luminal编译器后端:目标代码生成与优化

在深度学习框架的演进历程中,性能优化始终是核心挑战。传统框架如PyTorch采用即时执行(Eager Execution)模式,虽然便于调试但难以实现深度优化。Luminal通过基于搜索的编译技术,将计算图转换为高度优化的目标代码,在保持简洁性的同时实现卓越性能。 本文将深入解析Luminal编译器后端的核心机制,重点探讨目标代码生成与优化策略,揭示其如何在多种硬件平台上实现高效推理。 ## ...

gitblog_00518的博客 753

ESP32+ST7789 LCD屏实战:5分钟搞定中文显示与图片渲染(附完整代码)

本文提供了一份详细的ESP32驱动ST7789 LCD屏幕的实战指南。通过清晰的SPI硬件连接说明、ESP-IDF框架下的初始化代码,以及图片渲染与中文字库构建的核心算法,帮助开发者快速实现中文显示与图片渲染功能,并附有完整的代码示例和性能优化技巧。

weixin_29189377的博客 436

LLVM后端代码生成

编译器工作流程为在高级语言 C/C++ 编译过程中,源代码经历了多个重要阶段,从词法分析到生成目标代码。整个过程涉及前端和后端的多个步骤,并通过中间表示(IR)在不同阶段对代码进行转换、优化和分析。通过上述图像分别展示了 LLVM 的各个流程,和代码在不同流程下的状态,最后我们再回顾一下各个阶段所代表的功能和内容。前端阶段词法分析(Lexical Analysis):源代码被分解为词法单元,如标识符、关键字和常量。

喜欢打篮球的普通人 1263

COMSOL有限元仿真深度指南:连接薄结构与实体结构(I)

COMSOL有限元仿真深度指南:如何在结构力学仿真中连接薄结构与实体结构?

忘了面孔的Batou 1万+

机器人控制基础:电机编码器常见类型

电机编码器是测量电机位置、速度和方向的传感器,主要分为光电、磁性和旋转变压器三类。光电编码器精度高但需防尘,适用于伺服系统;磁性编码器耐环境性强,常用于汽车电机;旋转变压器适应极端环境,多用于军工领域。按信号类型可分为增量式(成本低)和绝对式(断电保留位置)。选型需综合考虑精度、环境适应性及成本。

start_up_go的博客 475

【AI系统】LLVM 后端代码生成

上一篇文章主要讲了 LLVM 的前端和优化层,前端主要对高级语言做一些词法的分析,把高级语言的特性转变为 token,再交给语法分析对代码的物理布局进行判别,之后交给语义分析对代码的的逻辑进行检查。优化层则是对代码进行优化,比如常量折叠、死代码消除、循环展开、内存分配优化等。本文将介绍 LLVM 后端的生成代码过程,LLVM 后端的作用主要是将优化后的代码生成目标代码,目标代码可以是汇编语言、机器码。

生活需要深度 1140

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

终极Idris2编译器架构指南:从源码解析到多后端代码生成的完整探索

Idris2是一款具有一流类型的纯函数式编程语言,其编译器架构融合了现代类型理论与多后端代码生成技术。本文将深入剖析Idris2编译器的内部工作原理,从核心模块设计到多后端代码生成流程,为开发者提供全面的技术洞察。 ## 编译器核心架构概览 Idris2编译器采用模块化设计,主要由核心类型检查器、中间代码优化器和多后端代码生成器三大部分组成。这种分层架构确保了语言特性的实现与目标平台的代码生成

gitblog_00775的博客 771

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

rustc使用LLVM作为编译器后端,什么是LLVM,什么是编译器后端?

要理解“LLVM”和“编译器后端”,我们可以从编译器的整体结构入手,逐步拆解这两个概念,并结合 Rust 编译器(rustc)的具体实现来说明它们的作用。

zmc04088335的专栏 1042

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

【AI系统】AI 编译器后端优化

AI 编译器分为多层架构,最顶层由各种 AI 训练框架编写的神经网络模型架构,一般由 Python 编写,常见的 AI 训练框架有 PyTorch、MindSpore、PaddlePaddle 等。在导入 AI 编译器时需要用对应框架的converter功能转换为 AI 编译器统一的 Graph IR,并在计算图级别由进行计算图级优化,也叫前端优化。前端优化主要的计算图优化包括图算融合、数据排布转换、内存优化、死代码消除,这些优化是硬件无关的通用优化。在得到优化后的计算图后,将其转换为TensorIR。

生活需要深度 1370

汇编代码生成和编译器的后端

算式识别程序,编译器后端,汇编代码生成

JJ-Li的博客 1071

自制编译器:后端代码生成

所谓的编译器后端的作用就是将语法树翻译成目标机器码。所谓目标机器码,考虑到直接翻译成具体平台(如X86,ARM等)过于复杂,因此先设计一个虚拟机,并翻译成这个虚拟机的机器码。 对于虚拟机以及其指令格式可参考这篇文章http://blog.youkuaiyun.com/roger__wong/article/details/8947720,如何去尝试实现这个虚拟机是在我的另外一个系列的博客里进行论述。

西红柿炒土豆 1784

深度剖析Kotlin编译器后端:从IR优化管道到JVM代码生成的完整流程

Kotlin编译器后端通过**中间表示(IR)** 实现了"一次编写,多平台运行"的能力。不同于传统编译器直接将源码转换为目标代码,Kotlin首先将代码编译为与平台无关的IR,再通过平台特定后端生成机器码。这种架构使Kotlin能够高效支持JVM、JS、Native和WASM等多目标平台。本文将以JVM后端为例,详解IR优化管道与目标代码生成的关键技术细节。 ## Kotlin IR基础架构

gitblog_00319的博客 810

【LLM编译器】LLVM基础:IR、Pass与后端代码生成

LLVM 深知 Pass 出错的风险,因此提供了一套验证器(Verifier)来检查 IR 的合法性与一致性。llvm-as生成的 IR 在输入后端前会经过验证。此外,opt工具在执行 Pass 之后也会调用验证器。每条指令的操作数数量与类型是否匹配SSA 形式是否被违反——每个值是否被定义恰好一次基本块的终止指令是否正确(只能以终结指令结尾)指令的支配关系是否成立——使用处是否被定义处支配下面这个简单的示例展示了一个违反 SSA 形式entry:%x 在此定义跳转到 merge 块。

ruiqideng的博客 410
上一篇: 自制编译器:后端代码生成(二)
下一篇: Android下同时使用WIFI与3G网络(二)
ROger__Wong
博客等级 码龄14年 250粉丝 78原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值