尾声是大多数功能性语言中迭代的唯一手段。因此,实施支持绝对至关重要。
例如,方案需要作为其language specification (PDF)的一部分尾声优化:
3.5正确的尾部递归
计划的实现必须适当地进行尾声。如果方案实现支持无限数量的主动尾声,则可以适当地进行尾声。如果调用过程仍可能返回,则呼叫活动活动。
多年来,已经开发了各种解决方案来满足这一要求,例如蹦床,goto
,编译器优化等。
Cheney在M.T.A.上
Cheney在M.T.A.上需要编写编译器和运行时。代码将转换为持续传递样式,并将其编译为一系列C函数。当程序运行时,这些功能称为普通C函数,但它们永远不会返回。堆栈不断成长...
该程序定期检查以查看是否达到了堆栈尺寸限制。发生这种情况时,Cheney copying garbage collector用于将实时数据从堆栈复制到堆。一旦复制了堆栈上的所有实时对象,请调用koude1返回堆栈的底部,调用下一个功能,然后继续进行。
一种思考的方法是,我们将经常跳出堆栈,而不是使用蹦床:
一方面这是一种疯狂的ð®。程序不应该这样工作!
然后,再次保证递归调用永远不会导致堆栈溢出,因为从堆栈中定期清除一切。因此,Cheney在M.T.A上保证了尾巴呼叫优化。
实际上,这种方法提供了功能性编程语言运行时需要的所有“硬”内容 - 尾部调用优化,世代垃圾收集和连续性。
执行
这是Cyclone Scheme的代码示例
Cyclone的运行时使用以下功能来设置新的蹦床。使用setjmp
明确标记了堆栈的底部。垃圾收集后,将在setjmp
之后的下一行恢复执行。那时,代码找到下一个函数-gc->cont
,我们的延续 - 并打电话到其中:
void Cyc_start_trampoline(gc_thread_data * thd)
{
// Tank, load the jump program
setjmp(*(thd->jmp_start));
if (obj_is_not_closure(thd->gc_cont)) {
Cyc_apply_from_buf(thd, thd->gc_num_args, thd->gc_cont, thd->gc_args);
} else {
closure clo = thd->gc_cont;
(clo->fn)(thd, clo, thd->gc_num_args, thd->gc_args);
}
fprintf(stderr, "Internal error: should never have reached this line\n");
exit(1);
}
下面的摘要演示了如何使用贝克的方法编写C功能。所有调用到其他功能的编译器生成和原始功能都必须以这种样式编写:
object Cyc_make_vector(object cont, object len, object fill) {
object v = NULL;
int i;
Cyc_check_int(len);
// Memory for vector can be allocated directly on the stack
v = alloca(sizeof(vector_type));
// Populate vector object
((vector)v)->tag = vector_tag;
...
// Check if GC is needed, then call into
// continuation with the new vector
return_closcall1(cont, v);
}
Macro return_closcall1
用于检查是否达到了堆栈限制 - 如果这样通过GC()
触发了次要集合。否则,另一个Macro closcall1
用于调用下一个功能:
#define return_closcall1(td, clo, a1) {
char top;
object buf[1]; buf[0] = a1;
if (stack_overflow(&top, (((gc_thread_data *)data)->stack_limit))) {
GC(td, clo, buf, 1);
return; // Never reached
} else {
closcall1(td, (closure) (clo), buf);
return; // Never reached
}
}
这是GC
,是Cyclone的次要垃圾收集器的包装纸。代码检索堆栈顶部/底部的位置,调用gc_minor
使用Cheney的算法收集对象,然后调用longjmp
以基础跳回蹦床的开始:
void GC(void *data, closure cont, object * args, int num_args)
{
char tmp;
object low_limit = &tmp; // This is one end of the stack...
object high_limit = ((gc_thread_data *) data)->stack_start;
int alloci = gc_minor(data, low_limit, high_limit, cont, args, num_args);
...
// Let it all go, Neo...
longjmp(*(((gc_thread_data *) data)->jmp_start), 1);
}
gc_minor
的经历足以使它不在本文的范围之内。但是,如果您很好奇,请在github上获得the code。
包起来
现在就全部。谢谢阅读! ð