yarv-dev:705
From: SASADA Koichi <ko1 atdot.net>
Date: Mon, 26 Dec 2005 07:50:43 +0900
Subject: [yarv-dev:705] fixed> Re: open> return from ensure
ささだです。 Minero Aoki wrote: > ensure 節から return したら SEGV しました。 > > ~/c/yarv % ./miniruby -e 'def m() 2 ensure return 3; end; p m()' > -e:1: DBG> : "-e:1:in `<main>'" > -- stack frame ------------ > 0x2a95ee2010 (0000): 00000001 > 0x2a95ee2018 (0001): 00000004 > 0x2a95ee2020 (0002): 00000001 > 0x2a95ee2028 (0003): 2a95ede1d8 > 0x2a95ee2030 (0004): 2a95ede1d8 > 0x2a95ee2038 (0005): 00000004 > 0x2a95ee2040 (0006): 00000001 <- lfp <- dfp > 0x2a95ee2048 (0007): 00000005 <- bp > -- control frame ---------- > c:0004 p:0005 s:0008 b:0007 l:0006 d:0006 METHOD i:m s: - > c:0003 p:0011 s:0004 b:0003 l:0002 d:0002 TOP i:<main> s: - > c:0002 p:-001 s:0001 b:0001 l:0000 d:0000 FINISH i:- s: - > c:0001 p:-001 s:0000 b:-001 l:0000 d:0000 ------ i:- s: - > --------------------------- > [BUG] Stack consistency error (sp: 0x2a95ee2050, bp: 0x2a95ee2048) > ruby 1.9.0 (2005-11-18) [x86_64-linux] > > zsh: 936 abort (core dumped) ./miniruby -e 'def m()return 2;ensure return 3; end; p m()' > SEGV じゃなくて、rb_bug() ですね。さいごに、 > [BUG] Stack consistency error (sp: 0x2a95ee2050, bp: 0x2a95ee2048) とあるのがわかると思います。 で、これはいったい何か、というとそのスタックフレームから戻る前に、ス タックの深さのチェックをしています。end 命令は 1 つ詰まれたスタックから 戻る、という命令なのですが、その際にはスタックにはただ 1 つしか詰まれて いないといけません(深さ 1)。そうじゃないと、バグです。実際、このチェッ クを入れて、多めに積んでいた場合、stack underflow していた場合のバグを発 見できました。多くの場合、この問題は compiler のバグです。 で、その問題というのは対応しました。 どういう話だったかというと、先の命令列をコンパイルすると以下のようにな ります。 > == disasm: <ISeq:<main>@../yarv/test.rb>================================ > local scope table (size: 1, argc: 0) > > 0000 methoddef :m, <ISeq:m@../yarv/test.rb> ( 1) > 0003 putself > 0004 putself > 0005 send :m, 0, nil, 4, <ic> > 0011 send :p, 1, nil, 4, <ic> > 0017 end > == disasm: <ISeq:m@../yarv/test.rb>===================================== > == catch table > | catch type: ensure st: 0000 ed: 0002 sp: 0002 cont: 0005 > == disasm: <ISeq:ensure in m@../yarv/test.rb>=========================== > local scope table (size: 1, argc: 0) > [ 1] __$! > 0000 putobject 3 ( 1) > 0002 throw 1 > 0004 getdynamic 1, 0 > 0007 throw 0 > |------------------------------------------------------------------------ > local scope table (size: 1, argc: 0) > > 0000 putobject 2 ( 1) > 0002 putobject 3 > 0004 end > 0005 end メソッド m() の定義は > 0000 putobject 2 > 0002 putobject 3 > 0004 end > 0005 end にあるとおりですが、putobject を2回呼び出してスタックの深さが 2 になっ ていたところで end 命令を呼んでいます。スタックの深さが 2 なので、stack consistency error になります。 > [BUG] Stack consistency error (sp: 0x2a95ee2050, bp: 0x2a95ee2048) という表記を見てみると、sp が bp (base pointer) よりも 16 (... そう か、64 bit machine か)と、2 word 多いことがわかり、深さが 1 じゃない、 とそういっているわけです。 で、根本的な原因は、 * YARV では method 中の return 文は end 命令へジャンプするだけ ということでした。ensure 節をコンパイル中に、その直前でいくつスタック が詰まれているかわからない、というのが問題です。 で、解決策としては、ensure 節(メソッド中)で return があった場合、 emptstack という命令を用意して、そいつでスタックを空にして、end 命令を実 行することにしました。 > 0000 putobject 2 > 0002 emptstack > 0003 putobject 3 > 0005 end > 0006 end 解決策の他の選択肢としては、 a) TAG_hoge を新しく用意する b) stack consistency を気にしない end 命令を用意する (*) などがあったのですが、もしかしたら今後役に立つかもしれない、と思って emptstack 命令を用意しました。 (*) ちなみに、最適化オプションを有効 (vm_opts.h) にするとこのチェックは なくなります。あくまでデバッグ用の機能だから。 で、よくよく考えてみると、 def m1 *args end def m2 m1(:a, :b, (return 1; :c)) end m2 というのも同じようにエラーになるため、ensure 関係ありませんでした。 で、毎回 emptstack を挿入しようとしたら、 def m return 1 p 2 end こういうのでも同様に emptstack をはさむことになってしまったので、無駄 な命令(return の前には stack は空です)なので、コンパイル時に nop に変 更するようにしたんですが、結局 nop が残ることになってしまいました(ス タックの深さがコンパイル時に確定する段階では、もう命令の削除ができない。 命令の番地が決まってしまうため)。やっぱり、return 用の命令を考えたほう がよかったような気がします。まぁ、nop くらいいいか、という気もするんですが。 ちなみに、私はファイルに書いてデバッグするので、miniruby -e の形ではな く、複数行で書いてもらえるとうれしいです。 -- // SASADA Koichi at atdot dot net -- ML: yarv-dev quickml.atdot.net 使い方: http://www.atdot.net/~ko1/quickml
702 2005-12-26 02:29 [aamine loveruby.net ] open> return from ensure -> 705 2005-12-26 07:50 ┗[ko1 atdot.net ] fixed> Re: open> return from ensure 715 2005-12-27 15:48 ┗[aamine loveruby.net ] close> Re: return from ensure