我正试图设计一个基本的编译器来伪汇编代码。然而,我不知道如何实现闭包。似乎我需要将特定的寄存器值与每个"子例程"关联起来。我曾考虑过使用堆叠,但再一次似乎还不够。似乎只有关联数组才能工作,但这或类似的事情怎么能在汇编中完成呢?
我选择的示例如下,为了简洁起见,以CoffeeScript的形式进行了交流。
((x) -> (y) -> x(y))((x) -> x)(2)
以下是我一直在尝试的总体结构。这是我正在编译的伪程序集的一个示例。
'((label lam1) ;; (x) -> x
(cp resp arg)
(ret)
(label lam2) ;; (y) -> x(y)
(jmp x)
(label lam3) ;; (x) -> [(y) -> ...]
(cp x arg) ;; this is the assignment intended for a closure
(cp resp lam2) ;; this is a returned lambda, needing the closure
(ret)
(label main)
(cp arg lam1)
(call lam3)
(set arg 2)
(call resp)))
这是有效的;然而,该值只是在名称x
下设置,然后返回lambda,在执行lambda之前,x
值可能很容易被污染。
《计算机程序的结构和解释》中对实现的描述如下,这对我来说在汇编中似乎是不可行的。我不知道他们还能用什么战术。
过程对象将在运行时通过将当前环境(定义点的环境)与编译过程的入口点(新生成的标签)相结合来构建。
总之,寄存器值如何与"子程序"关联?堆叠是否足够
堆栈不可能足够。。。考虑一个更简单的情况,他们进行
function bar(f) {
alert(f());
}
function foo(x) {
bar(function(){ return x; });
}
foo(42);
在上述情况下,闭包中的x
在理论上可能存在于foo
的堆栈帧中,因为闭包不会比其创建者foo
更长寿。然而,有一个小小的变化:
function bar(f) {
to_call_later.push(f);
}
则闭包将被存储起来,并且当CCD_ 6已经终止并且其激活记录的堆栈空间已经被回收时。显然,x
不能在该堆栈区域中,因为它必须生存。
因此存在两个问题:
闭包必须有一些存储(环境)。当您认为两次调用
foo
传递两个不同的值应该为x
创建两个独立的存储时,这一点就很明显了。如果闭包只是代码,那么这是不可能的,除非每次调用foo
时都会生成不同的代码。这个存储必须至少与闭包本身一样存在,而不仅仅是与创建闭包的人一样。
还要注意,如果您想对变量进行读/写关闭,则需要额外的间接级别,例如:
function bar(f) {
alert(f());
}
function foo(x) {
var c1 = function() { return ++x; };
var c2 = function() { return x *= 2; };
bar(c1);
bar(c2);
}
foo(42); // displays 42+1=43 and 43*2=86 (not 42*2=84!)
换句话说,您可以有几个不同的闭包共享相同的环境。
因此x
不可能在foo
激活记录的堆栈中,也不可能在闭包对象本身中。闭包对象必须有一个指向x
所在位置的指针。
在x86上实现这一点的一个可能的解决方案是:
使用垃圾收集或引用计数的内存管理系统。到目前为止,堆垛还不足以处理封盖。
每个闭包都是一个包含两个字段的对象:一个指向代码的指针和一个指向封闭变量("环境")的指针数组。
当执行代码时,您有一个堆栈
esp
,例如esi
指向闭包对象本身(因此(esi)
是代码的地址,(esi+4)
是第一个闭包变量的地址,而(esi+8)
是第二个闭包变量,依此类推)。每个变量都是一个独立的堆分配对象,只要有闭包指向它,它就可以生存
这当然是一种非常粗糙的方法。例如,SBCL要智能得多,并且未捕获的变量仅在堆栈和/或寄存器上分配。这需要分析闭包是如何使用的。
编辑
假设你只考虑一个纯粹的函数设置(换句话说,函数/闭包的返回值只取决于传递的参数,并且闭包状态不能发生变化),那么事情就可以简化一点。
您可以做的是使闭包对象包含捕获的值,而不是捕获的变量。同时使闭包本身成为可复制对象,理论上只能使用堆栈(除了闭包的大小可能因需要捕获的状态而异),因此,在这种情况下,至少对我来说,想象一个合理的仅基于堆栈的协议来传递参数和返回值并不容易。
通过使闭包成为固定大小的对象来消除可变大小的问题,您可以看到这个C程序如何仅使用堆栈实现闭包(注意没有malloc
调用)
#include <stdio.h>
typedef struct TClosure {
int (*code)(struct TClosure *env, int);
int state;
} Closure;
int call(Closure *c, int x) {
return c->code(c, x);
}
int adder_code(Closure *env, int x) {
return env->state + x;
}
int multiplier_code(Closure *env, int x) {
return env->state * x;
}
Closure make_closure(int op, int k) {
Closure c;
c.state = k;
c.code = (op == '+' ? adder_code : multiplier_code);
return c;
}
int main(int argc, const char *argv[]) {
Closure c1 = make_closure('+', 10);
Closure c2 = make_closure('*', 3);
printf("c1(3) = %i, c2(3) = %in",
call(&c1, 3), call(&c2, 3));
return 0;
}
Closure
结构可以在堆栈上传递、返回和存储,因为环境是只读的,所以您不存在生存期问题,因为可以在不影响语义的情况下复制不可变数据。
C编译器可以使用这样的方法来创建只能按值捕获变量的闭包,这确实是C++11 lambda所提供的(您也可以通过引用捕获,但这取决于程序员来确保捕获的变量的生存期足够长)。