将函数式语言编译为C

将函数式语言编译为C,c,compiler-construction,functional-programming,garbage-collection,language-implementation,C,Compiler Construction,Functional Programming,Garbage Collection,Language Implementation,假设您正在将一种函数式语言编译成可移植的C语言,并且假设出于各种原因,您希望进行精确的垃圾收集,而不是保守的垃圾收集。垃圾收集器没有可移植的方法(在一般情况下可能根本没有方法)来确定C堆栈上什么是指针,什么不是指针。在我看来,这个问题有两种解决方案: 阴影堆栈。让每个C函数维护关于什么是指针和什么不是指针的簿记信息。这是LLVM推荐的方法 利用编译函数式语言这一事实,这意味着主线代码没有副作用。当分配器检测到内存不足时,它将中止当前操作,而不是调用垃圾收集器本身,并将longjmp返回主循环,主

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

  • 阴影堆栈。让每个C函数维护关于什么是指针和什么不是指针的簿记信息。这是LLVM推荐的方法

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

  • 在我看来,如果您使用的是纯函数式语言,而第二种方法适用,那么它必须比第一种方法更有效,并且更容易与手写C混合使用


    有什么我忽略的问题吗?对该技术现有讨论或实现的任何引用?

    可以使用单个数据结构设计纯FP语言:

    typedef enum record_type { RT_SYMBOL, RT_NUMBER, RT_PAIR };
    
    struct record
    {
      record_type type;
      void *value;  
    };
    
    程序和数据可以使用
    记录的
    对来表示:

    struct pair
    {
      record *car;
      record *cdr;
    };
    
    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;
    
    下面是如何使用
    记录来表示一个简单表达式-
    2*3

    struct pair
    {
      record *car;
      record *cdr;
    };
    
    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表达式相同:
    (*23)
    。现在,您可以定义一台在
    对上运行的机器,将
    car
    视为运算符,将
    cdr
    视为操作数。因为我们只处理一个数据结构,所以精确的GC是可能的。有关此类VM的体系结构,请参阅


    在开始编写FP->C编译器之前,请仔细阅读。

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

  • 开始任何阶段的行动,让我们超越极限
  • 跑一会儿
  • 内存不足
  • 释放此阶段分配的所有内存(没有其他可释放的内存)
  • 转到1

  • 也就是说,一个无限循环。因此,至少您需要某种代际垃圾收集,使您能够检测循环并退出。

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