Monthly Archives: 4月 2022

2022-04-11

Windows上のEmacs 28.1でネイティブコンパイルする方法(まとめ)

(2024-02-19追記: 手っ取り早く使い方を知りたい人はMS-Windows版 Emacs 29.1への移行作業をご覧下さい)

前回の続き。

前提

  • MSYS2のMinGW64環境下でGCCが使える状況になっていること。
  • MSYS2にlibgccjitパッケージが入っていること。
  • 公式で配布しているWindowsバイナリ emacs-28.1.zip を使用すること。

MSYS2のセットアップについては割愛します。Emacsはlibgccjitを使用してネイティブコンパイルを行うため、それに関連したファイル群をMSYS2から入手する必要があります。おそらく base-devel, mingw-w64-x86_64-toolchain, mingw-w64-x86_64-libgccjit あたりのパッケージが入っていれば良いのだと思います。

ただし実際にEmacsを使用するときにはMSYS2全体は必ずしも必要なく、少数のファイルだけコピーして他のPCに移すことも出来ます。

ここでは公式配布の emacs-28.1.zip を前提に説明しますが、自分で --with-native-compilation を指定してビルドしたEmacsを他の環境へ移す場合にも同じ考え方が適用出来ると思います。ちなみに emacs-28.1.zip は --with-native-compilation を指定してビルドされていますが(C-h v system-configuration-optionsで確認できる)ネイティブコンパイルに必要なDLL等が含まれていない状態です。

方法1:Emacs起動前からmingw64/binへPATHを通す

Emacsが起動する前に、環境変数PATHにMSYS2内の mingw64/bin ディレクトリが含まれていればネイティブコンパイルは正常に動作します(もちろんlibgccjitパッケージがインストールされていること)。

Windowsの「システムの詳細設定」で環境変数を変更するか、次のようなbatファイルを経由するといった方法が考えられます。

set PATH=c:/hogehoge/msys/mingw64/bin;%PATH%
c:/hogehoge/emacs-28.1/bin/runemacs.exe

mingw64/bin/には libgccjit-0.dll, as.exe, ld.exe が含まれている必要があります。また、 mingw64/lib/以下には必要なライブラリが含まれている必要があります。

ここで大事なのは「Emacsの起動前から」という点です。

環境変数を変更するには early-init.el や init.el 内でsetenv関数を呼び出す方法もありますが、それだと起動後すぐのネイティブコンパイルには適用されません。一番最初のネイティブコンパイルは early-init.el よりも前に起動する場合があります。なので early-init.el で (setenv "PATH" ~) しても手遅れな場合があります。

この方法は環境変数PATHを常時変更するため、人によっては許容できない場合があります。例えばCygwinやその他GNUツールを含むプロダクトに既にPATHが通っていてEmacs使用中にそのコマンドを使いたい場合です。

方法2:必要なファイルをEmacsにコピーして必要なときだけPATHを通す

alpha.gnu.org has shiny new Emacs 28.0.91 Windows binaries : emacs のコメントによれば、ネイティブコンパイルに必要なファイルは次の17個だそうです。

  • binに入れるもの
    • libgccjit-0.dll
    • (追記: 2022-09-23)libisl-23.dll (←最新のlibgccjitが必要とするdll)
    • (追記: 2023-07-31)Emacs29.1が出たので入れ直しましたが、libmpc-3.dllとlibmpfr-6.dllも増えていました(MS-Windows版 Emacs 29.1へ移行)
  • lib/gccに入れるもの
    • crtbegin.o
    • crtend.o
    • dllcrt2.o
    • libadvapi32.a
    • libgcc.a
    • libgcc_s.a
    • libkernel32.a
    • libmingw32.a
    • libmingwex.a
    • libmoldname.a
    • libmsvcrt.a
    • libpthread.a
    • libshell32.a
    • libuser32.a
    • ld.exe
    • as.exe (ldとasはリンク先では libexec/emacs/28.0.91/x86_64-w64-mingw32/ とありますが、libexec/emacs/28.1/x86_64-w64-mingw32に置いても認識されず、色々試したところlib/gccに置いたら認識されました)

これらをemacs-28.1.zipを展開して出来たディレクトリの適切なディレクトリへコピーするとネイティブコンパイルが条件付きで動作するようになります。libgccjit-0.dll は bin (既にrunemacs.exe等があるディレクトリ)へ、それ以外は lib (既にemacs/やsystemd/がある)の下にgccというディレクトリを作成してその中へ入れてください。

これだけである程度の割合でネイティブコンパイルが成功するようになるのですが、自動で起動する非同期コンパイルがなぜか失敗する場合があります(カレントディレクトリがbinやlib等の階層に無い場合にas.exeが無いと言われます)。

そこで early-init.el (あるいはinit.el)に次のコードを追加します。

(2022-09-23追記: Windows版Emacsを28.1に上げたのでNative Compilationフィーバーに便乗する - Qiitaにあるように native-comp-driver-options 変数に -B オプションを指定した方が良さそうです。私はearly-init.elで (setq native-comp-driver-options (list "-B" (expand-file-name (file-name-concat invocation-directory "../lib/gcc")) )) のようにしました)

(when (and (fboundp #'native-comp-available-p) ;;emacs-28以降
           (native-comp-available-p) ;;libgccjitが使える
           (eq system-type 'windows-nt)) ;;Windowsの場合 (他必要に応じて条件を追加すること)
  (defun my-comp-wrap-process-call (orig-fun &rest args)
    (let* (;; emacs.exeのあるディレクトリの一つ上のlib/gcc
           (lib-dir (expand-file-name
                     (file-name-concat invocation-directory "../lib/gcc")))
           ;; 環境変数PATHとLIBRARY_PATHを一時的に変更
           (process-environment
            (append
             (list (concat "PATH=" lib-dir ";" (getenv "PATH"))
                   (concat "LIBRARY_PATH=" lib-dir))
             process-environment)))
      ;; 元の関数を呼び出す
      (apply orig-fun args)))

  ;; コンパイル用にemacsを起動する関数をラップする
  (advice-add #'comp-final :around #'my-comp-wrap-process-call)
  (advice-add #'comp-run-async-workers :around #'my-comp-wrap-process-call))

このコードはネイティブコンパイルのために別プロセスでemacsを起動するときにだけ環境変数PATHとLIBRARY_PATHを変更します。起動されたemacsは最初からネイティブコンパイルに必要なコマンドにPATHが通った状態になります。

つまり、early-init.elより前に起動するネイティブコンパイルについてはlib/gccに置いたas.exeやld.exeが使われることによって解決し、それ以降なぜかエラーになるケースについては環境変数の一時的な変更で解決します。

ちなみに前者はあらかじめ必要なファイルをネイティブコンパイルしておくことによって回避可能です。エラーが出るファイルを手動でネイティブコンパイルしたり、試してはいませんが NATIVE_FULL_AOT=1 でビルドすると回避できるかもしれません。

なお、既にCygwin等にPATHが通っていると、設定が間違っていてもCygwinのas.exeやld.exeが起動する場合があるので注意が必要です。

なぜか失敗するケースは、調べた限りコンパイル時のカレントディレクトリによるようなので次のようなコードでも良いかもしれません。ただ、ネイティブコンパイルがカレントディレクトリに依存している(または将来するようになる)とまずいかもしれません。

(when (and (fboundp #'native-comp-available-p) ;;emacs-28以降
           (native-comp-available-p) ;;libgccjitが使える
           (eq system-type 'windows-nt)) ;;Windowsの場合 (他必要に応じて条件を追加すること)
  (defun my-comp-wrap-process-call (orig-fun &rest args)
    ;; 一時的にカレントディレクトリを emacs-28.1/bin にする
    ;; でないと emacs-28.1/lib/gcc/as.exe を見つけてくれない
    (let ((default-directory invocation-directory))
      ;; 元の関数を呼び出す
      (apply orig-fun args)))

  ;; コンパイル用にemacsを起動する関数をラップする
  (advice-add #'comp-final :around #'my-comp-wrap-process-call)
  (advice-add #'comp-run-async-workers :around #'my-comp-wrap-process-call))

方法3:comp.elを修正する

最も単純な方法は comp.el を修正することだと思います。

前述の通りネイティブコンパイルはearly-init.elよりも前に起動する場合があり、comp.elもその時にロードされます。

従ってearly-init.elやinit.elで挙動を完全に修正するのは無理なので、comp.elを直接書き替えてしまった方が素直な方法となるでしょう。(Emacsの初期化プロセスについて詳しくないので他に何か方法があったらすみません)

具体的には、前と同じようなことをcomp.elの末尾に書き加えてやれば良いでしょう。例えば:

;; share/emacs/28.1/lisp/emacs-lisp/comp.el の末尾、(provide 'comp)の前に以下を追加
(defconst my-comp-tool-path "c:/hogehoge/msys/mingw64/bin") ;;自分のmingw64/binの場所

(defun my-comp-wrap-process-call (orig-fun &rest args)
  (let* ((process-environment
          (cons
           (concat "PATH=" my-comp-tool-path ";" (getenv "PATH"))
           process-environment)))
    ;; 元の関数を呼び出す
    (apply orig-fun args)))

;; コンパイル用にemacsを起動する関数をラップする
(advice-add #'comp-final :around #'my-comp-wrap-process-call)
(advice-add #'comp-run-async-workers :around #'my-comp-wrap-process-call)

my-comp-tool-pathにはmingw64/binの場所を指定してください。もしくは前にやったようにemacs.exeの位置から相対的に割り出しても良いでしょう(関連ファイルのコピーが必要になりますが)。

ファイル(comp.el)の途中を書き替えるのも分かりづらいかなと思ったのでadviceのままにしてあります。make-processやcall-processの前後を直接書き替えても良いでしょう。

comp.elを書き替えたら対応する.elcや.elnを削除するのもお忘れ無く。

(追記:2022-09-23)Emacs 28.2でNative Compileする

Emacs 28.2がリリースされてWindows版の公式ビルドも公開されました。

Index of /gnu/emacs/windows/emacs-28

emacs-28.2.zipを展開して、MSYS2のlibgccjit-0.dllとlibisl-23.dllをemacs-28.2/binへ、その他のライブラリやらas.exe、ld.exeやらをemacs-28.2/lib/gcc/へコピーし、early-init.elで (setq native-comp-driver-options (list "-B" (expand-file-name (file-name-concat invocation-directory "../lib/gcc")) )) と指定しただけで問題なくNative Compileできるようになりました。

ちなみに、最初MSYS2最新のlibgccjit-0.dllをbinに入れてもNative Compileが有効になりませんでした((native-comp-available-p)がnil)。

それをTwitterに書いたところ、libisl-23.dllも必要になったとの情報を頂きました。

調べてみると確かに最新のlibgccjit-0.dll内にはlibisl-23.dllという文字列があります。libgccjitのバージョンアップに伴い依存するdllが増えたようです。libisl-23.dllもemacs-28.2/binへコピーしたところnative-comp-available-pがtになりました。

こういうことがあるからバイナリ配布するならlibgccjit(や関連ファイル)も一緒に配布してほしいものです。

(追記:2023-07-31)Emacs 29.1

MS-Windows版 Emacs 29.1へ移行に書きました。dllが二つ増えていただけです。

2022-04-06

Windows上のEmacs 28.1でNative Compilationを使う

(2024-02-19追記: 手っ取り早く使い方を知りたい人はMS-Windows版 Emacs 29.1への移行作業をご覧下さい)

Emacs 28.1がリリースされましたね。私は少し前にpretestをいじりましたがすぐに27.2に戻ったので28系を本格的に使うのはようやくこれからです。

Windows版が見当たらないので自分でビルドしようかなーと思っていたらすぐにアップロードされたのでそれ(emacs-28.1.zip)をダウンロードして試してみました。

Emacs 28と言えば大分前からネイティブコンパイルという単語をあちこちで見かけていました。WindowsでGCCに依存した機能って大変なんじゃないの? と思っていたので期待はしていなかったのですが、一応どうなっているのか調べてみることにしました。

Native Compilation (GNU Emacs Lisp Reference Manual)

ええと何々

Emacsのカレントプロセスでネイティブコンパイル済みLispコードの生成およびロードが可能かどうか判断するには、native-comp-available-p (Native-Compilation Functionsを参照)を呼び出してください。

(native-comp-available-p)
nil

あー、やっぱり……。そもそも emacs-28.1.zip の中にGCCなんて入ってないですもんね。

ええと、ネイティブコンパイル機能はlibgccjitを利用していてビルド時に --with-native-compilation の指定が必要。MSYS2にはlibgccjitパッケージもちゃんとあるんですね。

仕方ない、ビルドしますか。以前ビルドした時のMSYS2環境が残っていますし。MSYS2のEmacsパッケージ(mingw-w64-x86_64-emacs)もまだ27.2みたいですし。

MSYS2でEmacs 28.1のビルド

(2022-04-07追記: 公式配布のWindows版はどうやら --with-native-compilation 付きでビルドされているようです。ただし必要なDLLが含まれていないので (native-comp-available-p)nil になります。MSYS2のmingw64/bin/libgccjit-0.dllをEmacsのbinへコピーするかmingw64/binへPATHを通したら (native-comp-available-p)t になりました。なので以下のビルドはしなくても行けます。ただしmingw64/binへ常にPATHを通したくない場合は色々と調整する必要があります(後述))

nt/INSTALLnt/INSTALL.W64等を見るにビルド方法に大きな変更は無さそうです。

まずはMSYS2から既存パッケージのアップグレード(sourceforgeのミラーが廃止されたんですね。/etc/pacman.d以下を少し修正)。

その後今回追加で必要になるであろうmingw-w64-x86_64-libgccjitをインストール。

pacman -S mingw-w64-x86_64-libgccjit

次にMinGW64環境からビルド。 --without-dbus--with-gnutls は前にビルドした時に付けていたので一応。今も必要なのか不明。 --with-native-compilation は無いとダメだったので付けました。 --prefix にはインストール先を明示的に指定します。後でMinGW64環境と分離したいので。

mkdir 20220406 && cd 20220406
wget https://ftp.gnu.org/gnu/emacs/emacs-28.1.tar.gz
tar xvfz emacs-28.1.tar.gz
./configure --without-dbus --with-gnutls --with-native-compilation --prefix=/c/hogehoge/emacs-28.1
make -j4 #4はコア数
make install-strip
/c/hogehoge/emacs-28.1/bin/runemacs.exe -Q # 試しに起動
(native-comp-available-p)
t

うん、大丈夫っぽい。というか裏で自動的にコンパイルが走っててキモいんですけど(笑)

MSYS2 MinGW環境からの分離

次にMSYS2 MinGW環境以外からでも単独で起動できるように必要なDLL等をemacsのディレクトリへコピーします。

これで runemacs.exe をエクスプローラ等から直接実行しても起動するようになりますが……やはりネイティブコンパイルでエラーが出まくります。libgccjitがGCCを見つけられないのですから当たり前ですよね。というかCygwinのが起動されてるっぽい? ldがライブラリが見つからないとか言ってます。私はCygwinの方にはPATHを通してしまっていますからね。Cygwinのldが起動されて、それがMinGWのライブラリが見つけられないという流れになってしまっているのかもしれません。

Internals — libgccjit 12.0.1 (experimental ) documentation あたりを見ると、PATH、LIBRARY_PATHあたりの環境変数をちゃんとすれば何とかなりそう。

試しに (setenv "PATH" (concat "c:/hogehoge/msys/mingw64/bin;" (getenv "PATH"))) を評価してPATHの先頭にMinGWのbinを追加したところコンパイルが成功するようになりました。

うーん、でも常時MinGWにPATHを通したくないんだよなぁ……。

native-comp-async-env-modifier-form という変数があったので、そこで環境変数を設定してみるもうまくいかず。

ネイティブコンパイルは外部のemacsを起動して行う(コンパイル用のelファイルを生成して、 emacs --batch -l ????.el を起動する)みたいなので、その起動時にだけ環境変数を追加することにしました。

(when (and (fboundp #'native-comp-available-p)
           (native-comp-available-p))
  (defun my-comp-set-env (orig-fun &rest args)
    (let ((process-environment
           (append
            (list (concat "PATH=c:/hogehoge/msys/mingw64/bin;" (getenv "PATH"))
                  ;;"LIBRARY_PATH=c:/hogehoge/msys/mingw64/lib" 不要っぽい
                  )
            process-environment)))
      (apply orig-fun args)))

  (require 'comp)
  (advice-add #'comp-final :around #'my-comp-set-env) ;; call-processでemacsを起動している
  (advice-add #'comp-run-async-workers :around #'my-comp-set-env)) ;;make-processでemacsを起動している

適当ですがとりあえず。これで直接runemacs.exeを起動したとき(PATHにmingw64/binが含まれていないとき)でもネイティブコンパイルが成功するようになりました。

で、 ~/.emacs.d/eln-cache/ に色々ファイルが生成されているわけですが、これってちゃんと使われているのでしょうか。スピードも上がっているのでしょうか。後で調べてみようっと。

(2022-04-08追記:)

28.0.91(pretest)のWindows用バイナリが出たときのredditの投稿に、参考になることが色々書いてありました。

alpha.gnu.org has shiny new Emacs 28.0.91 Windows binaries : emacs

中でも、MSYS2から17ファイルほどコピーすればMSYS2無しでもネイティブコンパイルが出来るのだとか。

  • bin/libgccjit-0.dll
  • lib/gcc/crtbegin.o
  • lib/gcc/crtend.o
  • lib/gcc/dllcrt2.o
  • lib/gcc/libadvapi32.a
  • lib/gcc/libgcc.a
  • lib/gcc/libgcc_s.a
  • lib/gcc/libkernel32.a
  • lib/gcc/libmingw32.a
  • lib/gcc/libmingwex.a
  • lib/gcc/libmoldname.a
  • lib/gcc/libmsvcrt.a
  • lib/gcc/libpthread.a
  • lib/gcc/libshell32.a
  • lib/gcc/libuser32.a
  • libexec/emacs/28.0.91/x86_64-w64-mingw32/ld.exe
  • libexec/emacs/28.0.91/x86_64-w64-mingw32/as.exe

MSYS2の中からファイルを探してemacs-28.1.zipを展開したディレクトリ下の上記の場所にコピーして試したところ、確かに関数の同期コンパイルくらいは出来ているように見えます(私のPCはCygwinのldにPATHが通ってたりするので、他のPCで再現しない可能性あり。後でちゃんと確認します)。非同期のコンパイルはPATHを調整しないとダメだとも書いてあります。

後でもう少し詳しく調べて追記します。

(2022-04-11追記: 別の記事として書きました)

Windows上のEmacs 28.1でネイティブコンパイルする方法(まとめ)

2022-04-06

Windows上のEmacsでSVG画像内のimage要素が表示されない問題

Emacs27になってからSVG画像内のimage要素が表示されなくて困ったのですが、調べたところ lib/gdk-pixbuf-2.0/2.10.0/loaders.cache ファイルが無いのが原因だと分かりました。

いずれも Index of /gnu/emacs/windows にある公式のWindows版ビルドでの話です。独自ビルドして必要そうなDLLファイルをMinGWから抜き出した場合も起こるかもしれません。

表示されないSVGの例

例えば次のようなSVGでimage要素の部分が表示されずblueのrectだけが表示されます。

<svg width="400" height="300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <rect x="10px" y="10px" width="100px" height="80px" fill="blue" />
  <image xlink:href="image1.jpg" x="20px" y="20px" width="100px" height="100px" />
  <image xlink:href="image2.png" x="30px" y="30px" width="100px" height="100px" />
  <image xlink:href="image3.bmp" x="40px" y="40px" width="100px" height="100px" />
</svg>

image1.jpgimage2.pngimage3.bmp はSVGと同じディレクトリにあるものとします。もちろんブラウザ等、他のソフトウェアでは正しく表示されます。Emacsでの確認方法は色々ありますがdiredから開くだけでも十分です。

Emacs 26.3の場合

https://ftp.gnu.org/gnu/emacs/windows/emacs-26/emacs-26.3-x86_64.zip

問題は起きません。正しく表示されます。

Emacs 27.2の場合

解決方法

cmdプロンプトで emacs-27.2-x86_64/bin ディレクトリへ移動し、次のコマンドを実行すると emacs-27.2-x86_64/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache というファイルが生成されます。

gdk-pixbuf-query-loaders.exe --update-cache

Emacsを再起動して再び試してみると正しく表示されました。

Emacs 28.1の場合

https://ftp.gnu.org/gnu/emacs/windows/emacs-28/emacs-28.1.zip

image要素が表示されません。

解決方法

そもそも emacs-28.1/lib/ 以下に gdk-pixbuf-2.0 がありません。

Emacs27のファイル(emacs-27.2-x86_64/lib/gdk-pixbuf-2.0 以下。もちろん loaders.cache も)をEmacs28へコピーしたところ正しく表示されました。

loaders.cache を削除すると正しく表示されないので、Emacs28でもこのファイルは必要なようです。 emacs-28.1.zip には gdk-pixbuf-query-loaders.exe が含まれていないので、生成するにはEmacs 27の場合の手順を踏んでそちらからコピーしてくるのが手っ取り早いと思います。

原理

EmacsはSVGの描画にlibrsvgを使用していますが、librsvgは画像の処理にlibgdk_pixbufを使用しています。

libgdk_pixbufで画像を読み込むには画像の形式に対応するローダーライブラリが必要で、それが lib/gdk-pixbuf-2.0/2.10.0/loaders/ 以下にあるDLLファイル群です。

loaders.cache ファイルには、DLLファイルへのパスとそれが受け付けるMIMEタイプ、拡張子、ファイル先頭のマジックナンバー等の対応表が記されているようです。詳しくは調べていませんが、このファイルが無いとlibgdk_pixbufは画像を読み込めないようです。

不思議なことにEmacs26はこのファイルが無くても正しく読み込めていました。おそらくライブラリのバージョンアップに伴って必要になったのではないでしょうか。

必要性

EmacsでSVG内のimage要素、それもWindowsで扱う必要がある人はどれだけいるのでしょうか。多分ほとんどの人が問題に気がついていないのだと思います(まさか私の環境だけ起きてる?)。

しかしsvg.elにはsvg-embedのようなimage要素を使う機能も存在します。

また、Emacs28ではSVG内のimage要素が外部ファイルを参照する時の基準ディレクトリを指定する機能も追加されました。(https://github.com/emacs-mirror/emacs/blob/78ecd67888566167fb4c881d8350f611fa039649/etc/NEWS.28#L2417 )

図形だけでも面白いのに画像を自由に配置できるとなればもっと面白いことが色々出来るはずです。皆さんも是非試してみてはいかがでしょうか。

私も試しにimage要素を沢山生成して遊んでみました。

2022-04-06-svg-image-ex.gif

一枚一枚の画像をtransform属性で変形し(残念ながら透視投影は無理なので正射影です)、面単位でZソートを行っています。

結構遅いです。

何となくEmacs Lisp側というよりは描画ライブラリ側が遅いような気が。