让官方编译的 Windows 版 Emacs 29.2 的 native-comp 特性生效

昨天我一直关注的类型声明分支总算是合并了:Merge branch ‘lisp-func-type-decls’ into ‘master’,理论上添加函数的类型声明后 native-comp 能够更有效地优化代码,不过具体的优化代码似乎还没有写。看到这个分支合并后我在 Windows 上用 MSYS2 拉下来编译了一下,没啥感觉(汗)。

这个帖子是介绍我在折腾 native-comp 的时候找到的不用在 MSYS2 的 Mingw64 SHELL 中启动 Emacs 也能用上 native-comp 的方法。使用的方法来自: MS-Windows版 Emacs 29.1への移行作業 | Misohena Blog

Emacs 官方提供的官方 Windows 编译版本

在 Emacs 的 FTP 里面提供了编译好的 Windows 版本可供下载,下载后运行 bin 目录下的 runemacs.exe 即可启动 Emacs。以 Emacs 29.2 为例,在 bin 目录下已经提供了许多 DLL 文件,但其中没有 libgccjit 相关的 DLL。

虽然我们在运行 (native-comp-available-p) 时得到的结果是 nil ,但是查看 system-configuration-options 时可以注意到它在编译时启用了 native-comp 特性:

system-configuration-options is a variable defined in ‘C source code’.

Its value is
"--with-modules --without-dbus --with-native-compilation=aot --without-compress-install --with-tree-sitter --with-sqlite3 CFLAGS=-O2"

String containing the configuration options Emacs was built with.

[back]

使 (native-comp-available-p) 返回 t

根据 Windows 的 DLL 查找规则,我们将 gccjit 相关的 DLL 放在 Emacs 的 bin 目录下即可。我们可以在 MSYS2 的 Mingw64 SHELL 中通过以下命令安装 libgccjit:

pacman -S mingw-w64-x86_64-libgccjit

安装完成后,MSYS2 的目录中的 mingw64/bin 中会出现一个名叫 libgccjit-0.dll 的文件,但光是把它拖到 emacs 的 bin 目录下还不够,它依赖这些 DLL:

其中除系统 DLL 和 libisl-23.dll, libmpc-3.dlllibmpfr-6.dll 外,Windows 版 Emacs 均已提供,因此我们只需要将 mingw64/bin 下的以下 DLL 移动至 Emacs 的 bin 目录下即可:

  • libgccjit-0.dll
  • libisl-23.dll
  • libmpc-3.dll
  • libmpfr-6.dll

完成以上操作并重启 Emacs 后,调用 (native-comp-available-p) 应该会得到 t

添加 as.exeld.exe

虽然此时 native-comp 特性已经可用,但当你切换到 *Async-native-compile-log* buffer 时,你会注意到编译因为找不到 as 而失败。(我在出现这个错误的时候忘记截图了,这是来自一篇文章的输出结果: Windows版Emacsを28.1に上げたのでNative Compilationフィーバーに便乗する

Compiling c:/emacs/share/emacs/28.1/lisp/emacs-lisp/cconv.el...
x86_64-w64-mingw32-gcc-11.3.0: fatal error: cannot execute 'as': CreateProcess: No such file or directory
compilation terminated.

参考在帖子开头给出的教程,我们可以在 Emacs 的 lib 目录下创建 gcc 目录,然后从 mingw64/bin 中复制一份 as.exeld.exe 过来,记得带上它们依赖的 DLL:

  • as.exe
  • ld.exe
  • libzstd.dll
  • zlib1.dll

添加文件后,在自己的配置文件中添加以下配置来指定这些文件的路径:

 (setq native-comp-driver-options (list "-B" (expand-file-name (file-name-concat invocation-directory "../lib/gcc"))))

此时再次重启 Emacs, *Async-native-compile-log* 中的错误会变成一系列的缺少库文件:(我也忘了截图了,只能偷别人的):

Compiling c:/emacs/share/emacs/28.1/lisp/language/japan-util.el...
ld: dllcrt2.o が見つかりません: No such file or directory
ld: crtbegin.o が見つかりません: No such file or directory
ld: -lmingw32 が見つかりません
ld: -lgcc_s が見つかりません
ld: -lgcc が見つかりません
ld: -lmoldname が見つかりません
ld: -lmingwex が見つかりません
ld: -lmsvcrt が見つかりません
ld: -lmingw32 が見つかりません
ld: -lgcc_s が見つかりません
ld: -lgcc が見つかりません
ld: -lmoldname が見つかりません
ld: -lmingwex が見つかりません
ld: -lmsvcrt が見つかりません
ld: crtend.o が見つかりません: No such file or directory
c:\emacs\bin\libgccjit-0.dll: error: error invoking gcc driver
c:/emacs/share/emacs/28.1/lisp/language/japan-util.el: Error: Internal native compiler error failed to compile

根据帖子开头的教程,需要从以下位置复制文件到 Emacs 目录中的 lib/gcc 目录下:

[mingw64/lib]
crtbegin.o
crtend.o
dllcrt2.o
libadvapi32.a
libgcc_s.a
libkernel32.a
libmingw32.a
libmingwex.a
libmoldname.a
libmsvcrt.a
libpthread.a
libshell32.a
libuser32.a
[mingw64/lib/gcc/x86_64-w64-mingw32/13.1.0/]
libgcc.a

再次重启 Emacs ,应该就能正常完成编译了。

Summary

以下是我现在使用的关于 native-comp 的配置:

;;@@NATIVE-COMP
;; https://misohena.jp/blog/2023-07-31-setup-emacs-29-1-for-windows.html
;;te 在 bin/ 下添加必要库
;; 注意 bin/ 中 libgccjit-0.dll 存在以下依赖项:
;; 可使用 https://github.com/lucasg/Dependencies 获取 DLL 的依赖项
;; system32/{advapi32.dll, kernel32.dll, MSVCRT.dll}
;; libgcc_s_seh-1.dll, libgmp-10.dll
;; libisl-23.dll, libmpc-3.dll, libmpfr-6.dll ; 注意这三个
;; libwinpthread-1.dll, zlib1.dll, libzstd.dll
;; 随后,在 /lib/gcc/ 下添加以下内容:(一共 18 个)
;; mingw64/bin/ 中的 as.exe 和 ld.exe,它们依赖同目录下的 libzstd.dll 和 zlib1.dll
;; mingw64/lib 下的以下文件:
;; crtbegin.o, crtend.o, dllcrt2.o
;; lib{ advapi32.a, gcc_s.a, kernel32.a, mingw32.a
;;      mingwex.a, moldname.a, msvcrt.a, pthread.a
;;      shell32.a, user32.a}
;; mingw64/lib/gcc 中的 libgcc.a

;; 忽略 native-comp 过程中的 warning
(setopt native-comp-async-report-warnings-errors nil)
;; 添加编译的工具和库位置
(when (and (fboundp #'native-comp-available-p)
	   (native-comp-available-p)
	   (eq system-type 'windows-nt))
  (setopt native-comp-driver-options
	  (list "-B" (expand-file-name (file-name-concat
					invocation-directory
					"../lib/gcc")))))

我原本打算重现一遍我之前遇到的错误,但即使我注释掉了上面的代码,native-comp 似乎也能正常工作,很奇怪…

也许之后版本的 Windows 版 Emacs 会提供 native-comp 依赖项,也就不用我们手动添加了。

1 个赞

这又有msys、又有win32环境,是不是msys本身就可以一步到位的? 注释了仍然能工作,猜测:有没有可能那些依赖dll本就在path环境变量里,无需复制?

1 个赞

直接在 MSYS2 的 Mingw64 SHELL 里启动 Emacs 是能够正常使用 native-comp 特性的,这个帖子主要是解决不在这个环境启动 Emacs 的情况。

我在写的时候检查过环境变量,我没有添加 mingw64/bin 到环境变量里面。现在看了一下也没有。不知道 native-comp 是不是有什么设定保存,这个得研究一下。

正好官方编译的 Emacs 29 有三个,29.1, 29.2 和 29.3。我在 29.2 里尝试了添加这些文件到 emacs 的 bin 目录和 /lib/gcc 目录并修改 native-comp 的相关选项。那接下来我试试测试 29.1 时添加 mingw64/bin 的环境变量,在 29.3 试试仅第一次在 MSYS2 环境中启动 Emacs,并测试随后是否能够正常进行 native-comp 编译,然后输出文件到 eln-cache 中。

emacs 29.1

在 Emacs 29.1 中,我同样将上个帖子提到了的和 libgccjit 有关的 4 个 DLL 放到了 Emacs 的 bin 目录下,当我没有设定到 mingw64/bin 下的环境变量 PATH 时,通过 emacs -Q 打开 Emacs 会出现如下 Warning:

在我在环境变量 PATH 中加上 mingw64/bin 路径后,再次打开 emacs -Q ,可以得到如下结果:

再次取消掉环境变量 PATH 中的 mingw64/bin,打开 emacs -Q ,然后通过 pacakge-install 随便安装一个包,可以看到如下结果:

这应该能说明添加 mingw64/bin 目录到 PATH 中可使 native-comp 正常工作。

emacs 29.3

和上一步一样,添加 4 个 DLL 到 Emacs 29.3 的 bin 目录下,首先在 cmd 中使用 emacs -Q 启动 Emacs:

然后在 Mingw64 SHELL 中通过 emacs -Q 启动 Emacs,package-initialzie 之后随便装个包:

再次在 cmd 中使用 emacs -Q 启动 Emacs,然后在 pacakge-initialize 后随便装个包:

可见在非 Mingw64 SHELL 环境中,仅添加 libgccjit DLL 及相关项到 Emacs 的 bin 目录中并不能让 native-comp 正常工作。

emacs 29.2

同样,首先添加 libgccjit 相关库到 Emacs bin 目录下,此时若直接运行 emacs -Q 得到的结果应该和前面一致,就不展示了。

接下来添加 as.exeld.exe 以及一些静态库文件到 Emacs 的 /lib/gcc 目录下,然后在 emacs -Q 启动后执行以下代码:

(setopt native-comp-driver-options (list "-B" (expand-file-name (file-name-concat invocation-directory "../lib/gcc"))))

接下来, package-initialize 后随便安装一个包,得到以下输出:

关闭 Emacs,随后通过 emacs -Q 启动 Emacs,但不执行以上代码,再通过 package-install 随便安装一个包:

这是让我感觉最奇怪的地方,似乎 native-comp-driver-options 只需一次设定,之后 native-comp 就“记住”它了…

草,这就说明只需要添加 native-comp 依赖项到 /lib/gcc 目录下,native-comp 就能找到这些东西,也就不需要设定 native-comp-driver-options 了。

补充

native-comp-driver-options 的用处可以参考 libgccjit 的文档:Compilation contexts — libgccjit 15.0.0 (experimental ) documentation 的末尾,上面设置 -B 参数的作用是添加搜索路径。

看来只需要把 libgccjit 的依赖项放到 Emacs 的 /lib/gcc 目录下,它就会自动查找了,不需要设定什么 native-comp-dirver-options

那么,最后总结一下,要让从官网下载的带有 native-comp 支持的 Windows 版 Emacs 能真正用上 native-comp ,我们只需要:

  1. 将 mingw64/bin 下的 libgccjit-0.dll, libisl-23.dll, libmpc-3.dll 和 libmpfr-6.dll 放到 Emacs 的 bin 目录

  2. 在 Emacs 的 lib 目录下创建 gcc 目录,并

    1. 添加来自 mingw64/bin 的 as.exe 和 ld.exe,以及它们的依赖项 libzstd.dll 和 zlib1.dll
    2. 添加来自 mingw64/lib 下的 crtbegin.o, crtend.o, dllcrt2.o 和 libadvapi32.a, libgcc_s.a, libkernel32.a, libmingw32.a, libmingwex.a, libmoldname.a, libmsvcrt.a, libpthread.a, libshell32.a, libuser32.a
    3. 添加来自 mingw64/lib/gcc/x86_64-w64-mingw32/13.1.0 的 libgcc.a

添加以上文件后,重启 Emacs 即可。

另,libgccjit 可通过以下命令安装:

pacman -S mingw-w64-x86_64-libgccjit
2 个赞

这么多lib,scoop能安装不?

没用过 scoop,应该不行…

scoop 可以直接安装编译好的

scoop bucket add kiennq https://github.com/kiennq/scoop-misc
scoop install emacs-k
3 个赞

看了一下编译脚本,考虑了native-comp,那确实应该可用