2010年2月13日土曜日

gccが誤ったコードを生成する(gcc 4.4.0)

最近Emacsが正しくビルドされないことがあり、少し原因を調べてみました。

gccに「-march=native -O3」オプションを渡して Emacs 23.1 をビルドすると make bootstrap が最後まで通りません。ビルドを通すためには、最適化レベルを落とすか、「-fno-tree-vectorize」オプションを加える必要があるようです(*)
*このオプションは@yunhさんから教えていただきました。

1.現象
make bootstapの最中に以下のコマンドが実行されたところでエラーダイアログが表示されます。
"./oo-spd/i386/temacs.exe" -batch -l loadup bootstrap




以下の環境でビルドしています。
OS:Windows 7 64bit
CPU: Core i7-860
ビルド環境:MinGW 5.1.6, MSYS 1.0.11, gcc 4.4.0 (mingw32)

2.解析(何がおきているのか?)
コマンドラインから上記のコマンドをたたくとエラーが再現できるので、gdbで様子を見てみました。gdbはMinGWのサイトからダウンロードした gdb-7.0.50.20100202-mingw32-bin.tar.gz を使っています。

C:\work\emacs-23.1\nt>mingw32-make.exe ARCH_CFLAGS="-c -mno-cygwin -march=native -O3" bootstrap

(中略)

echo oo-spd/i386/w32font.o oo-spd/i386/w32uniscribe.o >> oo-spd/i386/buildobj.lst
mingw32-make.exe[2]: Leaving directory `D:/temp/work/emacs-23.1/src'
"./oo-spd/i386/temacs.exe" -batch -l loadup bootstrap
mingw32-make.exe[1]: *** [bootstrap-emacs] Error 255
mingw32-make.exe[1]: Leaving directory `D:/temp/work/emacs-23.1/src'
mingw32-make.exe: *** [bootstrap-gmake] Error 2

エラーが発生したコマンドをgdb上で走らせてみます。
C:\work\emacs-23.1\nt>cd ..\src

C:\work\emacs-23.1\src>gdb .\oo-spd\i386\temacs.exe
GNU gdb (GDB) 7.0.50.20100202
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "mingw32".
For bug reporting instructions, please see:
...
Reading symbols from C:\work\emacs-23.1\src/.\oo-spd\i386\temacs.exe...done.
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) [answered Y; input not from terminal]
Environment variable "DISPLAY" not defined.
Environment variable "TERM" not defined.
Temporary breakpoint 1 at 0x10af525
(gdb) run -batch -l loadup bootstrap
Starting program: C:\work\emacs-23.1\src/.\oo-spd\i386\temacs.exe -batch -l loadup bootstrap
[New Thread 5208.0xa08]
Error: dll starting at 0x77630000 not found.
Error: dll starting at 0x76bf0000 not found.
Error: dll starting at 0x77630000 not found.
Error: dll starting at 0x77530000 not found.

Program received signal SIGSEGV, Segmentation fault.
0x010c6b52 in init_charset_once ()
(gdb) bt
#0  0x010c6b52 in init_charset_once ()
#1  0x01002ebf in main ()

Segmentation faultで落ちました。バックトレースすると main() から呼ばれた init_charset_once() の中で落ちているようです。
次に落ちている箇所のアセンブラコードとレジスタの状態を見てみることにします。
(gdb) disass
Dump of assembler code for function init_charset_once:
   0x010c6b30 <+0>:     push   %ebp
   0x010c6b31 <+1>:     mov    $0x16eedf8,%eax
   0x010c6b36 <+6>:     mov    %esp,%ebp
   0x010c6b38 <+8>:     push   %edi
   0x010c6b39 <+9>:     push   %esi
   0x010c6b3a <+10>:    push   %ebx
   0x010c6b3b <+11>:    sub    $0xc,%esp
   0x010c6b3e <+14>:    movdqa 0x16a4c30,%xmm0
   0x010c6b46 <+22>:    lea    0x400(%eax),%ecx
   0x010c6b4c <+28>:    lea    0x200(%eax),%edx
=> 0x010c6b52 <+34>:    movdqa %xmm0,(%eax)
   0x010c6b56 <+38>:    add    $0x10,%eax
   0x010c6b59 <+41>:    cmp    %edx,%eax

(中略)

   0x010c6f29 <+1017>:  lea    0x0(%esi,%eiz,1),%esi
End of assembler dump.

(gdb) info registers
eax            0x16eedf8        24047096
ecx            0x16ef1f8        24048120
edx            0x16eeff8        24047608
ebx            0x5      5
esp            0x88fe90 0x88fe90
ebp            0x88fea8 0x88fea8
esi            0x0      0
edi            0x1      1
eip            0x10c6b52        0x10c6b52
eflags         0x10206  [ PF IF RF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x53     83
gs             0x2b     43

(gdb)

 落ちた箇所は「0x010c6b52 <+34>:    movdqa %xmm0,(%eax)」で、このときの eaxレジスタの中身は「0x16eedf8(24047096)」です。

3.問題点
このmovdqa命令ですが、調べたところ128bit単位でmovするSSE2命令らしく、使用条件として「転送元/先のアドレスが16byte(128bit)でアラインメント」されている必要があります。

今回の場合は 0x16eedf8(24047096) なので16で割ると...割り切れません。正しくアラインメントされたアドレスを使っていないようです。。。

問題が発生したオプションは「-march=native -O3」でした。僕の環境では -march=native は以下の様に解釈されているようです。(CPUはCore i7-860)
-march=core2 -mcx16 -msahf --param l1-cache-size=32 --param l1-cache-line-size=64 --param l2-cache-size=256 -mtune=core2

#ここから色々推測が入ります
最適化レベルを-O2から-O3にすると、暗黙的に-ftree-vectorizeが指定されるようです。どうもこのオプションが有効になることで(-march=core2なので)SSEを使った最適化がされるようですが、アラインメントをミスったコードを生成してしまっているようです。このオプションが悪いわけではなく、gccが正しくアラインメントされたメモリアドレスを扱うコードを生成するべきでしょう。gccのバグっぽいです。

4.回避策
core2に特化したコード生成を行う場合で-O3以上の最適化レベルを指定する場合は「-fno-tree-vectorize」オプションを加えて逃げるというworkaroundをとるしかない(と思います)。新しいgccではもしかするとバグが修正されているかもしれません。


最後に、本件を調査する際に参考になったリンクをあげておきます。

最近はgdbを使ったり、SSE命令など調べたことがなかったので勉強になりました:)

2010年2月9日火曜日

Emacs Scratchバッファの自動保存とリストア

1年ほど前にどこかのサイトで発見して、ありがたく使わせていただいているのが「Scratchバッファの自動保存とリストア」をするためElisp。Emacs終了時に自動的にScratchバッファの内容を保存して、次回起動時に復元してくれる。
;;; auto save and restore scratch buffer
(defun save-scratch-data ()
  (let ((str (progn
               (set-buffer (get-buffer "*scratch*"))
               (buffer-substring-no-properties
                (point-min) (point-max))))
        (file "~/.scratch"))
    (if (get-file-buffer (expand-file-name file))
        (setq buf (get-file-buffer (expand-file-name file)))
      (setq buf (find-file-noselect file)))
    (set-buffer buf)
    (erase-buffer)
    (insert str)
    (save-buffer)
    (kill-buffer buf)))

(defadvice save-buffers-kill-emacs
  (before save-scratch-buffer activate)
  (save-scratch-data))

(defun read-scratch-data ()
  (let ((file "~/.scratch"))
    (when (file-exists-p file)
      (set-buffer (get-buffer "*scratch*"))
      (erase-buffer)
      (insert-file-contents file))
    ))

(read-scratch-data)
これを .emacs に記述して長い間使ってる。かなり便利で、即席のメモ帳として使える。どこのサイトでオリジナルを見つけたのか今となってはわからないのだが、、、ありがとうございます>作者の方。

#僕はEmacs Lisp初心者で勉強中。