如何避免使用延续传递样式的堆栈?

时间:2011-10-19 15:52:31

标签: clojure lisp scheme state-machine continuations

对于我的毕业论文,我选择实施ICFP 2004 contest的任务。

任务 - 我将其翻译成自己 - 是编写一个编译器,将高级ant语言翻译成低级ant-assembly。在我的例子中,这意味着使用用Clojure(一种Lisp方言)编写的DSL作为产生反汇编的高级蚂蚁语言。

更新

ant-assembly有几个限制:没有用于调用函数的汇编指令(也就是说,我不能写CALL function1, param1),也没有从函数返回,也没有将返回地址压入堆栈。此外,根本没有堆栈(用于传递参数),也没有任何堆或任何类型的内存。我唯一拥有的是GOTO / JUMP指令。

实际上,ant-assembly用于描述状态机的转换(=蚂蚁的“大脑”)。对于“函数调用”(=状态转换),我所有的都是JUMP / GOTO。

虽然没有像堆栈,堆或适当的CALL指令那样的东西,但我仍然希望能够在ant-assembly中调用函数(通过JUMPing到某些标签)。 在几个地方我读到将Clojure DSL函数调用转换为延续传递样式(CPS)我可以避免使用堆栈[1],并且我可以将我的ant-assembly函数调用转换为普通JUMP(或GOTO)。这正是我需要的,因为在ant-assembly中我根本没有堆栈,只有GOTO指令。

我的问题是,在ant-assembly函数完成后,我无法告诉解释器(解释ant-assembly指令)在哪里继续。也许一个例子有帮助:

高级Clojure DSL:

(defn search-for-food [cont]
  (sense-food-here? ; a conditional w/ 2 branches
    (pickup-food ; true branch, food was found
      (go-home ; ***
        (drop-food
          (search-for-food cont))))
    (move ; false branch, continue searching
      (search-for-food cont))))

(defn run-away-from-enemy [cont]
  (sense-enemy-here? ; a conditional w/ 2 branches
    (go-home ; ***
      (call-help-from-others cont))
    (search-for-food cont)))

(defn go-home [cont]
  (turn-backwards
    ; don't bother that this "while" is not in CPS now
    (while (not (sense-home-here?))
      (move))) 
    (cont))

我想从go-home函数生成的ant-assembly是:

FUNCTION-GO-HOME:
  turn left nextline
  turn left nextline
  turn left nextline ; now we turned backwards
SENSE-HOME:
  sense here home WE-ARE-AT-HOME CONTINUE-MOVING
CONTINUE-MOVING:
  move SENSE-HOME
WE-ARE-AT-HOME:
  JUMP ???

FUNCTION-DROP-FOOD:
  ...

FUNCTION-CALL-HELP-FROM-OTHERS:
  ...

上面的ant-asm指令的语法:

turn direction which-line-to-jump
sense direction what jump-if-true jump-if-false
move which-line-to-jump

我的问题是我找不到要写入程序集中最后一行的内容(JUMP ???)。因为 - 正如您在示例中所看到的那样 - go-home可以使用两个不同的延续来调用:

(go-home
  (drop-food))

(go-home
  (call-help-from-others))

go-home完成后,我想拨打drop-foodcall-help-from-others。汇编时:我到家后(= WE-ARE-AT-HOME标签)我想跳转到标签FUNCTION-DROP-FOODFUNCTION-CALL-HELP-FROM-OTHERS

如果没有堆栈,如果不将下一条指令(= FUNCTION-DROP-FOOD / FUNCTION-CALL-HELP-FROM-OTHERS)的地址推送到堆栈,我怎么能这样做呢?我的问题是我不明白继续传递样式(=没有堆栈,只有GOTO / JUMP)可以帮助我解决这个问题。

(如果上述内容难以理解,我可以尝试再解释一下。)

非常感谢您的帮助!

-
[1]“解释它不需要控制堆栈或其他无限制的临时存储”。 Steele:Rabbit:Scheme的编译器。

4 个答案:

答案 0 :(得分:5)

是的,你已经提供了继续传递风格的确切动机。

看起来你已经将代码部分翻译成了continuation-passing-style,但并不完全。

我建议你看看PLAI,但我可以向你展示一下你的函数将如何转换,假设我可以猜测clojure语法,并混合使用scheme的lambda。

(defn search-for-food [cont]
  (sense-food-here? ; a conditional w/ 2 branches
   (search-for-food
    (lambda (r)
      (drop-food r 
                 (lambda (s)
                   (go-home s cont)))))
   (search-for-food
    (lambda (r)
      (move r cont)))))

我对你在寻找食物的事实感到有点困惑,无论你是否在这里感觉到食物,我发现自己怀疑这是一个奇怪的半翻译代码,或者只是并不意味着什么你认为这意味着。

希望这有帮助!

真的:去看看PLAI。 CPS变换在那里有很详细的介绍,尽管有很多东西可供你先阅读。

答案 1 :(得分:3)

显然,你的两个基本选项是内联一切,没有“外部”程序(额外的信用在这里查找“内部”和“外部”的原始含义),或以某种方式“记住”你需要去的地方从程序“调用”中“返回”(其中返回点不一定需要落在“调用”点之后的物理位置)。基本上,返回点标识符可以是代码地址,分支表的索引,甚至是字符符号 - 它只需要识别相对于被调用过程的返回目标

这里最明显的是在编译器中跟踪给定调用目标的所有返回目标,然后,在调用过程结束时,构建一个分支表(或分支梯形图)以从中选择一个几个可能的回归目标。 (在大多数情况下,只有少数可能的返回目标,但对于常用的程序,可能有数百或数千个。)然后,在调用点,调用者需要加载一个带有其返回点索引的参数相对于被叫程序

显然,如果被调用者又调用另一个过程,则必须以某种方式保留第一个返回点标识符。

毕竟,继续传递只是一种更为通用的返回地址形式。

答案 2 :(得分:3)

你的蚂蚁汇编语言甚至不是图灵完备的。你说它没有内存,所以你应该如何为函数调用分配环境?你最多可以接受常规语言并模拟有限自动机:任何更复杂的东西都需要内存。要完成Turing-complete,你需要的是垃圾收集堆。要完成评估CPS术语所需的一切,您还需要一个间接的GOTO原语。 CPS中的函数调用基本上(可能是间接的)提供参数传递的GOTO,并且您传递的参数需要内存。

答案 3 :(得分:0)

您可能对Andrew Appel的书 Compiling with Continuations 感兴趣。