如何坚持 C 编译器将局部变量放在堆栈上,而不是寄存器中
How to insist a C compiler put local variables on the stack, not in registers
我正在尝试将历史函数式语言解释器(KRC for EMAS)移植到现代系统(C for Unix),它有一个垃圾收集器,希望能够扫描堆栈中的指针以了解堆在 GC 期间移动堆中的对象时,它必须重新定位哪些指针。为此,必须在堆栈中找到指向堆的所有函数参数和局部变量。
现在,有一段时间 "register" 关键字表示 "you can put this variable in a register if you like" 否则它在堆栈上,但现在所有(GCC、Clang、Tinyc/tcc)C 编译器似乎无论如何将局部变量放入寄存器,无法禁用此行为,结果是 GC 丢失了一些属于正在进行的函数的值,无法保留它们并破坏堆。
有没有办法告诉这些编译器使用原始的 C 语义,所有局部变量都在堆栈上,除非你说 "register"?
我有几个疣"solutions":
- 在各处添加额外的代码以获取每个面向堆的局部变量的地址并将其传递给虚拟函数,作为强制它位于内存位置的一种方式;
- 使所有静态函数成为全局函数,以避免函数内联和由此产生的内联函数参数优化;
- 用一个存根将 GC() 函数括起来,将所有机器寄存器压入堆栈,调用真正的 GC() 函数,然后弹出它们;
这一切似乎都可以改善问题,但非常不可靠且不可靠。
是否有更好的方法来实现所需的结果,确保所有函数参数和局部变量都在堆栈上?
好的,这是一个奇怪的 GC;好吧,您可能使用了 volatile
关键字。
它最初是为内存映射设备之类的东西设计的,在这些设备中,您希望强制编译器不要优化变量。它的用途 abuse 一直是一个长期讨论的话题。
Is there a better way to achieve the required result
真的很难回答。一方面:显然,是的:不要让您的 GC 依赖于不能依赖的东西。但这意味着重写它。另一方面:如果需要额外的代码来确保堆栈放置工作,那为什么不去做呢?这不像是为了 性能.
对历史解释器进行代码移植
我想你使用了一种 "mark and sweep" GC。在这种情况下,您只需要在标记阶段开始时保存寄存器。我的建议是检查您的 GC,找到 "mark and sweep" 操作开始的位置,并放置一个代码,将所有寄存器放入此处的可访问内存中。 setjmp
是一种实现此目的的半便携式方法(除非您正在使用 sparc)。
您的问题似乎有一个简单的解决方案:如果 GC 运行 是同步的,例如,如果从分配函数调用,那么其他函数是否将指针存储在堆栈上或在寄存器中,只要在 GC 运行 并扫描堆栈之前的某个时刻将所有寄存器保存到堆栈中。将 GC 代码包装在一个将所有寄存器保存在堆栈上的函数中,您就完成了。为此可能需要内联汇编,但如 Marian 所述,setjmp
应该足够了。
只是想告诉你结果如何。我在代码中发现了一些没有被告知 GC 的数据项,在进入 GC 之前用 setjmp() 将寄存器放在堆栈上,最后一件事是一些堆栈变量指向 "tail" 堆中链表的单元格而不是头部,以便能够快速追加。我忽略了这些,因为它们没有与单元格边界对齐。在这些事情中,我永远不会想到 setjmp() 技巧,非常感谢。您的建议使 interp 工作与不工作之间有所不同。祝福!
我正在尝试将历史函数式语言解释器(KRC for EMAS)移植到现代系统(C for Unix),它有一个垃圾收集器,希望能够扫描堆栈中的指针以了解堆在 GC 期间移动堆中的对象时,它必须重新定位哪些指针。为此,必须在堆栈中找到指向堆的所有函数参数和局部变量。
现在,有一段时间 "register" 关键字表示 "you can put this variable in a register if you like" 否则它在堆栈上,但现在所有(GCC、Clang、Tinyc/tcc)C 编译器似乎无论如何将局部变量放入寄存器,无法禁用此行为,结果是 GC 丢失了一些属于正在进行的函数的值,无法保留它们并破坏堆。
有没有办法告诉这些编译器使用原始的 C 语义,所有局部变量都在堆栈上,除非你说 "register"?
我有几个疣"solutions":
- 在各处添加额外的代码以获取每个面向堆的局部变量的地址并将其传递给虚拟函数,作为强制它位于内存位置的一种方式;
- 使所有静态函数成为全局函数,以避免函数内联和由此产生的内联函数参数优化;
- 用一个存根将 GC() 函数括起来,将所有机器寄存器压入堆栈,调用真正的 GC() 函数,然后弹出它们;
这一切似乎都可以改善问题,但非常不可靠且不可靠。
是否有更好的方法来实现所需的结果,确保所有函数参数和局部变量都在堆栈上?
好的,这是一个奇怪的 GC;好吧,您可能使用了 volatile
关键字。
它最初是为内存映射设备之类的东西设计的,在这些设备中,您希望强制编译器不要优化变量。它的用途 abuse 一直是一个长期讨论的话题。
Is there a better way to achieve the required result
真的很难回答。一方面:显然,是的:不要让您的 GC 依赖于不能依赖的东西。但这意味着重写它。另一方面:如果需要额外的代码来确保堆栈放置工作,那为什么不去做呢?这不像是为了 性能.
对历史解释器进行代码移植我想你使用了一种 "mark and sweep" GC。在这种情况下,您只需要在标记阶段开始时保存寄存器。我的建议是检查您的 GC,找到 "mark and sweep" 操作开始的位置,并放置一个代码,将所有寄存器放入此处的可访问内存中。 setjmp
是一种实现此目的的半便携式方法(除非您正在使用 sparc)。
您的问题似乎有一个简单的解决方案:如果 GC 运行 是同步的,例如,如果从分配函数调用,那么其他函数是否将指针存储在堆栈上或在寄存器中,只要在 GC 运行 并扫描堆栈之前的某个时刻将所有寄存器保存到堆栈中。将 GC 代码包装在一个将所有寄存器保存在堆栈上的函数中,您就完成了。为此可能需要内联汇编,但如 Marian 所述,setjmp
应该足够了。
只是想告诉你结果如何。我在代码中发现了一些没有被告知 GC 的数据项,在进入 GC 之前用 setjmp() 将寄存器放在堆栈上,最后一件事是一些堆栈变量指向 "tail" 堆中链表的单元格而不是头部,以便能够快速追加。我忽略了这些,因为它们没有与单元格边界对齐。在这些事情中,我永远不会想到 setjmp() 技巧,非常感谢。您的建议使 interp 工作与不工作之间有所不同。祝福!