生成字节码

在这篇文章中,我们将看到如何为我们的语言生成字节码。 到目前为止,我们已经看到了如何构建一种语言来表达我们想要的东西,如何验证该语言,如何为该语言构建编辑器,但实际上我们仍然无法运行代码。 是时候解决这个问题了。 通过为JVM进行编译,我们的代码将能够在各种平台上运行。 对我来说听起来很棒!

jvm_bytecode_write_your_own_compiler

建立自己的语言的系列

以前的帖子:

  1. 建立词法分析器
  2. 建立一个解析器
  3. 创建带有语法突出显示的编辑器
  4. 使用自动补全功能构建编辑器
  5. 将解析树映射到抽象语法树
  6. 模型转换模型
  7. 验证方式

代码在GitHub上的标签08_bytecode可用

添加打印声明

在跳入字节码生成之前,我们只需在我们的语言中添加一条打印语句即可。 这很容易:我们只需要在词法分析器和解析器定义中更改几行,就可以了。

// Changes to lexer
PRINT              : 'print';// Changes to parser
statement : varDeclaration # varDeclarationStatement| assignment     # assignmentStatement| print          # printStatement ;print : PRINT LPAREN expression RPAREN ;

我们的编译器的一般结构

让我们从编译器的入口点开始。 我们将从标准输入或文件中获取代码(将被指定为第一个参数)。 一旦获得代码,我们将尝试构建AST并检查词汇和语法错误。 如果没有,我们将验证AST并检查语义错误。 如果仍然没有错误,我们继续进行字节码生成。

fun main(args: Array<String>) {val code : InputStream? = when (args.size) {0 -> System.`in`1 -> FileInputStream(File(args[0]))else -> {System.err.println("Pass 0 arguments or 1")System.exit(1)null}}val parsingResult = SandyParserFacade.parse(code!!)if (!parsingResult.isCorrect()) {println("ERRORS:")parsingResult.errors.forEach { println(" * L${it.position.line}: ${it.message}") }return}val root = parsingResult.root!!println(root)val errors = root.validate()if (errors.isNotEmpty()) {println("ERRORS:")errors.forEach { println(" * L${it.position.line}: ${it.message}") }return}val bytes = JvmCompiler().compile(root, "MyClass")val fos = FileOutputStream("MyClass.class")fos.write(bytes)fos.close()
}

请注意,在此示例中,我们始终生成一个名为MyClass的类文件。 大概以后,我们想找到一种为类文件指定名称的方法,但是现在这已经足够了。

使用ASM生成字节码

现在,让我们潜入有趣的部分。 JvmCompiler编译方法是我们生成字节的地方,以后我们将这些字节保存到类文件中。 我们如何产生这些字节? 在ASM的帮助下,该库是生成Bytecode的库。 现在,我们可以自己生成bytes数组,但要点是,它将涉及到一些无聊的任务,例如生成类池结构。 ASM为我们做到了。 我们仍然需要对JVM的结构有一些了解,但是我们可以生存下来而无需成为专家的精髓。

class JvmCompiler {fun compile(root: SandyFile, name: String) : ByteArray {// this is how we tell ASM that we want to start writing a new class. We ask it to calculate some values for usval cw = ClassWriter(ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS)// here we specify that the class is in the format introduced with Java 8 (so it would require a JRE >= 8 to run)// we also specify the name of the class, the fact it extends Object and it implements no interfacescw.visit(V1_8, ACC_PUBLIC, name, null, "java/lang/Object", null)// our class will have just one method: the main method. We have to specify its signature// this string just says that it takes an array of Strings and return nothing (void)val mainMethodWriter = cw.visitMethod(ACC_PUBLIC or ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null)mainMethodWriter.visitCode()// labels are used by ASM to mark points in the codeval methodStart = Label()val methodEnd = Label()// with this call we indicate to what point in the method the label methodStart correspondsmainMethodWriter.visitLabel(methodStart)// Variable declarations:// we find all variable declarations in our code and we assign to them an index value// our vars map will tell us which variable name corresponds to which indexvar nextVarIndex = 0val vars = HashMap<String, Var>()root.specificProcess(VarDeclaration::class.java) {val index = nextVarIndex++vars[it.varName] = Var(it.type(vars), index)mainMethodWriter.visitLocalVariable(it.varName, it.type(vars).jvmDescription, null, methodStart, methodEnd, index)}// time to generate bytecode for all the statementsroot.statements.forEach { s ->when (s) {is VarDeclaration -> {// we calculate the type of the variable (more details later)val type = vars[s.varName]!!.type// the JVM is a stack based machine: it operated with values we have put on the stack// so as first thing when we meet a variable declaration we put its value on the stacks.value.pushAs(mainMethodWriter, vars, type)// now, depending on the type of the variable we use different operations to store the value// we put on the stack into the variable. Note that we refer to the variable using its index, not its namewhen (type) {IntType -> mainMethodWriter.visitVarInsn(ISTORE, vars[s.varName]!!.index)DecimalType -> mainMethodWriter.visitVarInsn(DSTORE, vars[s.varName]!!.index)else -> throw UnsupportedOperationException(type.javaClass.canonicalName)}}is Print -> {// this means that we access the field "out" of "java.lang.System" which is of type "java.io.PrintStream"mainMethodWriter.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")// we push the value we want to print on the stacks.value.push(mainMethodWriter, vars)// we call the method println of System.out to print the value. It will take its parameter from the stack// note that we have to tell the JVM which variant of println to call. To do that we describe the signature of the method,// depending on the type of the value we want to print. If we want to print an int we will produce the signature "(I)V",// we will produce "(D)V" for a doublemainMethodWriter.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(${s.value.type(vars).jvmDescription})V", false)}is Assignment -> {val type = vars[s.varName]!!.type// This code is the same we have seen for variable declarationss.value.pushAs(mainMethodWriter, vars, type)when (type) {IntType -> mainMethodWriter.visitVarInsn(ISTORE, vars[s.varName]!!.index)DecimalType -> mainMethodWriter.visitVarInsn(DSTORE, vars[s.varName]!!.index)else -> throw UnsupportedOperationException(type.javaClass.canonicalName)}}else -> throw UnsupportedOperationException(s.javaClass.canonicalName)}}// We just says that here is the end of the methodmainMethodWriter.visitLabel(methodEnd)// And we had the return instructionmainMethodWriter.visitInsn(RETURN)mainMethodWriter.visitEnd()mainMethodWriter.visitMaxs(-1, -1)cw.visitEnd()return cw.toByteArray()}}

关于类型

好的,我们已经看到我们的代码使用类型。 这是必需的,因为根据类型,我们需要使用不同的说明。 例如,将值放入整数变量中,我们使用ISTORE;而将值放入双重变量中,我们使用DSTORE 。 当我们以整数调用System.out.println时,我们需要指定签名(I)V,而当我们调用它以打印双精度字符时,则需要指定(D)V

为了做到这一点,我们需要知道每个表达式的类型。 在我们的超级简单语言中,我们现在仅使用intdouble 。 在真实的语言中,我们可能想使用更多的类型,但这足以向您展示这些原理。

interface SandyType {// given a type we want to get the corresponding string used in the JVM// for example: int -> I, double -> D, Object -> Ljava/lang/Object; String -> [Ljava.lang.String;val jvmDescription: String
}object IntType : SandyType {override val jvmDescription: Stringget() = "I"
}object DecimalType : SandyType {override val jvmDescription: Stringget() = "D"
}fun Expression.type(vars: Map<String, Var>) : SandyType {return when (this) {// an int literal has type int. Easy :)is IntLit -> IntTypeis DecLit -> DecimalType// the result of a binary expression depends on the type of the operandsis BinaryExpression -> {val leftType = left.type(vars)val rightType = right.type(vars)if (leftType != IntType && leftType != DecimalType) {throw UnsupportedOperationException()}if (rightType != IntType && rightType != DecimalType) {throw UnsupportedOperationException()}// an operation on two integers produces integersif (leftType == IntType && rightType == IntType) {return IntType// if at least a double is involved the result is a double} else {return DecimalType}}// when we refer to a variable the type is the type of the variableis VarReference -> vars[this.varName]!!.type// when we cast to a value, the resulting value is that type :)is TypeConversion -> this.targetType.toSandyType()else -> throw UnsupportedOperationException(this.javaClass.canonicalName)}
}

表达方式

如我们所见,JVM是基于堆栈的计算机。 因此,每次我们想使用一个值时,都会将其压入堆栈,然后执行一些操作。 让我们看看如何将值推入堆栈

// Convert, if needed
fun Expression.pushAs(methodWriter: MethodVisitor, vars: Map<String, Var>, desiredType: SandyType) {push(methodWriter, vars)val myType = type(vars)if (myType != desiredType) {if (myType == IntType && desiredType == DecimalType) {methodWriter.visitInsn(I2D)} else if (myType == DecimalType && desiredType == IntType) {methodWriter.visitInsn(D2I)} else {throw UnsupportedOperationException("Conversion from $myType to $desiredType")}}
}fun Expression.push(methodWriter: MethodVisitor, vars: Map<String, Var>) {when (this) {// We have specific operations to push integers and double valuesis IntLit -> methodWriter.visitLdcInsn(Integer.parseInt(this.value))is DecLit -> methodWriter.visitLdcInsn(java.lang.Double.parseDouble(this.value))// to push a sum we first push the two operands and then invoke an operation which// depend on the type of the operands (do we sum integers or doubles?)is SumExpression -> {left.pushAs(methodWriter, vars, this.type(vars))right.pushAs(methodWriter, vars, this.type(vars))when (this.type(vars)) {IntType -> methodWriter.visitInsn(IADD)DecimalType -> methodWriter.visitInsn(DADD)else -> throw UnsupportedOperationException("Summing ${this.type(vars)}")}}is SubtractionExpression -> {left.pushAs(methodWriter, vars, this.type(vars))right.pushAs(methodWriter, vars, this.type(vars))when (this.type(vars)) {IntType -> methodWriter.visitInsn(ISUB)DecimalType -> methodWriter.visitInsn(DSUB)else -> throw UnsupportedOperationException("Summing ${this.type(vars)}")}}is DivisionExpression -> {left.pushAs(methodWriter, vars, this.type(vars))right.pushAs(methodWriter, vars, this.type(vars))when (this.type(vars)) {IntType -> methodWriter.visitInsn(IDIV)DecimalType -> methodWriter.visitInsn(DDIV)else -> throw UnsupportedOperationException("Summing ${this.type(vars)}")}}is MultiplicationExpression -> {left.pushAs(methodWriter, vars, this.type(vars))right.pushAs(methodWriter, vars, this.type(vars))when (this.type(vars)) {IntType -> methodWriter.visitInsn(IMUL)DecimalType -> methodWriter.visitInsn(DMUL)else -> throw UnsupportedOperationException("Summing ${this.type(vars)}")}}// to push a variable we just load the value from the symbol tableis VarReference -> {val type = vars[this.varName]!!.typewhen (type) {IntType -> methodWriter.visitVarInsn(ILOAD, vars[this.varName]!!.index)DecimalType -> methodWriter.visitVarInsn(DLOAD, vars[this.varName]!!.index)else -> throw UnsupportedOperationException(type.javaClass.canonicalName)}}// the pushAs operation take care of conversions, as neededis TypeConversion -> {this.value.pushAs(methodWriter, vars, this.targetType.toSandyType())}else -> throw UnsupportedOperationException(this.javaClass.canonicalName)}
}

摇篮

我们还可以创建gradle任务来编译源文件

main = "me.tomassetti.sandy.compiling.JvmKt"args = "$sourceFile"classpath = sourceSets.main.runtimeClasspath
}

结论

我们没有详细介绍,我们急于浏览代码。 我的目的只是给您概述用于生成字节码的一般策略。 当然,如果您想构建一种严肃的语言,则需要做一些研究并了解JVM的内部,这是无可避免的。 我只是希望这个简短的介绍足以使您了解到这并不那么令人恐惧或复杂,大多数人都认为。

翻译自: https://www.javacodegeeks.com/2016/09/generating-bytecode.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/351799.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java迭代器contains的问题

功能&#xff1a;ArrayList去除集合中字符串的重复值(字符串的内容相同)&#xff0c;思路&#xff1a;创建新集合方式。 第一种编译运行没问题&#xff0c;第二种写法出错&#xff0c;原因是不可以两次使用it.next()。 错误提示&#xff1a;Exception in thread "main&q…

ad如何镜像器件_使用 Dockerfile 制作镜像

前面几篇文章已经给大家介绍了 Docker 的基本概念&#xff0c;相信大家也会使用 Docker 运行自己想要的容器了。但是只有学会制作镜像&#xff0c;才能将 Docker 应用到我们的项目中去。下面我们就来学习如何使用 Dockerfile 来制作镜像。Dockerfile 是一个文本文件&#xff0c…

centos7.5部署ELk

第1章 环境规划 1.1 ELK介绍 ELK是ElasticSerach、Logstash、Kibana三款产品名称的首字母集合&#xff0c;用于日志的搜集和搜索。 Elasticsearch&#xff1a;是一个开源分布式搜索引擎&#xff0c;提供搜集、分析、存储三大功能&#xff0c;特点是分布式、零配置、自动发…

使用Google Test的一个简单例子

0. 引子 本例是从 gtest-1.5.0 自带的 sample 中的 sample1 改写而来&#xff0c;笔者只添加了一个求 n 的阶层的函数&#xff0c;如下。 void Factorial(int n, int & result ) { result 1; for (int i 1; i < n; i) result * i; } 目的是想测试像这样将返回值放在参…

Java静态方法与非静态方法的泛型

Java中&#xff0c;非静态方法可以使用类的泛型&#xff0c;也可以定义自己的泛型&#xff1b;静态方法由于随着类的加载而加载&#xff0c;不能访问类的泛型&#xff08;因为在创建对象的时候才确定&#xff09;&#xff0c;因此必须定义自己的泛型类型。 详细请参考&#xf…

Android Studio 日志工具

在项目中提供5个方法打印日志 Log.v() 最常见的日志信息 Log.d() 调试信息 Log.i() 用于打印用户操作行为 Log.w()警告潜在风险 Log.e()报错信息 TAG 填入类名就好 msg:要打印的信息 也可以对信息进行过滤 点他弹出自定义的日志过滤器 转载于:https://www.cnblogs.com/feizianq…

jpa加密_使用JPA侦听器的数据库加密

jpa加密最近&#xff0c;我不得不将数据库加密添加到一些字段中&#xff0c;并且发现了很多不好的建议。 建筑问题 最大的问题是建筑。 如果持久性管理器静静地处理您的加密&#xff0c;那么根据定义&#xff0c;您的体系结构将在持久性和安全性设计之间要求紧密而不必要的绑…

同一进程中的线程究竟共享哪些资源

线程共享的环境包括&#xff1a;进程代码段、进程的公有数据(利用这些共享的数据&#xff0c;线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。 进程拥有这许多共性的同时&#xff0c;还拥有自己的个性。有了这些…

物联lot是什么意思_什么是物联网,物联网(lOT)简介

什么是物联网物联网(The Internet of Things&#xff0c;简称IOT)是指通过各种信息传感器、射频识别技术、全球定位系统、红外线感应器、激光扫描器等各种装置与技术&#xff0c;实时采集任何需要监控、 连接、互动的物体或过程&#xff0c;采集其声、光、热、电、力学、化 学、…

Python 位操作运算符

&按位与运算符&#xff1a;参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0(a & b) 输出结果 12 &#xff0c;二进制解释&#xff1a; 0000 1100|按位或运算符&#xff1a;只要对应的二个二进位有一个为1时&#xff0c;结果位就为1。(a | b) 输出结果 6…

JavaOne 2016后续活动

我很高兴今年参加了JavaOne&#xff0c;我可以用一个词概括一下这一经验&#xff1a;Brilliant。 对于我来说&#xff0c;今年与往年相比有很大不同&#xff0c;因为我在周日有一个演讲要共同演讲&#xff0c;而我剩下的一周时间都可以参加会议。 因此&#xff0c;我了解到在Ja…

python对初学者的看法_python学习之道(1)——新手小白对print()函数的理解,Python,之路,一,浅谈...

Python学习之路(一) ——浅谈新手小白对print()函数的理解写在前面笔者目前为在校大四学生(某末流211)&#xff0c;大学生活即将画上终点&#xff0c;然而却还没有真正精通一门语言&#xff0c;很是惭愧。在大学期间参加了各种文体活动&#xff0c;获得了很多次演讲比赛的奖项&…

理解Windows内核模式与用户模式

&#xfeff;&#xfeff;1、基础 运行 Windows 的计算机中的处理器有两个不同模式&#xff1a;“用户模式”和“内核模式”。根据处理器上运行的代码的类型&#xff0c;处理器在两个模式之间切换。应用程序在用户模式下运行&#xff0c;核心操作系统组件在内核模式下运行。多个…

判断使用设备是PC还是phone

<script type"text/javascript"> //如果是手机设备&#xff0c;则.. if(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) {window.location.href "http://www."; } </script><style type"text/css"> me…

求1+2+3+...+n

题目描述 求123...n&#xff0c;要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句&#xff08;A?B:C&#xff09;。 1、根据基本公式展开&#xff0c;利用java的幂函数代替乘法&#xff0c;利用位移运算代替除法 public class Solution {pub…

1 京东_推荐好友拿好礼 | 每1积分可兑换30元京东电子卡

三季度转推介活动火热进行中该积分最终以被推荐客户9月30日金融资产余额为依据积分兑现时间为活动结束后15个工作日我行会将京东电子卡密码通过短信方式直接发送至推荐客户预留手机号码上(请推荐客户登记真实、准确的电话号码&#xff0c;否则无法兑现积分奖励)尊敬的客户&…

在cmd命令行下编译运行C/C++源文件

一直用java来写程序&#xff0c;java配置好jre路径之后&#xff0c;在cmd下编译运行&#xff0c;很方便。 刚好要给一个舍友改下C程序&#xff0c;想到可不可以像java一样在环境变量里配置好C的编译路径呢&#xff1f; 于是上网搜了一下&#xff0c;得到如下结果&#xff1a; 一…

制作程序化装饰花纹图案_用装饰器设计图案装饰

制作程序化装饰花纹图案装饰图案是广泛使用的结构图案之一。 此模式在运行时动态更改对象的功能&#xff0c;而不会影响对象的现有功能。 简而言之&#xff0c;此模式通过包装将附加功能添加到对象。 问题陈述&#xff1a; 想象一个场景&#xff0c;我们有一个比萨饼&#xff…

10停止nginx命令 win_windows版nginx快速操控神器(重启,关闭)

众所周知,Windows 版本的Nginx 是linux版本的阉割版,但是在开发或者个人测试的时候,还是非常好用的&#xff0c;但是nginx运行的命令自己敲打起来不是那么的方便,因此呢&#xff0c;我们就写了个Bat批处理小程序&#xff0c;封装了这些命令&#xff0c;2.1版本您只需要配置下 N…

学习vi和vim编辑器(8):全局替换(1)

本章学习vi编辑器中的全局替换命令。通过全局替换命令&#xff0c;可以自动替换文件中所有出现过的某个单词。全局替换一般会用到两个ex命令&#xff1a;":g"(global)&#xff0c;":s"(substitute)。 替换命令&#xff1a; 替换命令的语法如下&#xff1…