【问题标题】:Compiling functional languages to C将函数式语言编译为 C
【发布时间】:2011-10-22 05:10:15
【问题描述】:

假设您正在将函数式语言编译为可移植的 C,并且还假设出于各种原因您想要精确而不是保守的垃圾收集。垃圾收集器没有可移植的方式(在一般情况下可能根本没有办法)来确定 C 堆栈上什么是指针,什么不是指针。在我看来,这个问题有两种解决方案:

  1. 影子堆栈。让每个 C 函数维护有关什么是指针和不是指针的簿记信息。这是例如推荐的方法。 LLVM。

  2. 利用您正在编译函数式语言这一事实​​,这意味着主线代码没有副作用。当分配器检测到内存不足时,它不会调用垃圾收集器本身,而是通过 longjmp 中止当前操作返回到调用垃圾收集器的主循环(在可能包含指针的变量集已知的上下文中)提前)然后重新开始操作。

在我看来,如果您正在处理适用于第二种方法的纯函数式语言,它必须比第一种方法更有效,并且更容易与手写 C 混合。

有没有我忽略的问题?有没有提到该技术的现有讨论或实现?

【问题讨论】:

  • 可能没有帮助,但我在为我的方案解释器编写标记扫描时尝试了第一个。性能很差,所以我最终在 C 运行时堆栈之外建立了一个纯虚拟堆栈,主要是因为跨运行时堆栈自省几乎是不可能的。性能也很差,但没有 gdb/ddd 更容易调试。我决定凑合着用,因为这是解释器,并在我进入编译器实现阶段时处理它(通常从未完成)。
  • 您打算如何重新启动当前操作?不时保存检查点,然后恢复上一个好的检查点(如何?)
  • @n.m.:在这方面问题的重要部分是“代码没有副作用”。提问者假设是一种纯函数式语言,因此不会修改任何状态。无需“采取”检查点,当您跳转到先前的状态时,您无需“撤消”任何更改,因为该语言无法进行更改。原则上,您在代码中的位置会告诉您有关程序状态的所有信息。
  • @n.m.这是个好问题。很容易想象一个字节码解释器,只需longjmp 回到eval()。但是对于编译,我不确定。您肯定不想在每次分配时都加上setjmps!
  • @luser droog:或者您实际上可以在函数式语言中的函数每次返回后添加setjmp。那是变量超出范围的时候,所以现在任何可收集的东西在最后一个这样的点上都是可收集的。提问者似乎只在主解释循环中建议setjmp,我认为这是因为它位于堆栈的顶部,并且他认为因此他不需要担心堆栈上的准确与保守标记。跨度>

标签: c compiler-construction functional-programming garbage-collection language-implementation


【解决方案1】:

使用单一数据结构设计纯FP语言是可能的:

typedef enum record_type { RT_SYMBOL, RT_NUMBER, RT_PAIR };

struct record
{
  record_type type;
  void *value;  
};

程序和数据可以用pairs of records表示:

struct pair
{
  record *car;
  record *cdr;
};

这是一个简单的表达式 - 2 * 3 - 可以使用 records 表示:

record r1;
r1.type = RT_NUMBER;
r1.value = &two; 

record r2;
r1.type = RT_NUMBER;
r1.value = &three; 

record opr1;
opr1.type = RT_NUMBER;
opr1.value = &OP_MULT; /* A machine op-code for multiplication. */

pair p_oprnds;
p_oprnds.car = &r1;
p_oprnds.cdr = &r2;

pair p;
p.car = opr1;
p.cdr = p_oprnds;

这与 Lisp 表达式相同:(* 2 3)。现在您可以定义一台在pairs 上运行的机器,将car 视为运算符,将cdr 视为操作数。由于我们只处理一种数据结构,精确的 GC 是可能的。有关此类 VM 的体系结构,请参阅 Lispkit Lisp

在认真尝试编写 FP -> C 编译器之前,还请阅读 Lisp in Small Pieces

【讨论】:

    【解决方案2】:

    我认为您没有描述的最明显的事情是如何处理持久的内存不足。正如您所描述的,假设我有一个操作使用的内存比可用内存多。最终我到达:

    1. 开始任何超出限制的操作阶段。
    2. 跑一会儿。
    3. 内存不足。
    4. 释放此阶段分配的所有内存(没有其他可释放的内存)。
    5. 转到 1。

    也就是无限循环。所以至少你需要某种分代垃圾收集,让你能够检测到循环并退出。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-06-30
      • 1970-01-01
      • 2020-04-13
      • 2010-09-28
      • 1970-01-01
      • 2016-08-06
      • 2017-09-27
      • 2010-10-13
      相关资源
      最近更新 更多