首页 > 解决方案 > 在编译器中通过 goto 目标/标签实现闭包的最佳方法是什么?

问题描述

考虑以下 Common Lisp 代码:

(let ((closure))
  (defun weird ()
    (tagbody
        (go :start)
      :here
        (write-line "Got :here")
        (return-from weird)
      :start
        (setf closure (lambda ()
                        (go :here)))
        (activate)))

  (defun activate ()
    (funcall closure)))

调用weird结果Got :here被打印。因此,存储在中的函数closure关闭了go标签:here。Common Lisp 的tagbodygo具有传输目标必须是词汇可见tagbody形式的限制;例如,以下内容无效:

(defun wrong ()
  (go :inner)
  (tagbody :inner
    (write-line "Can't happen")))

当然,对于 Lisp 的语法,这很有意义;我们不能去,:inner因为go不在它的范围内。然而,像 Algol 和 C 这样的语言没有这个限制:

void right(void)
{
        goto inner;
        {
inner:          printf("Can happen");
        }
}

假设我想为带有闭包的类似 Algol 的语言实现编译器,并且我正在为基于堆栈的虚拟机生成字节码。(重点是我不受某些真实计算机体系结构甚至现有虚拟机体系结构的限制。)这种语言需要像在 Pascal 中一样声明标签,尽管声明可以在任何地方,而不仅仅是在函数的开头/procedure,标签是符号的,而不是数字的。

标签/标签到代码中某个位置的“绑定”在 Common Lisp 中具有动态范围,在这种语言中也应该如此;一旦退出包含标签声明的块,传输点将变为无效。例如,对 in 之后的调用将activate导致违反此约束。但是,否则,标签可以放置在其声明的词法范围内的任何位置,包括内部块中。tagbodyweird

有关此工具的实际使用,请参阅handler-case语言标准(在注释部分)中给出的 Common Lisp 的示例扩展,或者restart-case(如果您还不熟悉 Common Lisp 的条件系统,那么这不会是非常有意义。)我不确定封闭式 goto 是否可以合理使用到内部块中……但是,省略该功能将是一个随机限制。

将控制转移闭包中没有意义,转移到任何过程的主体中也没有意义。

在这种情况下,支持关闭 goto 标签的最佳方法是什么?关闭变量绑定很简单,但是还需要什么来关闭传输点呢?

我想会有一个标签环境和词汇环境。这个环境会是什么样子(如果这是要走的路)?当 goto 针对比闭包定义点更深层次的块时,似乎会出现复杂情况。标签不可达后,让中转点失效怎么办?也许CLISP 的字节码(参见的说明tagbodygo)可以作为模型;它实现了所需的语义,除了能够转到内部标签。

Common Lisp 也有block操作符,它建立了一个命名的退出点,也可以关闭。但是,我相信这可以使用与封闭式 goto 相同的机制来实现。

(顺便说一句,我很确定weird可以在 Algol 68 中定义等效的 ,只有语法差异。事实上,我很确定 Algol 68 实现需要部分解决这个问题中描述的问题,尽管 Algol 68 一般没有闭包。)


维基百科关于 goto的文章指出以下内容:

在 Scheme 中,如果需要,延续甚至可以将控制从外部上下文移动到内部上下文。这种对接下来执行什么代码的几乎无限的控制使得复杂的控制结构(例如协程和协作多任务处理)相对容易编写。

这向我表明,延续绝对是一种可行的策略。以前我很犹豫,主要是出于无知,但现在我做了一些进一步的研究,这个想法更有吸引力,特别是因为协程听起来很“免费”。

我的第一个想法是有一个由延续组成的标签环境;通过执行(激活?)相应的延续来执行到标签的转移。这些延续必须在运行时动态设置。关闭一组标签的闭包将获得标签环境的副本。

但是,这个想法似乎存在一些问题。就目前而言,这些延续的范围是无限的——我认为这可以通过在环境中保留对延续的引用来解决,当声明超出范围后标签环境被拆除时,这可能会失效。一个更紧迫的问题是,要使这项工作看起来需要对执行模型进行实质性的重新思考,以适应包含的延续。我应该提一下,我不想要一流的延续;他们比 goto 可怕得多。

如果没有封闭标签,这种语言可以使用类似于经典 Algol 机器的虚拟机来实现:有一个“显示”来跟踪词汇环境,而闭包在其执行时存储显示的副本创建。在我看来,使用封闭式标签,一切都变得更加复杂。我觉得好像我错过了一种处理它们的合理方法,一种不会带来像延续一样沉重的东西。我还希望能够将普通的 goto 语句编译成简单的传输。

(注:我完全愿意接受我的假设是错误的,并且没有简单的出路。我也明白这确实是一个不必要的“问题”,因为该语言不需要 goto 语句。这只是一个障碍我在尝试将 Common Lisp 语义移植到 Algol 时遇到了问题,为了保持一致性,我想解决它。)

标签: assemblycompiler-constructionclosures

解决方案


推荐阅读