天泣記

2014-04-09 (Wed)

#1

ひさしぶりに Ruby の GC bug をひとつ潰した。

[ruby-dev:48098] [Bug #9717] で、callee save register を mark しそびれるというもの。

結局、gc.c の mark_current_machine_context の問題だったと思う。mark_current_machine_context は保守的 GC のために、スタックとレジスタを mark する関数なのだが、C で記述されているので正しく動作させるのはなかなか難しいところである。

gc.s から mark_current_machine_context を取り出すと以下のようになっていた。(Debian GNU/Linux testing (jessie) の gcc 4.8.2-16 で、-O0 を使っている)

mark_current_machine_context:
.LFB161:
 .loc 6 3499 0
 .cfi_startproc
 pushq %rbp
 .cfi_def_cfa_offset 16
 .cfi_offset 6, -16
 movq %rsp, %rbp
 .cfi_def_cfa_register 6
 pushq %r15
 pushq %r14
 pushq %r13
 pushq %r12
 pushq %rbx                     外から届いたrbxをスタックフレームにセーブ
 subq $248, %rsp
 .cfi_offset 15, -24
 .cfi_offset 14, -32
 .cfi_offset 13, -40
 .cfi_offset 12, -48
 .cfi_offset 3, -56
 movq %rdi, -280(%rbp)
 movq %rsi, -288(%rbp)
 .loc 6 3508 0
 leaq -256(%rbp), %rax
 leaq -48(%rbp), %rbx           rbx を書き換えてしまう (ここで外から届いた rbx はスタックフレームにセーブされたものしか存在しなくなる)
 movq %rbx, (%rax)
 leaq .L738(%rip), %rdx
 movq %rdx, 8(%rax)
 movq %rsp, 16(%rax)
 jmp .L739
.L738:
 leaq 48(%rbp), %rbp
.L739:
 .loc 6 3510 0                  ここから __builtin_setjmp か? rbx を扱っていない?
 movq -288(%rbp), %rax
 movq 488(%rax), %rax
 movq %rax, -272(%rbp)
 movq -288(%rbp), %rax
 movq 480(%rax), %rax
 movq %rax, -264(%rbp)
 .loc 6 3512 0
 leaq -256(%rbp), %rcx
 movq -280(%rbp), %rax
 movl $25, %edx
 movq %rcx, %rsi
 movq %rax, %rdi
 call mark_locations_array      mark_locations_array を呼んで jmpbuf を mark
 .loc 6 3514 0
 movq -264(%rbp), %rdx
 movq -272(%rbp), %rcx
 movq -280(%rbp), %rax
 movq %rcx, %rsi
 movq %rax, %rdi
 call gc_mark_locations         gc_mark_locations を呼ぶが、この関数自体のスタックフレームは範囲の外
                                SET_STACK_END を外で呼んでいるからこの関数のスタックフレーム境界になっていない
                                そのため、セーブした rbx を mark しない
 .loc 6 3522 0
 addq $248, %rsp
 popq %rbx
 popq %r12
 popq %r13
 popq %r14
 popq %r15
 popq %rbp
 .cfi_def_cfa 7, 8
 ret
.L740:
 .cfi_endproc
.LFE161:
 .size mark_current_machine_context, .-mark_current_machine_context

ちなみに、callee save な rbx にオブジェクトが入るのは parse.y の new_op_assign_gen で、

asgn->nd_value = NEW_CALL(gettable(vid), op, NEW_LIST(rhs));

という行が以下のようにっていた。

.loc 6 9598 0
movq -48(%rbp), %rax
movq %rax, -32(%rbp)
.loc 6 9599 0
movq -64(%rbp), %rdx
movq -40(%rbp), %rax
movl $0, %r8d
movl $1, %ecx
movl $40, %esi
movq %rax, %rdi
call node_newnode            NEW_LIST(rhs) の呼び出し
movq %rax, %rbx              返り値を rbx にコピー
movq -24(%rbp), %rdx
movq -40(%rbp), %rax         NEW_LIST(rhs) の値が入っていた rax を書き潰す (ここで NEW_LIST(rhs) の値は rbx にしか存在しなくなる)
movq %rdx, %rsi
movq %rax, %rdi
call gettable_gen            gettable を呼ぶ。ここで rbx は callee save なのでレジスタに入ったまま
                             つまり、この関数のスタックフレームには NEW_LIST(rhs) の値は記録されない
movq %rax, %rdx
movq -56(%rbp), %rcx
movq -40(%rbp), %rax
movq %rbx, %r8               rbx を NEW_CALL(...) の引数に使う
movl $35, %esi
movq %rax, %rdi
call node_newnode
movq -32(%rbp), %rdx
movq %rax, 24(%rdx)

rbx に入っている値は NEW_LIST(rhs) の返り値で NODE * つまり VALUE なので、new_op_assign_gen 自体は問題ではない。

new_op_assign_gen から mark_current_machine_context までの全部はたどらなかったのだが、きっと rbx がそのまま mark_current_machine_context まで届いて、mark しそびれるのだろう。

というわけで、r45542 で SET_STACK_END を mark_current_machine_context の中で呼んでそれ自身のスタックフレームの終端を検出し、そこまで mark するようにした。

というか、昔 SET_STACK_END を呼ぶようにしておいたのだが、r40703 で (ko1 により) 消されたので、復活させた。

しかし、改めて考えてみると、callee save register が mark_current_machine_context でも記録されずに gc_mark_locations までたどり着くと、mark されないかもしれない? (jmpbuf に rbx が書き込まれない場合)

__builtin_setjmp が jmpbuf に rbx をセーブしないのはなんでかなぁ。

2014-03-29 (Sat)

#1

svn ann と svn diff を繰り返し動かすのが面倒になったので、対話的にやるツールを作ってリリースした。

vcs-ann 0.1

svn だけじゃなくて git もサポートしたのだが、git のほうが完全に動く。svn ann は各行がどの revision に由来しているかは分かるのだが、その revision の何行目かはわからないのが悲しい。そのせいで、リンクをたどったときに違うところに飛んでしまうことがある。git は何行目かわかるので確実に飛べる。

2014-02-02 (Sun)

#1

localtime() で (UTCからの) 時差をいくつか調べればタイムゾーンを同定し、略称を求められるだろうか。ちょっと調べてみた。

% ruby -rpp -I. -rtzfile -e '
h = {}
TZFile.each {|tz_name, tz|
  a1 = []
  a2 = []
  tz.each_range {|t1, tt, t2|
    t1 = t1.strftime("%F %T") if tz === t1
    t2 = t2.strftime("%F %T") if tz === t2
    k1 = tt.utc_offset
    if a1.empty? || a1[-1][1] != k1
      a1 << [t1, k1, t2]
    else
      a1[-1][2] = t2
    end
    k2 = [tt.utc_offset, tt.abbrev]
    if a2.empty? || a2[-1][1] != k2
      a2 << [t1, k2, t2]
    else
      a2[-1][2] = t2
    end
  }
  h[a1] ||= {}
  h[a1][a2] ||= []
  h[a1][a2] << tz_name
}
h.each {|a1, a2_tzs|
  next if a2_tzs.size == 1
  pp a2_tzs
}
'

結果としては同定できないものがいくつか見つかった。

ほとんどは時差をまったく変えたことがないタイムゾーンのようだ。たとえば、ずっと +10:00 なタイムゾーンは

というものがあって、4種類のどれかまではわからない。

複数のタイムゾーンが同じタイミングで同じように時差を変えているが、略称は異なる、というケースもあった。Asia/Kuala_Lumpur と Asia/Singapore がそうで、出力から取り出して (ちょっと整形した後) 比較してみた。

@@ -4,9 +4,13 @@
   ["1936-01-01 00:00:00", [26400, "MALT"], "1941-09-01 00:10:00"],
   ["1941-09-01 00:10:00", [27000, "MALT"], "1942-02-16 01:30:00"],
   ["1942-02-16 01:30:00", [32400, "JST"], "1945-09-11 22:30:00"],
-  ["1945-09-11 22:30:00", [27000, "MALT"], "1982-01-01 00:30:00"],
-  ["1982-01-01 00:30:00", [28800, "MYT"], false]]=>
+  ["1945-09-11 22:30:00", [27000, "MALT"], "1965-08-09 00:00:00"],
+  ["1965-08-09 00:00:00", [27000, "SGT"], "1982-01-01 00:30:00"],
+  ["1982-01-01 00:30:00", [28800, "SGT"], false]]=>

-  ["Asia/Kuala_Lumpur",
-   "posix/Asia/Kuala_Lumpur",
-   "right/Asia/Kuala_Lumpur"],
+  ["Asia/Singapore",
+   "Singapore",
+   "posix/Asia/Singapore",
+   "posix/Singapore",
+   "right/Asia/Singapore",
+   "right/Singapore"]

1965-08-09 から異なる。調べてみるとマレーシアからシンガポールが独立した日のようだ。独立にともなってタイムゾーンの略称を変えたが、UTCからの時差は変えなかった、ということだろう。

同定するにはもうすこし情報が必要なようだ。

2014-01-29 (Wed)

#1

リモートで電源を制御する話を思い出して IPMI についてちょっと調べていたら、デスクトップとかには似たような機能で Intel AMT というものがあることを知った。vPRO に入っているらしい。

2014-01-15 (Wed)

#2

帰ってくるときに松山空港の保安検査場で荷物が引っかかって、何が引っかかったのか尋ねたらケーブル (電源タップ、ノートPCのACアダプタ、スマートフォンの充電アダプタ) だったらしい。

#1

松山

2014-01-14 (Tue)

#1

松山

2013-12-09 (Mon)

#1

ちょっと (特定用途向けの) プログラミング言語を作る必要があって、作ったのだが、名前をつけていなかった。

しかし論文にまとめることになって名前をつけざるを得なくなり、そして締切りが迫ってきて、今日、Conservation という名前に決めてみた。



田中哲