23

我开始在使用 LLVM 作为后端的语言中添加闭包(lambdas)。我已经为简单的情况实现了它们,它们总是可以内联,即不需要生成闭包定义本身的代码,因为它在使用的地方被内联。

但是如果闭包并不总是内联(例如,它被传递给另一个没有内联的函数),如何为闭包生成代码。最好,调用站点不应该关心它们是否传递了常规函数或闭包,而是将它们作为正常函数调用。

我可以生成一个具有合成名称的函数,但它必须将引用环境作为一个额外的参数,并且该函数不能只传递给另一个不知道所需额外参数的函数。

我已经想到了一种使用 LLVM 的蹦床内在函数的可能解决方案,它从函数中“删除”单个参数,返回一个指向蹦床函数的指针,该函数需要一个更少的参数。在这种情况下,如果为闭包生成的函数将引用环境作为第一个参数,我可以删除它并取回一个函数,该函数的参数与闭包实际声明的参数完全相同。这听起来可行吗?高效的?有没有更好的解决方案?

代码示例:

def applyFunctionTo(value: Int, f: (Int) -> Int) = f(value)

def main() = {
  val m := 4;
  val n := 5;
  val lambda := { (x: Int) => x + m + n };
  applyFunctionTo(3, lambda)
}

现在,让我们假设 this 不会被内联到def main() = 3 + 4 + 5,并且applyFunctionTo可能会单独编译,并且我们不能在那里更改调用站点。使用蹦床,我想生成的代码会是这样的(用伪代码表示,* 表示指针):

def main$lambda$1(env: {m: Int, n: Int}*, x: Int) = x + env.m + env.n
def main() = {
  m = 4
  n = 5
  env* = allocate-space-for {Int, Int}
  env = {m, n}
  tramp* = create-trampoline-for(main$lambda$1*, env*)
  return applyFunctionTo(3, tramp*)
  // release memory for env and trampoline if the lambda didn't escape
}

这看起来对吗?

4

2 回答 2

8

听起来可行且高效。

另一种不需要蹦床的方法是将闭包类型定义为一对函数指针和指向环境的指针,即堆栈指针。在 C 调用约定中,额外的参数被忽略,所以如果你提供环境作为最后一个参数,你甚至可以使用 (function_ptr, null) 作为常规函数的回调。

于 2012-01-03T10:40:02.473 回答
1

一个愚蠢的想法是,对于每个闭包,您都会生成一个线程本地结构来保存所需的数据(可能只是一个指向本地结构的指针,也可以是几个指针)。

闭包的创建者负责设置 TLS 变量并“保存”它们的状态(以允许递归调用)。

然后用户正常调用该函数,它被执行并使用环境。

调用后,闭包的创建者将原始值“恢复”到 TLS 变量中。

于 2012-01-09T16:27:57.683 回答