@ledsun blog

無味の味は佳境に入らざればすなわち知れず

Fixing Assignment Evaluation Order

rubykaigi.org

1行のRubyプログラムを左から順に評価していきます。 これは意図時にデザインしたルールではなく、実装の結果です。

a[0], a[1] = [b, b] のときはそうではありません。

発表者のJeremy Evansさんは、Rubyのバグを大量に直しているコミッターの方です。 そのなかでも面白いバグを初回してくれました。

内容はよくわからなかっかです。 ruby --dump=iを実行するとRubyのinstructionsを表示することがわかりました。 試しに a = 1のinstructionsを表示してみます。

ledsun@MSI:/m/c/U/led_l►ruby --dump=i -e 'a = 1'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,5)> (catch: FALSE)
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] a@0
0000 putobject_INT2FIX_1_                                             (   1)[Li]
0001 dup
0002 setlocal_WC_0                          a@0
0004 leave

表示されている命令の意味はよくわかりません。 a = 2b = 1のinstructionsを表示すると、内容が変わっていることがわかります。

ledsun@MSI:/m/c/U/led_l►ruby --dump=i -e 'a = 2'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,5)> (catch: FALSE)
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] a@0
0000 putobject                              2                         (   1)[Li]
0002 dup
0003 setlocal_WC_0                          a@0
0005 leave
ledsun@MSI:/m/c/U/led_l►ruby --dump=i -e 'b = 1'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,5)> (catch: FALSE)
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] b@0
0000 putobject_INT2FIX_1_                                             (   1)[Li]
0001 dup
0002 setlocal_WC_0                          b@0
0004 leave

この機能をつかって、a.b, c[0] = d, e のinstructionsを表示して、Ruby 3.0から3.2までの変更を説明してくれました。 実際に手元のPCで試してみると、発表と同じように表示されるinstructionsが変わっているのがわかります。

ledsun@MSI:/m/c/U/led_l►ruby -v --dump=i -e 'a.b, c[0] = d, e'
ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x86_64-linux]
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,16)> (catch: FALSE)
0000 putself                                                          (   1)[Li]
0001 opt_send_without_block                 <calldata!mid:d, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 putself
0004 opt_send_without_block                 <calldata!mid:e, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0006 newarray                               2
0008 dup
0009 expandarray                            2, 0
0012 putself
0013 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0015 topn                                   1
0017 opt_send_without_block                 <calldata!mid:b=, argc:1, ARGS_SIMPLE>
0019 pop
0020 pop
0021 putself
0022 opt_send_without_block                 <calldata!mid:c, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0024 putobject_INT2FIX_0_
0025 topn                                   2
0027 opt_aset                               <calldata!mid:[]=, argc:2, ARGS_SIMPLE>
0029 pop
0030 pop
0031 leave
ledsun@MSI:/m/c/U/led_l►ruby -v --dump=i -e 'a.b, c[0] = d, e'
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,16)> (catch: FALSE)
0000 putself                                                          (   1)[Li]
0001 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 putself
0004 opt_send_without_block                 <calldata!mid:c, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0006 putobject_INT2FIX_0_
0007 putself
0008 opt_send_without_block                 <calldata!mid:d, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0010 putself
0011 opt_send_without_block                 <calldata!mid:e, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0013 newarray                               2
0015 dup
0016 expandarray                            2, 0
0019 topn                                   5
0021 swap
0022 opt_send_without_block                 <calldata!mid:b=, argc:1, ARGS_SIMPLE>
0024 pop
0025 topn                                   3
0027 topn                                   3
0029 topn                                   2
0031 opt_aset                               <calldata!mid:[]=, argc:2, ARGS_SIMPLE>[CcCr]
0033 pop
0034 pop
0035 setn                                   3
0037 pop
0038 pop
0039 pop
0040 leave
ledsun@MSI:/m/c/U/led_l►ruby -v --dump=i -e 'a.b, c[0] = d, e'
ruby 3.2.0preview2 (2022-09-09 master 35cfc9a3bb) [x86_64-linux]
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,16)> (catch: false)
0000 putself                                                          (   1)[Li]
0001 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 putself
0004 opt_send_without_block                 <calldata!mid:c, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0006 putobject_INT2FIX_0_
0007 putself
0008 opt_send_without_block                 <calldata!mid:d, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0010 putself
0011 opt_send_without_block                 <calldata!mid:e, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0013 newarray                               2
0015 dup
0016 expandarray                            2, 0
0019 topn                                   5
0021 swap
0022 opt_send_without_block                 <calldata!mid:b=, argc:1, ARGS_SIMPLE>
0024 pop
0025 topn                                   3
0027 topn                                   3
0029 topn                                   2
0031 opt_aset                               <calldata!mid:[]=, argc:2, ARGS_SIMPLE>[CcCr]
0033 pop
0034 pop
0035 setn                                   3
0037 pop
0038 pop
0039 pop
0040 leave

どうやらJeremy Evansさんはこういうところを直しているみたいです。 instructionsが読めないので、具体的にどうなおしたのかはわかりませんでした。

を読んだら理解できるようになるでしょうか?

tatsu-zine.com