其他分享
首页 > 其他分享> > Berry 实现:自动扩充的调用栈

Berry 实现:自动扩充的调用栈

作者:互联网

概述

调用栈用于存储函数执行过程中调用链上所有函数的局部变量等调用信息。Berry 调用栈特指脚本程序的调用栈,而不是 C 的调用栈。

在 be_vm.h 中可以看到 VM 结构中和调用栈相关的字段:

struct bvm {
    // ...
    bvalue *stack; /* stack space */
    bvalue *stacktop; /* stack top register */
    bstack callstack; /* function call stack */
    // ...
};

stackstacktop 用于维护存储局部变量的栈(以下简称“变量栈”,函数的栈空间指 vm.stack 中被该函数使用的一段空间),而 callstack 为函数栈帧的堆栈。

我们用一个简单的脚本来说明上述字段的作用:

def func1(c)
    return c + 1
end

def func2(b)
    return func1(b) + 2
end

def func3(a)
    return func2(a) + 3
end

当我们执行 func3(10) 的时候,执行到 func1 内部时调用链最长:

call stack top
+-------------------------+
|    function: func1      |
|  local variable(s): c   |
+-------------------------+
|    function: func2      |
|  local variable(s): b   |
+-------------------------+
|    function: func3      |
|  local variable(s): a   |
+-------------------------+
call stack base

很显然,调用链上所有函数的局部变量都应该被存储,以保证被调函数返回后主调函数能够继续执行。调用链上所有函数的局部变量是按照调用顺序排列的,这些变量的值都存储在 vm.stack 中。因此 vm.stack 是一个 bvalue 数组。当调用链达到最深时,各变量在 vm.stack 中的排列方式为:

stack index |    0    |    1    |    2    |
variable    | func3:a | func2:b | func1:c |  

这里要注意到 vm.stack 中存储的 3 个变量分别属于不同的函数,那么该怎样去确定每个函数在 vm.stack 中占用那些部分来存储它的局部变量呢?答案是 vm.callstack 字段,该字段为 bstack 类型,其存储元素为 bcallframe 类型。后者的定义如下:

typedef struct {
    bvalue *func; /* function register pointer */
    bvalue *top; /* top register pointer */
    bvalue *reg; /* base register pointer */
    binstruction *ip; /* instruction pointer (only berry-function) */
    int status;
} bcallframe;

该结构体用于实现函数栈帧,每个字段的功能为:

每次发生函数调用时都要把虚拟机状态和主调函数的一些信息压入 vm.callstack 中,以便被调函数返回后能够恢复状态。这些信息包括了函数栈基址,栈顶和指令指针等。

变量栈

变量栈,也就是 vm.stack 会在 VM 创建时分配,vm.stacktop 字段指向 vm.stack 的最后一个元素,因此它包含了变量栈总容量的信息。Berry 的变量栈支持动态扩充,在 VM 刚刚创建时变量栈容量很小,而执行过程中会动态调整。栈扩充一般发生在函数调用时,解释器会检查函数需要的栈容量以决定是否扩充。如果发生扩充则执行以下流程:

  1. 重新分配变量栈并拷贝数据。
  2. 更新 vm.callstack 所有元素的的 functopreg 域。
  3. 更新所有 open upvalue 的 value 域。

其中 2、3 步中提到的结构都指向/引用了变量栈中的某个值,因此需要更新。具体的实现可以参考 be_stack_expansion() 函数的源码。

变量栈失效

变量栈失效是指变量栈扩充导致元素地址发生了变动,而指向变量栈中元素的指针却没有同时更新以致程序运行错误的现象。

Berry 的代码本身可能还存在一些变量栈失效的错误。为了避免出现该问题,我们总结了那些情况可能会导致变量栈失效:

以下情况不用考虑调用栈失效的问题:

标签:调用,函数,vm,Berry,扩充,stack,变量
来源: https://www.cnblogs.com/skiars/p/12232625.html