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:

  1. Loads comp-native.el to provide the synchronous compilation wrapper
  2. Configures native-comp-eln-load-path to point to the output directory
  3. Invokes run-native-compile-sync to compile every .el file to .eln format
  4. 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 runs run-native-compile-sync on all .el files. 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.withNativeCompilation or emacs.nativeComp in pkgs/emacs/wrapper.nix to determine if native compilation is available.
  • Build process: When enabled, pkgs/emacs/build/default.nix runs run-native-compile-sync from comp-native.el to compile .el files to .eln format during the build phase.
  • Runtime support: The wrapper in pkgs/emacs/wrapper.nix sets EMACSNATIVELOADPATH and native-comp-eln-load-path so Emacs 29+ can locate pre-compiled libraries.
  • Configuration: Users control the feature via nativeComp (capability detection) and nativeCompileAhead (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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →