0%

《动手写虚拟机》CALL调用以及RET返回

前面完成了JMP跳转功能,下面要完成的是函数调用

在所有高级语言中,都有函数调用,实际上函数调用也用到了上一篇完成的跳转指令

其流程就是:

  1. 在调用一个函数前,将参数压入当前栈帧,有多少个压入多少个
  2. CALL第一个参数指定跳转位置,第二个参数指定参数个数
  3. CALL指令执行依次做下面事情:新建一个栈帧,从上一个栈帧取出每个参数放入新栈帧,保存IP位置到上一个栈帧,跳转到新位置执行
  4. 新位置就是函数的开始位置,执行到RET代表函数执行结束,会依次做下面事情:返回值全部迁移到上一个栈帧中,删除当前栈帧,跳转到上一个栈帧中记录下的返回地址执行

下面是实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
case CALL:
if len(instruction.args) < 2 {
panic(fmt.Errorf("instruction error - %v", instruction))
}
targetPos, err := instruction.args[0].GetNumber() // 取出跳转到的位置
if err != nil {
return err
}
argsCount, err := instruction.args[1].GetNumber() // 取出参数个数
if err != nil {
return err
}
newStackFrame := NewStackFrame() // 新建一个栈帧
for argsCount > 0 {
newStackFrame.Push(currentStackFrame.Pop()) // 参数移动过来
argsCount--
}
vm.stack.Push(newStackFrame) // 放入栈帧

currentStackFrame.retAddress = vm.instructionPointer // 记录返回地址
vm.instructionPointer = int64(targetPos) - 1 // jmp
case RET:
if len(instruction.args) < 1 {
panic(fmt.Errorf("instruction error - %v", instruction))
}
returnCount, err := instruction.args[0].GetNumber()
if err != nil {
return err
}
lastStackFrame := vm.stack.GetLastStackFrame()
for returnCount > 0 {
lastStackFrame.Push(currentStackFrame.Pop())
returnCount--
}
vm.stack.Pop()

vm.instructionPointer = lastStackFrame.retAddress

从上面流程中不难发现,咱们函数调用是支持多返回值的

开源地址

go-vm

完结

到这里,咱们的简易虚拟机基本算是完成了。

以后有机会的话,咱们来实现一门运行于本虚拟机之上的脚本语言

注意:不可用于生产,只能用于学习研究




微信关注我,及时接收最新技术文章