Edited at

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

More than 1 year has passed since last update.

巷で話題の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動画は実際よりかなり遅いです。

temacs.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. */