我正在用 C 编写一个非侵入性的保守 GC,我对它的堆栈扫描阶段的正确性有些担心。
具体来说,在没有启用编译器优化的情况下,它可以正常工作,因为每个局部变量(指向一个对象)都“可预测地”分配在堆栈上。在-O3
中,GC 遗漏了一些有效的引用,我认为这是因为编译器选择使用寄存器(而不是 GC 扫描的堆栈)来传递一些变量和函数参数,而 GC 不是(还) 被编程来处理。(如果您怀疑这仍然不应该发生并且我误读了问题的根源,请告诉我。)
除了一些基本要求(必须使用 aGC_malloc
而不是malloc
GC 对象,不从非 GC 堆指向 GC 堆,当然,不显式调用free
),GC 不应该有来自客户端的任何更多要求代码或编译器。因此,不能从客户端代码中要求任何其他特殊模式。同样,强制使用此 GC 的程序使用特殊的编译器标志(抑制堆栈优化)编译应该是最后的选择,充其量是。解决了这两个问题,这是我的问题的基础。
我正在尝试找到一种方法让 GC-O3
无缝处理这种情况(通过堆栈优化)。这是我的思路(更准确地说是假设):
- 无论 GCC(或任何有效的编译器)在优化方面走多远,它都不会走得太远以至于会损害程序的正确性。
- 如果 [1] 成立,我相信(至少在 C 的实际实现中)如果一个可访问(在任何调用级别分配为本地)变量将完全保持可访问,它必须要么在堆栈上,要么在当前的寄存器集,但在其他任何地方都没有。
- 如果 [2] 成立,这应该意味着在 GC 周期开始时简单地将所有寄存器转储到堆栈中将保证后续堆栈扫描现在也能找到先前丢失的引用。
- 此外,如果 [1] 和 [2] 成立,那么如果编译器允许任何变量被覆盖(大多数情况下是基于寄存器的变量),这意味着它已经执行了某种程度的代码分析,并且已经证明该变量未在该函数的其他任何地方使用。所以,从这个意义上说,如果我们在堆栈或寄存器转储中看不到变量,即使它仍在范围内(理论上),这不应该成为警报的原因(对我来说),因为编译器有只是通过摆脱死引用来帮助 GC。
问题 #1:我的所有4 个假设都正确吗?
问题 #2:强制寄存器转储的最便携方式是什么?我发现一些消息来源声称一个简单的setjmp
调用就会产生这种效果。它是否正确?