How twist.nix Handles Native Compilation for Emacs 29+: A Complete Guide
twist.nix enables native compilation for Emacs 29+ by detecting the underlying Emacs binary's capabilities, conditionally running batch native compilation during the build phase, and configuring runtime environment variables to load pre-compiled .eln files.
twist.nix is a Nix-based package manager for Emacs that automates the entire build pipeline. For Emacs 29 and later, it supports the native compilation (native-comp) feature, which translates Emacs Lisp into native machine code for significant performance improvements. The implementation spans feature detection, build-time compilation, and runtime configuration.
How twist.nix Detects Native Compilation Support
Before running any compilation steps, twist.nix determines whether the target Emacs binary actually supports native compilation.
The nativeComp Flag in wrapper.nix
In pkgs/emacs/wrapper.nix (line 22), twist.nix computes a Boolean flag nativeComp by checking the Emacs package attributes:
nativeComp = emacs.withNativeCompilation or emacs.nativeComp or false;
This flag propagates through the entire build pipeline. If nativeComp evaluates to false, twist.nix skips all native compilation steps, allowing the same configuration to work with older Emacs versions or custom builds without native-comp support.
Build-Time Native Compilation in twist.nix
When nativeComp is true and the user opts into ahead-of-time compilation, twist.nix runs native compilation as a build phase after standard byte-compilation.
The buildAndInstallNativeLisp Phase
In pkgs/emacs/build/default.nix (lines 71-79), the derivation includes a buildAndInstallNativeLisp script that executes after byte-compilation:
emacs --batch -L $lispDir -l ./comp-native.el \
--eval "(push \"$nativeLispDir/\" native-comp-eln-load-path)" \
--eval "(setq native-compile-target-directory \"$nativeLispDir/\")" \
-f run-native-compile-sync $lispDir
This script:
- Loads
comp-native.elto provide the synchronous compilation wrapper - Configures
native-comp-eln-load-pathto point to the output directory - Invokes
run-native-compile-syncto compile every.elfile to.elnformat - Places results in
$out/share/emacs/native-lisp
The comp-native.el Helper
The file pkgs/emacs/build/comp-native.el defines run-native-compile-sync, a thin wrapper around Emacs's built-in native-compile-async. It blocks until the async job queue empties, ensuring Nix's build sandbox waits for compilation completion before proceeding to the install phase.
Runtime Configuration for Native Compilation
After building the packages, twist.nix configures the runtime environment so Emacs can locate and load the native-compiled libraries.
Environment Variables and Load Paths
In pkgs/emacs/wrapper.nix (lines 162-171), the wrapper script sets critical environment variables:
--prefix EMACSNATIVELOADPATH : $nativeLisp:$nativeLoadPath
This prepends the native-lisp directory to EMACSNATIVELOADPATH, which Emacs 29+ checks when resolving native-compiled units. The wrapper also ensures native-comp-eln-load-path includes these directories during early initialization.
Additionally, the wrapper runs batch-native-compile on the generated site-start.el file. This guarantees that any Emacs Lisp generated during the Nix build process itself gets native-compiled before the user starts Emacs.
Ahead-of-Time vs Lazy Compilation
twist.nix supports two modes controlled by the nativeCompileAhead flag (defined in pkgs/emacs/default.nix):
- Ahead-of-time (AOT): When
nativeCompileAhead = true, the build phase runsrun-native-compile-syncon all.elfiles. This increases build time but eliminates compilation pauses when the user loads packages. - Lazy (JIT): When
false, twist.nix skips the build-time native compilation. Emacs compiles packages on-demand during the first load, trading faster builds for potential initial lag.
The derivation checks both flags before enabling native compilation:
doNativeComp = nativeComp && nativeCompileAhead;
Practical Configuration Examples
Enabling Native Compilation in a Flake
To enable native compilation for your Emacs 29+ installation, set the nativeComp and nativeCompileAhead attributes in your flake:
{
inputs.twist-nix.url = "github:emacs-twist/twist.nix";
outputs = { self, nixpkgs, twist-nix }: {
packages.x86_64-linux.emacsWithNative = twist-nix.packages.x86_64-linux.emacs // {
nativeComp = true; # Enable if Emacs binary supports native-comp
nativeCompileAhead = true; # Compile everything at build time
};
};
}
Building Custom Packages with Native Comp
When defining custom packages that should benefit from native compilation, inherit the nativeComp flag from the surrounding Emacs package set:
{ pkgs, twist-nix }:
twist-nix.buildEmacsPackage {
pname = "my-awesome-mode";
version = "1.0";
src = pkgs.fetchFromGitHub {
owner = "myself";
repo = "my-awesome-mode";
rev = "v1.0";
sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
# Inherit nativeComp flag from the surrounding emacs package
inherit (twist-nix.emacs) nativeComp;
}
When twist-nix.emacs has nativeComp = true, this package automatically undergoes native compilation during its build.
Summary
- Feature detection: twist.nix checks
emacs.withNativeCompilationoremacs.nativeCompinpkgs/emacs/wrapper.nixto determine if native compilation is available. - Build process: When enabled,
pkgs/emacs/build/default.nixrunsrun-native-compile-syncfromcomp-native.elto compile.elfiles to.elnformat during the build phase. - Runtime support: The wrapper in
pkgs/emacs/wrapper.nixsetsEMACSNATIVELOADPATHandnative-comp-eln-load-pathso Emacs 29+ can locate pre-compiled libraries. - Configuration: Users control the feature via
nativeComp(capability detection) andnativeCompileAhead(build-time vs. lazy compilation) flags.
Frequently Asked Questions
What Emacs versions support native compilation in twist.nix?
Native compilation requires Emacs 29 or later, which includes the native-compile feature. twist.nix automatically detects support by checking the withNativeCompilation or nativeComp attributes of the Emacs package. If you use Emacs 28 or earlier, twist.nix skips all native compilation steps and falls back to traditional byte-compilation only.
How do I check if a package was native-compiled?
After building your Emacs configuration, inspect the native-lisp directory in the Nix store path. Native-compiled files have the .eln extension. For example, run tree result/share/emacs/native-lisp to see the directory structure. If the directory contains .eln files corresponding to your packages, native compilation succeeded. If the directory is empty or missing, check that nativeComp and nativeCompileAhead are set to true in your configuration.
Can I disable native compilation for specific packages?
Yes. While twist.nix applies the nativeComp flag globally to the Emacs distribution, you can override it per-package when calling buildEmacsPackage. Set nativeComp = false; in the package derivation to skip native compilation for that specific package while keeping it enabled for others. This is useful for packages with known compatibility issues with the native compiler or when debugging compilation errors.
What is the difference between nativeComp and nativeCompileAhead?
nativeComp is a Boolean flag that indicates whether the underlying Emacs binary supports native compilation and whether the user wants to enable the feature at all. When false, twist.nix performs no native compilation steps. nativeCompileAhead controls when compilation happens: when true, twist.nix runs run-native-compile-sync during the Nix build to compile all .el files to .eln format ahead of time; when false, Emacs compiles packages lazily at runtime on first load. You need nativeComp = true for nativeCompileAhead to have any effect.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →