在堆栈中转储寄存器以进行保守的堆栈扫描

时间:2013-01-24 23:31:57

标签: c garbage-collection stack compiler-optimization cpu-registers

我在C中写了一个非侵入性的保守GC,我对它的堆栈扫描阶段的正确性有些担忧。

具体来说,没有启用编译器优化,它工作正常,因为每个局部变量(指向一个对象)在堆栈上“可预测地”分配。在-O3中,GC错过了一些有效的引用,我认为这是因为编译器选择使用寄存器(而不是GC扫描的堆栈)来处理某些变量和函数参数传递以及GC (尚未)编程来处理这个问题。 (如果你怀疑这仍然不应该发生,并且我误解了问题的根源,请告诉我。)

除了一些基本要求(必须对GC对象使用GC_malloc而不是malloc,而不是从非GC堆指向GC堆,当然,不显式调用{ {1}}),GC不应再有来自客户端代码或编译器的任何要求。因此,不需要客户端代码中的任何其他特殊模式。类似地,强制使用此GC编译的程序使用特殊的编译器标志(抑制堆栈优化)应该是最后的选择。有了这两个,这就是我问题的积累。

我正在尝试找到一种方法让GC无缝地处理free情况(使用堆栈优化)。这是我的思路(假设,更准确地说):

  1. 无论GCC(或任何有效的编译器)与优化有多远,它都不会到目前为止会损害程序的正确性。
  2. 如果[1]成立,我相信(至少在C的实际实施中)如果可达(在任何通话级别分配为本地)变量正在进行为了保持可访问性,必须在堆栈上或当前的寄存器集,但实际上并不是其他任何地方。
  3. 如果[2]成立, 意味着在GC循环开始时简单地将所有寄存器转储到堆栈将保证后续堆栈扫描现在也能找到先前错过的引用。
  4. 此外,如果[1]和[2]成立,那么如果编译器允许覆盖任何变量(主要是基于寄存器的变量),则意味着它已经执行了某种程度的代码分析并且它已证明该变量不在该函数的任何其他地方使用。所以,从这个意义上讲,如果我们在堆栈或寄存器转储中没有看到变量,即使它仍然在范围内(理论上),这也不应该引起警报(对我来说),因为编译器已经通过摆脱死亡参考,简单地帮助了GC。
  5. 问题#1: 所有我的假设是否正确?

    问题2:强制注册转储的最便携方法是什么?我发现一些消息来源声称一个简单的-O3调用会产生这种效果。这是对的吗?

2 个答案:

答案 0 :(得分:1)

Q1。是的,我相信你的所有四个陈述都是正确的(至少如果我们忽略了编译器错误!)

Q2。 setjmp将保存SOME寄存器,但不一定是所有寄存器。但是,它应该足以满足您的需要,因为任何未被setjmp保存的寄存器都应保存在堆栈中。

我猜你的方案可能会出错,如果有人在某个地方存储了一个看起来像地址的东西,即使它不是。

你还必须记住,有时人们用指针做“有趣”的事情。例如。

struct blah
{
    size_t size;
    char *file;
    int line;
};


struct blah *p = malloc(sizeof(struct blah) + size); 
... more lines of code goes here to fll in size, file and line in blah. 
void *np = (p+1); 

这意味着您存储的指针根本不指向块的开头。

答案 1 :(得分:1)

#1的一大问题是执行未定义(或可疑)的程序,但这通常有效。比如使用指针作为魔术常量来存储在文件中或通过网络连接发送并且通常在本地被遗忘,但随后它们可能会(从文件或网络)返回并在以后被取消引用。或旧的“xor prev和next pointers”技巧,用于链接列表,每个节点只有一个开销指针。