6

我正在尝试将历史功能语言解释器(EMAS 的 KRC)移植到现代系统(Unix 的 C),它有一个垃圾收集器,希望能够扫描堆栈以查找指向堆的指针,以了解它必须重新定位哪些指针在 GC 期间移动堆中的对象时。为此,必须在堆栈中找到所有指向堆的函数参数和局部变量。

现在,有一段时间,“register”关键字的意思是“如果你愿意,你可以把这个变量放在寄存器中”,否则它就在堆栈上,但现在所有(GCC、Clang、Tinyc/tcc)C 编译器似乎无论如何都将局部变量放入寄存器中,无法禁用此行为,结果是 GC 丢失了一些属于正在进行的函数的值,未能保存它们并破坏了堆。

有没有办法告诉任何这些编译器使用原始的 C 语义,除非你说“注册”,否则所有局部变量都在堆栈上?

我有一些棘手的“解决方案”:

  • 在任何地方添加额外的代码来获取每个面向堆的局部变量的地址并将其传递给一个虚拟函数,作为强制它位于内存位置的一种方式;
  • 使所有静态函数全局化,以避免函数内联和由此产生的内联函数参数的优化;
  • 将所有机器寄存器压入堆栈的存根将 GC() 函数括起来,调用真正的 GC() 函数,然后将它们弹出;

这一切似乎都改善了问题,但非常老套且不可靠。

有没有更好的方法来实现所需的结果,确保所有函数参数和局部变量都在堆栈上?

4

4 回答 4

4

好的,这是一个奇怪的 GC;好吧,您可能已经使用了volatile关键字。

它最初是用于内存映射设备之类的,您希望强制编译器不要优化变量。它的使用和滥用一直是一个长期存在的讨论话题。

有没有更好的方法来达到所需的结果

真的真的很难回答。一方面:显然,是的:不要让你的 GC 依赖于不能依赖的东西。但这意味着重写它。另一方面:如果诸如确保堆栈放置工作的附加代码之类的事情,那为什么不去做呢?这不像您要对历史解释器进行代码移植以提高性能

于 2015-03-08T10:15:19.483 回答
4

我想你使用一种“标记和扫描”GC。在这种情况下,您只需要在标记阶段开始时保存寄存器。我的建议是检查您的 GC,找到“标记和扫描”操作开始的位置,然后在此处放置一个将所有寄存器放入可访问内存的代码。setjmp是实现此目的的一种半便携方式(除非您正在使用 sparc)。

于 2015-03-08T10:28:34.187 回答
0

您的问题似乎有一个简单的解决方案:如果 GC 同步运行,例如如果从分配函数调用,则其他函数是否将指针存储在堆栈或寄存器中并不重要,只要所有寄存器都保存到在 GC 运行之前的某个时间点堆栈并扫描堆​​栈。将 GC 代码包装在一个函数中,该函数将所有寄存器保存在堆栈上,然后就完成了。正如玛丽安所说,内联汇编可能是必要的,但setjmp应该足够了。

于 2015-03-08T10:35:42.083 回答
0

只是想我会让你知道结果如何。我在代码中发现了一些没有告诉 GC 的数据项,在进入 GC 之前使用 setjmp() 将寄存器放在堆栈上,最后一件事是一些堆栈变量指向“尾部”堆而不是头部的链表单元,以便能够快速追加。我忽略了这些,因为它们没有与单元格边界对齐。在这些事情中,我永远不会想到 setjmp() 技巧,非常感谢。您的建议决定了 interp 工作与否。祝福!

于 2015-04-06T09:26:25.830 回答