スタック走査して GC

GCのテスト実装にとりかかる。
とりあえず Mark & Sweep でいくことに。

レジスタ/スタック/ヒープ/グローバル変数 など走査すべき場所や考慮すべき事象はたくさんあるので、段階的にまずは単純にスタックから。
流れとしては

  • operator new を定義し、割り当てた領域の全アドレスをリストに保持
  • main のスタックボトムを保持しておく (これを bottom と呼ぶ)
  • gc_mark() 呼出し時のスタックポインタを取得する(これを top と呼ぶ)
  • 全アドレスリストのそれぞれのアドレスについて、top と bottom の間に保持されている auto変数から参照されていれば mark する
  • mark されていないものを sweep する


理論上はこれでうまくいくのだけど、いろいろ罠があって期待どおりに動かなかったりする。

        g++ -masm=intel -S gc.cpp   

なので、アセンブリのコードを読みまくって自前でスタックの推移を OpenOffice表計算で書いてみた。
さすがにここまでやればうまくいくものなのだな。


ただ g++ の吐くコードはときどき不可解で必要のなさそうに見えるところで無駄に多めにスタックを確保していたりする。
謎だなぁ。最適化のためだとは思うのだけども。

以下スタック表

(esp)
-76
-72 (esp) mallocの結果
-68
-64 testのebp(ebp)
-60 戻り(esp)
-56 (esp) (esp) (esp) 32 (esp)
-52
-48
-44
-40 mallocの結果 gcebp(ebp)
-36 newの結果 (esp)
-32 mainのebp mainのebp(ebp) mainのebp(ebp) mainのebp(ebp) mainのebp(ebp) mainのebp(ebp) (ebp)
-28 gc_init後の戻り場所(esp) 戻り場所 戻り場所(esp) 戻り場所(esp) mainのebp(ebp)
-24 &test_top &test_top &top &freeNodes 4 (esp)戻り
-20
-16
-12 int x =0x12345678
-8 int y = 0x99998888
-4 ecx
0 main突入前の ebpかつmainのebp (ebp) (ebp)
main gc_init main gc_node_initialize gc_node_initialize gc_node_initialize main new main test new test main gc gc_mark