diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,24 @@
 project adheres to the [Haskell Package Versioning
 Policy (PVP)](https://pvp.haskell.org)
 
+## [1.4.0.0] - ?
+### Changed
+  * Support for LLVM-15 to 22 (15 only on native backend, 16 and newer are supported on native and PTX).
+  * Simplified installation by using clang via the command line instead of linking with LLVM. Clang should be on the PATH or (on Windows) installed in a standard location.
+  * Updates for various new Accelerate features, including debugging functionality similar to Debug.Trace
+  * Allow empty inputs in scanl1 and scanr1
+
+### Contributors
+
+Special thanks to those who contributed patches as part of this release:
+
+  * Trevor L. McDonell (@tmcdonell)
+  * Tom Smeding (@tomsmeding)
+  * David van Balen (@dpvanbalen)
+  * Ivo Gabe de Wolff (@ivogabe)
+  * Robbert van der Helm (@robbert-vdh)
+  * Noah Williams (@noahmartinwilliams)
+
 ## [1.3.0.0] - 2018-08-27
 ### Added
   * Support for LLVM-9
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -3,21 +3,16 @@
 
 # LLVM backends for the Accelerate array language
 
-[![GitHub CI](https://github.com/tmcdonell/accelerate-llvm/workflows/CI/badge.svg)](https://github.com/tmcdonell/accelerate-llvm/actions)
+[![CI](https://github.com/AccelerateHS/accelerate-llvm/actions/workflows/ci.yml/badge.svg)](https://github.com/AccelerateHS/accelerate-llvm/actions/workflows/ci.yml)
 [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/AccelerateHS/Lobby)
-<br>
-[![Stackage LTS](https://stackage.org/package/accelerate-llvm/badge/lts)](https://stackage.org/lts/package/accelerate-llvm)
-[![Stackage Nightly](https://stackage.org/package/accelerate-llvm/badge/nightly)](https://stackage.org/nightly/package/accelerate-llvm)
 [![Hackage](https://img.shields.io/hackage/v/accelerate-llvm.svg)](https://hackage.haskell.org/package/accelerate-llvm)
-<br>
-[![Docker Automated build](https://img.shields.io/docker/automated/tmcdonell/accelerate-llvm.svg)](https://hub.docker.com/r/tmcdonell/accelerate-llvm/)
-[![Docker status](https://images.microbadger.com/badges/image/tmcdonell/accelerate-llvm.svg)](https://microbadger.com/images/tmcdonell/accelerate-llvm)
 
 </div>
 
 This package compiles Accelerate code to LLVM IR, and executes that code on
-multicore CPUs as well as NVIDIA GPUs. This avoids the need to go through `nvcc`
-or `clang`. For details on Accelerate, refer to the [main repository][GitHub].
+multicore CPUs as well as NVIDIA GPUs. This avoids the need to go through
+`nvcc` or write C++ code. For details on Accelerate, refer to the [main
+repository][GitHub].
 
 We love all kinds of contributions, so feel free to open issues for missing
 features as well as report (or fix!) bugs on the [issue tracker][Issues].
@@ -27,179 +22,143 @@
 
 
  * [Dependencies](#dependencies)
- * [Docker](#docker)
- * [Installing LLVM](#installing-llvm)
-   * [Homebrew](#homebrew)
+   * [macOS](#macos)
    * [Debian/Ubuntu](#debianubuntu)
-   * [Building from source](#building-from-source)
- * [Installing Accelerate-LLVM](#installing-accelerate-llvm)
-   * [libNVVM](#libNVVM)
+   * [Arch Linux](#archlinux)
+   * [Windows](#windows)
 
 
 Dependencies
 ------------
 
-Haskell dependencies are available from Hackage, but there are several external
+Haskell dependencies are available from Hackage, but there are some external
 library dependencies that you will need to install as well:
 
- * [`LLVM`](http://llvm.org)
- * [`libFFI`](http://sourceware.org/libffi/) (if using the `accelerate-llvm-native` backend for multicore CPUs)
- * [`CUDA`](https://developer.nvidia.com/cuda-downloads) (if using the `accelerate-llvm-ptx` backend for NVIDIA GPUs)
-
-
-Docker
-------
-
-A [docker](https://www.docker.com) container is provided with this package
-preinstalled (via stack) at `/opt/accelerate-llvm`. Note that if you wish to use
-the `accelerate-llvm-ptx` GPU backend, you will need to install the [NVIDIA
-docker](https://github.com/NVIDIA/nvidia-docker) plugin; see that page for more
-information.
-
-```sh
-$ docker run -it tmcdonell/accelerate-llvm
-```
-
-
-Installing LLVM
----------------
-
-When installing LLVM, make sure that it includes the `libLLVM` shared library.
-If you want to use the GPU targeting `accelerate-llvm-ptx` backend, make sure
-you install (or build) LLVM with the 'nvptx' target.
+- if using `accelerate-llvm-native` for multicore CPU:
+  [`libFFI`](http://sourceware.org/libffi/)
+- if using `accelerate-llvm-ptx` for GPU:
+  [`CUDA`](https://developer.nvidia.com/cuda-downloads);
+  [Note that not all versions of CUDA support all NVIDIA GPUs](https://en.wikipedia.org/wiki/CUDA#GPUs_supported)
+- [`clang`](https://clang.llvm.org/) (if using `accelerate-llvm-ptx`: version
+  16 or higher, built with support for the `nvptx` backend). `accelerate-llvm`
+  uses the command-line tool as a way to be compatible with many different LLVM
+  versions, not to compile C code. (Accelerate passes LLVM IR to `clang`.)
 
-## Homebrew
+Below are some OS-specific instructions. If anything here is wrong or out of
+date, please file an issue.
 
-Example using [Homebrew](http://brew.sh) on macOS:
+## macOS
 
-```sh
-$ brew install llvm-hs/llvm/llvm-9
-```
+To get `libFFI`, run `brew install libffi`. `clang` is already provided with
+macOS (you may need to `xcode-select --install`), and CUDA is not supported on
+macOS.
 
 ## Debian/Ubuntu
 
-For Debian/Ubuntu based Linux distributions, the LLVM.org website provides
-binary distribution packages. Check [apt.llvm.org](http://apt.llvm.org) for
-instructions for adding the correct package database for your OS version, and
-then:
-
-```sh
-$ apt-get install llvm-9-dev
-```
-
-## Building from source
-
-If your OS does not have an appropriate LLVM distribution available, you can also build from source. Detailed build instructions are available on the [LLVM.org website](http://releases.llvm.org/6.0.0/docs/CMake.html). Note that you will require at least [CMake 3.4.3](http://www.cmake.org/cmake/resources/software.html) and a recent C++ compiler; at least Clang 3.1, GCC 4.8, or Visual Studio 2015 (update 3).
-
-  1. Download and unpack the [LLVM-9 source code](https://github.com/llvm/llvm-project/releases/download/llvmorg-9.0.1/llvm-9.0.1.src.tar.xz). We'll refer to
-     the path that the source tree was unpacked to as `LLVM_SRC`. Only the main
-     LLVM source tree is required, but you can optionally add other components
-     such as the Clang compiler or Polly loop optimiser. See the [LLVM releases](http://releases.llvm.org/download.html#9.0.1)
-     page for the complete list.
-
-  2. Create a temporary build directory and `cd` into it, for example:
-     ```sh
-     $ mkdir /tmp/build
-     $ cd /tmp/build
-     ```
-
-  3. Execute the following to configure the build. Here `INSTALL_PREFIX` is
-     where LLVM is to be installed, for example `/usr/local` or
-     `$HOME/opt/llvm`:
-     ```sh
-     $ cmake $LLVM_SRC -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON -DLLVM_BUILD_LLVM_DYLIB=ON -DLLVM_LINK_LLVM_DYLIB=ON
-     ```
-     See [options and variables](http://llvm.org/docs/CMake.html#options-and-variables)
-     for a list of additional build parameters you can specify.
-
-  4. Build and install:
-     ```sh
-     $ cmake --build .
-     $ cmake --build . --target install
-     ```
-
-  5. For macOS only, some additional steps are useful to work around issues related
-     to [System Integrity Protection](https://en.wikipedia.org/wiki/System_Integrity_Protection):
-     ```sh
-     cd $INSTALL_PREFIX/lib
-     ln -s libLLVM.dylib libLLVM-9.dylib
-     install_name_tool -id $PWD/libLTO.dylib libLTO.dylib
-     install_name_tool -id $PWD/libLLVM.dylib libLLVM.dylib
-     install_name_tool -change '@rpath/libLLVM.dylib' $PWD/libLLVM.dylib libLTO.dylib
-     ```
-
+For `clang`:
+- On Ubuntu 24.04 (noble) / Debian trixie or higher: `sudo apt install clang`.
+- Otherwise, if you need only the CPU backend (`accelerate-llvm-native`):
+  `sudo apt install clang` will give you an old version of `clang`, but the CPU
+   backend is likely to work fine.
+- If you are on an older distro and need the GPU backend
+  (`accelerate-llvm-ptx`): `clang` version 16 or higher is required.
+  Add the apt source from [apt.llvm.org](https://apt.llvm.org/). The neatest
+  way to do this is to create a file `/etc/apt/sources.list.d/llvm.list` (the
+  precise file name does not matter) and put in it, for Ubuntu (change "jammy"
+  as appropriate):
 
-Installing Accelerate-LLVM
---------------------------
+      deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy main
+      deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy main
 
-Once the dependencies are installed, we are ready to install `accelerate-llvm`.
+  or for Debian (change "bookworm" as appropriate):
 
-For example, installation using [`stack`](http://docs.haskellstack.org/en/stable/README.html)
-just requires you to point it to the appropriate configuration file:
-```sh
-$ ln -s stack-8.8.yaml stack.yaml
-$ stack setup
-$ stack install
-```
+      deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm main
+      deb-src http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm main
 
-Note that the version of [`llvm-hs`](https://hackage.haskell.org/package/llvm-hs)
-used must match the installed version of LLVM, which is currently 9.0.
+  and `sudo apt update; sudo apt install clang`. This gets you the latest
+  version of `clang`; different sources are also available for specific
+  versions (see [apt.llvm.org](https://apt.llvm.org)).
 
+To use the CPU backend (`accelerate-llvm-native`), install `libFFI` using
+`sudo apt install libffi-dev`.
 
-## libNVVM
+To use the GPU backend (`accelerate-llvm-ptx`), install CUDA from
+[here](https://developer.nvidia.com/cuda-downloads?target_os=Linux)
+("deb (network)" is smoother than the "deb (local)" option).
 
-The `accelerate-llvm-ptx` backend can optionally be compiled to generate GPU
-code using the `libNVVM` library, rather than LLVM's inbuilt NVPTX code
-generator. `libNVVM` is a closed-source library distributed as part of the
-NVIDIA CUDA toolkit, and is what the `nvcc` compiler itself uses internally when
-compiling CUDA C code.
+## Arch Linux
 
-Using `libNVVM` _may_ improve GPU performance compared to the code generator
-built in to LLVM. One difficulty with using it however is that since `libNVVM`
-is also based on LLVM, and typically lags LLVM by several releases, you must
-install `accelerate-llvm` with a "compatible" version of LLVM, which will depend
-on the version of the CUDA toolkit you have installed. The following table shows
-combinations which have been tested:
+Run `sudo pacman -S clang`. To use the CPU backend (`accelerate-llvm-native`),
+additionally run `sudo pacman -S libffi`. To use the GPU backend
+(`accelerate-llvm-ptx`), additionally run `sudo pacman -S cuda`.
 
-|               | LLVM-3.3 | LLVM-3.4 | LLVM-3.5 | LLVM-3.8 | LLVM-3.9 | LLVM-4.0 | LLVM-5.0 | LLVM-6.0 | LLVM-7 | LLVM-8 | LLVM-9 |
-| ------------- | :------: | :------: | :------: | :------: | :------: | :------: | :------: | :------: | :----: | :----: | :----: |
-| **CUDA-7.0**  | ⭕       | ❌       |          |          |          |          |          |          |        |        |        |
-| **CUDA-7.5**  |          | ⭕       | ⭕       | ❌       |          |          |          |          |        |        |        |
-| **CUDA-8.0**  |          |          | ⭕       | ⭕       | ❌       | ❌       |          |          |        |        |        |
-| **CUDA-9.0**  |          |          |          |          |          | ❌       | ❌       |          |        |        |        |
-| **CUDA-9.1**  |          |          |          |          |          |          |          |          |        |        |        |
-| **CUDA-9.2**  |          |          |          |          |          |          |          |          |        |        |        |
-| **CUDA-10.0** |          |          |          |          |          |          |          |          |        |        |        |
-| **CUDA-10.1** |          |          |          |          |          |          |          |          |        |        |        |
+## Windows
 
-Where ⭕ = Works, and ❌ = Does not work.
+We recommend WSL2 (not WSL1, WSL2!) and following the Ubuntu instructions
+above. The remainder of this text attemps to give you a working system on
+Windows native.
 
-The above table is incomplete! If you try a particular combination and find that
-it does or does not work, please let us know!
+Install `clang`; you have two options:
+1. Using
+   [WinGet](https://learn.microsoft.com/en-us/windows/package-manager/winget/):
+   `winget install LLVM.LLVM`
+2. By downloading the installer directly (WinGet just runs the same installer)
+   from [here](https://github.com/llvm/llvm-project/releases) (choose
+   "LLVM-&lt;version>-win64.exe" from the latest release; you may need to click
+   "Show all 57 assets").
+This will also give you `libFFI`.
 
-Note that the above restrictions on CUDA and LLVM version exist _only_ if you
-want to use the NVVM component. Otherwise, you should be free to use any
-combination of CUDA and LLVM.
+<details><summary>Optionally, add <code>clang</code> (and more) to your system path. Click to see how.</summary>
 
-Also note that `accelerate-llvm-ptx` itself currently requires at least LLVM-4.0.
+Accelerate should be able to find `clang` automatically even if you do not do
+this. However, for easy access to `clang` and all other LLVM executables, add
+`C:\Program Files\LLVM\bin` to the system path as follows:
+1. Search for "environment variables" in the start menu
+2. Click "Edit the system environment variables"
+3. Click on "Environment Variables..."
+4. Double-click on the user variable called "Path"
+5. And add a new entry containing `C:\Program Files\LLVM\bin`.
 
-Using `stack`, either edit the `stack.yaml` and add the following section:
+Note that if you add an entry here manually, it is a good idea to clean it up
+again if you uninstall LLVM/clang. (Leaving it there is not very harmful,
+however.)
 
-```yaml
-flags:
-  accelerate-llvm-ptx:
-    nvvm: true
-```
+You may find that the LLVM/clang installer has already added the Path entry
+automatically (it did not for us); if so, no need to add a second entry.
 
-Or install using the following option on the command line:
+&mdash;&mdash;
+</details>
 
-```sh
-$ stack install accelerate-llvm-ptx --flag accelerate-llvm-ptx:nvvm
-```
+You may additionally need the VS Build Tools, if you have not yet installed and
+set up Visual Studio otherwise. You need this if `clang` complains that it is
+`unable to find a Visual Studio installation; try running Clang from a developer command prompt`.
 
-If installing via `cabal`:
+1. If you already have the Visual Studio Installer on your system, open it and
+   check if you already have Visual Studio (Community) installed. Note that
+   this is completely unrelated to VS _Code_.
+   - If you already have VS (Community): inside the Visual Studio Installer,
+     click on "Modify" in the VS (Community) box. This should get you a screen
+     with "workloads" you can select.
+   - If you do not yet have VS (Community), install the VS Build Tools: go to
+     https://visualstudio.microsoft.com/downloads, scroll down to "All
+     Downloads", open "Tools for Visual Studio", and select "Build Tools for
+     Visual Studio". If you run the installer, you should get a screen with
+     "workloads" you can select.
+2. Under the Workloads tab, choose the "Desktop development with C++" workload.
+   If you want to save a bit of disk space (not much), keep only the following
+   two options selected:
+   - "MSVC v143 - VE 2022 C++ x64/x86 build tools (Latest)"
+   - "Windows 11 SDK (…)" (choose the latest option). The attentive reader may
+     note that the wizard also offers Clang; we recommend a separate Clang
+     install for Accelerate because the one from VS somehow doesn’t seem to
+     work properly with Accelerate. If you find out why, please let us know.
+3. Install that. This takes a while.
 
-```sh
-$ cabal install accelerate-llvm-ptx -fnvvm
-```
+It turns out that having both Visual Studio and the Build Tools installed
+results in Clang getting confused between the two (it appears that Visual
+Studio is 64-bit (x64) and the Build Tools are 32-bit (x86)). If Clang
+complains about the bit-ness of your system libraries, double-check that you
+haven’t installed both simultaneously.
 
+The GPU backend (`accelerate-llvm-ptx`) probably doesn't work on Windows; in
+any case, it is untested.
diff --git a/accelerate-llvm.cabal b/accelerate-llvm.cabal
--- a/accelerate-llvm.cabal
+++ b/accelerate-llvm.cabal
@@ -1,65 +1,44 @@
+cabal-version:          2.2
+
 name:                   accelerate-llvm
-version:                1.3.0.0
-cabal-version:          >= 1.10
-tested-with:            GHC >= 8.6
+version:                1.4.0.0
+tested-with:            GHC >= 9.4
 build-type:             Simple
 
 synopsis:               Accelerate backend component generating LLVM IR
 description:
     This library implements direct LLVM IR generation for the /Accelerate/
     language. For further information, refer to the main
-    <http://hackage.haskell.org/package/accelerate accelerate> package.
+    <https://hackage.haskell.org/package/accelerate accelerate> package. As a
+    user of Accelerate, you do not need to use this package; use
+    @accelerate-llvm-native@ or @accelerate-llvm-ptx@ instead.
     .
     [/Dependencies/]
     .
     Haskell dependencies are available from Hackage. The following external
-    libraries are alse required:
-    .
-      * <http://llvm.org LLVM>
-    .
-      * <http://sourceware.org/libffi/ libFFI> (if using <http://hackage.haskell.org/package/accelerate-llvm-native accelerate-llvm-native>)
-    .
-      * <https://developer.nvidia.com/cuda-downloads CUDA> (if using <http://hackage.haskell.org/package/accelerate-llvm-ptx accelerate-llvm-ptx>)
-    .
-    [/Installing LLVM/]
-    .
-    /Homebrew/
-    .
-    Example using Homebrew on macOS:
-    .
-    > brew install llvm-hs/llvm/llvm-9
-    .
-    /Debian & Ubuntu/
-    .
-    For Debian/Ubuntu based Linux distributions, the LLVM.org website provides
-    binary distribution packages. Check <http://apt.llvm.org apt.llvm.org> for
-    instructions for adding the correct package database for your OS version,
-    and then:
-    .
-    > apt-get install llvm-9-dev
-    .
-    /Building from source/
+    dependencies are also required:
     .
-    If your OS does not have an appropriate LLVM distribution available, you can
-    also build from source. Detailed build instructions are available on
-    <http://releases.llvm.org/8.0.0/docs/CMake.html LLVM.org>. Make sure to
-    include the cmake build options
-    @-DLLVM_BUILD_LLVM_DYLIB=ON -DLLVM_LINK_LLVM_DYLIB=ON@ so that the @libLLVM@
-    shared library will be built.
+    * <https://clang.llvm.org/ clang> (not used to compile C code, but to compile generated LLVM IR via a mostly LLVM-version-independent interface)
+    * <https://sourceware.org/libffi/ libFFI> (if using <https://hackage.haskell.org/package/accelerate-llvm-native accelerate-llvm-native>)
+    * <https://developer.nvidia.com/cuda-downloads CUDA> (if using <https://hackage.haskell.org/package/accelerate-llvm-ptx accelerate-llvm-ptx>)
     .
-    If using the @accelerate-llvm-ptx@ backend, also ensure that the
-    @LLVM_TARGETS_TO_BUILD@ option includes the @NVPTX@ target (if not
-    specified, all targets are built).
+    For installation instructions, see the <https://github.com/AccelerateHS/accelerate-llvm#readme README>.
     .
+    This package includes (and exposes) a forked copy of
+    <https://hackage.haskell.org/package/llvm-pretty llvm-pretty> due to some
+    breaking changes needed to make accelerate-llvm work. Upstreaming these
+    changes is planned. This code (under
+    @Data.Array.Accelerate.LLVM.Internal.LLVMPretty@) is __not__ public API and
+    may disappear or change at any time.
 
-license:                BSD3
+license:                BSD-3-Clause
 license-file:           LICENSE
 author:                 Trevor L. McDonell
 maintainer:             Trevor L. McDonell <trevor.mcdonell@gmail.com>
 bug-reports:            https://github.com/AccelerateHS/accelerate/issues
 category:               Accelerate, Compilers/Interpreters, Concurrency, Data, Parallelism
 
-extra-source-files:
+extra-doc-files:
     CHANGELOG.md
     README.md
 
@@ -85,6 +64,7 @@
     Data.Array.Accelerate.LLVM.CodeGen.Module
     Data.Array.Accelerate.LLVM.CodeGen.Monad
     Data.Array.Accelerate.LLVM.CodeGen.Permute
+    Data.Array.Accelerate.LLVM.CodeGen.Profile
     Data.Array.Accelerate.LLVM.CodeGen.Ptr
     Data.Array.Accelerate.LLVM.CodeGen.Skeleton
     Data.Array.Accelerate.LLVM.CodeGen.Stencil
@@ -103,13 +83,13 @@
     Data.Array.Accelerate.LLVM.Link.Cache
     Data.Array.Accelerate.LLVM.State
     Data.Array.Accelerate.LLVM.Target
+    Data.Array.Accelerate.LLVM.Target.ClangInfo
 
     -- LLVM code generation
-    LLVM.AST.Type.AddrSpace
     LLVM.AST.Type.Constant
     LLVM.AST.Type.Downcast
-    LLVM.AST.Type.Flags
     LLVM.AST.Type.Function
+    LLVM.AST.Type.GetElementPtr
     LLVM.AST.Type.Global
     LLVM.AST.Type.InlineAssembly
     LLVM.AST.Type.Instruction
@@ -124,32 +104,73 @@
     LLVM.AST.Type.Terminator
 
     -- Extras
+    Data.Array.Accelerate.TH.Compat
     Data.ByteString.Short.Char8
     Data.ByteString.Short.Extra
 
   other-modules:
     Paths_accelerate_llvm
 
+  autogen-modules:
+    Paths_accelerate_llvm
+
   build-depends:
           base                          >= 4.10 && < 5
-        , accelerate                    == 1.3.*
+        , accelerate                    == 1.4.*
         , bytestring                    >= 0.10.4
         , constraints                   >= 0.9
-        , containers                    >= 0.5
+        , containers                    >= 0.5 && < 0.9
         , data-default-class            >= 0.0.1
         , deepseq                       >= 1.3
         , directory                     >= 1.2.3
         , dlist                         >= 0.6
         , exceptions                    >= 0.6
         , filepath                      >= 1.0
-        , llvm-hs                       >= 4.1 && < 9.1
-        , llvm-hs-pure                  >= 4.1 && < 9.1
+        , formatting                    >= 7.0
+        , hashable                      >= 1.1
+        -- , llvm-hs                       >= 4.1 && < 16
+        -- , llvm-hs-pure                  >= 4.1 && < 16
+        -- , llvm-pretty                   >= 0.12
         , mtl                           >= 2.0
         , primitive                     >= 0.6.4
+        , process
         , template-haskell
+        , text                          >= 1.2
         , unordered-containers          >= 0.2
         , vector                        >= 0.10
 
+  -- Expose the modules from the (forked and edited) llvm-pretty here under a
+  -- namespace prefix. We need these modules in accelerate-llvm-native and
+  -- accelerate-llvm-ptx, so we must expose them publically, unfortunately.
+  build-depends: llvm-pretty-accfork
+  reexported-modules:
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.AST,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Labels,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Labels.TH,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Lens,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Parser,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.PP,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.DebugUtils,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Triple,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Triple.AST,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Triple.Parse,
+    Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Triple.Print
+  mixins:
+    llvm-pretty-accfork
+      (Text.LLVM              as Data.Array.Accelerate.LLVM.Internal.LLVMPretty,
+       Text.LLVM.AST          as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.AST,
+       Text.LLVM.Labels       as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Labels,
+       Text.LLVM.Labels.TH    as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Labels.TH,
+       Text.LLVM.Lens         as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Lens,
+       Text.LLVM.Parser       as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Parser,
+       Text.LLVM.PP           as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.PP,
+       Text.LLVM.DebugUtils   as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.DebugUtils,
+       Text.LLVM.Triple       as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Triple,
+       Text.LLVM.Triple.AST   as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Triple.AST,
+       Text.LLVM.Triple.Parse as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Triple.Parse,
+       Text.LLVM.Triple.Print as Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Triple.Print)
+
   hs-source-dirs:
         src
 
@@ -161,21 +182,46 @@
         -Wall
         -fwarn-tabs
 
-  ghc-prof-options:
-        -caf-all
-        -auto-all
+-- Abridged from llvm-pretty.cabal
+library llvm-pretty-accfork
+  default-language: Haskell2010
+  ghc-options: -Wall
 
-  if impl(ghc >= 8.0)
-    ghc-options:
-        -Wmissed-specialisations
+  hs-source-dirs:      llvm-pretty/src
+  exposed-modules:     Text.LLVM
+                       Text.LLVM.AST
+                       Text.LLVM.Labels
+                       Text.LLVM.Labels.TH
+                       Text.LLVM.Lens
+                       Text.LLVM.Parser
+                       Text.LLVM.PP
+                       Text.LLVM.DebugUtils
+                       Text.LLVM.Triple
+                       Text.LLVM.Triple.AST
+                       Text.LLVM.Triple.Parse
+                       Text.LLVM.Triple.Print
+  other-modules:       Text.LLVM.Triple.Parse.ARM
+                       Text.LLVM.Triple.Parse.LookupTable
+                       Text.LLVM.Util
 
+  build-depends:       base             >= 4.11 && < 5,
+                       containers       >= 0.4,
+                       parsec           >= 3,
+                       pretty           >= 1.0.1,
+                       monadLib         >= 3.6.1,
+                       microlens        >= 0.4,
+                       microlens-th     >= 0.4,
+                       syb              >= 0.7,
+                       template-haskell >= 2.7,
+                       th-abstraction   >= 0.3.1 && <0.8
+
 source-repository head
   type:                 git
   location:             https://github.com/AccelerateHS/accelerate-llvm.git
 
 source-repository this
   type:                 git
-  tag:                  v1.3.0.0
+  tag:                  v1.4.0.0
   location:             https://github.com/AccelerateHS/accelerate-llvm.git
 
 -- vim: nospell
diff --git a/llvm-pretty/src/Text/LLVM.hs b/llvm-pretty/src/Text/LLVM.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM.hs
@@ -0,0 +1,720 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE RecursiveDo #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+module Text.LLVM (
+    -- * LLVM Monad
+    LLVM()
+  , runLLVM
+  , emitTypeDecl
+  , emitGlobal
+  , emitDeclare
+  , emitDefine
+
+    -- * Alias Introduction
+  , alias
+
+    -- * Function Definition
+  , freshSymbol
+  , (:>)(..)
+  , define, defineFresh, DefineArgs()
+  , define'
+  , declare
+  , global
+  , FunAttrs(..), emptyFunAttrs
+    -- * Types
+  , iT, ptrT, voidT, arrayT
+  , (=:), (-:)
+
+    -- * Values
+  , IsValue(..)
+  , int
+  , integer
+  , struct
+  , array
+  , string
+
+    -- * Basic Blocks
+  , BB()
+  , runBB
+  , freshLabel
+  , label
+  , comment
+  , assign
+
+    -- * Terminator Instructions
+  , ret
+  , retVoid
+  , jump
+  , br
+  , unreachable
+  , unwind
+
+    -- * Binary Operations
+  , add, fadd
+  , sub, fsub
+  , mul, fmul
+  , udiv, sdiv, fdiv
+  , urem, srem, frem
+
+    -- * Bitwise Binary Operations
+  , shl
+  , lshr, ashr
+  , band, bor, bxor
+
+    -- * Conversion Operations
+  , trunc
+  , zext
+  , sext
+  , fptrunc
+  , fpext
+  , fptoui, fptosi
+  , uitofp, sitofp
+  , ptrtoint, inttoptr
+  , bitcast
+
+    -- * Aggregate Operations
+  , extractValue
+  , insertValue
+
+    -- * Memory Access and Addressing Operations
+  , alloca
+  , load
+  , store
+  , getelementptr
+  , nullPtr
+
+    -- * Other Operations
+  , icmp
+  , fcmp
+  , phi, PhiArg, from
+  , select
+  , call, call_
+  , invoke
+  , switch
+  , shuffleVector
+
+    -- * Re-exported
+  , module Text.LLVM.AST
+  ) where
+
+import Text.LLVM.AST
+
+import Control.Monad.Fix (MonadFix)
+import Data.Char (ord)
+import Data.Int (Int8,Int16,Int32,Int64)
+import Data.Word (Word32, Word64)
+import Data.Maybe (maybeToList)
+import Data.String (IsString(..))
+import MonadLib hiding (jump,Label)
+import qualified Data.Foldable as F
+import qualified Data.Sequence as Seq
+import qualified Data.Map.Strict as Map
+
+
+-- Fresh Names -----------------------------------------------------------------
+
+type Names = Map.Map String Int
+
+-- | Avoid generating the provided name.  When the name already exists, return
+-- Nothing.
+avoid :: String -> Names -> Maybe Names
+avoid name ns =
+  case Map.lookup name ns of
+    Nothing -> Just (Map.insert name 0 ns)
+    Just _  -> Nothing
+
+nextName :: String -> Names -> (String,Names)
+nextName pfx ns =
+  case Map.lookup pfx ns of
+    Nothing -> (fmt (0 :: Int),  Map.insert pfx 1 ns)
+    Just ix -> (fmt ix, Map.insert pfx (ix+1) ns)
+  where
+  fmt i = showString pfx (shows i "")
+
+
+-- LLVM Monad ------------------------------------------------------------------
+
+newtype LLVM a = LLVM
+  { unLLVM :: WriterT Module (StateT Names Id) a
+  } deriving (Functor,Applicative,Monad,MonadFix)
+
+freshNameLLVM :: String -> LLVM String
+freshNameLLVM pfx = LLVM $ do
+  ns <- get
+  let (n,ns') = nextName pfx ns
+  set ns'
+  return n
+
+runLLVM :: LLVM a -> (a,Module)
+runLLVM  = fst . runId . runStateT Map.empty . runWriterT . unLLVM
+
+emitTypeDecl :: TypeDecl -> LLVM ()
+emitTypeDecl td = LLVM (put emptyModule { modTypes = [td] })
+
+emitGlobal :: Global -> LLVM (Typed Value)
+emitGlobal g =
+  do LLVM (put emptyModule { modGlobals = [g] })
+     return (ptrT (globalType g) -: globalSym g)
+
+emitDefine :: Define -> LLVM (Typed Value)
+emitDefine d =
+  do LLVM (put emptyModule { modDefines = [d] })
+     return (defFunType d -: defName d)
+
+emitDeclare :: Declare -> LLVM (Typed Value)
+emitDeclare d =
+  do LLVM (put emptyModule { modDeclares = [d] })
+     return (decFunType d -: decName d)
+
+alias :: Ident -> Type -> LLVM ()
+alias i ty = emitTypeDecl (TypeDecl i ty)
+
+freshSymbol :: LLVM Symbol
+freshSymbol  = Symbol `fmap` freshNameLLVM "f"
+
+-- | Emit a declaration.
+declare :: Type -> Symbol -> [Type] -> Bool -> LLVM (Typed Value)
+declare rty sym tys va = emitDeclare Declare
+  { decLinkage    = Nothing
+  , decVisibility = Nothing
+  , decRetType    = rty
+  , decName       = sym
+  , decArgs       = tys
+  , decVarArgs    = va
+  , decAttrs      = []
+  , decComdat     = Nothing
+  }
+
+-- | Emit a global declaration.
+global :: GlobalAttrs -> Symbol -> Type -> Maybe Value -> LLVM (Typed Value)
+global attrs sym ty mbVal = emitGlobal Global
+  { globalSym      = sym
+  , globalType     = ty
+  , globalValue    = toValue `fmap` mbVal
+  , globalAttrs    = attrs
+  , globalAlign    = Nothing
+  , globalMetadata = Map.empty
+  }
+
+-- | Output a somewhat clunky representation for a string global, that deals
+-- well with escaping in the haskell-source string.
+string :: Symbol -> String -> LLVM (Typed Value)
+string sym str =
+  global emptyGlobalAttrs { gaConstant = True } sym (typedType val)
+      (Just (typedValue val))
+  where
+  bytes = [ int (fromIntegral (ord c)) | c <- str ]
+  val   = array (iT 8) bytes
+
+
+-- Function Definition ---------------------------------------------------------
+
+data FunAttrs = FunAttrs
+  { funLinkage    :: Maybe Linkage
+  , funVisibility :: Maybe Visibility
+  , funGC         :: Maybe GC
+  } deriving (Show)
+
+emptyFunAttrs :: FunAttrs
+emptyFunAttrs  = FunAttrs
+  { funLinkage    = Nothing
+  , funVisibility = Nothing
+  , funGC         = Nothing
+  }
+
+
+-- XXX Do not export
+freshArg :: Type -> LLVM (Typed Ident)
+freshArg ty = (Typed ty . Ident) `fmap` freshNameLLVM "a"
+
+infixr 0 :>
+data a :> b = a :> b
+    deriving Show
+
+-- | Types that can be used to define the body of a function.
+class DefineArgs a k | a -> k where
+  defineBody :: [Typed Ident] -> a -> k -> LLVM ([Typed Ident], [BasicBlock])
+
+instance DefineArgs () (BB ()) where
+  defineBody tys () body = return $ runBB $ do
+    body
+    return (reverse tys)
+
+instance DefineArgs as k => DefineArgs (Type :> as) (Typed Value -> k) where
+  defineBody args (ty :> as) f = do
+    arg <- freshArg ty
+    defineBody (arg:args) as (f (toValue `fmap` arg))
+
+-- helper instances for DefineArgs
+
+instance DefineArgs Type (Typed Value -> BB ()) where
+  defineBody tys ty body = defineBody tys (ty :> ()) body
+
+instance DefineArgs (Type,Type) (Typed Value -> Typed Value -> BB ()) where
+  defineBody tys (a,b) body = defineBody tys (a :> b :> ()) body
+
+instance DefineArgs (Type,Type,Type)
+                    (Typed Value -> Typed Value -> Typed Value -> BB ()) where
+  defineBody tys (a,b,c) body = defineBody tys (a :> b :> c :> ()) body
+
+-- | Define a function.
+define :: DefineArgs sig k => FunAttrs -> Type -> Symbol -> sig -> k
+       -> LLVM (Typed Value)
+define attrs rty fun sig k = do
+  (args,body) <- defineBody [] sig k
+  emitDefine Define
+    { defLinkage    = funLinkage attrs
+    , defVisibility = funVisibility attrs
+    , defName       = fun
+    , defRetType    = rty
+    , defArgs       = args
+    , defVarArgs    = False
+    , defAttrs      = []
+    , defSection    = Nothing
+    , defGC         = funGC attrs
+    , defBody       = body
+    , defMetadata   = Map.empty
+    , defComdat     = Nothing
+    }
+
+-- | A combination of define and @freshSymbol@.
+defineFresh :: DefineArgs sig k => FunAttrs -> Type -> sig -> k
+            -> LLVM (Typed Value)
+defineFresh attrs rty args body = do
+  sym <- freshSymbol
+  define attrs rty sym args body
+
+-- | Function definition when the argument list isn't statically known.  This is
+-- useful when generating code.
+define' :: FunAttrs -> Type -> Symbol -> [Type] -> Bool
+        -> ([Typed Value] -> BB ())
+        -> LLVM (Typed Value)
+define' attrs rty sym sig va k = do
+  args <- mapM freshArg sig
+  emitDefine Define
+    { defLinkage    = funLinkage attrs
+    , defVisibility = funVisibility attrs
+    , defName       = sym
+    , defRetType    = rty
+    , defArgs       = args
+    , defVarArgs    = va
+    , defAttrs      = []
+    , defSection    = Nothing
+    , defGC         = funGC attrs
+    , defBody       = snd (runBB (k (map (fmap toValue) args)))
+    , defMetadata   = Map.empty
+    , defComdat     = Nothing
+    }
+
+-- Basic Block Monad -----------------------------------------------------------
+
+newtype BB a = BB
+  { unBB :: WriterT [BasicBlock] (StateT RW Id) a
+  } deriving (Functor,Applicative,Monad,MonadFix)
+
+avoidName :: String -> BB ()
+avoidName name = BB $ do
+  rw <- get
+  case avoid name (rwNames rw) of
+    Just ns' -> set rw { rwNames = ns' }
+    Nothing  -> error ("avoidName: " ++ name ++ " already registered")
+
+freshNameBB :: String -> BB String
+freshNameBB pfx = BB $ do
+  rw <- get
+  let (n,ns') = nextName pfx (rwNames rw)
+  set rw { rwNames = ns' }
+  return n
+
+runBB :: BB a -> (a,[BasicBlock])
+runBB m =
+  case runId (runStateT emptyRW (runWriterT (unBB body))) of
+    ((a,bbs),_rw) -> (a,bbs)
+  where
+  -- make sure that the last block is terminated
+  body = do
+    res <- m
+    terminateBasicBlock
+    return res
+
+data RW = RW
+  { rwNames :: Names
+  , rwLabel :: Maybe BlockLabel
+  , rwStmts :: Seq.Seq Stmt
+  } deriving Show
+
+emptyRW :: RW
+emptyRW  = RW
+  { rwNames = Map.empty
+  , rwLabel = Nothing
+  , rwStmts = Seq.empty
+  }
+
+rwBasicBlock :: RW -> (RW,Maybe BasicBlock)
+rwBasicBlock rw
+  | Seq.null (rwStmts rw) = (rw,Nothing)
+  | otherwise             =
+      let rw' = rw { rwLabel = Nothing, rwStmts = Seq.empty }
+          bb  = BasicBlock (rwLabel rw) (F.toList (rwStmts rw))
+       in (rw',Just bb)
+
+emitStmt :: Stmt -> BB ()
+emitStmt stmt = do
+  BB $ do
+    rw <- get
+    set $! rw { rwStmts = rwStmts rw Seq.|> stmt }
+  when (isTerminator (stmtInstr stmt)) terminateBasicBlock
+
+effect :: Instr -> BB ()
+effect i = emitStmt (Effect i mempty [])
+
+observe :: Type -> Instr -> BB (Typed Value)
+observe ty i = do
+  name <- freshNameBB "r"
+  let res = Ident name
+  emitStmt (Result res i mempty [])
+  return (Typed ty (ValIdent res))
+
+
+-- Basic Blocks ----------------------------------------------------------------
+
+freshLabel :: BB Ident
+freshLabel  = Ident `fmap` freshNameBB "L"
+
+-- | Force termination of the current basic block, and start a new one with the
+-- given label.  If the previous block had no instructions defined, it will just
+-- be thrown away.
+label :: Ident -> BB ()
+label l = do
+  terminateBasicBlock
+  BB $ do
+    rw <- get
+    set $! rw { rwLabel = Just (Named l) }
+
+instance IsString (BB a) where
+  fromString l = do
+    label (fromString l)
+    return (error ("Label ``" ++ l ++ "'' has no value"))
+
+terminateBasicBlock :: BB ()
+terminateBasicBlock  = BB $ do
+  rw <- get
+  let (rw',bb) = rwBasicBlock rw
+  put (maybeToList bb)
+  set rw'
+
+
+-- Type Helpers ----------------------------------------------------------------
+
+iT :: Word32 -> Type
+iT  = PrimType . Integer
+
+ptrT :: Type -> Type
+ptrT  = \ty -> PtrTo ty defaultAddrSpace
+
+voidT :: Type
+voidT  = PrimType Void
+
+arrayT :: Word64 -> Type -> Type
+arrayT  = Array
+
+
+-- Value Helpers ---------------------------------------------------------------
+
+class IsValue a where
+  toValue :: a -> Value
+
+instance IsValue Value where
+  toValue = id
+
+instance IsValue a => IsValue (Typed a) where
+  toValue = toValue . typedValue
+
+instance IsValue Bool where
+  toValue = ValBool
+
+instance IsValue Integer where
+  toValue = ValInteger
+
+instance IsValue Int where
+  toValue = ValInteger . toInteger
+
+instance IsValue Int8 where
+  toValue = ValInteger . toInteger
+
+instance IsValue Int16 where
+  toValue = ValInteger . toInteger
+
+instance IsValue Int32 where
+  toValue = ValInteger . toInteger
+
+instance IsValue Int64 where
+  toValue = ValInteger . toInteger
+
+instance IsValue Float where
+  toValue = ValFloat
+
+instance IsValue Double where
+  toValue = ValDouble
+
+instance IsValue Ident where
+  toValue = ValIdent
+
+instance IsValue Symbol where
+  toValue = ValSymbol
+
+(-:) :: IsValue a => Type -> a -> Typed Value
+ty -: a = ty =: toValue a
+
+(=:) :: Type -> a -> Typed a
+ty =: a = Typed
+  { typedType  = ty
+  , typedValue = a
+  }
+
+int :: Int -> Value
+int  = toValue
+
+integer :: Integer -> Value
+integer  = toValue
+
+struct :: Bool -> [Typed Value] -> Typed Value
+struct packed tvs
+  | packed    = PackedStruct (map typedType tvs) =: ValPackedStruct tvs
+  | otherwise = Struct (map typedType tvs)       =: ValStruct tvs
+
+array :: Type -> [Value] -> Typed Value
+array ty vs = Typed (Array (fromIntegral (length vs)) ty) (ValArray ty vs)
+
+
+-- Instructions ----------------------------------------------------------------
+
+comment :: String -> BB ()
+comment str = effect (Comment str)
+
+-- | Emit an assignment that uses the given identifier to name the result of the
+-- BB operation.
+--
+-- WARNING: this can throw errors.
+assign :: IsValue a => Ident -> BB (Typed a) -> BB (Typed Value)
+assign r@(Ident name) body = do
+  avoidName name
+  tv <- body
+  rw <- BB get
+  case Seq.viewr (rwStmts rw) of
+
+    stmts Seq.:> Result _ i d m ->
+      do BB (set rw { rwStmts = stmts Seq.|> Result r i d m })
+         return (const (ValIdent r) `fmap` tv)
+
+    _ -> error "assign: invalid argument"
+
+-- | Emit the ``ret'' instruction and terminate the current basic block.
+ret :: IsValue a => Typed a -> BB ()
+ret tv = effect (Ret (toValue `fmap` tv))
+
+-- | Emit ``ret void'' and terminate the current basic block.
+retVoid :: BB ()
+retVoid  = effect RetVoid
+
+jump :: Ident -> BB ()
+jump l = effect (Jump (Named l))
+
+br :: IsValue a => Typed a -> Ident -> Ident -> BB ()
+br c t f = effect (Br (toValue `fmap` c) (Named t) (Named f))
+
+unreachable :: BB ()
+unreachable  = effect Unreachable
+
+unwind :: BB ()
+unwind  = effect Unwind
+
+binop :: (IsValue a, IsValue b)
+      => (Typed Value -> Value -> Instr) -> Typed a -> b -> BB (Typed Value)
+binop k l r = observe (typedType l) (k (toValue `fmap` l) (toValue r))
+
+add :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+add  = binop (Arith (Add False False))
+
+fadd :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+fadd  = binop (Arith (FAdd []))
+
+sub :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+sub  = binop (Arith (Sub False False))
+
+fsub :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+fsub  = binop (Arith (FSub []))
+
+mul :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+mul  = binop (Arith (Mul False False))
+
+fmul :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+fmul  = binop (Arith (FMul []))
+
+udiv :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+udiv  = binop (Arith (UDiv False))
+
+sdiv :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+sdiv  = binop (Arith (SDiv False))
+
+fdiv :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+fdiv  = binop (Arith (FDiv []))
+
+urem :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+urem  = binop (Arith URem)
+
+srem :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+srem  = binop (Arith SRem)
+
+frem :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+frem  = binop (Arith (FRem []))
+
+shl :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+shl  = binop (Bit (Shl False False))
+
+lshr :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+lshr  = binop (Bit (Lshr False))
+
+ashr :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+ashr  = binop (Bit (Ashr False))
+
+band :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+band  = binop (Bit And)
+
+bor :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+bor  = binop (Bit Or)
+
+bxor :: (IsValue a, IsValue b) => Typed a -> b -> BB (Typed Value)
+bxor  = binop (Bit Xor)
+
+-- | Returns the value stored in the member field of an aggregate value.
+extractValue :: IsValue a => Typed a -> Int32 -> BB (Typed Value)
+extractValue ta i =
+  let etp = case typedType ta of
+              Struct fl -> fl !! fromIntegral i
+              Array _l etp' -> etp'
+              _ -> error "extractValue not given a struct or array."
+   in observe etp (ExtractValue (toValue `fmap` ta) [i])
+
+-- | Inserts a value into the member field of an aggregate value, and returns
+-- the new value.
+insertValue :: (IsValue a, IsValue b)
+            => Typed a -> Typed b -> Int32 -> BB (Typed Value)
+insertValue ta tv i =
+  observe (typedType ta)
+      (InsertValue (toValue `fmap` ta) (toValue `fmap` tv) [i])
+
+shuffleVector :: (IsValue a, IsValue b, IsValue c)
+              => Typed a -> b -> c -> BB (Typed Value)
+shuffleVector vec1 vec2 mask =
+  case typedType vec1 of
+    Vector n _ -> observe (typedType vec1)
+                $ ShuffleVector (toValue `fmap` vec1) (toValue vec2)
+                $ Typed (Vector n (PrimType (Integer 32))) (toValue mask)
+    _          -> error "shuffleVector not given a vector"
+
+alloca :: Type -> Maybe (Typed Value) -> Maybe Int -> BB (Typed Value)
+alloca ty mb align = observe (PtrTo ty defaultAddrSpace) (Alloca ty es align)
+  where
+  es = fmap toValue `fmap` mb
+
+load :: IsValue a => Type -> Typed a -> Maybe Align -> BB (Typed Value)
+load ty ptr ma = observe ty (Load False ty (toValue `fmap` ptr) Nothing ma)
+
+store :: (IsValue a, IsValue b) => a -> Typed b -> Maybe Align -> BB ()
+store a ptr ma =
+  case typedType ptr of
+    PtrTo ty _ -> effect (Store False (ty -: a) (toValue `fmap` ptr) Nothing ma)
+    _          -> error "store not given a pointer"
+
+nullPtr :: Type -> Typed Value
+nullPtr ty = ptrT ty =: ValNull
+
+convop :: IsValue a
+       => (Typed Value -> Type -> Instr) -> Typed a -> Type -> BB (Typed Value)
+convop k a ty = observe ty (k (toValue `fmap` a) ty)
+
+trunc :: IsValue a => Typed a -> Type -> BB (Typed Value)
+trunc  = convop (Conv (Trunc False False))
+
+zext :: IsValue a => Typed a -> Type -> BB (Typed Value)
+zext  = convop (Conv (ZExt False))
+
+sext :: IsValue a => Typed a -> Type -> BB (Typed Value)
+sext  = convop (Conv SExt)
+
+fptrunc :: IsValue a => Typed a -> Type -> BB (Typed Value)
+fptrunc  = convop (Conv FpTrunc)
+
+fpext :: IsValue a => Typed a -> Type -> BB (Typed Value)
+fpext  = convop (Conv FpExt)
+
+fptoui :: IsValue a => Typed a -> Type -> BB (Typed Value)
+fptoui  = convop (Conv FpToUi)
+
+fptosi :: IsValue a => Typed a -> Type -> BB (Typed Value)
+fptosi  = convop (Conv FpToSi)
+
+uitofp :: IsValue a => Typed a -> Type -> BB (Typed Value)
+uitofp  = convop (Conv (UiToFp False))
+
+sitofp :: IsValue a => Typed a -> Type -> BB (Typed Value)
+sitofp  = convop (Conv SiToFp)
+
+ptrtoint :: IsValue a => Typed a -> Type -> BB (Typed Value)
+ptrtoint  = convop (Conv PtrToInt)
+
+inttoptr :: IsValue a => Typed a -> Type -> BB (Typed Value)
+inttoptr  = convop (Conv IntToPtr)
+
+bitcast :: IsValue a => Typed a -> Type -> BB (Typed Value)
+bitcast  = convop (Conv BitCast)
+
+icmp :: (IsValue a, IsValue b) => ICmpOp -> Typed a -> b -> BB (Typed Value)
+icmp op l r = observe (iT 1) (ICmp False op (toValue `fmap` l) (toValue r))
+
+fcmp :: (IsValue a, IsValue b) => FCmpOp -> Typed a -> b -> BB (Typed Value)
+fcmp op l r = observe (iT 1) (FCmp [] op (toValue `fmap` l) (toValue r))
+
+data PhiArg = PhiArg Value BlockLabel
+
+from :: IsValue a => a -> BlockLabel -> PhiArg
+from a = PhiArg (toValue a)
+
+phi :: Type -> [PhiArg] -> BB (Typed Value)
+phi ty vs = observe ty (Phi [] ty [ (v,l) | PhiArg v l <- vs ])
+
+select :: (IsValue a, IsValue b, IsValue c)
+       => Typed a -> Typed b -> Typed c -> BB (Typed Value)
+select c t f = observe (typedType t)
+             $ Select [] (toValue `fmap` c) (toValue `fmap` t) (toValue f)
+
+getelementptr :: IsValue a
+              => Type -> Typed a -> [Typed Value] -> BB (Typed Value)
+getelementptr ty ptr ixs = observe ty (GEP [] ty (toValue `fmap` ptr) ixs)
+
+-- | Emit a call instruction, and generate a new variable for its result.
+call :: IsValue a => Typed a -> [Typed Value] -> BB (Typed Value)
+call sym vs = case typedType sym of
+  PtrTo ty@(FunTy rty _ _) _ -> observe rty (Call False [] ty (toValue sym) vs)
+  _                          -> error "invalid function type given to call"
+
+-- | Emit a call instruction, but don't generate a new variable for its result.
+call_ :: IsValue a => Typed a -> [Typed Value] -> BB ()
+call_ sym vs = effect (Call False [] (typedType sym) (toValue sym) vs)
+
+-- | Emit an invoke instruction, and generate a new variable for its result.
+invoke :: IsValue a =>
+          Type -> a -> [Typed Value] -> Ident -> Ident -> BB (Typed Value)
+invoke rty sym vs to uw = observe rty
+                        $ Invoke rty (toValue sym) vs (Named to) (Named uw)
+
+-- | Emit a call instruction, but don't generate a new variable for its result.
+switch :: IsValue a => Typed a -> Ident -> [(Integer, Ident)] -> BB ()
+switch idx def dests = effect (Switch (toValue `fmap` idx) (Named def)
+                                      (map (\(n, l) -> (n, Named l)) dests))
diff --git a/llvm-pretty/src/Text/LLVM/AST.hs b/llvm-pretty/src/Text/LLVM/AST.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/AST.hs
@@ -0,0 +1,2224 @@
+{- |
+Because this library supports many LLVM versions, it is possible to construct
+an AST with the types in this module that only some LLVM versions will accept.
+These cases are usually documented in the Haddocks for the relevant data types.
+When trying to pretty-print constructions that are unsupported by the current
+LLVM version, pretty-printing may 'error'.
+
+At the same time, while the AST coverage is fairly extensive, it is also
+incomplete: there are some values that new LLVM versions would accept but are
+not yet represented here.
+-}
+
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE DeriveDataTypeable, DeriveFunctor, DeriveGeneric #-}
+{-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveLift #-}
+
+module Text.LLVM.AST
+  ( -- * Modules
+    Module(..)
+  , emptyModule
+    -- * Named Metadata
+  , NamedMd(..)
+    -- * Unnamed Metadata
+  , UnnamedMd(..)
+    -- * Aliases
+  , GlobalAlias(..)
+    -- * Data Layout
+  , DataLayout
+  , LayoutSpec(..)
+  , Alignment(..)
+  , FunctionPointerAlignType(..)
+  , Storage(..)
+  , PointerSize(..)
+  , AddressSpace
+  , NumBits
+  , Mangling(..)
+  , parseDataLayout
+    -- * Inline Assembly
+  , InlineAsm
+    -- * Comdat
+  , SelectionKind(..)
+    -- * Identifiers
+  , Ident(..)
+    -- * Symbols
+  , Symbol(..)
+    -- * Types
+  , PrimType(..)
+  , FloatType(..)
+  , Type, Type'(..)
+  , AddrSpace(..)
+  , defaultAddrSpace
+  , updateAliasesA, updateAliases
+  , isFloatingPoint
+  , isAlias
+  , isPrimTypeOf
+  , isLabel
+  , isInteger
+  , isVector
+  , isVectorOf
+  , isArray
+  , isPointer
+  , eqTypeModuloOpaquePtrs
+  , cmpTypeModuloOpaquePtrs
+  , fixupOpaquePtrs
+    -- * Null values
+  , NullResult(..)
+  , primTypeNull
+  , floatTypeNull
+  , typeNull
+    -- * Type Elimination
+  , elimFunTy
+  , elimAlias
+  , elimPtrTo
+  , elimVector
+  , elimArray
+  , elimFunPtr
+  , elimPrimType
+  , elimFloatType
+  , elimSequentialType
+    -- * Top-level Type Aliases
+  , TypeDecl(..)
+    -- * Globals
+  , Global(..)
+  , addGlobal
+  , GlobalAttrs(..)
+  , emptyGlobalAttrs
+    -- * Declarations
+  , Declare(..)
+  , decFunType
+    -- * Function Definitions
+  , Define(..)
+  , defFunType, addDefine
+    -- * Function Attributes and attribute groups
+  , FunAttr(..)
+    -- * Basic Block Labels
+  , BlockLabel(..)
+    -- * Basic Blocks
+  , BasicBlock'(..), BasicBlock
+  , brTargets
+    -- * Attributes
+  , Linkage(..)
+  , Visibility(..)
+  , GC(..)
+    -- * Typed Things
+  , Typed(..)
+  , mapMTyped
+    -- * Instructions
+  , ArithOp(..)
+  , isIArith
+  , isFArith
+  , UnaryArithOp(..)
+  , BitOp(..)
+  , ConvOp(..)
+  , AtomicRWOp(..)
+  , AtomicOrdering(..)
+  , Align
+  , Instr'(..), Instr
+  , Clause'(..), Clause
+  , isTerminator
+  , isComment
+  , isPhi
+  , ICmpOp(..)
+  , FCmpOp(..)
+  , FMF(..)
+    -- * Values
+  , Value'(..), Value
+  , FP80Value(..)
+  , ValMd'(..), ValMd
+  , KindMd
+  , FnMdAttachments
+  , GlobalMdAttachments
+  , DebugLoc'(..), DebugLoc
+  , isConst
+    -- * Value Elimination
+  , elimValSymbol
+  , elimValInteger
+    -- * Statements
+  , Stmt'(..), Stmt
+  , stmtInstr
+  , stmtMetadata
+  , extendMetadata
+  , addDebugRecord
+    -- * Constant Expressions
+  , ConstExpr'(..), ConstExpr
+  , GEPAttr(..)
+  , orderedGEPAttrs
+  , RangeSpec(RangeIndex, Range)
+    -- * DWARF Debug Info
+  , DebugInfo'(..), DebugInfo
+  , DILabel, DILabel'(..)
+  , DIImportedEntity, DIImportedEntity'(..)
+  , DITemplateTypeParameter, DITemplateTypeParameter'(..)
+  , DITemplateValueParameter, DITemplateValueParameter'(..)
+  , DINameSpace, DINameSpace'(..)
+  , DwarfAttrEncoding
+  , DwarfLang
+  , DwarfTag
+  , DwarfVirtuality
+  , DIFlags
+  , DIEmissionKind
+  , DIBasicType'(..), DIBasicType
+  , DICompileUnit'(..), DICompileUnit
+  , DICompositeType'(..), DICompositeType
+  , DIDerivedType'(..), DIDerivedType
+  , DIExpression(..)
+  , DIFile(..)
+  , DIGlobalVariable'(..), DIGlobalVariable
+  , DIGlobalVariableExpression'(..), DIGlobalVariableExpression
+  , DILexicalBlock'(..), DILexicalBlock
+  , DILexicalBlockFile'(..), DILexicalBlockFile
+  , DILocalVariable'(..), DILocalVariable
+  , DISubprogram'(..), DISubprogram
+  , DISubrange'(..), DISubrange
+  , DISubroutineType'(..), DISubroutineType
+  , DIArgList'(..), DIArgList
+  , dwarf_DW_APPLE_ENUM_KIND_invalid
+  , DebugRecord, DebugRecord'(..)
+  , DbgRecAssign, DbgRecAssign'(..)
+  , DbgRecDeclare, DbgRecDeclare'(..)
+  , DbgRecLabel, DbgRecLabel'(..)
+  , DbgRecValueSimple, DbgRecValueSimple'(..)
+  , DbgRecValue, DbgRecValue'(..)
+    -- * Aggregate Utilities
+  , IndexResult(..)
+  , isInvalid
+  , resolveGepFull
+  , resolveGep
+  , resolveGepBody
+  , isGepIndex
+  , isGepStructIndex
+  , resolveValueIndex
+  ) where
+
+import Control.Monad (MonadPlus(mzero,mplus),(<=<),guard)
+import Data.Bits ( complement )
+import Data.Coerce (coerce)
+import Data.Data (Data)
+import Data.Functor.Identity (Identity(..))
+import Data.Generics (everywhere, extQ, mkT, something)
+import Data.Int (Int32,Int64)
+import Data.List (genericIndex,genericLength)
+import qualified Data.Map as Map
+import Data.Maybe (isJust)
+import Data.Semigroup as Sem
+import Data.String (IsString(fromString))
+import Data.Typeable (Typeable)
+import Data.Word (Word8,Word16,Word32,Word64)
+import GHC.Generics (Generic, Generic1)
+import Language.Haskell.TH.Syntax (Lift)
+
+import Text.Parsec
+import Text.Parsec.String
+
+import Text.LLVM.Triple.AST (TargetTriple)
+
+
+-- Modules ---------------------------------------------------------------------
+
+data Module = Module
+  { modSourceName :: Maybe String
+  , modTriple     :: TargetTriple  -- ^ target triple
+  , modDataLayout :: DataLayout    -- ^ type size and alignment information
+  , modTypes      :: [TypeDecl]    -- ^ top-level type aliases
+  , modNamedMd    :: [NamedMd]
+  , modUnnamedMd  :: [UnnamedMd]
+  , modComdat     :: Map.Map String SelectionKind
+  , modGlobals    :: [Global]      -- ^ global value declarations
+  , modDeclares   :: [Declare]     -- ^ external function declarations (without definitions)
+  , modDefines    :: [Define]      -- ^ internal function declarations (with definitions)
+  , modInlineAsm  :: InlineAsm
+  , modAliases    :: [GlobalAlias]
+  } deriving (Data, Eq, Ord, Generic, Show, Typeable)
+
+-- | Combines fields pointwise.
+instance Sem.Semigroup Module where
+  m1 <> m2 = Module
+    { modSourceName = modSourceName m1 `mplus`   modSourceName m2
+    , modTriple     = modTriple m1     <> modTriple     m2
+    , modDataLayout = modDataLayout m1 <> modDataLayout m2
+    , modTypes      = modTypes      m1 <> modTypes      m2
+    , modUnnamedMd  = modUnnamedMd  m1 <> modUnnamedMd  m2
+    , modNamedMd    = modNamedMd    m1 <> modNamedMd    m2
+    , modGlobals    = modGlobals    m1 <> modGlobals    m2
+    , modDeclares   = modDeclares   m1 <> modDeclares   m2
+    , modDefines    = modDefines    m1 <> modDefines    m2
+    , modInlineAsm  = modInlineAsm  m1 <> modInlineAsm  m2
+    , modAliases    = modAliases    m1 <> modAliases    m2
+    , modComdat     = modComdat     m1 <> modComdat     m2
+    }
+
+instance Monoid Module where
+  mempty = emptyModule
+  mappend = (<>)
+
+emptyModule :: Module
+emptyModule  = Module
+  { modSourceName = mempty
+  , modTriple     = mempty
+  , modDataLayout = mempty
+  , modTypes      = mempty
+  , modNamedMd    = mempty
+  , modUnnamedMd  = mempty
+  , modGlobals    = mempty
+  , modDeclares   = mempty
+  , modDefines    = mempty
+  , modInlineAsm  = mempty
+  , modAliases    = mempty
+  , modComdat     = mempty
+  }
+
+
+-- Named Metadata --------------------------------------------------------------
+
+data NamedMd = NamedMd
+  { nmName   :: String
+  , nmValues :: [Int]
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+
+-- Unnamed Metadata ------------------------------------------------------------
+
+data UnnamedMd = UnnamedMd
+  { umIndex    :: !Int
+  , umValues   :: ValMd
+  , umDistinct :: Bool
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+
+-- Aliases ---------------------------------------------------------------------
+
+data GlobalAlias = GlobalAlias
+  { aliasLinkage    :: Maybe Linkage
+  , aliasVisibility :: Maybe Visibility
+  , aliasName       :: Symbol
+  , aliasType       :: Type
+  , aliasTarget     :: Value
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+
+-- Data Layout -----------------------------------------------------------------
+-- https://releases.llvm.org/19.1.0/docs/LangRef.html#data-layout
+
+type DataLayout = [LayoutSpec]
+
+data LayoutSpec
+  = BigEndian
+  | LittleEndian
+  | PointerSize PointerSize
+  | IntegerSize Storage
+  | VectorSize Storage
+  | FloatSize Storage
+  | StackObjSize  Storage
+  | AggregateSize (Maybe Int) !Alignment -- n.b. first Int present pre-LLVM4
+  | NativeIntSize [NumBits]
+  | StackAlign    !NumBits -- ^ size
+  | ProgramAddrSpace !AddressSpace
+  | GlobalAddrSpace !AddressSpace
+  | AllocaAddrSpace !AddressSpace
+  | FunctionPointerAlign !FunctionPointerAlignType !NumBits -- ^ type, abi
+  | Mangling Mangling
+  | NonIntegralPointerSpaces [AddressSpace]
+    deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+data Alignment = Alignment
+  { alignABI :: !NumBits
+  , alignPreferred :: Maybe NumBits  -- ^ default = alignABI
+  }
+  deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+-- | How should a function pointer be aligned?
+data FunctionPointerAlignType
+  = IndependentOfFunctionAlign
+    -- ^ The alignment of function pointers is independent of the alignment of
+    -- functions.
+  | MultipleOfFunctionAlign
+    -- ^ The alignment of function pointers is a multiple of the explicit
+    -- alignment specified on the function.
+  deriving (Data, Eq, Enum, Generic, Ord, Show, Typeable)
+
+data Storage = Storage
+  { storageSize :: !NumBits  -- ^ valid range [1,2^24)
+  , storageAlignment :: Alignment
+  }
+  deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+data PointerSize = PtrSize
+  { ptrAddrSpace :: !AddressSpace
+  , ptrStorage :: Storage
+  , ptrAddrIndexSize :: Maybe NumBits  -- ^ m.b. <= ptrSize, default = ptrSize
+  }
+  deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+type AddressSpace = Int
+type NumBits = Int
+
+data Mangling = ElfMangling
+              | GoffMangling
+              | MipsMangling
+              | MachOMangling
+              | WindowsCoffMangling
+              | WindowsX86CoffMangling
+              | XCoffMangling
+                deriving (Data, Eq, Enum, Generic, Ord, Show, Typeable)
+
+-- | Parse the data layout string.
+parseDataLayout :: MonadPlus m => String -> m DataLayout
+parseDataLayout str =
+  case parse (pDataLayout <* eof) "<internal>" str of
+    Left _err -> {- debugging: trace (show err) -} mzero
+    Right specs -> return specs
+  where
+    pDataLayout :: Parser DataLayout
+    pDataLayout = sepBy pLayoutSpec (char '-')
+
+    pLayoutSpec :: Parser LayoutSpec
+    pLayoutSpec =
+      do c <- letter
+         case c of
+           'E' -> return BigEndian
+           'e' -> return LittleEndian
+           'S' -> StackAlign <$> pInt
+           'P' -> ProgramAddrSpace <$> pInt -- Added in LLVM7
+           'G' -> GlobalAddrSpace <$> pInt -- Added in LLVM11
+           'A' -> AllocaAddrSpace <$> pInt -- Added in LLVM11
+           'p' -> do as <- pInt <|> return 0
+                     st <- char ':' >> pStorage
+                     idx <- pCOInt -- Added in LLVM7
+                     return $ PointerSize $ PtrSize as st idx
+           'i' -> IntegerSize <$> pStorage
+           'v' -> VectorSize <$> pStorage
+           'f' -> FloatSize <$> pStorage
+                  -- Note that the data layout specified in the LLVM
+                  -- BC/IR file is not a directive to the backend, but
+                  -- is instead an indication of what the particular
+                  -- backend chosen expects to receive.  The actual
+                  -- floating point size and alignment is specified as
+                  -- zero or more "fSZ:A1:A2" portions of the
+                  -- datawidth, where SZ is the size, A1 is the ABI
+                  -- alignment, and A2 is the preferred alignment
+                  -- (defaulting to A1 if not specified).  Not
+                  -- included in the data layout is the actual width,
+                  -- alignment, and format for implementation.  See
+                  -- (for example) references to LongDoubleWidth and
+                  -- LongDoubleFormat in
+                  -- https://github.com/llvm/llvm-project/blob/release_60/clang/lib/Basic/Targets/X86.h
+           's' -> StackObjSize <$> pStorage -- Obsoleted in LLVM4
+           'a' -> alphaNum >>= \case
+             ':' -> AggregateSize Nothing <$> pAlignment
+             d   -> AggregateSize <$> (Just <$> pIntWithFirstDigit d)
+                    <* char ':' <*> pAlignment
+           'F' -> FunctionPointerAlign <$> pFunctionPointerAlignType <*> pInt -- Added in LLVM9
+           'm' -> Mangling <$ char ':' <*> pMangling
+           'n' -> alphaNum >>= \case
+             'i' -> char ':'
+                    >> (NonIntegralPointerSpaces <$> sepBy pInt (char ':'))
+             d -> do fs <- pIntWithFirstDigit d
+                     ss <- char ':' *> sepBy pInt (char ':')
+                     return $ NativeIntSize $ fs : ss
+           _   -> mzero
+
+    pFunctionPointerAlignType :: Parser FunctionPointerAlignType
+    pFunctionPointerAlignType =
+      do c <- letter
+         case c of
+           'i' -> return IndependentOfFunctionAlign
+           'n' -> return MultipleOfFunctionAlign
+           _   -> mzero
+
+    pMangling :: Parser Mangling
+    pMangling =
+      do c <- letter
+         case c of
+           'e' -> return ElfMangling
+           'l' -> return GoffMangling
+           'm' -> return MipsMangling
+           'o' -> return MachOMangling
+           'w' -> return WindowsCoffMangling
+           'x' -> return WindowsX86CoffMangling
+           'a' -> return XCoffMangling
+           _   -> mzero
+
+    pAlignment :: Parser Alignment
+    pAlignment = Alignment <$> pInt <*> pCOInt
+
+    pStorage :: Parser Storage
+    pStorage = Storage <$> pInt <* char ':' <*> pAlignment
+
+    pInt :: Parser Int
+    pInt = read <$> many1 digit
+
+    pIntWithFirstDigit :: Char -> Parser Int
+    pIntWithFirstDigit d0 = read . (d0:) <$> many digit
+
+    pCInt :: Parser Int
+    pCInt = char ':' >> pInt
+
+    pCOInt :: Parser (Maybe Int)
+    pCOInt = optionMaybe pCInt
+
+-- Inline Assembly -------------------------------------------------------------
+
+type InlineAsm = [String]
+
+-- Comdat ----------------------------------------------------------------------
+
+data SelectionKind = ComdatAny
+                   | ComdatExactMatch
+                   | ComdatLargest
+                   | ComdatNoDuplicates
+                   | ComdatSameSize
+    deriving (Data, Eq, Enum, Generic, Ord, Show, Typeable)
+
+-- Identifiers -----------------------------------------------------------------
+
+newtype Ident = Ident String
+    deriving (Data, Eq, Generic, Ord, Show, Typeable, Lift)
+
+instance IsString Ident where
+  fromString = Ident
+
+-- Symbols ---------------------------------------------------------------------
+
+newtype Symbol = Symbol String
+    deriving (Data, Eq, Generic, Ord, Show, Typeable, Lift)
+
+instance Sem.Semigroup Symbol where
+  Symbol a <> Symbol b = Symbol (a <> b)
+
+instance Monoid Symbol where
+  mappend = (<>)
+  mempty  = Symbol mempty
+
+instance IsString Symbol where
+  fromString = Symbol
+
+-- Types -----------------------------------------------------------------------
+
+data PrimType
+  = Label
+  | Void
+  | Integer Word32
+  | FloatType FloatType
+  | X86mmx
+  | Metadata
+    deriving (Data, Eq, Generic, Ord, Show, Typeable, Lift)
+
+data FloatType
+  = Half
+  | Float
+  | Double
+  | Fp128
+  | X86_fp80
+  | PPC_fp128
+    deriving (Data, Eq, Enum, Generic, Ord, Show, Typeable, Lift)
+
+type Type = Type' Ident
+
+data Type' ident
+  = PrimType PrimType
+  | Alias ident
+  | Array Word64 (Type' ident)
+  | FunTy (Type' ident) [Type' ident] Bool
+  | PtrTo (Type' ident) AddrSpace
+    -- ^ A pointer to a memory location of a particular type. See also
+    -- 'PtrOpaque', which represents a pointer without a pointee type.
+  | PtrOpaque AddrSpace
+    -- ^ A pointer to a memory location. Unlike 'PtrTo', a 'PtrOpaque' does not
+    -- have a pointee type. Instead, instructions interacting through opaque
+    -- pointers specify the type of the underlying memory they are interacting
+    -- with.
+    --
+    -- 'PtrOpaque' should not be confused with 'Opaque', which is a completely
+    -- separate type with a similar-sounding name.
+  | Struct [Type' ident]
+  | PackedStruct [Type' ident]
+  | Vector Word64 (Type' ident)
+  | Opaque
+    -- ^ An opaque structure type, used to represent structure types that do not
+    -- have a body specified. This is similar to C's notion of a
+    -- forward-declared structure.
+    --
+    -- 'Opaque' should not be confused with 'PtrOpaque', which is a completely
+    -- separate type with a similar-sounding name.
+    deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+data AddrSpace = AddrSpace Word32
+    deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+defaultAddrSpace :: AddrSpace
+defaultAddrSpace = AddrSpace 0
+
+-- | Applicatively traverse a type, updating or removing aliases.
+updateAliasesA :: (Applicative f) => (a -> f (Type' b)) -> Type' a -> f (Type' b)
+updateAliasesA f = loop
+  where
+  loop ty = case ty of
+    Array len ety    -> Array len    <$> (loop ety)
+    FunTy res ps var -> FunTy        <$> (loop res) <*> (traverse loop ps) <*> pure var
+    PtrTo pty addr   -> PtrTo        <$> (loop pty) <*> pure addr
+    PtrOpaque addr   -> pure (PtrOpaque addr)
+    Struct fs        -> Struct       <$> (traverse loop fs)
+    PackedStruct fs  -> PackedStruct <$> (traverse loop fs)
+    Vector len ety   -> Vector       <$> pure len <*> (loop ety)
+    PrimType pty     -> pure $ PrimType pty
+    Opaque           -> pure $ Opaque
+    Alias lab        -> f lab
+
+-- | Traverse a type, updating or removing aliases.
+updateAliases :: (a -> Type' b) -> Type' a -> Type' b
+updateAliases f = coerce $ updateAliasesA (Identity . f)
+
+isFloatingPoint :: PrimType -> Bool
+isFloatingPoint (FloatType _) = True
+isFloatingPoint _             = False
+
+isAlias :: Type' ident -> Bool
+isAlias Alias{} = True
+isAlias _       = False
+
+isPrimTypeOf :: (PrimType -> Bool) -> Type' ident -> Bool
+isPrimTypeOf p (PrimType pt) = p pt
+isPrimTypeOf _ _             = False
+
+isLabel :: PrimType -> Bool
+isLabel Label = True
+isLabel _     = False
+
+isInteger :: PrimType -> Bool
+isInteger Integer{} = True
+isInteger _         = False
+
+isVector :: Type' ident -> Bool
+isVector Vector{} = True
+isVector _        = False
+
+isVectorOf :: (Type' ident -> Bool) -> Type' ident -> Bool
+isVectorOf p (Vector _ e) = p e
+isVectorOf _ _            = False
+
+isArray :: Type' ident -> Bool
+isArray ty = case ty of
+  Array _ _ -> True
+  _         -> False
+
+isPointer :: Type' ident -> Bool
+isPointer PtrTo{}     = True
+isPointer PtrOpaque{} = True
+isPointer _           = False
+
+
+-- | Like `Type'`, but where the 'PtrTo' and 'PtrOpaque' constructors have been
+-- collapsed into a single 'PtrView' constructor. This provides a coarser notion
+-- of type equality than what `Type'` provides, which distinguishes the two
+-- types of pointers.
+--
+-- `TypeView'` is not used directly in any of the other AST types. Instead, it
+-- is used only as an internal data type to power the 'eqTypeModuloOpaquePtrs'
+-- and 'cmpTypeModuloOpaquePtrs' functions.
+data TypeView' ident
+  = PrimTypeView PrimType
+  | AliasView ident
+  | ArrayView Word64 (TypeView' ident)
+  | FunTyView (TypeView' ident) [TypeView' ident] Bool
+  | PtrView
+    -- ^ The sole pointer type. Both 'PtrTo' and 'PtrOpaque' are mapped to
+    -- 'PtrView'.
+  | StructView [TypeView' ident]
+  | PackedStructView [TypeView' ident]
+  | VectorView Word64 (TypeView' ident)
+  | OpaqueView
+    -- ^ An opaque structure type, used to represent structure types that do not
+    -- forward-declared structure.
+    --
+    -- 'OpaqueView' should not be confused with opaque pointers, which are
+    -- mapped to 'PtrView'.
+    deriving (Eq, Ord)
+
+-- | Convert a `Type'` value to a `TypeView'` value.
+typeView :: Type' ident -> TypeView' ident
+-- The two most important cases. Both forms of pointers are mapped to PtrView.
+typeView PtrTo{}             = PtrView
+typeView PtrOpaque{}         = PtrView
+-- All other cases are straightforward.
+typeView (PrimType pt)       = PrimTypeView pt
+typeView (Alias lab)         = AliasView lab
+typeView (Array len et)      = ArrayView len (typeView et)
+typeView (FunTy ret args va) = FunTyView (typeView ret) (map typeView args) va
+typeView (Struct fs)         = StructView (map typeView fs)
+typeView (PackedStruct fs)   = PackedStructView (map typeView fs)
+typeView (Vector len et)     = VectorView len (typeView et)
+typeView Opaque              = OpaqueView
+
+-- | Check two 'Type's for equality, but treat 'PtrOpaque' types as being equal
+-- to @'PtrTo' ty@ types (for any type @ty@). This is a coarser notion of
+-- equality than what is provided by the 'Eq' instance for 'Type'.
+eqTypeModuloOpaquePtrs :: Eq ident => Type' ident -> Type' ident -> Bool
+eqTypeModuloOpaquePtrs x y = typeView x == typeView y
+
+-- | Compare two 'Type's, but treat 'PtrOpaque' types as being equal to
+-- @'PtrTo' ty@ types (for any type @ty@). This is a coarser notion of ordering
+-- than what is provided by the 'Ord' instance for 'Type'.
+cmpTypeModuloOpaquePtrs :: Ord ident => Type' ident -> Type' ident -> Ordering
+cmpTypeModuloOpaquePtrs x y = typeView x `compare` typeView y
+
+-- | Ensure that if there are any occurrences of opaque pointers, then all
+-- non-opaque pointers are converted to opaque ones.
+--
+-- This is useful because LLVM tools like @llvm-as@ are stricter than
+-- @llvm-pretty@ in that the former forbids mixing opaque and non-opaque
+-- pointers, whereas the latter allows this. As a result, the result of
+-- pretty-printing an @llvm-pretty@ AST might not be suitable for @llvm-as@'s
+-- needs unless you first call this function to ensure that the two types of
+-- pointers are not intermixed.
+--
+-- This is implemented using "Data.Data" combinators under the hood, which could
+-- potentially require a full traversal of the AST. Because of the performance
+-- implications of this, we do not call 'fixupOpaquePtrs' in @llvm-pretty@'s
+-- pretty-printer. If you wish to combine opaque and non-opaque pointers in your
+-- AST, the burden is on you to call this function before pretty-printing.
+fixupOpaquePtrs :: Data a => a -> a
+fixupOpaquePtrs m
+    | isJust (gfind isOpaquePtr m)
+    = everywhere (mkT opaquifyPtr) m
+    | otherwise
+    = m
+  where
+    isOpaquePtr :: Type -> Bool
+    isOpaquePtr PtrOpaque{} = True
+    isOpaquePtr _           = False
+
+    opaquifyPtr :: Type -> Type
+    opaquifyPtr (PtrTo _ addr) = PtrOpaque addr
+    opaquifyPtr t              = t
+
+    -- Find the first occurrence of a @b@ value within the @a@ value that
+    -- satisfies the predicate and return it with 'Just'. Return 'Nothing' if there
+    -- are no such occurrences.
+    gfind :: (Data a, Typeable b) => (b -> Bool) -> a -> Maybe b
+    gfind p = something (const Nothing `extQ` \x -> if p x then Just x else Nothing)
+
+-- Null Values -----------------------------------------------------------------
+
+data NullResult lab
+  = HasNull (Value' lab)
+  | ResolveNull Ident
+    deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+primTypeNull :: PrimType -> Value' lab
+primTypeNull (Integer 1)    = ValBool False
+primTypeNull (Integer _)    = ValInteger 0
+primTypeNull (FloatType ft) = floatTypeNull ft
+primTypeNull _              = ValZeroInit
+
+floatTypeNull :: FloatType -> Value' lab
+floatTypeNull Float    = ValFloat 0
+floatTypeNull Double   = ValDouble 0 -- XXX not sure about this
+floatTypeNull X86_fp80 = ValFP80 $ FP80_LongDouble 0 0
+floatTypeNull _        = error "must be a float type"
+
+typeNull :: Type -> NullResult lab
+typeNull (PrimType pt) = HasNull (primTypeNull pt)
+typeNull PtrTo{}       = HasNull ValNull
+typeNull PtrOpaque{}   = HasNull ValNull
+typeNull (Alias i)     = ResolveNull i
+typeNull _             = HasNull ValZeroInit
+
+-- Type Elimination ------------------------------------------------------------
+
+elimFunTy :: MonadPlus m => Type -> m (Type,[Type],Bool)
+elimFunTy (FunTy ret args va) = return (ret,args,va)
+elimFunTy _                   = mzero
+
+elimAlias :: MonadPlus m => Type -> m Ident
+elimAlias (Alias i) = return i
+elimAlias _         = mzero
+
+elimPtrTo :: MonadPlus m => Type -> m Type
+elimPtrTo (PtrTo ty _) = return ty
+elimPtrTo _            = mzero
+
+elimVector :: MonadPlus m => Type -> m (Word64,Type)
+elimVector (Vector n pty) = return (n,pty)
+elimVector _              = mzero
+
+elimArray :: MonadPlus m => Type -> m (Word64, Type)
+elimArray (Array n ety) = return (n, ety)
+elimArray _             = mzero
+
+elimFunPtr :: MonadPlus m => Type -> m (Type,[Type],Bool)
+elimFunPtr  = elimFunTy <=< elimPtrTo
+
+elimPrimType :: MonadPlus m => Type -> m PrimType
+elimPrimType (PrimType pt) = return pt
+elimPrimType _             = mzero
+
+elimFloatType :: MonadPlus m => PrimType -> m FloatType
+elimFloatType (FloatType ft) = return ft
+elimFloatType _              = mzero
+
+-- | Eliminator for array, pointer and vector types.
+elimSequentialType :: MonadPlus m => Type -> m Type
+elimSequentialType ty = case ty of
+  Array _ elTy -> return elTy
+  PtrTo elTy _ -> return elTy
+  Vector _ pty -> return pty
+  _            -> mzero
+
+
+-- Top-level Type Aliases ------------------------------------------------------
+
+data TypeDecl = TypeDecl
+  { typeName  :: Ident
+  , typeValue :: Type
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+
+-- Globals ---------------------------------------------------------------------
+
+data Global = Global
+  { globalSym      :: Symbol
+  , globalAttrs    :: GlobalAttrs
+  , globalType     :: Type
+  , globalValue    :: Maybe Value
+  , globalAlign    :: Maybe Align
+  , globalMetadata :: GlobalMdAttachments
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+addGlobal :: Global -> Module -> Module
+addGlobal g m = m { modGlobals = g : modGlobals m }
+
+data GlobalAttrs = GlobalAttrs
+  { gaLinkage    :: Maybe Linkage
+  , gaVisibility :: Maybe Visibility
+  , gaAddrSpace  :: AddrSpace
+  , gaConstant   :: Bool
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+emptyGlobalAttrs :: GlobalAttrs
+emptyGlobalAttrs  = GlobalAttrs
+  { gaLinkage    = Nothing
+  , gaVisibility = Nothing
+  , gaAddrSpace  = defaultAddrSpace
+  , gaConstant   = False
+  }
+
+
+-- Declarations ----------------------------------------------------------------
+
+data Declare = Declare
+  { decLinkage    :: Maybe Linkage
+  , decVisibility :: Maybe Visibility
+  , decRetType    :: Type
+  , decName       :: Symbol
+  , decArgs       :: [Type]
+  , decVarArgs    :: Bool
+  , decAttrs      :: [FunAttr]
+  , decComdat     :: Maybe String
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+-- | The function type of this declaration
+decFunType :: Declare -> Type
+decFunType Declare { .. } = PtrTo (FunTy decRetType decArgs decVarArgs) defaultAddrSpace
+
+
+-- Function Definitions --------------------------------------------------------
+
+data Define = Define
+  { defLinkage    :: Maybe Linkage
+  , defVisibility :: Maybe Visibility
+  , defRetType    :: Type
+  , defName       :: Symbol
+  , defArgs       :: [Typed Ident]
+  , defVarArgs    :: Bool
+  , defAttrs      :: [FunAttr]
+  , defSection    :: Maybe String
+  , defGC         :: Maybe GC
+  , defBody       :: [BasicBlock]
+  , defMetadata   :: FnMdAttachments
+  , defComdat     :: Maybe String
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+defFunType :: Define -> Type
+defFunType Define { .. } =
+  PtrTo (FunTy defRetType (map typedType defArgs) defVarArgs) defaultAddrSpace
+
+addDefine :: Define -> Module -> Module
+addDefine d m = m { modDefines = d : modDefines m }
+
+-- Function Attributes and attribute groups ------------------------------------
+
+
+data FunAttr
+   = AlignStack Int
+   | Alwaysinline
+   | Builtin
+   | Cold
+   | Convergent  -- ^ Introduced in LLVM 3.7
+   | InaccessibleMemOnly  -- ^ Introduced in LLVM 3.8
+   | Inlinehint
+   | Jumptable
+   | Minsize
+   | Naked
+   | Nobuiltin
+   | Noduplicate
+   | Noimplicitfloat
+   | Noinline
+   | Nonlazybind
+   | Noredzone
+   | Noreturn
+   | Nounwind
+   | Optnone
+   | Optsize
+   | Readnone
+   | Readonly
+   | ReturnsTwice
+   | SanitizeAddress
+   | SanitizeMemory
+   | SanitizeThread
+   | SSP
+   | SSPreq
+   | SSPstrong
+   | UWTable
+  deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+-- Basic Block Labels ----------------------------------------------------------
+
+data BlockLabel
+  = Named Ident
+  | Anon Int
+    deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+instance IsString BlockLabel where
+  fromString str = Named (fromString str)
+
+-- Basic Blocks ----------------------------------------------------------------
+
+data BasicBlock' lab = BasicBlock
+  { bbLabel :: Maybe lab
+  , bbStmts :: [Stmt' lab]
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type BasicBlock = BasicBlock' BlockLabel
+
+brTargets :: BasicBlock' lab -> [lab]
+brTargets (BasicBlock _ stmts) =
+  case stmtInstr (last stmts) of
+    Br _ t1 t2         -> [t1, t2]
+    Invoke _ _ _ to uw -> [to, uw]
+    Jump t             -> [t]
+    Switch _ l ls      -> l : map snd ls
+    IndirectBr _ ls    -> ls
+    _                  -> []
+
+-- Attributes ------------------------------------------------------------------
+
+-- | Symbol Linkage
+data Linkage
+  = Private
+  | LinkerPrivate
+  | LinkerPrivateWeak
+  | LinkerPrivateWeakDefAuto
+  | Internal
+  | AvailableExternally
+  | Linkonce
+  | Weak
+  | Common
+  | Appending
+  | ExternWeak
+  | LinkonceODR
+  | WeakODR
+  | External
+  | DLLImport
+  | DLLExport
+    deriving (Data, Eq, Enum, Generic, Ord, Show, Typeable)
+
+data Visibility = DefaultVisibility
+                | HiddenVisibility
+                | ProtectedVisibility
+    deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+newtype GC = GC
+  { getGC :: String
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+-- Typed Things ----------------------------------------------------------------
+
+data Typed a = Typed
+  { typedType  :: Type
+  , typedValue :: a
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+instance Foldable Typed where
+  foldMap f t = f (typedValue t)
+
+instance Traversable Typed where
+  sequenceA t = mk `fmap` typedValue t
+    where
+    mk b = t { typedValue = b }
+
+mapMTyped :: Monad m => (a -> m b) -> Typed a -> m (Typed b)
+mapMTyped f t = do
+  b <- f (typedValue t)
+  return t { typedValue = b }
+
+-- Instructions ----------------------------------------------------------------
+
+data ArithOp
+  = Add Bool Bool
+    {- ^ * Integral addition.
+         * First boolean flag: check for unsigned overflow.
+         * Second boolean flag: check for signed overflow.
+         * If the checks fail, then the result is poisoned. -}
+  | FAdd [FMF]
+    -- ^ Floating point addition.
+
+  | Sub Bool Bool
+    {- ^ * Integral subtraction.
+         * First boolean flag: check for unsigned overflow.
+         * Second boolean flag: check for signed overflow.
+         * If the checks fail, then the result is poisoned. -}
+
+  | FSub [FMF]
+    -- ^ Floating point subtraction.
+
+  | Mul Bool Bool
+    {- ^ * Integral multiplication.
+         * First boolean flag: check for unsigned overflow.
+         * Second boolean flag: check for signed overflow.
+         * If the checks fail, then the result is poisoned. -}
+
+  | FMul [FMF]
+    -- ^ Floating point multiplication.
+
+  | UDiv Bool
+    {- ^ * Integral unsigned division.
+         * Boolean flag: check for exact result.
+         * If the check fails, then the result is poisoned. -}
+
+  | SDiv Bool
+    {- ^ * Integral signed division.
+         * Boolean flag: check for exact result.
+         * If the check fails, then the result is poisoned. -}
+
+  | FDiv [FMF]
+    -- ^ Floating point division.
+
+  | URem
+    -- ^ Integral unsigned reminder resulting from unsigned division.
+    -- Division by 0 is undefined.
+
+  | SRem
+    -- ^ * Integral signded reminder resulting from signed division.
+    --   * The sign of the reminder matches the divident (first parameter).
+    --   * Division by 0 is undefined.
+
+  | FRem [FMF]
+    -- ^ * Floating point reminder resulting from floating point division.
+    --   * The reminder has the same sign as the divident (first parameter).
+
+    deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+isIArith :: ArithOp -> Bool
+isIArith Add{}  = True
+isIArith Sub{}  = True
+isIArith Mul{}  = True
+isIArith UDiv{} = True
+isIArith SDiv{} = True
+isIArith URem   = True
+isIArith SRem   = True
+isIArith _      = False
+
+isFArith :: ArithOp -> Bool
+isFArith  = not . isIArith
+
+data UnaryArithOp
+  = FNeg [FMF]
+    -- ^ Floating point negation.
+    deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+-- | Binary bitwise operators.
+data BitOp
+  = Shl Bool Bool
+    {- ^ * Shift left.
+         * First bool flag: check for unsigned overflow (i.e., shifted out a 1).
+         * Second bool flag: check for signed overflow
+              (i.e., shifted out something that does not match the sign bit)
+
+         If a check fails, then the result is poisoned.
+
+         The value of the second parameter must be strictly less than the
+           number of bits in the first parameter,
+           otherwise the result is undefined.  -}
+
+  | Lshr Bool
+    {- ^ * Logical shift right.
+         * The boolean is for exact check: poison the result,
+              if we shift out a 1 bit (i.e., had to round).
+
+    The value of the second parameter must be strictly less than the
+    number of bits in the first parameter, otherwise the result is undefined.
+    -}
+
+  | Ashr Bool
+    {- ^ * Arithmetic shift right.
+         * The boolean is for exact check: poison the result,
+                if we shift out a 1 bit (i.e., had to round).
+
+    The value of the second parameter must be strictly less than the
+    number of bits in the first parameter, otherwise the result is undefined.
+    -}
+
+  | And
+  | Or
+  | Xor
+    deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+-- | Conversions from one type to another.
+data ConvOp
+  = Trunc Bool Bool
+    -- ^ Truncate an integer value to a smaller integer type.
+    --
+    -- The 'Bool' fields (added in in LLVM 20) encode whether to perform
+    -- overflow-related checks:
+    --
+    -- * First 'Bool': check for unsigned overflow.
+    -- * Second 'Bool': check for signed overflow.
+    --
+    -- If the checks fail, then the result is poisoned.
+    --
+    -- These fields can only ever 'True' in 'Conv' instructions in LLVM 20 or
+    -- later. These fields are always 'False' in 'ConstConv' constant
+    -- expressions or if the LLVM version is older than 20.
+  | ZExt Bool
+    -- ^ Zero extension.
+    --
+    -- The 'Bool' field (added in LLVM 18) encodes whether to enforce that the
+    -- argument is non-negative. If the 'Bool' is 'True' and the argument is
+    -- negative, then the result is poisoned.
+    --
+    -- This field can only ever 'True' in 'Conv' instructions in LLVM 18 or
+    -- later. This field is always 'False' in 'ConstConv' constant expressions
+    -- or if the LLVM version is older than 18.
+  | SExt
+  | FpTrunc
+  | FpExt
+  | FpToUi
+  | FpToSi
+  | UiToFp Bool
+    -- ^ Convert the argument from an unsigned integer to a floating-point
+    -- value.
+    --
+    -- The 'Bool' field (added in LLVM 19) encodes whether to enforce that the
+    -- argument is non-negative. If the 'Bool' is 'True' and the argument is
+    -- negative, then the result is poisoned.
+    --
+    -- This field can only ever 'True' in 'Conv' instructions in LLVM 19 or
+    -- later. This field is always 'False' in 'ConstConv' constant expressions
+    -- or if the LLVM version is older than 19.
+  | SiToFp
+  | PtrToInt
+  | IntToPtr
+  | BitCast
+    deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+data AtomicRWOp
+  = AtomicXchg
+  | AtomicAdd
+  | AtomicSub
+  | AtomicAnd
+  | AtomicNand
+  | AtomicOr
+  | AtomicXor
+  | AtomicMax
+  | AtomicMin
+  | AtomicUMax
+  | AtomicUMin
+  | AtomicFAdd  -- ^ Introduced in LLVM 9
+  | AtomicFSub  -- ^ Introduced in LLVM 9
+  | AtomicFMax  -- ^ Introduced in LLVM 15
+  | AtomicFMin  -- ^ Introduced in LLVM 15
+  | AtomicUIncWrap  -- ^ Introduced in LLVM 16
+  | AtomicUDecWrap  -- ^ Introduced in LLVM 16
+    deriving (Data, Eq, Enum, Generic, Ord, Show, Typeable)
+
+data AtomicOrdering
+  = Unordered
+  | Monotonic
+  | Acquire
+  | Release
+  | AcqRel
+  | SeqCst
+    deriving (Data, Eq, Enum, Generic, Ord, Show, Typeable)
+
+type Align = Int
+
+data Instr' lab
+  = Ret (Typed (Value' lab))
+    {- ^ * Return from function with the given value.
+         * Ends basic block. -}
+
+  | RetVoid
+    {- ^ * Return from function.
+         * Ends basic block. -}
+
+  | Arith ArithOp (Typed (Value' lab)) (Value' lab)
+    {- ^ * Binary arithmetic operation, both operands have the same type.
+         * Middle of basic block.
+         * The result is the same as parameters. -}
+
+  | UnaryArith UnaryArithOp (Typed (Value' lab))
+    {- ^ * Unary arithmetic operation.
+         * Middle of basic block.
+         * The result is the same as the parameter. -}
+
+  | Bit BitOp (Typed (Value' lab)) (Value' lab)
+    {- ^ * Binary bit-vector operation, both operands have the same type.
+         * Middle of basic block.
+         * The result is the same as parameters. -}
+
+  | Conv ConvOp (Typed (Value' lab)) Type
+    {- ^ * Convert a value from one type to another.
+         * Middle of basic block.
+         * The result matches the 3rd parameter.
+         Note: fptrunc and fpext allow fast-math flags in LLVM, but this is not represented yet. -}
+
+  | Call Bool [FMF] Type (Value' lab) [Typed (Value' lab)]
+    {- ^ * Call a function.
+            The boolean is tail-call hint (XXX: needs to be updated)
+         * Middle of basic block.
+         * The result is as indicated by the provided type.
+         * Fast-math flags are only allowed if the type is a floating-point type; see LLVM documentation. -}
+
+  | CallBr Type (Value' lab) [Typed (Value' lab)] lab [lab]
+    {- ^ * Call a function in asm-goto style:
+             return type;
+             function operand;
+             arguments;
+             default basic block destination;
+             other basic block destinations.
+         * Middle of basic block.
+         * The result is as indicated by the provided type.
+         * Introduced in LLVM 9. -}
+
+  | Alloca Type (Maybe (Typed (Value' lab))) (Maybe Int)
+    {- ^ * Allocated space on the stack:
+           type of elements;
+           how many elements (1 if 'Nothing');
+           required alignment.
+         * Middle of basic block.
+         * Returns a pointer to hold the given number of elements. -}
+
+  | Load Bool Type (Typed (Value' lab)) (Maybe AtomicOrdering) (Maybe Align)
+    {- ^ * Read a value from the given address:
+           whether the load is volatile;
+           type being loaded;
+           address to read from;
+           atomic ordering;
+           assumptions about alignment of the given pointer.
+         * Middle of basic block.
+         * Returns a value of type matching the pointer. -}
+
+  | Store Bool (Typed (Value' lab)) (Typed (Value' lab)) (Maybe AtomicOrdering) (Maybe Align)
+    {- ^ * Write a value to memory:
+             whether the store is volatile;
+             value to store;
+             pointer to location where to store;
+             atomic ordering;
+             assumptions about the alignment of the given pointer.
+         * Middle of basic block.
+         * Effect. -}
+
+
+  | Fence (Maybe String) AtomicOrdering
+    {- ^ * Introduce a happens-before relationship between operations:
+             synchronization scope;
+             type of ordering.
+         * Middle of basic block. -}
+
+  | CmpXchg Bool Bool (Typed (Value' lab)) (Typed (Value' lab)) (Typed (Value' lab)) (Maybe String) AtomicOrdering AtomicOrdering
+    {- ^ * Atomically compare and maybe exchange values in memory:
+             whether the exchange is weak;
+             whether the exchange is volatile;
+             pointer to read;
+             value to compare it with;
+             new value to write if the two prior values are equal;
+             synchronization scope;
+             synchronization ordering on success;
+             synchronization ordering on failure.
+         * Returns a pair of the original value and whether an exchange occurred.
+         * Middle of basic block.
+         * Effect. -}
+
+  | AtomicRW Bool AtomicRWOp (Typed (Value' lab)) (Typed (Value' lab)) (Maybe String) AtomicOrdering
+    {- ^ * Perform an atomic load, operation, and store:
+             whether the operation is volatile;
+             operation to apply to the read value and the provided value;
+             pointer to read;
+             value to combine it with, using the given operation;
+             synchronization scope;
+             synchronization ordering.
+         * Returns the original value at the given location.
+         * Middle of basic block.
+         * Effect. -}
+
+  | ICmp Bool ICmpOp (Typed (Value' lab)) (Value' lab)
+    {- ^ * Compare two integral values.
+         * Middle of basic block.
+         * Returns a boolean value.
+         * The 'Bool' field (added in LLVM 20) encodes whether to enforce that
+           the arguments have the same sign. If the 'Bool' is 'True' and the
+           arguments have mismatched signs, then the result is poisoned. This
+           field is always 'False' if the LLVM version is older than 20. -}
+
+  | FCmp [FMF] FCmpOp (Typed (Value' lab)) (Value' lab)
+    {- ^ * Compare two floating point values.
+         * Middle of basic block.
+         * Returns a boolean value. -}
+
+  | Phi [FMF] Type [(Value' lab,lab)]
+    {- ^ * Join point for an SSA value: we get one value per predecessor
+           basic block.
+         * Middle of basic block.
+         * Returns a value of the specified type.
+         * Fast-math flags are only allowed if the type is a floating-point type; see LLVM documentation. -}
+
+  | GEP [GEPAttr] Type (Typed (Value' lab)) [Typed (Value' lab)]
+    {- ^ * "Get element pointer",
+            compute the address of a field in a structure:
+            inbounds check attr (value poisoned if this fails);
+            type to use as a basis for calculations;
+            pointer to parent structure;
+            path to a sub-component of a structure.
+         * Middle of basic block.
+         * Returns the address of the requested member.
+
+    It's recommended that the GEPAttr list should be normalized (i.e. only one of
+    each entry).
+
+    The types in path are the types of the index, not the fields.
+
+    The indexes are in units of fields (i.e., the first element in
+    a struct is field 0, the next one is 1, etc., regardless of the size
+    of the fields in bytes). -}
+
+  | Select [FMF] (Typed (Value' lab)) (Typed (Value' lab)) (Value' lab)
+    {- ^ * Local if-then-else; the first argument is boolean, if
+           true pick the 2nd argument, otherwise evaluate to the 3rd.
+         * Middle of basic block.
+         * Returns either the 2nd or the 3rd argument.
+         * Fast-math flags are only allowed if the type is a floating-point type; see LLVM documentation.-}
+
+  | ExtractValue (Typed (Value' lab)) [Int32]
+    {- ^ * Get the value of a member of an aggregate value:
+           the first argument is an aggregate value (not a pointer!),
+           the second is a path of indexes, similar to the one in 'GEP'.
+         * Middle of basic block.
+         * Returns the given member of the aggregate value. -}
+
+  | InsertValue (Typed (Value' lab)) (Typed (Value' lab)) [Int32]
+    {- ^ * Set the value for a member of an aggregate value:
+           the first argument is the value to insert, the second is the
+           aggreagate value to be modified.
+         * Middle of basic block.
+         * Returns an updated aggregate value. -}
+
+  | ExtractElt (Typed (Value' lab)) (Value' lab)
+    {- ^ * Get an element from a vector: the first argument is a vector,
+           the second an index.
+         * Middle of basic block.
+         * Returns the element at the given position. -}
+
+  | InsertElt (Typed (Value' lab)) (Typed (Value' lab)) (Value' lab)
+    {- ^ * Modify an element of a vector: the first argument is the vector,
+           the second the value to be inserted, the third is the index where
+           to insert the value.
+         * Middle of basic block.
+         * Returns an updated vector. -}
+
+
+  | ShuffleVector (Typed (Value' lab)) (Value' lab) (Typed (Value' lab))
+    {- ^ * Constructs a fixed permutation of two input vectors: the first
+           and second arguments are input vectors, and the third argument
+           is the mask.
+         * Middle of basic block.
+         * Returns the permuted vector. For each element, the mask selects
+           an element from one of the input vectors to copy to the result:
+            * non-negative mask values represent an index into the concatenated
+              pair of input vectors, and
+            * -1 mask value indicates that the output element is poison.
+    -}
+
+  | Jump lab
+    {- ^ * Jump to the given basic block.
+         * Ends basic block. -}
+
+  | Br (Typed (Value' lab)) lab lab
+    {- ^ * Conditional jump: if the value is true jump to the first basic
+           block, otherwise jump to the second.
+         * Ends basic block. -}
+
+  | Invoke Type (Value' lab) [Typed (Value' lab)] lab lab
+    {- ^ * Calls the specified target function, then branches to the success
+           label.  If an exception occurs during the call, the exception unwind
+           handling branches to the second label.
+         * Arguments:
+           1. The function's return type
+           2. The function target itself (to be called)
+           3. arguments to the function
+           4. successful return target label
+           5. on-exception unwind target label
+         * Ends basic block. -}
+
+  | Comment String
+    -- ^ Comment
+
+  | Unreachable
+    -- ^ No defined sematics, we should not get to here.
+
+  | Unwind
+  | VaArg (Typed (Value' lab)) Type
+    -- ^ Accesses arguments passed through \"varargs\" areas of a function call.
+    -- The argument is a @va_list*@; this instruction returns the value of the
+    -- specified type located at the target and increments the pointer.
+
+  | IndirectBr (Typed (Value' lab)) [lab]
+    -- ^ Branch via pointer indirection.  The argument is the address of the
+    -- label to jump to.  (All) Possible destination targets are provided.
+
+  | Switch (Typed (Value' lab)) lab [(Integer,lab)]
+    {- ^ * Multi-way branch: the first value determines the target index
+           for the jump, which is looked up in the third argument table
+           (key values are unique).  The second argument is the default
+           destination if the target is not found in the table.
+         * Ends basic block. -}
+
+  | LandingPad Type (Maybe (Typed (Value' lab))) Bool [Clause' lab]
+    {- ^ Target of an exception (from the 'Invoke' instruction).
+         * Arguments:
+           1. The result type (the values set by the personality function
+              on re-entry to the function).
+           2. The second argument may be the personality function, which defines
+              values on re-entry. This is used in older LLVM versions and is
+              not supplied for recent LLVM versions.
+           3. True if this block is a "cleanup".
+           4. The list of clauses to handle the exception;  the clauses are
+              used to match the exception thrown.
+         * If no clause matches and cleanup not set, continue unwinding up
+           the stack (see 'Resume').
+         * If cleanup is false, there must be at least one clause
+     -}
+
+  | Resume (Typed (Value' lab))
+    {- ^ Resumes propagation of an in-flight exception whose unwinding was
+         interrupted by a 'LandingPad' instruction.
+         * Argument: the value of the exception to propagate.
+    -}
+
+  | Freeze (Typed (Value' lab))
+    {- ^ * Used to stop propagation of @undef@ and @poison@ values.
+         * If the argument is @undef@ or @poison@, returns an arbitrary
+           (but fixed) value of that type instead, otherwise a no-op and
+           returns its argument.
+         * Middle of basic block. -}
+
+    deriving (Data, Eq, Functor, Generic, Ord, Show, Typeable)
+
+type Instr = Instr' BlockLabel
+
+data Clause' lab
+  = Catch  (Typed (Value' lab))
+  | Filter (Typed (Value' lab))
+    deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type Clause = Clause' BlockLabel
+
+
+isTerminator :: Instr' lab -> Bool
+isTerminator instr = case instr of
+  Ret{}        -> True
+  RetVoid      -> True
+  Jump{}       -> True
+  CallBr{}     -> True
+  Br{}         -> True
+  Unreachable  -> True
+  Unwind       -> True
+  Invoke{}     -> True
+  IndirectBr{} -> True
+  Switch{}     -> True
+  Resume{}     -> True
+  _            -> False
+
+isComment :: Instr' lab -> Bool
+isComment Comment{} = True
+isComment _         = False
+
+isPhi :: Instr' lab -> Bool
+isPhi Phi{} = True
+isPhi _     = False
+
+-- | Integer comparison operators.
+data ICmpOp = Ieq | Ine | Iugt | Iuge | Iult | Iule | Isgt | Isge | Islt | Isle
+    deriving (Data, Eq, Enum, Generic, Ord, Show, Typeable)
+
+-- | Floating-point comparison operators.
+data FCmpOp = Ffalse  | Foeq | Fogt | Foge | Folt | Fole | Fone
+            | Ford    | Fueq | Fugt | Fuge | Fult | Fule | Fune
+            | Funo    | Ftrue
+    deriving (Data, Eq, Enum, Generic, Ord, Show, Typeable)
+
+-- | Fast-math flags. <https://llvm.org/docs/LangRef.html#fast-math-flags>
+data FMF
+  = Ffast  -- ^ Short-hand for all other flags combined
+  | Fnnan  -- ^ No NaNs
+  | Fninf  -- ^ No infs
+  | Fnsz  -- ^ No signed zeros
+  | Farcp  -- ^ Allow reciprocal (a / b == a * (1.0 / b))
+  | Fcontract  -- ^ Allow contraction (e.g. fused multiply-add)
+  | Fafn  -- ^ Approximate functions (for sqrt, sin, log, etc.)
+  | Freassoc  -- ^ Reassociation (important for e.g. vectorisation of horizontal sums)
+    deriving (Data, Eq, Enum, Generic, Ord, Show, Typeable)
+
+
+-- Debug Instructions ----------------------------------------------------------
+
+-- | Debug Instructions
+--
+-- In LLVM 19, debug instructions were added as a replacement for the intrinsic
+-- functions previously used.  This addition is described in
+-- llvm-project/llvm/docs/RemoveDIsDebugInfo.md in the LLVM repository.
+data DebugRecord' lab
+  = DebugRecordValue (DbgRecValue' lab)
+  | DebugRecordDeclare (DbgRecDeclare' lab)
+  | DebugRecordAssign (DbgRecAssign' lab)
+  | DebugRecordValueSimple (DbgRecValueSimple' lab)
+  | DebugRecordLabel (DbgRecLabel' lab)
+  deriving (Data, Eq, Functor, Generic, Ord, Show, Typeable)
+
+type DebugRecord = DebugRecord' BlockLabel
+
+data DbgRecValue' lab = DbgRecValue
+  {
+    drvLocation :: ValMd' lab -- ^ Expected to be a DILocation
+  , drvLocalVariable :: ValMd' lab -- ^ Expected to be a DILocalVariable
+  , drvExpression :: ValMd' lab -- ^ Expected to be a DIExpression
+  , drvValAsMetadata :: ValMd' lab
+  }
+  deriving (Data, Eq, Functor, Generic, Ord, Show, Typeable)
+
+type DbgRecValue = DbgRecValue' BlockLabel
+
+data DbgRecValueSimple' lab = DbgRecValueSimple
+  {
+    drvsLocation :: ValMd' lab -- ^ Expected to be a DILocation
+  , drvsLocalVariable :: ValMd' lab -- ^ Expected to be a DILocalVariable
+  , drvsExpression :: ValMd' lab -- ^ Expected to be a DIExpression
+  , drvsValue :: Typed (Value' lab)
+  }
+  deriving (Data, Eq, Functor, Generic, Ord, Show, Typeable)
+
+type DbgRecValueSimple = DbgRecValueSimple' BlockLabel
+
+data DbgRecDeclare' lab = DbgRecDeclare
+  {
+    drdLocation :: ValMd' lab -- ^ Expected to be a DILocation
+  , drdLocalVariable :: ValMd' lab -- ^ Expected to be a DILocalVariable
+  , drdExpression :: ValMd' lab -- ^ Expected to be a DIExpression
+  , drdValAsMetadata :: ValMd' lab
+  }
+  deriving (Data, Eq, Functor, Generic, Ord, Show, Typeable)
+
+type DbgRecDeclare = DbgRecDeclare' BlockLabel
+
+data DbgRecAssign' lab = DbgRecAssign
+  {
+    draLocation :: ValMd' lab -- ^ Expected to be a DILocation
+  , draLocalVariable :: ValMd' lab -- ^ Expected to be a DILocalVariable
+  , draExpression :: ValMd' lab -- ^ Expected to be a DIExpression
+  , draValAsMetadata :: ValMd' lab
+  , draAssignID :: ValMd' lab -- ^ Expected to be a DIAssignID
+  , draExpressionAddr :: ValMd' lab -- ^ Expected to be a DIExpression
+  , draValAsMetadataAddr :: ValMd' lab
+  }
+  deriving (Data, Eq, Functor, Generic, Ord, Show, Typeable)
+
+type DbgRecAssign = DbgRecAssign' BlockLabel
+
+data DbgRecLabel' lab = DbgRecLabel
+  {
+    drlLocation :: ValMd' lab -- ^ Expected to be a DILocation
+  , drlLabel :: ValMd' lab -- ^ Expected to be a DILabel
+  }
+  deriving (Data, Eq, Functor, Generic, Ord, Show, Typeable)
+
+type DbgRecLabel = DbgRecLabel' BlockLabel
+
+
+-- Values ----------------------------------------------------------------------
+
+data Value' lab
+  = ValInteger Integer
+  | ValBool Bool
+  | ValFloat Float
+  | ValDouble Double
+  | ValFP80 FP80Value
+  | ValIdent Ident
+  | ValSymbol Symbol
+  | ValNull
+  | ValArray Type [Value' lab]
+  | ValVector Type [Value' lab]
+  | ValStruct [Typed (Value' lab)]
+  | ValPackedStruct [Typed (Value' lab)]
+  | ValString [Word8]
+  | ValConstExpr (ConstExpr' lab)
+  | ValUndef
+  | ValLabel lab
+  | ValZeroInit
+  | ValAsm Bool Bool String String
+  | ValMd (ValMd' lab)
+  | ValPoison
+    deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type Value = Value' BlockLabel
+
+data FP80Value = FP80_LongDouble Word16 Word64
+               deriving (Data, Eq, Ord, Generic, Show, Typeable)
+
+data ValMd' lab
+  = ValMdString String
+  | ValMdValue (Typed (Value' lab))
+  | ValMdRef Int
+  | ValMdNode [Maybe (ValMd' lab)]
+  | ValMdLoc (DebugLoc' lab)
+  | ValMdDebugInfo (DebugInfo' lab)
+    deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type ValMd = ValMd' BlockLabel
+
+type KindMd = String
+type FnMdAttachments = Map.Map KindMd ValMd
+type GlobalMdAttachments = Map.Map KindMd ValMd
+
+data DebugLoc' lab = DebugLoc
+  { dlLine  :: Word32
+  , dlCol   :: Word32
+  , dlScope :: ValMd' lab
+  , dlIA    :: Maybe (ValMd' lab)
+  , dlImplicit :: Bool
+  , dlAtomGroup :: Word64 -- ^ Introduced in LLVM 21
+  , dlAtomRank :: Word64 -- ^ Introduced in LLVM 21
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DebugLoc = DebugLoc' BlockLabel
+
+isConst :: Value' lab -> Bool
+isConst ValInteger{}   = True
+isConst ValBool{}      = True
+isConst ValFloat{}     = True
+isConst ValDouble{}    = True
+isConst ValFP80{}      = True
+isConst ValConstExpr{} = True
+isConst ValZeroInit    = True
+isConst ValNull        = True
+isConst _              = False
+
+-- Value Elimination -----------------------------------------------------------
+
+elimValSymbol :: MonadPlus m => Value' lab -> m Symbol
+elimValSymbol (ValSymbol sym) = return sym
+elimValSymbol _               = mzero
+
+elimValInteger :: MonadPlus m => Value' lab -> m Integer
+elimValInteger (ValInteger i) = return i
+elimValInteger _              = mzero
+
+-- Statements ------------------------------------------------------------------
+
+-- | Each statement, which can return a value (`Result`) referenced by the
+-- `Ident` or else it has no return value (`Effect`).  The statement has a single
+-- Instruction, followed by any Debug Records or associated metadata.
+--
+-- See llvm-project/llvm/docs/RemoveDIsDebugInfo.md for discussion on the
+-- [DebugRecord] fields.  Note that DebugRecords and debug intrinsics may not be
+-- mixed in a module; the former is new and preferred over the latter.
+--
+-- Technically, DebugRecords are attached to Instructions, but since there's a
+-- 1:1 correspondence between Stmt and Instr, it is cleaner to attach the
+-- DebugRecords to the Stmt to keep the Instrs from getting additional
+-- complications.
+--
+-- Each statement may have both Debug Records (2nd-to-last field) and a list of
+-- metadata attributes (last field).  As noted above, bitcode file should not mix
+-- Debug Records and intrinsics; if Debug Records are used, the metadata
+-- attribute list should not contain intrinsics (although it may contain other
+-- metadata associated with this statement).
+
+data Stmt' lab
+  = Result Ident (Instr' lab) [DebugRecord' lab] [(String, ValMd' lab)]
+  | Effect (Instr' lab) [DebugRecord' lab] [(String, ValMd' lab)]
+    deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type Stmt = Stmt' BlockLabel
+
+stmtMetadata :: Stmt' lab -> [(String, ValMd' lab)]
+stmtMetadata = \case
+  Result _ _ _ mds -> mds
+  Effect _ _ mds   -> mds
+
+stmtInstr :: Stmt' lab -> Instr' lab
+stmtInstr (Result _ i _ _) = i
+stmtInstr (Effect i _ _)   = i
+
+extendMetadata :: Show lab => (String, ValMd' lab) -> Stmt' lab -> Stmt' lab
+extendMetadata md stmt = case stmt of
+  Result r i [] mds -> Result r i [] (md:mds)
+  Result _ _ _ _ -> error $ "Adding MD " <> show md <> " after DebugRecord"
+  Effect i drs mds   -> Effect i drs (md:mds)
+
+addDebugRecord :: DebugRecord' lab -> Stmt' lab -> Stmt' lab
+addDebugRecord dr = \case
+  Result r i drs mds -> Result r i (snoc dr drs) mds
+  Effect i drs mds   -> Effect i (snoc dr drs) mds
+  where
+    snoc e ls = ls <> [e]
+
+-- Constant Expressions --------------------------------------------------------
+
+data ConstExpr' lab
+  = ConstGEP [GEPAttr] (Maybe RangeSpec) Type (Typed (Value' lab)) [Typed (Value' lab)]
+  -- ^ Since LLVM 3.7, constant @getelementptr@ expressions include an explicit
+  -- type to use as a basis for calculations. For older versions of LLVM, this
+  -- type can be reconstructed by inspecting the pointee type of the parent
+  -- pointer value.
+  --
+  -- Since LLVM 19, the bool "inbounds" is now [GEPAttr] and range is via
+  -- RangeSpec instead of just Word64.  It's recommended that the GEPAttr list
+  -- should be normalized (i.e. only one of each entry).
+  | ConstConv ConvOp (Typed (Value' lab)) Type
+  | ConstSelect (Typed (Value' lab)) (Typed (Value' lab)) (Typed (Value' lab))
+  | ConstBlockAddr (Typed (Value' lab)) lab
+  | ConstFCmp FCmpOp (Typed (Value' lab)) (Typed (Value' lab))
+  | ConstICmp ICmpOp (Typed (Value' lab)) (Typed (Value' lab))
+  | ConstArith ArithOp (Typed (Value' lab)) (Value' lab)
+  | ConstUnaryArith UnaryArithOp (Typed (Value' lab))
+  | ConstBit BitOp (Typed (Value' lab)) (Value' lab)
+    deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type ConstExpr = ConstExpr' BlockLabel
+
+-- | Attributes imposing rules on the GEP; violating any rule results in a poison
+-- value.  If the base is a vector of pointers, the attributes apply to each
+-- computation element-wise.  See
+-- https://llvm.org/docs/LangRef.html#getelementptr-instruction for more
+-- information.
+data GEPAttr
+  = GEP_Inbounds
+    -- ^ Rules:
+    -- * Base pointer has an inbounds (but not necessarily live) address of the
+    --   allocated object it is based on (i.e. points into that allocation or to
+    --   its end.  Size for a growable allocated object is the max size, not the
+    --   current size.
+    -- * Pointer must remain inbounds at all times when adding the offsets
+    -- * Implies 'GEP_NUSW'
+  | GEP_NUSW
+    -- ^ No unsigned signed wrap.
+    -- Rules:
+    -- * If type of index is larger than ptr index type, truncation preserves
+    --   the signed value.
+    -- * Multiplication of an index by the type size does not wrap in a
+    --   signed sense.
+    -- * Offset additions (excluding base address) does not wrap in a
+    --   signed sense
+    -- * Addition of the current address (as unsigned, truncated to ptr
+    --   index type) and each offset (as signed) does not wrap the ptr
+    --   index type.
+  | GEP_NUW
+    -- ^ No unsigned wrap
+    -- Rules:
+    -- * If type of index is larger than ptr index type, truncation preserves
+    --   the unsigned value.
+    -- * Multiplication of an index by the type size does not wrap in an
+    --   unsigned sense.
+    -- * Offset additions (excluding base address) does not wrap in an
+    --   unsigned sense
+    -- * Addition of the current address (as unsigned, truncated to ptr
+    --   index type) and each offset (as unsigned) does not wrap the ptr
+    --   index type.
+    deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+orderedGEPAttrs :: [GEPAttr]
+orderedGEPAttrs = [GEP_Inbounds, GEP_NUSW, GEP_NUW] -- bit0, bit1, ...
+
+data RangeSpec
+  = RangeIndex Word64
+    -- ^ index of valid range as used in pre-LLVM19 for when "inbounds" as a
+    -- boolean was True.  Deprecated.
+  | Range Int Integer Integer
+    -- ^ width of arbitrary-precision integer (in bits) and lower and upper
+    -- arbitrary-precision integer bounds of that size as [lower, upper).
+  deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+
+-- DWARF Debug Info ------------------------------------------------------------
+
+data DebugInfo' lab
+  = DebugInfoBasicType (DIBasicType' lab)
+  | DebugInfoCompileUnit (DICompileUnit' lab)
+  | DebugInfoCompositeType (DICompositeType' lab)
+  | DebugInfoDerivedType (DIDerivedType' lab)
+  | DebugInfoEnumerator String !Integer Bool
+    -- ^ The 'Bool' field represents @isUnsigned@, introduced in LLVM 7.
+  | DebugInfoExpression DIExpression
+  | DebugInfoFile DIFile
+  | DebugInfoGlobalVariable (DIGlobalVariable' lab)
+  | DebugInfoGlobalVariableExpression (DIGlobalVariableExpression' lab)
+  | DebugInfoLexicalBlock (DILexicalBlock' lab)
+  | DebugInfoLexicalBlockFile (DILexicalBlockFile' lab)
+  | DebugInfoLocalVariable (DILocalVariable' lab)
+  | DebugInfoSubprogram (DISubprogram' lab)
+  | DebugInfoSubrange (DISubrange' lab)
+  | DebugInfoSubroutineType (DISubroutineType' lab)
+  | DebugInfoNameSpace (DINameSpace' lab)
+  | DebugInfoTemplateTypeParameter (DITemplateTypeParameter' lab)
+  | DebugInfoTemplateValueParameter (DITemplateValueParameter' lab)
+  | DebugInfoImportedEntity (DIImportedEntity' lab)
+  | DebugInfoLabel (DILabel' lab)
+  | DebugInfoArgList (DIArgList' lab)
+  | DebugInfoAssignID -- ^ Introduced in LLVM 17.
+    deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DebugInfo = DebugInfo' BlockLabel
+
+type DILabel = DILabel' BlockLabel
+data DILabel' lab = DILabel
+    { dilScope :: Maybe (ValMd' lab)
+    , dilName  :: String
+    , dilFile  :: Maybe (ValMd' lab)
+    , dilLine  :: Word32
+    , dilColumn :: Word32 -- ^ Introduced in LLVM 21.
+    , dilIsArtificial :: Bool -- ^ Introduced in LLVM 21.
+    , dilCoroSuspendIdx :: Maybe Word32 -- ^ Introduced in LLVM 21.
+    } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DIImportedEntity = DIImportedEntity' BlockLabel
+data DIImportedEntity' lab = DIImportedEntity
+    { diieTag    :: DwarfTag
+    , diieScope  :: Maybe (ValMd' lab)
+    , diieEntity :: Maybe (ValMd' lab)
+    , diieFile   :: Maybe (ValMd' lab)
+    , diieLine   :: Word32
+    , diieName   :: Maybe String
+    } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DITemplateTypeParameter = DITemplateTypeParameter' BlockLabel
+data DITemplateTypeParameter' lab = DITemplateTypeParameter
+    { dittpName      :: Maybe String
+    , dittpType      :: Maybe (ValMd' lab)
+    , dittpIsDefault :: Maybe Bool         -- since LLVM 11
+    } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DITemplateValueParameter = DITemplateValueParameter' BlockLabel
+data DITemplateValueParameter' lab = DITemplateValueParameter
+    { ditvpTag       :: DwarfTag
+    , ditvpName      :: Maybe String
+    , ditvpType      :: Maybe (ValMd' lab)
+    , ditvpIsDefault :: Maybe Bool         -- since LLVM 11
+    , ditvpValue     :: ValMd' lab
+    } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DINameSpace = DINameSpace' BlockLabel
+data DINameSpace' lab = DINameSpace
+    { dinsName  :: Maybe String
+    , dinsScope :: ValMd' lab
+    , dinsFile  :: ValMd' lab
+    , dinsLine  :: Word32
+    } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+-- TODO: Turn these into sum types
+-- See https://github.com/llvm-mirror/llvm/blob/release_38/include/llvm/Support/Dwarf.def
+type DwarfAttrEncoding = Word16
+type DwarfLang = Word16
+type DwarfTag = Word16
+type DwarfVirtuality = Word8
+-- See https://github.com/llvm-mirror/llvm/blob/release_38/include/llvm/IR/DebugInfoMetadata.h#L175
+type DIFlags = Word32
+-- This seems to be defined internally as a small enum, and defined
+-- differently across versions. Maybe turn this into a sum type once
+-- it stabilizes.
+type DIEmissionKind = Word8
+
+-- See https://github.com/llvm/llvm-project/commit/eb8901bda11fd55deeecd067fc4c9dcc0fb89984
+dwarf_DW_APPLE_ENUM_KIND_invalid :: Word32
+dwarf_DW_APPLE_ENUM_KIND_invalid = complement (0 :: Word32) -- ~ LLVM 19
+
+data DIBasicType' lab = DIBasicType
+  { dibtTag      :: DwarfTag
+  , dibtName     :: String
+  , dibtSize     :: Maybe (ValMd' lab)
+    -- ^ If using LLVM 20 or older, this will always be @Just@ an 'ValMdValue',
+    -- where the underlying value is a 64-bit 'ValInteger'. If using LLVM 21 or
+    -- later, this can also be a null reference (i.e., 'Nothing'), a variable
+    -- (i.e., @Just@ a 'DIGlobalVariable' or 'DILocalVariable'), or an
+    -- expression (i.e., @Just@ a 'DIExpression').
+  , dibtAlign    :: Word64
+  , dibtEncoding :: DwarfAttrEncoding
+  , dibtFlags    :: Maybe DIFlags
+  , dibtNumExtraInhabitants :: Word64 -- ^ added in LLVM 20.
+  } deriving (Data, Eq, Functor, Generic, Ord, Show, Typeable)
+
+type DIBasicType = DIBasicType' BlockLabel
+
+data DICompileUnit' lab = DICompileUnit
+  { dicuLanguage           :: DwarfLang
+  , dicuFile               :: Maybe (ValMd' lab)
+  , dicuProducer           :: Maybe String
+  , dicuIsOptimized        :: Bool
+  , dicuFlags              :: Maybe String
+  , dicuRuntimeVersion     :: Word16
+  , dicuSplitDebugFilename :: Maybe FilePath
+  , dicuEmissionKind       :: DIEmissionKind
+  , dicuEnums              :: Maybe (ValMd' lab)
+  , dicuRetainedTypes      :: Maybe (ValMd' lab)
+  , dicuSubprograms        :: Maybe (ValMd' lab)
+  , dicuGlobals            :: Maybe (ValMd' lab)
+  , dicuImports            :: Maybe (ValMd' lab)
+  , dicuMacros             :: Maybe (ValMd' lab)
+  , dicuDWOId              :: Word64
+  , dicuSplitDebugInlining :: Bool
+  , dicuDebugInfoForProf   :: Bool
+  , dicuNameTableKind      :: Word64
+    -- added in LLVM 11: dicuRangesBaseAddress, dicuSysRoot, and dicuSDK
+  , dicuRangesBaseAddress  :: Bool
+  , dicuSysRoot            :: Maybe String
+  , dicuSDK                :: Maybe String
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DICompileUnit = DICompileUnit' BlockLabel
+
+data DICompositeType' lab = DICompositeType
+  { dictTag            :: DwarfTag
+  , dictName           :: Maybe String
+  , dictFile           :: Maybe (ValMd' lab)
+  , dictLine           :: Word32
+  , dictScope          :: Maybe (ValMd' lab)
+  , dictBaseType       :: Maybe (ValMd' lab)
+  , dictSize           :: Maybe (ValMd' lab)
+    -- ^ If using LLVM 20 or older, this will always be @Just@ an 'ValMdValue',
+    -- where the underlying value is a 64-bit 'ValInteger'. If using LLVM 21 or
+    -- later, this can also be a null reference (i.e., 'Nothing'), a variable
+    -- (i.e., @Just@ a 'DIGlobalVariable' or 'DILocalVariable'), or an
+    -- expression (i.e., @Just@ a 'DIExpression').
+  , dictAlign          :: Word64
+  , dictOffset         :: Maybe (ValMd' lab)
+    -- ^ If using LLVM 20 or older, this will always be @Just@ an 'ValMdValue',
+    -- where the underlying value is a 64-bit 'ValInteger'. If using LLVM 21 or
+    -- later, this can also be a null reference (i.e., 'Nothing'), a variable
+    -- (i.e., @Just@ a 'DIGlobalVariable' or 'DILocalVariable'), or an
+    -- expression (i.e., @Just@ a 'DIExpression').
+  , dictFlags          :: DIFlags
+  , dictElements       :: Maybe (ValMd' lab)
+  , dictRuntimeLang    :: DwarfLang
+  , dictVTableHolder   :: Maybe (ValMd' lab)
+  , dictTemplateParams :: Maybe (ValMd' lab)
+  , dictIdentifier     :: Maybe String
+  , dictDiscriminator  :: Maybe (ValMd' lab)
+  , dictDataLocation   :: Maybe (ValMd' lab)
+  , dictAssociated     :: Maybe (ValMd' lab)
+  , dictAllocated      :: Maybe (ValMd' lab)
+  , dictRank           :: Maybe (ValMd' lab)
+  , dictAnnotations    :: Maybe (ValMd' lab) -- ^ Introduced in LLVM 14.
+  , dictNumExtraInhabitants :: Word64        -- ^ added in LLVM 20.
+  , dictSpecification  :: Maybe (ValMd' lab) -- ^ added in LLVM 20.
+  , dictEnumKind       :: Maybe Word32       -- ^ added in LLVM 20.
+  , dictBitStride      :: Maybe (ValMd' lab) -- ^ added in LLVM 20.
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DICompositeType = DICompositeType' BlockLabel
+
+data DIDerivedType' lab = DIDerivedType
+  { didtTag :: DwarfTag
+  , didtName :: Maybe String
+  , didtFile :: Maybe (ValMd' lab)
+  , didtLine :: Word32
+  , didtScope :: Maybe (ValMd' lab)
+  , didtBaseType :: Maybe (ValMd' lab)
+  , didtSize :: Maybe (ValMd' lab)
+    -- ^ If using LLVM 20 or older, this will always be @Just@ an 'ValMdValue',
+    -- where the underlying value is a 64-bit 'ValInteger'. If using LLVM 21 or
+    -- later, this can also be a null reference (i.e., 'Nothing'), a variable
+    -- (i.e., @Just@ a 'DIGlobalVariable' or 'DILocalVariable'), or an
+    -- expression (i.e., @Just@ a 'DIExpression').
+  , didtAlign :: Word64
+  , didtOffset :: Maybe (ValMd' lab)
+    -- ^ If using LLVM 20 or older, this will always be @Just@ an 'ValMdValue',
+    -- where the underlying value is a 64-bit 'ValInteger'. If using LLVM 21 or
+    -- later, this can also be a null reference (i.e., 'Nothing'), a variable
+    -- (i.e., @Just@ a 'DIGlobalVariable' or 'DILocalVariable'), or an
+    -- expression (i.e., @Just@ a 'DIExpression').
+  , didtFlags :: DIFlags
+  , didtExtraData :: Maybe (ValMd' lab)
+  , didtDwarfAddressSpace :: Maybe Word32
+  -- ^ Introduced in LLVM 5.
+  --
+  -- The 'Maybe' encodes the possibility that there is no associated address
+  -- space (in LLVM, the sentinel value @0@ is used for this).
+  , didtAnnotations :: Maybe (ValMd' lab)
+  -- ^ Introduced in LLVM 14
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DIDerivedType = DIDerivedType' BlockLabel
+
+data DIExpression = DIExpression
+  { dieElements :: [Word64]
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+data DIFile = DIFile
+  { difFilename  :: FilePath
+  , difDirectory :: FilePath
+  } deriving (Data, Eq, Generic, Ord, Show, Typeable)
+
+data DIGlobalVariable' lab = DIGlobalVariable
+  { digvScope                :: Maybe (ValMd' lab)
+  , digvName                 :: Maybe String
+  , digvLinkageName          :: Maybe String
+  , digvFile                 :: Maybe (ValMd' lab)
+  , digvLine                 :: Word32
+  , digvType                 :: Maybe (ValMd' lab)
+  , digvIsLocal              :: Bool
+  , digvIsDefinition         :: Bool
+  , digvVariable             :: Maybe (ValMd' lab)
+  , digvDeclaration          :: Maybe (ValMd' lab)
+  , digvAlignment            :: Maybe Word32
+  , digvAnnotations          :: Maybe (ValMd' lab)
+    -- ^ Introduced in LLVM 14.
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DIGlobalVariable = DIGlobalVariable' BlockLabel
+
+data DIGlobalVariableExpression' lab = DIGlobalVariableExpression
+  { digveVariable   :: Maybe (ValMd' lab)
+  , digveExpression :: Maybe (ValMd' lab)
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DIGlobalVariableExpression = DIGlobalVariableExpression' BlockLabel
+
+data DILexicalBlock' lab = DILexicalBlock
+  { dilbScope  :: Maybe (ValMd' lab)
+  , dilbFile   :: Maybe (ValMd' lab)
+  , dilbLine   :: Word32
+  , dilbColumn :: Word16
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DILexicalBlock = DILexicalBlock' BlockLabel
+
+data DILexicalBlockFile' lab = DILexicalBlockFile
+  { dilbfScope         :: ValMd' lab
+  , dilbfFile          :: Maybe (ValMd' lab)
+  , dilbfDiscriminator :: Word32
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DILexicalBlockFile = DILexicalBlockFile' BlockLabel
+
+data DILocalVariable' lab = DILocalVariable
+  { dilvScope :: Maybe (ValMd' lab)
+  , dilvName :: Maybe String
+  , dilvFile :: Maybe (ValMd' lab)
+  , dilvLine :: Word32
+  , dilvType :: Maybe (ValMd' lab)
+  , dilvArg :: Word16
+  , dilvFlags :: DIFlags
+  , dilvAlignment :: Maybe Word32
+    -- ^ Introduced in LLVM 4.
+  , dilvAnnotations :: Maybe (ValMd' lab)
+    -- ^ Introduced in LLVM 14.
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DILocalVariable = DILocalVariable' BlockLabel
+
+data DISubprogram' lab = DISubprogram
+  { dispScope          :: Maybe (ValMd' lab)
+  , dispName           :: Maybe String
+  , dispLinkageName    :: Maybe String
+  , dispFile           :: Maybe (ValMd' lab)
+  , dispLine           :: Word32
+  , dispType           :: Maybe (ValMd' lab)
+  , dispIsLocal        :: Bool
+  , dispIsDefinition   :: Bool
+  , dispScopeLine      :: Word32
+  , dispContainingType :: Maybe (ValMd' lab)
+  , dispVirtuality     :: DwarfVirtuality
+  , dispVirtualIndex   :: Word32
+  , dispThisAdjustment :: Int64
+  , dispFlags          :: DIFlags
+  , dispIsOptimized    :: Bool
+  , dispUnit           :: Maybe (ValMd' lab)
+  , dispTemplateParams :: Maybe (ValMd' lab)
+  , dispDeclaration    :: Maybe (ValMd' lab)
+  , dispRetainedNodes  :: Maybe (ValMd' lab)
+  , dispThrownTypes    :: Maybe (ValMd' lab)
+  , dispAnnotations    :: Maybe (ValMd' lab)
+    -- ^ Introduced in LLVM 14.
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DISubprogram = DISubprogram' BlockLabel
+
+-- | The DISubrange is a Value subrange specification, usually associated with
+-- arrays or enumerations.
+--
+-- * Early LLVM: only 'disrCount' and 'disrLowerBound' were present, where both
+--   were a direct signed 64-bit value.  This corresponds to "format 0" in the
+--   bitcode encoding (see reference below).
+--
+-- * LLVM 7: 'disrCount' changed to metadata representation ('ValMd').  The
+--   metadata representation should only be a signed 64-bit integer, a Variable,
+--   or an Expression.  This corresponds to "format 1" in the bitcode encoding.
+--
+-- * LLVM 11: 'disrLowerBound' was changed to a metadata representation and
+--   'disrUpperBound' and 'disrStride' were added (primarily driven by the
+--   addition of Fortran support in llvm).  All three should only be represented
+--   as a signed 64-bit integer, a Variable, or an Expression.  This corresponds
+--   to "format 2" in the bitcode encoding.  See
+--   https://github.com/llvm/llvm-project/commit/d20bf5a for this change.
+--
+-- Also see
+-- https://github.com/llvm/llvm-project/blob/bbe8cd1/llvm/lib/Bitcode/Reader/MetadataLoader.cpp#L1435-L1461
+-- for how this is read from the bitcode encoding and the use of the format
+-- values mentioned above.
+
+data DISubrange' lab = DISubrange
+  { disrCount      :: Maybe (ValMd' lab)
+  , disrLowerBound :: Maybe (ValMd' lab)
+  , disrUpperBound :: Maybe (ValMd' lab)
+  , disrStride     :: Maybe (ValMd' lab)
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DISubrange = DISubrange' BlockLabel
+
+data DISubroutineType' lab = DISubroutineType
+  { distFlags     :: DIFlags
+  , distTypeArray :: Maybe (ValMd' lab)
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DISubroutineType = DISubroutineType' BlockLabel
+
+-- | See <https://releases.llvm.org/13.0.0/docs/LangRef.html#diarglist>.
+newtype DIArgList' lab = DIArgList
+  { dialArgs :: [ValMd' lab]
+  } deriving (Data, Eq, Functor, Generic, Generic1, Ord, Show, Typeable)
+
+type DIArgList = DIArgList' BlockLabel
+
+-- Aggregate Utilities ---------------------------------------------------------
+
+data IndexResult
+  = Invalid                             -- ^ An invalid use of GEP
+  | HasType Type                        -- ^ A resolved type
+  | Resolve Ident (Type -> IndexResult) -- ^ Continue, after resolving an alias
+  deriving (Generic, Typeable)
+
+isInvalid :: IndexResult -> Bool
+isInvalid ir = case ir of
+  Invalid -> True
+  _       -> False
+
+-- | Resolves the type of a GEP instruction. Type aliases are resolved
+-- using the given function. An invalid use of GEP or one relying
+-- on unknown type aliases will return 'Nothing'
+resolveGepFull ::
+  (Ident -> Maybe Type) {- ^ Type alias resolution -} ->
+  Type                  {- ^ Base type used for calculations -} ->
+  Typed (Value' lab)    {- ^ Pointer value         -} ->
+  [Typed (Value' lab)]  {- ^ Path                  -} ->
+  Maybe Type            {- ^ Type of result        -}
+resolveGepFull env baseTy tv ixs = go (resolveGep baseTy tv ixs)
+  where
+  go Invalid                = Nothing
+  go (HasType result)       = Just result
+  go (Resolve ident resume) = go . resume =<< env ident
+
+
+-- | Resolve the type of a GEP instruction.  Note that the type produced is the
+-- type of the result, not necessarily a pointer.
+resolveGep :: Type -> Typed (Value' lab) -> [Typed (Value' lab)] -> IndexResult
+resolveGep baseTy tv ixs =
+  case ixs of
+    v:ixs0
+      |  -- If headed by a pointer and the first index value has a valid GEP
+         -- index type, proceed to resolve the body of the GEP instruction.
+         isPointer t
+      ,  isGepIndex v
+      -> resolveGepBody baseTy ixs0
+
+      |  -- If headed by a pointer and the first index has an alias type,
+         -- resolve the alias and try again.
+         isPointer t
+      ,  Just i <- elimAlias (typedType v)
+      -> Resolve i (\ty' -> resolveGep baseTy tv (Typed ty' (typedValue v):ixs0))
+
+    _ |  -- If headed by a value with an alias type, resolve the alias and
+         -- try again.
+         Alias i <- t
+      -> Resolve i (\ty' -> resolveGep baseTy (Typed ty' (typedValue tv)) ixs)
+
+      |  -- Otherwise, the GEP instruction is invalid.
+         otherwise
+      -> Invalid
+  where
+    t = typedType tv
+
+-- | Resolve the type of a GEP instruction.  This assumes that the input has
+-- already been processed as a pointer.
+resolveGepBody :: Type -> [Typed (Value' lab)] -> IndexResult
+resolveGepBody (Struct fs) (v:ixs)
+  | Just i <- isGepStructIndex v, genericLength fs > i =
+    resolveGepBody (genericIndex fs i) ixs
+resolveGepBody (PackedStruct fs) (v:ixs)
+  | Just i <- isGepStructIndex v, genericLength fs > i =
+    resolveGepBody (genericIndex fs i) ixs
+resolveGepBody (Alias name) is
+  | not (null is) =
+    Resolve name (\ty' -> resolveGepBody ty' is)
+resolveGepBody (Array _ ty') (v:ixs)
+  | isGepIndex v =
+    resolveGepBody ty' ixs
+resolveGepBody (Vector _ tp) [val]
+  | isGepIndex val =
+    HasType tp
+resolveGepBody ty (v:ixs)
+  | Just i <- elimAlias (typedType v) =
+    Resolve i (\ty' -> resolveGepBody ty (Typed ty' (typedValue v):ixs))
+resolveGepBody ty [] =
+    HasType ty
+resolveGepBody _ _ =
+    Invalid
+
+isGepIndex :: Typed (Value' lab) -> Bool
+isGepIndex tv =
+  isPrimTypeOf isInteger (typedType tv) ||
+  isVectorOf (isPrimTypeOf isInteger) (typedType tv)
+
+isGepStructIndex :: Typed (Value' lab) -> Maybe Integer
+isGepStructIndex tv = do
+  guard (isGepIndex tv)
+  elimValInteger (typedValue tv)
+
+resolveValueIndex :: Type -> [Int32] -> IndexResult
+resolveValueIndex ty is@(ix:ixs) = case ty of
+  Struct fs | genericLength fs > ix
+    -> resolveValueIndex (genericIndex fs ix) ixs
+
+  PackedStruct fs | genericLength fs > ix
+    -> resolveValueIndex (genericIndex fs ix) ixs
+
+  Array n ty' | fromIntegral ix < n
+    -> resolveValueIndex ty' ixs
+
+  Alias name
+    -> Resolve name (\ty' -> resolveValueIndex ty' is)
+
+  _ -> Invalid
+resolveValueIndex ty [] = HasType ty
diff --git a/llvm-pretty/src/Text/LLVM/DebugUtils.hs b/llvm-pretty/src/Text/LLVM/DebugUtils.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/DebugUtils.hs
@@ -0,0 +1,516 @@
+{-# Language TransformListComp, MonadComprehensions #-}
+{- |
+Module           : Text.LLVM.DebugUtils
+Description      : This module interprets the DWARF information associated
+                   with a function's argument and return types in order to
+                   interpret field name references.
+License          : BSD3
+Stability        : provisional
+Maintainer       : emertens@galois.com
+-}
+module Text.LLVM.DebugUtils
+  ( -- * Definition type analyzer
+    Info(..), StructFieldInfo(..), BitfieldInfo(..), UnionFieldInfo(..)
+  , computeFunctionTypes, valMdToInfo
+  , localVariableNameDeclarations
+
+  -- * Metadata lookup
+  , mkMdMap
+
+  -- * Type structure dereference
+  , derefInfo
+  , fieldIndexByPosition
+  , fieldIndexByName
+
+  -- * Info hueristics
+  , guessAliasInfo
+  , guessTypeInfo
+
+  -- * Function arguments
+  , debugInfoArgNames
+
+  -- * Line numbers of definitions
+  , debugInfoGlobalLines
+  , debugInfoDefineLines
+  ) where
+
+import           Control.Applicative    ((<|>))
+import           Control.Monad          ((<=<))
+import           Data.Bits              (Bits(..))
+import           Data.IntMap            (IntMap)
+import qualified Data.IntMap as IntMap
+import           Data.List              (elemIndex, tails, stripPrefix)
+import           Data.Map               (Map)
+import qualified Data.Map    as Map
+import           Data.Maybe             (fromMaybe, listToMaybe, maybeToList, mapMaybe)
+import           Data.Word              (Word16, Word64)
+import           Text.LLVM.AST
+
+dbgKind :: String
+dbgKind = "dbg"
+
+llvmDbgCuKey :: String
+llvmDbgCuKey = "llvm.dbg.cu"
+
+dwarfPointer, dwarfStruct, dwarfTypedef, dwarfUnion, dwarfBasetype,
+  dwarfConst, dwarfArray :: Word16
+dwarfPointer  = 0x0f
+dwarfStruct   = 0x13
+dwarfTypedef  = 0x16
+dwarfArray    = 0x01
+dwarfUnion    = 0x17
+dwarfBasetype = 0x24
+dwarfConst    = 0x26
+
+type MdMap = IntMap ValMd
+
+data Info
+  = Pointer Info
+  | Structure (Maybe String) [StructFieldInfo]
+  | Union     (Maybe String) [UnionFieldInfo]
+  | Typedef String Info
+  | ArrInfo Info
+  | BaseType String DIBasicType
+  | Unknown
+  deriving Show
+
+-- | Record debug information about a field in a struct type.
+data StructFieldInfo = StructFieldInfo
+  { sfiName :: String
+    -- ^ The field name.
+  , sfiOffset :: Word64
+    -- ^ The field's offset (in bits) from the start of the struct.
+  , sfiBitfield :: Maybe BitfieldInfo
+    -- ^ If this field resides within a bitfield, this is
+    -- @'Just' bitfieldInfo@. Otherwise, this is 'Nothing'.
+  , sfiInfo :: Info
+    -- ^ The debug 'Info' associated with the field's type.
+  } deriving Show
+
+-- | Record debug information about a field within a bitfield. For example,
+-- the following C struct:
+--
+-- @
+-- struct s {
+--   int32_t w;
+--   uint8_t x1:1;
+--   uint8_t x2:2;
+--   uint8_t y:1;
+--   int32_t z;
+-- };
+-- @
+--
+-- Corresponds to the following 'Info':
+--
+-- @
+-- 'Structure'
+--   [ 'StructFieldInfo' { 'sfiName' = \"w\"
+--                       , 'sfiOffset' = 0
+--                       , 'sfiBitfield' = Nothing
+--                       , 'sfiInfo' = 'BaseType' \"int32_t\"
+--                       }
+--   , 'StructFieldInfo' { 'sfiName' = \"x1\"
+--                       , 'sfiOffset' = 32
+--                       , 'sfiBitfield' = Just ('BitfieldInfo' { 'biFieldSize' = 1
+--                                                              , 'biBitfieldOffset' = 32
+--                                                              })
+--                       , 'sfiInfo' = 'BaseType' \"uint8_t\"
+--                       }
+--   , 'StructFieldInfo' { 'sfiName' = \"x2\"
+--                       , 'sfiOffset' = 33
+--                       , 'sfiBitfield' = Just ('BitfieldInfo' { 'biFieldSize' = 2
+--                                                              , 'biBitfieldOffset' = 32
+--                                                              })
+--                       , 'sfiInfo' = BaseType \"uint8_t\"
+--                       }
+--   , 'StructFieldInfo' { 'sfiName' = \"y\"
+--                       , 'sfiOffset' = 35
+--                       , 'sfiBitfield' = Just ('BitfieldInfo' { 'biFieldSize' = 1
+--                                                              , 'biBitfieldOffset' = 32
+--                                                              })
+--                       , 'sfiInfo' = 'BaseType' \"uint8_t\"
+--                       }
+--   , 'StructFieldInfo' { 'sfiName' = \"z\"
+--                       , 'sfiOffset' = 64
+--                       , 'sfiBitfield' = Nothing
+--                       , 'sfiInfo' = BaseType \"int32_t\"
+--                       }
+--   ]
+-- @
+--
+-- Notice that only @x1@, @x2@, and @y@ have 'BitfieldInfo's, as they are the
+-- only fields that were declared with bitfield syntax.
+data BitfieldInfo = BitfieldInfo
+  { biFieldSize :: Word64
+    -- ^ The field's size (in bits) within the bitfield. This should not be
+    --   confused with the size of the field's declared type. For example, the
+    --   'biFieldSize' of the @x1@ field is @1@, despite the fact that its
+    --   declared type, @uint8_t@, is otherwise 8 bits in size.
+  , biBitfieldOffset :: Word64
+    -- ^ The bitfield's offset (in bits) from the start of the struct. Note
+    --   that for a given field within a bitfield, its 'sfiOffset' is equal to
+    --   the 'biBitfieldOffset' plus the 'biFieldSize'.
+  } deriving Show
+
+-- | Record debug information about a field in a union type.
+data UnionFieldInfo = UnionFieldInfo
+  { ufiName :: String
+    -- ^ The field name.
+  , ufiInfo :: Info
+    -- ^ The debug 'Info' associated with the field's type.
+  } deriving Show
+
+-- | Compute an 'IntMap' of the unnamed metadata in a module
+mkMdMap :: Module -> IntMap ValMd
+mkMdMap m = IntMap.fromList [ (umIndex md, umValues md) | md <- modUnnamedMd m ]
+
+------------------------------------------------------------------------
+
+getDebugInfo :: MdMap -> ValMd -> Maybe DebugInfo
+getDebugInfo mdMap (ValMdRef i)    = getDebugInfo mdMap =<< IntMap.lookup i mdMap
+getDebugInfo _ (ValMdDebugInfo di) = Just di
+getDebugInfo _ _                   = Nothing
+
+getInteger :: MdMap -> ValMd -> Maybe Integer
+getInteger mdMap (ValMdRef i)                          = getInteger mdMap =<< IntMap.lookup i mdMap
+getInteger _     (ValMdValue (Typed _ (ValInteger i))) = Just i
+getInteger _     _                                     = Nothing
+
+getList :: MdMap -> ValMd -> Maybe [Maybe ValMd]
+getList mdMap (ValMdRef i) = getList mdMap =<< IntMap.lookup i mdMap
+getList _ (ValMdNode di)   = Just di
+getList _ _                = Nothing
+
+------------------------------------------------------------------------
+
+valMdToInfo :: MdMap -> ValMd -> Info
+valMdToInfo mdMap val =
+  maybe Unknown (debugInfoToInfo mdMap) (getDebugInfo mdMap val)
+
+
+valMdToInfo' :: MdMap -> Maybe ValMd -> Info
+valMdToInfo' = maybe Unknown . valMdToInfo
+
+
+debugInfoToInfo :: MdMap -> DebugInfo -> Info
+debugInfoToInfo mdMap (DebugInfoDerivedType dt)
+  | didtTag dt == dwarfPointer  = Pointer (valMdToInfo' mdMap (didtBaseType dt))
+  | didtTag dt == dwarfTypedef  = case didtName dt of
+                                    Nothing -> valMdToInfo' mdMap (didtBaseType dt)
+                                    Just nm -> Typedef nm (valMdToInfo' mdMap (didtBaseType dt))
+  | didtTag dt == dwarfConst    = valMdToInfo' mdMap (didtBaseType dt)
+debugInfoToInfo _     (DebugInfoBasicType bt)
+  | dibtTag bt == dwarfBasetype = BaseType (dibtName bt) bt
+debugInfoToInfo mdMap (DebugInfoCompositeType ct)
+  | dictTag ct == dwarfStruct   = maybe Unknown (Structure (dictName ct)) (getStructFields mdMap ct)
+  | dictTag ct == dwarfUnion    = maybe Unknown (Union     (dictName ct)) (getUnionFields mdMap ct)
+  | dictTag ct == dwarfArray    = ArrInfo (valMdToInfo' mdMap (dictBaseType ct))
+debugInfoToInfo _ _             = Unknown
+
+
+getFieldDIs :: MdMap -> DICompositeType -> Maybe [DebugInfo]
+getFieldDIs mdMap =
+  traverse (getDebugInfo mdMap) <=< sequence <=< getList mdMap <=< dictElements
+
+getStructFields :: MdMap -> DICompositeType -> Maybe [StructFieldInfo]
+getStructFields mdMap = traverse (debugInfoToStructField mdMap) <=< getFieldDIs mdMap
+
+debugInfoToStructField :: MdMap -> DebugInfo -> Maybe StructFieldInfo
+debugInfoToStructField mdMap di =
+  do DebugInfoDerivedType dt <- Just di
+     fieldName               <- didtName dt
+     -- We check if a struct field resides within a bitfield by checking its
+     -- `flags` field sets `BitField`, which has a numeric value of 19.
+     -- (https://github.com/llvm/llvm-project/blob/1bebc31c617d1a0773f1d561f02dd17c5e83b23b/llvm/include/llvm/IR/DebugInfoFlags.def#L51)
+     --
+     -- If so, the `size` field records the size in bits, and the `extraData`
+     -- field records the offset of the overall bitfield from the start of the
+     -- struct.
+     -- (https://github.com/llvm/llvm-project/blob/ee7652569854af567ba83e5255d70e80cc8619a1/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp#L2489-L2508)
+     let bitfield | testBit (didtFlags dt) 19
+                  , Just extraData      <- didtExtraData dt
+                  , Just bitfieldOffset <- getInteger mdMap extraData
+                  = do size <- getSizeOrOffset (didtSize dt)
+                       Just $ BitfieldInfo { biFieldSize      = size
+                                           , biBitfieldOffset = fromInteger bitfieldOffset
+                                           }
+                  | otherwise
+                  = Nothing
+     offset <- getSizeOrOffset (didtOffset dt)
+     Just (StructFieldInfo { sfiName     = fieldName
+                           , sfiOffset   = offset
+                           , sfiBitfield = bitfield
+                           , sfiInfo     = valMdToInfo' mdMap (didtBaseType dt)
+                           })
+  where
+    -- TODO: Currently, this only recognizes bare integer (i.e., 'ValInteger')
+    -- sizes and offsets. This is likely good enough for Clang-derived LLVM, but
+    -- Ada-derived LLVM may contain more complex metadata values that this
+    -- currently doesn't handle.
+    getSizeOrOffset :: Maybe ValMd -> Maybe Word64
+    getSizeOrOffset (Just (ValMdValue tv))
+      | ValInteger i <- typedValue tv
+      = Just (fromInteger i)
+    getSizeOrOffset _ = Nothing
+
+
+getUnionFields :: MdMap -> DICompositeType -> Maybe [UnionFieldInfo]
+getUnionFields mdMap = traverse (debugInfoToUnionField mdMap) <=< getFieldDIs mdMap
+
+
+debugInfoToUnionField :: MdMap -> DebugInfo -> Maybe UnionFieldInfo
+debugInfoToUnionField mdMap di =
+  do DebugInfoDerivedType dt <- Just di
+     fieldName               <- didtName dt
+     Just (UnionFieldInfo { ufiName = fieldName
+                          , ufiInfo = valMdToInfo' mdMap (didtBaseType dt)
+                          })
+
+
+
+-- | Compute the structures of a function's return and argument types
+-- using DWARF information metadata of the LLVM module. Different
+-- versions of LLVM make this information available via different
+-- paths. This function attempts to support the variations.
+computeFunctionTypes ::
+  Module       {- ^ module to search                     -} ->
+  Symbol       {- ^ function symbol                      -} ->
+  Maybe [Maybe Info] {- ^ return and argument type information -}
+computeFunctionTypes m sym =
+  [ fmap (valMdToInfo mdMap) <$> types
+     | let mdMap = mkMdMap m
+     , sp <- findSubprogramViaDefine mdMap m sym
+         <|> findSubprogramViaCu     mdMap m sym
+     , DebugInfoSubroutineType st <- getDebugInfo mdMap =<< dispType sp
+     , types                      <- getList mdMap      =<< distTypeArray st
+     ]
+
+
+-- | This method of computing argument type information works on at least LLVM 3.8
+findSubprogramViaDefine ::
+  IntMap ValMd       {- ^ unnamed metadata                             -} ->
+  Module             {- ^ module to search                             -} ->
+  Symbol             {- ^ function symbol to find                      -} ->
+  Maybe DISubprogram {- ^ debug information related to function symbol -}
+findSubprogramViaDefine mdMap m sym =
+  [ sp
+     | def                    <- modDefines m
+     , defName def == sym
+     , then listToMaybe ----- commits to a choice -----
+     , dbgMd                  <- Map.lookup dbgKind (defMetadata def)
+     , DebugInfoSubprogram sp <- getDebugInfo mdMap dbgMd
+     ]
+
+
+-- | This method of computing function debugging information works on LLVM 3.7
+findSubprogramViaCu ::
+  MdMap              {- ^ map of unnamed metadata                -} ->
+  Module             {- ^ module to search                       -} ->
+  Symbol             {- ^ function symbol to search for          -} ->
+  Maybe DISubprogram {- ^ debugging information for given symbol -}
+findSubprogramViaCu mdMap m (Symbol sym) = listToMaybe
+  [ sp
+    | md                      <- modNamedMd m
+    , nmName md == llvmDbgCuKey
+    , ref                     <- nmValues md
+    , DebugInfoCompileUnit cu <- maybeToList  $ getDebugInfo mdMap $ ValMdRef ref
+    , Just entry              <- fromMaybe [] $ getList mdMap =<< dicuSubprograms cu
+    , DebugInfoSubprogram sp  <- maybeToList  $ getDebugInfo mdMap entry
+    , dispName sp == Just sym
+    ]
+
+
+------------------------------------------------------------------------
+
+-- | If the argument describes a pointer, return the information for the
+-- type that it points do. If the argument describes an array, return
+-- information about the element type.
+derefInfo ::
+  Info {- ^ pointer type information                -} ->
+  Info {- ^ type information of pointer's base type -}
+derefInfo (Pointer x) = x
+derefInfo (ArrInfo x) = x
+derefInfo _           = Unknown
+
+-- | If the argument describes a composite type, returns the type of the
+-- field by zero-based index into the list of fields.
+fieldIndexByPosition ::
+  Int  {- ^ zero-based field index               -} ->
+  Info {- ^ composite type information           -} ->
+  Info {- ^ type information for specified field -}
+fieldIndexByPosition i info =
+  case info of
+    Typedef _ info' -> fieldIndexByPosition i info'
+    Structure _ xs  -> go [ x | StructFieldInfo{sfiInfo = x} <- xs ]
+    Union     _ xs  -> go [ x | UnionFieldInfo{ufiInfo = x}  <- xs ]
+    _               -> Unknown
+  where
+    go xs = case drop i xs of
+              []  -> Unknown
+              x:_ -> x
+
+-- | If the argument describes a composite type, return the first, zero-based
+-- index of the field in that type that matches the given name.
+fieldIndexByName ::
+  String    {- ^ field name                                  -} ->
+  Info      {- ^ composite type info                         -} ->
+  Maybe Int {- ^ zero-based index of field matching the name -}
+fieldIndexByName n info =
+  case info of
+    Typedef _ info' -> fieldIndexByName n info'
+    Structure _ xs  -> go [ x | StructFieldInfo{sfiName = x} <- xs ]
+    Union     _ xs  -> go [ x | UnionFieldInfo{ufiName = x}  <- xs ]
+    _               -> Nothing
+  where
+    go = elemIndex n
+
+------------------------------------------------------------------------
+
+localVariableNameDeclarations ::
+  IntMap ValMd    {- ^ unnamed metadata      -} ->
+  Define          {- ^ function definition   -} ->
+  Map Ident Ident {- ^ raw name, actual name -}
+localVariableNameDeclarations mdMap def =
+  case defBody def of
+    blk1 : _ -> foldr aux Map.empty (tails (bbStmts blk1))
+    _        -> Map.empty
+  where
+
+    aux :: [Stmt] -> Map Ident Ident -> Map Ident Ident
+    aux ( Effect (Store False src dst _ _) _ _
+        : Effect (Call _ _ _ (ValSymbol (Symbol what)) [var,md,_]) _ _
+        : _) sofar
+      | what == "llvm.dbg.declare"  -- pre-LLVM19: intrinsic declaration match
+      , Just dstIdent <- extractIdent dst
+      , Just srcIdent <- extractIdent src
+      , Just varIdent <- extractIdent var
+      , dstIdent == varIdent
+      , Just name <- extractLvName md
+      = Map.insert name srcIdent sofar
+
+    aux ( Effect (Call _ _ _ (ValSymbol (Symbol what)) [var,_,md,_]) _ _
+        : _) sofar
+      | what == "llvm.dbg.value"  -- pre-LLVM19: intrinsic declaration match
+      , Just key  <- extractIdent var
+      , Just name <- extractLvName md
+      = Map.insert name key sofar
+
+    aux _ sofar = sofar
+
+    extractIdent :: Typed Value -> Maybe Ident
+    extractIdent (Typed _ (ValIdent i)) = Just i
+    extractIdent _                      = Nothing
+
+    extractLvName :: Typed Value -> Maybe Ident
+    extractLvName mdArg =
+      do ValMd md                    <- Just (typedValue mdArg)
+         DebugInfoLocalVariable dilv <- getDebugInfo mdMap md
+         Ident <$> dilvName dilv
+
+------------------------------------------------------------------------
+
+-- | Search the metadata for debug info corresponding
+-- to a given type alias. This is considered a heuristic
+-- because there's no direct mapping between type aliases
+-- and debug info. The debug information must be search
+-- for a textual match.
+--
+-- Compared to @guessTypeInfo@, this function first tries
+-- to strip the \"struct.\" and \"union.\" prefixes that are
+-- commonly added by clang before searching for the type information.
+guessAliasInfo ::
+  IntMap ValMd    {- ^ unnamed metadata      -} ->
+  Ident           {- ^ alias                 -} ->
+  Info
+guessAliasInfo mdMap (Ident name)
+  | Just pfx <- stripPrefix "struct." name = guessTypeInfo mdMap pfx
+  | Just pfx <- stripPrefix "union."  name = guessTypeInfo mdMap pfx
+  | otherwise = guessTypeInfo mdMap name
+
+-- | Search the metadata for debug info corresponding
+-- to a given type alias. This is considered a heuristic
+-- because there's no direct mapping between type aliases
+-- and debug info. The debug information must be search
+-- for a textual match.
+guessTypeInfo ::
+  IntMap ValMd    {- ^ unnamed metadata      -} ->
+  String          {- ^ struct alias          -} ->
+  Info
+guessTypeInfo mdMap name =
+  case mapMaybe (go <=< getDebugInfo mdMap) (IntMap.elems mdMap) of
+    []  -> Unknown
+    x:_ -> x
+
+  where
+    go di | DebugInfoDerivedType didt <- di
+          , Just name == didtName didt
+          = Just (debugInfoToInfo mdMap di)
+
+    go di | DebugInfoCompositeType dict <- di
+          , Just name == dictName dict
+          = Just (debugInfoToInfo mdMap di)
+
+    go _ = Nothing
+
+------------------------------------------------------------------------
+
+-- | Find source-level names of function arguments
+debugInfoArgNames :: Module -> Define -> IntMap String
+debugInfoArgNames m d =
+  case Map.lookup dbgKind $ defMetadata d of
+    Just (ValMdRef s) -> scopeArgs s
+    _ -> IntMap.empty
+  where
+    scopeArgs :: Int -> IntMap String
+    scopeArgs s = IntMap.fromList . mapMaybe go $ modUnnamedMd m
+      where
+        go :: UnnamedMd -> Maybe (Int, String)
+        go
+          ( UnnamedMd
+              { umValues =
+                  ValMdDebugInfo
+                    ( DebugInfoLocalVariable
+                        DILocalVariable
+                          { dilvScope = Just (ValMdRef s'),
+                            dilvArg = a,
+                            dilvName = Just n
+                          }
+                      )
+              }) =
+            if s == s'
+            then Just (fromIntegral a - 1, n)
+            else Nothing
+        go _ = Nothing
+
+------------------------------------------------------------------------
+
+-- | Map global variable names to the line on which the global is defined
+debugInfoGlobalLines :: Module -> Map String Int
+debugInfoGlobalLines = Map.fromList . mapMaybe go . modUnnamedMd
+  where
+    go :: UnnamedMd -> Maybe (String, Int)
+    go (UnnamedMd
+         { umValues = ValMdDebugInfo
+           (DebugInfoGlobalVariable DIGlobalVariable
+             { digvName = Just n
+             , digvLine = l
+             }
+           )
+         }) = Just (n, (fromIntegral l))
+    go _ = Nothing
+
+-- | Map function names to the line on which the function is defined
+debugInfoDefineLines :: Module -> Map String Int
+debugInfoDefineLines = Map.fromList . mapMaybe go . modUnnamedMd
+  where
+    go :: UnnamedMd -> Maybe (String, Int)
+    go (UnnamedMd
+         { umValues = ValMdDebugInfo
+           (DebugInfoSubprogram DISubprogram
+             { dispName = Just n
+             , dispIsDefinition = True
+             , dispLine = l
+             }
+           )
+         }) = Just (n, (fromIntegral l))
+    go _ = Nothing
diff --git a/llvm-pretty/src/Text/LLVM/Labels.hs b/llvm-pretty/src/Text/LLVM/Labels.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Labels.hs
@@ -0,0 +1,160 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE EmptyCase, TypeOperators, FlexibleContexts #-}
+module Text.LLVM.Labels where
+
+import Text.LLVM.AST
+import Text.LLVM.Labels.TH
+
+class Functor f => HasLabel f where
+  -- | Given a function for resolving labels, where the presence of a symbol
+  -- denotes a label in a different function, rename all labels in a function.
+  relabel :: Applicative m => (Maybe Symbol -> a -> m b) -> f a -> m (f b)
+
+instance HasLabel Instr' where
+  relabel _ RetVoid               = pure  RetVoid
+  relabel _ Unreachable           = pure  Unreachable
+  relabel _ Unwind                = pure  Unwind
+  relabel _ (Comment str)         = pure (Comment str)
+  relabel f (Ret tv)              = Ret <$> traverse (relabel f) tv
+  relabel f (Arith op l r)        = Arith op
+                                <$> traverse (relabel f) l
+                                <*> relabel f r
+  relabel f (UnaryArith op a)     = UnaryArith op
+                                <$> traverse (relabel f) a
+  relabel f (Bit op l r)          = Bit op
+                                <$> traverse (relabel f) l
+                                <*> relabel f r
+  relabel f (Conv op l r)         = Conv op <$> traverse (relabel f) l <*> pure r
+  relabel f (Call t fmf r n as)   = Call t fmf r
+                                <$> relabel f n
+                                <*> traverse (traverse (relabel f)) as
+  relabel f (CallBr r n as u es)  = CallBr r
+                                <$> relabel f n
+                                <*> traverse (traverse (relabel f)) as
+                                <*> f Nothing u
+                                <*> traverse (f Nothing) es
+  relabel f (Alloca t n a)        = Alloca t
+                                <$> traverse (traverse (relabel f)) n
+                                <*> pure a
+  relabel f (Load vol t a mo ma)  = Load vol t
+                                <$> traverse (relabel f) a
+                                <*> pure mo
+                                <*> pure ma
+  relabel f (Store vol d v mo ma) = Store vol
+                                <$> traverse (relabel f) d
+                                <*> traverse (relabel f) v
+                                <*> pure mo
+                                <*> pure ma
+  relabel _ (Fence s o)           = pure (Fence s o)
+  relabel f (CmpXchg w v p a n s o o')
+                                  = CmpXchg w v
+                                <$> traverse (relabel f) p
+                                <*> traverse (relabel f) a
+                                <*> traverse (relabel f) n
+                                <*> pure s
+                                <*> pure o
+                                <*> pure o'
+  relabel f (AtomicRW v op p a s o)
+                                  = AtomicRW v op
+                                <$> traverse (relabel f) p
+                                <*> traverse (relabel f) a
+                                <*> pure s
+                                <*> pure o
+  relabel f (ICmp samesign op l r)
+                                  = ICmp samesign op
+                                <$> traverse (relabel f) l
+                                <*> relabel f r
+  relabel f (FCmp op fmf l r)     = FCmp op fmf
+                                <$> traverse (relabel f) l
+                                <*> relabel f r
+  relabel f (GEP ib t a is)       = GEP ib t
+                                <$> traverse (relabel f) a
+                                <*> traverse (traverse (relabel f)) is
+  relabel f (Select fmf c l r)    = Select fmf
+                                <$> traverse (relabel f) c
+                                <*> traverse (relabel f) l <*> relabel f r
+  relabel f (ExtractValue a is)   = ExtractValue
+                                <$> traverse (relabel f) a
+                                <*> pure is
+  relabel f (InsertValue a i is)  = InsertValue
+                                <$> traverse (relabel f) a
+                                <*> traverse (relabel f) i
+                                <*> pure is
+  relabel f (ShuffleVector a b m) = ShuffleVector
+                                <$> traverse (relabel f) a
+                                <*> relabel f b
+                                <*> traverse (relabel f) m
+  relabel f (Jump lab)            = Jump <$> f Nothing lab
+  relabel f (Br c l r)            = Br
+                                <$> traverse (relabel f) c
+                                <*> f Nothing l
+                                <*> f Nothing r
+  relabel f (Invoke r s as u e)   = Invoke r
+                                <$> relabel f s
+                                <*> traverse (traverse (relabel f)) as
+                                <*> f Nothing u
+                                <*> f Nothing e
+  relabel f (VaArg al t)          = VaArg
+                                <$> traverse (relabel f) al
+                                <*> pure t
+  relabel f (ExtractElt v i)      = ExtractElt
+                                <$> traverse (relabel f) v
+                                <*> relabel f i
+  relabel f (InsertElt v e i)     = InsertElt
+                                <$> traverse (relabel f) v
+                                <*> traverse (relabel f) e
+                                <*> relabel f i
+  relabel f (IndirectBr d ls)     = IndirectBr
+                                <$> traverse (relabel f) d
+                                <*> traverse (f Nothing) ls
+  relabel f (Switch c d ls)       =
+    let step (n,i) = (\l -> (n,l)) <$> f Nothing i
+     in Switch <$> traverse (relabel f) c <*> f Nothing d <*> traverse step ls
+  relabel f (Phi fmf t ls)        =
+    let step (a,l) = (,) <$> relabel f a <*> f Nothing l
+     in Phi fmf t <$> traverse step ls
+
+  relabel f (LandingPad ty fn c cs) = LandingPad ty
+                                  <$> traverse (traverse (relabel f)) fn
+                                  <*> pure c
+                                  <*> traverse (relabel f) cs
+
+  relabel f (Resume tv)           = Resume <$> traverse (relabel f) tv
+  relabel f (Freeze tv)           = Freeze <$> traverse (relabel f) tv
+
+instance HasLabel Stmt'                       where relabel = $(generateRelabel 'relabel ''Stmt')
+instance HasLabel Clause'                     where relabel = $(generateRelabel 'relabel ''Clause')
+instance HasLabel Value'                      where relabel = $(generateRelabel 'relabel ''Value')
+instance HasLabel ValMd'                      where relabel = $(generateRelabel 'relabel ''ValMd')
+instance HasLabel DebugRecord'                where relabel = $(generateRelabel 'relabel ''DebugRecord')
+instance HasLabel DbgRecAssign'               where relabel = $(generateRelabel 'relabel ''DbgRecAssign')
+instance HasLabel DbgRecDeclare'              where relabel = $(generateRelabel 'relabel ''DbgRecDeclare')
+instance HasLabel DbgRecLabel'                where relabel = $(generateRelabel 'relabel ''DbgRecLabel')
+instance HasLabel DbgRecValueSimple'          where relabel = $(generateRelabel 'relabel ''DbgRecValueSimple')
+instance HasLabel DbgRecValue'                where relabel = $(generateRelabel 'relabel ''DbgRecValue')
+instance HasLabel DILabel'                    where relabel = $(generateRelabel 'relabel ''DILabel')
+instance HasLabel DebugLoc'                   where relabel = $(generateRelabel 'relabel ''DebugLoc')
+instance HasLabel DebugInfo'                  where relabel = $(generateRelabel 'relabel ''DebugInfo')
+instance HasLabel DIBasicType'                where relabel = $(generateRelabel 'relabel ''DIBasicType')
+instance HasLabel DIDerivedType'              where relabel = $(generateRelabel 'relabel ''DIDerivedType')
+instance HasLabel DISubroutineType'           where relabel = $(generateRelabel 'relabel ''DISubroutineType')
+instance HasLabel DISubrange'                 where relabel = $(generateRelabel 'relabel ''DISubrange')
+instance HasLabel DIGlobalVariable'           where relabel = $(generateRelabel 'relabel ''DIGlobalVariable')
+instance HasLabel DIGlobalVariableExpression' where relabel = $(generateRelabel 'relabel ''DIGlobalVariableExpression')
+instance HasLabel DILocalVariable'            where relabel = $(generateRelabel 'relabel ''DILocalVariable')
+instance HasLabel DISubprogram'               where relabel = $(generateRelabel 'relabel ''DISubprogram')
+instance HasLabel DICompositeType'            where relabel = $(generateRelabel 'relabel ''DICompositeType')
+instance HasLabel DILexicalBlock'             where relabel = $(generateRelabel 'relabel ''DILexicalBlock')
+instance HasLabel DICompileUnit'              where relabel = $(generateRelabel 'relabel ''DICompileUnit')
+instance HasLabel DILexicalBlockFile'         where relabel = $(generateRelabel 'relabel ''DILexicalBlockFile')
+instance HasLabel DINameSpace'                where relabel = $(generateRelabel 'relabel ''DINameSpace')
+instance HasLabel DITemplateTypeParameter'    where relabel = $(generateRelabel 'relabel ''DITemplateTypeParameter')
+instance HasLabel DITemplateValueParameter'   where relabel = $(generateRelabel 'relabel ''DITemplateValueParameter')
+instance HasLabel DIImportedEntity'           where relabel = $(generateRelabel 'relabel ''DIImportedEntity')
+instance HasLabel DIArgList'                  where relabel = $(generateRelabel 'relabel ''DIArgList')
+
+-- | Clever instance that actually uses the block name
+instance HasLabel ConstExpr' where
+  relabel f (ConstBlockAddr t@(Typed { typedValue = ValSymbol s }) l) =
+    ConstBlockAddr <$> traverse (relabel f) t <*> f (Just s) l
+  relabel f x = $(generateRelabel 'relabel ''ConstExpr') f x
diff --git a/llvm-pretty/src/Text/LLVM/Labels/TH.hs b/llvm-pretty/src/Text/LLVM/Labels/TH.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Labels/TH.hs
@@ -0,0 +1,125 @@
+{-# Language TemplateHaskell #-}
+module Text.LLVM.Labels.TH (generateRelabel) where
+
+import Control.Monad (zipWithM)
+import Language.Haskell.TH
+import Language.Haskell.TH.Datatype
+
+generateRelabel :: Name -> Name -> ExpQ
+generateRelabel relabel dataCon =
+  do di <- reifyDatatype dataCon
+     generateRelabelData di (varE relabel)
+
+generateRelabelData :: DatatypeInfo -> ExpQ -> ExpQ
+generateRelabelData di relabelE =
+  [| \f x -> $(caseE [| x |] (mkMatch [| f |] <$> cons)) |]
+  where
+    mkMatch = generateRelabelCon lastArg relabelE
+    lastArg = tvName (last (datatypeVars di))
+    cons    = datatypeCons di
+
+-- | Generates the case arm for the given constructor that
+-- relabels values using this constructor given a relabeling
+-- function.
+generateRelabelCon ::
+  Name            {- ^ last type parameter            -} ->
+  ExpQ            {- ^ recusive relabel expression    -} ->
+  ExpQ            {- ^ function expression            -} ->
+  ConstructorInfo {- ^ current constructor            -} ->
+  MatchQ          {- ^ match arm for this constructor -}
+generateRelabelCon lastArg relabelE fE ci =
+  do names <- nameThings "x" (constructorFields ci)
+     match
+      (conP cn (map (varP . fst) names))
+      (normalB (bodyExp cn (map gen names)))
+      []
+  where
+    cn = constructorName ci
+
+    -- Give a field name and type returns:
+    -- Left for a pure field
+    -- Right for a field using the Applicative instance
+    gen :: (Name, Type) -> Either ExpQ ExpQ
+    gen (n,t) =
+      let nE = varE n in
+      case generateRelabelField lastArg fE relabelE t of
+        Just f  -> Right [| $f $nE |]
+        Nothing -> Left nE
+
+-- | Given a constructor and a list of pure and updated fields,
+-- build syntax that rebuilds the expression.
+bodyExp ::
+  Name               {- ^ constructor                         -} ->
+  [Either ExpQ ExpQ] {- ^ list of pure and applicative fields -} ->
+  ExpQ               {- ^ applicative result                  -}
+bodyExp conname fields = liftAE conLike updates
+  where
+    updates = [r | Right r <- fields]
+
+    -- Builds a value suitable to be the argument to liftAE that can
+    -- combine all of the updated field values
+    conLike =
+      do names <- map fst <$> nameThings "y" updates
+         lamE
+           (map varP names)
+           (appsE (conE conname : replaceRights (map varE names) fields))
+
+-- | Replaces all of the 'Right' values in the given list with elements
+-- from the first list. The number of replacements must exactly match
+-- the number of 'Right' values.
+replaceRights ::
+  [a]          {- ^ replacements  -} ->
+  [Either a b] {- ^ source list   -} ->
+  [a]          {- ^ replaced list -}
+replaceRights xs     (Left y  : ys) = y : replaceRights xs ys
+replaceRights (x:xs) (Right _ : ys) = x : replaceRights xs ys
+replaceRights []     []             = []
+replaceRights _      _              = error "Text.LLVM.Labels.TH.replaceRights: PANIC"
+
+-- | Generate the applicative update value for a field if it
+-- has an appropriate type otherwise return nothing if it
+-- should be left unchagned.
+generateRelabelField ::
+  Name       {- ^ last type parameter         -} ->
+  ExpQ       {- ^ function expression         -} ->
+  ExpQ       {- ^ relabel expression          -} ->
+  Type       {- ^ field type                  -} ->
+  Maybe ExpQ {- ^ applicative update function -}
+generateRelabelField lastArg fE relabelE t =
+  case typeDepth t of
+    (n, VarT tn) | tn == lastArg -> Just (exprs !! n)
+    _                            -> Nothing
+  where
+    exprs = [| $fE Nothing |] : iterate traverseE [| $relabelE $fE |]
+
+-- | Figure out the depth of the outer type applications and
+-- return the type at the bottom of the stack
+typeDepth ::
+  Type        {- ^ target type                                     -} ->
+  (Int, Type) {- ^ number of type applications and right-most type -}
+typeDepth = go 0
+  where
+    go i (AppT _ x) = go (i+1) x
+    go i t          = (i, t)
+
+-- | Associate each element in a list of things with a unique name
+-- derived from a given name stem.
+nameThings ::
+  String        {- ^ base name                       -} ->
+  [a]           {- ^ things to name                  -} ->
+  Q [(Name, a)] {- ^ things paired with unique names -}
+nameThings base xs = zipWithM nameThing [0 :: Int ..] xs
+  where
+    nameThing i x = do n <- newName (base ++ show i); return (n,x)
+
+-- | Apply 'traverse' to an expression
+traverseE ::
+  ExpQ {- ^ f          -} ->
+  ExpQ {- ^ traverse f -}
+traverseE e = [| traverse $e |]
+
+-- Applies a pure value to zero or more applicative things to be combined
+-- with (<$>) and (<*>)
+liftAE :: ExpQ -> [ExpQ] -> ExpQ
+liftAE c []     = [| pure $c |]
+liftAE c (x:xs) = foldl (\f e -> [| $f <*> $e |]) [| $c <$> $x |] xs
diff --git a/llvm-pretty/src/Text/LLVM/Lens.hs b/llvm-pretty/src/Text/LLVM/Lens.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Lens.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE TemplateHaskell #-}
+module Text.LLVM.Lens where
+
+import Text.LLVM
+import Lens.Micro.TH
+import Lens.Micro
+import Language.Haskell.TH.Syntax (mkName, nameBase)
+
+concat <$> mapM (makeLensesWith (lensRules & lensField .~ (\_ _ n -> [TopName $ mkName $ nameBase n ++ "Lens"])))
+    [ ''Module
+    , ''LayoutSpec
+    , ''TypeDecl
+    , ''GlobalAlias
+    , ''ConstExpr'
+    , ''Type'
+    , ''Mangling
+    , ''NamedMd
+    , ''Value'
+    , ''BlockLabel
+    , ''UnnamedMd
+    , ''Typed
+    , ''Global
+    , ''Declare
+    , ''Clause'
+    , ''FunAttr
+    , ''GlobalAttrs
+    , ''BasicBlock'
+    , ''Stmt'
+    , ''Linkage
+    , ''DebugLoc'
+    , ''DebugInfo'
+    , ''DIFile
+    , ''DISubrange'
+    , ''DIBasicType'
+    , ''DIExpression
+    , ''DISubprogram'
+    , ''DISubroutineType'
+    , ''DILocalVariable'
+    , ''DIGlobalVariableExpression'
+    , ''DIGlobalVariable'
+    , ''DICompileUnit'
+    , ''DICompositeType'
+    , ''DIDerivedType'
+    , ''DILexicalBlock'
+    , ''DILexicalBlockFile'
+    , ''DIArgList'
+    , ''Instr'
+    , ''ValMd'
+    , ''ConvOp
+    , ''BitOp
+    , ''ArithOp
+    , ''FCmpOp
+    , ''ICmpOp
+    , ''GC
+    , ''Define
+    , ''PrimType
+    ]
diff --git a/llvm-pretty/src/Text/LLVM/PP.hs b/llvm-pretty/src/Text/LLVM/PP.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/PP.hs
@@ -0,0 +1,1605 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE ImplicitParams #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Rank2Types #-}
+
+-- |
+-- Module      :  Text.LLVM.PP
+-- Copyright   :  Trevor Elliott 2011-2016
+-- License     :  BSD3
+--
+-- Maintainer  :  awesomelyawesome@gmail.com
+-- Stability   :  experimental
+-- Portability :  unknown
+--
+-- This is the pretty-printer for llvm assembly versions 3.6 and lower.
+--
+module Text.LLVM.PP where
+
+import Text.LLVM.AST
+import Text.LLVM.Triple.AST (TargetTriple)
+import Text.LLVM.Triple.Print (printTriple)
+
+import Control.Applicative ((<|>))
+import Data.Bits ( shiftR, (.&.) )
+import Data.Char (isAlphaNum,isAscii,isDigit,isPrint,ord,toUpper)
+import Data.List ( intersperse, nub )
+import qualified Data.Map as Map
+import Data.Maybe (catMaybes,fromMaybe,isJust)
+import GHC.Float (castDoubleToWord64, float2Double)
+import Numeric (showHex)
+import Text.PrettyPrint.HughesPJ
+import Data.Int
+import Prelude hiding ((<>))
+
+
+-- Pretty-printer Config -------------------------------------------------------
+
+
+-- | The value used to specify the LLVM major version.  The LLVM text format
+-- (i.e. assembly code) changes with different versions of LLVM, so this value is
+-- used to select the version the output should be generated for.
+--
+-- At the current time, changes primarily occur when the LLVM major version
+-- changes, and this is expected to be the case going forward, so it is
+-- sufficient to reference the LLVM version by the single major version number.
+-- There is one exception and one possible future exception to this approach:
+--
+--  1. During LLVM v3, there were changes in 3.5, 3.6, 3.7, and 3.8.  There are
+--     explicit @ppLLVMnn@ function entry points for those versions, but in the
+--     event that a numerical value is needed, we note the serendipitous fact
+--     that prior to LLVM 4, there are exactly 4 versions we need to
+--     differentiate and can therefore assign the values of 0, 1, 2, and 3 to
+--     those versions (and we have no intention of supporting any other pre-4.0
+--     versions at this point).
+--
+--  2. If at some future date, there are text format changes associated with a
+--     minor version, then the LLVM version designation here will need to be
+--     enhanced and made more sophisticated.  At the present time, the likelihood
+--     of that is small enough that the current simple implementation is a
+--     benefit over a more complex mechanism that might not be needed.
+--
+type LLVMVer = Int
+
+-- | Helpers for specifying the LLVM versions prior to v4
+llvmV3_5, llvmV3_6, llvmV3_7, llvmV3_8 :: LLVMVer
+llvmV3_5 = 0
+llvmV3_6 = 1
+llvmV3_7 = 2
+llvmV3_8 = 3
+
+-- | This value should be updated when support is added for new LLVM versions;
+-- this is used for defaulting and otherwise reporting the maximum LLVM version
+-- known to be supported.
+llvmVlatest :: LLVMVer
+llvmVlatest = 19
+
+
+-- | The differences between various versions of the llvm textual AST.
+newtype Config = Config { cfgVer :: LLVMVer }
+
+withConfig :: Config -> ((?config :: Config) => a) -> a
+withConfig cfg body = let ?config = cfg in body
+
+
+ppLLVM :: LLVMVer -> ((?config :: Config) => a) -> a
+ppLLVM llvmver = withConfig Config { cfgVer = llvmver }
+
+ppLLVM35, ppLLVM36, ppLLVM37, ppLLVM38 :: ((?config :: Config) => a) -> a
+
+ppLLVM35 = withConfig Config { cfgVer = llvmV3_5 }
+ppLLVM36 = withConfig Config { cfgVer = llvmV3_6 }
+ppLLVM37 = withConfig Config { cfgVer = llvmV3_7 }
+ppLLVM38 = withConfig Config { cfgVer = llvmV3_8 }
+
+llvmVer :: (?config :: Config) => LLVMVer
+llvmVer = cfgVer ?config
+
+llvmVerToString :: LLVMVer -> String
+llvmVerToString 0 = "3.5"
+llvmVerToString 1 = "3.6"
+llvmVerToString 2 = "3.7"
+llvmVerToString 3 = "3.8"
+llvmVerToString n
+  | n >= 4    = show n
+  | otherwise = error $ "Invalid LLVMVer: " ++ show n
+
+-- | This is a helper function for when a list of parameters is gated by a
+-- condition (usually the llvmVer value).
+when' :: Monoid a => Bool -> a -> a
+when' c l = if c then l else mempty
+
+
+-- | This type encapsulates the ability to convert an object into Doc
+-- format. Using this abstraction allows for a consolidated representation of the
+-- declaration.  Most pretty-printing for LLVM elements will have a @'Fmt' a@
+-- function signature for that element.
+type Fmt a = (?config :: Config) => a -> Doc
+
+
+-- | The LLVMPretty class has instances for most AST elements.  It allows the
+-- conversion of an AST element (and its sub-elements) into a Doc assembly format
+-- by simply using the 'llvmPP' method rather than needing to explicitly invoke
+-- the specific pretty-printing function for that element.
+class LLVMPretty a where llvmPP :: Fmt a
+
+instance LLVMPretty Module where llvmPP = ppModule
+instance LLVMPretty Symbol where llvmPP = ppSymbol
+instance LLVMPretty Ident  where llvmPP = ppIdent
+
+
+-- Modules ---------------------------------------------------------------------
+
+ppModule :: Fmt Module
+ppModule m = foldr ($+$) empty
+  $ ppSourceName (modSourceName m)
+  : ppTargetTriple (modTriple m)
+  : ppDataLayout (modDataLayout m)
+  : ppInlineAsm  (modInlineAsm m)
+  : concat [ map ppTypeDecl    (modTypes m)
+           , map ppGlobal      (modGlobals m)
+           , map ppGlobalAlias (modAliases m)
+           , map ppDeclare     (modDeclares m)
+           , map ppDefine      (modDefines m)
+           , map ppNamedMd     (modNamedMd m)
+           , map ppUnnamedMd   (modUnnamedMd m)
+           , map ppComdat      (Map.toList (modComdat m))
+           ]
+
+
+-- Source filename -------------------------------------------------------------
+
+ppSourceName :: Fmt (Maybe String)
+ppSourceName Nothing   = empty
+ppSourceName (Just sn) = "source_filename" <+> char '=' <+> doubleQuotes (text sn)
+
+-- Metadata --------------------------------------------------------------------
+
+ppNamedMd :: Fmt NamedMd
+ppNamedMd nm =
+  sep [ ppMetadata (text (nmName nm)) <+> char '='
+      , ppMetadata (braces (commas (map (ppMetadata . int) (nmValues nm)))) ]
+
+ppUnnamedMd :: Fmt UnnamedMd
+ppUnnamedMd um =
+  sep [ ppMetadata (int (umIndex um)) <+> char '='
+      , distinct <+> ppValMd (umValues um) ]
+  where
+  distinct | umDistinct um = "distinct"
+           | otherwise     = empty
+
+
+-- Aliases ---------------------------------------------------------------------
+
+ppGlobalAlias :: Fmt GlobalAlias
+ppGlobalAlias g = ppSymbol (aliasName g)
+              <+> char '='
+              <+> ppMaybe ppLinkage (aliasLinkage g)
+              <+> ppMaybe ppVisibility (aliasVisibility g)
+              <+> body
+  where
+  val  = aliasTarget g
+  body = case val of
+    ValSymbol _sym -> ppType (aliasType g) <+> ppValue val
+    _              -> ppValue val
+
+
+-- Target triple ---------------------------------------------------------------
+
+-- | Pretty print a 'TargetTriple'
+ppTargetTriple :: Fmt TargetTriple
+ppTargetTriple triple = "target" <+> "triple" <+> char '='
+    <+> doubleQuotes (text (printTriple triple))
+
+-- Data Layout -----------------------------------------------------------------
+
+-- | Pretty print a data layout specification.
+ppDataLayout :: Fmt DataLayout
+ppDataLayout [] = empty
+ppDataLayout ls = "target" <+> "datalayout" <+> char '='
+    <+> doubleQuotes (hcat (intersperse (char '-') (map ppLayoutSpec ls)))
+
+-- | Pretty print a single layout specification.
+ppLayoutSpec :: Fmt LayoutSpec
+ppLayoutSpec ls =
+  case ls of
+    BigEndian                 -> char 'E'
+    LittleEndian              -> char 'e'
+    PointerSize ps            -> char 'p' <> ppPointerSize ps
+    IntegerSize sz            -> char 'i' <> ppStorage sz
+    VectorSize  sz            -> char 'v' <> ppStorage sz
+    FloatSize   sz            -> char 'f' <> ppStorage sz
+    StackObjSize sz           -> char 's' <> ppStorage sz
+    AggregateSize Nothing a   -> char 'a' <> char ':' <> ppAlignment a
+    AggregateSize (Just s) a  -> char 'a' <> int s <> char ':' <> ppAlignment a
+    NativeIntSize szs         ->
+      char 'n' <> hcat (punctuate (char ':') (map int szs))
+    StackAlign a              -> char 'S' <> int a
+    ProgramAddrSpace as       -> char 'P' <> int as
+    GlobalAddrSpace as        -> char 'G' <> int as
+    AllocaAddrSpace as        -> char 'A' <> int as
+    FunctionPointerAlign ty abi ->
+      char 'F' <> ppFunctionPointerAlignType ty <> int abi
+    Mangling m                -> char 'm' <> char ':' <> ppMangling m
+    NonIntegralPointerSpaces asl ->
+      "ni:" <> hcat (punctuate (char ':') (map int asl))
+
+ppPointerSize :: Fmt PointerSize
+ppPointerSize ps =
+  if ptrAddrSpace ps == 0
+  then char ':' <> ppStorage (ptrStorage ps)
+       <> ppOptColonInt (ptrAddrIndexSize ps)
+  else int (ptrAddrSpace ps) <> char ':' <> ppStorage (ptrStorage ps)
+       <> ppOptColonInt (ptrAddrIndexSize ps)
+
+ppStorage :: Fmt Storage
+ppStorage s = int (storageSize s) <> char ':'
+              <> ppAlignment (storageAlignment s)
+
+ppAlignment :: Fmt Alignment
+ppAlignment a = int (alignABI a) <> ppOptColonInt (alignPreferred a)
+
+ppOptColonInt :: Fmt (Maybe Int)
+ppOptColonInt = \case
+  Nothing -> empty
+  Just i  -> char ':' <> int i
+
+ppFunctionPointerAlignType :: Fmt FunctionPointerAlignType
+ppFunctionPointerAlignType ty =
+  case ty of
+    IndependentOfFunctionAlign -> char 'i'
+    MultipleOfFunctionAlign -> char 'n'
+
+ppMangling :: Fmt Mangling
+ppMangling ElfMangling         = char 'e'
+ppMangling GoffMangling        = char 'l'
+ppMangling MipsMangling        = char 'm'
+ppMangling MachOMangling       = char 'o'
+ppMangling WindowsCoffMangling = char 'w'
+ppMangling WindowsX86CoffMangling = char 'x'
+ppMangling XCoffMangling       = char 'a'
+
+
+-- Inline Assembly -------------------------------------------------------------
+
+-- | Pretty-print the inline assembly block.
+ppInlineAsm :: Fmt InlineAsm
+ppInlineAsm  = foldr ($+$) empty . map ppLine
+  where
+  ppLine l = "module asm" <+> doubleQuotes (text l)
+
+
+-- Identifiers -----------------------------------------------------------------
+
+ppIdent :: Fmt Ident
+ppIdent (Ident n)
+  | validIdentifier n = char '%' <> text n
+  | otherwise         = char '%' <> ppStringLiteral n
+
+-- | According to the LLVM Language Reference Manual, the regular
+-- expression for LLVM identifiers is "[-a-zA-Z$._][-a-zA-Z$._0-9]".
+-- Identifiers may also be strings of one or more decimal digits.
+validIdentifier :: String -> Bool
+validIdentifier [] = False
+validIdentifier s@(c0 : cs)
+  | isDigit c0 = all isDigit cs
+  | otherwise  = all isIdentChar s
+  where
+  isIdentChar :: Char -> Bool
+  isIdentChar c = isAlphaNum c || c `elem` ("-$._" :: [Char])
+
+
+-- Symbols ---------------------------------------------------------------------
+
+ppSymbol :: Fmt Symbol
+ppSymbol (Symbol n)
+  | validIdentifier n = char '@' <> text n
+  | otherwise         = char '@' <> ppStringLiteral n
+
+
+-- Types -----------------------------------------------------------------------
+
+ppPrimType :: Fmt PrimType
+ppPrimType Label          = "label"
+ppPrimType Void           = "void"
+ppPrimType (Integer i)    = char 'i' <> integer (toInteger i)
+ppPrimType (FloatType ft) = ppFloatType ft
+ppPrimType X86mmx         = "x86mmx"
+ppPrimType Metadata       = "metadata"
+
+ppFloatType :: Fmt FloatType
+ppFloatType Half      = "half"
+ppFloatType Float     = "float"
+ppFloatType Double    = "double"
+ppFloatType Fp128     = "fp128"
+ppFloatType X86_fp80  = "x86_fp80"
+ppFloatType PPC_fp128 = "ppc_fp128"
+
+ppType :: Fmt Type
+ppType (PrimType pt)     = ppPrimType pt
+ppType (Alias i)         = ppIdent i
+ppType (Array len ty)    = brackets (integral len <+> char 'x' <+> ppType ty)
+ppType (PtrTo ty addr)   = ppType ty <+> ppAddrSpace addr <> char '*'
+ppType (PtrOpaque addr)  = "ptr" <+> ppAddrSpace addr
+ppType (Struct ts)       = structBraces (commas (map ppType ts))
+ppType (PackedStruct ts) = angles (structBraces (commas (map ppType ts)))
+ppType (FunTy r as va)   = ppType r <> ppArgList va (map ppType as)
+ppType (Vector len pt)   = angles (integral len <+> char 'x' <+> ppType pt)
+ppType Opaque            = "opaque"
+
+-- | If the address space is default (0), print nothing; otherwise, print
+-- @addrspace(n)@.
+ppAddrSpace :: Fmt AddrSpace
+ppAddrSpace (AddrSpace 0) = empty
+ppAddrSpace (AddrSpace n) = "addrspace(" <> integer (toInteger n) <> ")"
+
+ppTypeDecl :: Fmt TypeDecl
+ppTypeDecl td = ppIdent (typeName td) <+> char '='
+            <+> "type" <+> ppType (typeValue td)
+
+
+-- Declarations ----------------------------------------------------------------
+
+ppGlobal :: Fmt Global
+ppGlobal g = ppSymbol (globalSym g) <+> char '='
+         <+> ppGlobalAttrs (isJust $ globalValue g) (globalAttrs g)
+         <+> ppType (globalType g) <+> ppMaybe ppValue (globalValue g)
+          <> ppAlign (globalAlign g)
+          <> ppGlobalMetadata (Map.toList (globalMetadata g))
+
+ppGlobalMetadata :: Fmt [(String, ValMd' BlockLabel)]
+ppGlobalMetadata mds
+  | null mds  = empty
+  | otherwise = comma <+> commas (map step mds)
+  where
+  step (l,md) = ppMetadata (text l) <+> ppValMd md
+
+-- | Pretty-print Global Attributes (usually associated with a global variable
+-- declaration). The first argument to ppGlobalAttrs indicates whether there is a
+-- value associated with this global declaration: a global declaration with a
+-- value should not be identified as \"external\" and \"default\" visibility,
+-- whereas one without a value may have those attributes.
+
+ppGlobalAttrs :: Bool -> Fmt GlobalAttrs
+ppGlobalAttrs hasValue ga
+    -- LLVM 3.8 does not emit or parse linkage information w/ hidden visibility
+    | llvmVer <= llvmV3_8
+    , Just HiddenVisibility <- gaVisibility ga =
+            ppVisibility HiddenVisibility <+> constant
+    | Just External <- gaLinkage ga
+    , Just DefaultVisibility <- gaVisibility ga
+    , hasValue =
+        -- Just show the value, no "external" or "default".  This is based on
+        -- empirical testing as described in the comment above (testing the
+        -- following 6 configurations:
+        --   * uninitialized scalar
+        --   * uninitialized structure
+        --   * initialized scalar
+        --   * initialized structure
+        --   * external scalar
+        --   * external structure
+        constant
+    | otherwise = ppMaybe ppLinkage (gaLinkage ga)
+              <+> ppAddrSpace (gaAddrSpace ga)
+              <+> ppMaybe ppVisibility (gaVisibility ga)
+              <+> constant
+  where
+  constant | gaConstant ga = "constant"
+           | otherwise     = "global"
+
+ppDeclare :: Fmt Declare
+ppDeclare d = "declare"
+          <+> ppMaybe ppLinkage (decLinkage d)
+          <+> ppMaybe ppVisibility (decVisibility d)
+          <+> ppType (decRetType d)
+          <+> ppSymbol (decName d)
+           <> ppArgList (decVarArgs d) (map ppType (decArgs d))
+          <+> hsep (ppFunAttr <$> decAttrs d)
+          <> maybe empty ((char ' ' <>) . ppComdatName) (decComdat d)
+
+ppComdatName :: Fmt String
+ppComdatName s = "comdat" <> parens (char '$' <> text s)
+
+ppComdat :: Fmt (String,SelectionKind)
+ppComdat (n,k) = ppComdatName n <+> char '=' <+> text "comdat" <+> ppSelectionKind k
+
+ppSelectionKind :: Fmt SelectionKind
+ppSelectionKind k =
+    case k of
+      ComdatAny             -> "any"
+      ComdatExactMatch      -> "exactmatch"
+      ComdatLargest         -> "largest"
+      ComdatNoDuplicates    -> "noduplicates"
+      ComdatSameSize        -> "samesize"
+
+ppDefine :: Fmt Define
+ppDefine d = "define"
+         <+> ppMaybe ppLinkage (defLinkage d)
+         <+> ppMaybe ppVisibility (defVisibility d)
+         <+> ppType (defRetType d)
+         <+> ppSymbol (defName d)
+          <> ppArgList (defVarArgs d) (map (ppTyped ppIdent) (defArgs d))
+         <+> hsep (ppFunAttr <$> defAttrs d)
+         <+> ppMaybe (\s  -> "section" <+> doubleQuotes (text s)) (defSection d)
+         <+> ppMaybe (\gc -> "gc" <+> ppGC gc) (defGC d)
+         <+> ppMds (defMetadata d)
+         <+> char '{'
+         $+$ vcat (map ppBasicBlock (defBody d))
+         $+$ char '}'
+  where
+  ppMds mdm =
+    case Map.toList mdm of
+      [] -> empty
+      mds -> hsep [ "!" <> text k <+> ppValMd md | (k, md) <- mds ]
+
+-- FunAttr ---------------------------------------------------------------------
+
+ppFunAttr :: Fmt FunAttr
+ppFunAttr a =
+  case a of
+    AlignStack w    -> text "alignstack" <> parens (int w)
+    Alwaysinline    -> text "alwaysinline"
+    Builtin         -> text "builtin"
+    Cold            -> text "cold"
+    Convergent      -> onlyOnLLVM llvmV3_7 "Convergent" "convergent"
+    InaccessibleMemOnly -> onlyOnLLVM llvmV3_8 "InaccessibleMemOnly" "inaccessiblememonly"
+    Inlinehint      -> text "inlinehint"
+    Jumptable       -> text "jumptable"
+    Minsize         -> text "minsize"
+    Naked           -> text "naked"
+    Nobuiltin       -> text "nobuiltin"
+    Noduplicate     -> text "noduplicate"
+    Noimplicitfloat -> text "noimplicitfloat"
+    Noinline        -> text "noinline"
+    Nonlazybind     -> text "nonlazybind"
+    Noredzone       -> text "noredzone"
+    Noreturn        -> text "noreturn"
+    Nounwind        -> text "nounwind"
+    Optnone         -> text "optnone"
+    Optsize         -> text "optsize"
+    Readnone        -> text "readnone"
+    Readonly        -> text "readonly"
+    ReturnsTwice    -> text "returns_twice"
+    SanitizeAddress -> text "sanitize_address"
+    SanitizeMemory  -> text "sanitize_memory"
+    SanitizeThread  -> text "sanitize_thread"
+    SSP             -> text "ssp"
+    SSPreq          -> text "sspreq"
+    SSPstrong       -> text "sspstrong"
+    UWTable         -> text "uwtable"
+
+-- Basic Blocks ----------------------------------------------------------------
+
+ppLabelDef :: Fmt BlockLabel
+ppLabelDef (Named (Ident l)) = text l <> char ':'
+ppLabelDef (Anon i)          = char ';' <+> "<label>:" <+> int i
+
+ppLabel :: Fmt BlockLabel
+ppLabel (Named l) = ppIdent l
+ppLabel (Anon i)  = char '%' <> int i
+
+ppBasicBlock :: Fmt BasicBlock
+ppBasicBlock bb = ppMaybe ppLabelDef (bbLabel bb)
+              $+$ nest 2 (vcat (map ppStmt (bbStmts bb)))
+
+
+-- Statements ------------------------------------------------------------------
+
+ppStmt :: Fmt Stmt
+ppStmt stmt = case stmt of
+  Result var i drs mds -> ppDebugRecords drs (ppIdent var <+> char '='
+                                              <+> ppInstr i
+                                               <> ppAttachedMetadata mds)
+  Effect i drs mds     -> ppDebugRecords drs (ppInstr i
+                                              <> ppAttachedMetadata mds)
+
+ppAttachedMetadata :: Fmt [(String,ValMd)]
+ppAttachedMetadata mds
+  | null mds  = empty
+  | otherwise = comma <+> commas (map step mds)
+  where
+  step (l,md) = ppMetadata (text l) <+> ppValMd md
+
+
+-- Linkage ---------------------------------------------------------------------
+
+ppLinkage :: Fmt Linkage
+ppLinkage linkage = case linkage of
+  Private                  -> "private"
+  LinkerPrivate            -> "linker_private"
+  LinkerPrivateWeak        -> "linker_private_weak"
+  LinkerPrivateWeakDefAuto -> "linker_private_weak_def_auto"
+  Internal                 -> "internal"
+  AvailableExternally      -> "available_externally"
+  Linkonce                 -> "linkonce"
+  Weak                     -> "weak"
+  Common                   -> "common"
+  Appending                -> "appending"
+  ExternWeak               -> "extern_weak"
+  LinkonceODR              -> "linkonce_ddr"
+  WeakODR                  -> "weak_odr"
+  External                 -> "external"
+  DLLImport                -> "dllimport"
+  DLLExport                -> "dllexport"
+
+ppVisibility :: Fmt Visibility
+ppVisibility v = case v of
+    DefaultVisibility   -> "default"
+    HiddenVisibility    -> "hidden"
+    ProtectedVisibility -> "protected"
+
+ppGC :: Fmt GC
+ppGC  = doubleQuotes . text . getGC
+
+
+-- Expressions -----------------------------------------------------------------
+
+ppTyped :: Fmt a -> Fmt (Typed a)
+ppTyped fmt ty = ppType (typedType ty) <+> fmt (typedValue ty)
+
+ppSignBits :: Bool -> Fmt Bool
+ppSignBits nuw nsw = opt nuw "nuw" <+> opt nsw "nsw"
+
+ppFMF1 :: Fmt FMF
+ppFMF1 Ffast = "fast"
+ppFMF1 Fnnan = "nnan"
+ppFMF1 Fninf = "ninf"
+ppFMF1 Fnsz = "nsz"
+ppFMF1 Farcp = "arcp"
+ppFMF1 Fcontract = "contract"
+ppFMF1 Fafn = "afn"
+ppFMF1 Freassoc = "reassoc"
+
+ppFMF :: Fmt [FMF]
+ppFMF = hsep . map ppFMF1
+
+ppExact :: Fmt Bool
+ppExact e = opt e "exact"
+
+ppArithOp :: Fmt ArithOp
+ppArithOp (Add nuw nsw) = "add" <+> ppSignBits nuw nsw
+ppArithOp (FAdd fmf)    = "fadd" <+> ppFMF fmf
+ppArithOp (Sub nuw nsw) = "sub" <+> ppSignBits nuw nsw
+ppArithOp (FSub fmf)    = "fsub" <+> ppFMF fmf
+ppArithOp (Mul nuw nsw) = "mul" <+> ppSignBits nuw nsw
+ppArithOp (FMul fmf)    = "fmul" <+> ppFMF fmf
+ppArithOp (UDiv e)      = "udiv" <+> ppExact e
+ppArithOp (SDiv e)      = "sdiv" <+> ppExact e
+ppArithOp (FDiv fmf)    = "fdiv" <+> ppFMF fmf
+ppArithOp URem          = "urem"
+ppArithOp SRem          = "srem"
+ppArithOp (FRem fmf)    = "frem" <+> ppFMF fmf
+
+ppUnaryArithOp :: Fmt UnaryArithOp
+ppUnaryArithOp (FNeg fmf) = "fneg" <+> ppFMF fmf
+
+ppBitOp :: Fmt BitOp
+ppBitOp (Shl nuw nsw) = "shl"  <+> ppSignBits nuw nsw
+ppBitOp (Lshr e)      = "lshr" <+> ppExact e
+ppBitOp (Ashr e)      = "ashr" <+> ppExact e
+ppBitOp And           = "and"
+ppBitOp Or            = "or"
+ppBitOp Xor           = "xor"
+
+ppConvOp :: Fmt ConvOp
+ppConvOp (Trunc nuw nsw) = "trunc" <+> ppSignBits nuw nsw
+ppConvOp (ZExt nneg)  = "zext" <+> opt nneg "nneg"
+ppConvOp SExt     = "sext"
+ppConvOp FpTrunc  = "fptrunc"
+ppConvOp FpExt    = "fpext"
+ppConvOp FpToUi   = "fptoui"
+ppConvOp FpToSi   = "fptosi"
+ppConvOp (UiToFp nneg) = "uitofp" <+> opt nneg "nneg"
+ppConvOp SiToFp   = "sitofp"
+ppConvOp PtrToInt = "ptrtoint"
+ppConvOp IntToPtr = "inttoptr"
+ppConvOp BitCast  = "bitcast"
+
+ppAtomicOrdering :: Fmt AtomicOrdering
+ppAtomicOrdering Unordered = text "unordered"
+ppAtomicOrdering Monotonic = text "monotonic"
+ppAtomicOrdering Acquire   = text "acquire"
+ppAtomicOrdering Release   = text "release"
+ppAtomicOrdering AcqRel    = text "acq_rel"
+ppAtomicOrdering SeqCst    = text "seq_cst"
+
+ppAtomicOp :: Fmt AtomicRWOp
+ppAtomicOp AtomicXchg = "xchg"
+ppAtomicOp AtomicAdd  = "add"
+ppAtomicOp AtomicSub  = "sub"
+ppAtomicOp AtomicAnd  = "and"
+ppAtomicOp AtomicNand = "nand"
+ppAtomicOp AtomicOr   = "or"
+ppAtomicOp AtomicXor  = "xor"
+ppAtomicOp AtomicMax  = "max"
+ppAtomicOp AtomicMin  = "min"
+ppAtomicOp AtomicUMax = "umax"
+ppAtomicOp AtomicUMin = "umin"
+ppAtomicOp AtomicFAdd = onlyOnLLVM 9 "AtomicFAdd" "fadd"
+ppAtomicOp AtomicFSub = onlyOnLLVM 9 "AtomicFSub" "fsub"
+ppAtomicOp AtomicFMax = onlyOnLLVM 15 "AtomicFMax" "fmax"
+ppAtomicOp AtomicFMin = onlyOnLLVM 15 "AtomicFMin" "fmin"
+ppAtomicOp AtomicUIncWrap = onlyOnLLVM 16 "AtomicUIncWrap" "uinc_wrap"
+ppAtomicOp AtomicUDecWrap = onlyOnLLVM 16 "AtomicUDecWrap" "udec_wrap"
+
+ppScope ::  Fmt (Maybe String)
+ppScope Nothing = empty
+ppScope (Just s) = "syncscope" <> parens (doubleQuotes (text s))
+
+ppInstr :: Fmt Instr
+ppInstr instr = case instr of
+  Ret tv                 -> "ret" <+> ppTyped ppValue tv
+  RetVoid                -> "ret void"
+  Arith op l r           -> ppArithOp op <+> ppTyped ppValue l
+                         <> comma <+> ppValue r
+  UnaryArith op a        -> ppUnaryArithOp op <+> ppTyped ppValue a
+  Bit op l r             -> ppBitOp op <+> ppTyped ppValue l
+                         <> comma <+> ppValue r
+  Conv op a ty           -> ppConvOp op <+> ppTyped ppValue a
+                        <+> "to" <+> ppType ty
+  Call tc fmf ty f args  -> ppCall tc fmf ty f args
+  CallBr ty f args u es  -> ppCallBr ty f args u es
+  Alloca ty len align    -> ppAlloca ty len align
+  Load vol ty ptr mo ma  -> ppLoad vol ty ptr mo ma
+  Store vol a ptr mo ma  -> ppStore vol a ptr mo ma
+  Fence scope order      -> "fence" <+> ppScope scope <+> ppAtomicOrdering order
+  CmpXchg w v p a n s o o' -> "cmpxchg" <+> opt w "weak"
+                         <+> opt v "volatile"
+                         <+> ppTyped ppValue p
+                         <> comma <+> ppTyped ppValue a
+                         <> comma <+> ppTyped ppValue n
+                         <+> ppScope s
+                         <+> ppAtomicOrdering o
+                         <+> ppAtomicOrdering o'
+  AtomicRW v op p a s o  -> "atomicrmw"
+                         <+> opt v "volatile"
+                         <+> ppAtomicOp op
+                         <+> ppTyped ppValue p
+                         <> comma <+> ppTyped ppValue a
+                         <+> ppScope s
+                         <+> ppAtomicOrdering o
+  ICmp samesign op l r   -> "icmp" <+> opt samesign "samesign" <+> ppICmpOp op
+                        <+> ppTyped ppValue l <> comma <+> ppValue r
+  FCmp fmf op l r        -> "fcmp" <+> ppFMF fmf <+> ppFCmpOp op
+                        <+> ppTyped ppValue l <> comma <+> ppValue r
+  Phi fmf ty vls         -> "phi" <+> ppFMF fmf <+> ppType ty
+                        <+> commas (map ppPhiArg vls)
+  Select fmf c t f       -> "select" <+> ppFMF fmf <+> ppTyped ppValue c
+                         <> comma <+> ppTyped ppValue t
+                         <> comma <+> ppTyped ppValue (f <$ t)
+  ExtractValue v is      -> "extractvalue" <+> ppTyped ppValue v
+                         <> comma <+> (commas (map integral is))
+  InsertValue a v is     -> "insertvalue" <+> ppTyped ppValue a
+                         <> comma <+> ppTyped ppValue v
+                         <> comma <+> commas (map integral is)
+  ShuffleVector a b m    -> "shufflevector" <+> ppTyped ppValue a
+                         <> comma <+> ppTyped ppValue (b <$ a)
+                         <> comma <+> ppTyped ppValue m
+  GEP gf ty ptr ixs      -> ppGEP gf ty ptr ixs
+  Comment str            -> char ';' <+> text str
+  Jump i                 -> "br"
+                        <+> ppTypedLabel i
+  Br c t f               -> "br" <+> ppTyped ppValue c
+                         <> comma <+> ppType (PrimType Label)
+                        <+> ppLabel t
+                         <> comma <+> ppType (PrimType Label)
+                        <+> ppLabel f
+  Invoke ty f args to uw -> ppInvoke ty f args to uw
+  Unreachable            -> "unreachable"
+  Unwind                 -> "unwind"
+  VaArg al t             -> "va_arg" <+> ppTyped ppValue al
+                         <> comma <+> ppType t
+  ExtractElt v i         -> "extractelement"
+                        <+> ppTyped ppValue v
+                         <> comma <+> ppVectorIndex i
+  InsertElt v e i        -> "insertelement"
+                        <+> ppTyped ppValue v
+                         <> comma <+> ppTyped ppValue e
+                         <> comma <+> ppVectorIndex i
+  IndirectBr d ls        -> "indirectbr"
+                        <+> ppTyped ppValue d
+                         <> comma
+                        <+> char '['
+                        <+> commas (map ppTypedLabel ls)
+                        <+> char ']'
+  Switch c d ls          -> "switch"
+                        <+> ppTyped ppValue c
+                         <> comma <+> ppTypedLabel d
+                        <+> char '['
+                         $$ nest 2 (vcat (map (ppSwitchEntry (typedType c)) ls))
+                         $$ char ']'
+  LandingPad ty mfn c cs  ->
+        case mfn of
+            Just fn -> "landingpad"
+                        <+> ppType ty
+                        <+> "personality"
+                        <+> ppTyped ppValue fn
+                        $$ nest 2 (ppClauses c cs)
+            Nothing -> "landingpad"
+                        <+> ppType ty
+                        $$ nest 2 (ppClauses c cs)
+  Resume tv           -> "resume" <+> ppTyped ppValue tv
+  Freeze tv           -> "freeze" <+> ppTyped ppValue tv
+
+ppLoad :: Bool
+       -> Type
+       -> Typed (Value' BlockLabel)
+       -> Maybe AtomicOrdering
+       -> Fmt (Maybe Align)
+ppLoad volatile ty ptr mo ma =
+  "load" <+> (if volatile then "volatile" else empty)
+         <+> (if isAtomic   then "atomic" else empty)
+         <+> (if isExplicit then explicit else empty)
+         <+> ppTyped ppValue ptr
+         <+> ordering
+          <> ppAlign ma
+
+  where
+  isAtomic = isJust mo
+
+  isExplicit = llvmVer >= llvmV3_7
+
+  ordering =
+    case mo of
+      Just ao -> ppAtomicOrdering ao
+      _       -> empty
+
+  explicit = ppType ty <> comma
+
+ppStore :: Bool
+        -> Typed (Value' BlockLabel)
+        -> Typed (Value' BlockLabel)
+        -> Maybe AtomicOrdering
+        -> Fmt (Maybe Align)
+ppStore volatile ptr val mo ma =
+  "store" <+> (if volatile then "volatile" else empty)
+          <+> (if isJust mo  then "atomic" else empty)
+          <+> ppTyped ppValue ptr <> comma
+          <+> ppTyped ppValue val
+          <+> case mo of
+                Just ao -> ppAtomicOrdering ao
+                _       -> empty
+          <> ppAlign ma
+
+
+ppClauses :: Bool -> Fmt [Clause]
+ppClauses isCleanup cs = vcat (cleanup : map ppClause cs)
+  where
+  cleanup | isCleanup = "cleanup"
+          | otherwise = empty
+
+ppClause :: Fmt Clause
+ppClause c = case c of
+  Catch  tv -> "catch"  <+> ppTyped ppValue tv
+  Filter tv -> "filter" <+> ppTyped ppValue tv
+
+
+ppTypedLabel :: Fmt BlockLabel
+ppTypedLabel i = ppType (PrimType Label) <+> ppLabel i
+
+ppSwitchEntry :: Type -> Fmt (Integer,BlockLabel)
+ppSwitchEntry ty (i,l) = ppType ty <+> integer i <> comma <+> ppTypedLabel l
+
+ppVectorIndex :: Fmt Value
+ppVectorIndex i = ppType (PrimType (Integer 32)) <+> ppValue i
+
+ppAlign :: Fmt (Maybe Align)
+ppAlign Nothing      = empty
+ppAlign (Just align) = comma <+> "align" <+> int align
+
+ppAlloca :: Type -> Maybe (Typed Value) -> Fmt (Maybe Int)
+ppAlloca ty mbLen mbAlign = "alloca" <+> ppType ty <> len <> align
+  where
+  len = fromMaybe empty $ do
+    l <- mbLen
+    return (comma <+> ppTyped ppValue l)
+  align = fromMaybe empty $ do
+    a <- mbAlign
+    return (comma <+> "align" <+> int a)
+
+ppCall :: Bool -> [FMF] -> Type -> Value -> Fmt [Typed Value]
+ppCall tc fmf ty f args
+  | tc        = "tail" <+> body
+  | otherwise = body
+  where
+  body = "call" <+> ppFMF fmf <+> ppCallSym ty f
+      <> parens (commas (map (ppTyped ppValue) args))
+
+-- | Note that the textual syntax changed in LLVM 10 (@callbr@ was introduced in
+-- LLVM 9).
+ppCallBr :: Type -> Value -> [Typed Value] -> BlockLabel -> Fmt [BlockLabel]
+ppCallBr ty f args to indirectDests =
+  "callbr"
+     <+> ppCallSym ty f <> parens (commas (map (ppTyped ppValue) args))
+     <+> "to" <+> ppLab to <+> brackets (commas (map ppLab indirectDests))
+  where
+    ppLab l = ppType (PrimType Label) <+> ppLabel l
+
+-- | Print out the @<ty>|<fnty> <fnptrval>@ portion of a @call@, @callbr@, or
+-- @invoke@ instruction, where:
+--
+-- * @<ty>@ is the return type.
+--
+-- * @<fnty>@ is the overall function type.
+--
+-- * @<fnptrval>@ is a pointer value, where the memory it points to is treated
+--   as a value of type @<fnty>@.
+--
+-- The LLVM Language Reference Manual indicates that either @<ty>@ or @<fnty>@
+-- can be used, but in practice, @<ty>@ is typically preferred unless the
+-- function type involves varargs. We adopt the same convention here.
+ppCallSym :: Type -> Fmt Value
+ppCallSym ty val = pp_ty <+> ppValue val
+  where
+    pp_ty =
+      case ty of
+        FunTy res args va
+          |  va
+          -> ppType res <+> ppArgList va (map ppType args)
+          |  otherwise
+          -> ppType res
+        _ -> ppType ty
+
+ppGEP :: [GEPAttr] -> Type -> Typed Value -> Fmt [Typed Value]
+ppGEP gf ty ptr ixs =
+  "getelementptr"
+    <+> (if inlineIsBool
+         then (if GEP_Inbounds `elem` gf then "inbounds" else empty)
+         else ppGepFlags gf)
+    <+> (if isExplicit then explicit else empty)
+    <+> commas (map (ppTyped ppValue) (ptr:ixs))
+  where
+  isExplicit = llvmVer >= llvmV3_7
+  inlineIsBool = llvmVer < 19
+
+  explicit = ppType ty <> comma
+
+ppInvoke :: Type -> Value -> [Typed Value] -> BlockLabel -> Fmt BlockLabel
+ppInvoke ty f args to uw = body
+  where
+  body = "invoke" <+> ppCallSym ty f
+      <> parens (commas (map (ppTyped ppValue) args))
+     <+> "to" <+> ppType (PrimType Label) <+> ppLabel to
+     <+> "unwind" <+> ppType (PrimType Label) <+> ppLabel uw
+
+ppPhiArg :: Fmt (Value,BlockLabel)
+ppPhiArg (v,l) = char '[' <+> ppValue v <> comma <+> ppLabel l <+> char ']'
+
+ppICmpOp :: Fmt ICmpOp
+ppICmpOp Ieq  = "eq"
+ppICmpOp Ine  = "ne"
+ppICmpOp Iugt = "ugt"
+ppICmpOp Iuge = "uge"
+ppICmpOp Iult = "ult"
+ppICmpOp Iule = "ule"
+ppICmpOp Isgt = "sgt"
+ppICmpOp Isge = "sge"
+ppICmpOp Islt = "slt"
+ppICmpOp Isle = "sle"
+
+ppFCmpOp :: Fmt FCmpOp
+ppFCmpOp Ffalse = "false"
+ppFCmpOp Foeq   = "oeq"
+ppFCmpOp Fogt   = "ogt"
+ppFCmpOp Foge   = "oge"
+ppFCmpOp Folt   = "olt"
+ppFCmpOp Fole   = "ole"
+ppFCmpOp Fone   = "one"
+ppFCmpOp Ford   = "ord"
+ppFCmpOp Fueq   = "ueq"
+ppFCmpOp Fugt   = "ugt"
+ppFCmpOp Fuge   = "uge"
+ppFCmpOp Fult   = "ult"
+ppFCmpOp Fule   = "ule"
+ppFCmpOp Fune   = "une"
+ppFCmpOp Funo   = "uno"
+ppFCmpOp Ftrue  = "true"
+
+ppValue' :: Fmt i -> Fmt (Value' i)
+ppValue' pp val = case val of
+  ValInteger i       -> integer i
+  ValBool b          -> ppBool b
+  -- Note: for +Inf/-Inf/NaNs, we want to output the bit-correct sequence
+  ValFloat f         ->
+    -- WARNING: You should **not** use `castFloatToWord32` or `float` here.  LLVM IR does not
+    -- support 32-bit floating point constants, instead it wants to see 32-bit compatible 64-bit
+    -- constants.  We want to preserve the exact mantissa (zero-extended to the right from 23 to
+    -- 52 bits), which happens to be the behavior of `float2Double`.
+    text "0x" <> text (showHex (castDoubleToWord64 (float2Double f)) "")
+  ValDouble d        ->
+    if isInfinite d || isNaN d
+      then text "0x" <> text (showHex (castDoubleToWord64 d) "")
+      else double d
+  ValFP80 (FP80_LongDouble e s) ->
+    -- shown as 0xK<<20-hex-digits>>, per
+    -- https://llvm.org/docs/LangRef.html#simple-constants
+    let pad n | n < 0x10  = shows (0::Int) . showHex n
+              | otherwise = showHex n
+        fld v i = pad ((v `shiftR` (i * 8)) .&. 0xff)
+    in "0xK" <> text (foldr (fld e) (foldr (fld s) "" $ reverse [0..7::Int]) [1, 0])
+  ValIdent i         -> ppIdent i
+  ValSymbol s        -> ppSymbol s
+  ValNull            -> "null"
+  ValArray ty es     -> brackets
+                      $ commas (map (ppTyped (ppValue' pp) . Typed ty) es)
+  ValVector ty es   -> angles $ commas
+                     $ map (ppTyped (ppValue' pp) . Typed ty) es
+  ValStruct fs       -> structBraces (commas (map (ppTyped (ppValue' pp)) fs))
+  ValPackedStruct fs -> angles
+                      $ structBraces (commas (map (ppTyped (ppValue' pp)) fs))
+  ValString s        -> char 'c' <> ppStringLiteral (map (toEnum . fromIntegral) s)
+  ValConstExpr ce    -> ppConstExpr' pp ce
+  ValUndef           -> "undef"
+  ValLabel l         -> pp l
+  ValZeroInit        -> "zeroinitializer"
+  ValAsm s a i c     -> ppAsm s a i c
+  ValMd m            -> ppValMd' pp m
+  ValPoison          -> "poison"
+
+ppValue :: Fmt Value
+ppValue = ppValue' ppLabel
+
+ppValMd' :: Fmt i -> Fmt (ValMd' i)
+ppValMd' pp m = case m of
+  ValMdString str   -> ppMetadata (ppStringLiteral str)
+  ValMdValue tv     -> ppTyped (ppValue' pp) tv
+  ValMdRef i        -> ppMetadata (int i)
+  ValMdNode vs      -> ppMetadataNode' pp vs
+  ValMdLoc l        -> ppDebugLoc' pp l
+  ValMdDebugInfo di -> ppDebugInfo' pp di
+
+ppValMd :: Fmt ValMd
+ppValMd = ppValMd' ppLabel
+
+ppDebugLoc' :: Fmt i -> Fmt (DebugLoc' i)
+ppDebugLoc' pp dl = (if llvmVer >= llvmV3_7 then "!DILocation"
+                                            else "!MDLocation")
+             <> parens (commas [ "line:"   <+> integral (dlLine dl)
+                               , "column:" <+> integral (dlCol dl)
+                               , "scope:"  <+> ppValMd' pp (dlScope dl)
+                               ] <> mbIA <> mbImplicit <>
+                        when' (llvmVer >= 21) (mbAtomGroup <> mbAtomRank))
+
+  where
+  mbIA = case dlIA dl of
+           Just md -> comma <+> "inlinedAt:" <+> ppValMd' pp md
+           Nothing -> empty
+  mbImplicit = if dlImplicit dl then comma <+> "implicit" else empty
+  mbAtomGroup = if dlAtomGroup dl > 0
+                  then comma <+> "atomGroup:" <+> integral (dlAtomGroup dl)
+                  else empty
+  mbAtomRank = if dlAtomRank dl > 0
+                 then comma <+> "atomRank:" <+> integral (dlAtomRank dl)
+                 else empty
+
+ppDebugLoc :: Fmt DebugLoc
+ppDebugLoc = ppDebugLoc' ppLabel
+
+ppTypedValMd :: Fmt ValMd
+ppTypedValMd  = ppTyped ppValMd . Typed (PrimType Metadata)
+
+ppMetadata :: Fmt Doc
+ppMetadata body = char '!' <> body
+
+ppMetadataNode' :: Fmt i -> Fmt [Maybe (ValMd' i)]
+ppMetadataNode' pp vs = ppMetadata (braces (commas (map arg vs)))
+  where arg = maybe ("null") (ppValMd' pp)
+
+ppMetadataNode :: Fmt [Maybe ValMd]
+ppMetadataNode = ppMetadataNode' ppLabel
+
+ppStringLiteral :: Fmt String
+ppStringLiteral  = doubleQuotes . text . concatMap escape
+  where
+  escape c | c == '"' || c == '\\'  = '\\' : showHex (fromEnum c) ""
+           | isAscii c && isPrint c = [c]
+           | otherwise              = '\\' : pad (ord c)
+
+  pad n | n < 0x10  = '0' : map toUpper (showHex n "")
+        | otherwise =       map toUpper (showHex n "")
+
+ppAsm :: Bool -> Bool -> String -> Fmt String
+ppAsm s a i c =
+  "asm" <+> sideeffect <+> alignstack
+        <+> ppStringLiteral i <> comma <+> ppStringLiteral c
+  where
+  sideeffect | s         = "sideeffect"
+             | otherwise = empty
+
+  alignstack | a         = "alignstack"
+             | otherwise = empty
+
+
+ppConstExpr' :: Fmt i -> Fmt (ConstExpr' i)
+ppConstExpr' pp expr =
+  case expr of
+    ConstGEP optflgs mrng ty ptr ixs  ->
+      "getelementptr"
+        <+> ppGepFlags optflgs
+        <+> ppRange mrng
+        <+> parens (commas (
+                       let argIndices = 0 : [0..] -- rval, ptr, then ixs indices
+                       in reverse  -- ppTyp's pushes entries to the listg head
+                          $ foldl (ppTyp's mrng) [ppType ty]
+                          $ zip argIndices (ptr:ixs)))
+    ConstConv op tv t  ->
+      let droppedIn18 = case op of
+                          -- https://github.com/llvm/llvm-project commit e4a4122 dropped ZExt and SExt
+                          ZExt _ -> True
+                          SExt -> True
+                          -- https://github.com/llvm/llvm-project commit 17764d2 dropped FpTrunc through SiToFP
+                          FpTrunc -> True
+                          FpExt -> True
+                          FpToUi -> True
+                          FpToSi -> True
+                          UiToFp _ -> True
+                          SiToFp -> True
+                          _ -> False
+          ppConstConv = ppConvOp op <+> parens (ppTyp' tv <+> "to" <+> ppType t)
+      in if droppedIn18
+         then droppedInLLVM 18 "fptrunc/fpext/fptoui/fptosi/uitofp/sitofp constexprs" ppConstConv
+         else ppConstConv
+    ConstSelect c l r  ->
+      droppedInLLVM 17 "select constexpr" -- https://github.com/llvm/llvm-project commit bbfb13a
+
+      $ "select" <+> parens (commas [ ppTyp' c, ppTyp' l , ppTyp' r])
+    ConstBlockAddr t l -> "blockaddress" <+> parens (ppVal' (typedValue t) <> comma <+> pp l)
+    ConstFCmp       op a b -> droppedInLLVM 19 "fcmp constexprs"
+                              $ "fcmp" <+> ppFCmpOp op <+> ppTupleT a b
+    ConstICmp       op a b -> droppedInLLVM 19 "icmp constexprs"
+                              $ "icmp" <+> ppICmpOp op <+> ppTupleT a b
+    ConstArith      op a b -> ppArithOp op <+> ppTuple a b
+    ConstUnaryArith op a   -> ppUnaryArithOp op <+> ppTyp' a
+    ConstBit        op@(Shl _ _) a b -> droppedInLLVM 19 "shl constexprs"
+                                        $ ppBitOp op   <+> ppTuple a b
+    ConstBit        Xor a b -> ppBitOp Xor <+> ppTuple a b
+    ConstBit        op a b -> droppedInLLVM 18 "and/or/lshr/ashr constexprs"
+                              $ ppBitOp op <+> ppTuple a b
+  where ppTuple  a b = parens $ ppTyped ppVal' a <> comma <+> ppVal' b
+        ppTupleT a b = parens $ ppTyped ppVal' a <> comma <+> ppTyp' b
+        ppVal'       = ppValue' pp
+        ppTyp'       = ppTyped ppVal'
+        ppTyp's mrng a (i,t) =
+          let inrangeMark = if Just (RangeIndex i) == mrng then "inrange" else empty
+          in (inrangeMark <+> ppTyp' t) : a
+        ppRange =
+          let ppR = \case
+                RangeIndex _i -> empty -- handled in ppTyp's
+                Range _ l u ->
+                  "inrange(" <> integral l <> ", " <> integral u <> ")"
+          in maybe empty ppR
+
+ppGepFlags :: Fmt [GEPAttr]
+ppGepFlags s =
+  let fltr = if GEP_Inbounds `elem` s
+             then
+               -- inbounds implies nusw, but LLVM stipulates that if
+               -- inbounds is present, nusw is not also printed.
+               filter (/= GEP_NUSW)
+             else id
+      ppF = \case
+        GEP_Inbounds -> "inbounds"
+        GEP_NUSW -> "nusw"
+        GEP_NUW -> "nuw"
+  in foldl (\o f -> o <+> ppF f) empty $ fltr $ nub s
+
+ppConstExpr :: Fmt ConstExpr
+ppConstExpr = ppConstExpr' ppLabel
+
+-- DWARF Debug Info ------------------------------------------------------------
+
+ppDebugInfo' :: Fmt i -> Fmt (DebugInfo' i)
+ppDebugInfo' pp di = case di of
+  DebugInfoBasicType bt         -> ppDIBasicType' pp bt
+  DebugInfoCompileUnit cu       -> ppDICompileUnit' pp cu
+  DebugInfoCompositeType ct     -> ppDICompositeType' pp ct
+  DebugInfoDerivedType dt       -> ppDIDerivedType' pp dt
+  DebugInfoEnumerator nm v u    -> ppDIEnumerator nm v u
+  DebugInfoExpression e         -> ppDIExpression e
+  DebugInfoFile f               -> ppDIFile f
+  DebugInfoGlobalVariable gv    -> ppDIGlobalVariable' pp gv
+  DebugInfoGlobalVariableExpression gv -> ppDIGlobalVariableExpression' pp gv
+  DebugInfoLexicalBlock lb      -> ppDILexicalBlock' pp lb
+  DebugInfoLexicalBlockFile lbf -> ppDILexicalBlockFile' pp lbf
+  DebugInfoLocalVariable lv     -> ppDILocalVariable' pp lv
+  DebugInfoSubprogram sp        -> ppDISubprogram' pp sp
+  DebugInfoSubrange sr          -> ppDISubrange' pp sr
+  DebugInfoSubroutineType st    -> ppDISubroutineType' pp st
+  DebugInfoNameSpace ns         -> ppDINameSpace' pp ns
+  DebugInfoTemplateTypeParameter dttp  -> ppDITemplateTypeParameter' pp dttp
+  DebugInfoTemplateValueParameter dtvp -> ppDITemplateValueParameter' pp dtvp
+  DebugInfoImportedEntity diip         -> ppDIImportedEntity' pp diip
+  DebugInfoLabel dil            -> ppDILabel' pp dil
+  DebugInfoArgList args         -> ppDIArgList' pp args
+  DebugInfoAssignID             -> "!DIAssignID()"
+  -- DebugRecordDeclare drd        -> ppDbgRecDeclare' pp drd
+
+-- Prints DebugRecords (introduced in LLVM 19) which replace debug intrinsics and
+-- unlike the intrinsics that follow the instruction, the debug records *precede*
+-- the Instruction they affect.
+ppDebugRecords :: [DebugRecord' BlockLabel] -> Fmt Doc
+ppDebugRecords [] = id
+ppDebugRecords drs = ((nest 2 $ vcat (ppDebugRecord' ppLabel <$> drs)) $$)
+
+ppDebugRecord' :: Fmt lab -> Fmt (DebugRecord' lab)
+ppDebugRecord' pl = \case
+  DebugRecordValue drv -> ppDbgRecValue' pl drv
+  DebugRecordDeclare drd -> ppDbgRecDeclare' pl drd
+  DebugRecordAssign dra -> ppDbgRecAssign' pl dra
+  DebugRecordValueSimple dvs -> ppDbgRecValueSimple' pl dvs
+  DebugRecordLabel drl -> ppDbgRecLabel' pl drl
+
+ppDbgRecValue' :: Fmt lab -> Fmt (DbgRecValue' lab)
+ppDbgRecValue' pl dr =
+  "#dbg_value"
+  <> parens (commas [ ppValMd' pl $ drvValAsMetadata dr
+                    , ppValMd' pl $ drvLocalVariable dr
+                    , ppValMd' pl $ drvExpression dr
+                    , ppValMd' pl $ drvLocation dr
+                    ])
+
+ppDbgRecDeclare' :: Fmt lab -> Fmt (DbgRecDeclare' lab)
+ppDbgRecDeclare' pl dr =
+  "#dbg_declare"
+  <> parens (commas [ ppValMd' pl $ drdValAsMetadata dr
+                    , ppValMd' pl $ drdLocalVariable dr
+                    , ppValMd' pl $ drdExpression dr
+                    , ppValMd' pl $ drdLocation dr
+                    ])
+
+ppDbgRecAssign' :: Fmt lab -> Fmt (DbgRecAssign' lab)
+ppDbgRecAssign' pl dr =
+  "#dbg_assign"
+  <> parens (commas [ ppValMd' pl $ draValAsMetadata dr
+                    , ppValMd' pl $ draLocalVariable dr
+                    , ppValMd' pl $ draExpression dr
+                    , ppValMd' pl $ draAssignID dr
+                    , ppValMd' pl $ draValAsMetadataAddr dr
+                    , ppValMd' pl $ draExpressionAddr dr
+                    , ppValMd' pl $ draLocation dr
+                    ])
+
+ppDbgRecValueSimple' :: Fmt lab -> Fmt (DbgRecValueSimple' lab)
+ppDbgRecValueSimple' pl dr =
+  "#dbg_value"
+  <> parens (commas [ ppTyped (ppValue' pl) $ drvsValue dr
+                    , ppValMd' pl $ drvsLocalVariable dr
+                    , ppValMd' pl $ drvsExpression dr
+                    , ppValMd' pl $ drvsLocation dr
+                    ])
+
+ppDbgRecLabel' :: Fmt lab -> Fmt (DbgRecLabel' lab)
+ppDbgRecLabel' pl dr =
+  "#dbg_label"
+  <> parens (commas [ ppValMd' pl $ drlLabel dr
+                    , ppValMd' pl $ drlLocation dr
+                    ])
+
+ppDebugInfo :: Fmt DebugInfo
+ppDebugInfo = ppDebugInfo' ppLabel
+
+ppDIImportedEntity' :: Fmt i -> Fmt (DIImportedEntity' i)
+ppDIImportedEntity' pp ie = "!DIImportedEntity"
+  <> parens (mcommas [ pure ("tag:"    <+> integral (diieTag ie))
+                     , (("scope:"  <+>) . ppValMd' pp) <$> diieScope ie
+                     , (("entity:" <+>) . ppValMd' pp) <$> diieEntity ie
+                     , (("file:"   <+>) . ppValMd' pp) <$> diieFile ie
+                     , pure ("line:"   <+> integral (diieLine ie))
+                     , (("name:"   <+>) . text)        <$> diieName ie
+                     ])
+
+ppDIImportedEntity :: Fmt DIImportedEntity
+ppDIImportedEntity = ppDIImportedEntity' ppLabel
+
+ppDILabel' :: Fmt i -> Fmt (DILabel' i)
+ppDILabel' pp ie = "!DILabel"
+  <> parens (mcommas $
+       [ (("scope:"  <+>) . ppValMd' pp) <$> dilScope ie
+       , pure ("name:" <+> ppStringLiteral (dilName ie))
+       , (("file:"   <+>) . ppValMd' pp) <$> dilFile ie
+       , pure ("line:"   <+> integral (dilLine ie))
+       ] ++
+       when' (llvmVer >= 21)
+       [ pure ("column:" <+> integral (dilColumn ie))
+       , pure ("isArtificial:" <+> ppBool (dilIsArtificial ie))
+       , (("coroSuspendIdx:" <+>) . integral) <$> dilCoroSuspendIdx ie
+       ])
+
+ppDILabel :: Fmt DILabel
+ppDILabel = ppDILabel' ppLabel
+
+ppDINameSpace' :: Fmt i -> Fmt (DINameSpace' i)
+ppDINameSpace' pp ns = "!DINameSpace"
+  <> parens (mcommas [ ("name:"   <+>) . text <$> (dinsName ns)
+                     , pure ("scope:"  <+> ppValMd' pp (dinsScope ns))
+                     , pure ("file:"   <+> ppValMd' pp (dinsFile ns))
+                     , pure ("line:"   <+> integral (dinsLine ns))
+                     ])
+
+ppDINameSpace :: Fmt DINameSpace
+ppDINameSpace = ppDINameSpace' ppLabel
+
+ppDITemplateTypeParameter' :: Fmt i -> Fmt (DITemplateTypeParameter' i)
+ppDITemplateTypeParameter' pp tp = "!DITemplateTypeParameter"
+  <> parens (mcommas [ ("name:"  <+>) . text        <$> dittpName tp
+                     , ("type:"  <+>) . ppValMd' pp <$> dittpType tp
+                     ])
+
+ppDITemplateTypeParameter :: Fmt DITemplateTypeParameter
+ppDITemplateTypeParameter = ppDITemplateTypeParameter' ppLabel
+
+ppDITemplateValueParameter' :: Fmt i -> Fmt (DITemplateValueParameter' i)
+ppDITemplateValueParameter' pp vp = "!DITemplateValueParameter"
+  <> parens (mcommas [ pure ("tag:"   <+> integral (ditvpTag vp))
+                     , ("name:"  <+>) . text        <$> ditvpName vp
+                     , ("type:"  <+>) . ppValMd' pp <$> ditvpType vp
+                     , pure ("value:" <+> ppValMd' pp (ditvpValue vp))
+                     ])
+
+ppDITemplateValueParameter :: Fmt DITemplateValueParameter
+ppDITemplateValueParameter = ppDITemplateValueParameter' ppLabel
+
+ppDIBasicType' :: Fmt i -> Fmt (DIBasicType' i)
+ppDIBasicType' pp bt = "!DIBasicType"
+  <> parens (mcommas
+       [ pure ("tag:"      <+> integral (dibtTag bt))
+       , pure ("name:"     <+> doubleQuotes (text (dibtName bt)))
+       ,     (("size:"     <+>) . ppSizeOrOffsetValMd' pp) <$> dibtSize bt
+       , pure ("align:"    <+> integral (dibtAlign bt))
+       , pure ("encoding:" <+> integral (dibtEncoding bt))
+       ,     (("flags:"    <+>) . integral)
+             <$> dibtFlags bt
+       , if dibtNumExtraInhabitants bt > 0
+         then pure ("numExtraInhabitants:" <+> integral (dibtNumExtraInhabitants bt))
+         else Nothing
+       ])
+
+ppDICompileUnit' :: Fmt i -> Fmt (DICompileUnit' i)
+ppDICompileUnit' pp cu = "!DICompileUnit"
+  <> parens (mcommas $
+       [ pure ("language:"              <+> integral (dicuLanguage cu))
+       ,     (("file:"                  <+>) . ppValMd' pp) <$> (dicuFile cu)
+       ,     (("producer:"              <+>) . doubleQuotes . text)
+             <$> (dicuProducer cu)
+       , pure ("isOptimized:"           <+> ppBool (dicuIsOptimized cu))
+       , pure ("flags:"                 <+> ppFlags (dicuFlags cu))
+       , pure ("runtimeVersion:"        <+> integral (dicuRuntimeVersion cu))
+       ,     (("splitDebugFilename:"    <+>) . doubleQuotes . text)
+             <$> (dicuSplitDebugFilename cu)
+       , pure ("emissionKind:"          <+> integral (dicuEmissionKind cu))
+       ,     (("enums:"                 <+>) . ppValMd' pp) <$> (dicuEnums cu)
+       ,     (("retainedTypes:"         <+>) . ppValMd' pp) <$> (dicuRetainedTypes cu)
+       ,     (("subprograms:"           <+>) . ppValMd' pp) <$> (dicuSubprograms cu)
+       ,     (("globals:"               <+>) . ppValMd' pp) <$> (dicuGlobals cu)
+       ,     (("imports:"               <+>) . ppValMd' pp) <$> (dicuImports cu)
+       ,     (("macros:"                <+>) . ppValMd' pp) <$> (dicuMacros cu)
+       , pure ("dwoId:"                 <+> integral (dicuDWOId cu))
+       , pure ("splitDebugInlining:"    <+> ppBool (dicuSplitDebugInlining cu))
+       , pure ("debugInfoForProfiling:" <+> ppBool (dicuDebugInfoForProf cu))
+       , pure ("nameTableKind:"         <+> integral (dicuNameTableKind cu))
+       ]
+       ++
+       when' (llvmVer >= 11)
+       [ pure ("rangesBaseAddress:"     <+> ppBool (dicuRangesBaseAddress cu))
+       ,     (("sysroot:"               <+>) . doubleQuotes . text)
+             <$> (dicuSysRoot cu)
+       ,     (("sdk:"                   <+>) . doubleQuotes . text)
+             <$> (dicuSDK cu)
+       ]
+       )
+
+
+ppDICompileUnit :: Fmt DICompileUnit
+ppDICompileUnit = ppDICompileUnit' ppLabel
+
+ppFlags :: Fmt (Maybe String)
+ppFlags mb = doubleQuotes (maybe empty text mb)
+
+ppDICompositeType' :: Fmt i -> Fmt (DICompositeType' i)
+ppDICompositeType' pp ct = "!DICompositeType"
+  <> parens (mcommas
+       [ pure ("tag:"            <+> integral (dictTag ct))
+       ,     (("name:"           <+>) . doubleQuotes . text) <$> (dictName ct)
+       ,     (("file:"           <+>) . ppValMd' pp) <$> (dictFile ct)
+       , pure ("line:"           <+> integral (dictLine ct))
+       ,     (("baseType:"       <+>) . ppValMd' pp) <$> (dictBaseType ct)
+       ,     (("size:"           <+>) . ppSizeOrOffsetValMd' pp) <$> dictSize ct
+       , pure ("align:"          <+> integral (dictAlign ct))
+       ,     (("offset:"         <+>) . ppSizeOrOffsetValMd' pp) <$> dictOffset ct
+       , pure ("flags:"          <+> integral (dictFlags ct))
+       ,     (("elements:"       <+>) . ppValMd' pp) <$> (dictElements ct)
+       , pure ("runtimeLang:"    <+> integral (dictRuntimeLang ct))
+       ,     (("vtableHolder:"   <+>) . ppValMd' pp) <$> (dictVTableHolder ct)
+       ,     (("templateParams:" <+>) . ppValMd' pp) <$> (dictTemplateParams ct)
+       ,     (("identifier:"     <+>) . doubleQuotes . text)
+             <$> (dictIdentifier ct)
+       ,     (("discriminator:"  <+>) . ppValMd' pp) <$> (dictDiscriminator ct)
+       ,     (("associated:"     <+>) . ppValMd' pp) <$> (dictAssociated ct)
+       ,     (("allocated:"      <+>) . ppValMd' pp) <$> (dictAllocated ct)
+       ,     (("rank:"           <+>) . ppValMd' pp) <$> (dictRank ct)
+       ,     (("annotations:"    <+>) . ppValMd' pp) <$> (dictAnnotations ct)
+       , if dictNumExtraInhabitants ct > 0
+         then pure ("numExtraInhabitants:" <+> integral (dictNumExtraInhabitants ct))
+         else Nothing
+       ,     (("specification:"  <+>) . ppValMd' pp) <$> (dictSpecification ct)
+       ,     (("enumKind:"       <+>) . integral) <$> (dictEnumKind ct)
+       ,     (("bitStride:"      <+>) . ppValMd' pp) <$> (dictBitStride ct)
+       ])
+
+ppDICompositeType :: Fmt DICompositeType
+ppDICompositeType = ppDICompositeType' ppLabel
+
+ppDIDerivedType' :: Fmt i -> Fmt (DIDerivedType' i)
+ppDIDerivedType' pp dt = "!DIDerivedType"
+  <> parens (mcommas
+       [ pure ("tag:"       <+> integral (didtTag dt))
+       ,     (("name:"      <+>) . doubleQuotes . text) <$> (didtName dt)
+       ,     (("file:"      <+>) . ppValMd' pp) <$> (didtFile dt)
+       , pure ("line:"      <+> integral (didtLine dt))
+       ,     (("scope:"     <+>) . ppValMd' pp) <$> (didtScope dt)
+       ,      ("baseType:"  <+>) <$> (ppValMd' pp <$> didtBaseType dt <|> Just "null")
+       ,     (("size:"      <+>) . ppSizeOrOffsetValMd' pp) <$> didtSize dt
+       , pure ("align:"     <+> integral (didtAlign dt))
+       ,     (("offset:"    <+>) . ppSizeOrOffsetValMd' pp) <$> didtOffset dt
+       , pure ("flags:"     <+> integral (didtFlags dt))
+       ,     (("extraData:" <+>) . ppValMd' pp) <$> (didtExtraData dt)
+       ,     (("dwarfAddressSpace:" <+>) . integral) <$> didtDwarfAddressSpace dt
+       ,     (("annotations:" <+>) . ppValMd' pp) <$> (didtAnnotations dt)
+       ])
+
+ppDIDerivedType :: Fmt DIDerivedType
+ppDIDerivedType = ppDIDerivedType' ppLabel
+
+ppDIEnumerator :: String -> Integer -> Fmt Bool
+ppDIEnumerator n v u = "!DIEnumerator"
+  <> parens (commas [ "name:"  <+> doubleQuotes (text n)
+                    , "value:" <+> integral v
+                    , "isUnsigned:" <+> ppBool u
+                    ])
+
+ppDIExpression :: Fmt DIExpression
+ppDIExpression e = "!DIExpression"
+  <> parens (commas (map integral (dieElements e)))
+
+ppDIFile :: Fmt DIFile
+ppDIFile f = "!DIFile"
+  <> parens (commas [ "filename:"  <+> doubleQuotes (text (difFilename f))
+                    , "directory:" <+> doubleQuotes (text (difDirectory f))
+                    ])
+
+ppDIGlobalVariable' :: Fmt i -> Fmt (DIGlobalVariable' i)
+ppDIGlobalVariable' pp gv = "!DIGlobalVariable"
+  <> parens (mcommas
+       [      (("scope:"       <+>) . ppValMd' pp) <$> (digvScope gv)
+       ,      (("name:"        <+>) . doubleQuotes . text) <$> (digvName gv)
+       ,      (("linkageName:" <+>) . doubleQuotes . text)
+              <$> (digvLinkageName gv)
+       ,      (("file:"        <+>) . ppValMd' pp) <$> (digvFile gv)
+       , pure ("line:"         <+> integral (digvLine gv))
+       ,      (("type:"        <+>) . ppValMd' pp) <$> (digvType gv)
+       , pure ("isLocal:"      <+> ppBool (digvIsLocal gv))
+       , pure ("isDefinition:" <+> ppBool (digvIsDefinition gv))
+       ,      (("variable:"    <+>) . ppValMd' pp) <$> (digvVariable gv)
+       ,      (("declaration:" <+>) . ppValMd' pp) <$> (digvDeclaration gv)
+       ,      (("align:"       <+>) . integral) <$> digvAlignment gv
+       ,      (("annotations:" <+>) . ppValMd' pp) <$> (digvAnnotations gv)
+       ])
+
+ppDIGlobalVariable :: Fmt DIGlobalVariable
+ppDIGlobalVariable = ppDIGlobalVariable' ppLabel
+
+ppDIGlobalVariableExpression' :: Fmt i -> Fmt (DIGlobalVariableExpression' i)
+ppDIGlobalVariableExpression' pp gve = "!DIGlobalVariableExpression"
+  <> parens (mcommas
+       [      (("var:"  <+>) . ppValMd' pp) <$> (digveVariable gve)
+       ,      (("expr:" <+>) . ppValMd' pp) <$> (digveExpression gve)
+       ])
+
+ppDIGlobalVariableExpression :: Fmt DIGlobalVariableExpression
+ppDIGlobalVariableExpression = ppDIGlobalVariableExpression' ppLabel
+
+ppDILexicalBlock' :: Fmt i -> Fmt (DILexicalBlock' i)
+ppDILexicalBlock' pp ct = "!DILexicalBlock"
+  <> parens (mcommas
+       [     (("scope:"  <+>) . ppValMd' pp) <$> (dilbScope ct)
+       ,     (("file:"   <+>) . ppValMd' pp) <$> (dilbFile ct)
+       , pure ("line:"   <+> integral (dilbLine ct))
+       , pure ("column:" <+> integral (dilbColumn ct))
+       ])
+
+ppDILexicalBlock :: Fmt DILexicalBlock
+ppDILexicalBlock = ppDILexicalBlock' ppLabel
+
+ppDILexicalBlockFile' :: Fmt i -> Fmt (DILexicalBlockFile' i)
+ppDILexicalBlockFile' pp lbf = "!DILexicalBlockFile"
+  <> parens (mcommas
+       [ pure ("scope:"         <+> ppValMd' pp (dilbfScope lbf))
+       ,     (("file:"          <+>) . ppValMd' pp) <$> (dilbfFile lbf)
+       , pure ("discriminator:" <+> integral (dilbfDiscriminator lbf))
+       ])
+
+ppDILexicalBlockFile :: Fmt DILexicalBlockFile
+ppDILexicalBlockFile = ppDILexicalBlockFile' ppLabel
+
+ppDILocalVariable' :: Fmt i -> Fmt (DILocalVariable' i)
+ppDILocalVariable' pp lv = "!DILocalVariable"
+  <> parens (mcommas
+       [      (("scope:" <+>) . ppValMd' pp) <$> (dilvScope lv)
+       ,      (("name:"  <+>) . doubleQuotes . text) <$> (dilvName lv)
+       ,      (("file:"  <+>) . ppValMd' pp) <$> (dilvFile lv)
+       , pure ("line:"   <+> integral (dilvLine lv))
+       ,      (("type:"  <+>) . ppValMd' pp) <$> (dilvType lv)
+       , pure ("arg:"    <+> integral (dilvArg lv))
+       , pure ("flags:"  <+> integral (dilvFlags lv))
+       ,      (("align:" <+>) . integral) <$> dilvAlignment lv
+       ,      (("annotations:" <+>) . ppValMd' pp) <$> (dilvAnnotations lv)
+       ])
+
+ppDILocalVariable :: Fmt DILocalVariable
+ppDILocalVariable = ppDILocalVariable' ppLabel
+
+-- | See @writeDISubprogram@ in the LLVM source, in the file @AsmWriter.cpp@
+--
+-- Note that the textual syntax changed in LLVM 7, as the @retainedNodes@ field
+-- was called @variables@ in previous LLVM versions.
+ppDISubprogram' :: Fmt i -> Fmt (DISubprogram' i)
+ppDISubprogram' pp sp = "!DISubprogram"
+  <> parens (mcommas
+       [      (("scope:"          <+>) . ppValMd' pp) <$> (dispScope sp)
+       ,      (("name:"           <+>) . doubleQuotes . text) <$> (dispName sp)
+       ,      (("linkageName:"    <+>) . doubleQuotes . text)
+              <$> (dispLinkageName sp)
+       ,      (("file:"           <+>) . ppValMd' pp) <$> (dispFile sp)
+       , pure ("line:"            <+> integral (dispLine sp))
+       ,      (("type:"           <+>) . ppValMd' pp) <$> (dispType sp)
+       , pure ("isLocal:"         <+> ppBool (dispIsLocal sp))
+       , pure ("isDefinition:"    <+> ppBool (dispIsDefinition sp))
+       , pure ("scopeLine:"       <+> integral (dispScopeLine sp))
+       ,      (("containingType:" <+>) . ppValMd' pp) <$> (dispContainingType sp)
+       , pure ("virtuality:"      <+> integral (dispVirtuality sp))
+       , pure ("virtualIndex:"    <+> integral (dispVirtualIndex sp))
+       , pure ("flags:"           <+> integral (dispFlags sp))
+       , pure ("isOptimized:"     <+> ppBool (dispIsOptimized sp))
+       ,      (("unit:"           <+>) . ppValMd' pp) <$> (dispUnit sp)
+       ,      (("templateParams:" <+>) . ppValMd' pp) <$> (dispTemplateParams sp)
+       ,      (("declaration:"    <+>) . ppValMd' pp) <$> (dispDeclaration sp)
+       ,      (("retainedNodes:"  <+>) . ppValMd' pp) <$> (dispRetainedNodes sp)
+       ,      (("thrownTypes:"    <+>) . ppValMd' pp) <$> (dispThrownTypes sp)
+       ,      (("annotations:"    <+>) . ppValMd' pp) <$> (dispAnnotations sp)
+       ])
+
+ppDISubprogram :: Fmt DISubprogram
+ppDISubprogram = ppDISubprogram' ppLabel
+
+ppDISubrange' :: Fmt i -> Fmt (DISubrange' i)
+ppDISubrange' pp sr =
+  "!DISubrange"
+  <> parens
+  (mcommas
+   -- LLVM < 7: count and lowerBound as signed int 64
+   -- LLVM < 11: count as ValMd, lowerBound as signed in 64
+   -- LLVM >= 11: ValMd of count, lowerBound, upperBound, and stride
+   -- Valid through LLVM 17.
+   -- See AST.hs description for more details on the structure.
+   -- See https://github.com/llvm/llvm-project/blob/431969e/llvm/lib/IR/AsmWriter.cpp#L1888-L1927
+   -- for more details on output generation.
+   [
+     (("count:" <+>) . ppInt64ValMd' (llvmVer >= 7) pp) <$> disrCount sr
+   , (("lowerBound:" <+>) . ppInt64ValMd' (llvmVer >= 11) pp) <$> disrLowerBound sr
+   , when' (llvmVer >= 11)
+     $ (("upperBound:" <+>) . ppInt64ValMd' True pp) <$> disrUpperBound sr
+   , when' (llvmVer >= 11)
+     $ (("stride:" <+>) . ppInt64ValMd' True pp) <$> disrStride sr
+   ])
+
+ppDISubrange :: Fmt DISubrange
+ppDISubrange = ppDISubrange' ppLabel
+
+ppDISubroutineType' :: Fmt i -> Fmt (DISubroutineType' i)
+ppDISubroutineType' pp st = "!DISubroutineType"
+  <> parens (commas
+       [ "flags:" <+> integral (distFlags st)
+       , "types:" <+> fromMaybe "null" (ppValMd' pp <$> (distTypeArray st))
+       ])
+
+ppDISubroutineType :: Fmt DISubroutineType
+ppDISubroutineType = ppDISubroutineType' ppLabel
+
+ppDIArgList' :: Fmt i -> Fmt (DIArgList' i)
+ppDIArgList' pp args = "!DIArgList"
+  <> parens (commas (map (ppValMd' pp) (dialArgs args)))
+
+ppDIArgList :: Fmt DIArgList
+ppDIArgList = ppDIArgList' ppLabel
+
+-- Utilities -------------------------------------------------------------------
+
+ppBool :: Fmt Bool
+ppBool b | b         = "true"
+         | otherwise = "false"
+
+-- | Build a variable-argument argument list.
+ppArgList :: Bool -> Fmt [Doc]
+ppArgList True  ds = parens (commas (ds ++ ["..."]))
+ppArgList False ds = parens (commas ds)
+
+integral :: Integral i => Fmt i
+integral  = integer . fromIntegral
+
+hex :: (Integral i, Show i) => Fmt i
+hex i = text (showHex i "0x")
+
+opt :: Bool -> Fmt Doc
+opt True  = id
+opt False = const empty
+
+-- | Print a ValMd' value as a plain signed integer (Int64) if possible.  If the
+-- ValMd' is not representable as an Int64, defer to ValMd' printing (if
+-- canFallBack is True) or print nothing (for when a ValMd is not a valid
+-- representation).
+
+ppInt64ValMd' :: Bool -> Fmt i -> Fmt (ValMd' i)
+ppInt64ValMd' canFallBack pp = go
+  where go = \case
+          ValMdValue tv
+            | PrimType (Integer _) <- typedType tv
+            , ValInteger i <- typedValue tv
+              -> integer i  -- 64 bits is the largest Int, so no conversion needed
+          o@(ValMdDebugInfo (DebugInfoGlobalVariable gv)) ->
+            case digvVariable gv of
+              Nothing -> when' canFallBack $ ppValMd' pp o
+              Just v -> go v
+          o@(ValMdDebugInfo (DebugInfoGlobalVariableExpression expr)) ->
+            case digveExpression expr of
+              Nothing -> when' canFallBack $ ppValMd' pp o
+              Just e -> go e
+          ValMdDebugInfo (DebugInfoLocalVariable lv) ->
+            integer $ fromIntegral $ dilvArg lv  -- ??
+          -- ValMdRef _idx -> mempty -- no table here to look this up...
+          o -> when' canFallBack $ ppValMd' pp o
+
+-- | Print the size or offset of a type-related metadata node. This value can
+-- either be an integer literal (in which case the bare literal is printed), or
+-- in LLVM 21 or later, it can be a more complicated metadata expression (in
+-- which case the metadata is pretty-printed).
+ppSizeOrOffsetValMd' :: Fmt i -> Fmt (ValMd' i)
+ppSizeOrOffsetValMd' pp = \case
+  ValMdValue tv
+    | ValInteger i <- typedValue tv
+      -> integer i
+  o -> when' (llvmVer >= 21) $ ppValMd' pp o
+
+
+commas :: Fmt [Doc]
+commas  = fsep . punctuate comma
+
+-- | Helpful for all of the optional fields that appear in the
+-- metadata values
+mcommas :: Fmt [Maybe Doc]
+mcommas = commas . catMaybes
+
+angles :: Fmt Doc
+angles d = char '<' <> d <> char '>'
+
+structBraces :: Fmt Doc
+structBraces body = char '{' <+> body <+> char '}'
+
+ppMaybe :: Fmt a -> Fmt (Maybe a)
+ppMaybe  = maybe empty
+
+-- | Throw an error if the @?config@ version is older than the given version. The
+-- String indicates which constructor is unavailable in the error message.
+onlyOnLLVM :: (?config :: Config) => LLVMVer -> String -> a -> a
+onlyOnLLVM fromVer name
+  | llvmVer >= fromVer = id
+  | otherwise          = error $ name ++ " is supported only on LLVM >= "
+                                 ++ llvmVerToString fromVer
+
+-- | Throw an error if the @?config@ version is older than the given version. The
+-- String indicates which constructor is unavailable in the error message.
+droppedInLLVM :: (?config :: Config) => LLVMVer -> String -> a -> a
+droppedInLLVM fromVer name
+  | llvmVer >= fromVer = error $ name ++ " is supported only up to LLVM >= "
+                                 ++ llvmVerToString fromVer
+  | otherwise          = id
diff --git a/llvm-pretty/src/Text/LLVM/Parser.hs b/llvm-pretty/src/Text/LLVM/Parser.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Parser.hs
@@ -0,0 +1,142 @@
+module Text.LLVM.Parser where
+
+import Text.LLVM.AST
+
+import Data.Char (chr)
+import Data.Word (Word32, Word64)
+import Text.Parsec
+import Text.Parsec.String
+
+
+-- Identifiers and Symbols -----------------------------------------------------
+
+pNameChar :: Parser Char
+pNameChar = letter <|> digit <|> oneOf "-$._"
+
+pHexEscape :: Parser Char
+pHexEscape =
+  do _ <- char '\\'
+     a <- hexDigit
+     b <- hexDigit
+     return (chr (read ("0x" ++ [a, b])))
+
+pStringChar :: Parser Char
+pStringChar = noneOf "\"\\" <|> pHexEscape
+
+pName :: Parser String
+pName = many1 pNameChar <|> quotes (many1 pStringChar)
+
+pIdent :: Parser Ident
+pIdent = Ident <$> (char '%' >> pName)
+
+pSymbol :: Parser Symbol
+pSymbol = Symbol <$> (char '@' >> pName)
+
+
+-- Types -----------------------------------------------------------------------
+
+pWord32 :: Parser Word32
+pWord32 = read <$> many1 digit
+
+pWord64 :: Parser Word64
+pWord64 = read <$> many1 digit
+
+pPrimType :: Parser PrimType
+pPrimType = choice
+  [ Integer <$> try (char 'i' >> pWord32)
+  , FloatType <$> try pFloatType
+  , try (string "label")    >> return Label
+  , try (string "void")     >> return Void
+  , try (string "x86mmx")   >> return X86mmx
+  , try (string "metadata") >> return Metadata
+  ]
+
+pFloatType :: Parser FloatType
+pFloatType = choice
+  [ try (string "half")      >> return Half
+  , try (string "float")     >> return Float
+  , try (string "double")    >> return Double
+  , try (string "fp128")     >> return Fp128
+  , try (string "x86_fp80")  >> return X86_fp80
+  , try (string "ppc_fp128") >> return PPC_fp128
+  ]
+
+pType :: Parser Type
+pType = pType0 >>= pFunPtr
+  where
+    pType0 :: Parser Type
+    pType0 =
+      choice
+      [ Alias <$> pIdent
+      , brackets (pNumType Array)
+      , braces (Struct <$> pTypeList)
+      , angles (braces (PackedStruct <$> pTypeList) <|> spaced (pNumType Vector))
+      , string "opaque" >> return Opaque
+      , PrimType <$> pPrimType
+      , pOpaquePtr
+      ]
+
+    pTypeList :: Parser [Type]
+    pTypeList = sepBy (spaced pType) (char ',')
+
+    pNumType :: (Word64 -> Type -> Type) -> Parser Type
+    pNumType f =
+      do n <- pWord64
+         spaces >> char 'x' >> spaces
+         t <- pType
+         return (f n t)
+
+    pArgList :: Type -> Parser Type
+    pArgList t0 = spaces >> (p1 [] <|> return (FunTy t0 [] False))
+      where
+        p1 ts =
+          (string "..." >> spaces >> return (FunTy t0 (reverse ts) True))
+          <|> (pType >>= \t -> (spaces >> p2 (t : ts)))
+        p2 ts =
+          (char ',' >> spaces >> p1 ts)
+          <|> return (FunTy t0 (reverse ts) False)
+
+    pOpaquePtr :: Parser Type
+    pOpaquePtr = do
+      _ <- string "ptr"
+      PtrOpaque <$> option defaultAddrSpace (spaces >> pAddrSpace)
+
+    pFunPtr :: Type -> Parser Type
+    pFunPtr t0 = pFun <|> pPtr <|> return t0
+      where
+        pFun = parens (pArgList t0) >>= pFunPtr
+        pPtr = do
+          addr <- option defaultAddrSpace (spaces >> pAddrSpace)
+          _ <- char '*'
+          pFunPtr (PtrTo t0 addr)
+
+    pAddrSpace :: Parser AddrSpace
+    pAddrSpace = do
+      _ <- string "addrspace("
+      n <- pWord32
+      _ <- string ")"
+      return (AddrSpace n)
+
+parseType :: String -> Either ParseError Type
+parseType = parse (pType <* eof) "<internal>"
+
+
+-- Utilities -------------------------------------------------------------------
+
+angles :: Parser a -> Parser a
+angles body = char '<' *> body <* char '>'
+
+braces :: Parser a -> Parser a
+braces body = char '{' *> body <* char '}'
+
+brackets :: Parser a -> Parser a
+brackets body = char '[' *> body <* char ']'
+
+parens :: Parser a -> Parser a
+parens body = char '(' *> body <* char ')'
+
+quotes :: Parser a -> Parser a
+quotes body = char '"' *> body <* char '"'
+
+spaced :: Parser a -> Parser a
+spaced body = spaces *> body <* spaces
diff --git a/llvm-pretty/src/Text/LLVM/Triple.hs b/llvm-pretty/src/Text/LLVM/Triple.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Triple.hs
@@ -0,0 +1,17 @@
+{- |
+Module      : Text.LLVM.Triple
+Description : AST, parsing, and printing of LLVM target triples.
+License     : BSD3
+Maintainer  : Langston Barrett
+Stability   : experimental
+-}
+
+module Text.LLVM.Triple
+  ( module Text.LLVM.Triple.AST
+  , module Text.LLVM.Triple.Parse
+  , module Text.LLVM.Triple.Print
+  ) where
+
+import Text.LLVM.Triple.AST
+import Text.LLVM.Triple.Parse
+import Text.LLVM.Triple.Print
diff --git a/llvm-pretty/src/Text/LLVM/Triple/AST.hs b/llvm-pretty/src/Text/LLVM/Triple/AST.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Triple/AST.hs
@@ -0,0 +1,467 @@
+{- |
+Module      : Text.LLVM.Triple.AST
+Description : AST of LLVM target triples.
+License     : BSD3
+Maintainer  : Langston Barrett
+Stability   : experimental
+-}
+
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StrictData #-}
+
+module Text.LLVM.Triple.AST
+  ( Arch(..)
+  , SubArch(..)
+  , Vendor(..)
+  , OS(..)
+  , Environment(..)
+  , ObjectFormat(..)
+  , TargetTriple(..)
+  ) where
+
+import Data.Data (Data)
+import Data.Typeable (Typeable)
+import GHC.Generics (Generic)
+
+-- | The constructors of this type exactly mirror the LLVM @enum ArchType@,
+-- including inconsistent labeling of endianness. Capitalization is taken from
+-- the LLVM comments, which are reproduced below.
+--
+-- Last updated: LLVM 15.0.1:
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/include/llvm/ADT/Triple.h#L46
+data Arch
+  = UnknownArch
+    -- | ARM (little endian): arm armv.* xscale
+  | ARM
+    -- | ARM (big endian): armeb
+  | ARMEB
+    -- | AArch64 (little endian): aarch64
+  | AArch64
+    -- | AArch64 (big endian): aarch64_be
+  | AArch64_BE
+    -- | AArch64 (little endian) ILP32: aarch64_32
+  | AArch64_32
+    -- | ARC: Synopsys ARC
+  | ARC
+    -- | AVR: Atmel AVR microcontroller
+  | AVR
+    -- | eBPF or extended BPF or 64-bit BPF (little endian)
+  | BPFEL
+    -- | eBPF or extended BPF or 64-bit BPF (big endian)
+  | BPFEB
+    -- | CSKY: csky
+  | CSKY
+    -- | DXIL 32-bit DirectX bytecode
+  | DXIL
+    -- | Hexagon: hexagon
+  | Hexagon
+    -- | LoongArch (32-bit): loongarch32
+  | LoongArch32
+    -- | LoongArch (64-bit): loongarch64
+  | LoongArch64
+    -- | M68k: Motorola 680x0 family
+  | M68k
+    -- | MIPS: mips mipsallegrex mipsr6
+  | MIPS
+    -- | MIPSEL: mipsel mipsallegrexe mipsr6el
+  | MIPSEL
+    -- | MIPS64: mips64 mips64r6 mipsn32 mipsn32r6
+  | MIPS64
+    -- | MIPS64EL: mips64el mips64r6el mipsn32el mipsn32r6el
+  | MIPS64EL
+    -- | MSP430: msp430
+  | MSP430
+    -- | PPC: powerpc
+  | PPC
+    -- | PPCLE: powerpc (little endian)
+  | PPCLE
+    -- | PPC64: powerpc64 ppu
+  | PPC64
+    -- | PPC64LE: powerpc64le
+  | PPC64LE
+    -- | R600: AMD GPUs HD2XXX - HD6XXX
+  | R600
+    -- | AMDGCN: AMD GCN GPUs
+  | AMDGCN
+    -- | RISC-V (32-bit): riscv32
+  | RISCV32
+    -- | RISC-V (64-bit): riscv64
+  | RISCV64
+    -- | Sparc: sparc
+  | Sparc
+    -- | Sparcv9: Sparcv9
+  | Sparcv9
+    -- | Sparc: (endianness = little). NB: 'Sparcle' is a CPU variant
+  | SparcEL
+    -- | SystemZ: s390x
+  | SystemZ
+    -- | TCE (http://tce.cs.tut.fi/): tce
+  | TCE
+    -- | TCE little endian (http://tce.cs.tut.fi/): tcele
+  | TCELE
+    -- | Thumb (little endian): thumb thumbv.*
+  | Thumb
+    -- | Thumb (big endian): thumbeb
+  | ThumbEB
+    -- | X86: i[3-9]86
+  | X86
+    -- | X86-64: amd64 x86_64
+  | X86_64
+    -- | XCore: xcore
+  | XCore
+    -- | NVPTX: 32-bit
+  | NVPTX
+    -- | NVPTX: 64-bit
+  | NVPTX64
+    -- | le32: generic little-endian 32-bit CPU (PNaCl)
+  | Le32
+    -- | le64: generic little-endian 64-bit CPU (PNaCl)
+  | Le64
+    -- | AMDIL
+  | AMDIL
+    -- | AMDIL with 64-bit pointers
+  | AMDIL64
+    -- | AMD HSAIL
+  | HSAIL
+    -- | AMD HSAIL with 64-bit pointers
+  | HSAIL64
+    -- | SPIR: standard portable IR for OpenCL 32-bit version
+  | SPIR
+    -- | SPIR: standard portable IR for OpenCL 64-bit version
+  | SPIR64
+    -- | SPIR-V with 32-bit pointers
+  | SPIRV32
+    -- | SPIR-V with 64-bit pointers
+  | SPIRV64
+    -- | Kalimba: generic kalimba
+  | Kalimba
+    -- | SHAVE: Movidius vector VLIW processors
+  | SHAVE
+    -- | Lanai: Lanai 32-bit
+  | Lanai
+    -- | WebAssembly with 32-bit pointers
+  | Wasm32
+    -- | WebAssembly with 64-bit pointers
+  | Wasm64
+    -- | 32-bit RenderScript
+  | RenderScript32 -- 32-bit RenderScript
+    -- | 64-bit RenderScript
+  | RenderScript64
+    -- | NEC SX-Aurora Vector Engine
+  | VE
+  deriving (Bounded, Data, Eq, Enum, Generic, Ord, Read, Show, Typeable)
+
+-- | A 'First'-like semigroup instance that simply drops the RHS, unless the LHS
+-- is 'UnknownArch'.
+instance Semigroup Arch where
+  a <> UnknownArch{} = a
+  UnknownArch{} <> b = b
+  a <> _ = a
+
+-- | @'mempty' == 'UnknownArch'@
+instance Monoid Arch where
+  mempty = UnknownArch
+
+-- | The constructors of this type exactly mirror the LLVM @enum SubArchType@.
+--
+-- Last updated: LLVM 15.0.1:
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/include/llvm/ADT/Triple.h#L110
+data SubArch
+  = NoSubArch
+  | ARMSubArch_v9_3a
+  | ARMSubArch_v9_2a
+  | ARMSubArch_v9_1a
+  | ARMSubArch_v9
+  | ARMSubArch_v8_8a
+  | ARMSubArch_v8_7a
+  | ARMSubArch_v8_6a
+  | ARMSubArch_v8_5a
+  | ARMSubArch_v8_4a
+  | ARMSubArch_v8_3a
+  | ARMSubArch_v8_2a
+  | ARMSubArch_v8_1a
+  | ARMSubArch_v8
+  | ARMSubArch_v8r
+  | ARMSubArch_v8m_baseline
+  | ARMSubArch_v8m_mainline
+  | ARMSubArch_v8_1m_mainline
+  | ARMSubArch_v7
+  | ARMSubArch_v7em
+  | ARMSubArch_v7m
+  | ARMSubArch_v7s
+  | ARMSubArch_v7k
+  | ARMSubArch_v7ve
+  | ARMSubArch_v6
+  | ARMSubArch_v6m
+  | ARMSubArch_v6k
+  | ARMSubArch_v6t2
+  | ARMSubArch_v5
+  | ARMSubArch_v5te
+  | ARMSubArch_v4t
+
+  | AArch64SubArch_arm64e
+
+  | KalimbaSubArch_v3
+  | KalimbaSubArch_v4
+  | KalimbaSubArch_v5
+
+  | MipsSubArch_r6
+
+  | PPCSubArch_spe
+
+  | SPIRVSubArch_v10
+  | SPIRVSubArch_v11
+  | SPIRVSubArch_v12
+  | SPIRVSubArch_v13
+  | SPIRVSubArch_v14
+  | SPIRVSubArch_v15
+  deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable)
+
+-- | A 'First'-like semigroup instance that simply drops the RHS, unless the LHS
+-- is 'NoSubArch'.
+instance Semigroup SubArch where
+  a <> NoSubArch{} = a
+  NoSubArch{} <> b = b
+  a <> _ = a
+
+-- | @'mempty' == 'NoSubArch'@
+instance Monoid SubArch where
+  mempty = NoSubArch
+
+-- | The constructors of this type exactly mirror the LLVM @enum VendorType@,
+-- including ordering and grouping.
+--
+-- Last updated: LLVM 15.0.1:
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/include/llvm/ADT/Triple.h#L162
+data Vendor
+  = UnknownVendor
+  | Apple
+  | PC
+  | SCEI
+  | Freescale
+  | IBM
+  | ImaginationTechnologies
+  | MipsTechnologies
+  | NVIDIA
+  | CSR
+  | Myriad
+  | AMD
+  | Mesa
+  | SUSE
+  | OpenEmbedded
+  deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable)
+
+-- | A 'First'-like semigroup instance that simply drops the RHS, unless the LHS
+-- is 'UnknownVendor'.
+instance Semigroup Vendor where
+  a <> UnknownVendor{} = a
+  UnknownVendor{} <> b = b
+  a <> _ = a
+
+-- | @'mempty' == 'UnknownVendor'@
+instance Monoid Vendor where
+  mempty = UnknownVendor
+
+-- | The constructors of this type exactly mirror the LLVM @enum OSType@,
+-- including ordering and grouping.
+--
+-- End-of-line comments from LLVM are reproduced as constructor comments.
+--
+-- Last updated: LLVM 15.0.1:
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/include/llvm/ADT/Triple.h#L181
+data OS
+  = UnknownOS
+  | Ananas
+  | CloudABI
+  | Darwin
+  | DragonFly
+  | FreeBSD
+  | Fuchsia
+  | IOS
+  | KFreeBSD
+  | Linux
+    -- | PS3
+  | Lv2
+  | MacOSX
+  | NetBSD
+  | OpenBSD
+  | Solaris
+  | Win32
+  | ZOS
+  | Haiku
+  | Minix
+  | RTEMS
+    -- | Native Client
+  | NaCl
+  | AIX
+    -- | NVIDIA CUDA
+  | CUDA
+    -- | NVIDIA OpenCL
+  | NVCL
+    -- | AMD HSA Runtime
+  | AMDHSA
+  | PS4
+  | PS5
+  | ELFIAMCU
+    -- | Apple tvOS
+  | TvOS
+    -- | Apple watchOS
+  | WatchOS
+    -- | Apple DriverKit
+  | DriverKit
+  | Mesa3D
+  | Contiki
+    -- | AMD PAL Runtime
+  | AMDPAL
+    -- | HermitCore Unikernel/Multikernel
+  | HermitCore
+    -- | GNU/Hurd
+  | Hurd
+    -- | Experimental WebAssembly OS
+  | WASI
+  | Emscripten
+    -- | DirectX ShaderModel
+  | ShaderModel
+  deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable)
+
+-- | A 'First'-like semigroup instance that simply drops the RHS, unless the LHS
+-- is 'UnknownOS'.
+instance Semigroup OS where
+  a <> UnknownOS{} = a
+  UnknownOS{} <> b = b
+  a <> _ = a
+
+-- | @'mempty' == 'UnknownOS'@
+instance Monoid OS where
+  mempty = UnknownOS
+
+-- | The constructors of this type exactly mirror the LLVM @enum
+-- EnvironmentType@, including ordering and grouping.
+--
+-- End-of-line comments from LLVM are reproduced as constructor comments.
+--
+-- Last updated: LLVM 15.0.1:
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/include/llvm/ADT/Triple.h#L224
+data Environment
+  = UnknownEnvironment
+
+  | GNU
+  | GNUABIN32
+  | GNUABI64
+  | GNUEABI
+  | GNUEABIHF
+  | GNUX32
+  | GNUILP32
+  | CODE16
+  | EABI
+  | EABIHF
+  | Android
+  | Musl
+  | MuslEABI
+  | MuslEABIHF
+  | MuslX32
+
+  | MSVC
+  | Itanium
+  | Cygnus
+  | CoreCLR
+    -- | Simulator variants of other systems e.g. Apple's iOS
+  | Simulator
+    -- | Mac Catalyst variant of Apple's iOS deployment target.
+  | MacABI
+
+  | Pixel
+  | Vertex
+  | Geometry
+  | Hull
+  | Domain
+  | Compute
+  | Library
+  | RayGeneration
+  | Intersection
+  | AnyHit
+  | ClosestHit
+  | Miss
+  | Callable
+  | Mesh
+  | Amplification
+  deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable)
+
+-- | A 'First'-like semigroup instance that simply drops the RHS, unless the LHS
+-- is 'UnknownEnvironment'.
+instance Semigroup Environment where
+  a <> UnknownEnvironment{} = a
+  UnknownEnvironment{} <> b = b
+  a <> _ = a
+
+-- | @'mempty' == 'UnknownEnvironment'@
+instance Monoid Environment where
+  mempty = UnknownEnvironment
+
+-- | The constructors of this type exactly mirror the LLVM @enum
+-- ObjectFormatType@, including ordering.
+--
+-- Last updated: LLVM 15.0.1:
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/include/llvm/ADT/Triple.h#L269
+data ObjectFormat
+  = UnknownObjectFormat
+  | COFF
+  | DXContainer
+  | ELF
+  | GOFF
+  | MachO
+  | SPIRV
+  | Wasm
+  | XCOFF
+  deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable)
+
+-- | A 'First'-like semigroup instance that simply drops the RHS, unless the LHS
+-- is 'UnknownObjectFormat'.
+instance Semigroup ObjectFormat where
+  a <> UnknownObjectFormat{} = a
+  UnknownObjectFormat{} <> b = b
+  a <> _ = a
+
+-- | @'mempty' == 'UnknownObjectFormat'@
+instance Monoid ObjectFormat where
+  mempty = UnknownObjectFormat
+
+-- | More like a sextuple than a triple, if you think about it.
+--
+-- The LLVM version of this holds onto the un-normalized string representation.
+-- We discard it.
+data TargetTriple
+  = TargetTriple
+    { ttArch :: Arch
+    , ttSubArch :: SubArch
+    , ttVendor :: Vendor
+    , ttOS :: OS
+    , ttEnv :: Environment
+    , ttObjFmt :: ObjectFormat
+    }
+  deriving (Bounded, Data, Eq, Generic, Ord, Read, Show, Typeable)
+
+-- | Combines fields pointwise.
+instance Semigroup TargetTriple where
+  tt <> tt' =
+    TargetTriple
+    { ttArch = ttArch tt <> ttArch tt'
+    , ttSubArch = ttSubArch tt <> ttSubArch tt'
+    , ttVendor = ttVendor tt <> ttVendor tt'
+    , ttOS = ttOS tt <> ttOS tt'
+    , ttEnv = ttEnv tt <> ttEnv tt'
+    , ttObjFmt = ttObjFmt tt <> ttObjFmt tt'
+    }
+
+-- | Pointwise 'mempty'.
+instance Monoid TargetTriple where
+  mempty =
+    TargetTriple
+    { ttArch = mempty
+    , ttSubArch = mempty
+    , ttVendor = mempty
+    , ttOS = mempty
+    , ttEnv = mempty
+    , ttObjFmt = mempty
+    }
diff --git a/llvm-pretty/src/Text/LLVM/Triple/Parse.hs b/llvm-pretty/src/Text/LLVM/Triple/Parse.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Triple/Parse.hs
@@ -0,0 +1,294 @@
+{- |
+Module      : Text.LLVM.Triple.Parse
+Description : Parsing of LLVM target triples.
+License     : BSD3
+Maintainer  : Langston Barrett
+Stability   : experimental
+
+The declarations appear in this module in the same order as in the LLVM source.
+-}
+
+{- Note [Implementation]
+
+The very simplest parsing functions are implemented with the 'LookupTable'
+structure. For anything more complex, we endeavor to closely mirror the
+structure of LLVM's implementation. This will make the code more maintainable
+when updating to newer versions of LLVM.
+
+-}
+
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiWayIf #-}
+
+module Text.LLVM.Triple.Parse
+  ( parseArch
+  , parseVendor
+  , parseOS
+  , parseEnv
+  , parseObjFmt
+  , parseSubArch
+  , parseTriple
+  ) where
+
+import qualified Data.List as List
+
+import qualified MonadLib as M
+import qualified MonadLib.Monads as M
+
+import Text.LLVM.Triple.AST
+import qualified Text.LLVM.Triple.Print as Print
+import Text.LLVM.Triple.Parse.LookupTable
+import qualified Text.LLVM.Triple.Parse.ARM as ARM
+
+-- | @llvm::parseArch@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/Triple.cpp#L442
+parseArch :: String -> Arch
+parseArch s =
+  let mArch =
+        -- See Note [Implementation] for the reasoning behind the strange structure.
+        --
+        -- It would be easy to forget to add patterns here when adding a new
+        -- constructor to Arch, but we have exhaustive print-then-parse
+        -- roundtrip tests to mitigate this risk.
+        if | cases ["i386", "i486", "i586", "i686"] -> X86
+           | cases ["i786", "i886", "i986"] -> X86
+           | cases ["amd64", "x86_64", "x86_64h"] -> X86_64
+           | cases ["powerpc", "powerpcspe", "ppc", "ppc32"] -> PPC
+           | cases ["powerpcle", "ppcle", "ppc32le"] -> PPCLE
+           | cases ["powerpc64", "ppu", "ppc64"] -> PPC64
+           | cases ["powerpc64le", "ppc64le"] -> PPC64LE
+           | cases ["xscale"] -> ARM
+           | cases ["xscaleeb"] -> ARMEB
+           | cases ["aarch64"] -> AArch64
+           | cases ["aarch64_be"] -> AArch64_BE
+           | cases ["aarch64_32"] -> AArch64_32
+           | cases ["arc"] -> ARC
+           | cases ["arm64"] -> AArch64
+           | cases ["arm64_32"] -> AArch64_32
+           | cases ["arm64e"] -> AArch64
+           | cases ["arm"] -> ARM
+           | cases ["armeb"] -> ARMEB
+           | cases ["thumb"] -> Thumb
+           | cases ["thumbeb"] -> ThumbEB
+           | cases ["avr"] -> AVR
+           | cases ["m68k"] -> M68k
+           | cases ["msp430"] -> MSP430
+           | cases ["mips", "mipseb", "mipsallegrex", "mipsisa32r6"
+                   , "mipsr6"] -> MIPS
+           | cases ["mipsel", "mipsallegrexel", "mipsisa32r6el", "mipsr6el"] -> MIPSEL
+           | cases ["mips64", "mips64eb", "mipsn32", "mipsisa64r6"
+                   , "mips64r6", "mipsn32r6"] -> MIPS64
+           | cases ["mips64el", "mipsn32el", "mipsisa64r6el", "mips64r6el"
+                   , "mipsn32r6el"] -> MIPS64EL
+           | cases ["r600"] -> R600
+           | cases ["amdgcn"] -> AMDGCN
+           | cases ["riscv32"] -> RISCV32
+           | cases ["riscv64"] -> RISCV64
+           | cases ["hexagon"] -> Hexagon
+           | cases ["s390x", "systemz"] -> SystemZ
+           | cases ["sparc"] -> Sparc
+           | cases ["sparcel"] -> SparcEL
+           | cases ["sparcv9", "sparc64"] -> Sparcv9
+           | cases ["tce"] -> TCE
+           | cases ["tcele"] -> TCELE
+           | cases ["xcore"] -> XCore
+           | cases ["nvptx"] -> NVPTX
+           | cases ["nvptx64"] -> NVPTX64
+           | cases ["le32"] -> Le32
+           | cases ["le64"] -> Le64
+           | cases ["amdil"] -> AMDIL
+           | cases ["amdil64"] -> AMDIL64
+           | cases ["hsail"] -> HSAIL
+           | cases ["hsail64"] -> HSAIL64
+           | cases ["spir"] -> SPIR
+           | cases ["spir64"] -> SPIR64
+           | cases ["spirv32", "spirv32v1.0", "spirv32v1.1", "spirv32v1.2"
+                   , "spirv32v1.3", "spirv32v1.4", "spirv32v1.5"] -> SPIRV32
+           | cases ["spirv64", "spirv64v1.0", "spirv64v1.1", "spirv64v1.2"
+                   , "spirv64v1.3", "spirv64v1.4", "spirv64v1.5"] -> SPIRV64
+           | archPfx Kalimba -> Kalimba
+           | cases ["lanai"] -> Lanai
+           | cases ["renderscript32"] -> RenderScript32
+           | cases ["renderscript64"] -> RenderScript64
+           | cases ["shave"] -> SHAVE
+           | cases ["ve"] -> VE
+           | cases ["wasm32"] -> Wasm32
+           | cases ["wasm64"] -> Wasm64
+           | cases ["csky"] -> CSKY
+           | cases ["loongarch32"] -> LoongArch32
+           | cases ["loongarch64"] -> LoongArch64
+           | cases ["dxil"] -> DXIL
+           | otherwise -> UnknownArch
+  in case mArch of
+        UnknownArch ->
+          if | archPfx ARM || archPfx Thumb || archPfx AArch64 ->
+                 ARM.parseARMArch (ARM.ArchName s)
+             | "bpf" `List.isPrefixOf` s -> parseBPFArch s
+             | otherwise -> UnknownArch
+        arch -> arch
+  where
+    cases = any (== s)
+    archPfx arch = Print.archName arch `List.isPrefixOf` s
+
+    -- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/Triple.cpp#L292
+    parseBPFArch arch =
+      if arch == "bpf"
+      -- The way that LLVM parses the arch for BPF depends on the endianness of
+      -- the host in this case, which feels deeply wrong. We don't do that, not
+      -- least since we're not in IO. We default to little-endian instead.
+      then BPFEL
+      else if | arch == "bpf_be" || arch == "bpfeb" -> BPFEB
+              | arch == "bpf_le" || arch == "bpfel" -> BPFEL
+              | otherwise -> UnknownArch
+
+-- | @llvm::parseVendor@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/Triple.cpp#L529
+parseVendor :: String -> Vendor
+parseVendor = lookupWithDefault table UnknownVendor
+  where table = enumTable Print.vendorName
+
+-- | @llvm::parseOS@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/Triple.cpp#L549
+parseOS :: String -> OS
+parseOS = lookupByPrefixWithDefault table UnknownOS
+  where table = enumTable Print.osName
+
+-- | @llvm::parseEnvironment@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/Triple.cpp#L593
+parseEnv :: String -> Environment
+parseEnv = lookupByPrefixWithDefault table UnknownEnvironment
+  where table = enumTable Print.envName
+
+-- | @llvm::parseFormat@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/Triple.cpp#L634
+parseObjFmt :: String -> ObjectFormat
+parseObjFmt = lookupBySuffixWithDefault table UnknownObjectFormat
+  where table = enumTable Print.objFmtName
+
+-- | @llvm::parseSubArch@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/Triple.cpp#L648
+parseSubArch :: String -> SubArch
+parseSubArch subArchName =
+  if | startsWith "mips" && (endsWith "r6el" || endsWith "r6") -> MipsSubArch_r6
+
+     | subArchName == "powerpcspe" -> PPCSubArch_spe
+
+     | subArchName == "arm64e" -> AArch64SubArch_arm64e
+
+     | startsWith "arm64e" -> AArch64SubArch_arm64e
+
+     | startsWith "spirv" ->
+         if | endsWith "v1.0" -> SPIRVSubArch_v10
+            | endsWith "v1.1" -> SPIRVSubArch_v11
+            | endsWith "v1.2" -> SPIRVSubArch_v12
+            | endsWith "v1.3" -> SPIRVSubArch_v13
+            | endsWith "v1.4" -> SPIRVSubArch_v14
+            | endsWith "v1.5" -> SPIRVSubArch_v15
+            | otherwise -> NoSubArch
+     | otherwise ->
+         case ARM.parseArch <$> armSubArch of
+           Nothing ->
+             if | endsWith "kalimba3" -> KalimbaSubArch_v3
+                | endsWith "kalimba4" -> KalimbaSubArch_v4
+                | endsWith "kalimba5" -> KalimbaSubArch_v5
+                | otherwise -> NoSubArch
+           Just armArch ->
+             if | armArch == ARM.ARMV4 -> NoSubArch
+                | armArch == ARM.ARMV4T -> ARMSubArch_v4t
+                | armArch == ARM.ARMV5T -> ARMSubArch_v5
+                | armArch == ARM.ARMV5TE ||
+                  armArch == ARM.IWMMXT ||
+                  armArch == ARM.IWMMXT2 ||
+                  armArch == ARM.XSCALE ||
+                  armArch == ARM.ARMV5TEJ -> ARMSubArch_v5te
+                | armArch == ARM.ARMV6 ->  ARMSubArch_v6
+                | armArch == ARM.ARMV6K ||
+                  armArch == ARM.ARMV6KZ -> ARMSubArch_v6k
+                | armArch == ARM.ARMV6T2 ->  ARMSubArch_v6t2
+                | armArch == ARM.ARMV6M ->  ARMSubArch_v6m
+                | armArch == ARM.ARMV7A ||
+                  armArch == ARM.ARMV7R -> ARMSubArch_v7
+                | armArch == ARM.ARMV7VE -> ARMSubArch_v7ve
+                | armArch == ARM.ARMV7K -> ARMSubArch_v7k
+                | armArch == ARM.ARMV7M -> ARMSubArch_v7m
+                | armArch == ARM.ARMV7S -> ARMSubArch_v7s
+                | armArch == ARM.ARMV7EM -> ARMSubArch_v7em
+                | armArch == ARM.ARMV8A -> ARMSubArch_v8
+                | armArch == ARM.ARMV8_1A -> ARMSubArch_v8_1a
+                | armArch == ARM.ARMV8_2A -> ARMSubArch_v8_2a
+                | armArch == ARM.ARMV8_3A -> ARMSubArch_v8_3a
+                | armArch == ARM.ARMV8_4A -> ARMSubArch_v8_4a
+                | armArch == ARM.ARMV8_5A -> ARMSubArch_v8_5a
+                | armArch == ARM.ARMV8_6A -> ARMSubArch_v8_6a
+                | armArch == ARM.ARMV8_7A -> ARMSubArch_v8_7a
+                | armArch == ARM.ARMV8_8A -> ARMSubArch_v8_8a
+                | armArch == ARM.ARMV9A -> ARMSubArch_v9
+                | armArch == ARM.ARMV9_1A -> ARMSubArch_v9_1a
+                | armArch == ARM.ARMV9_2A -> ARMSubArch_v9_2a
+                | armArch == ARM.ARMV9_3A -> ARMSubArch_v9_3a
+                | armArch == ARM.ARMV8R -> ARMSubArch_v8r
+                | armArch == ARM.ARMV8MBaseline -> ARMSubArch_v8m_baseline
+                | armArch == ARM.ARMV8MMainline -> ARMSubArch_v8m_mainline
+                | armArch == ARM.ARMV8_1MMainline -> ARMSubArch_v8_1m_mainline
+                | otherwise -> NoSubArch
+  where
+    startsWith = (`List.isPrefixOf` subArchName)
+    endsWith = (`List.isSuffixOf` subArchName)
+    armSubArch = ARM.getCanonicalArchName (ARM.ArchName subArchName)
+
+-- | @llvm::Triple::getDefaultFormat@
+--
+-- TODO(#97): Implement me!
+defaultObjFmt :: TargetTriple -> ObjectFormat
+defaultObjFmt _tt = UnknownObjectFormat
+
+-- | @llvm::Triple::Triple@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/Triple.cpp#L869
+parseTriple :: String -> TargetTriple
+parseTriple str =
+  execState (split '-' str) $ do
+    let pop def f =
+          M.sets $
+            \case
+              (hd:rest) -> (f hd, rest)
+              [] -> (def, [])
+    (arch, subArch) <-
+      pop (UnknownArch, NoSubArch) (\s -> (parseArch s, parseSubArch s))
+    vendor <- pop UnknownVendor parseVendor
+    os <- pop UnknownOS parseOS
+    (env, objFmt) <-
+      pop (UnknownEnvironment, UnknownObjectFormat) (\s -> (parseEnv s, parseObjFmt s))
+    let tt =
+          TargetTriple
+          { ttArch = arch
+          , ttSubArch = subArch
+          , ttVendor = vendor
+          , ttOS = os
+          , ttEnv = env
+          , ttObjFmt = objFmt
+          }
+    return (tt { ttObjFmt =
+                   if ttObjFmt tt == UnknownObjectFormat
+                   then defaultObjFmt tt
+                   else ttObjFmt tt
+               })
+  where
+
+    -- > split '-' "foo-bar" == ["foo", "bar"]
+    split :: Char -> String -> [String]
+    split splitter =
+      foldr (\c strs -> if c == splitter then ([]:strs) else push c strs) [[]]
+      where
+        push c [] = [[c]]
+        push c (s:strs) = (c:s):strs
+
+    -- Not in MonadLib...
+    execState s = fst . M.runState s
diff --git a/llvm-pretty/src/Text/LLVM/Triple/Parse/ARM.hs b/llvm-pretty/src/Text/LLVM/Triple/Parse/ARM.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Triple/Parse/ARM.hs
@@ -0,0 +1,347 @@
+{- |
+Module      : Text.LLVM.Triple.Parse.ARM
+Description : ARM utilities used in target triple parsing.
+License     : BSD3
+Maintainer  : Langston Barrett
+Stability   : experimental
+
+This module is not exposed as part of the library API.
+-}
+
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE StrictData #-}
+
+module Text.LLVM.Triple.Parse.ARM
+  ( ArchName(..)
+    -- * Endianness
+  , EndianKind(..)
+  , parseEndianKind
+    -- * ISA
+  , ISAKind(..)
+  , parseISAKind
+    -- * Arch
+  , getCanonicalArchName
+  , parseARMArch
+  , ARMArch(..)
+  , armArchName
+  , parseArch
+  ) where
+
+import qualified Data.Char as Char
+import           Control.Monad (liftM2, when)
+import qualified MonadLib as M
+import qualified MonadLib.Monads as M
+import qualified Data.List as List
+
+import           Text.LLVM.Triple.AST
+import qualified Text.LLVM.Triple.Parse.LookupTable as Lookup
+
+-- | The "arch" portion of a target triple
+newtype ArchName = ArchName { getArchName :: String }
+
+--------------------------------------------------------------------------------
+-- Endianness
+
+-- | @llvm::EndianKind@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/include/llvm/Support/ARMTargetParser.h#L166
+data EndianKind
+  = Little
+  | Big
+  deriving (Bounded, Enum, Eq, Ord)
+
+-- | @llvm::ARM::parseArchEndian@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/ARMTargetParser.cpp#L248
+parseEndianKind :: ArchName -> Maybe EndianKind
+parseEndianKind (ArchName arch) =
+  if | hasPfx "armeb" || hasPfx "thumbeb" || hasPfx "aarch64_be" -> Just Big
+     | hasPfx "arm" || hasPfx "thumb" ->
+         if hasSfx "eb"
+         then Just Big
+         else Just Little
+     | hasPfx "aarch64" || hasPfx "aarch64_32" -> Just Little
+     | otherwise -> Nothing
+  where
+    hasPfx = (`List.isPrefixOf` arch)
+    hasSfx = (`List.isSuffixOf` arch)
+
+--------------------------------------------------------------------------------
+-- ISA
+
+-- | @llvm::ISAKind@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/include/llvm/Support/ARMTargetParser.h#L162
+data ISAKind
+  = ISAArm
+  | ISAThumb
+  | ISAAArch64
+  deriving (Bounded, Enum, Eq, Ord)
+
+-- | @llvm::ARM::parseArchISA@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/ARMTargetParser.cpp#L267
+parseISAKind :: ArchName -> Maybe ISAKind
+parseISAKind (ArchName arch) = Lookup.lookupByPrefix arch table
+  where
+    table =
+      Lookup.makeTable
+        [ ("aarch64", ISAAArch64)
+        , ("arm64", ISAAArch64)
+        , ("thumb", ISAThumb)
+        , ("arm", ISAArm)
+        ]
+
+--------------------------------------------------------------------------------
+-- Arch
+
+-- | @llvm::ARM::getArchSynonym@
+getArchSynonym :: ArchName -> ArchName
+getArchSynonym (ArchName arch) =
+  ArchName $
+    if | cases ["v5"] -> "v5t"
+       | cases ["v5e"] -> "v5te"
+       | cases ["v6j"] -> "v6"
+       | cases ["v6hl"] -> "v6k"
+       | cases ["v6m", "v6sm", "v6s-m"] -> "v6-m"
+       | cases ["v6z", "v6zk"] -> "v6kz"
+       | cases ["v7", "v7a", "v7hl", "v7l"] -> "v7-a"
+       | cases ["v7r"] -> "v7-r"
+       | cases ["v7m"] -> "v7-m"
+       | cases ["v7em"] -> "v7e-m"
+       | cases ["v8", "v8a", "v8l", "aarch64", "arm64"] -> "v8-a"
+       | cases ["v8.1a"] -> "v8.1-a"
+       | cases ["v8.2a"] -> "v8.2-a"
+       | cases ["v8.3a"] -> "v8.3-a"
+       | cases ["v8.4a"] -> "v8.4-a"
+       | cases ["v8.5a"] -> "v8.5-a"
+       | cases ["v8.6a"] -> "v8.6-a"
+       | cases ["v8.7a"] -> "v8.7-a"
+       | cases ["v8.8a"] -> "v8.8-a"
+       | cases ["v8r"] -> "v8-r"
+       | cases ["v9", "v9a"] -> "v9-a"
+       | cases ["v9.1a"] -> "v9.1-a"
+       | cases ["v9.2a"] -> "v9.2-a"
+       | cases ["v9.3a"] -> "v9.3-a"
+       | cases ["v8m.base"] -> "v8-m.base"
+       | cases ["v8m.main"] -> "v8-m.main"
+       | cases ["v8.1m.main"] -> "v8.1-m.main"
+       | otherwise -> arch
+  where
+    cases = any (== arch)
+
+data CanonicalArchNameState
+  = CanonicalArchNameState
+    { offset :: Int
+    , archStr :: String -- ^ @A@ in the LLVM
+    }
+
+-- | @llvm::ARM::getCanonicalArchName@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/ARMTargetParser.cpp#L295
+getCanonicalArchName :: ArchName -> Maybe ArchName
+getCanonicalArchName (ArchName arch) =
+  -- See Note [Implementation] for the reasoning behind the strange structure.
+  --
+  -- Could probably be translated even more directly using ContT, but that feels
+  -- like a bit much.
+  execState (CanonicalArchNameState 0 arch) $ do
+    ifM (liftM2 (&&) (startsWith "aarch64") (contains "eb")) (return Nothing) $ do
+      whenM (startsWith "arm64_32") $
+        setOffset 8
+      whenM (startsWith "arm64e") $
+        setOffset 6
+      whenM (startsWith "arm64") $
+        setOffset 5
+      whenM (startsWith "aarch64_32") $
+        setOffset 10
+      whenM (startsWith "arm") $
+        setOffset 3
+      whenM (startsWith "thumb") $
+        setOffset 5
+      whenM (startsWith "aarch64") $ do
+        setOffset 7
+        whenM ((== "_be") <$> archOffSubstr 3) $
+          addOffset 3
+
+      off <- offset <$> M.get
+      sub <- archOffSubstr 2
+      when (off /= npos && sub == "eb") $
+        addOffset 2
+      whenM (endsWith "eb") $ do
+        changeArch (\arch' -> substr 0 (length arch' - 2) arch')
+      off' <- offset <$> M.get
+      when (off' /= npos) $
+        changeArch (drop off')
+
+      arch' <- archStr <$> M.get
+      if | length arch' == 0 -> return Nothing
+         | off' /= npos && length arch' > 2 &&
+             (take 1 arch' /= "v" || not (all Char.isDigit (substr 1 1 arch'))) ->
+               return Nothing
+         | off' /= npos && "eb" `List.isInfixOf` arch' -> return Nothing
+         | otherwise -> return (Just (ArchName arch'))
+  where
+    npos = 0
+
+    ifM b thn els = b >>= \b' -> if b' then thn else els
+    whenM b k = ifM b k (return ())
+    startsWith pfx = (pfx `List.isPrefixOf`) . archStr <$> M.get
+    endsWith sfx = (sfx `List.isSuffixOf`) . archStr <$> M.get
+    contains ifx = (ifx `List.isInfixOf`) . archStr <$> M.get
+
+    substr start sz = take sz . drop start
+    archSubstr begin sz = substr begin sz . archStr <$> M.get
+    archOffSubstr sz = do
+      off <- offset <$> M.get
+      archSubstr off sz
+
+    changeOffset f = do
+      s <- M.get
+      M.set (s { offset = f (offset s) })
+    addOffset n = changeOffset (n+)
+    setOffset n = changeOffset (const n)
+
+    changeArch f = M.set . (\s -> s { archStr = f (archStr s) }) =<< M.get
+
+    -- Not in MonadLib...
+    execState s = fst . M.runState s
+
+-- | @llvm::parseARMArch@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/lib/Support/Triple.cpp#L377
+parseARMArch :: ArchName -> Arch
+parseARMArch archName =
+  let
+    isa = parseISAKind archName
+    endian = parseEndianKind archName
+    arch =
+      case endian of
+        Just Little ->
+          case isa of
+            Just ISAArm -> ARM
+            Just ISAThumb -> Thumb
+            Just ISAAArch64 -> AArch64
+            Nothing -> UnknownArch
+        Just Big ->
+          case isa of
+            Just ISAArm -> ARMEB
+            Just ISAThumb -> ThumbEB
+            Just ISAAArch64 -> AArch64_BE
+            Nothing -> UnknownArch
+        Nothing -> UnknownArch
+    mArchName = getCanonicalArchName archName
+  in case mArchName of
+      Nothing -> UnknownArch
+      Just (ArchName archNm) ->
+        if | isa == Just ISAThumb &&
+             ("v2" `List.isPrefixOf` archNm || "v3" `List.isPrefixOf` archNm) -> UnknownArch
+            -- TODO(#98): LLVM has one more check here involving the "arch
+            -- profile" that's not yet implemented here... Probably not a big
+            -- deal because this case is only executed when the target arch
+            -- doesn't match the "canonical" versions like "arm", "thumb", and
+            -- "aarch64".
+           | otherwise -> arch
+
+-- | See LLVM's @ARMTargetParser.def@
+--
+-- https://github.com/llvm/llvm-project/blob/llvmorg-15.0.1/llvm/include/llvm/Support/ARMTargetParser.def#L48
+data ARMArch
+  = ARMArchInvalid
+  | ARMV2
+  | ARMV2A
+  | ARMV3
+  | ARMV3M
+  | ARMV4
+  | ARMV4T
+  | ARMV5T
+  | ARMV5TE
+  | ARMV5TEJ
+  | ARMV6
+  | ARMV6K
+  | ARMV6T2
+  | ARMV6KZ
+  | ARMV6M
+  | ARMV7A
+  | ARMV7VE
+  | ARMV7R
+  | ARMV7M
+  | ARMV7EM
+  | ARMV8A
+  | ARMV8_1A
+  | ARMV8_2A
+  | ARMV8_3A
+  | ARMV8_4A
+  | ARMV8_5A
+  | ARMV8_6A
+  | ARMV8_7A
+  | ARMV8_8A
+  | ARMV9A
+  | ARMV9_1A
+  | ARMV9_2A
+  | ARMV9_3A
+  | ARMV8R
+  | ARMV8MBaseline
+  | ARMV8MMainline
+  | ARMV8_1MMainline
+  | IWMMXT
+  | IWMMXT2
+  | XSCALE
+  | ARMV7S
+  | ARMV7K
+  deriving (Bounded, Enum, Eq, Ord)
+
+armArchName :: ARMArch -> String
+armArchName =
+  \case
+    ARMArchInvalid -> "invalid"
+    ARMV2 -> "armv2"
+    ARMV2A -> "armv2a"
+    ARMV3 -> "armv3"
+    ARMV3M -> "armv3m"
+    ARMV4 -> "armv4"
+    ARMV4T -> "armv4t"
+    ARMV5T -> "armv5t"
+    ARMV5TE -> "armv5te"
+    ARMV5TEJ -> "armv5tej"
+    ARMV6 -> "armv6"
+    ARMV6K -> "armv6k"
+    ARMV6T2 -> "armv6t2"
+    ARMV6KZ -> "armv6kz"
+    ARMV6M -> "armv6-m"
+    ARMV7A -> "armv7-a"
+    ARMV7VE -> "armv7ve"
+    ARMV7R -> "armv7-r"
+    ARMV7M -> "armv7-m"
+    ARMV7EM -> "armv7e-m"
+    ARMV8A -> "armv8-a"
+    ARMV8_1A -> "armv8.1-a"
+    ARMV8_2A -> "armv8.2-a"
+    ARMV8_3A -> "armv8.3-a"
+    ARMV8_4A -> "armv8.4-a"
+    ARMV8_5A -> "armv8.5-a"
+    ARMV8_6A -> "armv8.6-a"
+    ARMV8_7A -> "armv8.7-a"
+    ARMV8_8A -> "armv8.8-a"
+    ARMV9A -> "armv9-a"
+    ARMV9_1A -> "armv9.1-a"
+    ARMV9_2A -> "armv9.2-a"
+    ARMV9_3A -> "armv9.3-a"
+    ARMV8R -> "armv8-r"
+    ARMV8MBaseline -> "armv8-m.base"
+    ARMV8MMainline -> "armv8-m.main"
+    ARMV8_1MMainline -> "armv8.1-m.main"
+    IWMMXT -> "iwmmxt"
+    IWMMXT2 -> "iwmmxt2"
+    XSCALE -> "xscale"
+    ARMV7S -> "armv7s"
+    ARMV7K -> "armv7k"
+
+-- | @llvm::ARM::parseArch@
+parseArch :: ArchName -> ARMArch
+parseArch arch =
+  let ArchName syn = getArchSynonym arch
+      table = Lookup.enumTable armArchName
+  in Lookup.lookupBySuffixWithDefault table ARMArchInvalid syn
diff --git a/llvm-pretty/src/Text/LLVM/Triple/Parse/LookupTable.hs b/llvm-pretty/src/Text/LLVM/Triple/Parse/LookupTable.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Triple/Parse/LookupTable.hs
@@ -0,0 +1,77 @@
+{- |
+Module      : Text.LLVM.Triple.Parse.LookupTable
+Description : Helpers for parsing a finite set of strings.
+License     : BSD3
+Maintainer  : Langston Barrett
+Stability   : experimental
+
+This module is not exposed as part of the library API.
+-}
+
+{-# LANGUAGE TupleSections #-}
+
+module Text.LLVM.Triple.Parse.LookupTable
+  ( -- * Construction
+    LookupTable
+  , makeTable
+  , enumTable
+  , enumTableVariants
+    -- * Queries
+  , lookupBy
+  , lookup
+  , lookupByPrefix
+  , lookupByWithDefault
+  , lookupWithDefault
+  , lookupByPrefixWithDefault
+  , lookupBySuffixWithDefault
+  ) where
+
+import           Prelude hiding (lookup)
+import qualified Data.Maybe as Maybe
+import qualified Data.List as List
+
+--------------------------------------------------------------------------------
+-- Construction
+
+-- | A table from constrant strings to values, to be used in parsing a finite
+-- set of possibilities. Could be replaced by a fancier data structure, but
+-- there's only one target triple per module, after all.
+--
+-- Hopefully, GHC is smart enough to construct these at compile-time.
+newtype LookupTable a = LookupTable { getLookupTable :: [(String, a)] }
+
+makeTable :: [(String, a)] -> LookupTable a
+makeTable = LookupTable
+
+enumTable :: Bounded a => Enum a => (a -> String) -> LookupTable a
+enumTable prnt = makeTable [(prnt a, a) | a <- enumFrom minBound]
+
+enumTableVariants :: Bounded a => Enum a => (a -> [String]) -> LookupTable a
+enumTableVariants prnt =
+  makeTable (concatMap (\a -> map (,a) (prnt a)) (enumFrom minBound))
+
+--------------------------------------------------------------------------------
+-- Queries
+
+lookupBy :: (String -> Bool) -> LookupTable a -> Maybe a
+lookupBy p = Maybe.listToMaybe . map snd . filter (p . fst) . getLookupTable
+
+lookup :: String -> LookupTable a -> Maybe a
+lookup s = lookupBy (== s)
+
+lookupByPrefix :: String -> LookupTable a -> Maybe a
+lookupByPrefix pfx = lookupBy (pfx `List.isPrefixOf`)
+
+lookupByWithDefault :: a -> (String -> Bool) -> LookupTable a -> a
+lookupByWithDefault def p = Maybe.fromMaybe def . lookupBy p
+
+lookupWithDefault :: LookupTable a -> a -> String -> a
+lookupWithDefault table def val = lookupByWithDefault def (== val) table
+
+lookupByPrefixWithDefault :: LookupTable a -> a -> String -> a
+lookupByPrefixWithDefault table def pfx =
+  lookupByWithDefault def (pfx `List.isPrefixOf`) table
+
+lookupBySuffixWithDefault :: LookupTable a -> a -> String -> a
+lookupBySuffixWithDefault table def sfx =
+  lookupByWithDefault def (sfx `List.isSuffixOf`) table
diff --git a/llvm-pretty/src/Text/LLVM/Triple/Print.hs b/llvm-pretty/src/Text/LLVM/Triple/Print.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Triple/Print.hs
@@ -0,0 +1,229 @@
+{- |
+Module      : Text.LLVM.Triple.Print
+Description : Printing of LLVM target triples.
+License     : BSD3
+Maintainer  : Langston Barrett
+Stability   : experimental
+-}
+
+{-# LANGUAGE LambdaCase #-}
+
+module Text.LLVM.Triple.Print
+  ( archName
+  , vendorName
+  , osName
+  , envName
+  , objFmtName
+  , printTriple
+  ) where
+
+import qualified Data.List as List
+
+import Text.LLVM.Triple.AST
+
+-- | @llvm::Triple::getArchTypeName@.
+--
+-- Retained in the order in which they appear in the LLVM source, rather than an
+-- order consistent with the constructors of 'Arch'.
+archName :: Arch -> String
+archName =
+  \case
+    UnknownArch -> "unknown"
+    AArch64 -> "aarch64"
+    AArch64_32 -> "aarch64_32"
+    AArch64_BE -> "aarch64_be"
+    AMDGCN -> "amdgcn"
+    AMDIL64 -> "amdil64"
+    AMDIL -> "amdil"
+    ARC -> "arc"
+    ARM -> "arm"
+    ARMEB -> "armeb"
+    AVR -> "avr"
+    BPFEB -> "bpfeb"
+    BPFEL -> "bpfel"
+    CSKY -> "csky"
+    DXIL -> "dxil"
+    Hexagon -> "hexagon"
+    HSAIL64 -> "hsail64"
+    HSAIL -> "hsail"
+    Kalimba -> "kalimba"
+    Lanai -> "lanai"
+    Le32 -> "le32"
+    Le64 -> "le64"
+    LoongArch32 -> "loongarch32"
+    LoongArch64 -> "loongarch64"
+    M68k -> "m68k"
+    MIPS64 -> "mips64"
+    MIPS64EL -> "mips64el"
+    MIPS -> "mips"
+    MIPSEL -> "mipsel"
+    MSP430 -> "msp430"
+    NVPTX64 -> "nvptx64"
+    NVPTX -> "nvptx"
+    PPC64 -> "powerpc64"
+    PPC64LE -> "powerpc64le"
+    PPC -> "powerpc"
+    PPCLE -> "powerpcle"
+    R600 -> "r600"
+    RenderScript32 -> "renderscript32"
+    RenderScript64 -> "renderscript64"
+    RISCV32 -> "riscv32"
+    RISCV64 -> "riscv64"
+    SHAVE -> "shave"
+    Sparc -> "sparc"
+    SparcEL -> "sparcel"
+    Sparcv9 -> "sparcv9"
+    SPIR64 -> "spir64"
+    SPIR -> "spir"
+    SPIRV32 -> "spirv32"
+    SPIRV64 -> "spirv64"
+    SystemZ -> "s390x"
+    TCE -> "tce"
+    TCELE -> "tcele"
+    Thumb -> "thumb"
+    ThumbEB -> "thumbeb"
+    VE -> "ve"
+    Wasm32 -> "wasm32"
+    Wasm64 -> "wasm64"
+    X86 -> "i386"
+    X86_64 -> "x86_64"
+    XCore -> "xcore"
+
+-- | @llvm::Triple::getVendorTypeName@.
+--
+-- Retained in the order in which they appear in the LLVM source.
+vendorName :: Vendor -> String
+vendorName =
+  \case
+    UnknownVendor -> "unknown"
+    AMD -> "amd"
+    Apple -> "apple"
+    CSR -> "csr"
+    Freescale -> "fsl"
+    IBM -> "ibm"
+    ImaginationTechnologies -> "img"
+    Mesa -> "mesa"
+    MipsTechnologies -> "mti"
+    Myriad -> "myriad"
+    NVIDIA -> "nvidia"
+    OpenEmbedded -> "oe"
+    PC -> "pc"
+    SCEI -> "scei"
+    SUSE -> "suse"
+
+-- | @llvm::Triple::getOSTypeName@.
+--
+-- Retained in the order in which they appear in the LLVM source.
+osName :: OS -> String
+osName =
+  \case
+    UnknownOS -> "unknown"
+    AIX -> "aix"
+    AMDHSA -> "amdhsa"
+    AMDPAL -> "amdpal"
+    Ananas -> "ananas"
+    CUDA -> "cuda"
+    CloudABI -> "cloudabi"
+    Contiki -> "contiki"
+    Darwin -> "darwin"
+    DragonFly -> "dragonfly"
+    DriverKit -> "driverkit"
+    ELFIAMCU -> "elfiamcu"
+    Emscripten -> "emscripten"
+    FreeBSD -> "freebsd"
+    Fuchsia -> "fuchsia"
+    Haiku -> "haiku"
+    HermitCore -> "hermit"
+    Hurd -> "hurd"
+    IOS -> "ios"
+    KFreeBSD -> "kfreebsd"
+    Linux -> "linux"
+    Lv2 -> "lv2"
+    MacOSX -> "macosx"
+    Mesa3D -> "mesa3d"
+    Minix -> "minix"
+    NVCL -> "nvcl"
+    NaCl -> "nacl"
+    NetBSD -> "netbsd"
+    OpenBSD -> "openbsd"
+    PS4 -> "ps4"
+    PS5 -> "ps5"
+    RTEMS -> "rtems"
+    Solaris -> "solaris"
+    TvOS -> "tvos"
+    WASI -> "wasi"
+    WatchOS -> "watchos"
+    Win32 -> "windows"
+    ZOS -> "zos"
+    ShaderModel -> "shadermodel"
+
+-- | @llvm::Triple::getEnvironmentName@.
+--
+-- Retained in the order in which they appear in the LLVM source.
+envName :: Environment -> String
+envName =
+  \case
+  UnknownEnvironment -> "unknown"
+  Android -> "android"
+  CODE16 -> "code16"
+  CoreCLR -> "coreclr"
+  Cygnus -> "cygnus"
+  EABI -> "eabi"
+  EABIHF -> "eabihf"
+  GNU -> "gnu"
+  GNUABI64 -> "gnuabi64"
+  GNUABIN32 -> "gnuabin32"
+  GNUEABI -> "gnueabi"
+  GNUEABIHF -> "gnueabihf"
+  GNUX32 -> "gnux32"
+  GNUILP32 -> "gnu_ilp32"
+  Itanium -> "itanium"
+  MSVC -> "msvc"
+  MacABI -> "macabi"
+  Musl -> "musl"
+  MuslEABI -> "musleabi"
+  MuslEABIHF -> "musleabihf"
+  MuslX32 -> "muslx32"
+  Simulator -> "simulator"
+  Pixel -> "pixel"
+  Vertex -> "vertex"
+  Geometry -> "geometry"
+  Hull -> "hull"
+  Domain -> "domain"
+  Compute -> "compute"
+  Library -> "library"
+  RayGeneration -> "raygeneration"
+  Intersection -> "intersection"
+  AnyHit -> "anyhit"
+  ClosestHit -> "closesthit"
+  Miss -> "miss"
+  Callable -> "callable"
+  Mesh -> "mesh"
+  Amplification -> "amplification"
+
+-- | @llvm::Triple::getObjectFormatTypeName@
+--
+-- Retained in the order in which they appear in the LLVM source.
+objFmtName :: ObjectFormat -> String
+objFmtName =
+  \case
+    UnknownObjectFormat -> ""  -- NB: Not "unknown"
+    COFF -> "coff"
+    ELF -> "elf"
+    GOFF -> "goff"
+    MachO -> "macho"
+    Wasm -> "wasm"
+    XCOFF -> "xcoff"
+    DXContainer -> "dxcontainer"
+    SPIRV -> "spirv"
+
+printTriple :: TargetTriple -> String
+printTriple tt =
+  List.intercalate
+    "-"
+    [ archName (ttArch tt)
+    , vendorName (ttVendor tt)
+    , osName (ttOS tt)
+    , envName (ttEnv tt)
+    , objFmtName (ttObjFmt tt)
+    ]
diff --git a/llvm-pretty/src/Text/LLVM/Util.hs b/llvm-pretty/src/Text/LLVM/Util.hs
new file mode 100644
--- /dev/null
+++ b/llvm-pretty/src/Text/LLVM/Util.hs
@@ -0,0 +1,18 @@
+module Text.LLVM.Util where
+
+import Control.Monad (MonadPlus,mzero)
+import Data.List (unfoldr)
+
+
+breaks :: (a -> Bool) -> [a] -> [[a]]
+breaks p = unfoldr step
+  where
+  step [] = Nothing
+  step xs = case break p xs of
+    (as,_:bs) -> Just (as,bs)
+    (as,  []) -> Just (as,[])
+
+uncons :: MonadPlus m => [a] -> m (a,[a])
+uncons (a:as) = return (a,as)
+uncons _      = mzero
+
diff --git a/src/Data/Array/Accelerate/LLVM/AST.hs b/src/Data/Array/Accelerate/LLVM/AST.hs
--- a/src/Data/Array/Accelerate/LLVM/AST.hs
+++ b/src/Data/Array/Accelerate/LLVM/AST.hs
@@ -25,7 +25,7 @@
 
 import Data.Array.Accelerate.LLVM.Execute.Async
 
-import Data.Array.Accelerate.AST                                    ( PreOpenAfun(..), HasArraysR(..), ArrayVar, ALeftHandSide, Exp, Direction, PrimBool, arrayR )
+import Data.Array.Accelerate.AST                                    ( PreOpenAfun(..), HasArraysR(..), ArrayVar, ALeftHandSide, Exp, Direction, Message, PrimBool, arrayR )
 import Data.Array.Accelerate.AST.Idx
 import Data.Array.Accelerate.AST.Var
 import Data.Array.Accelerate.Representation.Array
@@ -65,6 +65,11 @@
 
   Anil        :: PreOpenAccCommand acc arch aenv ()
 
+  Atrace      :: Message                         arrs1
+              -> acc                   arch aenv arrs1
+              -> acc                   arch aenv arrs2
+              -> PreOpenAccCommand acc arch aenv arrs2
+
   Apply       :: ArraysR bs
               -> PreOpenAfun      (acc arch) aenv (as -> bs)
               -> acc                   arch  aenv as
@@ -194,6 +199,7 @@
   arraysR (Unit tp _)                           = TupRsingle $ ArrayR ShapeRz tp
   arraysR (Apair a1 a2)                         = arraysR a1 `TupRpair` arraysR a2
   arraysR Anil                                  = TupRunit
+  arraysR (Atrace _ _ a2)                       = arraysR a2
   arraysR (Apply repr _ _)                      = repr
   arraysR (Aforeign repr _ _ _)                 = repr
   arraysR (Acond _ a1 _)                        = arraysR a1
diff --git a/src/Data/Array/Accelerate/LLVM/Array/Data.hs b/src/Data/Array/Accelerate/LLVM/Array/Data.hs
--- a/src/Data/Array/Accelerate/LLVM/Array/Data.hs
+++ b/src/Data/Array/Accelerate/LLVM/Array/Data.hs
@@ -6,6 +6,7 @@
 {-# LANGUAGE ScopedTypeVariables  #-}
 {-# LANGUAGE TypeApplications     #-}
 {-# LANGUAGE TypeFamilies         #-}
+{-# LANGUAGE TypeOperators        #-}
 {-# LANGUAGE TypeSynonymInstances #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen.hs b/src/Data/Array/Accelerate/LLVM/CodeGen.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE FlexibleContexts    #-}
 {-# LANGUAGE GADTs               #-}
+{-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE RecordWildCards     #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications    #-}
@@ -46,6 +47,7 @@
 import Data.Array.Accelerate.LLVM.Foreign
 import Data.Array.Accelerate.LLVM.State
 
+import Formatting
 import Prelude                                                      hiding ( map, scanl, scanl1, scanr, scanr1 )
 
 
@@ -83,6 +85,7 @@
     Awhile{}    -> unexpectedError
     Apair{}     -> unexpectedError
     Anil        -> unexpectedError
+    Atrace{}    -> unexpectedError
     Use{}       -> unexpectedError
     Unit{}      -> unexpectedError
     Aforeign{}  -> unexpectedError
@@ -122,6 +125,6 @@
 
     -- sadness
     fusionError, unexpectedError :: error
-    fusionError      = internalError $ "unexpected fusible material: " ++ showPreAccOp pacc
-    unexpectedError  = internalError $ "unexpected array primitive: "  ++ showPreAccOp pacc
+    fusionError      = internalError ("unexpected fusible material: " % formatPreAccOp) pacc
+    unexpectedError  = internalError ("unexpected array primitive: "  % formatPreAccOp) pacc
 
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Arithmetic.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Arithmetic.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Arithmetic.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Arithmetic.hs
@@ -79,12 +79,11 @@
     IntegralNumType i
       | unsigned i                     -> return x
       | IntegralDict <- integralDict i ->
-          let p = ScalarPrimType (SingleScalarType (NumSingleType n))
-              t = PrimType p
+          let p  = ScalarPrimType (SingleScalarType (NumSingleType n))
+              t  = PrimType p
+              fn = fromString $ printf "llvm.abs.i%d" (finiteBitSize (undefined::a))
           in
-          case finiteBitSize (undefined :: a) of
-            64 -> call (Lam p (op n x) (Body t Nothing "llabs")) [NoUnwind, ReadNone]
-            _  -> call (Lam p (op n x) (Body t Nothing "abs"))   [NoUnwind, ReadNone]
+          call (Lam p (op n x) (Lam primType (boolean False) (Body t Nothing fn))) [NoUnwind, ReadNone]
 
 signum :: forall arch a. NumType a -> Operands a -> CodeGen arch (Operands a)
 signum t x =
@@ -262,20 +261,22 @@
 rotateL :: forall arch a. IntegralType a -> Operands a -> Operands Int -> CodeGen arch (Operands a)
 rotateL t x i
   | IntegralDict <- integralDict t
-  = do let wsib = finiteBitSize (undefined::a)
-       i1 <- band integralType i (ir integralType (integral integralType (wsib P.- 1)))
-       i2 <- sub numType (ir numType (integral integralType wsib)) i1
-       --
-       a  <- shiftL t x i1
-       b  <- shiftRL t x i2
-       c  <- bor t a b
-       return c
+  = do let fshl = fromString $ printf "llvm.fshl.i%d" (finiteBitSize (undefined::a))
+           p    = ScalarPrimType (SingleScalarType (NumSingleType (IntegralNumType t)))
+           x'   = op t x
+       i' <- fromIntegral integralType (IntegralNumType t) i
+       r  <- call (Lam p x' (Lam p x' (Lam p (op t i') (Body (PrimType p) Nothing fshl)))) [NoUnwind, ReadNone]
+       return r
 
-rotateR :: IntegralType a -> Operands a -> Operands Int -> CodeGen arch (Operands a)
-rotateR t x i = do
-  i' <- negate numType i
-  r  <- rotateL t x i'
-  return r
+rotateR :: forall arch a. IntegralType a -> Operands a -> Operands Int -> CodeGen arch (Operands a)
+rotateR t x i
+  | IntegralDict <- integralDict t
+  = do let fshr = fromString $ printf "llvm.fshr.i%d" (finiteBitSize (undefined::a))
+           p    = ScalarPrimType (SingleScalarType (NumSingleType (IntegralNumType t)))
+           x'   = op t x
+       i' <- fromIntegral integralType (IntegralNumType t) i
+       r  <- call (Lam p x' (Lam p x' (Lam p (op t i') (Body (PrimType p) Nothing fshr)))) [NoUnwind, ReadNone]
+       return r
 
 popCount :: forall arch a. IntegralType a -> Operands a -> CodeGen arch (Operands Int)
 popCount i x
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Array.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Array.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Array.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Array.hs
@@ -24,7 +24,7 @@
 import Prelude                                                      hiding ( read )
 import Data.Bits
 
-import LLVM.AST.Type.AddrSpace
+import LLVM.AST.Type.GetElementPtr
 import LLVM.AST.Type.Instruction
 import LLVM.AST.Type.Instruction.Volatile
 import LLVM.AST.Type.Operand
@@ -133,19 +133,22 @@
     -> Operand (Ptr e)
     -> Operand int
     -> CodeGen arch (Operand (Ptr e))
-getElementPtr _ SingleScalarType{}   _ arr ix = instr' $ GetElementPtr arr [ix]
-getElementPtr a (VectorScalarType v) i arr ix
-  | VectorType n _ <- v
-  , IntegralDict   <- integralDict i
+getElementPtr _ t@(SingleScalarType{})   _ arr ix = instr' $ GetElementPtr $ GEP1 t arr ix
+getElementPtr a t@(VectorScalarType v) i arr ix
+  | VectorType n eltty <- v
+  , IntegralDict       <- integralDict i
   = if popCount n == 1
-       then instr' $ GetElementPtr arr [ix]
+       then instr' $ GetElementPtr $ GEP1 t arr ix
        else do
           -- Note the initial zero into to the GEP instruction. It is not
           -- really recommended to use GEP to index into vector elements, but
-          -- is not forcefully disallowed (at this time)
-          ix'  <- instr' $ Mul (IntegralNumType i) ix (integral i (fromIntegral n))
-          p'   <- instr' $ GetElementPtr arr [integral i 0, ix']
-          p    <- instr' $ PtrCast (PtrPrimType (ScalarPrimType (VectorScalarType v)) a) p'
+          -- is not forcefully disallowed (at this time).
+          -- Cast the <n x t>* to a t*, do a scaled GEP, and cast the resulting
+          -- t* back to an <n x t>*.
+          ix'    <- instr' $ Mul (IntegralNumType i) ix (integral i (fromIntegral n))
+          pPlain <- instr' $ PtrCast (PtrPrimType (ScalarPrimType (SingleScalarType eltty)) a) arr
+          p'     <- instr' $ GetElementPtr $ GEP1 (SingleScalarType eltty) pPlain ix'
+          p      <- instr' $ PtrCast (PtrPrimType (ScalarPrimType (VectorScalarType v)) a) p'
           return p
 
 
@@ -182,7 +185,7 @@
          let go i w
                | i >= m    = return w
                | otherwise = do
-                   q  <- instr' $ GetElementPtr p' [integral integralType i]
+                   q  <- instr' $ GetElementPtr $ GEP1 (SingleScalarType base) p' (integral integralType i)
                    r  <- instr' $ Load (SingleScalarType base) v q
                    w' <- instr' $ InsertElement i w r
                    go (i+1) w'
@@ -215,7 +218,7 @@
                | i >= m    = return ()
                | otherwise = do
                    x <- instr' $ ExtractElement i v
-                   q <- instr' $ GetElementPtr p' [integral integralType i]
+                   q <- instr' $ GetElementPtr $ GEP1 (SingleScalarType base) p' (integral integralType i)
                    _ <- instr' $ Store volatility q x
                    go (i+1)
          go 0
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Base.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Base.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Base.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Base.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE AllowAmbiguousTypes #-}
 {-# LANGUAGE GADTs               #-}
 {-# LANGUAGE LambdaCase          #-}
+{-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE RankNTypes          #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications    #-}
@@ -34,7 +35,6 @@
 
 ) where
 
-import LLVM.AST.Type.AddrSpace
 import LLVM.AST.Type.Constant
 import LLVM.AST.Type.Downcast
 import LLVM.AST.Type.Function
@@ -46,6 +46,7 @@
 import LLVM.AST.Type.Operand
 import LLVM.AST.Type.Representation
 
+import Data.Array.Accelerate.Error
 import Data.Array.Accelerate.LLVM.CodeGen.Environment
 import Data.Array.Accelerate.LLVM.CodeGen.IR
 import Data.Array.Accelerate.LLVM.CodeGen.Monad
@@ -55,7 +56,7 @@
 import Data.Array.Accelerate.Representation.Type
 import {-# SOURCE #-} Data.Array.Accelerate.LLVM.CodeGen.Exp
 
-import qualified LLVM.AST.Global                                    as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LP
 
 import Data.Monoid
 import Data.String
@@ -117,7 +118,7 @@
 mutableArray
     :: ArrayR (Array sh e)
     -> Name (Array sh e)
-    -> (IRArray (Array sh e), [LLVM.Parameter])
+    -> (IRArray (Array sh e), [LP.Typed LP.Ident])
 mutableArray repr name =
   ( irArray repr name
   , arrayParam repr name )
@@ -129,7 +130,7 @@
 delayedArray
     :: Name (Array sh e)
     -> MIRDelayed arch aenv (Array sh e)
-    -> (IRDelayed arch aenv (Array sh e), [LLVM.Parameter])
+    -> (IRDelayed arch aenv (Array sh e), [LP.Typed LP.Ident])
 delayedArray name = \case
   IRDelayedJust a -> (a, [])
   IRDelayedNothing repr ->
@@ -197,29 +198,45 @@
 -- Function parameters
 -- -------------------
 
--- | Call a global function. The function declaration is inserted into the
--- symbol table.
+-- | Call a global function.
 --
+-- If the function being called has a special prefix (see 'labelIsAccPrelude'),
+-- the call instruction is generated as-is; otherwise (the common case), a
+-- function declaration is also inserted into the symbol table, resulting in a
+-- 'declare' statement in the LLVM module. A special prelude function is
+-- currently not allowed to have any function attributes.
+--
+-- TODO: The original llvm-hs code put function attributes both on the external
+-- function declaration and on the call instruction; this code puts them only
+-- on the declaration. We should compare LLVM IR / benchmark to see if this is
+-- an issue (@llvm-pretty@ does not yet support function attributes on call
+-- instructions).
+--
 call :: GlobalFunction args t -> [FunctionAttribute] -> CodeGen arch (Operands t)
 call f attrs = do
-  let decl      = (downcast f) { LLVM.functionAttributes = downcast attrs' }
-      attrs'    = map Right attrs
+  let decl      = (downcast f) { LP.decAttrs = downcast attrs }
       --
-      go :: GlobalFunction args t -> Function (Either InlineAssembly Label) args t
-      go (Body t k l) = Body t k (Right l)
-      go (Lam t x l)  = Lam t x (go l)
+      go :: GlobalFunction args t -> (Label, Function (Either InlineAssembly Label) args t)
+      go (Body t k l) = (l, Body t k (Right l))
+      go (Lam t x l)  = Lam t x <$> go l
   --
-  declare decl
-  instr (Call (go f) attrs')
+  let (lab, f') = go f
+  if labelIsAccPrelude lab
+    then case attrs of
+           [] -> return ()
+           _ -> internalError "Function attributes passed to a call to an acc prelude LLVM function"
+    else declareExternFunc decl
 
+  instr (Call f')
 
-parameter :: TypeR t -> Name t -> [LLVM.Parameter]
-parameter tp n = travTypeToList tp (\s i -> scalarParameter s (rename n i))
 
-scalarParameter :: ScalarType t -> Name t -> LLVM.Parameter
+parameter :: TypeR t -> Name t -> [LP.Typed LP.Ident]
+parameter tp n = travTypeToList tp (\s i -> LP.Typed (downcast s) (nameToPrettyI (rename n i)))
+
+scalarParameter :: ScalarType t -> Name t -> LP.Typed LP.Ident
 scalarParameter t x = downcast (Parameter (ScalarPrimType t) x)
 
-ptrParameter :: ScalarType t -> Name (Ptr t) -> LLVM.Parameter
+ptrParameter :: ScalarType t -> Name (Ptr t) -> LP.Typed LP.Ident
 ptrParameter t x = downcast (Parameter (PtrPrimType (ScalarPrimType t) defaultAddrSpace) x)
 
 
@@ -227,11 +244,8 @@
 -- The environment here refers only to the actual free array variables that are
 -- accessed by the function.
 --
-envParam :: forall aenv. Gamma aenv -> [LLVM.Parameter]
-envParam aenv = concatMap (\(Label n, Idx' repr _) -> toParam repr (Name n)) (IM.elems aenv)
-  where
-    toParam :: ArrayR (Array sh e) -> Name (Array sh e) -> [LLVM.Parameter]
-    toParam repr name = arrayParam repr name
+envParam :: forall aenv. Gamma aenv -> [LP.Typed LP.Ident]
+envParam aenv = concatMap (\(Label n, Idx' repr _) -> arrayParam repr (Name n)) (IM.elems aenv)
 
 
 -- | Generate function parameters for an Array with given base name.
@@ -240,7 +254,7 @@
 arrayParam
     :: ArrayR (Array sh e)
     -> Name (Array sh e)
-    -> [LLVM.Parameter]
+    -> [LP.Typed LP.Ident]
 arrayParam (ArrayR shr tp) name = ad ++ sh
   where
     ad = travTypeToList tp              (\t i -> ptrParameter    t (arrayName name i))
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Environment.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Environment.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Environment.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Environment.hs
@@ -1,4 +1,6 @@
-{-# LANGUAGE GADTs #-}
+{-# LANGUAGE GADTs             #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms   #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : Data.Array.Accelerate.LLVM.CodeGen.Environment
@@ -19,7 +21,7 @@
 import qualified Data.IntMap                                    as IM
 
 import Data.Array.Accelerate.AST                                ( ArrayVar )
-import Data.Array.Accelerate.AST.Idx                            ( Idx(..), idxToInt )
+import Data.Array.Accelerate.AST.Idx                            ( Idx, pattern ZeroIdx, pattern SuccIdx, idxToInt )
 import Data.Array.Accelerate.AST.Var                            ( Var(..) )
 import Data.Array.Accelerate.Error                              ( internalError )
 import Data.Array.Accelerate.Representation.Array               ( Array, ArrayR(..) )
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Exp.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Exp.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Exp.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Exp.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE RankNTypes          #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TupleSections       #-}
@@ -104,7 +105,7 @@
                                           llvmOfOpenExp body (env `pushE` (lhs, x)) aenv
         Evar (Var _ ix)             -> return $ prj ix env
         Const tp c                  -> return $ ir tp $ scalar tp c
-        PrimConst c                 -> let tp = (SingleScalarType $ primConstType c)
+        PrimConst c                 -> let tp = SingleScalarType (primConstType c)
                                        in  return $ ir tp $ scalar tp $ primConst c
         PrimApp f x                 -> primFun f x
         Undef tp                    -> return $ ir tp $ undef tp
@@ -113,7 +114,8 @@
         VecPack   vecr e            -> vecPack   vecr =<< cvtE e
         VecUnpack vecr e            -> vecUnpack vecr =<< cvtE e
         Foreign tp asm f x          -> foreignE tp asm f =<< cvtE x
-        Case tag xs mx              -> A.caseof (expType (snd (head xs))) (cvtE tag) [(t,cvtE e) | (t,e) <- xs] (fmap cvtE mx)
+        Case _   [] _               -> internalError "Empty Case"
+        Case tag xs@((_, e1):_) mx  -> A.caseof (expType e1) (cvtE tag) [(t,cvtE e) | (t,e) <- xs] (fmap cvtE mx)
         Cond c t e                  -> cond (expType t) (cvtE c) (cvtE t) (cvtE e)
         IndexSlice slice slix sh    -> indexSlice slice <$> cvtE slix <*> cvtE sh
         IndexFull slice slix sh     -> indexFull slice  <$> cvtE slix <*> cvtE sh
@@ -224,7 +226,7 @@
 
     foreignE :: A.Foreign asm
              => TypeR b
-             -> asm           (a -> b)
+             -> asm    (a -> b)
              -> Fun () (a -> b)
              -> Operands a
              -> IRExp arch () b
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/IR.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/IR.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/IR.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/IR.hs
@@ -1,8 +1,9 @@
-{-# LANGUAGE GADTs         #-}
-{-# LANGUAGE LambdaCase    #-}
-{-# LANGUAGE RankNTypes    #-}
-{-# LANGUAGE TypeFamilies  #-}
-{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE GADTs             #-}
+{-# LANGUAGE LambdaCase        #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes        #-}
+{-# LANGUAGE TypeFamilies      #-}
+{-# LANGUAGE TypeOperators     #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : Data.Array.Accelerate.LLVM.CodeGen.IR
@@ -27,6 +28,7 @@
 
 import Data.Array.Accelerate.Error
 import Data.Primitive.Vec
+import Formatting
 
 import qualified Data.ByteString.Short                              as B
 
@@ -35,7 +37,7 @@
 -- representing a single Accelerate type. Using a data family rather than a type
 -- family means that Operands is bijective.
 --
-data family Operands e :: *
+data family Operands e
 data instance Operands ()         = OP_Unit
 data instance Operands Int        = OP_Int     (Operand Int)
 data instance Operands Int8       = OP_Int8    (Operand Int8)
@@ -72,10 +74,10 @@
 instance IROP PrimType where
   op (ScalarPrimType t) = op t
   op BoolPrimType       = \case OP_Bool x -> x
-  op t                  = internalError ("unhandled type: " ++ show t)
+  op t                  = internalError ("unhandled type: " % formatPrimType) t
   ir (ScalarPrimType t) = ir t
   ir BoolPrimType       = OP_Bool
-  ir t                  = internalError ("unhandled type: " ++ show t)
+  ir t                  = internalError ("unhandled type: " % formatPrimType) t
 
 instance IROP ScalarType where
   op (SingleScalarType t) = op t
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Module.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Module.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Module.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Module.hs
@@ -13,11 +13,9 @@
 module Data.Array.Accelerate.LLVM.CodeGen.Module
   where
 
--- llvm-hs
-import qualified LLVM.AST                                 as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty as LLVM
 
--- standard library
-import Data.Map                                           ( Map )
+import Data.Map.Strict                                    ( Map )
 
 
 -- | A compiled module consists of a number of global functions (kernels). The
@@ -26,14 +24,14 @@
 --
 data Module arch aenv a
   = Module { unModule       :: LLVM.Module
-           , moduleMetadata :: Map LLVM.Name (KernelMetadata arch)
+           , moduleMetadata :: Map LLVM.Symbol (KernelMetadata arch)
            }
 
 -- | A fully-instantiated skeleton is a [collection of] kernel(s) that can be compiled
 -- by LLVM into a global function that we can execute.
 --
 data Kernel arch aenv a
-  = Kernel { unKernel       :: LLVM.Global
+  = Kernel { unKernel       :: LLVM.Define
            , kernelMetadata :: KernelMetadata arch
            }
 
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Monad.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Monad.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Monad.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Monad.hs
@@ -22,9 +22,14 @@
   evalCodeGen,
   liftCodeGen,
 
+  -- codegen context
+  getLLVMversion,
+
   -- declarations
-  fresh, freshName,
-  declare,
+  fresh, freshLocalName, freshGlobalName,
+  declareGlobalVar,
+  declareExternFunc,
+  typedef,
   intrinsic,
 
   -- basic blocks
@@ -45,11 +50,11 @@
 import Data.Array.Accelerate.LLVM.CodeGen.Intrinsic
 import Data.Array.Accelerate.LLVM.CodeGen.Module
 import Data.Array.Accelerate.LLVM.CodeGen.Sugar                     ( IROpenAcc(..) )
-import Data.Array.Accelerate.LLVM.State                             ( LLVM )
+import Data.Array.Accelerate.LLVM.State                             ( LLVM, getLLVMVer )
 import Data.Array.Accelerate.LLVM.Target
 import Data.Array.Accelerate.Representation.Tag
 import Data.Array.Accelerate.Representation.Type
-import qualified Data.Array.Accelerate.Debug                        as Debug
+import qualified Data.Array.Accelerate.Debug.Internal               as Debug
 
 import LLVM.AST.Type.Constant
 import LLVM.AST.Type.Downcast
@@ -59,22 +64,26 @@
 import LLVM.AST.Type.Operand
 import LLVM.AST.Type.Representation
 import LLVM.AST.Type.Terminator
-import qualified LLVM.AST                                           as LLVM
-import qualified LLVM.AST.Global                                    as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LP
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty.PP  as LP
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty.Triple.Parse as LP
 
 import Control.Applicative
+import Control.Monad
+import Control.Monad.Reader                                         ( ReaderT, MonadReader, runReaderT, asks )
 import Control.Monad.State
 import Data.ByteString.Short                                        ( ShortByteString )
+import qualified Data.ByteString.Short.Char8                        as SBS8
 import Data.Function
 import Data.HashMap.Strict                                          ( HashMap )
-import Data.Map                                                     ( Map )
 import Data.Sequence                                                ( Seq )
 import Data.String
+import Data.Text.Lazy.Builder                                       ( Builder )
+import Formatting
 import Prelude
-import Text.Printf
 import qualified Data.Foldable                                      as F
 import qualified Data.HashMap.Strict                                as HashMap
-import qualified Data.Map                                           as Map
+import qualified Data.Map.Strict                                    as Map
 import qualified Data.Sequence                                      as Seq
 import qualified Data.ByteString.Short                              as B
 
@@ -89,59 +98,84 @@
 --
 data CodeGenState = CodeGenState
   { blockChain          :: Seq Block                                      -- blocks for this function
-  , symbolTable         :: Map Label LLVM.Global                          -- global (external) function declarations
+  , globalvarTable      :: HashMap Label LP.Global                        -- global variable definitions
+  , fundeclTable        :: HashMap Label LP.Declare                       -- global (external) function declarations
+  , typedefTable        :: HashMap Label LP.Type                          -- global type definitions
   , metadataTable       :: HashMap ShortByteString (Seq [Maybe Metadata]) -- module metadata to be collected
   , intrinsicTable      :: HashMap ShortByteString Label                  -- standard math intrinsic functions
-  , next                :: {-# UNPACK #-} !Word                           -- a name supply
+  , local               :: {-# UNPACK #-} !Word                           -- a name supply
+  , global              :: {-# UNPACK #-} !Word                           -- a name supply for global variables
   }
 
 data Block = Block
-  { blockLabel          :: {-# UNPACK #-} !Label                          -- block label
-  , instructions        :: Seq (LLVM.Named LLVM.Instruction)              -- stack of instructions
-  , terminator          :: LLVM.Terminator                                -- block terminator
+  { blockLabel          :: {-# UNPACK #-} !Label -- block label
+  , instructions        :: Seq LP.Stmt           -- stack of instructions
+  , terminator          :: LP.Stmt               -- block terminator
   }
 
-newtype CodeGen target a = CodeGen { runCodeGen :: StateT CodeGenState (LLVM target) a }
-  deriving (Functor, Applicative, Monad, MonadState CodeGenState)
+-- | Code generation context: read-only additional context of the compilation.
+--
+-- This contains the LLVM version.
+data CodeGenContext = CodeGenContext
+  { codegenLLVMversion :: LP.LLVMVer }
 
+newtype CodeGen target a = CodeGen
+  { runCodeGen :: ReaderT CodeGenContext (StateT CodeGenState (LLVM target)) a }
+  deriving (Functor, Applicative, Monad, MonadReader CodeGenContext, MonadState CodeGenState)
+
 liftCodeGen :: LLVM arch a -> CodeGen arch a
-liftCodeGen = CodeGen . lift
+liftCodeGen = CodeGen . lift . lift
 
+getLLVMversion :: CodeGen arch LP.LLVMVer
+getLLVMversion = asks codegenLLVMversion
 
+
 {-# INLINEABLE evalCodeGen #-}
 evalCodeGen
     :: forall arch aenv a. (HasCallStack, Target arch, Intrinsic arch)
     => CodeGen arch (IROpenAcc arch aenv a)
     -> LLVM    arch (Module    arch aenv a)
 evalCodeGen ll = do
-  (IROpenAcc ks, st)   <- runStateT (runCodeGen ll)
+  llvmver <- getLLVMVer
+  let context = CodeGenContext
+        { codegenLLVMversion = llvmver }
+  (IROpenAcc ks, st)   <- runStateT (runReaderT (runCodeGen ll) context)
                         $ CodeGenState
                             { blockChain        = initBlockChain
-                            , symbolTable       = Map.empty
+                            , globalvarTable    = HashMap.empty
+                            , fundeclTable      = HashMap.empty
+                            , typedefTable      = HashMap.empty
                             , metadataTable     = HashMap.empty
                             , intrinsicTable    = intrinsicForTarget @arch
-                            , next              = 0
+                            , local             = 0
+                            , global            = 0
                             }
 
-  let (kernels, md)     = let (fs, as) = unzip [ (f , (LLVM.name f, a)) | Kernel f a <- ks ]
+  let (kernels, md)     = let (fs, as) = unzip [ (f , (LP.defName f, a)) | Kernel f a <- ks ]
                           in  (fs, Map.fromList as)
 
-      definitions       = map LLVM.GlobalDefinition (kernels ++ Map.elems (symbolTable st))
-                       ++ createMetadata (metadataTable st)
+      createTypedefs    = map (\(n,t) -> LP.TypeDecl (labelToPrettyI n) t) . HashMap.toList
 
-      name | x:_               <- kernels
-           , f@LLVM.Function{} <- x
-           , LLVM.Name s       <- LLVM.name f = s
-           | otherwise                        = "<undefined>"
+      (namedMd, unnamedMd) = createMetadata (metadataTable st)
 
   return $
     Module { moduleMetadata = md
-           , unModule       = LLVM.Module
-                            { LLVM.moduleName           = name
-                            , LLVM.moduleSourceFileName = B.empty
-                            , LLVM.moduleDataLayout     = targetDataLayout @arch
-                            , LLVM.moduleTargetTriple   = targetTriple @arch
-                            , LLVM.moduleDefinitions    = definitions
+           , unModule       = LP.Module
+                            { LP.modSourceName = Nothing
+                            , LP.modDataLayout = []  -- TODO: targetDataLayout @arch; this will be important for PTX
+                            , LP.modTriple     =
+                                case targetTriple @arch of
+                                  Just s -> LP.parseTriple (SBS8.unpack s)
+                                  Nothing -> error "TODO: module target triple"
+                            , LP.modTypes      = createTypedefs (typedefTable st)
+                            , LP.modNamedMd    = namedMd
+                            , LP.modUnnamedMd  = unnamedMd
+                            , LP.modComdat     = mempty
+                            , LP.modGlobals    = HashMap.elems (globalvarTable st)
+                            , LP.modDeclares   = HashMap.elems (fundeclTable st)
+                            , LP.modDefines    = kernels
+                            , LP.modInlineAsm  = []
+                            , LP.modAliases    = []
                             }
            }
 
@@ -189,7 +223,7 @@
     let idx     = Seq.length (blockChain s)
         label   = let (h,t) = break (== '.') nm in (h ++ shows idx t)
         next    = Block (fromString label) Seq.empty err
-        err     = internalError (printf "block `%s' has no terminator" label)
+        err     = internalError ("block `" % string % "' has no terminator") label
     in
     ( next, s )
 
@@ -216,18 +250,18 @@
 -- body. The block stream is re-initialised, but module-level state such as the
 -- global symbol table is left intact.
 --
-createBlocks :: HasCallStack => CodeGen arch [LLVM.BasicBlock]
+createBlocks :: HasCallStack => CodeGen arch [LP.BasicBlock]
 createBlocks
   = state
-  $ \s -> let s'     = s { blockChain = initBlockChain, next = 0 }
+  $ \s -> let s'     = s { blockChain = initBlockChain, local = 0 }
               blocks = makeBlock `fmap` blockChain s
               m      = Seq.length (blockChain s)
               n      = F.foldl' (\i b -> i + Seq.length (instructions b)) 0 (blockChain s)
           in
-          trace (printf "generated %d instructions in %d blocks" (n+m) m) ( F.toList blocks , s' )
+          trace (bformat ("generated " % int % " instructions in " % int % " blocks") (n+m) m) ( F.toList blocks , s' )
   where
     makeBlock Block{..} =
-      LLVM.BasicBlock (downcast blockLabel) (F.toList instructions) (LLVM.Do terminator)
+      LP.BasicBlock (Just (labelToPrettyBL blockLabel)) (F.toList instructions ++ [terminator])
 
 
 -- Instructions
@@ -238,14 +272,19 @@
 fresh :: TypeR a -> CodeGen arch (Operands a)
 fresh TupRunit         = return OP_Unit
 fresh (TupRpair t2 t1) = OP_Pair <$> fresh t2 <*> fresh t1
-fresh (TupRsingle t)   = ir t . LocalReference (PrimType (ScalarPrimType t)) <$> freshName
+fresh (TupRsingle t)   = ir t . LocalReference (PrimType (ScalarPrimType t)) <$> freshLocalName
 
--- | Generate a fresh (un)name.
+-- | Generate a fresh local (un)name
 --
-freshName :: CodeGen arch (Name a)
-freshName = state $ \s@CodeGenState{..} -> ( UnName next, s { next = next + 1 } )
+freshLocalName :: CodeGen arch (Name a)
+freshLocalName = state $ \s@CodeGenState{..} -> ( UnName local, s { local = local + 1 } )
 
+-- | Generate a fresh global (un)name
+--
+freshGlobalName :: CodeGen arch (Name a)
+freshGlobalName = state $ \s@CodeGenState{..} -> ( UnName global, s { global = global + 1 } )
 
+
 -- | Add an instruction to the state of the currently active block so that it is
 -- computed, and return the operand (LocalReference) that can be used to later
 -- refer to it.
@@ -262,18 +301,18 @@
       return $ LocalReference VoidType (Name B.empty)
     --
     ty -> do
-      name <- freshName
-      instr_ $ downcast (name := ins)
+      name <- freshLocalName
+      instr_ $ LP.Result (nameToPrettyI name) (downcast ins) [] []
       return $ LocalReference ty name
 
 -- | Execute an unnamed instruction
 --
 do_ :: HasCallStack => Instruction () -> CodeGen arch ()
-do_ ins = instr_ $ downcast (Do ins)
+do_ ins = instr_ $ LP.Effect (downcast ins) [] []
 
 -- | Add raw assembly instructions to the execution stream
 --
-instr_ :: HasCallStack => LLVM.Named LLVM.Instruction -> CodeGen arch ()
+instr_ :: HasCallStack => LP.Stmt -> CodeGen arch ()
 instr_ ins =
   modify $ \s ->
     case Seq.viewr (blockChain s) of
@@ -335,7 +374,7 @@
 phi1 :: HasCallStack => Block -> Name a -> [(Operand a, Block)] -> CodeGen arch (Operand a)
 phi1 target crit incoming =
   let cmp       = (==) `on` blockLabel
-      update b  = b { instructions = downcast (crit := Phi t [ (p,blockLabel) | (p,Block{..}) <- incoming ]) Seq.<| instructions b }
+      update b  = b { instructions = LP.Result (nameToPrettyI crit) (downcast $ Phi t [ (p,blockLabel) | (p,Block{..}) <- incoming ]) [] [] Seq.<| instructions b }
       t         = case incoming of
                     []        -> internalError "no incoming values specified"
                     (o,_):_   -> case typeOf o of
@@ -357,24 +396,48 @@
   state $ \s ->
     case Seq.viewr (blockChain s) of
       Seq.EmptyR  -> internalError "empty block chain"
-      bs Seq.:> b -> ( b, s { blockChain = bs Seq.|> b { terminator = downcast term } } )
+      bs Seq.:> b -> ( b, s { blockChain = bs Seq.|> b { terminator = LP.Effect (downcast term) [] [] } } )
 
 
--- | Add a global declaration to the symbol table
+-- | Add a global variable declaration to the module
 --
-declare :: HasCallStack => LLVM.Global -> CodeGen arch ()
-declare g =
+declareGlobalVar :: HasCallStack => LP.Global -> CodeGen arch ()
+declareGlobalVar g =
   let unique (Just q) | g /= q    = internalError "duplicate symbol"
                       | otherwise = Just g
       unique _                    = Just g
 
-      name = case LLVM.name g of
-               LLVM.Name n      -> Label n
-               LLVM.UnName n    -> Label (fromString (show n))
+      name = case LP.globalSym g of
+               LP.Symbol n -> Label (fromString n)
   in
-  modify (\s -> s { symbolTable = Map.alter unique name (symbolTable s) })
+  modify (\s -> s { globalvarTable = HashMap.alter unique name (globalvarTable s) })
 
 
+-- | Add an external function declaration to the module
+--
+declareExternFunc :: HasCallStack => LP.Declare -> CodeGen arch ()
+declareExternFunc g =
+  let unique (Just q) | g /= q    = internalError "duplicate symbol"
+                      | otherwise = Just g
+      unique _                    = Just g
+
+      name = case LP.decName g of
+               LP.Symbol n -> Label (fromString n)
+  in
+  modify (\s -> s { fundeclTable = HashMap.alter unique name (fundeclTable s) })
+
+
+-- | Add a global type definition
+--
+typedef :: HasCallStack => Label -> LP.Type -> CodeGen arch ()
+typedef name t =
+  let unique (Just s) | t /= s    = internalError "duplicate typedef"
+                      | otherwise = Just t
+      unique _                    = Just t
+  in
+  modify (\s -> s { typedefTable = HashMap.alter unique name (typedefTable s) })
+
+
 -- | Get name of the corresponding intrinsic function implementing a given C
 -- function. If there is no mapping, the C function name is used.
 --
@@ -402,24 +465,24 @@
 -- represent the metadata node definitions that will be attached to that
 -- definition.
 --
-createMetadata :: HashMap ShortByteString (Seq [Maybe Metadata]) -> [LLVM.Definition]
-createMetadata md = build (HashMap.toList md) (Seq.empty, Seq.empty)
+createMetadata :: HashMap ShortByteString (Seq [Maybe Metadata]) -> ([LP.NamedMd], [LP.UnnamedMd])
+createMetadata md = go (HashMap.toList md) (Seq.empty, Seq.empty)
   where
-    build :: [(ShortByteString, Seq [Maybe Metadata])]
-          -> (Seq LLVM.Definition, Seq LLVM.Definition) -- accumulator of (names, metadata)
-          -> [LLVM.Definition]
-    build []     (k,d) = F.toList (k Seq.>< d)
-    build (x:xs) (k,d) =
+    go :: [(ShortByteString, Seq [Maybe Metadata])]
+       -> (Seq LP.NamedMd, Seq LP.UnnamedMd) -- accumulator of (names, metadata)
+       -> ([LP.NamedMd], [LP.UnnamedMd])
+    go []     (k,d) = (F.toList k, F.toList d)
+    go (x:xs) (k,d) =
       let (k',d') = meta (Seq.length d) x
-      in  build xs (k Seq.|> k', d Seq.>< d')
+      in  go xs (k Seq.|> k', d Seq.>< d')
 
     meta :: Int                                         -- number of metadata node definitions so far
          -> (ShortByteString, Seq [Maybe Metadata])     -- current assoc of the metadata map
-         -> (LLVM.Definition, Seq LLVM.Definition)
+         -> (LP.NamedMd, Seq LP.UnnamedMd)
     meta n (key, vals)
-      = let node i      = LLVM.MetadataNodeID (fromIntegral (i+n))
-            nodes       = Seq.mapWithIndex (\i x -> LLVM.MetadataNodeDefinition (node i) (downcast (F.toList x))) vals
-            name        = LLVM.NamedMetadataDefinition key [ node i | i <- [0 .. Seq.length vals - 1] ]
+      = let node i      = i + n
+            nodes       = Seq.mapWithIndex (\i x -> LP.UnnamedMd (node i) (LP.ValMdNode (downcast (F.toList x))) False) vals
+            name        = LP.NamedMd (SBS8.unpack key) [ node i | i <- [0 .. Seq.length vals - 1] ]
         in
         (name, nodes)
 
@@ -428,6 +491,6 @@
 -- =====
 
 {-# INLINE trace #-}
-trace :: String -> a -> a
-trace msg = Debug.trace Debug.dump_cc ("llvm: " ++ msg)
+trace :: Builder -> a -> a
+trace msg = Debug.trace Debug.dump_cc ("llvm: " <> msg)
 
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Monad.hs-boot b/src/Data/Array/Accelerate/LLVM/CodeGen/Monad.hs-boot
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Monad.hs-boot
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Monad.hs-boot
@@ -11,9 +11,12 @@
 module Data.Array.Accelerate.LLVM.CodeGen.Monad ( CodeGen )
   where
 
+import Control.Monad.Reader
 import Control.Monad.State
 import Data.Array.Accelerate.LLVM.State
 
 data CodeGenState
-newtype CodeGen target a = CodeGen { runCodeGen :: StateT CodeGenState (LLVM target) a }
+data CodeGenContext
+newtype CodeGen target a = CodeGen
+  { runCodeGen :: ReaderT CodeGenContext (StateT CodeGenState (LLVM target)) a }
 
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Permute.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Permute.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Permute.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Permute.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE GADTs               #-}
+{-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE RecordWildCards     #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TupleSections       #-}
@@ -29,7 +30,7 @@
 import Data.Array.Accelerate.AST.Idx
 import Data.Array.Accelerate.AST.LeftHandSide
 import Data.Array.Accelerate.AST.Var
-import Data.Array.Accelerate.Debug
+import Data.Array.Accelerate.Debug.Internal
 import Data.Array.Accelerate.Error
 import Data.Array.Accelerate.Representation.Type
 import Data.Array.Accelerate.Trafo.Substitution
@@ -43,7 +44,6 @@
 import Data.Array.Accelerate.LLVM.CodeGen.Type
 import Data.Array.Accelerate.LLVM.Foreign
 
-import LLVM.AST.Type.AddrSpace
 import LLVM.AST.Type.Instruction
 import LLVM.AST.Type.Instruction.Atomic
 import LLVM.AST.Type.Instruction.RMW                                as RMW
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Profile.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Profile.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Profile.hs
@@ -0,0 +1,201 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE OverloadedStrings        #-}
+{-# LANGUAGE TypeApplications         #-}
+{-# OPTIONS_HADDOCK hide #-}
+-- |
+-- Module      : Data.Array.Accelerate.LLVM.CodeGen.Profile
+-- Copyright   : [2015..2020] The Accelerate Team
+-- License     : BSD3
+--
+-- Maintainer  : Trevor L. McDonell <trevor.mcdonell@gmail.com>
+-- Stability   : experimental
+-- Portability : non-portable (GHC extensions)
+--
+
+module Data.Array.Accelerate.LLVM.CodeGen.Profile (
+
+  zone_begin, zone_begin_alloc,
+  zone_end,
+
+) where
+
+import LLVM.AST.Type.Constant
+import LLVM.AST.Type.Downcast
+import LLVM.AST.Type.Function
+import LLVM.AST.Type.GetElementPtr
+import LLVM.AST.Type.Global
+import LLVM.AST.Type.Name
+import LLVM.AST.Type.Operand
+import LLVM.AST.Type.Representation
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LP
+
+import Data.Array.Accelerate.LLVM.CodeGen.Base
+import Data.Array.Accelerate.LLVM.CodeGen.Constant
+import Data.Array.Accelerate.LLVM.CodeGen.IR
+import Data.Array.Accelerate.LLVM.CodeGen.Monad
+
+import Data.Array.Accelerate.Sugar.Elt
+import Data.Array.Accelerate.Debug.Internal                         ( tracyIsEnabled, SrcLoc, Zone )
+
+import Control.Monad
+import Data.Char
+
+
+call' :: GlobalFunction args t -> CodeGen arch (Operands t)
+call' f = call f [NoUnwind, NoDuplicate]
+
+global_string :: String -> CodeGen arch (Name (Ptr (LLArray Word8)), Word64)
+global_string str = do
+  let str0  = str ++ "\0"
+      l     = fromIntegral (length str0)
+  --
+  nm <- freshGlobalName
+  _  <- declareGlobalVar $ LP.Global
+    { LP.globalSym = nameToPrettyS nm
+    , LP.globalAttrs = LP.GlobalAttrs
+        { LP.gaLinkage = Just LP.Private
+        , LP.gaVisibility = Nothing
+        , LP.gaAddrSpace = LP.defaultAddrSpace
+        , LP.gaConstant = True }
+    , LP.globalType = LP.Array l (LP.PrimType (LP.Integer 8))
+    , LP.globalValue = Just $ LP.ValArray (LP.PrimType (LP.Integer 8)) [ LP.ValInteger (toInteger (ord c)) | c <- str0 ]
+    , LP.globalAlign = Nothing
+    , LP.globalMetadata = mempty
+    }
+  return (nm, l)
+
+derefGlobalString :: Word64 -> Name (Ptr (LLArray Word8)) -> Constant (Ptr Word8)
+derefGlobalString slen sname =
+  -- Global references are _pointers_ to their values. A string is an
+  -- [_ x i8], hence the global reference is an [_ x i8]*. The GEP needs
+  -- to index the outer pointer (with a 0) and index the array (at index
+  -- 0) to address the first i8 in the string; GEP then returns a pointer
+  -- to this i8.
+  ConstantGetElementPtr $ GEP
+    (PrimType (ArrayPrimType slen scalarType))
+    (GlobalReference (PrimType (PtrPrimType (ArrayPrimType slen scalarType) defaultAddrSpace)) sname)
+    (ScalarConstant scalarType 0 :: Constant Int32)
+    (GEPArray (ScalarConstant scalarType 0 :: Constant Int32) (GEPEmpty primType))
+
+
+-- struct ___tracy_source_location_data
+-- {
+--     const char* name;
+--     const char* function;
+--     const char* file;
+--     uint32_t line;
+--     uint32_t color;
+-- };
+--
+source_location_data :: String -> String -> String -> Int -> Word32 -> CodeGen arch (Name a)
+source_location_data nm fun src line colour = do
+  let i8ptr_t = LP.PtrTo (LP.PrimType (LP.Integer 8)) defaultAddrSpace
+      i32_t = LP.PrimType (LP.Integer 32)
+  _       <- typedef "___tracy_source_location_data" $ LP.Struct [ i8ptr_t, i8ptr_t, i8ptr_t, i32_t, i32_t ]
+  (s, sl) <- global_string src
+  (f, fl) <- global_string fun
+  (n, nl) <- global_string nm
+  let
+      source     = if null src then NullPtrConstant type' else derefGlobalString sl s
+      function   = if null fun then NullPtrConstant type' else derefGlobalString fl f
+      name       = if null nm  then NullPtrConstant type' else derefGlobalString nl n
+  --
+  v <- freshGlobalName
+  _ <- declareGlobalVar $ LP.Global
+    { LP.globalSym = nameToPrettyS v
+    , LP.globalAttrs = LP.GlobalAttrs
+        { LP.gaLinkage = Just LP.Internal
+        , LP.gaVisibility = Nothing
+        , LP.gaAddrSpace = LP.defaultAddrSpace
+        , LP.gaConstant = True }
+    , LP.globalType = LP.Alias (LP.Ident "___tracy_source_location_data")
+    , LP.globalValue = Just $
+        LP.ValStruct
+          [ downcast name
+          , downcast function
+          , downcast source
+          , LP.Typed (LP.PrimType (LP.Integer 32)) (LP.ValInteger (toInteger line))
+          , LP.Typed (LP.PrimType (LP.Integer 32)) (LP.ValInteger (toInteger colour))
+          ]
+    , LP.globalAlign = Just 8
+    , LP.globalMetadata = mempty
+    }
+  return v
+
+
+alloc_srcloc_name
+    :: Int      -- line
+    -> String   -- source file
+    -> String   -- function
+    -> String   -- name
+    -> CodeGen arch (Operands SrcLoc)
+alloc_srcloc_name l src fun nm
+  | not tracyIsEnabled = return (constant (eltR @SrcLoc) 0)
+  | otherwise              = do
+      (s, sl) <- global_string src
+      (f, fl) <- global_string fun
+      (n, nl) <- global_string nm
+      let
+          line       = ConstantOperand $ ScalarConstant scalarType (fromIntegral l :: Word32)
+          source     = ConstantOperand $ if null src then NullPtrConstant type' else derefGlobalString sl s
+          function   = ConstantOperand $ if null fun then NullPtrConstant type' else derefGlobalString fl f
+          name       = ConstantOperand $ if null nm  then NullPtrConstant type' else derefGlobalString nl n
+          sourceSz   = ConstantOperand $ ScalarConstant scalarType (sl-1) -- null
+          functionSz = ConstantOperand $ ScalarConstant scalarType (fl-1) -- null
+          nameSz     = ConstantOperand $ ScalarConstant scalarType (nl-1) -- null
+      --
+      call' $ Lam primType line
+            $ Lam primType source
+            $ Lam primType sourceSz
+            $ Lam primType function
+            $ Lam primType functionSz
+            $ Lam primType name
+            $ Lam primType nameSz
+            $ Body (type' @SrcLoc) (Just Tail) "___tracy_alloc_srcloc_name"
+
+zone_begin
+    :: Int      -- line
+    -> String   -- source file
+    -> String   -- function
+    -> String   -- name
+    -> Word32   -- colour
+    -> CodeGen arch (Operands Zone)
+zone_begin line src fun name colour
+  | not tracyIsEnabled = return (constant (eltR @SrcLoc) 0)
+  | otherwise              = do
+      srcloc <- source_location_data name fun src line colour
+      let srcloc_ty = PtrPrimType (NamedPrimType "___tracy_source_location_data") defaultAddrSpace
+      --
+      call' $ Lam srcloc_ty (ConstantOperand (GlobalReference (PrimType srcloc_ty) srcloc))
+            $ Lam primType (ConstantOperand (ScalarConstant scalarType (1 :: Int32)))
+            $ Body (type' @SrcLoc) (Just Tail) "___tracy_emit_zone_begin"
+
+zone_begin_alloc
+    :: Int      -- line
+    -> String   -- source file
+    -> String   -- function
+    -> String   -- name
+    -> Word32   -- colour
+    -> CodeGen arch (Operands Zone)
+zone_begin_alloc line src fun name colour
+  | not tracyIsEnabled = return (constant (eltR @Zone) 0)
+  | otherwise              = do
+      srcloc <- alloc_srcloc_name line src fun name
+      zone   <- call' $ Lam primType (op primType srcloc)
+                      $ Lam primType (ConstantOperand (ScalarConstant scalarType (1 :: Int32)))
+                      $ Body (type' @SrcLoc) (Just Tail) "___tracy_emit_zone_begin_alloc"
+      when (colour /= 0) $
+        void . call' $ Lam primType (op primType zone)
+                     $ Lam primType (ConstantOperand (ScalarConstant scalarType colour))
+                     $ Body VoidType (Just Tail) "___tracy_emit_zone_color"
+      return zone
+
+zone_end
+    :: Operands Zone
+    -> CodeGen arch ()
+zone_end zone
+  | not tracyIsEnabled = return ()
+  | otherwise              =
+      void . call' $ Lam primType (op primType zone)
+                   $ Body VoidType (Just Tail) "___tracy_emit_zone_end"
+
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Ptr.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Ptr.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Ptr.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Ptr.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE GADTs #-}
+{-# LANGUAGE GADTs             #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : Data.Array.Accelerate.LLVM.CodeGen.Ptr
@@ -13,7 +14,6 @@
 module Data.Array.Accelerate.LLVM.CodeGen.Ptr
   where
 
-import LLVM.AST.Type.AddrSpace
 import LLVM.AST.Type.Constant
 import LLVM.AST.Type.Name
 import LLVM.AST.Type.Operand
@@ -60,5 +60,7 @@
     LocalReference t n                    -> LocalReference (retype t) (rename n)
     ConstantOperand (GlobalReference t n) -> ConstantOperand (GlobalReference (retype t) (rename n))
     ConstantOperand (UndefConstant t)     -> ConstantOperand (UndefConstant (retype t))
+    ConstantOperand NullPtrConstant{}     -> internalError "unexpected null pointer constant"
     ConstantOperand ScalarConstant{}      -> internalError "unexpected scalar constant"
+    ConstantOperand _                     -> internalError "unexpected constant operand"
 
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Stencil.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Stencil.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Stencil.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Stencil.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE GADTs               #-}
+{-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE RebindableSyntax    #-}
 {-# LANGUAGE RecordWildCards     #-}
 {-# LANGUAGE ScopedTypeVariables #-}
@@ -34,6 +35,7 @@
 import qualified Data.Array.Accelerate.LLVM.CodeGen.Arithmetic      as A
 
 import Control.Applicative
+import Data.String
 import Prelude
 
 
@@ -270,7 +272,7 @@
            nb <- br ifExit
 
            setBlock ifExit
-           crit <- freshName
+           crit <- freshLocalName
            r    <- phi1 ifExit crit [(boolean False, eb), (A.unbool nv, nb)]
 
            return (OP_Bool r)
diff --git a/src/Data/Array/Accelerate/LLVM/CodeGen/Sugar.hs b/src/Data/Array/Accelerate/LLVM/CodeGen/Sugar.hs
--- a/src/Data/Array/Accelerate/LLVM/CodeGen/Sugar.hs
+++ b/src/Data/Array/Accelerate/LLVM/CodeGen/Sugar.hs
@@ -24,8 +24,8 @@
 
 ) where
 
-import LLVM.AST.Type.AddrSpace
 import LLVM.AST.Type.Instruction.Volatile
+import LLVM.AST.Type.Representation                                 ( AddrSpace )
 
 import Data.Array.Accelerate.Representation.Array
 
diff --git a/src/Data/Array/Accelerate/LLVM/Compile.hs b/src/Data/Array/Accelerate/LLVM/Compile.hs
--- a/src/Data/Array/Accelerate/LLVM/Compile.hs
+++ b/src/Data/Array/Accelerate/LLVM/Compile.hs
@@ -1,6 +1,6 @@
-{-# LANGUAGE CPP                 #-}
 {-# LANGUAGE EmptyCase           #-}
 {-# LANGUAGE GADTs               #-}
+{-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE RecordWildCards     #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TupleSections       #-}
@@ -51,6 +51,7 @@
 import qualified Data.Array.Accelerate.LLVM.AST                     as AST
 
 import Data.IntMap                                                  ( IntMap )
+import Formatting                                                   ( (%) )
 import Control.Applicative                                          hiding ( Const )
 import Prelude                                                      hiding ( map, unzip, zipWith, scanl, scanl1, scanr, scanr1, exp )
 import qualified Data.IntMap                                        as IntMap
@@ -149,6 +150,7 @@
         Acond p t e                 -> plain =<< liftA3 AST.Acond     <$> travE  p <*> travA  t <*> travA e
         Apair a1 a2                 -> plain =<< liftA2 AST.Apair     <$> travA a1 <*> travA a2
         Anil                        -> plain $ pure AST.Anil
+        Atrace msg a1 a2            -> plain =<< liftA2 (AST.Atrace msg) <$> travA a1 <*> travA a2
 
         -- Foreign arrays operations
         Aforeign repr ff afun a     -> foreignA repr ff afun a
@@ -164,12 +166,16 @@
         Map _ f a
           | Just (t,x) <- unzip f a -> plain $ pure (AST.Unzip t x)
 
-        -- Skeleton operations resulting in compiled code
+        -- Skeleton operations resulting in compiled code.
+        -- Important: AST fragments that accelerate:DAA.Analysis.Hash ignores,
+        -- must also be ignored here! Otherwise kernels with different aenvs
+        -- get the same hash. These fragments are ignored using ignoreE.
+
         -- Producers
         Map tp f a                  -> build =<< liftA2 (map tp)      <$> travF f  <*> travA a
-        Generate r sh f             -> build =<< liftA2 (generate r)  <$> travE sh <*> travF f
-        Transform r sh p f a        -> build =<< liftA4 (transform r) <$> travE sh <*> travF p <*> travF f <*> travA a
-        Backpermute shr sh f a      -> build =<< liftA3 (backpermute shr) <$> travE sh <*> travF f <*> travA a
+        Generate r sh f             -> build =<< liftA2 (generate r)  <$> ignoreE sh <*> travF f
+        Transform r sh p f a        -> build =<< liftA4 (transform r) <$> ignoreE sh <*> travF p <*> travF f <*> travA a
+        Backpermute shr sh f a      -> build =<< liftA3 (backpermute shr) <$> ignoreE sh <*> travF f <*> travA a
 
         -- Consumers
         Fold f z a                  -> build =<< liftA3 fold          <$> travF f <*> travME z <*> travD a
@@ -219,7 +225,7 @@
             (_,   h2) = stencilHalo s2
 
         fusionError :: error
-        fusionError = internalError $ "unexpected fusible material: " ++ showPreAccOp pacc
+        fusionError = internalError ("unexpected fusible material: " % formatPreAccOp) pacc
 
         travA :: HasCallStack
               => DelayedOpenAcc aenv a
@@ -265,6 +271,11 @@
         travB (Constant c) = return $ pure (Constant c)
         travB (Function f) = liftA Function <$> travF f
 
+        ignoreE :: HasCallStack
+                => OpenExp env aenv e
+                -> LLVM arch (IntMap (Idx' aenv), OpenExp env aenv e)
+        ignoreE exp = return $ pure exp
+
         build :: (IntMap (Idx' aenv), AST.PreOpenAccSkeleton CompiledOpenAcc arch aenv arrs)
               -> LLVM arch (CompiledOpenAcc arch aenv arrs)
         build (aenv, eacc) = do
@@ -323,8 +334,7 @@
             go (TupRpair v1 v2)        = AST.UnzipPair (go v1) (go v2)
             go (TupRsingle (Var _ ix)) = case lookupVar lhs ix of
               Right u -> u
-              Left ix' -> case ix' of {}
-              -- Left branch is unreachable, as `Idx () y` is an empty type
+              Left _  -> internalError "Left branch is unreachable as `Idx () y` is an empty type"
 
             lookupVar :: ELeftHandSide x env1 env2 -> Idx env2 y -> Either (Idx env1 y) (AST.UnzipIdx x y)
             lookupVar (LeftHandSideWildcard _) ix = Left ix
@@ -334,7 +344,7 @@
             lookupVar (LeftHandSidePair l1 l2) ix = case lookupVar l2 ix of
               Right u -> Right $ AST.UnzipPrj PairIdxRight u
               Left ix' -> case lookupVar l1 ix' of
-                Right u -> Right $ AST.UnzipPrj PairIdxLeft u
+                Right u   -> Right $ AST.UnzipPrj PairIdxLeft u
                 Left ix'' -> Left ix''
 
         -- Is there a foreign version available for this backend? If so, take
diff --git a/src/Data/Array/Accelerate/LLVM/Compile/Cache.hs b/src/Data/Array/Accelerate/LLVM/Compile/Cache.hs
--- a/src/Data/Array/Accelerate/LLVM/Compile/Cache.hs
+++ b/src/Data/Array/Accelerate/LLVM/Compile/Cache.hs
@@ -20,7 +20,7 @@
 
 import Data.Array.Accelerate.AST
 import Data.Array.Accelerate.Analysis.Hash
-import Data.Array.Accelerate.Debug
+import Data.Array.Accelerate.Debug.Internal
 import Data.Array.Accelerate.Trafo.Delayed
 
 import Data.Array.Accelerate.LLVM.State
@@ -75,19 +75,20 @@
     => UID
     -> LLVM arch FilePath
 cacheOfUID uid = do
-  dbg       <- liftIO $ if debuggingIsEnabled then getFlag debug else return False
-  appdir    <- liftIO $ getXdgDirectory XdgCache "accelerate"
-  template  <- targetCacheTemplate
+  appdir   <- liftIO $ getXdgDirectory XdgCache "accelerate"
+  template <- targetCacheTemplate
   let
       (base, file)  = splitFileName template
       (name, ext)   = splitExtensions file
       --
-      cachepath     = appdir </> "accelerate-llvm-" ++ showVersion version </> base </> if dbg then "dbg" else "rel"
+      tag | tracyIsEnabled = "tracy"
+          | debuggingIsEnabled = "dbg"
+          | otherwise = "rel"
+      cachepath     = appdir </> "accelerate-llvm-" ++ showVersion version </> base </> tag
       cachefile     = cachepath </> printf "%s%s" name (show uid) <.> ext
   --
   liftIO $ createDirectoryIfMissing True cachepath
   return cachefile
-
 
 -- | Remove the cache directory
 --
diff --git a/src/Data/Array/Accelerate/LLVM/Embed.hs b/src/Data/Array/Accelerate/LLVM/Embed.hs
--- a/src/Data/Array/Accelerate/LLVM/Embed.hs
+++ b/src/Data/Array/Accelerate/LLVM/Embed.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE CPP                 #-}
 {-# LANGUAGE GADTs               #-}
+{-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE QuasiQuotes         #-}
 {-# LANGUAGE RankNTypes          #-}
 {-# LANGUAGE ScopedTypeVariables #-}
@@ -26,7 +27,7 @@
 
 import LLVM.AST.Type.Name
 
-import Data.Array.Accelerate.AST                                    ( PreOpenAfun(..), ArrayVar, Direction(..), Exp, liftALeftHandSide, liftOpenExp, arrayR )
+import Data.Array.Accelerate.AST                                    ( PreOpenAfun(..), ArrayVar, Direction(..), Exp, liftALeftHandSide, liftOpenExp, liftMessage, arraysR, arrayR )
 import Data.Array.Accelerate.AST.Idx
 import Data.Array.Accelerate.AST.Var
 import Data.Array.Accelerate.Error
@@ -43,11 +44,10 @@
 
 import Data.ByteString.Short                                        ( ShortByteString )
 import GHC.Ptr                                                      ( Ptr(..) )
-import Language.Haskell.TH                                          ( Q, TExp )
+import Data.Array.Accelerate.TH.Compat                              ( CodeQ )
 import System.IO.Unsafe
 import qualified Data.ByteString.Short.Internal                     as BS
-import qualified Language.Haskell.TH                                as TH
-import qualified Language.Haskell.TH.Syntax                         as TH
+import qualified Data.Array.Accelerate.TH.Compat                    as TH
 
 #if MIN_VERSION_containers(0,5,9)
 import qualified Data.IntMap.Internal                               as IM
@@ -67,7 +67,7 @@
   embedForTarget
       :: arch
       -> ObjectR arch
-      -> Q (TExp (ExecutableR arch))
+      -> CodeQ (ExecutableR arch)
 
 
 -- | Embed the compiled array function into a TemplateHaskell expression,
@@ -78,7 +78,7 @@
     :: Embed arch
     => arch
     -> CompiledAfun arch f
-    -> Q (TExp (ExecAfun arch f))
+    -> CodeQ (ExecAfun arch f)
 embedAfun = embedOpenAfun
 
 {-# INLINEABLE embedOpenAfun #-}
@@ -86,7 +86,7 @@
     :: (HasCallStack, Embed arch)
     => arch
     -> CompiledOpenAfun arch aenv f
-    -> Q (TExp (ExecOpenAfun arch aenv f))
+    -> CodeQ (ExecOpenAfun arch aenv f)
 embedOpenAfun arch (Alam lhs l) = [|| Alam $$(liftALeftHandSide lhs) $$(embedOpenAfun arch l) ||]
 embedOpenAfun arch (Abody b)    = [|| Abody $$(embedOpenAcc arch b) ||]
 
@@ -95,39 +95,43 @@
     :: forall arch aenv arrs. (HasCallStack, Embed arch)
     => arch
     -> CompiledOpenAcc arch aenv arrs
-    -> Q (TExp (ExecOpenAcc arch aenv arrs))
+    -> CodeQ (ExecOpenAcc arch aenv arrs)
 embedOpenAcc arch = liftA
   where
-    liftA :: CompiledOpenAcc arch aenv' arrs' -> Q (TExp (ExecOpenAcc arch aenv' arrs'))
+    liftA :: CompiledOpenAcc arch aenv' arrs' -> CodeQ (ExecOpenAcc arch aenv' arrs')
     liftA acc = case acc of
         PlainAcc repr pacc          -> [|| EvalAcc $$(liftArraysR repr) $$(liftPreOpenAccCommand arch pacc) ||]
         BuildAcc repr aenv obj pacc -> [|| ExecAcc $$(liftArraysR repr) $$(liftGamma aenv) $$(embedForTarget arch obj) $$(liftPreOpenAccSkeleton arch pacc) ||]
 
-    liftGamma :: Gamma aenv' -> Q (TExp (Gamma aenv'))
-#if MIN_VERSION_containers(0,5,8)
+    liftGamma :: Gamma aenv' -> CodeQ (Gamma aenv')
+#if MIN_VERSION_containers(0,8,0)
     liftGamma IM.Nil           = [|| IM.Nil ||]
+    liftGamma (IM.Bin p l r)   = [|| IM.Bin p $$(liftGamma l) $$(liftGamma r) ||]
+    liftGamma (IM.Tip k v)     = [|| IM.Tip k $$(liftV v) ||]
+#elif MIN_VERSION_containers(0,5,8)
+    liftGamma IM.Nil           = [|| IM.Nil ||]
     liftGamma (IM.Bin p m l r) = [|| IM.Bin p m $$(liftGamma l) $$(liftGamma r) ||]
     liftGamma (IM.Tip k v)     = [|| IM.Tip k $$(liftV v) ||]
 #else
     -- O(n) at runtime to reconstruct the set
     liftGamma aenv             = [|| IM.fromAscList $$(liftIM (IM.toAscList aenv)) ||]
       where
-        liftIM :: [(Int, (Label, Idx' aenv'))] -> Q (TExp [(Int, (Label, Idx' aenv'))])
+        liftIM :: [(Int, (Label, Idx' aenv'))] -> CodeQ [(Int, (Label, Idx' aenv'))]
         liftIM im =
           TH.TExp . TH.ListE <$> mapM (\(k,v) -> TH.unTypeQ [|| (k, $$(liftV v)) ||]) im
 #endif
-    liftV :: (Label, Idx' aenv') -> Q (TExp (Label, Idx' aenv'))
+    liftV :: (Label, Idx' aenv') -> CodeQ (Label, Idx' aenv')
     liftV (Label n, Idx' repr ix) = [|| (Label $$(liftSBS n), Idx' $$(liftArrayR repr) $$(liftIdx ix)) ||]
 
     -- O(n) at runtime to copy from the Addr# to the ByteArray#. We should
     -- be able to do this without copying, but I don't think the definition of
     -- ByteArray# is exported (or it is deeply magical).
-    liftSBS :: ShortByteString -> Q (TExp ShortByteString)
+    liftSBS :: ShortByteString -> CodeQ ShortByteString
     liftSBS bs =
       let bytes = BS.unpack bs
           len   = BS.length bs
       in
-      [|| unsafePerformIO $ BS.createFromPtr $$( TH.unsafeTExpCoerce [| Ptr $(TH.litE (TH.StringPrimL bytes)) |]) len ||]
+      [|| unsafePerformIO $ BS.createFromPtr $$( TH.unsafeCodeCoerce [| Ptr $(TH.litE (TH.StringPrimL bytes)) |]) len ||]
 
 
 {-# INLINEABLE liftPreOpenAfun #-}
@@ -135,7 +139,7 @@
     :: (HasCallStack, Embed arch)
     => arch
     -> PreOpenAfun (CompiledOpenAcc arch) aenv t
-    -> Q (TExp (PreOpenAfun (ExecOpenAcc arch) aenv t))
+    -> CodeQ (PreOpenAfun (ExecOpenAcc arch) aenv t)
 liftPreOpenAfun arch (Alam lhs f) = [|| Alam $$(liftALeftHandSide lhs) $$(liftPreOpenAfun arch f) ||]
 liftPreOpenAfun arch (Abody b)    = [|| Abody $$(embedOpenAcc arch b) ||]
 
@@ -144,16 +148,16 @@
     :: forall arch aenv a. (HasCallStack, Embed arch)
     => arch
     -> PreOpenAccCommand CompiledOpenAcc arch aenv a
-    -> Q (TExp (PreOpenAccCommand ExecOpenAcc arch aenv a))
+    -> CodeQ (PreOpenAccCommand ExecOpenAcc arch aenv a)
 liftPreOpenAccCommand arch pacc =
   let
-      liftA :: CompiledOpenAcc arch aenv' arrs -> Q (TExp (ExecOpenAcc arch aenv' arrs))
+      liftA :: CompiledOpenAcc arch aenv' arrs -> CodeQ (ExecOpenAcc arch aenv' arrs)
       liftA = embedOpenAcc arch
 
-      liftE :: Exp aenv t -> Q (TExp (Exp aenv t))
+      liftE :: Exp aenv t -> CodeQ (Exp aenv t)
       liftE = liftOpenExp
 
-      liftAF :: PreOpenAfun (CompiledOpenAcc arch) aenv f -> Q (TExp (PreOpenAfun (ExecOpenAcc arch) aenv f))
+      liftAF :: PreOpenAfun (CompiledOpenAcc arch) aenv f -> CodeQ (PreOpenAfun (ExecOpenAcc arch) aenv f)
       liftAF = liftPreOpenAfun arch
   in
   case pacc of
@@ -164,6 +168,7 @@
     Unit tp e         -> [|| Unit $$(liftTypeR tp) $$(liftE e) ||]
     Apair a1 a2       -> [|| Apair $$(liftA a1) $$(liftA a2) ||]
     Anil              -> [|| Anil ||]
+    Atrace msg a1 a2  -> [|| Atrace $$(liftMessage (arraysR a1) msg) $$(liftA a1) $$(liftA a2) ||]
     Apply repr f a    -> [|| Apply $$(liftArraysR repr) $$(liftAF f) $$(liftA a) ||]
     Acond p t e       -> [|| Acond $$(liftE p) $$(liftA t) $$(liftA e) ||]
     Awhile p f a      -> [|| Awhile $$(liftAF p) $$(liftAF f) $$(liftA a) ||]
@@ -176,27 +181,27 @@
     :: forall arch aenv a. (HasCallStack, Embed arch)
     => arch
     -> PreOpenAccSkeleton CompiledOpenAcc arch aenv a
-    -> Q (TExp (PreOpenAccSkeleton ExecOpenAcc arch aenv a))
+    -> CodeQ (PreOpenAccSkeleton ExecOpenAcc arch aenv a)
 liftPreOpenAccSkeleton arch pacc =
   let
-      liftA :: CompiledOpenAcc arch aenv arrs -> Q (TExp (ExecOpenAcc arch aenv arrs))
+      liftA :: CompiledOpenAcc arch aenv arrs -> CodeQ (ExecOpenAcc arch aenv arrs)
       liftA = embedOpenAcc arch
 
-      liftD :: DelayedOpenAcc CompiledOpenAcc arch aenv arrs -> Q (TExp (DelayedOpenAcc ExecOpenAcc arch aenv arrs))
+      liftD :: DelayedOpenAcc CompiledOpenAcc arch aenv arrs -> CodeQ (DelayedOpenAcc ExecOpenAcc arch aenv arrs)
       liftD (Delayed repr sh) = [|| Delayed $$(liftArrayR repr) $$(liftE sh) ||]
       liftD (Manifest repr a) = [|| Manifest $$(liftArraysR repr) $$(liftA a) ||]
 
-      liftE :: Exp aenv t -> Q (TExp (Exp aenv t))
+      liftE :: Exp aenv t -> CodeQ (Exp aenv t)
       liftE = liftOpenExp
 
-      liftS :: ShapeR sh -> sh -> Q (TExp sh)
+      liftS :: ShapeR sh -> sh -> CodeQ sh
       liftS shr sh = [|| $$(liftElt (shapeType shr) sh) ||]
 
-      liftZ :: HasInitialValue -> Q (TExp HasInitialValue)
+      liftZ :: HasInitialValue -> CodeQ HasInitialValue
       liftZ True  = [|| True  ||]
       liftZ False = [|| False ||]
 
-      liftDir :: Direction -> Q (TExp Direction)
+      liftDir :: Direction -> CodeQ Direction
       liftDir LeftToRight = [|| LeftToRight ||]
       liftDir RightToLeft = [|| RightToLeft ||]
   in
@@ -213,10 +218,10 @@
     Stencil1 tp h a      -> [|| Stencil1 $$(liftTypeR tp) $$(liftS (arrayRshape $ arrayR a) h) $$(liftD a) ||]
     Stencil2 tp h a b    -> [|| Stencil2 $$(liftTypeR tp) $$(liftS (arrayRshape $ arrayR a) h) $$(liftD a) $$(liftD b) ||]
 
-liftArrayVar :: ArrayVar aenv v -> Q (TExp (ArrayVar aenv v))
+liftArrayVar :: ArrayVar aenv v -> CodeQ (ArrayVar aenv v)
 liftArrayVar (Var tp v) = [|| Var $$(liftArrayR tp) $$(liftIdx v) ||]
 
-liftUnzipIdx :: UnzipIdx tup e -> Q (TExp (UnzipIdx tup e))
+liftUnzipIdx :: UnzipIdx tup e -> CodeQ (UnzipIdx tup e)
 liftUnzipIdx UnzipId                    = [|| UnzipId ||]
 liftUnzipIdx (UnzipPrj PairIdxLeft  ix) = [|| UnzipPrj PairIdxLeft  $$(liftUnzipIdx ix) ||]
 liftUnzipIdx (UnzipPrj PairIdxRight ix) = [|| UnzipPrj PairIdxRight $$(liftUnzipIdx ix) ||]
diff --git a/src/Data/Array/Accelerate/LLVM/Execute.hs b/src/Data/Array/Accelerate/LLVM/Execute.hs
--- a/src/Data/Array/Accelerate/LLVM/Execute.hs
+++ b/src/Data/Array/Accelerate/LLVM/Execute.hs
@@ -1,6 +1,5 @@
 {-# LANGUAGE AllowAmbiguousTypes   #-}
 {-# LANGUAGE BangPatterns          #-}
-{-# LANGUAGE CPP                   #-}
 {-# LANGUAGE GADTs                 #-}
 {-# LANGUAGE LambdaCase            #-}
 {-# LANGUAGE ScopedTypeVariables   #-}
@@ -31,7 +30,7 @@
 import Data.Array.Accelerate.AST.Var
 import Data.Array.Accelerate.Analysis.Match
 import Data.Array.Accelerate.Array.Data
-import Data.Array.Accelerate.Interpreter                        ( evalPrim, evalPrimConst, evalCoerceScalar )
+import Data.Array.Accelerate.Interpreter                        ( evalPrim, evalPrimConst, evalCoerceScalar, atraceOp )
 import Data.Array.Accelerate.Representation.Array
 import Data.Array.Accelerate.Representation.Elt
 import Data.Array.Accelerate.Representation.Shape
@@ -40,7 +39,7 @@
 import Data.Array.Accelerate.Representation.Type
 import Data.Array.Accelerate.Representation.Vec
 import Data.Array.Accelerate.Type
-import qualified Data.Array.Accelerate.Debug                    as Debug
+import qualified Data.Array.Accelerate.Debug.Internal           as Debug
 
 import Data.Array.Accelerate.LLVM.AST                           hiding ( Delayed, Manifest )
 import Data.Array.Accelerate.LLVM.Array.Data
@@ -51,6 +50,7 @@
 import qualified Data.Array.Accelerate.LLVM.AST                 as AST
 
 import Control.Monad
+import Control.Monad.Trans                                      ( liftIO )
 import System.IO.Unsafe
 import Prelude                                                  hiding ( exp, map, unzip, scanl, scanr, scanl1, scanr1 )
 
@@ -279,6 +279,12 @@
         Anil                   -> return ()
         Alloc repr sh          -> allocate repr sh
         Apply _ f a            -> travAF f =<< spawn (travA a)
+        Atrace msg a1 a2       -> do
+          let repr = arraysR a1
+          a1' <- travA a1 >>= blockArrays repr >>= copyToHost repr
+          liftIO $ atraceOp msg a1'
+          travA a2
+
         -- We need quite some type applications in the rules for acond and awhile, and cannot use do notation.
         -- For some unknown reason, GHC will "simplify" 'FutureArraysR arch a' to 'FutureR arch a', which is not sound.
         -- It then complains that 'FutureR arch a' isn't assignable to 'FutureArraysR arch a'. By adding explicit
diff --git a/src/Data/Array/Accelerate/LLVM/Execute/Async.hs b/src/Data/Array/Accelerate/LLVM/Execute/Async.hs
--- a/src/Data/Array/Accelerate/LLVM/Execute/Async.hs
+++ b/src/Data/Array/Accelerate/LLVM/Execute/Async.hs
@@ -22,19 +22,21 @@
 import Data.Array.Accelerate.Representation.Array
 import Data.Array.Accelerate.Representation.Type
 
+import Control.Monad.Trans                                          ( MonadIO )
+import Data.Kind
 import GHC.Stack
 
 
-class Monad (Par arch) => Async arch where
+class (Monad (Par arch), MonadIO (Par arch)) => Async arch where
 
   -- | The monad parallel computations will be executed in. Presumably a stack
   -- with the LLVM monad at the base.
   --
-  data Par arch :: * -> *
+  data Par arch :: Type -> Type
 
   -- | Parallel computations can communicate via futures.
   --
-  type FutureR arch :: * -> *
+  type FutureR arch :: Type -> Type
 
   -- | Create a new (empty) promise, to be fulfilled at some future point.
   --
diff --git a/src/Data/Array/Accelerate/LLVM/Execute/Marshal.hs b/src/Data/Array/Accelerate/LLVM/Execute/Marshal.hs
--- a/src/Data/Array/Accelerate/LLVM/Execute/Marshal.hs
+++ b/src/Data/Array/Accelerate/LLVM/Execute/Marshal.hs
@@ -35,6 +35,7 @@
 import Data.Array.Accelerate.LLVM.Execute.Environment
 import Data.Array.Accelerate.LLVM.Execute.Async
 
+import Data.Bifunctor                                           ( first )
 import Data.DList                                               ( DList )
 import qualified Data.DList                                     as DL
 import qualified Data.IntMap                                    as IM
@@ -42,49 +43,53 @@
 
 -- Marshalling arguments
 -- ---------------------
-class Async arch => Marshal arch where
+class (Async arch, Monoid (MarshalCleanup arch)) => Marshal arch where
   -- | A type family that is used to specify a concrete kernel argument and
   -- stream/context type for a given backend target.
   --
   type ArgR arch
 
+  -- | A cleanup action for a marshalled argument. On PTX this is an IO action;
+  -- on Native, no cleanup is necessary.
+  type MarshalCleanup arch
+
   -- | Used to pass shapes as arguments to kernels.
   marshalInt :: Int -> ArgR arch
 
   -- | Pass arrays to kernels
-  marshalScalarData' :: SingleType e -> ScalarArrayData e -> Par arch (DList (ArgR arch))
+  marshalScalarData' :: SingleType e -> ScalarArrayData e -> Par arch (DList (ArgR arch), MarshalCleanup arch)
 
 -- | Convert function arguments into stream a form suitable for function calls
 -- The functions ending in a prime return a DList, other functions return lists.
 --
-marshalArrays :: forall arch arrs. Marshal arch => ArraysR arrs -> arrs -> Par arch [ArgR arch]
-marshalArrays repr arrs = DL.toList <$> marshalArrays' @arch repr arrs
+marshalArrays :: forall arch arrs. Marshal arch => ArraysR arrs -> arrs -> Par arch ([ArgR arch], MarshalCleanup arch)
+marshalArrays repr arrs = first DL.toList <$> marshalArrays' @arch repr arrs
 
-marshalArrays' :: forall arch arrs. Marshal arch => ArraysR arrs -> arrs -> Par arch (DList (ArgR arch))
+marshalArrays' :: forall arch arrs. Marshal arch => ArraysR arrs -> arrs -> Par arch (DList (ArgR arch), MarshalCleanup arch)
 marshalArrays' = marshalTupR' @arch (marshalArray' @arch)
 
-marshalArray' :: forall arch a. Marshal arch => ArrayR a -> a -> Par arch (DList (ArgR arch))
+marshalArray' :: forall arch a. Marshal arch => ArrayR a -> a -> Par arch (DList (ArgR arch), MarshalCleanup arch)
 marshalArray' (ArrayR shr tp) (Array sh a) = do
-  arg1 <- marshalArrayData' @arch tp a
+  (arg1, c1) <- marshalArrayData' @arch tp a
   let arg2 = marshalShape' @arch shr sh
-  return $ arg1 `DL.append` arg2
+  return (arg1 `DL.append` arg2, c1)
 
-marshalArrayData' :: forall arch t. Marshal arch => TypeR t -> ArrayData t -> Par arch (DList (ArgR arch))
-marshalArrayData' TupRunit ()               = return DL.empty
+marshalArrayData' :: forall arch t. Marshal arch => TypeR t -> ArrayData t -> Par arch (DList (ArgR arch), MarshalCleanup arch)
+marshalArrayData' TupRunit ()               = return (DL.empty, mempty)
 marshalArrayData' (TupRpair t1 t2) (a1, a2) = do
-  l1 <- marshalArrayData' t1 a1
-  l2 <- marshalArrayData' t2 a2
-  return $ l1 `DL.append` l2
+  (l1, c1) <- marshalArrayData' t1 a1
+  (l2, c2) <- marshalArrayData' t2 a2
+  return (l1 `DL.append` l2, c1 <> c2)
 marshalArrayData' (TupRsingle t) ad
   | ScalarArrayDict _ s <- scalarArrayDict t
   = marshalScalarData' @arch s ad
 
-marshalEnv :: forall arch aenv. Marshal arch => Gamma aenv -> ValR arch aenv -> Par arch [ArgR arch]
-marshalEnv g a = DL.toList <$> marshalEnv' g a
+marshalEnv :: forall arch aenv. Marshal arch => Gamma aenv -> ValR arch aenv -> Par arch ([ArgR arch], MarshalCleanup arch)
+marshalEnv g a = first DL.toList <$> marshalEnv' g a
 
-marshalEnv' :: forall arch aenv. Marshal arch => Gamma aenv -> ValR arch aenv -> Par arch (DList (ArgR arch))
+marshalEnv' :: forall arch aenv. Marshal arch => Gamma aenv -> ValR arch aenv -> Par arch (DList (ArgR arch), MarshalCleanup arch)
 marshalEnv' gamma aenv
-    = fmap DL.concat
+    = fmap mconcat
     $ mapM (\(_, Idx' repr idx) -> marshalArray' @arch repr =<< get (prj idx aenv)) (IM.elems gamma)
 
 marshalShape :: forall arch sh. Marshal arch => ShapeR sh -> sh -> [ArgR arch]
@@ -105,22 +110,22 @@
   ParamRshape  :: ShapeR sh           -> ParamR arch sh
   ParamRargs   ::                        ParamR arch (DList (ArgR arch))
 
-marshalParam' :: forall arch a. Marshal arch => ParamR arch a -> a -> Par arch (DList (ArgR arch))
+marshalParam' :: forall arch a. Marshal arch => ParamR arch a -> a -> Par arch (DList (ArgR arch), MarshalCleanup arch)
 marshalParam' (ParamRarray repr)  a        = marshalArray' repr a
-marshalParam' (ParamRmaybe _   )  Nothing  = return $ DL.empty
+marshalParam' (ParamRmaybe _   )  Nothing  = return (DL.empty, mempty)
 marshalParam' (ParamRmaybe repr)  (Just a) = marshalParam' repr a
 marshalParam' (ParamRfuture repr) future   = marshalParam' repr =<< get future
 marshalParam' (ParamRenv gamma)   aenv     = marshalEnv'   gamma aenv
-marshalParam'  ParamRint          x        = return $ DL.singleton $ marshalInt @arch x
-marshalParam' (ParamRshape shr)   sh       = return $ marshalShape' @arch shr sh
-marshalParam'  ParamRargs         args     = return args
+marshalParam'  ParamRint          x        = return (DL.singleton (marshalInt @arch x), mempty)
+marshalParam' (ParamRshape shr)   sh       = return (marshalShape' @arch shr sh, mempty)
+marshalParam'  ParamRargs         args     = return (args, mempty)
 
-marshalParams' :: forall arch a. Marshal arch => ParamsR arch a -> a -> Par arch (DList (ArgR arch))
+marshalParams' :: forall arch a. Marshal arch => ParamsR arch a -> a -> Par arch (DList (ArgR arch), MarshalCleanup arch)
 marshalParams' = marshalTupR' @arch (marshalParam' @arch)
 
 {-# INLINE marshalTupR' #-}
-marshalTupR' :: forall arch s a. Marshal arch => (forall b. s b -> b -> Par arch (DList (ArgR arch))) -> TupR s a -> a -> Par arch (DList (ArgR arch))
-marshalTupR' _ TupRunit         ()       = return $ DL.empty
+marshalTupR' :: forall arch s a. Marshal arch => (forall b. s b -> b -> Par arch (DList (ArgR arch), MarshalCleanup arch)) -> TupR s a -> a -> Par arch (DList (ArgR arch), MarshalCleanup arch)
+marshalTupR' _ TupRunit         ()       = return (DL.empty, mempty)
 marshalTupR' f (TupRsingle t)   x        = f t x
-marshalTupR' f (TupRpair t1 t2) (x1, x2) = DL.append <$> marshalTupR' @arch f t1 x1 <*> marshalTupR' @arch f t2 x2
+marshalTupR' f (TupRpair t1 t2) (x1, x2) = (<>) <$> marshalTupR' @arch f t1 x1 <*> marshalTupR' @arch f t2 x2
 
diff --git a/src/Data/Array/Accelerate/LLVM/Extra.hs b/src/Data/Array/Accelerate/LLVM/Extra.hs
--- a/src/Data/Array/Accelerate/LLVM/Extra.hs
+++ b/src/Data/Array/Accelerate/LLVM/Extra.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : Data.Array.Accelerate.LLVM.Extra
diff --git a/src/Data/Array/Accelerate/LLVM/Link.hs b/src/Data/Array/Accelerate/LLVM/Link.hs
--- a/src/Data/Array/Accelerate/LLVM/Link.hs
+++ b/src/Data/Array/Accelerate/LLVM/Link.hs
@@ -122,6 +122,7 @@
         Awhile p f a            -> Awhile          <$> travAF p <*> travAF f <*> travA a
         Acond p t e             -> Acond p         <$> travA t  <*> travA e
         Apair a1 a2             -> Apair           <$> travA a1 <*> travA a2
+        Atrace msg a1 a2        -> Atrace msg      <$> travA a1 <*> travA a2
         Anil                    -> return Anil
         Reshape shr s ix        -> Reshape shr s   <$> pure ix
         Aforeign s r f a        -> Aforeign s r f  <$> travA a
diff --git a/src/Data/Array/Accelerate/LLVM/Link/Cache.hs b/src/Data/Array/Accelerate/LLVM/Link/Cache.hs
--- a/src/Data/Array/Accelerate/LLVM/Link/Cache.hs
+++ b/src/Data/Array/Accelerate/LLVM/Link/Cache.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : Data.Array.Accelerate.LLVM.Link.Cache
@@ -16,15 +17,15 @@
 
 ) where
 
-import Data.Array.Accelerate.Debug
+import Data.Array.Accelerate.Debug.Internal
 import Data.Array.Accelerate.Lifetime
 import Data.Array.Accelerate.LLVM.Compile.Cache
 
 import Control.Monad
 import Control.Concurrent.MVar
 import Data.Map.Strict                                              ( Map )
+import Formatting
 import Prelude                                                      hiding ( lookup )
-import Text.Printf
 import qualified Data.Map.Strict                                    as Map
 
 
@@ -106,7 +107,7 @@
   ticket <- newLifetime fun
   addFinalizer ticket $
     let refcount (Entry c f o)
-          | c <= 1    = trace dump_ld (printf "ld: remove object code %s" (show key)) Nothing
+          | c <= 1    = trace dump_ld (bformat ("ld: remove object code " % shown) key) Nothing
           | otherwise = Just (Entry (c-1) f o)
     in
     modifyMVar_ var $ \m -> return $! Map.update refcount key m
diff --git a/src/Data/Array/Accelerate/LLVM/State.hs b/src/Data/Array/Accelerate/LLVM/State.hs
--- a/src/Data/Array/Accelerate/LLVM/State.hs
+++ b/src/Data/Array/Accelerate/LLVM/State.hs
@@ -1,5 +1,7 @@
-{-# LANGUAGE CPP                        #-}
+{-# LANGUAGE FlexibleInstances          #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses      #-}
+{-# LANGUAGE RankNTypes                 #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : Data.Array.Accelerate.LLVM.State
@@ -14,11 +16,16 @@
 module Data.Array.Accelerate.LLVM.State
   where
 
--- library
-import Control.Concurrent                               ( forkIO, threadDelay )
+-- accelerate
+import Data.Array.Accelerate.LLVM.Target.ClangInfo
+
+-- llvm-pretty
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty.PP as LP
+
+-- standard library
 import Control.Monad.Catch                              ( MonadCatch, MonadThrow, MonadMask )
-import Control.Monad.State                              ( StateT, MonadState, evalStateT )
-import Control.Monad.Trans                              ( MonadIO )
+import Control.Monad.Reader                             ( ReaderT(..), MonadReader, runReaderT, ask, local )
+import Control.Monad.Trans                              ( MonadIO, lift )
 import Prelude
 
 
@@ -29,10 +36,15 @@
 -- for the LLVM execution context as well as the per-execution target specific
 -- state 'target'.
 --
-newtype LLVM target a = LLVM { runLLVM :: StateT target IO a }
-  deriving (Functor, Applicative, Monad, MonadIO, MonadState target, MonadThrow, MonadCatch, MonadMask)
+newtype LLVM target a = LLVM { runLLVM :: ReaderT LP.LLVMVer (ReaderT target IO) a }
+  deriving (Functor, Applicative, Monad, MonadIO, MonadThrow, MonadCatch, MonadMask)
 
--- | Extract the execution state: 'gets llvmTarget'
+-- not derived because the LLVMVer reader masks this one
+instance MonadReader target (LLVM target) where
+  ask = LLVM (lift ask)
+  local f (LLVM (ReaderT g)) = LLVM (ReaderT (local f . g))
+
+-- | Extract the execution state: 'asks llvmTarget'
 --
 llvmTarget :: t -> t
 llvmTarget = id
@@ -41,19 +53,33 @@
 --
 evalLLVM :: t -> LLVM t a -> IO a
 evalLLVM target acc =
-  evalStateT (runLLVM acc) target
+  case llvmverFromTuple hostLLVMVersion of
+    Just version -> runReaderT (runReaderT (runLLVM acc) version) target
+    Nothing -> fail "accelerate-llvm: Could not determine LLVM version from Clang output"
 
+getLLVMVer :: LLVM target LP.LLVMVer
+getLLVMVer = LLVM ask
 
--- | Make sure the GC knows that we want to keep this thing alive forever.
---
--- We may want to introduce some way to actually shut this down if, for example,
--- the object has not been accessed in a while (whatever that means).
---
--- Broken in ghci-7.6.1 Mac OS X due to bug #7299.
---
-keepAlive :: a -> IO a
-keepAlive x = forkIO (caffeine x) >> return x
+-- | This is a valid implementation of @withRunInIO@ in
+-- unliftio-core:Control.Monad.IO.Unlift(MonadUnliftIO); it's not an instance
+-- to avoid a dependency.
+unliftIOLLVM :: ((forall a. LLVM target a -> IO a) -> IO b) -> LLVM target b
+unliftIOLLVM f = LLVM (ReaderT (\llvmver -> ReaderT (\target -> f (run llvmver target))))
   where
-    caffeine hit = do threadDelay (5 * 1000 * 1000) -- microseconds = 5 seconds
-                      caffeine hit
+    run :: LP.LLVMVer -> target -> LLVM target a -> IO a
+    run llvmver target (LLVM m) = runReaderT (runReaderT m llvmver) target
+
+
+-- -- | Make sure the GC knows that we want to keep this thing alive forever.
+-- --
+-- -- We may want to introduce some way to actually shut this down if, for example,
+-- -- the object has not been accessed in a while (whatever that means).
+-- --
+-- -- Broken in ghci-7.6.1 Mac OS X due to bug #7299.
+-- --
+-- keepAlive :: a -> IO a
+-- keepAlive x = forkIO (caffeine x) >> return x
+--   where
+--     caffeine hit = do threadDelay (5 * 1000 * 1000) -- microseconds = 5 seconds
+--                       caffeine hit
 
diff --git a/src/Data/Array/Accelerate/LLVM/Target.hs b/src/Data/Array/Accelerate/LLVM/Target.hs
--- a/src/Data/Array/Accelerate/LLVM/Target.hs
+++ b/src/Data/Array/Accelerate/LLVM/Target.hs
@@ -13,7 +13,7 @@
 module Data.Array.Accelerate.LLVM.Target
   where
 
-import LLVM.AST.DataLayout                                ( DataLayout )
+import Data.Array.Accelerate.LLVM.Internal.LLVMPretty     ( DataLayout )
 import Data.ByteString.Short                              ( ShortByteString )
 
 
diff --git a/src/Data/Array/Accelerate/LLVM/Target/ClangInfo.hs b/src/Data/Array/Accelerate/LLVM/Target/ClangInfo.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Array/Accelerate/LLVM/Target/ClangInfo.hs
@@ -0,0 +1,193 @@
+{-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_HADDOCK hide #-}
+module Data.Array.Accelerate.LLVM.Target.ClangInfo where
+
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty.PP  as LP
+
+-- standard library
+import qualified Control.Exception                                  as E
+import Data.ByteString                                              ( ByteString )
+import qualified Data.ByteString.Char8                              as BS8
+import Data.ByteString.Short                                        ( ShortByteString )
+import qualified Data.ByteString.Short.Char8                        as SBS8
+import Data.Char                                                    ( isDigit, isSpace )
+import Data.List                                                    ( tails, sortBy )
+import Data.List.NonEmpty                                           ( NonEmpty )
+import qualified Data.List.NonEmpty                                 as NE
+import Data.Maybe                                                   ( fromMaybe, catMaybes, isJust )
+import Data.Ord                                                     ( comparing, Down(..) )
+import System.Directory                                             ( executable, getPermissions, listDirectory )
+import System.Environment                                           ( lookupEnv )
+import qualified System.Info                                        as Info
+import System.IO                                                    ( hPutStrLn, stderr )
+import System.IO.Unsafe
+import System.Process
+
+
+-- | String that describes the native target
+--
+nativeTargetTriple :: ShortByteString
+nativeTargetTriple =
+  SBS8.pack $
+    fromMaybe (error $ "Could not extract native target triple from `clang -###` output: <" ++ clangMachineVersionOutput ++ ">")
+              (getLinePrefixedBy "Target: " clangMachineVersionOutput)
+
+-- | String that describes the host CPU
+--
+nativeCPUName :: ByteString
+nativeCPUName =
+  case catMaybes (map tryFromLine (lines clangMachineVersionOutput)) of
+    cpu : _ -> BS8.pack cpu
+    _ -> error $ "Could not extract target CPU from `clang -###` output: <" ++ clangMachineVersionOutput ++ ">"
+  where
+  tryFromLine :: String -> Maybe String
+  tryFromLine line =
+    case map snd
+         . filter ((== "\"-target-cpu\" ") . fst)
+         . map (splitAt 14)
+         $ tails line of
+      ('"' : rest) : _
+        | (cpu, '"' : _) <- break (== '"') rest
+        -> Just cpu
+      _ -> Nothing
+
+
+-- | Returns list of version components: '12.3' becomes [12, 3].
+hostLLVMVersion :: NonEmpty Int
+hostLLVMVersion =
+  fmap read . splitOn '.' $
+    let firstLine = takeWhile (/= '\n') $
+                      dropWhile isSpace clangMachineVersionOutput
+    in case catMaybes (map (`startsWith` "clang version") (tails firstLine)) of
+         rest : _ ->
+           -- "Ubuntu clang version 14.0.0-1ubuntu1.1" <- we care about the "14.0.0" part only
+           takeWhile (\c -> not (isSpace c) && c /= '-')
+           $ dropWhile isSpace
+           $ rest
+         [] -> error $ "Could not extract clang version from `clang -###` output: <" ++ clangMachineVersionOutput ++ ">"
+  where
+  splitOn :: Eq a => a -> [a] -> NonEmpty [a]
+  splitOn _ [] = [] NE.:| []
+  splitOn sep (c:cs)
+    | c == sep = [] NE.<| splitOn sep cs
+    | otherwise = let hd NE.:| tl = splitOn sep cs
+                  in (c : hd) NE.:| tl
+
+  startsWith :: Eq a => [a] -> [a] -> Maybe [a]
+  long `startsWith` short =
+    let (pre, post) = splitAt (length short) long
+    in if pre == short then Just post else Nothing
+
+llvmverFromTuple :: NonEmpty Int -> Maybe LP.LLVMVer
+llvmverFromTuple (3 NE.:| 5 : _) = Just LP.llvmV3_5
+llvmverFromTuple (3 NE.:| 6 : _) = Just LP.llvmV3_6
+llvmverFromTuple (3 NE.:| 7 : _) = Just LP.llvmV3_7
+llvmverFromTuple (3 NE.:| 8 : _) = Just LP.llvmV3_8
+llvmverFromTuple (n NE.:| _)
+  | n >= 4
+  -- Don't compare against the "latest" here, because in practice, LLVM textual
+  -- IR is fairly forward-compatible.
+  -- , n <= P.llvmVlatest
+  = Just n
+llvmverFromTuple _ = Nothing
+
+-- | This is a string that contains some debug metadata from clang (clang
+-- version, target triple, etc.) as well as the inferred command-line
+-- invocation for a simple operation (preprocessor mode). This list of
+-- arguments happens to include the CPU name.
+--
+-- Yes, this does include the requisite information in the requisite format for
+-- clang versions (tested) 7, 8, 9, 11, 12, 15, 18 and 19. Probably more.
+{-# NOINLINE clangMachineVersionOutput #-}
+clangMachineVersionOutput :: String
+clangMachineVersionOutput =
+  unsafePerformIO $ do
+    mstderrOutput <- E.try @IOError $ do
+      -- The -w flag is to suppress warnings.
+      -- The -march is for x86, and -mcpu is for ARM. Handling of these flags
+      -- is backend-dependent in clang and gcc and weirdly idiosyncratic; clang
+      -- barely documents this if at all, but forum posts imply that it tries
+      -- to match gcc's behaviour. Fortunately, it seems that clang still
+      -- prints all we need even if the incompatible flag is present, so we
+      -- just pass both.
+      (_ec, _out, err) <- readProcessWithExitCode clangExePath ["-E", "-", "-march=native", "-mcpu=native", "-w", "-###"] ""
+      return err
+    case mstderrOutput of
+      Left _ -> do
+        let overridden = isJust clangExePathEnvironment
+        let msg1 =
+              "[accelerate-llvm] The Accelerate LLVM backend requires Clang to be installed. " ++
+              "Furthermore, accelerate-llvm-ptx, if you use it, requires clang version >= 16. " ++
+              "(Tried to run: '" ++ clangExePath ++ "'."
+            msg2 | overridden = ""
+                 | otherwise =
+                     " To override this choice, set the " ++
+                     "ACCELERATE_LLVM_CLANG_PATH environment variable to point to the desired " ++
+                     "clang executable."
+            msg3 = ")"
+        hPutStrLn stderr $ msg1 ++ msg2 ++ msg3
+        -- not an IOError because we're in unsafePerformIO
+        errorWithoutStackTrace $
+          "accelerate-llvm: Clang not found: " ++ clangExePath ++
+           (if overridden then "" else " (set ACCELERATE_LLVM_CLANG_PATH to override)")
+      Right out -> return out
+
+clangExePath :: FilePath
+clangExePath
+  | Just path <- clangExePathEnvironment = path
+  -- For some reason, Windows is always "mingw32", even if it's actually 64-bit, or whatever.
+  | Info.os == "mingw32" = clangExePathWindows
+  | otherwise            = "clang"  -- on unix, don't try fancy logic
+
+{-# NOINLINE clangExePathWindows #-}
+clangExePathWindows :: FilePath
+clangExePathWindows = unsafePerformIO $ do
+  let attempts :: [IO (Maybe a)] -> IO (Maybe a)
+      attempts [] = return Nothing
+      attempts (act:acts) = act >>= maybe (attempts acts) (return . Just)
+
+  -- Tries "$base\LLVM{,-[0-9]+}\bin\clang.exe"
+  let tryLLVMdir base = do
+        listing <- E.catch @E.IOException
+                     (listDirectory base)
+                     (\_ -> return [])
+        let llvmdirs = filter (\s -> take 4 s == "LLVM") listing
+        let plain = filter (== "LLVM") llvmdirs
+        let versions = [(s, read (drop 5 s) :: Int)
+                       | s <- llvmdirs
+                       , take 5 s == "LLVM-"
+                       , length s > 5
+                       , all isDigit (drop 5 s)]
+        -- Prefer a plain "LLVM" directory; if there are only versioned ones,
+        -- prefer the highest-versioned.
+        attempts
+          [do isExe <- E.catch @E.IOException
+                         (executable <$> getPermissions exe)
+                         (\_ -> return False)
+              return $ if isExe then Just exe else Nothing
+          | name <- plain ++ map fst (sortBy (comparing (Down . snd)) versions)
+          , let exe = base ++ "\\" ++ name ++ "\\bin\\clang.exe"]
+
+  -- TODO: any more places to look?
+  mpath <- attempts
+    [tryLLVMdir "C:\\Program Files"
+    ,tryLLVMdir "C:\\"]
+  case mpath of
+    Just path -> return path
+    Nothing -> return "clang"  -- fall back to the system path
+
+{-# NOINLINE clangExePathEnvironment #-}
+clangExePathEnvironment :: Maybe FilePath
+clangExePathEnvironment = unsafePerformIO $ lookupEnv "ACCELERATE_LLVM_CLANG_PATH"
+
+-- | Returns trimmed right-hand side
+getLinePrefixedBy :: String -> String -> Maybe String
+getLinePrefixedBy prefix str =
+  case map snd
+       . filter ((== prefix) . fst)
+       . map (splitAt (length prefix))
+       $ lines str of
+    rhs : _ -> Just (trim rhs)
+    _ -> Nothing
+  where
+    trim = reverse . dropWhile (== '\n') . reverse
diff --git a/src/Data/Array/Accelerate/TH/Compat.hs b/src/Data/Array/Accelerate/TH/Compat.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Array/Accelerate/TH/Compat.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE CPP                 #-}
+{-# LANGUAGE KindSignatures      #-}
+{-# LANGUAGE PolyKinds           #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell     #-}
+{-# OPTIONS_HADDOCK hide #-}
+-- |
+-- Module      : Data.Array.Accelerate.TH.Compat
+-- Copyright   : [2019..2021] The Accelerate Team
+-- License     : BSD3
+--
+-- Maintainer  : Trevor L. McDonell <trevor.mcdonell@gmail.com>
+-- Stability   : experimental
+-- Portability : non-portable (GHC extensions)
+--
+
+module Data.Array.Accelerate.TH.Compat (
+
+  module Language.Haskell.TH,
+#if! MIN_VERSION_template_haskell(2,17,0)
+  module Data.Array.Accelerate.TH.Compat,
+#endif
+
+) where
+
+import Language.Haskell.TH
+
+#if !MIN_VERSION_template_haskell(2,17,0)
+import Language.Haskell.TH.Syntax                                   ( unTypeQ, unsafeTExpCoerce )
+#if MIN_VERSION_template_haskell(2,16,0)
+import GHC.Exts                                                     ( RuntimeRep, TYPE )
+#endif
+#endif
+
+
+#if !MIN_VERSION_template_haskell(2,17,0)
+#if MIN_VERSION_template_haskell(2,16,0)
+type Code m (a :: TYPE (r :: RuntimeRep)) = m (TExp a)
+type CodeQ (a :: TYPE (r :: RuntimeRep)) = Code Q a
+
+unsafeCodeCoerce :: forall (r :: RuntimeRep) (a :: TYPE r). Q Exp -> CodeQ a
+unsafeCodeCoerce = unsafeTExpCoerce
+
+unTypeCode :: forall (r :: RuntimeRep) (a :: TYPE r). CodeQ a -> Q Exp
+unTypeCode = unTypeQ
+
+bindCode :: forall m a (r :: RuntimeRep) (b :: TYPE r). Monad m => m a -> (a -> Code m b) -> Code m b
+bindCode = (>>=)
+
+#else
+type Code m a = m (TExp a)
+type CodeQ a = Code Q a
+
+unsafeCodeCoerce :: Q Exp -> Q (TExp a)
+unsafeCodeCoerce = unsafeTExpCoerce
+
+unTypeCode :: CodeQ a -> Q Exp
+unTypeCode = unTypeQ
+
+bindCode :: Monad m => m a -> (a -> Code m b) -> Code m b
+bindCode = (>>=)
+#endif
+#endif
+
diff --git a/src/Data/ByteString/Short/Char8.hs b/src/Data/ByteString/Short/Char8.hs
--- a/src/Data/ByteString/Short/Char8.hs
+++ b/src/Data/ByteString/Short/Char8.hs
@@ -22,7 +22,7 @@
 import Prelude                                                      as P hiding ( takeWhile )
 import qualified Data.ByteString.Internal                           as BI
 import qualified Data.ByteString.Short                              as BS
-import qualified Data.ByteString.Short.Extra                        as BS
+import qualified Data.ByteString.Short.Extra                        as BE
 
 
 -- | /O(n)/ Convert a 'ShortByteString' into a list.
@@ -42,5 +42,5 @@
 --
 {-# INLINEABLE takeWhile #-}
 takeWhile :: (Char -> Bool) -> ShortByteString -> ShortByteString
-takeWhile f = BS.takeWhile (f . BI.w2c)
+takeWhile f = BE.takeWhile (f . BI.w2c)
 
diff --git a/src/Data/ByteString/Short/Extra.hs b/src/Data/ByteString/Short/Extra.hs
--- a/src/Data/ByteString/Short/Extra.hs
+++ b/src/Data/ByteString/Short/Extra.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BangPatterns    #-}
+{-# LANGUAGE CPP             #-}
 {-# LANGUAGE MagicHash       #-}
 {-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE UnboxedTuples   #-}
@@ -22,22 +23,34 @@
 
 ) where
 
-import Data.ByteString.Short                                        ( ShortByteString )
+import Data.ByteString.Short
 import qualified Data.ByteString.Short                              as BS
 import qualified Data.ByteString.Short.Internal                     as BI
 
-import Language.Haskell.TH                                          ( Q, TExp )
-import qualified Language.Haskell.TH                                as TH
-import qualified Language.Haskell.TH.Syntax                         as TH
+import Data.Array.Accelerate.TH.Compat                              ( CodeQ )
+import qualified Data.Array.Accelerate.TH.Compat                    as TH
 
 import System.IO.Unsafe
 import Prelude                                                      hiding ( take, takeWhile )
 
+#if !MIN_VERSION_bytestring(0,11,3)
 import GHC.ST
-import GHC.Exts
 import GHC.Word
+#endif
+import GHC.Exts
 
 
+-- | Lift a ShortByteString into a Template Haskell splice
+--
+liftSBS :: ShortByteString -> CodeQ ShortByteString
+liftSBS bs =
+  let bytes = BS.unpack bs
+      len   = BS.length bs
+  in
+  [|| unsafePerformIO $ BI.createFromPtr $$( TH.unsafeCodeCoerce [| Ptr $(TH.litE (TH.StringPrimL bytes)) |]) len ||]
+
+
+#if !MIN_VERSION_bytestring(0,11,3)
 -- | /O(n)/ @'take' n@ applied to the ShortByteString @xs@, returns the prefix
 -- of @xs@ of length @n@ as a new ShortByteString, or @xs@ itself if
 -- @n > 'length' xs@
@@ -75,15 +88,6 @@
           | otherwise                = go (i+1)
 
 
--- | Lift a ShortByteString into a Template Haskell splice
---
-liftSBS :: ShortByteString -> Q (TExp ShortByteString)
-liftSBS bs =
-  let bytes = BS.unpack bs
-      len   = BS.length bs
-  in
-  [|| unsafePerformIO $ BI.createFromPtr $$( TH.unsafeTExpCoerce [| Ptr $(TH.litE (TH.StringPrimL bytes)) |]) len ||]
-
 ------------------------------------------------------------------------
 -- Internal utils
 
@@ -117,4 +121,5 @@
 copyByteArray (BA# src#) (I# src_off#) (MBA# dst#) (I# dst_off#) (I# len#) =
     ST $ \s -> case copyByteArray# src# src_off# dst# dst_off# len# s of
                  s' -> (# s', () #)
+#endif
 
diff --git a/src/LLVM/AST/Type/AddrSpace.hs b/src/LLVM/AST/Type/AddrSpace.hs
deleted file mode 100644
--- a/src/LLVM/AST/Type/AddrSpace.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-{-# OPTIONS_HADDOCK hide #-}
--- |
--- Module      : LLVM.AST.Type.AddrSpace
--- Copyright   : [2016..2020] The Accelerate Team
--- License     : BSD3
---
--- Maintainer  : Trevor L. McDonell <trevor.mcdonell@gmail.com>
--- Stability   : experimental
--- Portability : non-portable (GHC extensions)
---
--- Pointers exist in a particular address space
---
-
-module LLVM.AST.Type.AddrSpace (
-
-  AddrSpace(..),
-  defaultAddrSpace,
-
-) where
-
-import LLVM.AST.AddrSpace
-
-
--- | The default address space is number zero. The semantics of non-zero address
--- spaces are target dependent.
---
-defaultAddrSpace :: AddrSpace
-defaultAddrSpace = AddrSpace 0
-
diff --git a/src/LLVM/AST/Type/Constant.hs b/src/LLVM/AST/Type/Constant.hs
--- a/src/LLVM/AST/Type/Constant.hs
+++ b/src/LLVM/AST/Type/Constant.hs
@@ -19,12 +19,11 @@
   where
 
 import LLVM.AST.Type.Downcast
+import LLVM.AST.Type.GetElementPtr
 import LLVM.AST.Type.Name
 import LLVM.AST.Type.Representation
 
-import qualified LLVM.AST.Constant                                  as LLVM
-import qualified LLVM.AST.Float                                     as LLVM
-import qualified LLVM.AST.Type                                      as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LLVM
 
 import Data.Constraint
 import Data.Primitive.ByteArray
@@ -53,44 +52,55 @@
   UndefConstant         :: Type a
                         -> Constant a
 
+  NullPtrConstant       :: Type (Ptr a)
+                        -> Constant (Ptr a)
+
   GlobalReference       :: Type a
                         -> Name a
                         -> Constant a
 
+  ConstantGetElementPtr :: GetElementPtr Constant (Ptr a) (Ptr b)
+                        -> Constant (Ptr b)
 
--- | Convert to llvm-hs
+
+-- | Convert to llvm-pretty
 --
-instance Downcast (Constant a) LLVM.Constant where
+instance Downcast (Constant a) (LLVM.Typed LLVM.Value) where
   downcast = \case
-    UndefConstant t       -> LLVM.Undef (downcast t)
-    GlobalReference t n   -> LLVM.GlobalReference (downcast t) (downcast n)
-    BooleanConstant x     -> LLVM.Int 1 (toInteger (fromEnum x))
-    ScalarConstant t x    -> scalar t x
+    UndefConstant t             -> LLVM.Typed (downcast t) LLVM.ValUndef
+    GlobalReference t n         -> LLVM.Typed (downcast t) (LLVM.ValSymbol (nameToPrettyS n))
+    instr@(ConstantGetElementPtr (GEP t n i1 path)) ->
+      LLVM.Typed (downcast (typeOf instr)) (LLVM.ValConstExpr (LLVM.ConstGEP inbounds Nothing (downcast t) (downcast n) (downcast i1 : downcast path)))
+    BooleanConstant x           -> LLVM.Typed (LLVM.PrimType (LLVM.Integer 1)) (LLVM.ValInteger (toInteger (fromEnum x)))
+    NullPtrConstant t           -> LLVM.Typed (downcast t) LLVM.ValNull
+    ScalarConstant t x          -> scalar t x
     where
-      scalar :: ScalarType s -> s -> LLVM.Constant
+      scalar :: ScalarType s -> s -> LLVM.Typed LLVM.Value
       scalar (SingleScalarType s) = single s
       scalar (VectorScalarType s) = vector s
 
-      single :: SingleType s -> s -> LLVM.Constant
+      single :: SingleType s -> s -> LLVM.Typed LLVM.Value
       single (NumSingleType s) = num s
 
-      vector :: VectorType s -> s -> LLVM.Constant
-      vector (VectorType _ s) (Vec ba#)
-        = LLVM.Vector
-        $ map (single s)
+      vector :: VectorType s -> s -> LLVM.Typed LLVM.Value
+      vector (VectorType n s) (Vec ba#)
+        = LLVM.Typed (LLVM.Vector (fromIntegral n) (downcast s))
+        $ LLVM.ValVector (downcast s)
+        $ map (LLVM.typedValue . single s)
         $ singlePrim s `withDict` foldrByteArray (:) [] (ByteArray ba#)
 
-      num :: NumType s -> s -> LLVM.Constant
+      num :: NumType s -> s -> LLVM.Typed LLVM.Value
       num (IntegralNumType s) v
         | IntegralDict <- integralDict s
-        = LLVM.Int (LLVM.typeBits (downcast s)) (fromIntegral v)
+        = LLVM.Typed (downcast s) (LLVM.ValInteger (fromIntegral v))
 
       num (FloatingNumType s) v
-        = LLVM.Float
-        $ case s of
-            TypeFloat                        -> LLVM.Single v
-            TypeDouble                       -> LLVM.Double v
-            TypeHalf | Half (CUShort u) <- v -> LLVM.Half u
+        = case s of
+            TypeFloat                        -> LLVM.Typed (LLVM.PrimType (LLVM.FloatType LLVM.Float)) (LLVM.ValFloat v)
+            TypeDouble                       -> LLVM.Typed (LLVM.PrimType (LLVM.FloatType LLVM.Double)) (LLVM.ValDouble v)
+            TypeHalf | Half (CUShort _u) <- v ->
+              -- LLVM.Typed (LLVM.PrimType (LLVM.FloatType LLVM.Half)) (_ _u)
+              error "TODO: Half floats unsupported by llvm-pretty"
 
       singlePrim :: SingleType s -> Dict (Prim s)
       singlePrim (NumSingleType s) = numPrim s
@@ -116,9 +126,13 @@
       floatingPrim TypeFloat  = Dict
       floatingPrim TypeDouble = Dict
 
-instance TypeOf Constant where
-  typeOf (BooleanConstant _)   = type'
-  typeOf (ScalarConstant t _)  = PrimType (ScalarPrimType t)
-  typeOf (UndefConstant t)     = t
-  typeOf (GlobalReference t _) = t
+      inbounds :: [LLVM.GEPAttr]
+      inbounds = [LLVM.GEP_Inbounds]
 
+instance TypeOf Constant where
+  typeOf (BooleanConstant _)           = type'
+  typeOf (ScalarConstant t _)          = PrimType (ScalarPrimType t)
+  typeOf (UndefConstant t)             = t
+  typeOf (NullPtrConstant t)           = t
+  typeOf (GlobalReference t _)         = t
+  typeOf (ConstantGetElementPtr gep)   = typeOf gep
diff --git a/src/LLVM/AST/Type/Downcast.hs b/src/LLVM/AST/Type/Downcast.hs
--- a/src/LLVM/AST/Type/Downcast.hs
+++ b/src/LLVM/AST/Type/Downcast.hs
@@ -1,6 +1,8 @@
 {-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TemplateHaskell       #-}
+{-# LANGUAGE TypeSynonymInstances  #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : LLVM.AST.Type.Downcast
@@ -19,7 +21,8 @@
 ) where
 
 import Data.Array.Accelerate.Type
-import qualified LLVM.AST.Type                                      as LLVM
+-- import qualified LLVM.AST.Type                                      as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LLVM
 
 import Data.Bits
 
@@ -58,7 +61,7 @@
   downcast (NumSingleType t) = downcast t
 
 instance Downcast (VectorType a) LLVM.Type where
-  downcast (VectorType n t) = LLVM.VectorType (fromIntegral n) (downcast t)
+  downcast (VectorType n t) = LLVM.Vector (fromIntegral n) (downcast t)
 
 instance Downcast (BoundedType t) LLVM.Type where
   downcast (IntegralBoundedType t) = downcast t
@@ -68,19 +71,19 @@
   downcast (FloatingNumType t) = downcast t
 
 instance Downcast (IntegralType a) LLVM.Type where
-  downcast TypeInt     = LLVM.IntegerType $( [| fromIntegral (finiteBitSize (undefined :: Int)) |] )
-  downcast TypeInt8    = LLVM.IntegerType 8
-  downcast TypeInt16   = LLVM.IntegerType 16
-  downcast TypeInt32   = LLVM.IntegerType 32
-  downcast TypeInt64   = LLVM.IntegerType 64
-  downcast TypeWord    = LLVM.IntegerType $( [| fromIntegral (finiteBitSize (undefined :: Word)) |] )
-  downcast TypeWord8   = LLVM.IntegerType 8
-  downcast TypeWord16  = LLVM.IntegerType 16
-  downcast TypeWord32  = LLVM.IntegerType 32
-  downcast TypeWord64  = LLVM.IntegerType 64
+  downcast TypeInt     = LLVM.PrimType (LLVM.Integer (fromIntegral (finiteBitSize (undefined :: Int))))
+  downcast TypeInt8    = LLVM.PrimType (LLVM.Integer 8)
+  downcast TypeInt16   = LLVM.PrimType (LLVM.Integer 16)
+  downcast TypeInt32   = LLVM.PrimType (LLVM.Integer 32)
+  downcast TypeInt64   = LLVM.PrimType (LLVM.Integer 64)
+  downcast TypeWord    = LLVM.PrimType (LLVM.Integer (fromIntegral (finiteBitSize (undefined :: Word))))
+  downcast TypeWord8   = LLVM.PrimType (LLVM.Integer 8)
+  downcast TypeWord16  = LLVM.PrimType (LLVM.Integer 16)
+  downcast TypeWord32  = LLVM.PrimType (LLVM.Integer 32)
+  downcast TypeWord64  = LLVM.PrimType (LLVM.Integer 64)
 
 instance Downcast (FloatingType a) LLVM.Type where
-  downcast TypeHalf    = LLVM.FloatingPointType LLVM.HalfFP
-  downcast TypeFloat   = LLVM.FloatingPointType LLVM.FloatFP
-  downcast TypeDouble  = LLVM.FloatingPointType LLVM.DoubleFP
+  downcast TypeHalf    = LLVM.PrimType (LLVM.FloatType LLVM.Half)
+  downcast TypeFloat   = LLVM.PrimType (LLVM.FloatType LLVM.Float)
+  downcast TypeDouble  = LLVM.PrimType (LLVM.FloatType LLVM.Double)
 
diff --git a/src/LLVM/AST/Type/Flags.hs b/src/LLVM/AST/Type/Flags.hs
deleted file mode 100644
--- a/src/LLVM/AST/Type/Flags.hs
+++ /dev/null
@@ -1,50 +0,0 @@
-{-# LANGUAGE CPP #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-{-# OPTIONS_HADDOCK hide #-}
--- |
--- Module      : LLVM.AST.Type.Flags
--- Copyright   : [2015..2020] The Accelerate Team
--- License     : BSD3
---
--- Maintainer  : Trevor L. McDonell <trevor.mcdonell@gmail.com>
--- Stability   : experimental
--- Portability : non-portable (GHC extensions)
---
-
-module LLVM.AST.Type.Flags (
-
-  NSW(..), NUW(..), FastMathFlags(..)
-
-) where
-
-import Data.Default.Class
-import LLVM.AST.Instruction                               ( FastMathFlags(..) )
-
-
--- If the 'NoSignedWrap' or 'NoUnsignedWrap' keywords are present, the result
--- value of an operation is a poison value if signed and/or unsigned overflow,
--- respectively, occurs.
---
-data NSW = NoSignedWrap   | SignedWrap
-data NUW = NoUnsignedWrap | UnsignedWrap
-
-instance Default NSW where
-  def = SignedWrap
-
-instance Default NUW where
-  def = UnsignedWrap
-
-instance Default FastMathFlags where
-#if MIN_VERSION_llvm_hs_pure(6,0,0)
-  def = FastMathFlags
-          { allowReassoc    = True
-          , noNaNs          = True
-          , noInfs          = True
-          , noSignedZeros   = True
-          , allowReciprocal = True
-          , allowContract   = True
-          , approxFunc      = True
-          }
-#else
-  def = UnsafeAlgebra -- allow everything
-#endif
diff --git a/src/LLVM/AST/Type/Function.hs b/src/LLVM/AST/Type/Function.hs
--- a/src/LLVM/AST/Type/Function.hs
+++ b/src/LLVM/AST/Type/Function.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE GADTs                 #-}
 {-# LANGUAGE KindSignatures        #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
@@ -22,9 +23,10 @@
 import LLVM.AST.Type.Operand
 import LLVM.AST.Type.Representation
 
-import qualified LLVM.AST.Attribute                                 as LLVM
-import qualified LLVM.AST.Global                                    as LLVM
-import qualified LLVM.AST.Instruction                               as LLVM
+-- import qualified LLVM.AST.Attribute                                 as LLVM
+-- import qualified LLVM.AST.Global                                    as LLVM
+-- import qualified LLVM.AST.Instruction                               as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LLVM
 
 
 -- | Attributes for the function call instruction
@@ -37,6 +39,7 @@
   | AlwaysInline
   | NoDuplicate
   | Convergent
+  | InaccessibleMemOnly
 
 -- | Tail call kind for function call instruction
 --
@@ -64,31 +67,28 @@
   Body :: Type r -> Maybe TailCall -> kind                -> Function kind '[]         r
   Lam  :: PrimType a -> Operand a -> Function kind args t -> Function kind (a ': args) t
 
-data HList (l :: [*]) where
-  HNil  ::                 HList '[]
-  HCons :: e -> HList l -> HList (e ': l)
 
-
-instance Downcast FunctionAttribute LLVM.FunctionAttribute where
-  downcast NoReturn     = LLVM.NoReturn
-  downcast NoUnwind     = LLVM.NoUnwind
-  downcast ReadOnly     = LLVM.ReadOnly
-  downcast ReadNone     = LLVM.ReadNone
-  downcast AlwaysInline = LLVM.AlwaysInline
-  downcast NoDuplicate  = LLVM.NoDuplicate
-  downcast Convergent   = LLVM.Convergent
+instance Downcast FunctionAttribute LLVM.FunAttr where
+  downcast NoReturn            = LLVM.Noreturn
+  downcast NoUnwind            = LLVM.Nounwind
+  downcast ReadOnly            = LLVM.Readonly
+  downcast ReadNone            = LLVM.Readnone
+  downcast AlwaysInline        = LLVM.Alwaysinline
+  downcast NoDuplicate         = LLVM.Noduplicate
+  downcast Convergent          = LLVM.Convergent
+  downcast InaccessibleMemOnly = LLVM.InaccessibleMemOnly
 
-instance Downcast (Parameter a) LLVM.Parameter where
-  downcast (Parameter t n) = LLVM.Parameter (downcast t) (downcast n) attrs
-    where
-      attrs | PtrPrimType{} <- t = [LLVM.NoAlias, LLVM.NoCapture] -- XXX: alignment
-            | otherwise          = []
+instance Downcast (Parameter a) (LLVM.Typed LLVM.Ident) where
+  -- TODO attributes! llvm-pretty doesn't seem to support them, but we put
+  -- [NoAlias, NoCapture] on pointer types.
+  -- TODO: Should check if these parameters are necessary (by benchmarking the old backend with llvm-hs), and if so, should send a PR to llvm-pretty
+  downcast (Parameter t n) = LLVM.Typed (downcast t) (nameToPrettyI n)
 
-instance Downcast TailCall LLVM.TailCallKind where
-  downcast Tail     = LLVM.Tail
-  downcast NoTail   = LLVM.NoTail
-  downcast MustTail = LLVM.MustTail
+instance Downcast TailCall Bool where
+  downcast Tail     = True
+  downcast NoTail   = False
+  downcast MustTail = error "TODO MustTail"
 
-instance Downcast GroupID LLVM.GroupID where
-  downcast (GroupID n) = LLVM.GroupID n
+-- instance Downcast GroupID LLVM.GroupID where
+--   downcast (GroupID n) = LLVM.GroupID n
 
diff --git a/src/LLVM/AST/Type/GetElementPtr.hs b/src/LLVM/AST/Type/GetElementPtr.hs
new file mode 100644
--- /dev/null
+++ b/src/LLVM/AST/Type/GetElementPtr.hs
@@ -0,0 +1,58 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE PatternSynonyms #-}
+{-# LANGUAGE QuantifiedConstraints #-}
+{-# OPTIONS_HADDOCK hide #-}
+module LLVM.AST.Type.GetElementPtr where
+
+import LLVM.AST.Type.Downcast
+import LLVM.AST.Type.Representation
+
+
+-- | A @getelementptr@ instruction. The @op@ parameter is the type of operands
+-- (in practice, 'LLVM.AST.Type.Operand.Operand' or
+-- 'LLVM.AST.Type.Constant.Constant'). The @ptra@ and @ptrb@ type indices are
+-- the input and output /pointer/ type.
+--
+-- The type of indices is unconstrained, which is more flexible than reality,
+-- and the kind of operands must be uniform, which is /less/ flexible than
+-- reality. Reality is quite cumbersome:
+-- * When indexing into a structure (currently unsupported by this data type),
+--   the index type must be @i32@ and its value must be constant.
+-- * When indexing into an array, the index type may be any /integral/ type,
+--   signed or unsigned, which are then treated as signed integers.
+--
+-- <https://llvm.org/docs/LangRef.html#getelementptr-instruction>
+data GetElementPtr op ptra ptrb where
+  GEP :: Type a
+      -> op (Ptr a)
+      -> op i             -- ^ the offset to the initial pointer (counted in pointees, not in bytes)
+      -> GEPIndex op a b  -- ^ field/index selection path
+      -> GetElementPtr op (Ptr a) (Ptr b)
+
+-- | A convenience pattern synonym for the common case of a full path of length 1.
+pattern GEP1 :: ScalarType a -> op (Ptr a) -> op i -> GetElementPtr op (Ptr a) (Ptr a)
+pattern GEP1 ty ptr ix <- GEP (PrimType (ScalarPrimType ty)) ptr ix (GEPEmpty (ScalarPrimType _))
+  where GEP1 ty ptr ix = GEP (PrimType (ScalarPrimType ty)) ptr ix (GEPEmpty (ScalarPrimType ty))
+
+-- | An index sequence that goes from a 'Ptr a' to a 'Ptr b'. Note that this
+-- data type is indexed with the base types of the pointers, not the pointer
+-- types themselves.
+data GEPIndex op a b where
+  GEPEmpty :: PrimType b -> GEPIndex op b b
+  GEPArray :: op i       -> GEPIndex op a b -> GEPIndex op (LLArray a) b
+  -- TODO: structure indexing
+
+instance (forall i. Downcast (op i) v) => Downcast (GEPIndex op a b) [v] where
+  downcast (GEPEmpty _) = []
+  downcast (GEPArray i l) = downcast i : downcast l
+
+gepIndexOutType :: GEPIndex op a b -> PrimType b
+gepIndexOutType (GEPEmpty t) = t
+gepIndexOutType (GEPArray _ l) = gepIndexOutType l
+
+instance TypeOf op => TypeOf (GetElementPtr op ptra) where
+  typeOf (GEP _ p _ path) =
+    case typeOf p of
+      PrimType (PtrPrimType _ addr) -> PrimType (PtrPrimType (gepIndexOutType path) addr)
+      _ -> error "Pointer type is not a pointer type"
diff --git a/src/LLVM/AST/Type/Global.hs b/src/LLVM/AST/Type/Global.hs
--- a/src/LLVM/AST/Type/Global.hs
+++ b/src/LLVM/AST/Type/Global.hs
@@ -1,7 +1,6 @@
 {-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE GADTs                 #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE ParallelListComp      #-}
 {-# LANGUAGE TypeSynonymInstances  #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {-# OPTIONS_HADDOCK hide #-}
@@ -22,26 +21,28 @@
 import LLVM.AST.Type.Function
 import LLVM.AST.Type.Name
 
-import qualified LLVM.AST.Global                                    as LLVM
-import qualified LLVM.AST.Name                                      as LLVM
-import qualified LLVM.AST.Type                                      as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LLVM
 
 
--- | A global function definition.
+-- | An external global function definition.
 --
 type GlobalFunction args t = Function Label args t
 
-instance Downcast (GlobalFunction args t) LLVM.Global where
-  downcast f = LLVM.functionDefaults { LLVM.name       = nm
-                                     , LLVM.returnType = res
-                                     , LLVM.parameters = (params, False)
-                                     }
+instance Downcast (GlobalFunction args t) LLVM.Declare where
+  downcast f = LLVM.Declare
+    { LLVM.decLinkage = Just LLVM.External
+    , LLVM.decVisibility = Nothing
+    , LLVM.decRetType = res
+    , LLVM.decName = nm
+    , LLVM.decArgs = args
+    , LLVM.decVarArgs = False
+    , LLVM.decAttrs = []
+    , LLVM.decComdat = Nothing }
     where
-      trav :: GlobalFunction args t -> ([LLVM.Type], LLVM.Type, LLVM.Name)
-      trav (Body t _ n) = ([], downcast t, downcast n)
+      trav :: GlobalFunction args t -> ([LLVM.Type], LLVM.Type, LLVM.Symbol)
+      trav (Body t _ n) = ([], downcast t, labelToPrettyS n)
       trav (Lam a _ l)  = let (as, r, n) = trav l
                           in  (downcast a : as, r, n)
       --
       (args, res, nm)  = trav f
-      params           = [ LLVM.Parameter t (LLVM.UnName i) [] | t <- args | i <- [0..] ]
 
diff --git a/src/LLVM/AST/Type/InlineAssembly.hs b/src/LLVM/AST/Type/InlineAssembly.hs
--- a/src/LLVM/AST/Type/InlineAssembly.hs
+++ b/src/LLVM/AST/Type/InlineAssembly.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE DeriveDataTypeable    #-}
 {-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE GADTs                 #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
@@ -15,19 +16,29 @@
 module LLVM.AST.Type.InlineAssembly (
 
   module LLVM.AST.Type.InlineAssembly,
-  LLVM.Dialect(..),
+  Dialect(..),
 
 ) where
 
-import LLVM.AST.Type.Downcast
+-- import LLVM.AST.Type.Downcast
 
-import qualified LLVM.AST.Type                                      as LLVM
-import qualified LLVM.AST.InlineAssembly                            as LLVM
+-- import qualified LLVM.AST.Type                                      as LLVM
+-- import qualified LLVM.AST.InlineAssembly                            as LLVM
 
 import Data.ByteString
 import Data.ByteString.Short
+import Data.Data
 
 
+-- | the dialect of assembly used in an inline assembly string
+-- <http://en.wikipedia.org/wiki/X86_assembly_language#Syntax>
+--
+-- Copied from llvm-hs-pure.
+data Dialect
+  = ATTDialect
+  | IntelDialect
+  deriving (Eq, Read, Show, Typeable, Data)
+
 -- | The 'call' instruction might be a label or inline assembly
 --
 data InlineAssembly where
@@ -35,10 +46,10 @@
                  -> ShortByteString       -- constraints
                  -> Bool                  -- has side effects?
                  -> Bool                  -- align stack?
-                 -> LLVM.Dialect
+                 -> Dialect
                  -> InlineAssembly
 
-instance Downcast (LLVM.Type, InlineAssembly) LLVM.InlineAssembly where
-  downcast (t, InlineAssembly asm cst s a d) =
-    LLVM.InlineAssembly t asm cst s a d
+-- instance Downcast (LLVM.Type, InlineAssembly) LLVM.InlineAssembly where
+--   downcast (t, InlineAssembly asm cst s a d) =
+--     LLVM.InlineAssembly t asm cst s a d
 
diff --git a/src/LLVM/AST/Type/Instruction.hs b/src/LLVM/AST/Type/Instruction.hs
--- a/src/LLVM/AST/Type/Instruction.hs
+++ b/src/LLVM/AST/Type/Instruction.hs
@@ -1,13 +1,11 @@
-{-# LANGUAGE CPP                   #-}
 {-# LANGUAGE DataKinds             #-}
 {-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE GADTs                 #-}
 {-# LANGUAGE LambdaCase            #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings     #-}
 {-# LANGUAGE RankNTypes            #-}
-{-# LANGUAGE TemplateHaskell       #-}
-{-# LANGUAGE TypeApplications      #-}
-{-# LANGUAGE TypeOperators         #-}
 {-# LANGUAGE ViewPatterns          #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
@@ -23,9 +21,10 @@
 module LLVM.AST.Type.Instruction
   where
 
-import LLVM.AST.Type.Constant
+import LLVM.AST.Type.Constant                             ( Constant(ScalarConstant) )
 import LLVM.AST.Type.Downcast
 import LLVM.AST.Type.Function
+import LLVM.AST.Type.GetElementPtr
 import LLVM.AST.Type.InlineAssembly
 import LLVM.AST.Type.Name
 import LLVM.AST.Type.Operand
@@ -34,25 +33,21 @@
 import LLVM.AST.Type.Instruction.Atomic                   ( Atomicity, MemoryOrdering )
 import LLVM.AST.Type.Instruction.Compare                  ( Ordering(..) )
 import LLVM.AST.Type.Instruction.RMW                      ( RMWOperation )
-import LLVM.AST.Type.Instruction.Volatile                 ( Volatility )
+import LLVM.AST.Type.Instruction.Volatile                 ( Volatility(..) )
 
-import qualified LLVM.AST.Constant                        as LLVM ( Constant(GlobalReference, Int) )
-import qualified LLVM.AST.AddrSpace                       as LLVM
-import qualified LLVM.AST.CallingConvention               as LLVM
-import qualified LLVM.AST.FloatingPointPredicate          as FP
-import qualified LLVM.AST.Instruction                     as LLVM
-import qualified LLVM.AST.IntegerPredicate                as IP
-import qualified LLVM.AST.Operand                         as LLVM ( Operand(..), CallableOperand )
-import qualified LLVM.AST.ParameterAttribute              as LLVM ( ParameterAttribute )
-import qualified LLVM.AST.Type                            as LLVM ( Type(..) )
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty as LP
 
 import Data.Array.Accelerate.AST                          ( PrimBool )
 import Data.Array.Accelerate.AST.Idx
+import qualified Data.Array.Accelerate.Debug.Internal     as Debug
 import Data.Array.Accelerate.Error
 import Data.Array.Accelerate.Representation.Type
 import Data.Primitive.Vec
 
 import Prelude                                            hiding ( Ordering(..), quot, rem, div, isNaN, tail )
+import Data.Bifunctor                                     ( bimap )
+import Data.Maybe                                         ( fromMaybe )
+import System.IO.Unsafe                                   ( unsafePerformIO )
 
 
 -- | Non-terminating instructions
@@ -233,9 +228,8 @@
 
   -- <http://llvm.org/docs/LangRef.html#getelementptr-instruction>
   --
-  GetElementPtr   :: Operand (Ptr a)
-                  -> [Operand i]
-                  -> Instruction (Ptr a)
+  GetElementPtr   :: GetElementPtr Operand (Ptr a) (Ptr b)
+                  -> Instruction (Ptr b)
 
   -- <http://llvm.org/docs/LangRef.html#i-fence>
   --
@@ -361,7 +355,6 @@
   -- <http://llvm.org/docs/LangRef.html#call-instruction>
   --
   Call            :: Function (Either InlineAssembly Label) args t
-                  -> [Either GroupID FunctionAttribute]
                   -> Instruction t
 
   -- <http://llvm.org/docs/LangRef.html#select-instruction>
@@ -385,50 +378,52 @@
   Do   :: ins ()          -> Named ins ()
 
 
--- | Convert to llvm-hs
+-- | Convert to llvm-pretty
 --
-instance Downcast (Instruction a) LLVM.Instruction where
+instance Downcast (Instruction a) LP.Instr where
   downcast = \case
     Add t x y             -> add t (downcast x) (downcast y)
     Sub t x y             -> sub t (downcast x) (downcast y)
     Mul t x y             -> mul t (downcast x) (downcast y)
     Quot t x y            -> quot t (downcast x) (downcast y)
     Rem t x y             -> rem t (downcast x) (downcast y)
-    Div _ x y             -> LLVM.FDiv fmf (downcast x) (downcast y) md
-    ShiftL _ x i          -> LLVM.Shl nsw nuw (downcast x) (downcast i) md
-    ShiftRL _ x i         -> LLVM.LShr exact (downcast x) (downcast i) md
-    ShiftRA _ x i         -> LLVM.AShr exact (downcast x) (downcast i) md
-    BAnd _ x y            -> LLVM.And (downcast x) (downcast y) md
-    LAnd x y              -> LLVM.And (downcast x) (downcast y) md
-    BOr _ x y             -> LLVM.Or (downcast x) (downcast y) md
-    LOr x y               -> LLVM.Or (downcast x) (downcast y) md
-    BXor _ x y            -> LLVM.Xor (downcast x) (downcast y) md
-    LNot x                -> LLVM.Xor (downcast x) (LLVM.ConstantOperand (LLVM.Int 1 1)) md
-    InsertElement i v x   -> LLVM.InsertElement (downcast v) (downcast x) (constant i) md
-    ExtractElement i v    -> LLVM.ExtractElement (downcast v) (constant i) md
+    Div _ x y             -> LP.Arith (LP.FDiv fmf) (downcast x) (LP.typedValue (downcast y))
+    ShiftL _ x i          -> LP.Bit (LP.Shl nsw nuw) (downcast x) (LP.typedValue (downcast i))
+    ShiftRL _ x i         -> LP.Bit (LP.Lshr exact) (downcast x) (LP.typedValue (downcast i))
+    ShiftRA _ x i         -> LP.Bit (LP.Ashr exact) (downcast x) (LP.typedValue (downcast i))
+    BAnd _ x y            -> LP.Bit LP.And (downcast x) (LP.typedValue (downcast y))
+    LAnd x y              -> LP.Bit LP.And (downcast x) (LP.typedValue (downcast y))
+    BOr _ x y             -> LP.Bit LP.Or (downcast x) (LP.typedValue (downcast y))
+    LOr x y               -> LP.Bit LP.Or (downcast x) (LP.typedValue (downcast y))
+    BXor _ x y            -> LP.Bit LP.Xor (downcast x) (LP.typedValue (downcast y))
+    LNot x                -> LP.Bit LP.Xor (downcast x) (LP.ValInteger 1)
+    InsertElement i v x   -> LP.InsertElt (downcast v) (downcast x) (constant i)
+    ExtractElement i v    -> LP.ExtractElt (downcast v) (constant i)
     ExtractValue _ i s    -> extractStruct i (downcast s)
-    Load _ v p            -> LLVM.Load (downcast v) (downcast p) atomicity alignment md
-    Store v p x           -> LLVM.Store (downcast v) (downcast p) (downcast x) atomicity alignment md
-    GetElementPtr n i     -> LLVM.GetElementPtr inbounds (downcast n) (downcast i) md
-    Fence a               -> LLVM.Fence (downcast a) md
-    CmpXchg _ v p x y a m -> LLVM.CmpXchg (downcast v) (downcast p) (downcast x) (downcast y) (downcast a) (downcast m) md
-    AtomicRMW t v f p x a -> LLVM.AtomicRMW (downcast v) (downcast (t,f)) (downcast p) (downcast x) (downcast a) md
-    Trunc _ t x           -> LLVM.Trunc (downcast x) (downcast t) md
-    IntToBool _ x         -> LLVM.Trunc (downcast x) (LLVM.IntegerType 1) md
-    FTrunc _ t x          -> LLVM.FPTrunc (downcast x) (downcast t) md
+    Store vol p x         -> LP.Store (downcast vol) (downcast x) (downcast p) atomicity alignment
+    Load t vol p          -> LP.Load (downcast vol) (downcast t) (downcast p) atomicity alignment
+    GetElementPtr (GEP t n i1 path) ->
+      LP.GEP inbounds (downcast t) (downcast n) (downcast i1 : downcast path)
+    Fence a               -> LP.Fence (downcast (fst a)) (downcast (snd a))
+    -- TODO: this is now a STRONG cmpxchg. Is that what was intended? I think llvm-hs defaulted to strong, but the LLVM source is very obtuse about this.
+    CmpXchg _ v p x y a m -> LP.CmpXchg False (downcast v) (downcast p) (downcast x) (downcast y) (downcast (fst a)) (downcast (snd a)) (downcast m)
+    AtomicRMW t v f p x a -> LP.AtomicRW (downcast v) (downcast (t,f)) (downcast p) (downcast x) (downcast (fst a)) (downcast (snd a))
+    Trunc _ t x           -> LP.Conv (LP.Trunc False False) (downcast x) (downcast t)
+    IntToBool _ x         -> LP.Conv (LP.Trunc False False) (downcast x) (LP.PrimType (LP.Integer 1))
+    FTrunc _ t x          -> LP.Conv LP.FpTrunc (downcast x) (downcast t)
     Ext a b x             -> ext a b (downcast x)
-    BoolToInt a x         -> LLVM.ZExt (downcast x) (downcast a) md
-    BoolToFP x a          -> LLVM.UIToFP (downcast a) (downcast x) md
-    FExt _ t x            -> LLVM.FPExt (downcast x) (downcast t) md
+    BoolToInt a x         -> LP.Conv (LP.ZExt False) (downcast x) (downcast a)
+    BoolToFP x a          -> LP.Conv (LP.UiToFp False) (downcast a) (downcast x)
+    FExt _ t x            -> LP.Conv LP.FpExt (downcast x) (downcast t)
     FPToInt _ b x         -> float2int b (downcast x)
     IntToFP a b x         -> int2float a b (downcast x)
-    BitCast t x           -> LLVM.BitCast (downcast x) (downcast t) md
-    PtrCast t x           -> LLVM.BitCast (downcast x) (downcast t) md
-    Phi t e               -> LLVM.Phi (downcast t) (downcast e) md
-    Select _ p x y        -> LLVM.Select (downcast p) (downcast x) (downcast y) md
+    BitCast t x           -> LP.Conv LP.BitCast (downcast x) (downcast t)
+    PtrCast t x           -> LP.Conv LP.BitCast (downcast x) (downcast t)
+    Phi t e               -> LP.Phi (fmfFor t) (downcast t) (map (bimap (LP.typedValue . downcast) (LP.Named . labelToPrettyI)) e)
+    Select t p x y        -> LP.Select (fmfFor t) (downcast p) (downcast x) (LP.typedValue (downcast y))
     IsNaN _ x             -> isNaN (downcast x)
     Cmp t p x y           -> cmp t p (downcast x) (downcast y)
-    Call f a              -> call f a
+    Call f                -> call f
 
     where
       nsw :: Bool       -- no signed wrap
@@ -440,140 +435,141 @@
       exact :: Bool     -- does not lose any information
       exact = False
 
-      inbounds :: Bool
-      inbounds = True
+      fmf :: [LP.FMF]
+      fmf = fastmathFlags
 
-      atomicity :: Maybe LLVM.Atomicity
-      atomicity = Nothing
+      inbounds :: [LP.GEPAttr]
+      inbounds = [LP.GEP_Inbounds]
 
-      alignment :: Word32
-      alignment = 0
+      atomicity :: Maybe LP.AtomicOrdering
+      atomicity = Nothing
 
-      fmf :: LLVM.FastMathFlags
-#if MIN_VERSION_llvm_hs_pure(6,0,0)
-      fmf = LLVM.FastMathFlags
-              { LLVM.allowReassoc    = True
-              , LLVM.noNaNs          = True
-              , LLVM.noInfs          = True
-              , LLVM.noSignedZeros   = True
-              , LLVM.allowReciprocal = True
-              , LLVM.allowContract   = True
-              , LLVM.approxFunc      = True
-              }
-#else
-      fmf = LLVM.UnsafeAlgebra -- allow everything
-#endif
+      alignment :: Maybe LP.Align
+      alignment = Nothing  -- was: 0
 
-      md :: LLVM.InstructionMetadata
-      md = []
+      -- fmf :: LLVM.FastMathFlags
+      -- fmf = LLVM.FastMathFlags
+      --         { LLVM.allowReassoc    = True
+      --         , LLVM.noNaNs          = True
+      --         , LLVM.noInfs          = True
+      --         , LLVM.noSignedZeros   = True
+      --         , LLVM.allowReciprocal = True
+      --         , LLVM.allowContract   = True
+      --         , LLVM.approxFunc      = True
+      --         }
 
-      constant :: IsScalar a => a -> LLVM.Operand
-      constant x = downcast (ConstantOperand (ScalarConstant scalarType x))
+      constant :: IsScalar a => a -> LP.Value
+      constant x = LP.typedValue $ downcast (ConstantOperand (ScalarConstant scalarType x))
 
-      add :: NumType a -> LLVM.Operand -> LLVM.Operand -> LLVM.Instruction
-      add IntegralNumType{} x y = LLVM.Add nsw nuw x y md
-      add FloatingNumType{} x y = LLVM.FAdd fmf    x y md
+      add :: NumType a -> LP.Typed LP.Value -> LP.Typed LP.Value -> LP.Instr
+      add IntegralNumType{} x (LP.Typed _ y) = LP.Arith (LP.Add nsw nuw) x y
+      add FloatingNumType{} x (LP.Typed _ y) = LP.Arith (LP.FAdd fmf)    x y
 
-      sub :: NumType a -> LLVM.Operand -> LLVM.Operand -> LLVM.Instruction
-      sub IntegralNumType{} x y = LLVM.Sub nsw nuw x y md
-      sub FloatingNumType{} x y = LLVM.FSub fmf    x y md
+      sub :: NumType a -> LP.Typed LP.Value -> LP.Typed LP.Value -> LP.Instr
+      sub IntegralNumType{} x (LP.Typed _ y) = LP.Arith (LP.Sub nsw nuw) x y
+      sub FloatingNumType{} x (LP.Typed _ y) = LP.Arith (LP.FSub fmf)    x y
 
-      mul :: NumType a -> LLVM.Operand -> LLVM.Operand -> LLVM.Instruction
-      mul IntegralNumType{} x y = LLVM.Mul nsw nuw x y md
-      mul FloatingNumType{} x y = LLVM.FMul fmf    x y md
+      mul :: NumType a -> LP.Typed LP.Value -> LP.Typed LP.Value -> LP.Instr
+      mul IntegralNumType{} x (LP.Typed _ y) = LP.Arith (LP.Mul nsw nuw) x y
+      mul FloatingNumType{} x (LP.Typed _ y) = LP.Arith (LP.FMul fmf)    x y
 
-      quot :: IntegralType a -> LLVM.Operand -> LLVM.Operand -> LLVM.Instruction
-      quot t x y
-        | signed t  = LLVM.SDiv exact x y md
-        | otherwise = LLVM.UDiv exact x y md
+      quot :: IntegralType a -> LP.Typed LP.Value -> LP.Typed LP.Value -> LP.Instr
+      quot t x (LP.Typed _ y)
+        | signed t  = LP.Arith (LP.SDiv exact) x y
+        | otherwise = LP.Arith (LP.UDiv exact) x y
 
-      rem :: IntegralType a -> LLVM.Operand -> LLVM.Operand -> LLVM.Instruction
-      rem t x y
-        | signed t  = LLVM.SRem x y md
-        | otherwise = LLVM.URem x y md
+      rem :: IntegralType a -> LP.Typed LP.Value -> LP.Typed LP.Value -> LP.Instr
+      rem t x (LP.Typed _ y)
+        | signed t  = LP.Arith LP.SRem x y
+        | otherwise = LP.Arith LP.URem x y
 
-      extractStruct :: PairIdx s t -> LLVM.Operand -> LLVM.Instruction
-      extractStruct i s = LLVM.ExtractValue s ix md
+      extractStruct :: PairIdx s t -> LP.Typed LP.Value -> LP.Instr
+      extractStruct i s = LP.ExtractValue s ix
         where
           ix = case i of
             PairIdxLeft  -> [0]
             PairIdxRight -> [1]
 
-      ext :: BoundedType a -> BoundedType b -> LLVM.Operand -> LLVM.Instruction
+      ext :: BoundedType a -> BoundedType b -> LP.Typed LP.Value -> LP.Instr
       ext a (downcast -> b) x
-        | signed a  = LLVM.SExt x b md
-        | otherwise = LLVM.ZExt x b md
+        | signed a  = LP.Conv LP.SExt x b
+        | otherwise = LP.Conv (LP.ZExt False) x b
 
-      float2int :: IntegralType b -> LLVM.Operand -> LLVM.Instruction
+      float2int :: IntegralType b -> LP.Typed LP.Value -> LP.Instr
       float2int t@(downcast -> t') x
-        | signed t  = LLVM.FPToSI x t' md
-        | otherwise = LLVM.FPToUI x t' md
+        | signed t  = LP.Conv LP.FpToSi x t'
+        | otherwise = LP.Conv LP.FpToUi x t'
 
-      int2float :: IntegralType a -> FloatingType b -> LLVM.Operand -> LLVM.Instruction
+      int2float :: IntegralType a -> FloatingType b -> LP.Typed LP.Value -> LP.Instr
       int2float a (downcast -> b) x
-        | signed a  = LLVM.SIToFP x b md
-        | otherwise = LLVM.UIToFP x b md
+        | signed a  = LP.Conv LP.SiToFp x b
+        | otherwise = LP.Conv (LP.UiToFp False) x b
 
-      isNaN :: LLVM.Operand -> LLVM.Instruction
-      isNaN x = LLVM.FCmp FP.UNO x x md
+      isNaN :: LP.Typed LP.Value -> LP.Instr
+      isNaN x = LP.FCmp fmf LP.Funo x (LP.typedValue x)
 
-      cmp :: SingleType a -> Ordering -> LLVM.Operand -> LLVM.Operand -> LLVM.Instruction
-      cmp t p x y =
+      cmp :: SingleType a -> Ordering -> LP.Typed LP.Value -> LP.Typed LP.Value -> LP.Instr
+      cmp t p x (LP.Typed _ y) =
         case t of
-          NumSingleType FloatingNumType{} -> LLVM.FCmp (fp p) x y md
-          _ | signed t                    -> LLVM.ICmp (si p) x y md
-            | otherwise                   -> LLVM.ICmp (ui p) x y md
+          NumSingleType FloatingNumType{} -> LP.FCmp fastmathFlags (fp p) x y
+          _ | signed t                    -> LP.ICmp False (si p) x y
+            | otherwise                   -> LP.ICmp False (ui p) x y
         where
-          fp :: Ordering -> FP.FloatingPointPredicate
-          fp EQ = FP.OEQ
-          fp NE = FP.ONE
-          fp LT = FP.OLT
-          fp LE = FP.OLE
-          fp GT = FP.OGT
-          fp GE = FP.OGE
+          fp :: Ordering -> LP.FCmpOp
+          fp EQ = LP.Foeq
+          fp NE = LP.Fone
+          fp LT = LP.Folt
+          fp LE = LP.Fole
+          fp GT = LP.Fogt
+          fp GE = LP.Foge
 
-          si :: Ordering -> IP.IntegerPredicate
-          si EQ = IP.EQ
-          si NE = IP.NE
-          si LT = IP.SLT
-          si LE = IP.SLE
-          si GT = IP.SGT
-          si GE = IP.SGE
+          si :: Ordering -> LP.ICmpOp
+          si EQ = LP.Ieq
+          si NE = LP.Ine
+          si LT = LP.Islt
+          si LE = LP.Isle
+          si GT = LP.Isgt
+          si GE = LP.Isge
 
-          ui :: Ordering -> IP.IntegerPredicate
-          ui EQ = IP.EQ
-          ui NE = IP.NE
-          ui LT = IP.ULT
-          ui LE = IP.ULE
-          ui GT = IP.UGT
-          ui GE = IP.UGE
+          ui :: Ordering -> LP.ICmpOp
+          ui EQ = LP.Ieq
+          ui NE = LP.Ine
+          ui LT = LP.Iult
+          ui LE = LP.Iule
+          ui GT = LP.Iugt
+          ui GE = LP.Iuge
 
-      call :: Function (Either InlineAssembly Label) args t -> [Either GroupID FunctionAttribute] -> LLVM.Instruction
-      call f as = LLVM.Call tail LLVM.C [] target argv (downcast as) md
+      call :: Function (Either InlineAssembly Label) args t -> LP.Instr
+      call f = LP.Call tail fmFlags fun_ty target argv
         where
           trav :: Function (Either InlineAssembly Label) args t
-               -> ( [LLVM.Type]                                 -- argument types
-                  , [(LLVM.Operand, [LLVM.ParameterAttribute])] -- argument operands
-                  , Maybe LLVM.TailCallKind                     -- calling kind
-                  , LLVM.Type                                   -- return type
-                  , LLVM.CallableOperand                        -- function name or inline assembly
+               -> ( [LP.Type]           -- argument types
+                  , [LP.Typed LP.Value] -- argument operands
+                  , Bool                -- tail call?
+                  , LP.Type             -- return type
+                  , [LP.FMF]            -- fast-math flags for this return type
+                  , LP.Value            -- target: function name or inline assembly
                   )
           trav (Body u k o) =
             case o of
-              Left asm -> ([], [], downcast k, downcast u, Left  (downcast (LLVM.FunctionType ret argt False, asm)))
-              Right n  -> ([], [], downcast k, downcast u, Right (LLVM.ConstantOperand (LLVM.GlobalReference ptr_fun_ty (downcast n))))
+              Left _asm ->
+                internalError
+                  "Inline assembly should not be used as llvm-pretty does not \
+                  \support it. For a workaround, see the solution for nanosleep \
+                  \in Data.Array.Accelerate.LLVM.PTX.Compile."
+              -- Left asm -> ([], [], downcast k, downcast u, Left  (downcast (LLVM.FunctionType ret argt False, asm)))
+              Right n  -> ([], [], fromMaybe False (downcast k), downcast u, fmfFor u, LP.ValSymbol (labelToPrettyS n))
           trav (Lam t x l)  =
-            let (ts, xs, k, r, n)  = trav l
-            in  (downcast t : ts, (downcast x, []) : xs, k, r, n)
+            let (ts, xs, k, r, fm, n) = trav l
+            in  (downcast t : ts, downcast x : xs, k, r, fm, n)
 
-          (argt, argv, tail, ret, target) = trav f
-          fun_ty                          = LLVM.FunctionType ret argt False
-          ptr_fun_ty                      = LLVM.PointerType fun_ty (LLVM.AddrSpace 0)
+          (argt, argv, tail, ret, fmFlags, target) = trav f
+          fun_ty                                   = LP.FunTy ret argt False
 
 
-instance Downcast (i a) i' => Downcast (Named i a) (LLVM.Named i') where
-  downcast (x := op) = downcast x LLVM.:= downcast op
-  downcast (Do op)   = LLVM.Do (downcast op)
+-- instance Downcast (i a) i' => Downcast (Named i a) (LLVM.Named i') where
+--   downcast (x := op) = downcast x LLVM.:= downcast op
+--   downcast (Do op)   = LLVM.Do (downcast op)
 
 
 instance TypeOf Instruction where
@@ -598,9 +594,9 @@
     ExtractValue t _ _    -> scalar t
     Load t _ _            -> scalar t
     Store{}               -> VoidType
-    GetElementPtr x _     -> typeOf x
+    GetElementPtr gep     -> typeOf gep
     Fence{}               -> VoidType
-    CmpXchg t _ _ _ _ _ _ -> PrimType . StructPrimType $ SingleScalarType (NumSingleType (IntegralNumType t)) `pair` scalarType
+    CmpXchg t _ _ _ _ _ _ -> PrimType . StructPrimType False $ ScalarPrimType (SingleScalarType (NumSingleType (IntegralNumType t))) `pair` primType
     AtomicRMW _ _ _ _ x _ -> typeOf x
     FTrunc _ t _          -> floating t
     FExt _ t _            -> floating t
@@ -617,7 +613,7 @@
     IsNaN{}               -> type'
     Phi t _               -> PrimType t
     Select _ _ x _        -> typeOf x
-    Call f _              -> fun f
+    Call f                -> fun f
     where
       typeOfVec :: HasCallStack => Operand (Vec n a) -> Type a
       typeOfVec x
@@ -642,7 +638,7 @@
       integral :: IntegralType a -> Type a
       integral = single . NumSingleType . IntegralNumType
 
-      pair :: ScalarType a -> ScalarType b -> TypeR (a, b)
+      pair :: PrimType a -> PrimType b -> TupR PrimType (a, b)
       pair a b = TupRsingle a `TupRpair` TupRsingle b
 
       bounded :: BoundedType a -> Type a
@@ -651,4 +647,43 @@
       fun :: Function kind args a -> Type a
       fun (Lam _ _ l)  = fun l
       fun (Body t _ _) = t
+
+
+{-# NOINLINE fmfEnabled #-}
+fmfEnabled :: Bool
+fmfEnabled = unsafePerformIO $ Debug.getFlag Debug.fast_math
+
+fastmathFlags :: [LP.FMF]
+fastmathFlags
+  -- We explicitly exclude 'nnan' and 'ninf' from this list, because apparently
+  -- we care about NaN and Inf values:
+  --   <https://github.com/AccelerateHS/accelerate/issues/407>
+  | fmfEnabled = [LP.Fnsz, LP.Farcp, LP.Fcontract, LP.Fafn, LP.Freassoc]
+  | otherwise  = []
+
+-- | Some LLVM instructions allow fast math flags only if their return type is
+-- a "compatible floating-point type". This class returns fast-math flags given
+-- such a return type.
+class FmfFor s where
+  fmfFor :: s a -> [LP.FMF]
+
+instance FmfFor PrimType where
+  fmfFor BoolPrimType = []
+  fmfFor (ScalarPrimType t) = fmfFor t
+  fmfFor PtrPrimType{} = []
+  fmfFor (ArrayPrimType _ t) = fmfFor t
+  fmfFor StructPrimType{} = []  -- TODO: homogeneous float structs are allowed by LLVM
+  fmfFor NamedPrimType{} = []
+
+instance FmfFor ScalarType where
+  fmfFor (SingleScalarType st) = fmfFor st
+  fmfFor (VectorScalarType (VectorType _ st)) = fmfFor st
+
+instance FmfFor SingleType where
+  fmfFor (NumSingleType (IntegralNumType _)) = []
+  fmfFor (NumSingleType (FloatingNumType _)) = fastmathFlags
+
+instance FmfFor Type where
+  fmfFor VoidType = []
+  fmfFor (PrimType t) = fmfFor t
 
diff --git a/src/LLVM/AST/Type/Instruction/Atomic.hs b/src/LLVM/AST/Type/Instruction/Atomic.hs
--- a/src/LLVM/AST/Type/Instruction/Atomic.hs
+++ b/src/LLVM/AST/Type/Instruction/Atomic.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE CPP                   #-}
+{-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
@@ -15,7 +15,7 @@
   where
 
 import LLVM.AST.Type.Downcast
-import qualified LLVM.AST.Instruction                               as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LLVM
 
 
 -- | Atomic instructions take ordering parameters that determine which other
@@ -49,21 +49,18 @@
 type Atomicity = (Synchronisation, MemoryOrdering)
 
 
--- | Convert to llvm-hs
+-- | Convert to llvm-pretty
 --
-instance Downcast MemoryOrdering LLVM.MemoryOrdering where
+instance Downcast MemoryOrdering LLVM.AtomicOrdering where
   downcast Unordered              = LLVM.Unordered
   downcast Monotonic              = LLVM.Monotonic
   downcast Acquire                = LLVM.Acquire
   downcast Release                = LLVM.Release
-  downcast AcquireRelease         = LLVM.AcquireRelease
-  downcast SequentiallyConsistent = LLVM.SequentiallyConsistent
+  downcast AcquireRelease         = LLVM.AcqRel
+  downcast SequentiallyConsistent = LLVM.SeqCst
 
-instance Downcast Synchronisation LLVM.SynchronizationScope where
-  downcast SingleThread = LLVM.SingleThread
-#if MIN_VERSION_llvm_hs_pure(5,0,0)
-  downcast CrossThread  = LLVM.System
-#else
-  downcast CrossThread  = LLVM.CrossThread
-#endif
+-- llvm-pretty's typing is weak here
+instance Downcast Synchronisation (Maybe String) where
+  downcast SingleThread = Just "singlethread"
+  downcast CrossThread  = Nothing  -- "system"
 
diff --git a/src/LLVM/AST/Type/Instruction/RMW.hs b/src/LLVM/AST/Type/Instruction/RMW.hs
--- a/src/LLVM/AST/Type/Instruction/RMW.hs
+++ b/src/LLVM/AST/Type/Instruction/RMW.hs
@@ -1,6 +1,6 @@
-{-# LANGUAGE CPP                   #-}
 {-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings     #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : LLVM.AST.Type.Instruction.RMW
@@ -20,7 +20,7 @@
 import LLVM.AST.Type.Downcast
 import LLVM.AST.Type.Representation
 
-import qualified LLVM.AST.RMWOperation                              as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LLVM
 
 
 -- | Operations for the 'AtomicRMW' instruction.
@@ -39,34 +39,32 @@
     | Max
 
 
--- | Convert to llvm-hs
+-- | Convert to llvm-pretty
 --
-instance Downcast (NumType a, RMWOperation) LLVM.RMWOperation where
+instance Downcast (NumType a, RMWOperation) LLVM.AtomicRWOp where
   downcast (IntegralNumType t, rmw) = downcast (t,rmw)
   downcast (FloatingNumType t, rmw) = downcast (t,rmw)
 
-instance Downcast (IntegralType a, RMWOperation) LLVM.RMWOperation where
+instance Downcast (IntegralType a, RMWOperation) LLVM.AtomicRWOp where
   downcast (t, rmw) =
     case rmw of
-      Exchange        -> LLVM.Xchg
-      Add             -> LLVM.Add
-      Sub             -> LLVM.Sub
-      And             -> LLVM.And
-      Or              -> LLVM.Or
-      Xor             -> LLVM.Xor
-      Nand            -> LLVM.Nand
-      Min | signed t  -> LLVM.Min
-          | otherwise -> LLVM.UMin
-      Max | signed t  -> LLVM.Max
-          | otherwise -> LLVM.UMax
+      Exchange        -> LLVM.AtomicXchg
+      Add             -> LLVM.AtomicAdd
+      Sub             -> LLVM.AtomicSub
+      And             -> LLVM.AtomicAnd
+      Or              -> LLVM.AtomicOr
+      Xor             -> LLVM.AtomicXor
+      Nand            -> LLVM.AtomicNand
+      Min | signed t  -> LLVM.AtomicMin
+          | otherwise -> LLVM.AtomicUMin
+      Max | signed t  -> LLVM.AtomicMax
+          | otherwise -> LLVM.AtomicUMax
 
-instance Downcast (FloatingType a, RMWOperation) LLVM.RMWOperation where
+instance Downcast (FloatingType a, RMWOperation) LLVM.AtomicRWOp where
   downcast (_, rmw) =
     case rmw of
-      Exchange        -> LLVM.Xchg
-#if MIN_VERSION_llvm_hs_pure(10,0,0)
-      Add             -> LLVM.FAdd
-      Sub             -> LLVM.FSub
-#endif
+      Exchange        -> LLVM.AtomicXchg
+      Add             -> LLVM.AtomicFAdd
+      Sub             -> LLVM.AtomicFSub
       _               -> internalError "unsupported operand type to RMWOperation"
 
diff --git a/src/LLVM/AST/Type/Metadata.hs b/src/LLVM/AST/Type/Metadata.hs
--- a/src/LLVM/AST/Type/Metadata.hs
+++ b/src/LLVM/AST/Type/Metadata.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP                   #-}
 {-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE GADTs                 #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
@@ -18,10 +17,10 @@
 
 import LLVM.AST.Type.Downcast
 
-import qualified LLVM.AST.Constant                        as LLVM
-import qualified LLVM.AST.Operand                         as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty as LLVM
 
 import Data.ByteString.Short                              ( ShortByteString )
+import qualified Data.ByteString.Short.Char8              as SBS8
 
 
 -- | Metadata does not have a type, and is not a value.
@@ -30,31 +29,21 @@
 --
 data MetadataNode
   = MetadataNode ![Maybe Metadata]
-  | MetadataNodeReference {-# UNPACK #-} !LLVM.MetadataNodeID
+  | MetadataNodeReference {-# UNPACK #-} !Int
 
 data Metadata
   = MetadataStringOperand {-# UNPACK #-} !ShortByteString
-  | MetadataConstantOperand !LLVM.Constant
+  | MetadataConstantOperand !(LLVM.Typed LLVM.Value)
   | MetadataNodeOperand !MetadataNode
 
 
 -- | Convert to llvm-hs
 --
-instance Downcast Metadata LLVM.Metadata where
-  downcast (MetadataStringOperand s)   = LLVM.MDString s
-  downcast (MetadataConstantOperand o) = LLVM.MDValue (LLVM.ConstantOperand o)
-  downcast (MetadataNodeOperand n)     = LLVM.MDNode (downcast n)
-
-#if MIN_VERSION_llvm_hs_pure(6,1,0)
-instance Downcast MetadataNode (LLVM.MDRef LLVM.MDNode) where
-  downcast (MetadataNode n)            = LLVM.MDInline (downcast n)
-  downcast (MetadataNodeReference r)   = LLVM.MDRef r
-
-instance Downcast [Maybe Metadata] LLVM.MDNode where
-  downcast = LLVM.MDTuple . map downcast
-#else
-instance Downcast MetadataNode LLVM.MetadataNode where
-  downcast (MetadataNode n)            = LLVM.MetadataNode (downcast n)
-  downcast (MetadataNodeReference r)   = LLVM.MetadataNodeReference r
-#endif
+instance Downcast Metadata LLVM.ValMd where
+  downcast (MetadataStringOperand s)   = LLVM.ValMdString (SBS8.unpack s)
+  downcast (MetadataConstantOperand o) = LLVM.ValMdValue o
+  downcast (MetadataNodeOperand n)     = downcast n
 
+instance Downcast MetadataNode LLVM.ValMd where
+  downcast (MetadataNode n)            = LLVM.ValMdNode (downcast n)
+  downcast (MetadataNodeReference r)   = LLVM.ValMdRef r
diff --git a/src/LLVM/AST/Type/Name.hs b/src/LLVM/AST/Type/Name.hs
--- a/src/LLVM/AST/Type/Name.hs
+++ b/src/LLVM/AST/Type/Name.hs
@@ -1,6 +1,8 @@
 {-# LANGUAGE DeriveDataTypeable    #-}
+{-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE RoleAnnotations       #-}
+{-# LANGUAGE TypeSynonymInstances  #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : LLVM.AST.Type.Name
@@ -16,15 +18,16 @@
   where
 
 import Data.ByteString.Short                                        ( ShortByteString )
+import qualified Data.ByteString.Short                              as SBS
+import qualified Data.ByteString.Short.Char8                        as SBS8
 import Data.Data
 import Data.Semigroup
+import Data.Hashable
 import Data.String
 import Data.Word
 import Prelude
 
-import LLVM.AST.Type.Downcast
-
-import qualified LLVM.AST.Name                                      as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LLVM
 
 
 -- | Objects of various sorts in LLVM IR are identified by address in the LLVM
@@ -83,13 +86,41 @@
 instance Monoid Label where
   mempty = Label mempty
 
+instance Hashable Label where
+  hashWithSalt salt (Label sbs) = hashWithSalt salt sbs
 
--- | Convert to llvm-hs
+-- | Some names are not external function references but instead references to
+-- definitions that we provide inline in the module to be compiled by LLVM. See
+-- 'Data.Array.Accelerate.LLVM.CodeGen.Base.call' for more details. This
+-- function checks whether the given label is one of those inline-provided
+-- functions.
+labelIsAccPrelude :: Label -> Bool
+labelIsAccPrelude (Label x) = SBS.take (SBS.length prefix) x == prefix
+  where Label prefix = makeAccPreludeLabel SBS.empty
+
+-- | Create a reference to an inline-provided function. See
+-- 'labelIsAccPrelude'.
+makeAccPreludeLabel :: ShortByteString -> Label
+makeAccPreludeLabel s = Label (SBS8.pack "accprelude_" <> s)
+
+
+-- | Convert to llvm-pretty
 --
-instance Downcast (Name a) LLVM.Name where
-  downcast (Name s)   = LLVM.Name s
-  downcast (UnName n) = LLVM.UnName n
+-- We only explicit conversion functions for symbols and identifiers separately.
+--
+nameToPrettyS :: Name a -> LLVM.Symbol
+nameToPrettyS (Name s) = LLVM.Symbol (SBS8.unpack s)
+nameToPrettyS (UnName n) = LLVM.Symbol ("tollpr_s_" ++ show n)
 
-instance Downcast Label LLVM.Name where
-  downcast (Label l)  = LLVM.Name l
+nameToPrettyI :: Name a -> LLVM.Ident
+nameToPrettyI (Name s) = LLVM.Ident (SBS8.unpack s)
+nameToPrettyI (UnName n) = LLVM.Ident ("tollpr_i_" ++ show n)
 
+labelToPrettyS :: Label -> LLVM.Symbol
+labelToPrettyS (Label s) = LLVM.Symbol (SBS8.unpack s)
+
+labelToPrettyI :: Label -> LLVM.Ident
+labelToPrettyI (Label s) = LLVM.Ident (SBS8.unpack s)
+
+labelToPrettyBL :: Label -> LLVM.BlockLabel
+labelToPrettyBL (Label s) = LLVM.Named (LLVM.Ident (SBS8.unpack s))
diff --git a/src/LLVM/AST/Type/Operand.hs b/src/LLVM/AST/Type/Operand.hs
--- a/src/LLVM/AST/Type/Operand.hs
+++ b/src/LLVM/AST/Type/Operand.hs
@@ -23,21 +23,21 @@
 import LLVM.AST.Type.Name
 import LLVM.AST.Type.Representation
 
-import qualified LLVM.AST.Operand                                   as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LLVM
 
 
 -- | An 'Operand' is roughly anything that is an argument to an 'Instruction'
 --
 data Operand a where
-  LocalReference        :: Type a -> Name a -> Operand a
-  ConstantOperand       :: Constant a -> Operand a
+  LocalReference  :: Type a -> Name a -> Operand a
+  ConstantOperand :: Constant a -> Operand a
 
 
--- | Convert to llvm-hs
+-- | Convert to llvm-pretty
 --
-instance Downcast (Operand a) LLVM.Operand where
-  downcast (LocalReference t n) = LLVM.LocalReference (downcast t) (downcast n)
-  downcast (ConstantOperand c)  = LLVM.ConstantOperand (downcast c)
+instance Downcast (Operand a) (LLVM.Typed LLVM.Value) where
+  downcast (LocalReference t n) = LLVM.Typed (downcast t) (LLVM.ValIdent (nameToPrettyI n))
+  downcast (ConstantOperand c)  = downcast c
 
 instance TypeOf Operand where
   typeOf (LocalReference t _) = t
diff --git a/src/LLVM/AST/Type/Representation.hs b/src/LLVM/AST/Type/Representation.hs
--- a/src/LLVM/AST/Type/Representation.hs
+++ b/src/LLVM/AST/Type/Representation.hs
@@ -2,6 +2,7 @@
 {-# LANGUAGE GADTs                 #-}
 {-# LANGUAGE LambdaCase            #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings     #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : LLVM.AST.Type.Representation
@@ -19,19 +20,26 @@
   module Data.Array.Accelerate.Type,
   Ptr,
   AddrSpace(..),
+  defaultAddrSpace,
 
 ) where
 
 import Data.Array.Accelerate.Type
 import Data.Array.Accelerate.Representation.Type
 
-import LLVM.AST.Type.AddrSpace
 import LLVM.AST.Type.Downcast
+import LLVM.AST.Type.Name
 
-import qualified LLVM.AST.Type                                      as LLVM
+-- import qualified LLVM.AST.Type                                      as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LLVM
+import Data.Array.Accelerate.LLVM.Internal.LLVMPretty               ( AddrSpace(..), defaultAddrSpace )
 
+import Data.List
+import Data.Text.Lazy.Builder
 import Foreign.Ptr
+import Formatting
 import Text.Printf
+import qualified Data.ByteString.Short.Char8                        as S8
 
 
 -- Witnesses to observe the LLVM type hierarchy:
@@ -68,13 +76,17 @@
   VoidType  :: Type ()
   PrimType  :: PrimType a -> Type a
 
+data LLArray a
+
 data PrimType a where
   BoolPrimType    ::                            PrimType Bool
   ScalarPrimType  :: ScalarType a            -> PrimType a          -- scalar value types (things in registers)
   PtrPrimType     :: PrimType a -> AddrSpace -> PrimType (Ptr a)    -- pointers (XXX: volatility?)
-  StructPrimType  :: TypeR a                 -> PrimType a          -- opaque structures (required for CmpXchg)
-  ArrayPrimType   :: Word64 -> ScalarType a  -> PrimType a          -- static arrays
+  ArrayPrimType   :: Word64 -> ScalarType a  -> PrimType (LLArray a) -- static arrays (TODO: type-level array length)
+  StructPrimType  :: Bool -> TupR PrimType l -> PrimType l          -- aggregate structures
+  NamedPrimType   :: Label                   -> PrimType a          -- typedef (TODO: add a type witness)
 
+
 -- | All types
 --
 
@@ -251,23 +263,51 @@
   primType = BoolPrimType
 
 instance Show (Type a) where
-  show VoidType        = "()"
-  show (PrimType t)    = show t
+  show VoidType     = "()"
+  show (PrimType t) = show t
 
 instance Show (PrimType a) where
-  show BoolPrimType                   = "Bool"
-  show (ScalarPrimType t)             = show t
-  show (StructPrimType t)             = show t
-  show (ArrayPrimType n t)            = printf "[%d x %s]" n (show t)
-  show (PtrPrimType t (AddrSpace n))  = printf "Ptr%s %s" a p
+  show BoolPrimType              = "Bool"
+  show (ScalarPrimType t)        = show t
+  show (NamedPrimType (Label l)) = S8.unpack l
+  show (ArrayPrimType n t)       = printf "[%d x %s]" n (show t)
+  show (StructPrimType _ t)      = printf "{ %s }" (intercalate ", " (go t))
     where
+      go :: TupR PrimType t -> [String]
+      go TupRunit         = []
+      go (TupRsingle s)   = [show s]
+      go (TupRpair ta tb) = go ta ++ go tb
+
+  show (PtrPrimType t (AddrSpace n)) = printf "Ptr%s %s" a p
+    where
       p             = show t
       a | n == 0    = ""
-        | otherwise = printf "[addrspace %d]" n
+        | otherwise = printf "[addrspace %d]" n :: String
       -- p | PtrPrimType{} <- t  = printf "(%s)" (show t)
       --   | otherwise           = show t
 
+formatType :: Format r (Type a -> r)
+formatType = later $ \case
+  VoidType   -> "()"
+  PrimType t -> bformat formatPrimType t
 
+formatPrimType :: Format r (PrimType a -> r)
+formatPrimType = later $ \case
+  BoolPrimType            -> "Bool"
+  ScalarPrimType t        -> bformat formatScalarType t
+  NamedPrimType (Label t) -> bformat string (S8.unpack t)
+  ArrayPrimType n t       -> bformat (squared (int % " x " % formatScalarType)) n t
+  StructPrimType _ t      -> bformat (braced (commaSpaceSep builder)) (go t)
+    where
+      go :: TupR PrimType t -> [Builder]
+      go TupRunit         = []
+      go (TupRsingle s)   = [bformat formatPrimType s]
+      go (TupRpair ta tb) = go ta ++ go tb
+
+  PtrPrimType t (AddrSpace 0) -> bformat ("Ptr "                        % formatPrimType) t
+  PtrPrimType t (AddrSpace n) -> bformat ("Ptr[addrspace " % int % "] " % formatPrimType) n t
+
+
 -- | Does the concrete type represent signed or unsigned values?
 --
 class IsSigned dict where
@@ -313,20 +353,21 @@
   typeOf :: f a -> Type a
 
 
--- | Convert to llvm-hs
+-- | Convert to llvm-pretty
 --
 instance Downcast (Type a) LLVM.Type where
-  downcast VoidType     = LLVM.VoidType
+  downcast VoidType     = LLVM.PrimType LLVM.Void
   downcast (PrimType t) = downcast t
 
 instance Downcast (PrimType a) LLVM.Type where
-  downcast BoolPrimType         = LLVM.IntegerType 1
+  downcast BoolPrimType         = LLVM.PrimType (LLVM.Integer 1)
+  downcast (NamedPrimType lab)  = LLVM.Alias (labelToPrettyI lab)
   downcast (ScalarPrimType t)   = downcast t
-  downcast (PtrPrimType t a)    = LLVM.PointerType (downcast t) a
-  downcast (ArrayPrimType n t)  = LLVM.ArrayType n (downcast t)
-  downcast (StructPrimType t)   = LLVM.StructureType False (go t)
+  downcast (PtrPrimType t a)    = LLVM.PtrTo (downcast t) a
+  downcast (ArrayPrimType n t)  = LLVM.Array n (downcast t)
+  downcast (StructPrimType p t) = (if p then LLVM.PackedStruct else LLVM.Struct) (go t)
     where
-      go :: TypeR t -> [LLVM.Type]
+      go :: TupR PrimType t -> [LLVM.Type]
       go TupRunit         = []
       go (TupRsingle s)   = [downcast s]
       go (TupRpair ta tb) = go ta ++ go tb
diff --git a/src/LLVM/AST/Type/Terminator.hs b/src/LLVM/AST/Type/Terminator.hs
--- a/src/LLVM/AST/Type/Terminator.hs
+++ b/src/LLVM/AST/Type/Terminator.hs
@@ -1,6 +1,9 @@
+{-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE GADTs                 #-}
 {-# LANGUAGE LambdaCase            #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeSynonymInstances  #-}
 {-# OPTIONS_HADDOCK hide #-}
 -- |
 -- Module      : LLVM.AST.Type.Terminator
@@ -20,9 +23,11 @@
 import LLVM.AST.Type.Operand
 import LLVM.AST.Type.Downcast
 
-import qualified LLVM.AST.Instruction                               as LLVM
+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty     as LLVM
 
+import Data.Bifunctor                                               ( bimap )
 
+
 -- | <http://llvm.org/docs/LangRef.html#terminators>
 --
 -- TLM: well, I don't think the types of these terminators make any sense. When
@@ -63,14 +68,17 @@
 
 -- | Convert to llvm-hs
 --
-instance Downcast (Terminator a) LLVM.Terminator where
+instance Downcast (Terminator a) LLVM.Instr where
   downcast = \case
-    Ret           -> LLVM.Ret Nothing md
-    RetVal x      -> LLVM.Ret (Just (downcast x)) md
-    Br l          -> LLVM.Br (downcast l) md
-    CondBr p t f  -> LLVM.CondBr (downcast p) (downcast t) (downcast f) md
-    Switch p d a  -> LLVM.Switch (downcast p) (downcast d) (downcast a) md
-    where
-      md :: LLVM.InstructionMetadata
-      md = []
-
+    Ret           -> LLVM.RetVoid
+    RetVal x      -> LLVM.Ret (downcast x)
+    Br l          -> LLVM.Jump (LLVM.Named (labelToPrettyI l))
+    CondBr p t f  -> LLVM.Br (downcast p) (LLVM.Named (labelToPrettyI t)) (LLVM.Named (labelToPrettyI f))
+    Switch p d a  -> LLVM.Switch (downcast p)
+                                 (labelToPrettyBL d)
+                                 (map (bimap fromConstant labelToPrettyBL) a)
+      where
+        fromConstant :: Constant a -> Integer
+        fromConstant cnst = case downcast @_ @(LLVM.Typed LLVM.Value) cnst of
+          LLVM.Typed _ (LLVM.ValInteger n) -> n
+          _ -> error "TODO: llvm-pretty supports only integral cases for Switch"
