2025-02-26
       Tags: emacs
       
       Disaster [1] is an Emacs package authored by the venerable  Justine
       Tunney that  allows disassembling C/C++ and  Fortran code  from the
       comfort of your source editing buffer.
       I really like  this idea, but  most of the time  I'm  interested in
       `x86_64` assembly instead of my  `aarch64` host
       platform,  so I decided to hack on cross compilation support  using
       Zig as a C/C++ cross compiler [2]!
       
       I'm using disaster because its simplicity fits my use case, but you
       should be able to use the same idea with other packages like
       
 (HTM) RMSbolt
       Has  support  for  tons  of  languages  (and   bytecode,  not  just
       assembly), and you can  track  point across source and  compilation
       buffers
       
 (HTM) Beardbolt
       Rewrite of RMSbolt that  is faster/simpler  than  RMSbolt, but only
       supports C/C++/Rust
       
       == PoC Or GTFO
       
       (use-package disaster
           :config
         (setq disaster-cflags (setq disaster-cxxflags "-Wno-everything -g"))
         (setq disaster-assembly-mode 'nasm-mode)
         :init
         (define-advice disaster (:around (fn &rest args) cross-compile)
           (interactive)
           ;; OPTIONAL: support for non-file buffers
           (setq arg (or args
                           (list (if-let* ((buf (current-buffer))
                                           (buf-name (buffer-name buf))
                                           (file (buffer-file-name (current-buffer))))
                                     file
                                   (make-temp-file
                                    (file-name-base buf-name) nil
                                    (cond ((eq major-mode 'fundamental-mode) (c-or-c++-ts-mode))
                                          ((member major-mode '(c-mode c-ts-mode)) ".c")
                                          ((member major-mode '(c++-mode c++-ts-mode)) ".cpp")
                                          ((eq major-mode 'fortran-mode) ".f")
                                          (t (file-name-extension buf-name)))
                                    (buffer-string)))))
                 ;; replace `doit` with `apply fn args` if you get rid of this
                 doit (lambda () (if args (apply fn arg)
                                   (with-temp-buffer (apply fn arg)))))
           ;; END-OPTIONAL
           (if (and current-prefix-arg (mapc (lambda (exe)
                                               (or (executable-find exe) (user-error "disaster: %s not found" exe)))
                                             '("zig" "jq")))
               (let* ((monch (lambda (prompt collection default)
                               (completing-read prompt (split-string collection " ") nil nil nil nil default)))
                      (file-exists-wrapped (symbol-function #'file-exists-p))
                      (targets (split-string (shell-command-to-string "zig targets | jq -r '.arch,.os,.abi | join(\" \")'") "\n" t))
                      (host-target (mapcar (lambda (s) (car (split-string s "[ \.\t\n\r]+"))) (split-string (shell-command-to-string "zig env | jq -r '.target'") "-")))
                      (target-arg (apply #'format " -target %s-%s-%s" (cl-mapcar monch '("Arch: " "OS: " "ABI: ") targets host-target)))
                      (disaster-cc "zig cc")
                      (disaster-cxx "zig c++")
                      (disaster-cflags (concat disaster-cflags target-arg))
                      (disaster-cxxflags (concat disaster-cxxflags target-arg)))
                 (with-environment-variables (("CC" disaster-cc)
                                              ("CXX" disaster-cxx)
                                              ("CFLAGS" disaster-cflags)
                                              ("CXXFLAGS" disaster-cxxflags))
                   (cl-letf (((symbol-function #'file-exists-p)
                              (lambda (file)
                                (unless (string= "compile_commands.json"
                                                 (file-name-nondirectory file))
                                  (funcall file-exists-wrapped file)))))
                     (funcall doit))))
             (funcall doit))
           ;; OPTIONAL: Put point in assembly buffer
           (switch-to-buffer-other-window disaster-buffer-assembly)))
       
       This         requires         `zig`         and
       `jq`  to be  in  Emac's  `exec-path`,
       although  I'm sure you  could  use Elisp  to do  the  JSON  parsing
       instead, especially  now that Emacs 30.1  ships with  its  own JSON
       implementation.
       Most sane people would object  to my usage  of  `cl-letf`
       and advice  instead  of  a separate  wrapper function;  this  would
       probably be  more readable as a patch instead, but I  like having a
       snippet  that  you   can  quickly  try  out  with  `eval-last-
       sexp`.
       
       Caveat emptor: this falls  apart for large projects, poorly behaved
       Makefiles,  and  probably CMake (I  tried ameliorating  the  latter
       issue upstream, but I don't use CMake that often so YMMV).
       Also, this definitely doesn't count as a "Compiler Explorer" in the
       strict  sense  because  you're  using  the  same  version  of  LLVM
       regardless  of  target;  you  might be able  to  do  something like
       leverage nixpkgs' cross  compilation support to build  older cross-
       compilers,  but  you're probably better off using Docker or Godbolt
       [3] at that point.
       
       References:
 (HTM)   [1] Disaster
 (HTM)   [2] Zig as a C/C++ cross compiler
 (HTM)   [3] Godbolt
       >=================================================================<
       
 (DIR) Blog
 (DIR) Writeups
 (DIR) jp
       
       copyright 2026 George Huebner
 (HTM) email