In article <87ekhwiv7g.fsf / serein.a02.aist.go.jp>, Tanaka Akira <akr / m17n.org> writes: > 得られた知見から、GNU/Linux でも次の変更を行うと怪しげなことが起こるはずです。 > というわけで試してみると、やはり変なことが起こります。 これどうやら別の話なようです。 NetBSD の gcc 3.3.3 の問題を再現させるには次のパッチで可能なようです。 ([yarv-dev:366] が参考になりました、ってゆーかこんんなことできたんです ね) Index: gc.c =================================================================== RCS file: /src/ruby/gc.c,v retrieving revision 1.192 diff -u -p -r1.192 gc.c --- gc.c 10 Nov 2004 07:16:24 -0000 1.192 +++ gc.c 12 Dec 2004 17:34:57 -0000 @@ -1291,6 +1291,7 @@ garbage_collect() struct gc_list *list; struct FRAME * volatile frame; /* gcc 2.7.2.3 -O2 bug?? */ jmp_buf save_regs_gc_mark; + register int _during_gc asm("esi"); SET_STACK_END; #ifdef HAVE_NATIVETHREAD @@ -1298,7 +1299,8 @@ garbage_collect() rb_bug("cross-thread violation on rb_gc()"); } #endif - if (dont_gc || during_gc) { + _during_gc = during_gc; + if (dont_gc || _during_gc) { if (!freelist) { add_heap(); } % ./ruby test/runner.rb test/rss Loaded suite rss Started ...................E.................................................................................... Finished in 4.498391 seconds. 1) Error: test_parser(RSS::TestDublinCore): NotImplementedError: method `respond_to?' called on terminated object (0x401a84cc) /home/akr/ruby/head-ruby/lib/ruby/1.9/rss/parser.rb:365:in `start_have_something_element' /home/akr/ruby/head-ruby/lib/ruby/1.9/rss/parser.rb:282:in `start_else_element' /home/akr/ruby/head-ruby/lib/ruby/1.9/rss/parser.rb:246:in `tag_start' /home/akr/ruby/head-ruby/lib/ruby/1.9/rexml/parsers/streamparser.rb:24:in `parse' /home/akr/ruby/head-ruby/lib/ruby/1.9/rexml/document.rb:171:in `parse_stream' /home/akr/ruby/head-ruby/lib/ruby/1.9/rss/rexmlparser.rb:21:in `_parse' /home/akr/ruby/head-ruby/lib/ruby/1.9/rss/parser.rb:113:in `parse' /home/akr/ruby/head-ruby/lib/ruby/1.9/rss/parser.rb:69:in `parse' ./test/rss/test_dublincore.rb:64:in `test_parser' ./test/rss/test_dublincore.rb:63:in `assert_too_much_tag' /home/akr/ruby/head-ruby/ruby/test/rss/rss-assertions.rb:51:in `_wrap_assertion' /home/akr/ruby/head-ruby/ruby/test/rss/rss-assertions.rb:51:in `assert_too_much_tag' ./test/rss/test_dublincore.rb:63:in `test_parser' ./test/rss/test_dublincore.rb:62:in `each' ./test/rss/test_dublincore.rb:62:in `test_parser' 104 tests, 1300 assertions, 0 failures, 1 errors この場合は core は吐いてませんが、called on terminated object ですから 変に GC されちゃってることがわかります。(ちなみに test-all すると core を吐きました。) なにをやっているかというと、setjmp する前に ESI を別の用途に使っている わけです。NetBSD 上の gcc 3.3.3 ではもともとそうなっていたのですが、 GNU/Linux 上の gcc 3.3.4 で無理矢理そうさせるために asm を使っています。 それによって、呼び出し元での ESI が jmp_buf に入らなくなり、また、スタッ クに保存された場所は [ruby-dev:25158] で述べたように GC でスキャンする 範囲外になっているので、GC されちゃうことになります。 過去には [ruby-dev:7757] で同じ問題が報告されているようです。そのとき は alloca(1) の結果を stack_end とすることによって解決されたようです。 [ruby-dev:25158] での && 0 の追加も alloca(1) を使うようにするわけなの で同じ解決法であったといえます。 なぜ __builtin_frame_address を使うようになったのかがわからないので、 どう解決するのが適切なのかはいまひとつ判断がつきませんが、 __builtin_frame_address をつかったまま問題を解決するなら、次の変更が考 えられます。 Index: gc.c =================================================================== RCS file: /src/ruby/gc.c,v retrieving revision 1.192 diff -u -p -r1.192 gc.c --- gc.c 10 Nov 2004 07:16:24 -0000 1.192 +++ gc.c 12 Dec 2004 17:50:34 -0000 @@ -435,7 +435,12 @@ static unsigned int STACK_LEVEL_MAX = 65 # define STACK_END (&stack_end) #else # if defined(__GNUC__) && defined(USE_BUILTIN_FRAME_ADDRESS) && !defined(__ia64__) -# define SET_STACK_END VALUE *stack_end = __builtin_frame_address(0) +__attribute__ ((noinline)) static VALUE * +stack_end_address(void) +{ + return (VALUE *)__builtin_frame_address(0); +} +# define SET_STACK_END VALUE *stack_end = stack_end_address() # else # define SET_STACK_END VALUE *stack_end = alloca(1) # endif よーするに、garbage_collect 関数自身のスタックフレームがスキャン対象に なってないのが問題なわけです。なので、フレームポインタじゃなくてスタッ クポインタを使えばいいわけで、garbage_collect 関数でのスタックポインタ はgarbage_collect 関数の中から呼んだ関数のフレームポインタなことを利用 してスタックポインタを得ています。 -- [田中 哲][たなか あきら][Tanaka Akira]