Conservative GC 使ってる時に、スタックにポインタぽいものが残ると、もう使ってなくてもメモリが解放されないみたいな話があって、コンパイラで関数から出る時にポインタを持ちうるメモリをクリアすると良いかなぁと作った clang plugin 。
https://github.com/shinh/clearstack
Ruby で適当にイヤなケース作って試そうと思ったんですけど、なんか Ruby の GC の挙動が私の予想に従わなくてよくわからなかったので、 Boehm GC 使う例を適当にでっちあげました。こういうの
#include <gc/gc.h> #include <stdio.h> #include <stdlib.h> void alloc() { void* p = GC_malloc(3); // Let the pointer be on the stack. asm(""::"m"(p)); } void main_loop(void* p) { size_t before_gc = GC_get_free_bytes(); GC_gcollect(); size_t after_gc = GC_get_free_bytes(); printf("%s: %zu => %zu\n", before_gc < after_gc ? "reclaimed" : "not reclaimed", before_gc, after_gc); } void call_main_loop() { // The stack location used in alloc() is not used in this function. void* p; // The allocated memory will be properly reclaimed even without our // clang plugin if we have this line. // p = NULL; asm(""::"m"(p)); main_loop(p); } int main() { GC_init(); GC_gcollect(); alloc(); call_main_loop(); return 0; }
alloc でメモリ確保して、 asm で無理矢理 p を stack に置いてもらってます。で、 call_main_loop は同じ stack のところを未初期化のポインタで使ってから、 main_loop という関数を呼びます。 alloc がプログラムの初期化みたいなイメージで、 alloc の中の p は初期化中だけに使うデータだから、解放されて欲しい、っていう想定です。 main_loop がプログラム本体で、十分長く実行される想定なので GC_gcollect がある、と。
一応このケースだと clang plugin 無しではメモリが解放されてないけど、 plugin 使うとメモリが解放されるようになりました。
かなり非効率ぽい実装をしてるんで、もうちょい効率的にしたり、構造体の中のポインタとかもハンドリングしたり、どのくらいオーバヘッドがあるか調べたり、もうちょい実際的な例を探したり、したいところですが、めんどくさいなっていう。