My Experience with GCCEmacs on macOS

Gcc Emacs is the latest attempt to bring native code compilation to Emacs’s elisp. It uses gcc’s libgccjit to produce native code binaries for the current system from elisp.

Installation

Installation on my arch linux system was quite simple, but things turned out to be a bit less simple on macOS.

I initially tried to use this guide, but needed to perform several tweaks to get things working.

The gist suggests installing gcc from homebrew, with the following modification to /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/gcc.rb:

diff --git a/Formula/gcc.rb b/Formula/gcc.rb
index 1bd636d496..03ad124218 100644
--- a/Formula/gcc.rb
+++ b/Formula/gcc.rb
@@ -53,7 +53,7 @@ class Gcc < Formula
     #  - Ada, which requires a pre-existing GCC Ada compiler to bootstrap
     #  - Go, currently not supported on macOS
     #  - BRIG
-    languages = %w[c c++ objc obj-c++ fortran]
+    languages = %w[c c++ objc obj-c++ fortran jit]

     osmajor = `uname -r`.split(".").first
     pkgversion = "Homebrew GCC #{pkg_version} #{build.used_options*" "}".strip
@@ -73,6 +73,7 @@ class Gcc < Formula
       --with-system-zlib
       --with-pkgversion=#{pkgversion}
       --with-bugurl=https://github.com/Homebrew/homebrew-core/issues
+      --enable-host-shared
     ]

     # Xcode 10 dropped 32-bit support

and then simply running brew install gcc --build-from-source --force. This didn’t quite work to start with, homebrew, by default, will want to “update” the gcc formula back to the original, however, this can be fixed by simply running env HOMEBREW_NO_AUTO_UPDATE=1 brew install gcc --build-from-source --force instead.

The guide also neglected to mention that you need jansson installed for the --with-json configure option to work. The version of jansson provided by brew install jansson

The gist is, additionally, out of date, so I had to edit its references to gcc 10.1.0 to 10.2.0. It also additionally assumes that CC is set to the homebrew-installed gcc. I personally rectified that by manually pointing my gcc symlink to the homebrew provided gcc.

Attempting to compile with the provided build.sh also additionally caused the configure script to scream about AppKit.h being available, but not usable. This was rectified by running temporarily setting CC to /usr/bin/clang while running the configure script.

After the build and install completed, emacs was complaining about not being able to find eln files, which I was able to fix, somewhat jankily, by copying the native-lisp directory in the build folder to /usr/local.

All-in-all, this is the final version of the modified build.sh that I wound up using:

#!/bin/sh
# native-comp optimization
export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:${PATH}"
export CFLAGS="-I/usr/local/Cellar/gcc/10.2.0/include -O2 -march=native"
export LDFLAGS="-L/usr/local/Cellar/gcc/10.2.0/lib/gcc/10 -I/usr/local/Cellar/gcc/10.2.0/include"
export LIBRARY_PATH="/usr/local/Cellar/gcc/10.2.0/lib/gcc/10:${LIBRARY_PATH:-}"
cd emacs  || exit

git clean -xfd

echo ""
echo "autogen"
echo ""
./autogen.sh

echo ""
echo "configure"
echo ""
export CC=/usr/bin/clang
./configure \
     --disable-silent-rules \
     --enable-locallisppath=/usr/local/share/emacs/28.0.50/site-lisp \
     --prefix=/usr/local/opt/gccemacs \
     --without-dbus \
     --without-imagemagick \
     --with-mailutils \
     --with-ns \
     --with-json \
     --disable-ns-self-contained \
     --with-cairo \
     --with-modules \
     --with-xml2 \
     --with-gnutls \
     --with-rsvg \
     --with-nativecomp

read -p "Press any key to resume ..."

# Ensure /usr/local/opt/gccemacs exists
rm -rf /usr/local/opt/gccemacs
mkdir /usr/local/opt/gccemacs

# Ensure the directory to which we will dump Emacs exists and has the correct
# permissions set.
libexec=/usr/local/libexec/emacs/28.0.50
if [ ! -d $libexec ]; then
  sudo mkdir -p $libexec
  sudo chown $USER $libexec
fi

echo ""
echo "make"
echo ""
export CC=gcc
make -j6
make install

rm -rf "/Applications/Gccemacs.app"
mv nextstep/Emacs.app "/Applications/Gccemacs.app"

rm -rf /usr/local/native-lisp/
cp -R native-lisp /usr/local

cd /usr/local/bin || exit
rm gccemacs
rm gccemacsclient
ln -s /usr/local/opt/gccemacs/bin/emacs ./gccemacs
ln -s /usr/local/opt/gccemacs/bin/emacsclient ./gccemacsclient


cd /Applications/Gccemacs.app/Contents || exit
ln -s /usr/local/opt/gccemacs/share/emacs/28.0.50/lisp .

I, personally, decided to name my binaries as gccemacs, to allow coexistence with regular emacs.

Impressions

The first start on Gcc Emacs is, well, brutal.

I don’t know exactly how long it took to compile all my packages, as I wound up leaving my desk to do some things around the house, and came back an hour later to find it complete.

After the initial cost of compiling everything is paid, emacs is notably snappier. My init time has dropped from 5.2 seconds to 4.1 seconds, and there is no longer a noticeable hangup when a lazy loaded package kicks in, and commands that were once painful to run, such as helm-org-rifle, are now much smoother and I no longer experience a noticeable hang upon running them. I haven’t actually experienced a momentary hang since switching over.

I have not noticed any behavioral difference (besides speed) over running regular emacs. straight.el’s native-comp support just worked.

Gcc Emacs is going excellent so far, and I hope its path to master is short and pleasant.