您如何存储具有复杂词法范围和返回函数作为结果等的激活记录?

How do you store activation records with complex lexical scopes and returning functions as results and such?

我在 here 等几个地方读到关于查找非局部变量的“访问链接”和“显示方法”。然而,none 似乎触及了复杂的情况,比如 JavaScript 中你可以在函数内部拥有函数,而 return 那些函数来自函数,同时保持“内部范围” " 的那些函数。

这是一个复杂的嵌套函数。

let m = foo()
m()
m()

function foo() {
  let x = 10

  function bar() {
    let y = 2 ** x

    function hello() {
      let z = 30

      function another() {
        // PLACE_2
        let q = x + y + z

        function nested() {
          q += (17 * x) - (3 * z)

          // PLACE_1
          console.log(q)
          console.log(w)
        }

        return nested
      }

      // PLACE_3
      let w = another()
      return w
    }

    return hello
  }

  // PLACE_4
  let a = bar()
  let nested = a() // hello returns another
  nested()
  nested()

  return nested
}

PLACE_1,我们正在使用变量 x/z/q,而不是 y/a/nested。基本上我们在作用域树中创建所有这些延迟对象。

在这种情况下,带有“访问链接”或“显示方法”的激活记录如何工作?我习惯于让函数调用其他函数,但不保留过去函数的环境。这些在像这样的范围挥之不去的复杂系统中如何工作?

我正在尝试实现一个编译器,但目前一直在思考如何实际实现激活记录。我可以在激活记录是基于调用顺序的简单 parent/child 关系的情况下执行此操作,但我不知道如何让它在这种情况下工作。我想有一种变量查找机制,但我不知道在这种情况下需要存储什么。

阅读类似 this 的内容并不能深入了解像这样的真实世界情况,以及它是如何在幕后建模的。

仅当语言的语义不允许 non-local 变量超出其原始范围时才能使用显示,因为显示基本上是查找激活记录的有效方式 在堆栈上。如果你想实现真正的闭包,你需要使用一种不同的机制,它涉及 heap-allocation 个封闭变量。 (除非你能证明封闭变量永远不会逃逸。这种情况经常发生,但很难证明。)

AUUI,Javascript 保留了创建封闭变量的函数调用的完整激活记录。但这取决于语言的语义,并且不必要地在激活记录中保留与其他变量的活动链接会阻止它们被及时收集。应该只需要保留指向实际包含的变量的链接。

请注意,有两种不同的情况(同样,这取决于语言语义)。在简单的情况下,捕获的变量是不可变的(尽管它可能包含可变值)。在这种情况下,您可以通过将其添加为闭包中的数据成员来捕获该值。 (闭包对象本身需要 heap-allocated 但在实际创建闭包之前您不需要这样做,这可能永远不会发生。)

在更复杂的情况下,变量本身是可变的并且可能被内部函数改变[注1]。在那种情况下,变量本身必须被“装箱”;也就是说,包含在用作变量句柄的容器中。但是突变可能发生在变量范围终止之前,在这种情况下,更改需要在该范围内可见。如果变量被装箱,则外部函数需要知道这一点,因为对变量的访问是间接的。因此,将变量装箱到堆上是一种成本,必须在已知有必要之前支付(取决于逃逸分析的质量)。

这就是 Lua 实现的有趣之处,至少。在许多情况下,它设法避开 heap-allocating 盒子,但这样做会产生额外的成本。这似乎适用于 Lua 个用例,但我不知道它是否适合您的情况。


备注:

  1. 有一个非常常见的情况,变量是可变的,因为它的值不能方便地在单个初始化表达式中计算,但所有的变化都发生在变量被捕获之前。可以将这样的变量视为不可变的(或者就好像它是经过计算然后生成具有相同名称的不同变量的值)。这种情况很容易被发现。 (还有一些变量的最后一次突变是在捕获之后但在第一次调用捕获函数之前。通常也可以检测到这些变量。)这些变量可以像常量一样被捕获,只需将值放入闭包中即可。

    非正式调查表明,绝大多数捕获的变量要么是常量,要么是 constant-after-capture,这可以大大减少对 heap-allocated 个框的需求。