emacs 25.1をMac OSXでコンパイルしてみた


巷で話題のemacsをmacOSでコンパイルしてみました。

目的

  • malloc_set_state malloc_get_stateなしでもコンパイルできることを実証する。
  • unexecされていないemacsの起動時間を確認する

追記:malloc_get_state/malloc_set_stateはOSXではそもそも使われていないので、実証する意味がありませんでした。

環境

  • macOS Sierra 10.12.2
  • コンパイラ Apple LLVM 8.0.0 (clang-800.0.42.1)

コンパイル手順

wget http://ftp.gnu.org/gnu/emacs/emacs-25.1.tar.gz
tar xf emacs-25.1.tar.gz
cd emacs-25.1

# /usr/localに余計なものがたくさんあるので、標準のパスだけ使う
export PATH=/usr/bin:/bin:/usr/sbin:/sbin

./configure
# GNU mallocは使わない設定になる。
#   Should Emacs use the GNU version of malloc?             no

time make

# real  1m5.474s
# user  0m59.310s
# sys   0m4.596s

tgzで60MB, コンパイル時間1分強。最近の重量級のソフトに比べたらかなり小さいです。バイナリはsrcの下にできます。

調査

malloc_set_state malloc_get_stateを使っていないことを確認します。 otool(Linuxのldd相当)で何をロードするのか確認してみます。

$ otool -L src/emacs
src/emacs:
    /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 1504.76.0)
    /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
    /usr/local/opt/jpeg/lib/libjpeg.8.dylib (compatibility version 13.0.0, current version 13.0.0)
    /usr/lib/libxml2.2.dylib (compatibility version 10.0.0, current version 10.9.0)
    /usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
    /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.8)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.0.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1348.28.0)
    /System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics (compatibility version 64.0.0, current version 1070.13.0)
    /System/Library/Frameworks/CoreText.framework/Versions/A/CoreText (compatibility version 1.0.0, current version 1.0.0)
    /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1349.25.0)
    /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)

C/Foundationがlibc相当だと思うので、mallocをgrepしてみます。malloc_set_state / malloc_get_stateはありませんね。

追記:その代わりzoneを使って実現しているそうです。これはMacOS専用らしいです

$ nm /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation | grep malloc
                 U _malloc
00000000001b087c t _mallocAcquire
                 U _malloc_create_zone
                 U _malloc_default_zone
                 U _malloc_get_zone_name
                 U _malloc_good_size
                 U _malloc_set_zone_name
                 U _malloc_size
                 U _malloc_zone_calloc
                 U _malloc_zone_free
                 U _malloc_zone_from_ptr
                 U _malloc_zone_malloc
                 U _malloc_zone_realloc

起動

./src/temacsでtemacs起動。ざっくり6秒くらいです。このGIF動画は実際よりかなり遅いです。

画面は載せませんが./src/emacs -nwでemacsを起動したら一瞬でした。emacsはdump済みのバイナリでMakefileを見て見ると、次のコマンドでdumpしている様子。

./temacs --batch --load loadup bootstrap

感想(考察でさえないです)

あのelispをスルスルとロードする様子、昔大学でDEC Ultrixを使っていた時に見た気がする。ということはUltrixのemacsはunexecされていなかったのかも。今となってはGNU libcにリンクされていたのかどうかもわからないけど。

Macでdumpができているのはおそらくemacsが独自に持っている仕組みのためかな?どこかで読んだ気がする。 追記: リンク先の記事にそう明記されている。dumpできるのは当たり前。

emacsの話はおっさんホイホイだと自覚しました。

追記

Mac OSのunexecはsrc/unexmacosx.c実装されていました。説明を読んでもよくわかりませんが、OS毎に実装されているunexecがメンテ不能になるのは想像に難くありません。

     36 /* The Mac OS X implementation of unexec makes use of Darwin's `zone'
     37    memory allocator.  All calls to malloc, realloc, and free in Emacs
     38    are redirected to unexec_malloc, unexec_realloc, and unexec_free in
     39    this file.  When temacs is run, all memory requests are handled in
     40    the zone EmacsZone.  The Darwin memory allocator library calls
     41    maintain the data structures to manage this zone.  Dumping writes
     42    its contents to data segments of the executable file.  When emacs
     43    is run, the loader recreates the contents of the zone in memory.
     44    However since the initialization routine of the zone memory
     45    allocator is run again, this `zone' can no longer be used as a
     46    heap.  That is why emacs uses the ordinary malloc system call to
     47    allocate memory.  Also, when a block of memory needs to be
     48    reallocated and the new size is larger than the old one, a new
     49    block must be obtained by malloc and the old contents copied to
     50    it.  */