我正在用C编写一个Lisp解释器。每个Lisp对象都由一个带struct LispObject *
字段的type
表示,以指示它是否是int、symbol、cons等。我已经实现了一个全局环境,它是一个包含名称和值对的哈希表。LispObject
s始终使用malloc
动态分配无论何时创建新对象,都会将其添加到弱引用列表中当垃圾收集器运行时,它会标记所有可从全局环境访问的对象,然后清除弱引用并释放未标记的对象。
很容易保护全球环境免受垃圾收集的影响我一直在研究如何保护本地Lisp对象很明显,我还没有实现Lisp函数我要问的是如何保护LispObject *
类型的局部C变量例如,eval
是一个C函数,它接受一个LispObject *
表达式,应用求值规则,并返回一个LispObject *
值在函数返回之前,我需要保护LispObject *
中的本地eval
变量(以及处理Lisp对象的其他C函数)不被垃圾收集。
做这个最干净的方法是什么有没有办法标记任何可以从C调用堆栈访问的LispObject
s?
我考虑过实现一个单独的堆栈,它只用于存储不应该被垃圾收集的本地Lisp对象,但这感觉很笨拙,因为本地LispObject *
变量存储在C调用堆栈和垃圾收集堆栈上,为了调用C函数,我必须手动推送和弹出对象理想情况下,LISP对象会在本地范围内被自动保护,然后在超出范围后自动丢失该保护。
完整代码:https://notabug.org/jtherrmann/lisp-in-c
最佳答案
我认为你的GC是精确的GC首先需要定义何时可能调用GC一个常见的场景是让每个分配例程都可能调用GC。
您需要编写一个例程来扫描调用堆栈中的本地根因此,您需要有一个机制来向GC注册这些局部变量换句话说,您应该显式地显示解释器的调用堆栈(或者采用一些continuation-passing style方法)。
一种可能是将局部帧显式为一些struct
例如,查看Ocaml运行时所做的工作(阅读它的§20.5 Living in harmony with the garbage collector部分)或我的旧的(未维护的)GC例如,您可以采用这样的约定,即每个本地解释器帧都在一些_
本地变量(astruct
)中,并使用该约定。在我的Qish项目中,我将编写与此几乎相同的代码(在预处理器扩展之后),对于一个C例程crout
有一个指针参数a
和两个本地指针b
和c
void crout(struct callingframe_st *cf, LispObject*a) {
struct mycallframe_st {
struct callingframe_st* from;
int nbloc;
LispObject* aa;
LispObject* bb;
LispObject* cc;
} _;
memset(&_, 0, sizeof(_));
_.from = cf;
_.nbloc = 3; // the current frame has 3 locals: aa, bb, cc
_.aa = a;
#define a _.aa
#define b _.bb
#define c _.cc
接下来是
crout
的主体它将把(struct callingframe_st*)(&_)
传递给适当的例程最后,确保#undef a
等。。。从分配例程调用的GC必须以
(struct callingframe_st *)(&_)
作为参数(给出当前调用帧)。当然,假设
b_cons
可以间接调用GC,那么应该声明为LispObject* b_cons(struct callingframe_st*cf,
LispObject * car, LispObject * cdr);
否则,需要定义何时调用GC。
您需要了解垃圾收集是如何工作的(以及精确GC和保守GC之间的区别)我强烈建议阅读bismon或至少是保罗·威尔逊的旧GC handbook论文您可以采用这样一种约定,即您的所有例程都遵循Uniprocessor Garbage Collection Techniques样式(因此,您永远不会在C
f(g(x),h(x,y))
中直接编写allf
,g
,h
或者执行对象分配)。您还可以使用一些现有的精确GC,如A-normal form。
否则,使用一些保守的GC,比如Ravenbrook MPS。
还要查看现有的具有GC的自由软件解释器的源代码。
同时阅读奎因内克的书
为了调用C函数,我必须手动推送和弹出对象。
这可能是一个好主意(但是你需要重写你的大部分代码,你实际上可以定义你自己的Boehm's GC机器)看看Lisp In Small Pieces或bytecode或Lua字节码解释器或NimElisp解释器在做什么。
为了完成这项工作,您可能会考虑编写一些Ocaml来生成和/或添加特定的调用帧元数据和/或生成与调用帧相关的代码来帮助您精确地执行GC(这真的很难,我不建议走这条路,因为这需要很多年的工作)这真的很难IIRC,Emacs正在做类似的事情(高于Clang,而不是GCC)。
别忘了垃圾收集是一个完整的程序。
关于c - 如何保护解释器的本地调用堆栈免受垃圾回收的影响?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52800135/