从零写一个编译器(十一):代码生成之Java字节码基础
项目的完整代码在 C2j-Compiler
前言
第十一篇,终于要进入代码生成部分了,但是但是在此之前,因为我们要做的是C语言到字节码的编译,所以自然要了解一些字节码,但是由于C语言比较简单,所以只需要了解一些字节码基础
JVM的基本机制
JVM有一个执行环境叫做stack frame
这个环境有两个基本数据结构
- 执行堆栈:指令的执行,都会围绕这个堆栈来进行
- 局部变量数组,参数和局部变量就存储在这个数组。
还有一个PC指针,它指向下一条要执行的指令。
举一个例子
int f(int a, int b) {
return a+b;
}
f(1,2);
JVM的执行环境是这样变化的
stack:
localarray:1,2
pc:把a从localarray取出放到stack
stack:1
localarray:2
pc:把b从localarray取出放到stack
stack:1,2
localarray:
pc:把a,b弹出堆栈并且相加压入堆栈
对于JVM提供的对象
.class public CSourceToJava
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Hello World!"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
return
.end method
.end class
getstatic、ldc和invokevirtual都相当于JVM提供的指令
getstatic和ldc相当于压入堆栈操作。invokevirtual则是从堆栈弹出参数,然后调用方法
stack: out "Hello World!"
JVM的基本指令
pusu load store
JVM的运行基本都是围绕着堆栈来进行,所以指令也都是和堆栈相关,比如进行一个乘法1 * 2:
bipush 1
bipush 2
imul
可以看到JVM的指令操作时带数据的类型,b代表byte,也就是只能操作-128 ~ 128之间的数,而i代表是整形操作,所以相应也会有sipush等等了
下面加入要把1 * 2打印用prinft打印在控制台上,就需要把out对象压入堆栈,此时的堆栈:
stack: 2 out
但是调用out的参数需要在堆栈顶部,所以这时候就需要两个指令iload、istore
istore 0把2放到局部变量队列,再把out压入堆栈,再用iload 0把2放入堆栈中
stack: out 2
局部变量和函数参数
局部变量
在字节码里,局部变量和函数参数都会存储在队列上
int func() {
int a;
int b;
a = 1;
b = 2;
return a + b;
}
看一下这个方法执行的时候堆栈的变化情况
// 执行a = 1,把1压到stack上,再把1放入到队列里
stack:
array:1
// 执行b = 1,也同理
stack:
array:1, 2
最后的return也有相应的return指令,所以完整的指令如下
sipush 1
istore 0
sipush 2
istore 1
iload 0
iload 1
iadd
ireturn
函数参数
int func(int a, int b, int c, int d){}
在调用这个函数的适合,函数参数就会按照顺序被压入堆栈中,然后拷贝到队列上
stack: a b c d
array:
stack:
array: d c b a
所以在之后的代码生成部分就需要一个来找到局部变量的位置的函数
数组
创建数组
下面这段指令的作用是创建一个大小为100的整形数组
sipush 100
newarray int
astore 0
- sipush 100 把元素个数压入堆栈
- newarray int 创建一个数组,后面是数据类型
- astore 表示把数组对象移入队列 a表示的是一个对象引用
读取数组
下面这段指令是读取数组的第66个元素
aload 0
sipush 66
iaload
- aload 0 把数组对象放到堆栈上
- sipush 放入要读取的元素下标
- iaload 把读取的值压入堆栈
元素赋值
aload 0
sipush 7
sipush 10
iastore
- aload 0 把数组对象加载到堆栈
- sipush 7 把要赋值的值压入堆栈
- sipush 10 把元素下标压入堆栈
- iastore 进行赋值
结构体
C语言里的结构体其实就相当于没有方法只有属性的类,所以可以把结构体编译成一个类
创建一个类
new MyClass //创建一个名字为MyClass的类
invokespecial ClassName/<init>() V //调用类的无参构造函数
例子
public class MyClass {
public int a;
public char c;
public MyClass () {
this.a = 0;
this.c = 0;
}
}
public class MyClass生成下面的代码,都是对应生成一个类的特殊指令
.class public MyClass
.super java/lang/Object
下面的则是对应属性的声明
.field public c C
.field public a I
声明完属性,就是构造函数了,首先是先把类的实例加载到堆栈,再调用它的父类构造函数,对属性的赋值:
- 加载类的实例到堆栈上 aload 0
- 压入值 sipush 0
- 赋值的对应指令 putfield MyClass/c C
aload 0
invokespecial java/lang/Object/<init>()V
aload 0
sipush 0
putfield MyClass/c C
aload 0
sipush 0
putfield MyClass/a I
return
完整的对应的Java字节码如下:
.class public MyClass
.super java/lang/Object
.field public c C
.field public a I
.method public <init>()V
aload 0
invokespecial java/lang/Object/<init>()V
aload 0
sipush 0
putfield MyClass/c C
aload 0
sipush 0
putfield MyClass/a I
return
.end method
.end class
读取类的属性
aload 3 ;假设类实例位于局部变量队列第3个位置
putfield ClassName/x I
结构体数组
下面的指令创建了10个字符串类型的数组,这时候堆栈上的对象是一个引用,指向heap上一个10个字符串类型的数组
sipush 10
anewarray java/lang/String
下面的指令则是对数组的第一个元素进行赋值
astore 0
aload 0
sipush 0
ldc "hello world"
aastore
所以对于我们自己定义的类也是一样的
sipush 10
anewarray MyClass
astore 0
下面则是对数组第一个下标生成一个MyClass对象
aload 0
sipush 1
new MyClass
invokespecial CTag/<init>()V
aastore
下面是对数组里的对象的属性的取值和赋值操作,只是组合了之前的指令而已
aload 0
sipush 1
aaload
sipush 1
putfield MyClass/x I
aload 0
sipush 1
aaload
getfield MyClass/x I
分支语句
JVM指令还有两个个非常重要的指令就是分支和循环指令,我们先来看分支指令
if (1 < 2) {
a = 1;
} else {
a = 2;
}
上面对应的JVM指令如下:
- 先把1和2压入堆栈
- if_cmpge指令是大于等于,即如果1大于等于2就去执行else分支
- goto指令是跳转到相应的标签,也就是执行完if,就跳出else部分
sipush 1
sipush 2
if_cmpge branch0
sipush 1
astore 0
goto out_branch0
branch0:
sipush 2
istore 0
out_branch0:
sipush 3
istore 0
循环语句
基本的JVM指令只剩循环语句了,逻辑也不困难,基本的JVM指令相对于汇编算是非常简单了
for (i = 0; i < 3; i++) {
a[i] = i;
}
上面生成的对应字节码如下(假设现在变量i在队列的第5个位置,a在队列的第2个位置):
- 首先对i赋值
- 再把3压入堆栈和i做比较,判断i < 3
- 之后就是对数组的操作
- 然后修改i的值
- 返回loop0继续判断i < 3
sipush 0
istore 5
loop0:
iload 5
sipush 3
if_icmpge branch0
aload 2 ;加载数组
iload 3 ;加载标i
iload 3 ;加载变量i
iastore ;把i的值存入到a[i]
iload 3 ;加i
sipush 1 ;把1压入堆栈
iadd ;i++
istore 3 ;把i+1后的值放入到i的队列上的位置
goto loop0 ;跳转到循环开头
branch0:
小结
这一篇主要就是了解一下Java基本的字节码,因为C语言的语法比较简单,所以只需要知道一点就足够生成代码了。所以相对于汇编来说,是非常简单的了。这样下一篇就可以正式进入代码生成部分
另外,欢迎Star这个项目!
从零写一个编译器(十一):代码生成之Java字节码基础的更多相关文章
- 从零写一个编译器(十三):代码生成之遍历AST
项目的完整代码在 C2j-Compiler 前言 在上一篇完成对JVM指令的生成,下面就可以真正进入代码生成部分了.通常现代编译器都是先把生成IR,再经过代码优化等等,最后才编译成目标平台代码.但是时 ...
- 从零写一个编译器(九):语义分析之构造抽象语法树(AST)
项目的完整代码在 C2j-Compiler 前言 在上一篇完成了符号表的构建,下一步就是输出抽象语法树(Abstract Syntax Tree,AST) 抽象语法树(abstract syntax ...
- 手把手教你从零写一个简单的 VUE
本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...
- 手把手教你从零写一个简单的 VUE--模板篇
教程目录1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 Hello,我又回来了,上一次的文章教会了大家如何书写一个简单 VUE,里面实现了VUE 的数据驱动视图 ...
- 打造一个简单的Java字节码反编译器
简介 本文示范了一种反编译Java字节码的方法,首先通过解析class文件,然后将解析的结果转成java代码.但是本文并没有覆盖所有的class文件的特性和指令,只针对部分规范进行解析. 所有的代码代 ...
- 学了编译原理能否用 Java 写一个编译器或解释器?
16 个回答 默认排序 RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和 ...
- 从零写一个Asp.net core手脚架(模型验证)
一个asp.net core项目,一定包含了各种的实体,在RESTful api里面,有很多的参数传递,不建立实体则大量的参数需要自定验证正确性,并且Action上面会写的密密麻麻的参数 在asp.n ...
- 从零写一个Asp.net core手脚架 (异常处理)
既然是手脚架,那么肯定得明白,手脚架是有限资源的一个整合,我们尽可能完善它,并保留可扩展性才是最终目的,尽可能减少硬编码,让业务不满足的情况下,可以自行修改 我们把解决方案取名Asp.netCoreT ...
- 动手写一个Remoting接口测试工具(附源码下载)
基于.NET开发分布式系统,经常用到Remoting技术.在测试驱动开发流行的今天,如果针对分布式系统中的每个Remoting接口的每个方法都要写详细的测试脚本,无疑非常浪费时间.所以,我想写一个能自 ...
随机推荐
- 【学习笔记】动态规划—斜率优化DP(超详细)
[学习笔记]动态规划-斜率优化DP(超详细) [前言] 第一次写这么长的文章. 写完后感觉对斜优的理解又加深了一些. 斜优通常与决策单调性同时出现.可以说决策单调性是斜率优化的前提. 斜率优化 \(D ...
- 【Netty】Netty简介及服务器客户端简单开发流程
什么是Netty Netty是一个基于Java NIO的编写客服端服务器的框架,是一个异步事件框架. 官网https://netty.io/ 为什么选择Netty 由于JAVA NIO编写服务器的过程 ...
- 【排序函数讲解】sort-C++
c++标准库里的排序函数,用于对给定区间所有元素进行排序.头 文件是#include 使用 Sort()在具体实现中规避了经典快速排序可能出现的.会导 致实际复杂度退化到 o(n²)的极端情况.它根据 ...
- 打开pycharm,提示invalid Log Path【已解决】
问题:打开pycharm,提示invalid Log Path 解决: 网上其他方法都说重装,这个成本有点高,所以我不去尝试. 因为我下载的是免安装版,所以使用时生成的文件是后来才生成的,所以我尝试 ...
- 个人永久性免费-Excel催化剂功能第91波-地图数据挖宝之行政区域信息实时下载(含经纬度)
移动互联网和O2O兴起的这十年时间里,由地图LBS功能衍生出一大堆的极高商业价值的数据及应用,地图相关的数据,也是数据分析过程中一个大宝藏,从此篇开始将带给大家一系列的地图相关的数据采集,满足数据分析 ...
- hexo-theme-yilia使用遇到的问题
该项目的github地址:https://github.com/litten/hexo-theme-yilia 下面是该项目的README.md 在使用过中遇到这么一个问题. 文章不会自动的摘要,显示 ...
- Spark-windows安装
Spark 目的:达到能在pycharm中测试 1.安装必要的文件: JDK AnaConda spark hadoop jdk测试:java -version Anaconda测试: 打开Anaco ...
- NetCore跨平台桌面框架Avalonia的OSX程序打包
虽然工作开发语言已经转到了java,但平时仍会用netcore做一些小工具,提升工作效率,但是笔记本换成了Mac,小工具只能做成命令行形式,很是痛苦,迫切需要一个.net跨平台的桌面程序解决方案. 为 ...
- 100天搞定机器学习|Day9-12 支持向量机
机器学习100天|Day1数据预处理 100天搞定机器学习|Day2简单线性回归分析 100天搞定机器学习|Day3多元线性回归 100天搞定机器学习|Day4-6 逻辑回归 100天搞定机器学习|D ...
- 原创:微信小程序开发要点总结
废话不多少,下面是对我从开发微信小程序的第一步开始到发布的总结,觉得对您有帮助的话,可以赞赏下,以对我表示鼓励. 一:首先注册登录微信公众平台,这个平台很重要,以后查文档全在上面看.https://m ...