java开发C编译器:jvm函数调用时的参数传递

时间:2023-02-16 22:59:29

更详细的讲解和代码调试演示过程,请参看视频
用java开发C语言编译器

更详细的讲解和代码调试演示过程,请参看视频
如何进入google,算法面试技能全面提升指南

如果你对机器学习感兴趣,请参看一下链接:
机器学习:神经网络导论

更详细的讲解和代码调试演示过程,请参看视频
Linux kernel Hacker, 从零构建自己的内核

jvm在执行代码时,必须围绕两个数据结构,一个是堆栈,一个是队列。当执行字节码指令时,如果指令需要处理数据,那么此时必须保证数据存储在堆栈顶部,如果堆栈上的数据需要转移存储,那么他们会被存储在队列上。

前面我们讲过,java函数的局部变量都是存储在队列上的,只有当代码需要处理这些变量时,才会从队列转移到堆栈上。在上一节我们把C代码转换成java字节码的例子中,C语言函数含有两个局部变量,这两个变量会对应于队列上的某两个元素,他们到底对应队列上哪个元素是可以由代码指定的,只要访问变量时,从队列的相应位置读取就可以,但有一种情况,局部变量与队列中的元素位置是一一对应的,这种情况就是函数的输入参数,例如函数:

static double lotsOfArguments(int a, long b, float c, String[][] d) {
....
}

当上面函数运行时,在执行函数lotsOfArguments前,jvm会把输入参数全部放到堆栈上,当函数被执行时,参数会从堆栈拷贝到局部变量队列,因此当lotsOfArguments执行前,堆栈上参数如下:
stack:
d
c
b
a
函数执行时堆栈上的参数会依次拷贝到局部变量队列,情况如下:
local_list: d c b a
也就是队列第0个元素存储参数d, 第一个元素存储参数c,第二个元素存储参数b,第三个元素存储参数a。基于这个特点,当C代码函数调用有参数传递时,编译器把代码编译成java字节码时,要注意上面所说的机制。

完成本节代码后,我们的编译器能将下面代码正确编译成java字节码

int f(int a, int b) {
printf("value of a and b is : %d, %d", a, b);
int c;
c = a + b;
return c;
}

void main() {
int c;
c = f(1, 2);
printf("result of calling f is :%d", c);
}

本节代码与上节相差不大,主要目的就是要确定函数的输入参数与jvm队列中元素的对应关系。

上面代码中,main函数调用函数f,输入参数是1,2。于是当f调用前,会把参数先压到堆栈上,情景如下:
stack:
2
1
因此f执行是,堆栈上的参数拷贝到局部变量队列时如下:
local_list: 2, 1
所以在函数f运行时,对变量a的读写,要对应到队列的第1个元素,对变量b的读写,要对应到队列的第0个元素。

被调函数f返回值是整形,同时有两个整形输入参数,因此编译成java字节码时,函数声明如下:
.method public static f(II)I
因此我们需要修改FunctDeclExecutor.java中的代码,以便生成上面的函数声明:

private String emitArgs(Symbol funSymbol) {
Symbol s = funSymbol.getArgList();
ArrayList<Symbol> params = new ArrayList<Symbol>();
while (s != null) {
params.add(s);
s = s.getNextSymbol();
}
Collections.reverse(params);
String args = "(";
for (int i = 0; i < params.size(); i++) {
Symbol symbol = params.get(i);
String arg = "";
if (symbol.getDeclarator(Declarator.ARRAY) != null) {
arg += "[";
}

if (symbol.hasType(Specifier.INT)) {
arg += "I";
}

args += arg;
}

if (funSymbol.hasType(Specifier.INT)) {
args += ")I";
} else {
args += ")V";
}

return args;
}

上面代码中,输入参数是函数f对应的符号对象,从该符号对象获得输入参数的符号对象,进而判断输入参数的类型,如果输入参数类型是整形的话,编译器就输出字符I, 同时获得函数返回值类型,如果返回值是整形,那么需要在函数声明的最后加上字符I.

接下来,我们需要确定输入参数a和b与局部变量队列的对应关系,相关实现代码如下programGenerator.java:

public int getLocalVariableIndex(Symbol symbol) {
TypeSystem typeSys = TypeSystem.getTypeSystem();
String funcName = nameStack.peek();
Symbol funcSym = typeSys.getSymbolByText(funcName, 0, "main");
ArrayList<Symbol> localVariables = new ArrayList<Symbol>();
Symbol s = funcSym.getArgList();
while (s != null) {
localVariables.add(s);
s = s.getNextSymbol();
}

ArrayList<Symbol> list = typeSys.getSymbolsByScope(symbol.getScope());
for (int i = 0; i < list.size(); i++) {
if (localVariables.contains(list.get(i)) == false) {
localVariables.add(list.get(i));
}
}

for (int i = 0; i < localVariables.size(); i++) {
if (localVariables.get(i) == symbol) {
return i;
}
}

return -1;
}

当编译器要确定变量a在局部队列中的位置时,把变量a的符号对象输入上面函数。该函数先通过nameStack获得当前正在执行的函数名,通过函数名找到函数对应的符号对象,进而找到函数对应的输入参数,例如通过函数f的符号对象就可以找到其输入参数的符号对象,因为输入参数的符号对象会以链表的方式存储在函数的符号对象中:
Symbol(f)->Symbol(b)->Symbol(a).
上面的代码先把参数b和a的符号对象拿到,存入到一个队列中:
localVariables:Symbol(b), Symbol(a).
然后再通过函数名f,找到所以作用域范围为f的变量的符号对象:
list: Symbol(b), Symbol(a), Symbol(c)
把存在list中,但不存在localVariables中的符号对象加入到localVariables队列,于是localVariables队列情况如下:
localVariables: Symbol(b), Symbol(a), Symbol(c)

要查找给定变量在局部队列的位置,只要把变量的符号对象拿到localVariables队列中查询就可以了,例如想获得变量a在局部队列中的位置,那么拿到变量a的Symbol对象,在localVariables队列中查询,得到结果1,就是输入参数a对应在局部变量队列中的位置。

上面代码运行后,把给定的C代码编译成java汇编代码时结果如下:

.class public CSourceToJava
.super java/lang/Object

.method public static main([Ljava/lang/String;)V
sipush 1
sipush 2
invokestatic CSourceToJava/f(II)I
istore 0
iload 0
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "result of calling f is :"
invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V
istore 1
getstatic java/lang/System/out Ljava/io/PrintStream;
iload 1
invokevirtual java/io/PrintStream/print(I)V
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "
"

invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V
return
.end method
.method public static f(II)I
iload 1
iload 0
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "value of a and b is : "
invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V
istore 3
getstatic java/lang/System/out Ljava/io/PrintStream;
iload 3
invokevirtual java/io/PrintStream/print(I)V
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc ", "
invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V
istore 3
getstatic java/lang/System/out Ljava/io/PrintStream;
iload 3
invokevirtual java/io/PrintStream/print(I)V
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "
"

invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V
iload 1
iload 0
iadd
istore 2
iload 2
ireturn
.end method

.end class

把java汇编代码编译成二进制字节码运行后结果如下:
java开发C编译器:jvm函数调用时的参数传递

变量a的值打印出来结果是1,变量b的值打印出来后结果是2,也就是说,编译器把代码编译成java字节码中,读写输入参数时,能在局部变量队列中找到正确的对应元素。

更加详细的代码讲解和代码调试演示过程,请参看视频。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
java开发C编译器:jvm函数调用时的参数传递