diff --git a/docs/c-api.rst b/docs/c-api.rst
--- a/docs/c-api.rst
+++ b/docs/c-api.rst
@@ -332,7 +332,8 @@
 
    * If ``*p`` is ``NULL``, the function will allocate sufficient
      storage with ``malloc()``, serialise the value, and write the
-     address of the byte representation to ``*p``.
+     address of the byte representation to ``*p``.  The caller gains
+     ownership of this allocation and is responsible for freeing it.
 
    * Otherwise, the serialised representation of the value will be
      stored at ``*p``, which *must* have room for at least ``*n``
@@ -380,7 +381,7 @@
 .. c:function:: int futhark_new_opaque_t(struct futhark_context *ctx, struct futhark_opaque_t **out, const struct futhark_opaque_t2 *bar, const struct futhark_opaque_t1 *foo);
 
    Construct a record in ``*out`` which has the given values for the
-   ``bar`` and ``foo`` fields.  The parameter ordering constitutes the
+   ``bar`` and ``foo`` fields.  The parameters are the
    fields in alphabetic order.  Tuple fields are named ``vX`` where
    ``X`` is an integer.  The resulting record *aliases* the values
    provided for ``bar`` and ``foo``, but has its own lifetime, and all
@@ -416,8 +417,8 @@
    sure to call :c:func:`futhark_context_sync` before using the value
    of ``out0``.
 
-Errors are indicated by a nonzero return value.  On error, nothing is
-written to the *out*-parameters.
+Errors are indicated by a nonzero return value.  On error, the
+*out*-parameters are not touched.
 
 The precise semantics of the return value depends on the backend.  For
 the sequential C backend, errors will always be available when the
@@ -618,6 +619,10 @@
 
   * A list of all *outputs*, including their type (as a name) and
     *whether they are unique*.
+
+  * A list of all *tuning parameters* that can influence the execution
+    of this entry point.  These are not necessarily unique to the
+    entry point.
 
 * A mapping from the name of each non-scalar type to:
 
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -19,43 +19,49 @@
 from pygments.lexer import RegexLexer, bygroups
 from pygments import token
 from pygments import unistring as uni
-from pygments.token import Text, Comment, Operator, Keyword, Name, String, \
-    Number, Punctuation, Whitespace
+from pygments.token import (
+    Text,
+    Comment,
+    Operator,
+    Keyword,
+    Name,
+    String,
+    Number,
+    Punctuation,
+    Whitespace,
+)
 from sphinx.highlighting import lexers
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+# sys.path.insert(0, os.path.abspath('.'))
 
 # -- General configuration ------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
-extensions = [
-    'sphinx.ext.todo',
-    'sphinx.ext.mathjax'
-]
+extensions = ["sphinx.ext.todo", "sphinx.ext.mathjax"]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = 'Futhark'
-copyright = '2013-2020, DIKU, University of Copenhagen'
+project = "Futhark"
+copyright = "2013-2020, DIKU, University of Copenhagen"
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -63,48 +69,52 @@
 #
 # The short X.Y version.
 
+
 # No reason for a cabal file parser; let's just hack it.
 def get_version():
     # Get cabal file
-    cabal_file = open('../futhark.cabal', 'r').read()
+    cabal_file = open("../futhark.cabal", "r").read()
     # Extract version
-    return re.search(r'^version:[ ]*([^ ]*)$', cabal_file, flags=re.MULTILINE).group(1)
+    return re.search(
+        r"^version:[ ]*([^ ]*)$", cabal_file, flags=re.MULTILINE
+    ).group(1)
 
+
 version = get_version()
 # The full version, including alpha/beta/rc tags.
 release = version
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
-#language = None
+# language = None
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
-#today = ''
+# today = ''
 # Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
-exclude_patterns = ['_build', 'lib']
+exclude_patterns = ["_build", "lib"]
 
 # The reST default role (used for this markup: `text`) to use for all
 # documents.
-#default_role = None
+# default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
 
 # If true, the current module name will be prepended to all description
 # unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
 
 # If true, sectionauthor and moduleauthor directives will be shown in the
 # output. They are ignored by default.
-#show_authors = false
+# show_authors = false
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 
 class FutharkLexer(RegexLexer):
@@ -113,105 +123,171 @@
 
     .. versionadded:: 2.8
     """
-    name = 'Futhark'
-    url = 'https://futhark-lang.org/'
-    aliases = ['futhark']
-    filenames = ['*.fut']
-    mimetypes = ['text/x-futhark']
 
-    num_types = ('i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64')
+    name = "Futhark"
+    url = "https://futhark-lang.org/"
+    aliases = ["futhark"]
+    filenames = ["*.fut"]
+    mimetypes = ["text/x-futhark"]
 
-    other_types = ('bool', )
+    num_types = (
+        "i8",
+        "i16",
+        "i32",
+        "i64",
+        "u8",
+        "u16",
+        "u32",
+        "u64",
+        "f32",
+        "f64",
+    )
 
-    reserved = ('if', 'then', 'else', 'def', 'let', 'loop', 'in', 'with', 'type',
-                'val', 'entry', 'for', 'while', 'do', 'case', 'match',
-                'include', 'import', 'module', 'open', 'local', 'assert', '_')
+    other_types = ("bool",)
 
-    ascii = ('NUL', 'SOH', '[SE]TX', 'EOT', 'ENQ', 'ACK',
-             'BEL', 'BS', 'HT', 'LF', 'VT', 'FF', 'CR', 'S[OI]', 'DLE',
-             'DC[1-4]', 'NAK', 'SYN', 'ETB', 'CAN',
-             'EM', 'SUB', 'ESC', '[FGRU]S', 'SP', 'DEL')
+    reserved = (
+        "if",
+        "then",
+        "else",
+        "def",
+        "let",
+        "loop",
+        "in",
+        "with",
+        "type",
+        "val",
+        "entry",
+        "for",
+        "while",
+        "do",
+        "case",
+        "match",
+        "include",
+        "import",
+        "module",
+        "open",
+        "local",
+        "assert",
+        "_",
+    )
 
-    num_postfix = r'(%s)?' % '|'.join(num_types)
+    ascii = (
+        "NUL",
+        "SOH",
+        "[SE]TX",
+        "EOT",
+        "ENQ",
+        "ACK",
+        "BEL",
+        "BS",
+        "HT",
+        "LF",
+        "VT",
+        "FF",
+        "CR",
+        "S[OI]",
+        "DLE",
+        "DC[1-4]",
+        "NAK",
+        "SYN",
+        "ETB",
+        "CAN",
+        "EM",
+        "SUB",
+        "ESC",
+        "[FGRU]S",
+        "SP",
+        "DEL",
+    )
 
-    identifier_re = '[a-zA-Z_][a-zA-Z_0-9\']*'
+    num_postfix = r"(%s)?" % "|".join(num_types)
 
+    identifier_re = "[a-zA-Z_][a-zA-Z_0-9']*"
+
     # opstart_re = '+\-\*/%=\!><\|&\^'
 
     tokens = {
-        'root': [
-            (r'--(.*?)$', Comment.Single),
-            (r'\s+', Whitespace),
-            (r'\(\)', Punctuation),
-            (r'\b(%s)(?!\')\b' % '|'.join(reserved), Keyword.Reserved),
-            (r'\b(%s)(?!\')\b' % '|'.join(num_types + other_types), Keyword.Type),
-
+        "root": [
+            (r"--(.*?)$", Comment.Single),
+            (r"\s+", Whitespace),
+            (r"\(\)", Punctuation),
+            (r"\b(%s)(?!\')\b" % "|".join(reserved), Keyword.Reserved),
+            (
+                r"\b(%s)(?!\')\b" % "|".join(num_types + other_types),
+                Keyword.Type,
+            ),
             # Identifiers
-            (r'#\[([a-zA-Z_\(\) ]*)\]', Comment.Preproc),
-            (r'[#!]?(%s\.)*%s' % (identifier_re, identifier_re), Name),
-
-            (r'\\', Operator),
-            (r'[-+/%=!><|&*^][-+/%=!><|&*^.]*', Operator),
-            (r'[][(),:;`{}?.\']', Punctuation),
-
+            (r"#\[([a-zA-Z_\(\) ]*)\]", Comment.Preproc),
+            (r"[#!]?(%s\.)*%s" % (identifier_re, identifier_re), Name),
+            (r"\\", Operator),
+            (r"[-+/%=!><|&*^][-+/%=!><|&*^.]*", Operator),
+            (r"[][(),:;`{}?.\']", Punctuation),
             #  Numbers
-            (r'0[xX]_*[\da-fA-F](_*[\da-fA-F])*_*[pP][+-]?\d(_*\d)*' + num_postfix,
-             Number.Float),
-            (r'0[xX]_*[\da-fA-F](_*[\da-fA-F])*\.[\da-fA-F](_*[\da-fA-F])*'
-             r'(_*[pP][+-]?\d(_*\d)*)?' + num_postfix, Number.Float),
-            (r'\d(_*\d)*_*[eE][+-]?\d(_*\d)*' + num_postfix, Number.Float),
-            (r'\d(_*\d)*\.\d(_*\d)*(_*[eE][+-]?\d(_*\d)*)?' + num_postfix, Number.Float),
-            (r'0[bB]_*[01](_*[01])*' + num_postfix, Number.Bin),
-            (r'0[xX]_*[\da-fA-F](_*[\da-fA-F])*' + num_postfix, Number.Hex),
-            (r'\d(_*\d)*' + num_postfix, Number.Integer),
-
+            (
+                r"0[xX]_*[\da-fA-F](_*[\da-fA-F])*_*[pP][+-]?\d(_*\d)*"
+                + num_postfix,
+                Number.Float,
+            ),
+            (
+                r"0[xX]_*[\da-fA-F](_*[\da-fA-F])*\.[\da-fA-F](_*[\da-fA-F])*"
+                r"(_*[pP][+-]?\d(_*\d)*)?" + num_postfix,
+                Number.Float,
+            ),
+            (r"\d(_*\d)*_*[eE][+-]?\d(_*\d)*" + num_postfix, Number.Float),
+            (
+                r"\d(_*\d)*\.\d(_*\d)*(_*[eE][+-]?\d(_*\d)*)?" + num_postfix,
+                Number.Float,
+            ),
+            (r"0[bB]_*[01](_*[01])*" + num_postfix, Number.Bin),
+            (r"0[xX]_*[\da-fA-F](_*[\da-fA-F])*" + num_postfix, Number.Hex),
+            (r"\d(_*\d)*" + num_postfix, Number.Integer),
             #  Character/String Literals
-            (r"'", String.Char, 'character'),
-            (r'"', String, 'string'),
+            (r"'", String.Char, "character"),
+            (r'"', String, "string"),
             #  Special
-            (r'\[[a-zA-Z_\d]*\]', Keyword.Type),
-            (r'\(\)', Name.Builtin),
+            (r"\[[a-zA-Z_\d]*\]", Keyword.Type),
+            (r"\(\)", Name.Builtin),
         ],
-        'character': [
+        "character": [
             # Allows multi-chars, incorrectly.
-            (r"[^\\']'", String.Char, '#pop'),
-            (r"\\", String.Escape, 'escape'),
-            ("'", String.Char, '#pop'),
+            (r"[^\\']'", String.Char, "#pop"),
+            (r"\\", String.Escape, "escape"),
+            ("'", String.Char, "#pop"),
         ],
-        'string': [
+        "string": [
             (r'[^\\"]+', String),
-            (r"\\", String.Escape, 'escape'),
-            ('"', String, '#pop'),
+            (r"\\", String.Escape, "escape"),
+            ('"', String, "#pop"),
         ],
-
-        'escape': [
-            (r'[abfnrtv"\'&\\]', String.Escape, '#pop'),
-            (r'\^[][' + uni.Lu + r'@^_]', String.Escape, '#pop'),
-            ('|'.join(ascii), String.Escape, '#pop'),
-            (r'o[0-7]+', String.Escape, '#pop'),
-            (r'x[\da-fA-F]+', String.Escape, '#pop'),
-            (r'\d+', String.Escape, '#pop'),
-            (r'(\s+)(\\)', bygroups(Whitespace, String.Escape), '#pop'),
+        "escape": [
+            (r'[abfnrtv"\'&\\]', String.Escape, "#pop"),
+            (r"\^[][" + uni.Lu + r"@^_]", String.Escape, "#pop"),
+            ("|".join(ascii), String.Escape, "#pop"),
+            (r"o[0-7]+", String.Escape, "#pop"),
+            (r"x[\da-fA-F]+", String.Escape, "#pop"),
+            (r"\d+", String.Escape, "#pop"),
+            (r"(\s+)(\\)", bygroups(Whitespace, String.Escape), "#pop"),
         ],
     }
 
-lexers['futhark'] = FutharkLexer()
 
-highlight_language = 'text'
+lexers["futhark"] = FutharkLexer()
 
+highlight_language = "text"
+
 # A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
 
 # If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = false
+# keep_warnings = false
 
 
 # -- Options for HTML output ----------------------------------------------
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'futhark'
-html_theme_path = ['_theme']
+html_theme = "futhark"
+html_theme_path = ["_theme"]
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
@@ -219,23 +295,23 @@
 html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+# html_title = None
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-#html_logo = None
+# html_logo = None
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
-#html_favicon = None
+# html_favicon = None
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
@@ -245,93 +321,97 @@
 # Add any extra paths that contain custom files (such as robots.txt or
 # .htaccess) here, relative to this directory. These files are copied
 # directly to the root of the documentation.
-#html_extra_path = []
+# html_extra_path = []
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-html_sidebars = { '**': ['globaltoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'] }
+html_sidebars = {
+    "**": [
+        "globaltoc.html",
+        "relations.html",
+        "sourcelink.html",
+        "searchbox.html",
+    ]
+}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
 
 # If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
 
 # If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
 
 # If true, the index is split into individual pages for each letter.
-#html_split_index = false
+# html_split_index = false
 
 # If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
 
 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
 
 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 # base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
 
 # This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'Futharkdoc'
+htmlhelp_basename = "Futharkdoc"
 
 
 # -- Options for LaTeX output ---------------------------------------------
 
 latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
+    # The paper size ('letterpaper' or 'a4paper').
+    #'papersize': 'letterpaper',
+    # The font size ('10pt', '11pt' or '12pt').
+    #'pointsize': '10pt',
+    # Additional stuff for the LaTeX preamble.
+    #'preamble': '',
 }
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-  ('index', 'Futhark.tex', 'Futhark User\'s Guide',
-   'DIKU', 'manual'),
+    ("index", "Futhark.tex", "Futhark User's Guide", "DIKU", "manual"),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
 # the title page.
-#latex_logo = None
+# latex_logo = None
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
-#latex_use_parts = false
+# latex_use_parts = false
 
 # If true, show page references after internal links.
-#latex_show_pagerefs = false
+# latex_show_pagerefs = false
 
 # If true, show URL addresses after external links.
-#latex_show_urls = false
+# latex_show_urls = false
 
 # Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
 
 # If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
 
 
 # -- Options for manual page output ---------------------------------------
@@ -339,29 +419,107 @@
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    ('man/futhark', 'futhark', 'a parallel functional array language', [], 1),
-    ('man/futhark-autotune', 'futhark-autotune', 'calibrate run-time parameters', [], 1),
-    ('man/futhark-c', 'futhark-c', 'compile Futhark to sequential C', [], 1),
-    ('man/futhark-multicore', 'futhark-multicore', 'compile Futhark to multithreaded C', [], 1),
-    ('man/futhark-ispc', 'futhark-ispc', 'compile Futhark to multithreaded ISPC', [], 1),
-    ('man/futhark-opencl', 'futhark-opencl', 'compile Futhark to OpenCL', [], 1),
-    ('man/futhark-cuda', 'futhark-cuda', 'compile Futhark to CUDA', [], 1),
-    ('man/futhark-python', 'futhark-python', 'compile Futhark to sequential Python', [], 1),
-    ('man/futhark-pyopencl', 'futhark-pyopencl', 'compile Futhark to Python and OpenCL', [], 1),
-    ('man/futhark-wasm', 'futhark-wasm', 'compile Futhark to WebAssembly', [], 1),
-    ('man/futhark-wasm-multicore', 'futhark-wasm-multicore', 'compile Futhark to parallel WebAssembly', [], 1),
-    ('man/futhark-run', 'futhark-run', 'interpret Futhark program', [], 1),
-    ('man/futhark-repl', 'futhark-repl', 'interactive Futhark read-eval-print-loop', [], 1),
-    ('man/futhark-test', 'futhark-test', 'test Futhark programs', [], 1),
-    ('man/futhark-bench', 'futhark-bench', 'benchmark Futhark programs', [], 1),
-    ('man/futhark-doc', 'futhark-doc', 'generate documentation for Futhark code', [], 1),
-    ('man/futhark-dataset', 'futhark-dataset', 'generate random data sets', [], 1),
-    ('man/futhark-pkg', 'futhark-pkg', 'manage Futhark packages', [], 1),
-    ('man/futhark-literate', 'futhark-literate', 'execute literate Futhark program', [], 1)
+    ("man/futhark", "futhark", "a parallel functional array language", [], 1),
+    (
+        "man/futhark-autotune",
+        "futhark-autotune",
+        "calibrate run-time parameters",
+        [],
+        1,
+    ),
+    ("man/futhark-c", "futhark-c", "compile Futhark to sequential C", [], 1),
+    (
+        "man/futhark-multicore",
+        "futhark-multicore",
+        "compile Futhark to multithreaded C",
+        [],
+        1,
+    ),
+    (
+        "man/futhark-ispc",
+        "futhark-ispc",
+        "compile Futhark to multithreaded ISPC",
+        [],
+        1,
+    ),
+    (
+        "man/futhark-opencl",
+        "futhark-opencl",
+        "compile Futhark to OpenCL",
+        [],
+        1,
+    ),
+    ("man/futhark-cuda", "futhark-cuda", "compile Futhark to CUDA", [], 1),
+    (
+        "man/futhark-python",
+        "futhark-python",
+        "compile Futhark to sequential Python",
+        [],
+        1,
+    ),
+    (
+        "man/futhark-pyopencl",
+        "futhark-pyopencl",
+        "compile Futhark to Python and OpenCL",
+        [],
+        1,
+    ),
+    (
+        "man/futhark-wasm",
+        "futhark-wasm",
+        "compile Futhark to WebAssembly",
+        [],
+        1,
+    ),
+    (
+        "man/futhark-wasm-multicore",
+        "futhark-wasm-multicore",
+        "compile Futhark to parallel WebAssembly",
+        [],
+        1,
+    ),
+    ("man/futhark-run", "futhark-run", "interpret Futhark program", [], 1),
+    (
+        "man/futhark-repl",
+        "futhark-repl",
+        "interactive Futhark read-eval-print-loop",
+        [],
+        1,
+    ),
+    ("man/futhark-test", "futhark-test", "test Futhark programs", [], 1),
+    (
+        "man/futhark-bench",
+        "futhark-bench",
+        "benchmark Futhark programs",
+        [],
+        1,
+    ),
+    (
+        "man/futhark-doc",
+        "futhark-doc",
+        "generate documentation for Futhark code",
+        [],
+        1,
+    ),
+    (
+        "man/futhark-dataset",
+        "futhark-dataset",
+        "generate random data sets",
+        [],
+        1,
+    ),
+    ("man/futhark-pkg", "futhark-pkg", "manage Futhark packages", [], 1),
+    (
+        "man/futhark-literate",
+        "futhark-literate",
+        "execute literate Futhark program",
+        [],
+        1,
+    ),
 ]
 
 # If true, show URL addresses after external links.
-#man_show_urls = false
+# man_show_urls = false
 
 
 # -- Options for Texinfo output -------------------------------------------
@@ -370,19 +528,25 @@
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-  ('index', 'Futhark', 'Futhark Documentation',
-   'DIKU', 'Futhark', 'One line description of project.',
-   'Miscellaneous'),
+    (
+        "index",
+        "Futhark",
+        "Futhark Documentation",
+        "DIKU",
+        "Futhark",
+        "One line description of project.",
+        "Miscellaneous",
+    ),
 ]
 
 # Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
 
 # If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
 
 # How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
 
 # If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = false
+# texinfo_no_detailmenu = false
diff --git a/docs/error-index.rst b/docs/error-index.rst
--- a/docs/error-index.rst
+++ b/docs/error-index.rst
@@ -505,6 +505,27 @@
 
 Such an array will take up no space at runtime.
 
+.. _anonymous-nonconstructive:
+
+"Type abbreviation contains an anonymous size not used constructively as an array size."
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This error occurs for type abbreviations that use anonymous sizes,
+such as the following:
+
+.. code-block:: futhark
+
+   type^ t = []bool -> bool
+
+Such an abbreviation is actually shorthand for
+
+.. code-block:: futhark
+
+   type^ t = ?[n].[n]bool -> bool
+
+which is erroneous, but with workarounds, as explained in
+:ref:`unused-existential`.
+
 .. _unify-param-existential:
 
 "Parameter *x* used as size would go out of scope."
diff --git a/docs/glossary.rst b/docs/glossary.rst
new file mode 100644
--- /dev/null
+++ b/docs/glossary.rst
@@ -0,0 +1,242 @@
+.. _glossary:
+
+Glossary
+========
+
+The following defines various Futhark-specific terms used in the
+documentation and in compiler output.
+
+.. glossary::
+   :sorted:
+
+   Aliases
+
+     The *aliases* of a variable is a set of those other variables
+     with which it might be :term:`aliased<aliasing>`.
+
+   Aliasing
+
+     Whether two values might potentially share the same memory at
+     run-time.  Clearly, after ``let y = x``, ``x`` and ``y`` are
+     aliased.  Also, the slice ``x[i:j]`` is also aliased with ``x``.
+     Aliasing is used to type-check :ref:`in-place-updates`.
+
+   Anonymous size
+
+     In a type expression, a size of the form `[]`.  Will be
+     :term:`elaborated<elaboration>` to some name (possibly
+     :term:`existentially bound<existential size>`) by the type
+     checker.
+
+   Attribute
+
+     Auxiliary information attached to an expression or declaration,
+     which the compiler or other tool might use for various purposes.
+     See :ref:`attributes`.
+
+   Coercion
+
+     Shorthand for a :ref:`size-coercion`.
+
+   Compiler backend
+
+     A Futhark compiler backend is technically only responsible for
+     the final compilation result, but from the user's perspective is
+     also coupled with a :term:`compiler pipeline`.  The backend
+     corresponds to compiler subcommand, such as ``futhark c``,
+     ``futhark cuda``, ``futhark multicore``, etc.
+
+   Compiler frontend
+
+     The part of the compiler responsible for reading code from files,
+     parsing it, and type checking it.
+
+   Compiler pipeline
+
+     The series of compiler passes that lie between the
+     :term:`compiler frontend` and the :term:`compiler backend`.
+     Responsible for the majority of program optimisations.  In
+     principle the pipeline could be configurable, but in practice
+     each backend is coupled with a specific pipeline.
+
+   Constructive use
+
+     A variable ``n`` is used *constructively* in a type if it is used
+     as the size of an array at least once outside of any function
+     arrows.  For example, the following types use ``n``
+     constructively:
+
+       * ``[n]bool``
+       * ``([n]bool, bool -> [n]bool)``
+
+     The following do not:
+
+       * ``[n+1]bool``
+       * ``bool -> [n]bool``
+
+   Consumption
+
+     If a value is passed for a *consuming* function parameter, that
+     value may no longer be used.  We say that a an expression is
+     *with consumption* if any values are consumed in the expression.
+     This is banned in some cases where that expression might
+     otherwise be evaluated multiple times. See
+     :ref:`in-place-updates`.
+
+   Data parallelism
+
+     Performing the same operation on multiple elements of a
+     collection, such as an array.  The ``map`` :term:`SOAC` is the
+     simplest example.  This is the form of parallelism supported by
+     Futhark. `See also Wikipedia
+     <https://en.wikipedia.org/wiki/Data_parallelism>`_.
+
+   Elaboration
+
+     The process conducted out by the type checker, where it infers
+     and inserts information not explicitly provided in the program.
+     The most important part of this is type inference, but also
+     includes various other things.
+
+   Existential size
+
+     An existential size is a size that is bound by the existential
+     quantifier ``?`` in the same type.  For example, in a type
+     ``[n]bool -> ?[m].[m]bool``, the size ``m`` is existential.  When
+     such a function is applied, each existential size is instantiated
+     as an :term:`unknown size`.
+
+   Functor
+
+     The Standard ML term for what Futhark calls a :term:`parametric
+     module`.
+
+   GPU backend
+
+     A :term:`compiler backend` that ultimately produces GPU code.
+     The backends ``opencl`` and ``gpu`` are GPU backends.  These have
+     more restrictions than some other backends, particularly with
+     respect to :term:`irregular nested data parallelism`.
+
+   In-place updates
+
+     A somewhat misleading term for the syntactic forms ``x with [i] =
+     v`` and ``let x[i] = v``.  These are not semantic in-place
+     updates, but can be operationally understood as thus.  See
+     :ref:`in-place-updates`.
+
+   Irregular
+
+     Something that is not regular.  Usually used as shorthand for
+     :term:`irregular nested data parallelism` or :term:`irregular
+     array`.
+
+   Irregular array
+
+     An array where the elements do not have the same size.  For
+     example, ``[[1], [2,3]`` is irregular.  These are not supported
+     in Futhark.
+
+   Irregular nested data parallelism
+
+     An instance of :term:`nested data parallelism`, where the
+     :term:`parallel width` of inner parallelism is term:`variant` to
+     the outer parallelism.  For example, the expression following
+     expression exhibits irregular nested data parallelism::
+
+       map (\n -> reduce (+) 0 (iota n)) ns
+
+     Because the width of the inner ``reduce`` is ``n``, and every
+     iteration of the outer ``map`` has a (potentially) different
+     ``n``.  The Futhark :term:`GPU backends<GPU backend>` *currently*
+     do not support irregular nested data parallelism well, and will
+     usually sequentialise the irregular loops.  In cases that require
+     an :term:`irregular memory allocation`, the compiler may entirely
+     fail to generate code.
+
+   Irregular memory allocation
+
+     A situation that occurs when the generated code has to allocate
+     memory inside of an instance of :term:`nested data parallelism`,
+     where the amount to allocate is variant to the outer parallel
+     levels.  As a contrived example (that the actual compiler would
+     just optimise away), consider::
+
+       map (\n -> let A = iota n
+                  in A[10])
+           ns
+
+     To construct the array ``A`` in memory, we require ``8n`` bytes,
+     but ``n`` is not known until we start executing the body of the
+     ``map``.  While such simple cases are handled, more complicated
+     ones that involve nested sequential loops are not supported by
+     the :term:`GPU backends<GPU backend>`.
+
+   Parametric module
+
+     A function from :term:`modules<module>` to modules.  The most
+     powerful form of abstraction provided by Futhark.
+
+   Lifted type
+
+     A type that may contain functions.  These have various
+     restrictions on their use.  See :ref:`hofs`.
+
+   Module
+
+     A mapping from names to definitions of types, values, or nested
+     modules.  See :ref:`module-system`.
+
+   Nested data parallelism
+
+     Nested :term:`data parallelism` occurs when a parallel construct
+     is used inside of another parallel construct.  For example, a
+     ``reduce`` might be used inside a function passed to ``map``.
+
+   Parallel width
+
+     A somewhat informal term used to describe the size of an array on
+     which we apply a :term:`SOAC`.  For example, if ``x`` has type
+     ``[1000]i32``, then ``map f x`` has a parallel width of 1000.
+     Intuitively, the "amount of processors" that would be needed to
+     fully exploit the parallelism of the program, although
+     :term:`nested data parallelism` muddles the picture.
+
+   Regular nested data parallelism
+
+     An instance of :term:`nested data parallelism` that is not
+     :term:`irregular`.  Fully supports by any :term:`GPU backend`.
+
+   Size types
+   Size-dependent types
+
+     An umbrella term for the part of Futhark's type system that
+     tracks array sizes.  See :ref:`size-types`.
+
+   Size-lifted type
+
+     A type that may contain internal hidden sizes.  These cannot be
+     array elements, as that might potentially result in an
+     :term:`irregular array`.  See :ref:`typeabbrevs`.
+
+   SOAC
+   Second Order Array Combinator
+
+     A term covering the main parallel building blocks provided by
+     Futhark: functions such as ``map``, ``reduce``, ``scan``, and so
+     on.  They are *second order* because they accept a functional
+     argument, and so permit :term:`nested data parallelism`.
+
+   Uniqueness types
+
+     A somewhat misleading term that describes Futhark's system of
+     allowing :term:`consumption` of values, in the interest of
+     allowing :term:`in-place updates`.  The only place where
+     *uniqueness* truly occurs is in return types, where e.g. the
+     return type of ``copy`` is *unique* to indicate that the result
+     does not :term:`alias<aliasing>` the argument.
+
+   Unknown size
+
+     A size produced by invoking a function whose result type contains
+     an existentially quantified size, such as ``filter``.
diff --git a/docs/index.rst b/docs/index.rst
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -39,6 +39,7 @@
    c-porting-guide.rst
    versus-other-languages.rst
    binary-data-format.rst
+   glossary.rst
 
 .. toctree::
    :caption: Manual Pages
diff --git a/docs/language-reference.rst b/docs/language-reference.rst
--- a/docs/language-reference.rst
+++ b/docs/language-reference.rst
@@ -1245,6 +1245,8 @@
 Adding type annotations to the loop parameter can be used to resolve
 this.
 
+.. _size-coercion:
+
 Size coercion
 ~~~~~~~~~~~~~
 
diff --git a/docs/versus-other-languages.rst b/docs/versus-other-languages.rst
--- a/docs/versus-other-languages.rst
+++ b/docs/versus-other-languages.rst
@@ -19,7 +19,7 @@
 various quirks and unexpected limitations imposed by Futhark. We also
 recommended reading some of the `example programs`_ along with this
 guide.  The guide does *not* cover all Futhark features worth knowing,
-so do also skim :ref:`language-reference`.
+so do also skim :ref:`language-reference` and the :ref:`glossary`.
 
 .. _`example programs`: https://futhark-lang.org/examples.html
 
diff --git a/futhark.cabal b/futhark.cabal
--- a/futhark.cabal
+++ b/futhark.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name:           futhark
-version:        0.24.1
+version:        0.24.2
 synopsis:       An optimising compiler for a functional, array-oriented language.
 
 description:    Futhark is a small programming language designed to be compiled to
@@ -483,7 +483,7 @@
     , filepath >=1.4.1.1
     , free >=4.12.4
     , futhark-data >= 1.1.0.0
-    , futhark-server >= 1.2.2.0
+    , futhark-server >= 1.2.2.1
     , futhark-manifest >= 1.2.0.1
     , githash >=0.1.6.1
     , half >= 0.3
diff --git a/rts/python/memory.py b/rts/python/memory.py
--- a/rts/python/memory.py
+++ b/rts/python/memory.py
@@ -2,44 +2,57 @@
 
 import ctypes as ct
 
+
 def addressOffset(x, offset, bt):
-  return ct.cast(ct.addressof(x.contents)+int(offset), ct.POINTER(bt))
+    return ct.cast(ct.addressof(x.contents) + int(offset), ct.POINTER(bt))
 
+
 def allocateMem(size):
-  return ct.cast((ct.c_byte * max(0,size))(), ct.POINTER(ct.c_byte))
+    return ct.cast((ct.c_byte * max(0, size))(), ct.POINTER(ct.c_byte))
 
+
 # Copy an array if its is not-None.  This is important for treating
 # Numpy arrays as flat memory, but has some overhead.
 def normaliseArray(x):
-  if (x.base is x) or (x.base is None):
-    return x
-  else:
-    return x.copy()
+    if (x.base is x) or (x.base is None):
+        return x
+    else:
+        return x.copy()
 
+
 def unwrapArray(x):
-  return normaliseArray(x).ctypes.data_as(ct.POINTER(ct.c_byte))
+    return normaliseArray(x).ctypes.data_as(ct.POINTER(ct.c_byte))
 
+
 def createArray(x, shape, t):
-  # HACK: np.ctypeslib.as_array may fail if the shape contains zeroes,
-  # for some reason.
-  if any(map(lambda x: x == 0, shape)):
-      return np.ndarray(shape, dtype=t)
-  else:
-      return np.ctypeslib.as_array(x, shape=shape).view(t)
+    # HACK: np.ctypeslib.as_array may fail if the shape contains zeroes,
+    # for some reason.
+    if any(map(lambda x: x == 0, shape)):
+        return np.ndarray(shape, dtype=t)
+    else:
+        return np.ctypeslib.as_array(x, shape=shape).view(t)
 
+
 def indexArray(x, offset, bt):
-  return addressOffset(x, offset*ct.sizeof(bt), bt)[0]
+    return addressOffset(x, offset * ct.sizeof(bt), bt)[0]
 
+
 def writeScalarArray(x, offset, v):
-  ct.memmove(ct.addressof(x.contents)+int(offset)*ct.sizeof(v), ct.addressof(v), ct.sizeof(v))
+    ct.memmove(
+        ct.addressof(x.contents) + int(offset) * ct.sizeof(v),
+        ct.addressof(v),
+        ct.sizeof(v),
+    )
 
+
 # An opaque Futhark value.
 class opaque(object):
-  def __init__(self, desc, *payload):
-    self.data = payload
-    self.desc = desc
+    def __init__(self, desc, *payload):
+        self.data = payload
+        self.desc = desc
 
-  def __repr__(self):
-    return "<opaque Futhark value of type {}>".format(self.desc)
+    def __repr__(self):
+        return "<opaque Futhark value of type {}>".format(self.desc)
+
 
 # End of memory.py.
diff --git a/rts/python/opencl.py b/rts/python/opencl.py
--- a/rts/python/opencl.py
+++ b/rts/python/opencl.py
@@ -4,13 +4,16 @@
 import numpy as np
 import sys
 
-if cl.version.VERSION < (2015,2):
-    raise Exception('Futhark requires at least PyOpenCL version 2015.2.  Installed version is %s.' %
-                    cl.version.VERSION_TEXT)
+if cl.version.VERSION < (2015, 2):
+    raise Exception(
+        "Futhark requires at least PyOpenCL version 2015.2.  Installed version is %s."
+        % cl.version.VERSION_TEXT
+    )
 
+
 def parse_preferred_device(s):
     pref_num = 0
-    if len(s) > 1 and s[0] == '#':
+    if len(s) > 1 and s[0] == "#":
         i = 1
         while i < len(s):
             if not s[i].isdigit():
@@ -24,7 +27,10 @@
     else:
         return (s, 0)
 
-def get_prefered_context(interactive=False, platform_pref=None, device_pref=None):
+
+def get_prefered_context(
+    interactive=False, platform_pref=None, device_pref=None
+):
     if device_pref != None:
         (device_pref, device_num) = parse_preferred_device(device_pref)
     else:
@@ -34,10 +40,16 @@
         return cl.create_some_context(interactive=True)
 
     def blacklisted(p, d):
-        return platform_pref == None and device_pref == None and \
-            p.name == "Apple" and d.name.find("Intel(R) Core(TM)") >= 0
+        return (
+            platform_pref == None
+            and device_pref == None
+            and p.name == "Apple"
+            and d.name.find("Intel(R) Core(TM)") >= 0
+        )
+
     def platform_ok(p):
         return not platform_pref or p.name.find(platform_pref) >= 0
+
     def device_ok(d):
         return not device_pref or d.name.find(device_pref) >= 0
 
@@ -47,49 +59,67 @@
         if not platform_ok(p):
             continue
         for d in p.get_devices():
-            if blacklisted(p,d) or not device_ok(d):
+            if blacklisted(p, d) or not device_ok(d):
                 continue
             if device_matches == device_num:
                 return cl.Context(devices=[d])
             else:
                 device_matches += 1
-    raise Exception('No OpenCL platform and device matching constraints found.')
+    raise Exception(
+        "No OpenCL platform and device matching constraints found."
+    )
 
+
 def param_assignment(s):
-    name, value = s.split('=')
+    name, value = s.split("=")
     return (name, int(value))
 
+
 def check_types(self, required_types):
-    if 'f64' in required_types:
-        if self.device.get_info(cl.device_info.PREFERRED_VECTOR_WIDTH_DOUBLE) == 0:
-            raise Exception('Program uses double-precision floats, but this is not supported on chosen device: %s' % self.device.name)
+    if "f64" in required_types:
+        if (
+            self.device.get_info(cl.device_info.PREFERRED_VECTOR_WIDTH_DOUBLE)
+            == 0
+        ):
+            raise Exception(
+                "Program uses double-precision floats, but this is not supported on chosen device: %s"
+                % self.device.name
+            )
 
+
 def apply_size_heuristics(self, size_heuristics, sizes):
-    for (platform_name, device_type, size, valuef) in size_heuristics:
-        if sizes[size] == None \
-           and self.platform.name.find(platform_name) >= 0 \
-           and (self.device.type & device_type) == device_type:
-               sizes[size] = valuef(self.device)
+    for platform_name, device_type, size, valuef in size_heuristics:
+        if (
+            sizes[size] == None
+            and self.platform.name.find(platform_name) >= 0
+            and (self.device.type & device_type) == device_type
+        ):
+            sizes[size] = valuef(self.device)
     return sizes
 
-def initialise_opencl_object(self,
-                             program_src='',
-                             build_options=[],
-                             command_queue=None,
-                             interactive=False,
-                             platform_pref=None,
-                             device_pref=None,
-                             default_group_size=None,
-                             default_num_groups=None,
-                             default_tile_size=None,
-                             default_reg_tile_size=None,
-                             default_threshold=None,
-                             size_heuristics=[],
-                             required_types=[],
-                             all_sizes={},
-                             user_sizes={}):
+
+def initialise_opencl_object(
+    self,
+    program_src="",
+    build_options=[],
+    command_queue=None,
+    interactive=False,
+    platform_pref=None,
+    device_pref=None,
+    default_group_size=None,
+    default_num_groups=None,
+    default_tile_size=None,
+    default_reg_tile_size=None,
+    default_threshold=None,
+    size_heuristics=[],
+    required_types=[],
+    all_sizes={},
+    user_sizes={},
+):
     if command_queue is None:
-        self.ctx = get_prefered_context(interactive, platform_pref, device_pref)
+        self.ctx = get_prefered_context(
+            interactive, platform_pref, device_pref
+        )
         self.queue = cl.CommandQueue(self.ctx)
     else:
         self.ctx = command_queue.context
@@ -115,140 +145,181 @@
     self.max_local_memory -= 4
 
     # See comment in rts/c/opencl.h.
-    if self.platform.name.find('NVIDIA CUDA') >= 0:
+    if self.platform.name.find("NVIDIA CUDA") >= 0:
         self.max_local_memory -= 12
-    elif self.platform.name.find('AMD') >= 0:
+    elif self.platform.name.find("AMD") >= 0:
         self.max_local_memory -= 16
 
     self.free_list = {}
 
     self.global_failure = self.pool.allocate(np.int32().itemsize)
-    cl.enqueue_fill_buffer(self.queue, self.global_failure, np.int32(-1), 0, np.int32().itemsize)
-    self.global_failure_args = self.pool.allocate(np.int64().itemsize *
-                                                  (self.global_failure_args_max+1))
+    cl.enqueue_fill_buffer(
+        self.queue, self.global_failure, np.int32(-1), 0, np.int32().itemsize
+    )
+    self.global_failure_args = self.pool.allocate(
+        np.int64().itemsize * (self.global_failure_args_max + 1)
+    )
     self.failure_is_an_option = np.int32(0)
 
-    if 'default_group_size' in sizes:
-        default_group_size = sizes['default_group_size']
-        del sizes['default_group_size']
+    if "default_group_size" in sizes:
+        default_group_size = sizes["default_group_size"]
+        del sizes["default_group_size"]
 
-    if 'default_num_groups' in sizes:
-        default_num_groups = sizes['default_num_groups']
-        del sizes['default_num_groups']
+    if "default_num_groups" in sizes:
+        default_num_groups = sizes["default_num_groups"]
+        del sizes["default_num_groups"]
 
-    if 'default_tile_size' in sizes:
-        default_tile_size = sizes['default_tile_size']
-        del sizes['default_tile_size']
+    if "default_tile_size" in sizes:
+        default_tile_size = sizes["default_tile_size"]
+        del sizes["default_tile_size"]
 
-    if 'default_reg_tile_size' in sizes:
-        default_reg_tile_size = sizes['default_reg_tile_size']
-        del sizes['default_reg_tile_size']
+    if "default_reg_tile_size" in sizes:
+        default_reg_tile_size = sizes["default_reg_tile_size"]
+        del sizes["default_reg_tile_size"]
 
-    if 'default_threshold' in sizes:
-        default_threshold = sizes['default_threshold']
-        del sizes['default_threshold']
+    if "default_threshold" in sizes:
+        default_threshold = sizes["default_threshold"]
+        del sizes["default_threshold"]
 
     default_group_size_set = default_group_size != None
     default_tile_size_set = default_tile_size != None
-    default_sizes = apply_size_heuristics(self, size_heuristics,
-                                          {'group_size': default_group_size,
-                                           'tile_size': default_tile_size,
-                                           'reg_tile_size': default_reg_tile_size,
-                                           'num_groups': default_num_groups,
-                                           'lockstep_width': None,
-                                           'threshold': default_threshold})
-    default_group_size = default_sizes['group_size']
-    default_num_groups = default_sizes['num_groups']
-    default_threshold = default_sizes['threshold']
-    default_tile_size = default_sizes['tile_size']
-    default_reg_tile_size = default_sizes['reg_tile_size']
-    lockstep_width = default_sizes['lockstep_width']
+    default_sizes = apply_size_heuristics(
+        self,
+        size_heuristics,
+        {
+            "group_size": default_group_size,
+            "tile_size": default_tile_size,
+            "reg_tile_size": default_reg_tile_size,
+            "num_groups": default_num_groups,
+            "lockstep_width": None,
+            "threshold": default_threshold,
+        },
+    )
+    default_group_size = default_sizes["group_size"]
+    default_num_groups = default_sizes["num_groups"]
+    default_threshold = default_sizes["threshold"]
+    default_tile_size = default_sizes["tile_size"]
+    default_reg_tile_size = default_sizes["reg_tile_size"]
+    lockstep_width = default_sizes["lockstep_width"]
 
     if default_group_size > max_group_size:
         if default_group_size_set:
-            sys.stderr.write('Note: Device limits group size to {} (down from {})\n'.
-                             format(max_tile_size, default_group_size))
+            sys.stderr.write(
+                "Note: Device limits group size to {} (down from {})\n".format(
+                    max_tile_size, default_group_size
+                )
+            )
         default_group_size = max_group_size
 
     if default_tile_size > max_tile_size:
         if default_tile_size_set:
-            sys.stderr.write('Note: Device limits tile size to {} (down from {})\n'.
-                             format(max_tile_size, default_tile_size))
+            sys.stderr.write(
+                "Note: Device limits tile size to {} (down from {})\n".format(
+                    max_tile_size, default_tile_size
+                )
+            )
         default_tile_size = max_tile_size
 
-    for (k,v) in user_sizes.items():
+    for k, v in user_sizes.items():
         if k in all_sizes:
-            all_sizes[k]['value'] = v
+            all_sizes[k]["value"] = v
         else:
-            raise Exception('Unknown size: {}\nKnown sizes: {}'.format(k, ' '.join(all_sizes.keys())))
+            raise Exception(
+                "Unknown size: {}\nKnown sizes: {}".format(
+                    k, " ".join(all_sizes.keys())
+                )
+            )
 
     self.sizes = {}
-    for (k,v) in all_sizes.items():
-        if v['class'] == 'group_size':
+    for k, v in all_sizes.items():
+        if v["class"] == "group_size":
             max_value = max_group_size
             default_value = default_group_size
-        elif v['class'] == 'num_groups':
-            max_value = max_group_size # Intentional!
+        elif v["class"] == "num_groups":
+            max_value = max_group_size  # Intentional!
             default_value = default_num_groups
-        elif v['class'] == 'tile_size':
+        elif v["class"] == "tile_size":
             max_value = max_tile_size
             default_value = default_tile_size
-        elif v['class'] == 'reg_tile_size':
+        elif v["class"] == "reg_tile_size":
             max_value = None
             default_value = default_reg_tile_size
-        elif v['class'].startswith('threshold'):
+        elif v["class"].startswith("threshold"):
             max_value = None
             default_value = default_threshold
         else:
             # Bespoke sizes have no limit or default.
             max_value = None
-        if v['value'] == None:
+        if v["value"] == None:
             self.sizes[k] = default_value
-        elif max_value != None and v['value'] > max_value:
-            sys.stderr.write('Note: Device limits {} to {} (down from {}\n'.
-                             format(k, max_value, v['value']))
+        elif max_value != None and v["value"] > max_value:
+            sys.stderr.write(
+                "Note: Device limits {} to {} (down from {}\n".format(
+                    k, max_value, v["value"]
+                )
+            )
             self.sizes[k] = max_value
         else:
-            self.sizes[k] = v['value']
+            self.sizes[k] = v["value"]
 
     # XXX: we perform only a subset of z-encoding here.  Really, the
     # compiler should provide us with the variables to which
     # parameters are mapped.
-    if (len(program_src) >= 0):
+    if len(program_src) >= 0:
         build_options += ["-DLOCKSTEP_WIDTH={}".format(lockstep_width)]
 
-        build_options += ["-D{}={}".format('max_group_size', max_group_size)]
+        build_options += ["-D{}={}".format("max_group_size", max_group_size)]
 
-        build_options += ["-D{}={}".format(s.
-                                           replace('z', 'zz').
-                                           replace('.', 'zi').
-                                           replace('#', 'zh').
-                                           replace('\'', 'zq'),
-                                           v) for (s,v) in self.sizes.items()]
+        build_options += [
+            "-D{}={}".format(
+                s.replace("z", "zz")
+                .replace(".", "zi")
+                .replace("#", "zh")
+                .replace("'", "zq"),
+                v,
+            )
+            for (s, v) in self.sizes.items()
+        ]
 
-        if (self.platform.name == 'Oclgrind'):
-            build_options += ['-DEMULATE_F16']
+        if self.platform.name == "Oclgrind":
+            build_options += ["-DEMULATE_F16"]
 
         return cl.Program(self.ctx, program_src).build(build_options)
 
+
 def opencl_alloc(self, min_size, tag):
     min_size = 1 if min_size == 0 else min_size
     assert min_size > 0
     return self.pool.allocate(min_size)
 
+
 def opencl_free_all(self):
     self.pool.free_held()
 
+
 def sync(self):
     failure = np.empty(1, dtype=np.int32)
     cl.enqueue_copy(self.queue, failure, self.global_failure, is_blocking=True)
     self.failure_is_an_option = np.int32(0)
     if failure[0] >= 0:
         # Reset failure information.
-        cl.enqueue_fill_buffer(self.queue, self.global_failure, np.int32(-1), 0, np.int32().itemsize)
+        cl.enqueue_fill_buffer(
+            self.queue,
+            self.global_failure,
+            np.int32(-1),
+            0,
+            np.int32().itemsize,
+        )
 
         # Read failure args.
-        failure_args = np.empty(self.global_failure_args_max+1, dtype=np.int64)
-        cl.enqueue_copy(self.queue, failure_args, self.global_failure_args, is_blocking=True)
+        failure_args = np.empty(
+            self.global_failure_args_max + 1, dtype=np.int64
+        )
+        cl.enqueue_copy(
+            self.queue,
+            failure_args,
+            self.global_failure_args,
+            is_blocking=True,
+        )
 
         raise Exception(self.failure_msgs[failure[0]].format(*failure_args))
diff --git a/rts/python/panic.py b/rts/python/panic.py
--- a/rts/python/panic.py
+++ b/rts/python/panic.py
@@ -1,9 +1,11 @@
 # Start of panic.py.
 
+
 def panic(exitcode, fmt, *args):
-    sys.stderr.write('%s: ' % sys.argv[0])
+    sys.stderr.write("%s: " % sys.argv[0])
     sys.stderr.write(fmt % args)
-    sys.stderr.write('\n')
+    sys.stderr.write("\n")
     sys.exit(exitcode)
+
 
 # End of panic.py.
diff --git a/rts/python/scalar.py b/rts/python/scalar.py
--- a/rts/python/scalar.py
+++ b/rts/python/scalar.py
@@ -4,228 +4,291 @@
 import math
 import struct
 
+
 def intlit(t, x):
-  if t == np.int8:
-    return np.int8(x)
-  elif t == np.int16:
-    return np.int16(x)
-  elif t == np.int32:
-    return np.int32(x)
-  else:
-    return np.int64(x)
+    if t == np.int8:
+        return np.int8(x)
+    elif t == np.int16:
+        return np.int16(x)
+    elif t == np.int32:
+        return np.int32(x)
+    else:
+        return np.int64(x)
 
+
 def signed(x):
-  if type(x) == np.uint8:
-    return np.int8(x)
-  elif type(x) == np.uint16:
-    return np.int16(x)
-  elif type(x) == np.uint32:
-    return np.int32(x)
-  else:
-    return np.int64(x)
+    if type(x) == np.uint8:
+        return np.int8(x)
+    elif type(x) == np.uint16:
+        return np.int16(x)
+    elif type(x) == np.uint32:
+        return np.int32(x)
+    else:
+        return np.int64(x)
 
+
 def unsigned(x):
-  if type(x) == np.int8:
-    return np.uint8(x)
-  elif type(x) == np.int16:
-    return np.uint16(x)
-  elif type(x) == np.int32:
-    return np.uint32(x)
-  else:
-    return np.uint64(x)
+    if type(x) == np.int8:
+        return np.uint8(x)
+    elif type(x) == np.int16:
+        return np.uint16(x)
+    elif type(x) == np.int32:
+        return np.uint32(x)
+    else:
+        return np.uint64(x)
 
-def shlN(x,y):
-  return x << y
 
-def ashrN(x,y):
-  return x >> y
+def shlN(x, y):
+    return x << y
 
+
+def ashrN(x, y):
+    return x >> y
+
+
 # Python is so slow that we just make all the unsafe operations safe,
 # always.
 
-def sdivN(x,y):
-  if y == 0:
-    return intlit(type(x), 0)
-  else:
-    return x // y
 
-def sdiv_upN(x,y):
-  if y == 0:
-    return intlit(type(x), 0)
-  else:
-    return (x+y-intlit(type(x), 1)) // y
+def sdivN(x, y):
+    if y == 0:
+        return intlit(type(x), 0)
+    else:
+        return x // y
 
-def smodN(x,y):
-  if y == 0:
-    return intlit(type(x), 0)
-  else:
-    return x % y
 
-def udivN(x,y):
-  if y == 0:
-    return intlit(type(x), 0)
-  else:
-    return signed(unsigned(x) // unsigned(y))
+def sdiv_upN(x, y):
+    if y == 0:
+        return intlit(type(x), 0)
+    else:
+        return (x + y - intlit(type(x), 1)) // y
 
-def udiv_upN(x,y):
-  if y == 0:
-    return intlit(type(x), 0)
-  else:
-    return signed((unsigned(x)+unsigned(y)-unsigned(intlit(type(x),1))) // unsigned(y))
 
-def umodN(x,y):
-  if y == 0:
-    return intlit(type(x), 0)
-  else:
-    return signed(unsigned(x) % unsigned(y))
+def smodN(x, y):
+    if y == 0:
+        return intlit(type(x), 0)
+    else:
+        return x % y
 
-def squotN(x,y):
-  if y == 0:
-    return intlit(type(x), 0)
-  else:
-    return np.floor_divide(np.abs(x), np.abs(y)) * np.sign(x) * np.sign(y)
 
-def sremN(x,y):
-  if y == 0:
-    return intlit(type(x), 0)
-  else:
-    return np.remainder(np.abs(x), np.abs(y)) * np.sign(x)
+def udivN(x, y):
+    if y == 0:
+        return intlit(type(x), 0)
+    else:
+        return signed(unsigned(x) // unsigned(y))
 
-def sminN(x,y):
-  return min(x,y)
 
-def smaxN(x,y):
-  return max(x,y)
+def udiv_upN(x, y):
+    if y == 0:
+        return intlit(type(x), 0)
+    else:
+        return signed(
+            (unsigned(x) + unsigned(y) - unsigned(intlit(type(x), 1)))
+            // unsigned(y)
+        )
 
-def uminN(x,y):
-  return signed(min(unsigned(x),unsigned(y)))
 
-def umaxN(x,y):
-  return signed(max(unsigned(x),unsigned(y)))
+def umodN(x, y):
+    if y == 0:
+        return intlit(type(x), 0)
+    else:
+        return signed(unsigned(x) % unsigned(y))
 
-def fminN(x,y):
-  return np.fmin(x,y)
 
-def fmaxN(x,y):
-  return np.fmax(x,y)
+def squotN(x, y):
+    if y == 0:
+        return intlit(type(x), 0)
+    else:
+        return np.floor_divide(np.abs(x), np.abs(y)) * np.sign(x) * np.sign(y)
 
-def powN(x,y):
-  return x ** y
 
-def fpowN(x,y):
-  return x ** y
+def sremN(x, y):
+    if y == 0:
+        return intlit(type(x), 0)
+    else:
+        return np.remainder(np.abs(x), np.abs(y)) * np.sign(x)
 
-def sleN(x,y):
-  return x <= y
 
-def sltN(x,y):
-  return x < y
+def sminN(x, y):
+    return min(x, y)
 
-def uleN(x,y):
-  return unsigned(x) <= unsigned(y)
 
-def ultN(x,y):
-  return unsigned(x) < unsigned(y)
+def smaxN(x, y):
+    return max(x, y)
 
-def lshr8(x,y):
-  return np.int8(np.uint8(x) >> np.uint8(y))
 
-def lshr16(x,y):
-  return np.int16(np.uint16(x) >> np.uint16(y))
+def uminN(x, y):
+    return signed(min(unsigned(x), unsigned(y)))
 
-def lshr32(x,y):
-  return np.int32(np.uint32(x) >> np.uint32(y))
 
-def lshr64(x,y):
-  return np.int64(np.uint64(x) >> np.uint64(y))
+def umaxN(x, y):
+    return signed(max(unsigned(x), unsigned(y)))
 
+
+def fminN(x, y):
+    return np.fmin(x, y)
+
+
+def fmaxN(x, y):
+    return np.fmax(x, y)
+
+
+def powN(x, y):
+    return x**y
+
+
+def fpowN(x, y):
+    return x**y
+
+
+def sleN(x, y):
+    return x <= y
+
+
+def sltN(x, y):
+    return x < y
+
+
+def uleN(x, y):
+    return unsigned(x) <= unsigned(y)
+
+
+def ultN(x, y):
+    return unsigned(x) < unsigned(y)
+
+
+def lshr8(x, y):
+    return np.int8(np.uint8(x) >> np.uint8(y))
+
+
+def lshr16(x, y):
+    return np.int16(np.uint16(x) >> np.uint16(y))
+
+
+def lshr32(x, y):
+    return np.int32(np.uint32(x) >> np.uint32(y))
+
+
+def lshr64(x, y):
+    return np.int64(np.uint64(x) >> np.uint64(y))
+
+
 def sext_T_i8(x):
-  return np.int8(x)
+    return np.int8(x)
 
+
 def sext_T_i16(x):
-  return np.int16(x)
+    return np.int16(x)
 
+
 def sext_T_i32(x):
-  return np.int32(x)
+    return np.int32(x)
 
+
 def sext_T_i64(x):
-  return np.int64(x)
+    return np.int64(x)
 
+
 def itob_T_bool(x):
-  return bool(x)
+    return bool(x)
 
+
 def btoi_bool_i8(x):
-  return np.int8(x)
+    return np.int8(x)
 
+
 def btoi_bool_i16(x):
-  return np.int16(x)
+    return np.int16(x)
 
+
 def btoi_bool_i32(x):
-  return np.int32(x)
+    return np.int32(x)
 
+
 def btoi_bool_i64(x):
-  return np.int64(x)
+    return np.int64(x)
 
+
 def ftob_T_bool(x):
-  return bool(x)
+    return bool(x)
 
+
 def btof_bool_f16(x):
-  return np.float16(x)
+    return np.float16(x)
 
+
 def btof_bool_f32(x):
-  return np.float32(x)
+    return np.float32(x)
 
+
 def btof_bool_f64(x):
-  return np.float64(x)
+    return np.float64(x)
 
+
 def zext_i8_i8(x):
-  return np.int8(np.uint8(x))
+    return np.int8(np.uint8(x))
 
+
 def zext_i8_i16(x):
-  return np.int16(np.uint8(x))
+    return np.int16(np.uint8(x))
 
+
 def zext_i8_i32(x):
-  return np.int32(np.uint8(x))
+    return np.int32(np.uint8(x))
 
+
 def zext_i8_i64(x):
-  return np.int64(np.uint8(x))
+    return np.int64(np.uint8(x))
 
+
 def zext_i16_i8(x):
-  return np.int8(np.uint16(x))
+    return np.int8(np.uint16(x))
 
+
 def zext_i16_i16(x):
-  return np.int16(np.uint16(x))
+    return np.int16(np.uint16(x))
 
+
 def zext_i16_i32(x):
-  return np.int32(np.uint16(x))
+    return np.int32(np.uint16(x))
 
+
 def zext_i16_i64(x):
-  return np.int64(np.uint16(x))
+    return np.int64(np.uint16(x))
 
+
 def zext_i32_i8(x):
-  return np.int8(np.uint32(x))
+    return np.int8(np.uint32(x))
 
+
 def zext_i32_i16(x):
-  return np.int16(np.uint32(x))
+    return np.int16(np.uint32(x))
 
+
 def zext_i32_i32(x):
-  return np.int32(np.uint32(x))
+    return np.int32(np.uint32(x))
 
+
 def zext_i32_i64(x):
-  return np.int64(np.uint32(x))
+    return np.int64(np.uint32(x))
 
+
 def zext_i64_i8(x):
-  return np.int8(np.uint64(x))
+    return np.int8(np.uint64(x))
 
+
 def zext_i64_i16(x):
-  return np.int16(np.uint64(x))
+    return np.int16(np.uint64(x))
 
+
 def zext_i64_i32(x):
-  return np.int32(np.uint64(x))
+    return np.int32(np.uint64(x))
 
+
 def zext_i64_i64(x):
-  return np.int64(np.uint64(x))
+    return np.int64(np.uint64(x))
 
+
 sdiv8 = sdiv16 = sdiv32 = sdiv64 = sdivN
 sdiv_up8 = sdiv1_up6 = sdiv_up32 = sdiv_up64 = sdiv_upN
 sdiv_safe8 = sdiv1_safe6 = sdiv_safe32 = sdiv_safe64 = sdivN
@@ -264,497 +327,691 @@
 itob_i8_bool = itob_i16_bool = itob_i32_bool = itob_i64_bool = itob_T_bool
 ftob_f16_bool = ftob_f32_bool = ftob_f64_bool = ftob_T_bool
 
+
 def clz_T(x):
-  n = np.int32(0)
-  bits = x.itemsize * 8
-  for i in range(bits):
-    if x < 0:
-      break
-    n += 1
-    x <<= np.int8(1)
-  return n
+    n = np.int32(0)
+    bits = x.itemsize * 8
+    for i in range(bits):
+        if x < 0:
+            break
+        n += 1
+        x <<= np.int8(1)
+    return n
 
+
 def ctz_T(x):
-  n = np.int32(0)
-  bits = x.itemsize * 8
-  for i in range(bits):
-    if (x & 1) == 1:
-      break
-    n += 1
-    x >>= np.int8(1)
-  return n
+    n = np.int32(0)
+    bits = x.itemsize * 8
+    for i in range(bits):
+        if (x & 1) == 1:
+            break
+        n += 1
+        x >>= np.int8(1)
+    return n
 
+
 def popc_T(x):
-  c = np.int32(0)
-  while x != 0:
-    x &= x - np.int8(1)
-    c += np.int8(1)
-  return c
+    c = np.int32(0)
+    while x != 0:
+        x &= x - np.int8(1)
+        c += np.int8(1)
+    return c
 
+
 futhark_popc8 = futhark_popc16 = futhark_popc32 = futhark_popc64 = popc_T
 futhark_clzz8 = futhark_clzz16 = futhark_clzz32 = futhark_clzz64 = clz_T
 futhark_ctzz8 = futhark_ctzz16 = futhark_ctzz32 = futhark_ctzz64 = ctz_T
 
+
 def ssignum(x):
-  return np.sign(x)
+    return np.sign(x)
 
+
 def usignum(x):
-  if x < 0:
-    return ssignum(-x)
-  else:
-    return ssignum(x)
+    if x < 0:
+        return ssignum(-x)
+    else:
+        return ssignum(x)
 
+
 def sitofp_T_f32(x):
-  return np.float32(x)
+    return np.float32(x)
+
+
 sitofp_i8_f32 = sitofp_i16_f32 = sitofp_i32_f32 = sitofp_i64_f32 = sitofp_T_f32
 
+
 def sitofp_T_f64(x):
-  return np.float64(x)
+    return np.float64(x)
+
+
 sitofp_i8_f64 = sitofp_i16_f64 = sitofp_i32_f64 = sitofp_i64_f64 = sitofp_T_f64
 
+
 def uitofp_T_f32(x):
-  return np.float32(unsigned(x))
+    return np.float32(unsigned(x))
+
+
 uitofp_i8_f32 = uitofp_i16_f32 = uitofp_i32_f32 = uitofp_i64_f32 = uitofp_T_f32
 
+
 def uitofp_T_f64(x):
-  return np.float64(unsigned(x))
+    return np.float64(unsigned(x))
+
+
 uitofp_i8_f64 = uitofp_i16_f64 = uitofp_i32_f64 = uitofp_i64_f64 = uitofp_T_f64
 
+
 def fptosi_T_i8(x):
-  if np.isnan(x) or np.isinf(x):
-    return np.int8(0)
-  else:
-    return np.int8(np.trunc(x))
+    if np.isnan(x) or np.isinf(x):
+        return np.int8(0)
+    else:
+        return np.int8(np.trunc(x))
+
+
 fptosi_f16_i8 = fptosi_f32_i8 = fptosi_f64_i8 = fptosi_T_i8
 
+
 def fptosi_T_i16(x):
-  if np.isnan(x) or np.isinf(x):
-    return np.int16(0)
-  else:
-    return np.int16(np.trunc(x))
+    if np.isnan(x) or np.isinf(x):
+        return np.int16(0)
+    else:
+        return np.int16(np.trunc(x))
+
+
 fptosi_f16_i16 = fptosi_f32_i16 = fptosi_f64_i16 = fptosi_T_i16
 
+
 def fptosi_T_i32(x):
-  if np.isnan(x) or np.isinf(x):
-    return np.int32(0)
-  else:
-    return np.int32(np.trunc(x))
+    if np.isnan(x) or np.isinf(x):
+        return np.int32(0)
+    else:
+        return np.int32(np.trunc(x))
+
+
 fptosi_f16_i32 = fptosi_f32_i32 = fptosi_f64_i32 = fptosi_T_i32
 
+
 def fptosi_T_i64(x):
-  if np.isnan(x) or np.isinf(x):
-    return np.int64(0)
-  else:
-    return np.int64(np.trunc(x))
+    if np.isnan(x) or np.isinf(x):
+        return np.int64(0)
+    else:
+        return np.int64(np.trunc(x))
+
+
 fptosi_f16_i64 = fptosi_f32_i64 = fptosi_f64_i64 = fptosi_T_i64
 
+
 def fptoui_T_i8(x):
-  if np.isnan(x) or np.isinf(x):
-    return np.int8(0)
-  else:
-    return np.int8(np.trunc(x))
+    if np.isnan(x) or np.isinf(x):
+        return np.int8(0)
+    else:
+        return np.int8(np.trunc(x))
+
+
 fptoui_f16_i8 = fptoui_f32_i8 = fptoui_f64_i8 = fptoui_T_i8
 
+
 def fptoui_T_i16(x):
-  if np.isnan(x) or np.isinf(x):
-    return np.int16(0)
-  else:
-    return np.int16(np.trunc(x))
+    if np.isnan(x) or np.isinf(x):
+        return np.int16(0)
+    else:
+        return np.int16(np.trunc(x))
+
+
 fptoui_f16_i16 = fptoui_f32_i16 = fptoui_f64_i16 = fptoui_T_i16
 
+
 def fptoui_T_i32(x):
-  if np.isnan(x) or np.isinf(x):
-    return np.int32(0)
-  else:
-    return np.int32(np.trunc(x))
+    if np.isnan(x) or np.isinf(x):
+        return np.int32(0)
+    else:
+        return np.int32(np.trunc(x))
+
+
 fptoui_f16_i32 = fptoui_f32_i32 = fptoui_f64_i32 = fptoui_T_i32
 
+
 def fptoui_T_i64(x):
-  if np.isnan(x) or np.isinf(x):
-    return np.int64(0)
-  else:
-    return np.int64(np.trunc(x))
+    if np.isnan(x) or np.isinf(x):
+        return np.int64(0)
+    else:
+        return np.int64(np.trunc(x))
+
+
 fptoui_f16_i64 = fptoui_f32_i64 = fptoui_f64_i64 = fptoui_T_i64
 
+
 def fpconv_f16_f32(x):
-  return np.float32(x)
+    return np.float32(x)
 
+
 def fpconv_f16_f64(x):
-  return np.float64(x)
+    return np.float64(x)
 
+
 def fpconv_f32_f16(x):
-  return np.float16(x)
+    return np.float16(x)
 
+
 def fpconv_f32_f64(x):
-  return np.float64(x)
+    return np.float64(x)
 
+
 def fpconv_f64_f16(x):
-  return np.float16(x)
+    return np.float16(x)
 
+
 def fpconv_f64_f32(x):
-  return np.float32(x)
+    return np.float32(x)
 
+
 def futhark_umul_hi8(a, b):
-  return np.int8((np.uint64(np.uint8(a))*np.uint64(np.uint8(b))) >> np.uint64(8))
+    return np.int8(
+        (np.uint64(np.uint8(a)) * np.uint64(np.uint8(b))) >> np.uint64(8)
+    )
 
+
 def futhark_umul_hi16(a, b):
-  return np.int16((np.uint64(np.uint16(a))*np.uint64(np.uint16(b))) >> np.uint64(16))
+    return np.int16(
+        (np.uint64(np.uint16(a)) * np.uint64(np.uint16(b))) >> np.uint64(16)
+    )
 
+
 def futhark_umul_hi32(a, b):
-  return np.int32((np.uint64(np.uint32(a))*np.uint64(np.uint32(b))) >> np.uint64(32))
+    return np.int32(
+        (np.uint64(np.uint32(a)) * np.uint64(np.uint32(b))) >> np.uint64(32)
+    )
 
+
 def futhark_umul_hi64(a, b):
-  return np.int64(np.uint64(int(np.uint64(a))*int(np.uint64(b)) >> 64))
+    return np.int64(np.uint64(int(np.uint64(a)) * int(np.uint64(b)) >> 64))
 
+
 def futhark_smul_hi8(a, b):
-  return np.int8((np.int64(a)*np.int64(b)) >> np.int64(8))
+    return np.int8((np.int64(a) * np.int64(b)) >> np.int64(8))
 
+
 def futhark_smul_hi16(a, b):
-  return np.int16((np.int64(a)*np.int64(b)) >> np.int64(16))
+    return np.int16((np.int64(a) * np.int64(b)) >> np.int64(16))
 
+
 def futhark_smul_hi32(a, b):
-  return np.int32((np.int64(a)*np.int64(b)) >> np.int64(32))
+    return np.int32((np.int64(a) * np.int64(b)) >> np.int64(32))
 
+
 def futhark_smul_hi64(a, b):
-  return np.int64(int(a)*int(b) >> 64)
+    return np.int64(int(a) * int(b) >> 64)
 
-def futhark_umad_hi8(a, b, c):  return futhark_umul_hi8(a,b) + c
-def futhark_umad_hi16(a, b, c): return futhark_umul_hi16(a,b) + c
-def futhark_umad_hi32(a, b, c): return futhark_umul_hi32(a,b) + c
-def futhark_umad_hi64(a, b, c): return futhark_umul_hi64(a,b) + c
-def futhark_smad_hi8(a, b, c):  return futhark_smul_hi8(a,b) + c
-def futhark_smad_hi16(a, b, c): return futhark_smul_hi16(a,b) + c
-def futhark_smad_hi32(a, b, c): return futhark_smul_hi32(a,b) + c
-def futhark_smad_hi64(a, b, c): return futhark_smul_hi64(a,b) + c
 
+def futhark_umad_hi8(a, b, c):
+    return futhark_umul_hi8(a, b) + c
+
+
+def futhark_umad_hi16(a, b, c):
+    return futhark_umul_hi16(a, b) + c
+
+
+def futhark_umad_hi32(a, b, c):
+    return futhark_umul_hi32(a, b) + c
+
+
+def futhark_umad_hi64(a, b, c):
+    return futhark_umul_hi64(a, b) + c
+
+
+def futhark_smad_hi8(a, b, c):
+    return futhark_smul_hi8(a, b) + c
+
+
+def futhark_smad_hi16(a, b, c):
+    return futhark_smul_hi16(a, b) + c
+
+
+def futhark_smad_hi32(a, b, c):
+    return futhark_smul_hi32(a, b) + c
+
+
+def futhark_smad_hi64(a, b, c):
+    return futhark_smul_hi64(a, b) + c
+
+
 def futhark_log64(x):
-  return np.float64(np.log(x))
+    return np.float64(np.log(x))
 
+
 def futhark_log2_64(x):
-  return np.float64(np.log2(x))
+    return np.float64(np.log2(x))
 
+
 def futhark_log10_64(x):
-  return np.float64(np.log10(x))
+    return np.float64(np.log10(x))
 
+
 def futhark_log1p_64(x):
-  return np.float64(np.log1p(x))
+    return np.float64(np.log1p(x))
 
+
 def futhark_sqrt64(x):
-  return np.sqrt(x)
+    return np.sqrt(x)
 
+
 def futhark_cbrt64(x):
-  return np.cbrt(x)
+    return np.cbrt(x)
 
+
 def futhark_exp64(x):
-  return np.exp(x)
+    return np.exp(x)
 
+
 def futhark_cos64(x):
-  return np.cos(x)
+    return np.cos(x)
 
+
 def futhark_sin64(x):
-  return np.sin(x)
+    return np.sin(x)
 
+
 def futhark_tan64(x):
-  return np.tan(x)
+    return np.tan(x)
 
+
 def futhark_acos64(x):
-  return np.arccos(x)
+    return np.arccos(x)
 
+
 def futhark_asin64(x):
-  return np.arcsin(x)
+    return np.arcsin(x)
 
+
 def futhark_atan64(x):
-  return np.arctan(x)
+    return np.arctan(x)
 
+
 def futhark_cosh64(x):
-  return np.cosh(x)
+    return np.cosh(x)
 
+
 def futhark_sinh64(x):
-  return np.sinh(x)
+    return np.sinh(x)
 
+
 def futhark_tanh64(x):
-  return np.tanh(x)
+    return np.tanh(x)
 
+
 def futhark_acosh64(x):
-  return np.arccosh(x)
+    return np.arccosh(x)
 
+
 def futhark_asinh64(x):
-  return np.arcsinh(x)
+    return np.arcsinh(x)
 
+
 def futhark_atanh64(x):
-  return np.arctanh(x)
+    return np.arctanh(x)
 
+
 def futhark_atan2_64(x, y):
-  return np.arctan2(x, y)
+    return np.arctan2(x, y)
 
+
 def futhark_hypot64(x, y):
-  return np.hypot(x, y)
+    return np.hypot(x, y)
 
+
 def futhark_gamma64(x):
-  return np.float64(math.gamma(x))
+    return np.float64(math.gamma(x))
 
+
 def futhark_lgamma64(x):
-  return np.float64(math.lgamma(x))
+    return np.float64(math.lgamma(x))
 
+
 def futhark_erf64(x):
-  return np.float64(math.erf(x))
+    return np.float64(math.erf(x))
 
+
 def futhark_erfc64(x):
-  return np.float64(math.erfc(x))
+    return np.float64(math.erfc(x))
 
+
 def futhark_round64(x):
-  return np.round(x)
+    return np.round(x)
 
+
 def futhark_ceil64(x):
-  return np.ceil(x)
+    return np.ceil(x)
 
+
 def futhark_floor64(x):
-  return np.floor(x)
+    return np.floor(x)
 
+
 def futhark_nextafter64(x, y):
-  return np.nextafter(x, y)
+    return np.nextafter(x, y)
 
+
 def futhark_isnan64(x):
-  return np.isnan(x)
+    return np.isnan(x)
 
+
 def futhark_isinf64(x):
-  return np.isinf(x)
+    return np.isinf(x)
 
+
 def futhark_to_bits64(x):
-  s = struct.pack('>d', x)
-  return np.int64(struct.unpack('>q', s)[0])
+    s = struct.pack(">d", x)
+    return np.int64(struct.unpack(">q", s)[0])
 
+
 def futhark_from_bits64(x):
-  s = struct.pack('>q', x)
-  return np.float64(struct.unpack('>d', s)[0])
+    s = struct.pack(">q", x)
+    return np.float64(struct.unpack(">d", s)[0])
 
+
 def futhark_log32(x):
-  return np.float32(np.log(x))
+    return np.float32(np.log(x))
 
+
 def futhark_log2_32(x):
-  return np.float32(np.log2(x))
+    return np.float32(np.log2(x))
 
+
 def futhark_log10_32(x):
-  return np.float32(np.log10(x))
+    return np.float32(np.log10(x))
 
+
 def futhark_log1p_32(x):
-  return np.float32(np.log1p(x))
+    return np.float32(np.log1p(x))
 
+
 def futhark_sqrt32(x):
-  return np.float32(np.sqrt(x))
+    return np.float32(np.sqrt(x))
 
+
 def futhark_cbrt32(x):
-  return np.float32(np.cbrt(x))
+    return np.float32(np.cbrt(x))
 
+
 def futhark_exp32(x):
-  return np.exp(x)
+    return np.exp(x)
 
+
 def futhark_cos32(x):
-  return np.cos(x)
+    return np.cos(x)
 
+
 def futhark_sin32(x):
-  return np.sin(x)
+    return np.sin(x)
 
+
 def futhark_tan32(x):
-  return np.tan(x)
+    return np.tan(x)
 
+
 def futhark_acos32(x):
-  return np.arccos(x)
+    return np.arccos(x)
 
+
 def futhark_asin32(x):
-  return np.arcsin(x)
+    return np.arcsin(x)
 
+
 def futhark_atan32(x):
-  return np.arctan(x)
+    return np.arctan(x)
 
+
 def futhark_cosh32(x):
-  return np.cosh(x)
+    return np.cosh(x)
 
+
 def futhark_sinh32(x):
-  return np.sinh(x)
+    return np.sinh(x)
 
+
 def futhark_tanh32(x):
-  return np.tanh(x)
+    return np.tanh(x)
 
+
 def futhark_acosh32(x):
-  return np.arccosh(x)
+    return np.arccosh(x)
 
+
 def futhark_asinh32(x):
-  return np.arcsinh(x)
+    return np.arcsinh(x)
 
+
 def futhark_atanh32(x):
-  return np.arctanh(x)
+    return np.arctanh(x)
 
+
 def futhark_atan2_32(x, y):
-  return np.arctan2(x, y)
+    return np.arctan2(x, y)
 
+
 def futhark_hypot32(x, y):
-  return np.hypot(x, y)
+    return np.hypot(x, y)
 
+
 def futhark_gamma32(x):
-  return np.float32(math.gamma(x))
+    return np.float32(math.gamma(x))
 
+
 def futhark_lgamma32(x):
-  return np.float32(math.lgamma(x))
+    return np.float32(math.lgamma(x))
 
+
 def futhark_erf32(x):
-  return np.float32(math.erf(x))
+    return np.float32(math.erf(x))
 
+
 def futhark_erfc32(x):
-  return np.float32(math.erfc(x))
+    return np.float32(math.erfc(x))
 
+
 def futhark_round32(x):
-  return np.round(x)
+    return np.round(x)
 
+
 def futhark_ceil32(x):
-  return np.ceil(x)
+    return np.ceil(x)
 
+
 def futhark_floor32(x):
-  return np.floor(x)
+    return np.floor(x)
 
+
 def futhark_nextafter32(x, y):
-  return np.nextafter(x, y)
+    return np.nextafter(x, y)
 
+
 def futhark_isnan32(x):
-  return np.isnan(x)
+    return np.isnan(x)
 
+
 def futhark_isinf32(x):
-  return np.isinf(x)
+    return np.isinf(x)
 
+
 def futhark_to_bits32(x):
-  s = struct.pack('>f', x)
-  return np.int32(struct.unpack('>l', s)[0])
+    s = struct.pack(">f", x)
+    return np.int32(struct.unpack(">l", s)[0])
 
+
 def futhark_from_bits32(x):
-  s = struct.pack('>l', x)
-  return np.float32(struct.unpack('>f', s)[0])
+    s = struct.pack(">l", x)
+    return np.float32(struct.unpack(">f", s)[0])
 
+
 def futhark_log16(x):
-  return np.float16(np.log(x))
+    return np.float16(np.log(x))
 
+
 def futhark_log2_16(x):
-  return np.float16(np.log2(x))
+    return np.float16(np.log2(x))
 
+
 def futhark_log10_16(x):
-  return np.float16(np.log10(x))
+    return np.float16(np.log10(x))
 
+
 def futhark_log1p_16(x):
-  return np.float16(np.log1p(x))
+    return np.float16(np.log1p(x))
 
+
 def futhark_sqrt16(x):
-  return np.float16(np.sqrt(x))
+    return np.float16(np.sqrt(x))
 
+
 def futhark_cbrt16(x):
-  return np.float16(np.cbrt(x))
+    return np.float16(np.cbrt(x))
 
+
 def futhark_exp16(x):
-  return np.exp(x)
+    return np.exp(x)
 
+
 def futhark_cos16(x):
-  return np.cos(x)
+    return np.cos(x)
 
+
 def futhark_sin16(x):
-  return np.sin(x)
+    return np.sin(x)
 
+
 def futhark_tan16(x):
-  return np.tan(x)
+    return np.tan(x)
 
+
 def futhark_acos16(x):
-  return np.arccos(x)
+    return np.arccos(x)
 
+
 def futhark_asin16(x):
-  return np.arcsin(x)
+    return np.arcsin(x)
 
+
 def futhark_atan16(x):
-  return np.arctan(x)
+    return np.arctan(x)
 
+
 def futhark_cosh16(x):
-  return np.cosh(x)
+    return np.cosh(x)
 
+
 def futhark_sinh16(x):
-  return np.sinh(x)
+    return np.sinh(x)
 
+
 def futhark_tanh16(x):
-  return np.tanh(x)
+    return np.tanh(x)
 
+
 def futhark_acosh16(x):
-  return np.arccosh(x)
+    return np.arccosh(x)
 
+
 def futhark_asinh16(x):
-  return np.arcsinh(x)
+    return np.arcsinh(x)
 
+
 def futhark_atanh16(x):
-  return np.arctanh(x)
+    return np.arctanh(x)
 
+
 def futhark_atan2_16(x, y):
-  return np.arctan2(x, y)
+    return np.arctan2(x, y)
 
+
 def futhark_hypot16(x, y):
-  return np.hypot(x, y)
+    return np.hypot(x, y)
 
+
 def futhark_gamma16(x):
-  return np.float16(math.gamma(x))
+    return np.float16(math.gamma(x))
 
+
 def futhark_lgamma16(x):
-  return np.float16(math.lgamma(x))
+    return np.float16(math.lgamma(x))
 
+
 def futhark_erf16(x):
-  return np.float16(math.erf(x))
+    return np.float16(math.erf(x))
 
+
 def futhark_erfc16(x):
-  return np.float16(math.erfc(x))
+    return np.float16(math.erfc(x))
 
+
 def futhark_round16(x):
-  return np.round(x)
+    return np.round(x)
 
+
 def futhark_ceil16(x):
-  return np.ceil(x)
+    return np.ceil(x)
 
+
 def futhark_floor16(x):
-  return np.floor(x)
+    return np.floor(x)
 
+
 def futhark_nextafter16(x, y):
-  return np.nextafter(x, y)
+    return np.nextafter(x, y)
 
+
 def futhark_isnan16(x):
-  return np.isnan(x)
+    return np.isnan(x)
 
+
 def futhark_isinf16(x):
-  return np.isinf(x)
+    return np.isinf(x)
 
+
 def futhark_to_bits16(x):
-  s = struct.pack('>e', x)
-  return np.int16(struct.unpack('>H', s)[0])
+    s = struct.pack(">e", x)
+    return np.int16(struct.unpack(">H", s)[0])
 
+
 def futhark_from_bits16(x):
-  s = struct.pack('>H', np.uint16(x))
-  return np.float16(struct.unpack('>e', s)[0])
+    s = struct.pack(">H", np.uint16(x))
+    return np.float16(struct.unpack(">e", s)[0])
 
+
 def futhark_lerp16(v0, v1, t):
-  return v0 + (v1-v0)*t
+    return v0 + (v1 - v0) * t
 
+
 def futhark_lerp32(v0, v1, t):
-  return v0 + (v1-v0)*t
+    return v0 + (v1 - v0) * t
 
+
 def futhark_lerp64(v0, v1, t):
-  return v0 + (v1-v0)*t
+    return v0 + (v1 - v0) * t
 
+
 def futhark_mad16(a, b, c):
-  return a * b + c
+    return a * b + c
 
+
 def futhark_mad32(a, b, c):
-  return a * b + c
+    return a * b + c
 
+
 def futhark_mad64(a, b, c):
-  return a * b + c
+    return a * b + c
 
+
 def futhark_fma16(a, b, c):
-  return a * b + c
+    return a * b + c
 
+
 def futhark_fma32(a, b, c):
-  return a * b + c
+    return a * b + c
 
+
 def futhark_fma64(a, b, c):
-  return a * b + c
+    return a * b + c
+
 
 # End of scalar.py.
diff --git a/rts/python/server.py b/rts/python/server.py
--- a/rts/python/server.py
+++ b/rts/python/server.py
@@ -2,8 +2,9 @@
 
 import sys
 import time
-import shlex # For string splitting
+import shlex  # For string splitting
 
+
 class Server:
     def __init__(self, ctx):
         self._ctx = ctx
@@ -17,21 +18,21 @@
         if i < len(args):
             return args[i]
         else:
-            raise self.Failure('Insufficient command args')
+            raise self.Failure("Insufficient command args")
 
     def _get_entry_point(self, entry):
         if entry in self._ctx.entry_points:
             return self._ctx.entry_points[entry]
         else:
-            raise self.Failure('Unknown entry point: %s' % entry)
+            raise self.Failure("Unknown entry point: %s" % entry)
 
     def _check_var(self, vname):
         if not vname in self._vars:
-            raise self.Failure('Unknown variable: %s' % vname)
+            raise self.Failure("Unknown variable: %s" % vname)
 
     def _check_new_var(self, vname):
         if vname in self._vars:
-            raise self.Failure('Variable already exists: %s' % vname)
+            raise self.Failure("Variable already exists: %s" % vname)
 
     def _get_var(self, vname):
         self._check_var(vname)
@@ -71,27 +72,27 @@
         exp_len = 1 + num_outs + num_ins
 
         if len(args) != exp_len:
-            raise self.Failure('Invalid argument count, expected %d' % exp_len)
+            raise self.Failure("Invalid argument count, expected %d" % exp_len)
 
-        out_vnames = args[1:num_outs+1]
+        out_vnames = args[1 : num_outs + 1]
 
         for out_vname in out_vnames:
             self._check_new_var(out_vname)
 
-        in_vnames = args[1+num_outs:]
-        ins = [ self._get_var(in_vname) for in_vname in in_vnames ]
+        in_vnames = args[1 + num_outs :]
+        ins = [self._get_var(in_vname) for in_vname in in_vnames]
 
         try:
             (runtime, vals) = getattr(self._ctx, entry_fname)(*ins)
         except Exception as e:
             raise self.Failure(str(e))
 
-        print('runtime: %d' % runtime)
+        print("runtime: %d" % runtime)
 
         if num_outs == 1:
             self._vars[out_vnames[0]] = vals
         else:
-            for (out_vname, val) in zip(out_vnames, vals):
+            for out_vname, val in zip(out_vnames, vals):
                 self._vars[out_vname] = val
 
     def _store_val(self, f, value):
@@ -101,7 +102,12 @@
         if isinstance(value, opaque):
             for component in value.data:
                 self._store_val(f, component)
-        elif isinstance(value, np.number) or isinstance(value, bool) or isinstance(value, np.bool_) or isinstance(value, np.ndarray):
+        elif (
+            isinstance(value, np.number)
+            or isinstance(value, bool)
+            or isinstance(value, np.bool_)
+            or isinstance(value, np.ndarray)
+        ):
             # Ordinary NumPy value.
             f.write(construct_binary_value(value))
         else:
@@ -111,7 +117,7 @@
     def _cmd_store(self, args):
         fname = self._get_arg(args, 0)
 
-        with open(fname, 'wb') as f:
+        with open(fname, "wb") as f:
             for i in range(1, len(args)):
                 self._store_val(f, self._get_var(args[i]))
 
@@ -126,12 +132,12 @@
 
     def _cmd_restore(self, args):
         if len(args) % 2 == 0:
-            raise self.Failure('Invalid argument count')
+            raise self.Failure("Invalid argument count")
 
         fname = args[0]
         args = args[1:]
 
-        with open(fname, 'rb') as f:
+        with open(fname, "rb") as f:
             reader = ReaderInput(f)
             while args != []:
                 vname = args[0]
@@ -139,18 +145,19 @@
                 args = args[2:]
 
                 if vname in self._vars:
-                    raise self.Failure('Variable already exists: %s' % vname)
+                    raise self.Failure("Variable already exists: %s" % vname)
 
                 try:
                     self._vars[vname] = self._restore_val(reader, typename)
                 except ValueError:
-                    raise self.Failure('Failed to restore variable %s.\n'
-                                       'Possibly malformed data in %s.\n'
-                                       % (vname, fname))
+                    raise self.Failure(
+                        "Failed to restore variable %s.\n"
+                        "Possibly malformed data in %s.\n" % (vname, fname)
+                    )
 
             skip_spaces(reader)
-            if reader.get_char() != b'':
-                raise self.Failure('Expected EOF after reading values')
+            if reader.get_char() != b"":
+                raise self.Failure("Expected EOF after reading values")
 
     def _cmd_types(self, args):
         for k in self._ctx.opaques.keys():
@@ -160,47 +167,49 @@
         for k in self._ctx.entry_points.keys():
             print(k)
 
-    _commands = { 'inputs': _cmd_inputs,
-                  'outputs': _cmd_outputs,
-                  'call': _cmd_call,
-                  'restore': _cmd_restore,
-                  'store': _cmd_store,
-                  'free': _cmd_free,
-                  'rename': _cmd_rename,
-                  'clear': _cmd_dummy,
-                  'pause_profiling': _cmd_dummy,
-                  'unpause_profiling': _cmd_dummy,
-                  'report': _cmd_dummy,
-                  'types': _cmd_types,
-                  'entry_points': _cmd_entry_points,
-                 }
+    _commands = {
+        "inputs": _cmd_inputs,
+        "outputs": _cmd_outputs,
+        "call": _cmd_call,
+        "restore": _cmd_restore,
+        "store": _cmd_store,
+        "free": _cmd_free,
+        "rename": _cmd_rename,
+        "clear": _cmd_dummy,
+        "pause_profiling": _cmd_dummy,
+        "unpause_profiling": _cmd_dummy,
+        "report": _cmd_dummy,
+        "types": _cmd_types,
+        "entry_points": _cmd_entry_points,
+    }
 
     def _process_line(self, line):
         lex = shlex.shlex(line)
         lex.quotes = '"'
         lex.whitespace_split = True
-        lex.commenters = ''
+        lex.commenters = ""
         words = list(lex)
         if words == []:
-            raise self.Failure('Empty line')
+            raise self.Failure("Empty line")
         else:
             cmd = words[0]
             args = words[1:]
             if cmd in self._commands:
                 self._commands[cmd](self, args)
             else:
-                raise self.Failure('Unknown command: %s' % cmd)
+                raise self.Failure("Unknown command: %s" % cmd)
 
     def run(self):
         while True:
-            print('%%% OK', flush=True)
+            print("%%% OK", flush=True)
             line = sys.stdin.readline()
-            if line == '':
+            if line == "":
                 return
             try:
                 self._process_line(line)
             except self.Failure as e:
-                print('%%% FAILURE')
+                print("%%% FAILURE")
                 print(e.msg)
+
 
 # End of server.py
diff --git a/rts/python/tuning.py b/rts/python/tuning.py
--- a/rts/python/tuning.py
+++ b/rts/python/tuning.py
@@ -1,9 +1,11 @@
 # Start of tuning.py
 
+
 def read_tuning_file(kvs, f):
     for line in f.read().splitlines():
-        size, value = line.split('=')
+        size, value = line.split("=")
         kvs[size] = int(value)
     return kvs
+
 
 # End of tuning.py.
diff --git a/rts/python/values.py b/rts/python/values.py
--- a/rts/python/values.py
+++ b/rts/python/values.py
@@ -9,6 +9,7 @@
 import struct
 import sys
 
+
 class ReaderInput:
     def __init__(self, f):
         self.f = f
@@ -27,7 +28,7 @@
 
     def get_chars(self, n):
         n1 = min(n, len(self.lookahead_buffer))
-        s = b''.join(self.lookahead_buffer[:n1])
+        s = b"".join(self.lookahead_buffer[:n1])
         self.lookahead_buffer = self.lookahead_buffer[n1:]
         n2 = n - n1
         if n2 > 0:
@@ -40,24 +41,26 @@
             self.unget_char(c)
         return c
 
+
 def skip_spaces(f):
     c = f.get_char()
     while c != None:
         if c.isspace():
             c = f.get_char()
-        elif c == b'-':
-          # May be line comment.
-          if f.peek_char() == b'-':
-            # Yes, line comment. Skip to end of line.
-            while (c != b'\n' and c != None):
-              c = f.get_char()
-          else:
-            break
+        elif c == b"-":
+            # May be line comment.
+            if f.peek_char() == b"-":
+                # Yes, line comment. Skip to end of line.
+                while c != b"\n" and c != None:
+                    c = f.get_char()
+            else:
+                break
         else:
-          break
+            break
     if c:
         f.unget_char(c)
 
+
 def parse_specific_char(f, expected):
     got = f.get_char()
     if got != expected:
@@ -65,12 +68,13 @@
         raise ValueError
     return True
 
+
 def parse_specific_string(f, s):
     # This funky mess is intended, and is caused by the fact that if `type(b) ==
     # bytes` then `type(b[0]) == int`, but we need to match each element with a
     # `bytes`, so therefore we make each character an array element
-    b = s.encode('utf8')
-    bs = [b[i:i+1] for i in range(len(b))]
+    b = s.encode("utf8")
+    bs = [b[i : i + 1] for i in range(len(b))]
     read = []
     try:
         for c in bs:
@@ -82,24 +86,27 @@
             f.unget_char(c)
         raise
 
+
 def optional(p, *args):
     try:
         return p(*args)
     except ValueError:
         return None
 
+
 def optional_specific_string(f, s):
     c = f.peek_char()
     # This funky mess is intended, and is caused by the fact that if `type(b) ==
     # bytes` then `type(b[0]) == int`, but we need to match each element with a
     # `bytes`, so therefore we make each character an array element
-    b = s.encode('utf8')
-    bs = [b[i:i+1] for i in range(len(b))]
+    b = s.encode("utf8")
+    bs = [b[i : i + 1] for i in range(len(b))]
     if c == bs[0]:
         return parse_specific_string(f, s)
     else:
         return False
 
+
 def sepBy(p, sep, *args):
     elems = []
     x = optional(p, *args)
@@ -110,34 +117,36 @@
             elems += [x]
     return elems
 
+
 # Assumes '0x' has already been read
 def parse_hex_int(f):
-    s = b''
+    s = b""
     c = f.get_char()
     while c != None:
-        if c in b'01234556789ABCDEFabcdef':
+        if c in b"01234556789ABCDEFabcdef":
             s += c
             c = f.get_char()
-        elif c == b'_':
-            c = f.get_char() # skip _
+        elif c == b"_":
+            c = f.get_char()  # skip _
         else:
             f.unget_char(c)
             break
-    return str(int(s, 16)).encode('utf8') # ugh
+    return str(int(s, 16)).encode("utf8")  # ugh
 
+
 def parse_int(f):
-    s = b''
+    s = b""
     c = f.get_char()
-    if c == b'0' and f.peek_char() in b'xX':
-        c = f.get_char() # skip X
+    if c == b"0" and f.peek_char() in b"xX":
+        c = f.get_char()  # skip X
         return parse_hex_int(f)
     else:
         while c != None:
             if c.isdigit():
                 s += c
                 c = f.get_char()
-            elif c == b'_':
-                c = f.get_char() # skip _
+            elif c == b"_":
+                c = f.get_char()  # skip _
             else:
                 f.unget_char(c)
                 break
@@ -145,72 +154,92 @@
             raise ValueError
         return s
 
+
 def parse_int_signed(f):
-    s = b''
+    s = b""
     c = f.get_char()
 
-    if c == b'-' and f.peek_char().isdigit():
-      return c + parse_int(f)
+    if c == b"-" and f.peek_char().isdigit():
+        return c + parse_int(f)
     else:
-      if c != b'+':
-          f.unget_char(c)
-      return parse_int(f)
+        if c != b"+":
+            f.unget_char(c)
+        return parse_int(f)
 
+
 def read_str_comma(f):
     skip_spaces(f)
-    parse_specific_char(f, b',')
-    return b','
+    parse_specific_char(f, b",")
+    return b","
 
+
 def read_str_int(f, s):
     skip_spaces(f)
     x = int(parse_int_signed(f))
     optional_specific_string(f, s)
     return x
 
+
 def read_str_uint(f, s):
     skip_spaces(f)
     x = int(parse_int(f))
     optional_specific_string(f, s)
     return x
 
+
 def read_str_i8(f):
-    return np.int8(read_str_int(f, 'i8'))
+    return np.int8(read_str_int(f, "i8"))
+
+
 def read_str_i16(f):
-    return np.int16(read_str_int(f, 'i16'))
+    return np.int16(read_str_int(f, "i16"))
+
+
 def read_str_i32(f):
-    return np.int32(read_str_int(f, 'i32'))
+    return np.int32(read_str_int(f, "i32"))
+
+
 def read_str_i64(f):
-    return np.int64(read_str_int(f, 'i64'))
+    return np.int64(read_str_int(f, "i64"))
 
+
 def read_str_u8(f):
-    return np.uint8(read_str_int(f, 'u8'))
+    return np.uint8(read_str_int(f, "u8"))
+
+
 def read_str_u16(f):
-    return np.uint16(read_str_int(f, 'u16'))
+    return np.uint16(read_str_int(f, "u16"))
+
+
 def read_str_u32(f):
-    return np.uint32(read_str_int(f, 'u32'))
+    return np.uint32(read_str_int(f, "u32"))
+
+
 def read_str_u64(f):
-    return np.uint64(read_str_int(f, 'u64'))
+    return np.uint64(read_str_int(f, "u64"))
 
+
 def read_char(f):
     skip_spaces(f)
-    parse_specific_char(f, b'\'')
+    parse_specific_char(f, b"'")
     c = f.get_char()
-    parse_specific_char(f, b'\'')
+    parse_specific_char(f, b"'")
     return c
 
+
 def read_str_hex_float(f, sign):
     int_part = parse_hex_int(f)
-    parse_specific_char(f, b'.')
+    parse_specific_char(f, b".")
     frac_part = parse_hex_int(f)
-    parse_specific_char(f, b'p')
+    parse_specific_char(f, b"p")
     exponent = parse_int(f)
 
     int_val = int(int_part, 16)
     frac_val = float(int(frac_part, 16)) / (16 ** len(frac_part))
     exp_val = int(exponent)
 
-    total_val = (int_val + frac_val) * (2.0 ** exp_val)
-    if sign == b'-':
+    total_val = (int_val + frac_val) * (2.0**exp_val)
+    if sign == b"-":
         total_val = -1 * total_val
 
     return float(total_val)
@@ -219,15 +248,15 @@
 def read_str_decimal(f):
     skip_spaces(f)
     c = f.get_char()
-    if (c == b'-'):
-      sign = b'-'
+    if c == b"-":
+        sign = b"-"
     else:
-      f.unget_char(c)
-      sign = b''
+        f.unget_char(c)
+        sign = b""
 
     # Check for hexadecimal float
     c = f.get_char()
-    if (c == '0' and (f.peek_char() in ['x', 'X'])):
+    if c == "0" and (f.peek_char() in ["x", "X"]):
         f.get_char()
         return read_str_hex_float(f, sign)
     else:
@@ -235,132 +264,143 @@
 
     bef = optional(parse_int, f)
     if bef == None:
-        bef = b'0'
-        parse_specific_char(f, b'.')
+        bef = b"0"
+        parse_specific_char(f, b".")
         aft = parse_int(f)
-    elif optional(parse_specific_char, f, b'.'):
+    elif optional(parse_specific_char, f, b"."):
         aft = parse_int(f)
     else:
-        aft = b'0'
-    if (optional(parse_specific_char, f, b'E') or
-        optional(parse_specific_char, f, b'e')):
+        aft = b"0"
+    if optional(parse_specific_char, f, b"E") or optional(
+        parse_specific_char, f, b"e"
+    ):
         expt = parse_int_signed(f)
     else:
-        expt = b'0'
-    return float(sign + bef + b'.' + aft + b'E' + expt)
+        expt = b"0"
+    return float(sign + bef + b"." + aft + b"E" + expt)
 
+
 def read_str_f16(f):
     skip_spaces(f)
     try:
-        parse_specific_string(f, 'f16.nan')
+        parse_specific_string(f, "f16.nan")
         return np.float32(np.nan)
     except ValueError:
         try:
-            parse_specific_string(f, 'f16.inf')
+            parse_specific_string(f, "f16.inf")
             return np.float32(np.inf)
         except ValueError:
             try:
-               parse_specific_string(f, '-f16.inf')
-               return np.float32(-np.inf)
+                parse_specific_string(f, "-f16.inf")
+                return np.float32(-np.inf)
             except ValueError:
-               x = read_str_decimal(f)
-               optional_specific_string(f, 'f16')
-               return x
+                x = read_str_decimal(f)
+                optional_specific_string(f, "f16")
+                return x
 
+
 def read_str_f32(f):
     skip_spaces(f)
     try:
-        parse_specific_string(f, 'f32.nan')
+        parse_specific_string(f, "f32.nan")
         return np.float32(np.nan)
     except ValueError:
         try:
-            parse_specific_string(f, 'f32.inf')
+            parse_specific_string(f, "f32.inf")
             return np.float32(np.inf)
         except ValueError:
             try:
-               parse_specific_string(f, '-f32.inf')
-               return np.float32(-np.inf)
+                parse_specific_string(f, "-f32.inf")
+                return np.float32(-np.inf)
             except ValueError:
-               x = read_str_decimal(f)
-               optional_specific_string(f, 'f32')
-               return x
+                x = read_str_decimal(f)
+                optional_specific_string(f, "f32")
+                return x
 
+
 def read_str_f64(f):
     skip_spaces(f)
     try:
-        parse_specific_string(f, 'f64.nan')
+        parse_specific_string(f, "f64.nan")
         return np.float64(np.nan)
     except ValueError:
         try:
-            parse_specific_string(f, 'f64.inf')
+            parse_specific_string(f, "f64.inf")
             return np.float64(np.inf)
         except ValueError:
             try:
-               parse_specific_string(f, '-f64.inf')
-               return np.float64(-np.inf)
+                parse_specific_string(f, "-f64.inf")
+                return np.float64(-np.inf)
             except ValueError:
-               x = read_str_decimal(f)
-               optional_specific_string(f, 'f64')
-               return x
+                x = read_str_decimal(f)
+                optional_specific_string(f, "f64")
+                return x
 
+
 def read_str_bool(f):
     skip_spaces(f)
-    if f.peek_char() == b't':
-        parse_specific_string(f, 'true')
+    if f.peek_char() == b"t":
+        parse_specific_string(f, "true")
         return True
-    elif f.peek_char() == b'f':
-        parse_specific_string(f, 'false')
+    elif f.peek_char() == b"f":
+        parse_specific_string(f, "false")
         return False
     else:
         raise ValueError
 
+
 def read_str_empty_array(f, type_name, rank):
-    parse_specific_string(f, 'empty')
-    parse_specific_char(f, b'(')
+    parse_specific_string(f, "empty")
+    parse_specific_char(f, b"(")
     dims = []
     for i in range(rank):
-        parse_specific_string(f, '[')
+        parse_specific_string(f, "[")
         dims += [int(parse_int(f))]
-        parse_specific_string(f, ']')
+        parse_specific_string(f, "]")
     if np.product(dims) != 0:
         raise ValueError
     parse_specific_string(f, type_name)
-    parse_specific_char(f, b')')
+    parse_specific_char(f, b")")
 
     return tuple(dims)
 
+
 def read_str_array_elems(f, elem_reader, type_name, rank):
     skip_spaces(f)
     try:
-        parse_specific_char(f, b'[')
+        parse_specific_char(f, b"[")
     except ValueError:
         return read_str_empty_array(f, type_name, rank)
     else:
         xs = sepBy(elem_reader, read_str_comma, f)
         skip_spaces(f)
-        parse_specific_char(f, b']')
+        parse_specific_char(f, b"]")
         return xs
 
+
 def read_str_array_helper(f, elem_reader, type_name, rank):
     def nested_row_reader(_):
-        return read_str_array_helper(f, elem_reader, type_name, rank-1)
+        return read_str_array_helper(f, elem_reader, type_name, rank - 1)
+
     if rank == 1:
         row_reader = elem_reader
     else:
         row_reader = nested_row_reader
     return read_str_array_elems(f, row_reader, type_name, rank)
 
+
 def expected_array_dims(l, rank):
-  if rank > 1:
-      n = len(l)
-      if n == 0:
-          elem = []
-      else:
-          elem = l[0]
-      return [n] + expected_array_dims(elem, rank-1)
-  else:
-      return [len(l)]
+    if rank > 1:
+        n = len(l)
+        if n == 0:
+            elem = []
+        else:
+            elem = l[0]
+        return [n] + expected_array_dims(elem, rank - 1)
+    else:
+        return [len(l)]
 
+
 def verify_array_dims(l, dims):
     if dims[0] != len(l):
         raise ValueError
@@ -368,6 +408,7 @@
         for x in l:
             verify_array_dims(x, dims[1:])
 
+
 def read_str_array(f, elem_reader, type_name, rank, bt):
     elems = read_str_array_helper(f, elem_reader, type_name, rank)
     if type(elems) == tuple:
@@ -378,6 +419,7 @@
         verify_array_dims(elems, dims)
         return np.array(elems, dtype=bt)
 
+
 ################################################################################
 
 READ_BINARY_VERSION = 2
@@ -385,180 +427,231 @@
 # struct format specified at
 # https://docs.python.org/2/library/struct.html#format-characters
 
+
 def mk_bin_scalar_reader(t):
     def bin_reader(f):
-        fmt = FUTHARK_PRIMTYPES[t]['bin_format']
-        size = FUTHARK_PRIMTYPES[t]['size']
-        tf = FUTHARK_PRIMTYPES[t]['numpy_type']
-        return tf(struct.unpack('<' + fmt, f.get_chars(size))[0])
+        fmt = FUTHARK_PRIMTYPES[t]["bin_format"]
+        size = FUTHARK_PRIMTYPES[t]["size"]
+        tf = FUTHARK_PRIMTYPES[t]["numpy_type"]
+        return tf(struct.unpack("<" + fmt, f.get_chars(size))[0])
+
     return bin_reader
 
-read_bin_i8 = mk_bin_scalar_reader('i8')
-read_bin_i16 = mk_bin_scalar_reader('i16')
-read_bin_i32 = mk_bin_scalar_reader('i32')
-read_bin_i64 = mk_bin_scalar_reader('i64')
 
-read_bin_u8 = mk_bin_scalar_reader('u8')
-read_bin_u16 = mk_bin_scalar_reader('u16')
-read_bin_u32 = mk_bin_scalar_reader('u32')
-read_bin_u64 = mk_bin_scalar_reader('u64')
+read_bin_i8 = mk_bin_scalar_reader("i8")
+read_bin_i16 = mk_bin_scalar_reader("i16")
+read_bin_i32 = mk_bin_scalar_reader("i32")
+read_bin_i64 = mk_bin_scalar_reader("i64")
 
-read_bin_f16 = mk_bin_scalar_reader('f16')
-read_bin_f32 = mk_bin_scalar_reader('f32')
-read_bin_f64 = mk_bin_scalar_reader('f64')
+read_bin_u8 = mk_bin_scalar_reader("u8")
+read_bin_u16 = mk_bin_scalar_reader("u16")
+read_bin_u32 = mk_bin_scalar_reader("u32")
+read_bin_u64 = mk_bin_scalar_reader("u64")
 
-read_bin_bool = mk_bin_scalar_reader('bool')
+read_bin_f16 = mk_bin_scalar_reader("f16")
+read_bin_f32 = mk_bin_scalar_reader("f32")
+read_bin_f64 = mk_bin_scalar_reader("f64")
 
+read_bin_bool = mk_bin_scalar_reader("bool")
+
+
 def read_is_binary(f):
     skip_spaces(f)
     c = f.get_char()
-    if c == b'b':
+    if c == b"b":
         bin_version = read_bin_u8(f)
         if bin_version != READ_BINARY_VERSION:
-            panic(1, "binary-input: File uses version %i, but I only understand version %i.\n",
-                  bin_version, READ_BINARY_VERSION)
+            panic(
+                1,
+                "binary-input: File uses version %i, but I only understand version %i.\n",
+                bin_version,
+                READ_BINARY_VERSION,
+            )
         return True
     else:
         f.unget_char(c)
         return False
 
-FUTHARK_PRIMTYPES = {
-    'i8':  {'binname' : b"  i8",
-            'size' : 1,
-            'bin_reader': read_bin_i8,
-            'str_reader': read_str_i8,
-            'bin_format': 'b',
-            'numpy_type': np.int8 },
 
-    'i16': {'binname' : b" i16",
-            'size' : 2,
-            'bin_reader': read_bin_i16,
-            'str_reader': read_str_i16,
-            'bin_format': 'h',
-            'numpy_type': np.int16 },
-
-    'i32': {'binname' : b" i32",
-            'size' : 4,
-            'bin_reader': read_bin_i32,
-            'str_reader': read_str_i32,
-            'bin_format': 'i',
-            'numpy_type': np.int32 },
-
-    'i64': {'binname' : b" i64",
-            'size' : 8,
-            'bin_reader': read_bin_i64,
-            'str_reader': read_str_i64,
-            'bin_format': 'q',
-            'numpy_type': np.int64},
-
-    'u8':  {'binname' : b"  u8",
-            'size' : 1,
-            'bin_reader': read_bin_u8,
-            'str_reader': read_str_u8,
-            'bin_format': 'B',
-            'numpy_type': np.uint8 },
-
-    'u16': {'binname' : b" u16",
-            'size' : 2,
-            'bin_reader': read_bin_u16,
-            'str_reader': read_str_u16,
-            'bin_format': 'H',
-            'numpy_type': np.uint16 },
-
-    'u32': {'binname' : b" u32",
-            'size' : 4,
-            'bin_reader': read_bin_u32,
-            'str_reader': read_str_u32,
-            'bin_format': 'I',
-            'numpy_type': np.uint32 },
-
-    'u64': {'binname' : b" u64",
-            'size' : 8,
-            'bin_reader': read_bin_u64,
-            'str_reader': read_str_u64,
-            'bin_format': 'Q',
-            'numpy_type': np.uint64 },
-
-    'f16': {'binname' : b" f16",
-            'size' : 2,
-            'bin_reader': read_bin_f16,
-            'str_reader': read_str_f16,
-            'bin_format': 'e',
-            'numpy_type': np.float16 },
-
-    'f32': {'binname' : b" f32",
-            'size' : 4,
-            'bin_reader': read_bin_f32,
-            'str_reader': read_str_f32,
-            'bin_format': 'f',
-            'numpy_type': np.float32 },
-
-    'f64': {'binname' : b" f64",
-            'size' : 8,
-            'bin_reader': read_bin_f64,
-            'str_reader': read_str_f64,
-            'bin_format': 'd',
-            'numpy_type': np.float64 },
-
-    'bool': {'binname' : b"bool",
-             'size' : 1,
-             'bin_reader': read_bin_bool,
-             'str_reader': read_str_bool,
-             'bin_format': 'b',
-             'numpy_type': bool }
+FUTHARK_PRIMTYPES = {
+    "i8": {
+        "binname": b"  i8",
+        "size": 1,
+        "bin_reader": read_bin_i8,
+        "str_reader": read_str_i8,
+        "bin_format": "b",
+        "numpy_type": np.int8,
+    },
+    "i16": {
+        "binname": b" i16",
+        "size": 2,
+        "bin_reader": read_bin_i16,
+        "str_reader": read_str_i16,
+        "bin_format": "h",
+        "numpy_type": np.int16,
+    },
+    "i32": {
+        "binname": b" i32",
+        "size": 4,
+        "bin_reader": read_bin_i32,
+        "str_reader": read_str_i32,
+        "bin_format": "i",
+        "numpy_type": np.int32,
+    },
+    "i64": {
+        "binname": b" i64",
+        "size": 8,
+        "bin_reader": read_bin_i64,
+        "str_reader": read_str_i64,
+        "bin_format": "q",
+        "numpy_type": np.int64,
+    },
+    "u8": {
+        "binname": b"  u8",
+        "size": 1,
+        "bin_reader": read_bin_u8,
+        "str_reader": read_str_u8,
+        "bin_format": "B",
+        "numpy_type": np.uint8,
+    },
+    "u16": {
+        "binname": b" u16",
+        "size": 2,
+        "bin_reader": read_bin_u16,
+        "str_reader": read_str_u16,
+        "bin_format": "H",
+        "numpy_type": np.uint16,
+    },
+    "u32": {
+        "binname": b" u32",
+        "size": 4,
+        "bin_reader": read_bin_u32,
+        "str_reader": read_str_u32,
+        "bin_format": "I",
+        "numpy_type": np.uint32,
+    },
+    "u64": {
+        "binname": b" u64",
+        "size": 8,
+        "bin_reader": read_bin_u64,
+        "str_reader": read_str_u64,
+        "bin_format": "Q",
+        "numpy_type": np.uint64,
+    },
+    "f16": {
+        "binname": b" f16",
+        "size": 2,
+        "bin_reader": read_bin_f16,
+        "str_reader": read_str_f16,
+        "bin_format": "e",
+        "numpy_type": np.float16,
+    },
+    "f32": {
+        "binname": b" f32",
+        "size": 4,
+        "bin_reader": read_bin_f32,
+        "str_reader": read_str_f32,
+        "bin_format": "f",
+        "numpy_type": np.float32,
+    },
+    "f64": {
+        "binname": b" f64",
+        "size": 8,
+        "bin_reader": read_bin_f64,
+        "str_reader": read_str_f64,
+        "bin_format": "d",
+        "numpy_type": np.float64,
+    },
+    "bool": {
+        "binname": b"bool",
+        "size": 1,
+        "bin_reader": read_bin_bool,
+        "str_reader": read_str_bool,
+        "bin_format": "b",
+        "numpy_type": bool,
+    },
 }
 
+
 def read_bin_read_type(f):
     read_binname = f.get_chars(4)
 
-    for (k,v) in FUTHARK_PRIMTYPES.items():
-        if v['binname'] == read_binname:
+    for k, v in FUTHARK_PRIMTYPES.items():
+        if v["binname"] == read_binname:
             return k
     panic(1, "binary-input: Did not recognize the type '%s'.\n", read_binname)
 
+
 def numpy_type_to_type_name(t):
-    for (k,v) in FUTHARK_PRIMTYPES.items():
-        if v['numpy_type'] == t:
+    for k, v in FUTHARK_PRIMTYPES.items():
+        if v["numpy_type"] == t:
             return k
-    raise Exception('Unknown Numpy type: {}'.format(t))
+    raise Exception("Unknown Numpy type: {}".format(t))
 
+
 def read_bin_ensure_scalar(f, expected_type):
-  dims = read_bin_i8(f)
+    dims = read_bin_i8(f)
 
-  if dims != 0:
-      panic(1, "binary-input: Expected scalar (0 dimensions), but got array with %i dimensions.\n", dims)
+    if dims != 0:
+        panic(
+            1,
+            "binary-input: Expected scalar (0 dimensions), but got array with %i dimensions.\n",
+            dims,
+        )
 
-  bin_type = read_bin_read_type(f)
-  if bin_type != expected_type:
-      panic(1, "binary-input: Expected scalar of type %s but got scalar of type %s.\n",
-            expected_type, bin_type)
+    bin_type = read_bin_read_type(f)
+    if bin_type != expected_type:
+        panic(
+            1,
+            "binary-input: Expected scalar of type %s but got scalar of type %s.\n",
+            expected_type,
+            bin_type,
+        )
 
+
 # ------------------------------------------------------------------------------
 # General interface for reading Primitive Futhark Values
 # ------------------------------------------------------------------------------
 
+
 def read_scalar(f, ty):
     if read_is_binary(f):
         read_bin_ensure_scalar(f, ty)
-        return FUTHARK_PRIMTYPES[ty]['bin_reader'](f)
-    return FUTHARK_PRIMTYPES[ty]['str_reader'](f)
+        return FUTHARK_PRIMTYPES[ty]["bin_reader"](f)
+    return FUTHARK_PRIMTYPES[ty]["str_reader"](f)
 
+
 def read_array(f, expected_type, rank):
     if not read_is_binary(f):
-        str_reader = FUTHARK_PRIMTYPES[expected_type]['str_reader']
-        return read_str_array(f, str_reader, expected_type, rank,
-                              FUTHARK_PRIMTYPES[expected_type]['numpy_type'])
+        str_reader = FUTHARK_PRIMTYPES[expected_type]["str_reader"]
+        return read_str_array(
+            f,
+            str_reader,
+            expected_type,
+            rank,
+            FUTHARK_PRIMTYPES[expected_type]["numpy_type"],
+        )
 
     bin_rank = read_bin_u8(f)
 
     if bin_rank != rank:
-        panic(1, "binary-input: Expected %i dimensions, but got array with %i dimensions.\n",
-              rank, bin_rank)
+        panic(
+            1,
+            "binary-input: Expected %i dimensions, but got array with %i dimensions.\n",
+            rank,
+            bin_rank,
+        )
 
     bin_type_enum = read_bin_read_type(f)
     if expected_type != bin_type_enum:
-        panic(1, "binary-input: Expected %iD-array with element type '%s' but got %iD-array with element type '%s'.\n",
-              rank, expected_type, bin_rank, bin_type_enum)
+        panic(
+            1,
+            "binary-input: Expected %iD-array with element type '%s' but got %iD-array with element type '%s'.\n",
+            rank,
+            expected_type,
+            bin_rank,
+            bin_type_enum,
+        )
 
     shape = []
     elem_count = 1
@@ -567,43 +660,51 @@
         elem_count *= bin_size
         shape.append(bin_size)
 
-    bin_fmt = FUTHARK_PRIMTYPES[bin_type_enum]['bin_format']
+    bin_fmt = FUTHARK_PRIMTYPES[bin_type_enum]["bin_format"]
 
     # We first read the expected number of types into a bytestring,
     # then use np.frombuffer.  This is because np.fromfile does not
     # work on things that are insufficiently file-like, like a network
     # stream.
-    bytes = f.get_chars(elem_count * FUTHARK_PRIMTYPES[expected_type]['size'])
-    arr = np.frombuffer(bytes, dtype=FUTHARK_PRIMTYPES[bin_type_enum]['numpy_type'])
+    bytes = f.get_chars(elem_count * FUTHARK_PRIMTYPES[expected_type]["size"])
+    arr = np.frombuffer(
+        bytes, dtype=FUTHARK_PRIMTYPES[bin_type_enum]["numpy_type"]
+    )
     arr.shape = shape
 
     return arr
 
-if sys.version_info >= (3,0):
+
+if sys.version_info >= (3, 0):
     input_reader = ReaderInput(sys.stdin.buffer)
 else:
     input_reader = ReaderInput(sys.stdin)
 
 import re
 
+
 def read_value(type_desc, reader=input_reader):
     """Read a value of the given type.  The type is a string
-representation of the Futhark type."""
-    m = re.match(r'((?:\[\])*)([a-z0-9]+)$', type_desc)
+    representation of the Futhark type."""
+    m = re.match(r"((?:\[\])*)([a-z0-9]+)$", type_desc)
     if m:
-        dims = int(len(m.group(1))/2)
+        dims = int(len(m.group(1)) / 2)
         basetype = m.group(2)
-    assert m and basetype in FUTHARK_PRIMTYPES, "Unknown type: {}".format(type_desc)
+    assert m and basetype in FUTHARK_PRIMTYPES, "Unknown type: {}".format(
+        type_desc
+    )
     if dims > 0:
         return read_array(reader, basetype, dims)
     else:
         return read_scalar(reader, basetype)
 
+
 def end_of_input(entry, f=input_reader):
     skip_spaces(f)
-    if f.get_char() != b'':
-        panic(1, "Expected EOF on stdin after reading input for \"%s\".", entry)
+    if f.get_char() != b"":
+        panic(1, 'Expected EOF on stdin after reading input for "%s".', entry)
 
+
 def write_value_text(v, out=sys.stdout):
     if type(v) == np.uint8:
         out.write("%uu8" % v)
@@ -628,63 +729,71 @@
             out.write("false")
     elif type(v) == np.float16:
         if np.isnan(v):
-            out.write('f16.nan')
+            out.write("f16.nan")
         elif np.isinf(v):
             if v >= 0:
-                out.write('f16.inf')
+                out.write("f16.inf")
             else:
-                out.write('-f16.inf')
+                out.write("-f16.inf")
         else:
             out.write("%.6ff16" % v)
     elif type(v) == np.float32:
         if np.isnan(v):
-            out.write('f32.nan')
+            out.write("f32.nan")
         elif np.isinf(v):
             if v >= 0:
-                out.write('f32.inf')
+                out.write("f32.inf")
             else:
-                out.write('-f32.inf')
+                out.write("-f32.inf")
         else:
             out.write("%.6ff32" % v)
     elif type(v) == np.float64:
         if np.isnan(v):
-            out.write('f64.nan')
+            out.write("f64.nan")
         elif np.isinf(v):
             if v >= 0:
-                out.write('f64.inf')
+                out.write("f64.inf")
             else:
-                out.write('-f64.inf')
+                out.write("-f64.inf")
         else:
             out.write("%.6ff64" % v)
     elif type(v) == np.ndarray:
         if np.product(v.shape) == 0:
             tname = numpy_type_to_type_name(v.dtype)
-            out.write('empty({}{})'.format(''.join(['[{}]'.format(d)
-                                                    for d in v.shape]), tname))
+            out.write(
+                "empty({}{})".format(
+                    "".join(["[{}]".format(d) for d in v.shape]), tname
+                )
+            )
         else:
             first = True
-            out.write('[')
+            out.write("[")
             for x in v:
-                if not first: out.write(', ')
+                if not first:
+                    out.write(", ")
                 first = False
                 write_value(x, out=out)
-            out.write(']')
+            out.write("]")
     else:
         raise Exception("Cannot print value of type {}: {}".format(type(v), v))
 
-type_strs = { np.dtype('int8'): b'  i8',
-              np.dtype('int16'): b' i16',
-              np.dtype('int32'): b' i32',
-              np.dtype('int64'): b' i64',
-              np.dtype('uint8'): b'  u8',
-              np.dtype('uint16'): b' u16',
-              np.dtype('uint32'): b' u32',
-              np.dtype('uint64'): b' u64',
-              np.dtype('float16'): b' f16',
-              np.dtype('float32'): b' f32',
-              np.dtype('float64'): b' f64',
-              np.dtype('bool'): b'bool'}
 
+type_strs = {
+    np.dtype("int8"): b"  i8",
+    np.dtype("int16"): b" i16",
+    np.dtype("int32"): b" i32",
+    np.dtype("int64"): b" i64",
+    np.dtype("uint8"): b"  u8",
+    np.dtype("uint16"): b" u16",
+    np.dtype("uint32"): b" u32",
+    np.dtype("uint64"): b" u64",
+    np.dtype("float16"): b" f16",
+    np.dtype("float32"): b" f32",
+    np.dtype("float64"): b" f64",
+    np.dtype("bool"): b"bool",
+}
+
+
 def construct_binary_value(v):
     t = v.dtype
     shape = v.shape
@@ -695,27 +804,30 @@
 
     num_bytes = 1 + 1 + 1 + 4 + len(shape) * 8 + elems * t.itemsize
     bytes = bytearray(num_bytes)
-    bytes[0] = np.int8(ord('b'))
+    bytes[0] = np.int8(ord("b"))
     bytes[1] = 2
     bytes[2] = np.int8(len(shape))
     bytes[3:7] = type_strs[t]
 
     for i in range(len(shape)):
-        bytes[7+i*8:7+(i+1)*8] = np.int64(shape[i]).tobytes()
+        bytes[7 + i * 8 : 7 + (i + 1) * 8] = np.int64(shape[i]).tobytes()
 
-    bytes[7+len(shape)*8:] = np.ascontiguousarray(v).tobytes()
+    bytes[7 + len(shape) * 8 :] = np.ascontiguousarray(v).tobytes()
 
     return bytes
 
+
 def write_value_binary(v, out=sys.stdout):
-    if sys.version_info >= (3,0):
+    if sys.version_info >= (3, 0):
         out = out.buffer
     out.write(construct_binary_value(v))
 
+
 def write_value(v, out=sys.stdout, binary=False):
     if binary:
         return write_value_binary(v, out=out)
     else:
         return write_value_text(v, out=out)
+
 
 # End of values.py.
diff --git a/src/Futhark/AD/Rev/Loop.hs b/src/Futhark/AD/Rev/Loop.hs
--- a/src/Futhark/AD/Rev/Loop.hs
+++ b/src/Futhark/AD/Rev/Loop.hs
@@ -4,7 +4,7 @@
 
 import Control.Monad
 import Data.Foldable (toList)
-import Data.List (nub, (\\))
+import Data.List ((\\))
 import Data.Map qualified as M
 import Data.Maybe
 import Futhark.AD.Rev.Monad
@@ -16,7 +16,7 @@
 import Futhark.Tools
 import Futhark.Transform.Rename
 import Futhark.Transform.Substitute
-import Futhark.Util (traverseFold)
+import Futhark.Util (nubOrd, traverseFold)
 
 -- | A convenience function to bring the components of a for-loop into
 -- scope and throw an error if the passed 'Exp' is not a for-loop.
@@ -353,7 +353,7 @@
                   loopFree =
                     (namesToList (freeIn loop') \\ loop_var_arrays') \\ mapMaybe getVName loop_vals',
                   loopVars = loop_var_arrays',
-                  loopVals = nub $ mapMaybe getVName loop_vals'
+                  loopVals = nubOrd $ mapMaybe getVName loop_vals'
                 }
 
         renameLoopTape $ M.fromList $ zip (map paramName loop_params) (map paramName loop_params')
diff --git a/src/Futhark/Analysis/Interference.hs b/src/Futhark/Analysis/Interference.hs
--- a/src/Futhark/Analysis/Interference.hs
+++ b/src/Futhark/Analysis/Interference.hs
@@ -3,6 +3,7 @@
 -- | Interference analysis for Futhark programs.
 module Futhark.Analysis.Interference (Graph, analyseProgGPU) where
 
+import Control.Monad
 import Control.Monad.Reader
 import Data.Foldable (toList)
 import Data.Function ((&))
diff --git a/src/Futhark/Analysis/LastUse.hs b/src/Futhark/Analysis/LastUse.hs
--- a/src/Futhark/Analysis/LastUse.hs
+++ b/src/Futhark/Analysis/LastUse.hs
@@ -18,6 +18,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State.Strict
 import Data.Bifunctor (bimap)
diff --git a/src/Futhark/Analysis/MemAlias.hs b/src/Futhark/Analysis/MemAlias.hs
--- a/src/Futhark/Analysis/MemAlias.hs
+++ b/src/Futhark/Analysis/MemAlias.hs
@@ -8,6 +8,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Reader
 import Data.Bifunctor
 import Data.Function ((&))
diff --git a/src/Futhark/Analysis/Metrics.hs b/src/Futhark/Analysis/Metrics.hs
--- a/src/Futhark/Analysis/Metrics.hs
+++ b/src/Futhark/Analysis/Metrics.hs
@@ -15,6 +15,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Writer
 import Data.List (tails)
 import Data.Map.Strict qualified as M
diff --git a/src/Futhark/Bench.hs b/src/Futhark/Bench.hs
--- a/src/Futhark/Bench.hs
+++ b/src/Futhark/Bench.hs
@@ -16,7 +16,9 @@
 where
 
 import Control.Applicative
-import Control.Monad.Except
+import Control.Monad
+import Control.Monad.Except (ExceptT, MonadError (..), liftEither, runExceptT)
+import Control.Monad.IO.Class (MonadIO, liftIO)
 import Data.Aeson qualified as JSON
 import Data.Aeson.Key qualified as JSON
 import Data.Aeson.KeyMap qualified as JSON
diff --git a/src/Futhark/CLI/Bench.hs b/src/Futhark/CLI/Bench.hs
--- a/src/Futhark/CLI/Bench.hs
+++ b/src/Futhark/CLI/Bench.hs
@@ -4,7 +4,7 @@
 import Control.Arrow (first)
 import Control.Exception
 import Control.Monad
-import Control.Monad.Except hiding (throwError)
+import Control.Monad.IO.Class (liftIO)
 import Data.ByteString.Char8 qualified as SBS
 import Data.ByteString.Lazy.Char8 qualified as LBS
 import Data.Either
@@ -404,11 +404,12 @@
       g <- create
       resampled <- liftIO $ resample g [Mean] 70000 vec_runtimes
       let bootstrapCI =
-            ( estPoint boot - confIntLDX (estError boot),
-              estPoint boot + confIntUDX (estError boot)
-            )
-            where
-              boot = head $ bootstrapBCA cl95 vec_runtimes resampled
+            case bootstrapBCA cl95 vec_runtimes resampled of
+              boot : _ ->
+                ( estPoint boot - confIntLDX (estError boot),
+                  estPoint boot + confIntUDX (estError boot)
+                )
+              _ -> (0, 0)
 
       reportResult runtimes bootstrapCI
       -- We throw away the 'errout' because it is almost always
diff --git a/src/Futhark/CLI/Doc.hs b/src/Futhark/CLI/Doc.hs
--- a/src/Futhark/CLI/Doc.hs
+++ b/src/Futhark/CLI/Doc.hs
@@ -4,6 +4,7 @@
 -- | @futhark doc@
 module Futhark.CLI.Doc (main) where
 
+import Control.Monad
 import Control.Monad.State
 import Data.FileEmbed
 import Data.List (nubBy)
diff --git a/src/Futhark/CLI/Eval.hs b/src/Futhark/CLI/Eval.hs
--- a/src/Futhark/CLI/Eval.hs
+++ b/src/Futhark/CLI/Eval.hs
@@ -2,8 +2,9 @@
 
 import Control.Exception
 import Control.Monad
-import Control.Monad.Except
+import Control.Monad.Except (ExceptT, runExceptT, throwError)
 import Control.Monad.Free.Church
+import Control.Monad.IO.Class (MonadIO, liftIO)
 import Data.Map qualified as M
 import Data.Maybe
 import Data.Text qualified as T
diff --git a/src/Futhark/CLI/Literate.hs b/src/Futhark/CLI/Literate.hs
--- a/src/Futhark/CLI/Literate.hs
+++ b/src/Futhark/CLI/Literate.hs
@@ -2,6 +2,7 @@
 module Futhark.CLI.Literate (main) where
 
 import Codec.BMP qualified as BMP
+import Control.Monad
 import Control.Monad.Except
 import Control.Monad.State hiding (State)
 import Data.Bifunctor (first, second)
@@ -530,10 +531,10 @@
     line = T.unwords . map prettyText
 
 imgBlock :: FilePath -> T.Text
-imgBlock f = "\n\n![](" <> T.pack f <> ")\n\n"
+imgBlock f = "![](" <> T.pack f <> ")\n"
 
 videoBlock :: VideoParams -> FilePath -> T.Text
-videoBlock opts f = "\n\n![](" <> T.pack f <> ")" <> opts' <> "\n\n"
+videoBlock opts f = "![](" <> T.pack f <> ")" <> opts' <> "\n"
   where
     opts'
       | all T.null [loop, autoplay] =
@@ -718,14 +719,7 @@
     newFileContents env (Nothing, "eval.txt") $ \resultf -> do
       v <- either nope pure =<< evalExpToGround literateBuiltin (envServer env) e
       liftIO $ T.writeFile resultf $ prettyText v
-  pure $
-    T.unlines
-      [ "",
-        "```",
-        result,
-        "```",
-        ""
-      ]
+  pure $ T.unlines ["```", result, "```"]
   where
     nope t =
       throwError $ "Cannot show value of type " <> prettyText t
@@ -1021,8 +1015,8 @@
 
 processBlock :: Env -> Block -> IO (Failure, T.Text, Files)
 processBlock _ (BlockCode code)
-  | T.null code = pure (Success, "\n", mempty)
-  | otherwise = pure (Success, "\n```futhark\n" <> code <> "```\n\n", mempty)
+  | T.null code = pure (Success, mempty, mempty)
+  | otherwise = pure (Success, "```futhark\n" <> code <> "```\n", mempty)
 processBlock _ (BlockComment pretty) =
   pure (Success, pretty, mempty)
 processBlock env (BlockDirective directive text) = do
@@ -1039,7 +1033,7 @@
   (r, files) <- runScriptM $ processDirective env' directive
   case r of
     Left err -> failed prompt err files
-    Right t -> pure (Success, prompt <> t, files)
+    Right t -> pure (Success, prompt <> "\n" <> t, files)
   where
     failed prompt err files = do
       let message = prettyTextOneLine directive <> " failed:\n" <> err <> "\n"
@@ -1072,7 +1066,7 @@
   (failures, outputs, files) <-
     unzip3 <$> mapM (processBlock env) script
   cleanupImgDir env $ mconcat files
-  pure (foldl' min Success failures, mconcat outputs)
+  pure (foldl' min Success failures, T.intercalate "\n" outputs)
 
 commandLineOptions :: [FunOptDescr Options]
 commandLineOptions =
diff --git a/src/Futhark/CLI/Misc.hs b/src/Futhark/CLI/Misc.hs
--- a/src/Futhark/CLI/Misc.hs
+++ b/src/Futhark/CLI/Misc.hs
@@ -9,6 +9,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.State
 import Data.ByteString.Lazy qualified as BS
 import Data.Function (on)
diff --git a/src/Futhark/CLI/Pkg.hs b/src/Futhark/CLI/Pkg.hs
--- a/src/Futhark/CLI/Pkg.hs
+++ b/src/Futhark/CLI/Pkg.hs
@@ -1,6 +1,7 @@
 -- | @futhark pkg@
 module Futhark.CLI.Pkg (main) where
 
+import Control.Monad
 import Control.Monad.IO.Class
 import Control.Monad.Reader
 import Control.Monad.State
diff --git a/src/Futhark/CLI/REPL.hs b/src/Futhark/CLI/REPL.hs
--- a/src/Futhark/CLI/REPL.hs
+++ b/src/Futhark/CLI/REPL.hs
@@ -20,7 +20,7 @@
 import Futhark.MonadFreshNames
 import Futhark.Util (fancyTerminal)
 import Futhark.Util.Options
-import Futhark.Util.Pretty (AnsiStyle, Color (..), Doc, align, annotate, bgColorDull, bold, brackets, color, docText, docTextForHandle, hardline, pretty, putDoc, putDocLn, unAnnotate, (<+>))
+import Futhark.Util.Pretty (AnsiStyle, Color (..), Doc, align, annotate, bgColorDull, bold, brackets, color, docText, docTextForHandle, hardline, oneLine, pretty, putDoc, putDocLn, unAnnotate, (<+>))
 import Futhark.Version
 import Language.Futhark
 import Language.Futhark.Interpreter qualified as I
@@ -288,7 +288,7 @@
             Left err -> liftIO $ print err
             Right v -> liftIO $ putDoc $ I.prettyValue v <> hardline
       | otherwise -> liftIO $ do
-          T.putStrLn $ "Inferred type of expression: " <> prettyText (typeOf e')
+          putDocLn $ "Inferred type of expression: " <> align (pretty (typeOf e'))
           T.putStrLn $
             "The following types are ambiguous: "
               <> T.intercalate ", " (map (nameToText . toName . typeParamName) tparams)
@@ -382,7 +382,7 @@
 genTypeCommand ::
   (String -> T.Text -> Either SyntaxError a) ->
   (Imports -> VNameSource -> T.Env -> a -> (Warnings, Either T.TypeError b)) ->
-  (b -> String) ->
+  (b -> Doc AnsiStyle) ->
   Command
 genTypeCommand f g h e = do
   prompt <- getPrompt
@@ -392,17 +392,17 @@
       (imports, src, tenv, _) <- getIt
       case snd $ g imports src tenv e' of
         Left err -> liftIO $ putDoc $ T.prettyTypeErrorNoLoc err
-        Right x -> liftIO $ putStrLn $ h x
+        Right x -> liftIO $ putDocLn $ h x
 
 typeCommand :: Command
 typeCommand = genTypeCommand parseExp T.checkExp $ \(ps, e) ->
-  prettyString e
-    <> concatMap ((" " <>) . prettyString) ps
+  oneLine (pretty e)
+    <> foldMap ((" " <>) . pretty) ps
     <> " : "
-    <> prettyString (typeOf e)
+    <> oneLine (pretty (typeOf e))
 
 mtypeCommand :: Command
-mtypeCommand = genTypeCommand parseModExp T.checkModExp $ prettyString . fst
+mtypeCommand = genTypeCommand parseModExp T.checkModExp $ pretty . fst
 
 unbreakCommand :: Command
 unbreakCommand _ = do
diff --git a/src/Futhark/CLI/Run.hs b/src/Futhark/CLI/Run.hs
--- a/src/Futhark/CLI/Run.hs
+++ b/src/Futhark/CLI/Run.hs
@@ -3,8 +3,9 @@
 
 import Control.Exception
 import Control.Monad
-import Control.Monad.Except
+import Control.Monad.Except (ExceptT, runExceptT, throwError)
 import Control.Monad.Free.Church
+import Control.Monad.IO.Class (MonadIO, liftIO)
 import Data.ByteString.Lazy qualified as BS
 import Data.Map qualified as M
 import Data.Maybe
diff --git a/src/Futhark/CLI/Test.hs b/src/Futhark/CLI/Test.hs
--- a/src/Futhark/CLI/Test.hs
+++ b/src/Futhark/CLI/Test.hs
@@ -7,8 +7,10 @@
 import Control.Concurrent
 import Control.Exception
 import Control.Monad
-import Control.Monad.Except hiding (throwError)
+import Control.Monad.Except (ExceptT (..), MonadError, runExceptT, withExceptT)
 import Control.Monad.Except qualified as E
+import Control.Monad.IO.Class (MonadIO, liftIO)
+import Control.Monad.Trans.Class (lift)
 import Data.ByteString qualified as SBS
 import Data.ByteString.Lazy qualified as LBS
 import Data.List (delete, partition)
diff --git a/src/Futhark/CodeGen/Backends/COpenCL/Boilerplate.hs b/src/Futhark/CodeGen/Backends/COpenCL/Boilerplate.hs
--- a/src/Futhark/CodeGen/Backends/COpenCL/Boilerplate.hs
+++ b/src/Futhark/CodeGen/Backends/COpenCL/Boilerplate.hs
@@ -17,6 +17,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.State
 import Data.Map qualified as M
 import Data.Text qualified as T
diff --git a/src/Futhark/CodeGen/Backends/GenericC.hs b/src/Futhark/CodeGen/Backends/GenericC.hs
--- a/src/Futhark/CodeGen/Backends/GenericC.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC.hs
@@ -17,6 +17,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.Bifunctor (second)
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Code.hs b/src/Futhark/CodeGen/Backends/GenericC/Code.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/Code.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/Code.hs
@@ -11,7 +11,8 @@
   )
 where
 
-import Control.Monad.Reader
+import Control.Monad
+import Control.Monad.Reader (asks)
 import Data.Maybe
 import Data.Text qualified as T
 import Futhark.CodeGen.Backends.GenericC.Monad
diff --git a/src/Futhark/CodeGen/Backends/GenericC/EntryPoints.hs b/src/Futhark/CodeGen/Backends/GenericC/EntryPoints.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/EntryPoints.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/EntryPoints.hs
@@ -6,7 +6,8 @@
   )
 where
 
-import Control.Monad.Reader
+import Control.Monad
+import Control.Monad.Reader (asks)
 import Data.Maybe
 import Data.Text qualified as T
 import Futhark.CodeGen.Backends.GenericC.Monad
@@ -133,9 +134,6 @@
 entryName :: Name -> T.Text
 entryName = ("entry_" <>) . escapeName . nameToText
 
-tuningParamsName :: Name -> T.Text
-tuningParamsName = ("tuning_params_for_" <>) . escapeName . nameToText
-
 onEntryPoint ::
   [C.BlockItem] ->
   [Name] ->
@@ -152,7 +150,6 @@
   decl_mem <- declAllocatedMem
 
   entry_point_function_name <- publicName $ entryName ename
-  tuning_params_name <- publicName $ tuningParamsName ename
 
   (inputs', unpack_entry_inputs) <- prepareEntryInputs $ map snd args
   let (entry_point_input_params, entry_point_input_checks) = unzip inputs'
@@ -168,10 +165,6 @@
                                      ($ty:ctx_ty *ctx,
                                       $params:entry_point_output_params,
                                       $params:entry_point_input_params);|]
-
-  headerDecl
-    MiscDecl
-    [C.cedecl|const int* $id:tuning_params_name(void);|]
 
   let checks = catMaybes entry_point_input_checks
       check_input =
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Fun.hs b/src/Futhark/CodeGen/Backends/GenericC/Fun.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/Fun.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/Fun.hs
@@ -9,7 +9,7 @@
   )
 where
 
-import Control.Monad.Reader
+import Control.Monad
 import Futhark.CodeGen.Backends.GenericC.Code
 import Futhark.CodeGen.Backends.GenericC.Monad
 import Futhark.CodeGen.ImpCode
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Monad.hs b/src/Futhark/CodeGen/Backends/GenericC/Monad.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/Monad.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/Monad.hs
@@ -77,7 +77,7 @@
   )
 where
 
-import Control.Monad.Identity
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.Bifunctor (first)
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Types.hs b/src/Futhark/CodeGen/Backends/GenericC/Types.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/Types.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/Types.hs
@@ -8,8 +8,9 @@
   )
 where
 
-import Control.Monad.Reader
-import Control.Monad.State
+import Control.Monad
+import Control.Monad.Reader (asks)
+import Control.Monad.State (gets, modify)
 import Data.Char (isDigit)
 import Data.Map.Strict qualified as M
 import Data.Maybe
diff --git a/src/Futhark/CodeGen/Backends/GenericPython.hs b/src/Futhark/CodeGen/Backends/GenericPython.hs
--- a/src/Futhark/CodeGen/Backends/GenericPython.hs
+++ b/src/Futhark/CodeGen/Backends/GenericPython.hs
@@ -41,7 +41,7 @@
   )
 where
 
-import Control.Monad.Identity
+import Control.Monad
 import Control.Monad.RWS
 import Data.Char (isAlpha, isAlphaNum)
 import Data.Map qualified as M
diff --git a/src/Futhark/CodeGen/Backends/GenericWASM.hs b/src/Futhark/CodeGen/Backends/GenericWASM.hs
--- a/src/Futhark/CodeGen/Backends/GenericWASM.hs
+++ b/src/Futhark/CodeGen/Backends/GenericWASM.hs
@@ -15,13 +15,13 @@
   )
 where
 
-import Data.List (intercalate, nub)
+import Data.List (intercalate)
 import Data.Text qualified as T
 import Futhark.CodeGen.Backends.GenericC qualified as GC
 import Futhark.CodeGen.Backends.SimpleRep (opaqueName)
 import Futhark.CodeGen.ImpCode.Sequential qualified as Imp
 import Futhark.CodeGen.RTS.JavaScript
-import Futhark.Util (showText)
+import Futhark.Util (nubOrd, showText)
 import Language.Futhark.Primitive
 import NeatInterpolation (text)
 
@@ -69,7 +69,7 @@
   where
     arrays = filter isArray typs
     opaques = filter isOpaque typs
-    typs = nub $ concatMap (\jse -> parameters jse ++ ret jse) jses
+    typs = nubOrd $ concatMap (\jse -> parameters jse ++ ret jse) jses
     gfn typ str = "_futhark_" ++ typ ++ "_" ++ baseType str ++ "_" ++ show (dim str) ++ "d"
 
 javascriptWrapper :: [JSEntryPoint] -> T.Text
@@ -101,7 +101,7 @@
     ]
   where
     arrays = filter isArray typs
-    typs = nub $ concatMap (\jse -> parameters jse ++ ret jse) entryPoints
+    typs = nubOrd $ concatMap (\jse -> parameters jse ++ ret jse) entryPoints
 
 constructor :: [JSEntryPoint] -> T.Text
 constructor jses =
diff --git a/src/Futhark/CodeGen/ImpGen.hs b/src/Futhark/CodeGen/ImpGen.hs
--- a/src/Futhark/CodeGen/ImpGen.hs
+++ b/src/Futhark/CodeGen/ImpGen.hs
@@ -120,9 +120,9 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
-import Control.Monad.Writer
 import Control.Parallel.Strategies
 import Data.Bifunctor (first)
 import Data.DList qualified as DL
diff --git a/src/Futhark/CodeGen/ImpGen/GPU.hs b/src/Futhark/CodeGen/ImpGen/GPU.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU.hs
@@ -11,7 +11,7 @@
   )
 where
 
-import Control.Monad.Except
+import Control.Monad
 import Data.List (foldl')
 import Data.Map qualified as M
 import Data.Maybe
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/Base.hs b/src/Futhark/CodeGen/ImpGen/GPU/Base.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/Base.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/Base.hs
@@ -46,7 +46,7 @@
   )
 where
 
-import Control.Monad.Except
+import Control.Monad
 import Data.List (foldl')
 import Data.Map.Strict qualified as M
 import Data.Maybe
@@ -1061,7 +1061,7 @@
         (tvExp phys_group_id + i * sExt32 (kernelNumGroups constants))
     -- Make sure the virtual group is actually done before we let
     -- another virtual group have its way with it.
-    sOp $ Imp.Barrier Imp.FenceGlobal
+    sOp $ Imp.ErrorSync Imp.FenceGlobal
 virtualiseGroups _ _ m = do
   gid <- kernelGroupIdVar . kernelConstants <$> askEnv
   m $ Imp.le32 gid
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/Group.hs b/src/Futhark/CodeGen/ImpGen/GPU/Group.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/Group.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/Group.hs
@@ -14,7 +14,7 @@
   )
 where
 
-import Control.Monad.Except
+import Control.Monad
 import Data.Bifunctor
 import Data.List (partition, zip4)
 import Data.Map.Strict qualified as M
@@ -282,8 +282,10 @@
 compileGroupExp (Pat [dest]) (BasicOp (ArrayLit es _)) =
   forM_ (zip [0 ..] es) $ \(i, e) ->
     copyDWIMFix (patElemName dest) [fromIntegral (i :: Int64)] e []
-compileGroupExp _ (BasicOp (UpdateAcc acc is vs)) =
-  updateAcc acc is vs
+compileGroupExp _ (BasicOp (UpdateAcc acc is vs)) = do
+  ltid <- kernelLocalThreadId . kernelConstants <$> askEnv
+  sWhen (ltid .==. 0) $ updateAcc acc is vs
+  sOp $ Imp.Barrier Imp.FenceLocal
 compileGroupExp (Pat [dest]) (BasicOp (Replicate ds se)) = do
   flat <- newVName "rep_flat"
   is <- replicateM (arrayRank dest_t) (newVName "rep_i")
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegHist.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegHist.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegHist.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegHist.hs
@@ -37,7 +37,7 @@
 --   use global memory
 module Futhark.CodeGen.ImpGen.GPU.SegHist (compileSegHist) where
 
-import Control.Monad.Except
+import Control.Monad
 import Data.List (foldl', genericLength, zip5)
 import Data.Map qualified as M
 import Data.Maybe
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegMap.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegMap.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegMap.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegMap.hs
@@ -6,7 +6,7 @@
 -- by having actual workgroups run a loop to imitate multiple workgroups.
 module Futhark.CodeGen.ImpGen.GPU.SegMap (compileSegMap) where
 
-import Control.Monad.Except
+import Control.Monad
 import Futhark.CodeGen.ImpCode.GPU qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.GPU.Base
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegRed.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegRed.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegRed.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegRed.hs
@@ -48,7 +48,7 @@
   )
 where
 
-import Control.Monad.Except
+import Control.Monad
 import Data.List (genericLength, zip7)
 import Data.Maybe
 import Futhark.CodeGen.ImpCode.GPU qualified as Imp
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegScan/SinglePass.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegScan/SinglePass.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegScan/SinglePass.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegScan/SinglePass.hs
@@ -5,7 +5,7 @@
 -- with some constraints on the operator.  We use this when we can.
 module Futhark.CodeGen.ImpGen.GPU.SegScan.SinglePass (compileSegScan) where
 
-import Control.Monad.Except
+import Control.Monad
 import Data.List (zip4)
 import Data.Maybe
 import Futhark.CodeGen.ImpCode.GPU qualified as Imp
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegScan/TwoPass.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegScan/TwoPass.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegScan/TwoPass.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegScan/TwoPass.hs
@@ -4,7 +4,7 @@
 -- fairly inefficient two-pass algorithm, but can handle anything.
 module Futhark.CodeGen.ImpGen.GPU.SegScan.TwoPass (compileSegScan) where
 
-import Control.Monad.Except
+import Control.Monad
 import Control.Monad.State
 import Data.List (delete, find, foldl', zip4)
 import Data.Maybe
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/ToOpenCL.hs b/src/Futhark/CodeGen/ImpGen/GPU/ToOpenCL.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/ToOpenCL.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/ToOpenCL.hs
@@ -8,6 +8,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Identity
 import Control.Monad.Reader
 import Control.Monad.State
diff --git a/src/Futhark/CodeGen/ImpGen/Multicore/SegHist.hs b/src/Futhark/CodeGen/ImpGen/Multicore/SegHist.hs
--- a/src/Futhark/CodeGen/ImpGen/Multicore/SegHist.hs
+++ b/src/Futhark/CodeGen/ImpGen/Multicore/SegHist.hs
@@ -5,6 +5,7 @@
 
 import Control.Monad
 import Data.List (zip4)
+import Data.Maybe (listToMaybe)
 import Futhark.CodeGen.ImpCode.Multicore qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.Multicore.Base
@@ -58,7 +59,7 @@
   let ns = map snd $ unSegSpace space
       ns_64 = map pe64 ns
       num_histos' = tvExp num_histos
-      hist_width = histSize $ head histops
+      hist_width = maybe 0 histSize $ listToMaybe histops
       use_subhistogram = sExt64 num_histos' * hist_width .<=. product ns_64
 
   histops' <- renameHistOpLambda histops
diff --git a/src/Futhark/Compiler.hs b/src/Futhark/Compiler.hs
--- a/src/Futhark/Compiler.hs
+++ b/src/Futhark/Compiler.hs
@@ -18,7 +18,8 @@
 where
 
 import Control.Monad
-import Control.Monad.Except
+import Control.Monad.Except (MonadError)
+import Control.Monad.IO.Class (MonadIO, liftIO)
 import Data.Bifunctor (first)
 import Data.List (sortOn)
 import Data.List.NonEmpty qualified as NE
diff --git a/src/Futhark/Compiler/Program.hs b/src/Futhark/Compiler/Program.hs
--- a/src/Futhark/Compiler/Program.hs
+++ b/src/Futhark/Compiler/Program.hs
@@ -31,7 +31,7 @@
     readMVar,
   )
 import Control.Monad
-import Control.Monad.Except
+import Control.Monad.IO.Class (MonadIO, liftIO)
 import Control.Monad.State (execStateT, gets, modify)
 import Data.Bifunctor (first)
 import Data.List (intercalate, sort)
diff --git a/src/Futhark/Construct.hs b/src/Futhark/Construct.hs
--- a/src/Futhark/Construct.hs
+++ b/src/Futhark/Construct.hs
@@ -120,6 +120,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Identity
 import Control.Monad.State
 import Data.List (foldl', sortOn, transpose)
diff --git a/src/Futhark/Doc/Generator.hs b/src/Futhark/Doc/Generator.hs
--- a/src/Futhark/Doc/Generator.hs
+++ b/src/Futhark/Doc/Generator.hs
@@ -5,7 +5,7 @@
 import Control.Arrow ((***))
 import Control.Monad
 import Control.Monad.Reader
-import Control.Monad.Writer hiding (Sum)
+import Control.Monad.Writer (Writer, WriterT, runWriter, runWriterT, tell)
 import Data.Char (isAlpha, isSpace, toUpper)
 import Data.List (find, groupBy, inits, intersperse, isPrefixOf, partition, sort, sortOn, tails)
 import Data.Map qualified as M
diff --git a/src/Futhark/IR/Aliases.hs b/src/Futhark/IR/Aliases.hs
--- a/src/Futhark/IR/Aliases.hs
+++ b/src/Futhark/IR/Aliases.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
 
 -- | A representation where all patterns are annotated with aliasing
 -- information.  It also records consumption of variables in bodies.
@@ -129,7 +130,13 @@
   scope <- asksScope removeScopeAliases
   runReaderT m scope
 
-instance (ASTRep rep, AliasedOp (OpC rep (Aliases rep))) => ASTRep (Aliases rep) where
+instance
+  ( ASTRep rep,
+    AliasedOp (OpC rep (Aliases rep)),
+    IsOp (OpC rep (Aliases rep))
+  ) =>
+  ASTRep (Aliases rep)
+  where
   expTypesFromPat =
     withoutAliases . expTypesFromPat . removePatAliases
 
@@ -137,7 +144,13 @@
   bodyAliases = map unAliases . fst . fst . bodyDec
   consumedInBody = unAliases . snd . fst . bodyDec
 
-instance (ASTRep rep, AliasedOp (OpC rep (Aliases rep))) => PrettyRep (Aliases rep) where
+instance
+  ( ASTRep rep,
+    AliasedOp (OpC rep (Aliases rep)),
+    Pretty (OpC rep (Aliases rep))
+  ) =>
+  PrettyRep (Aliases rep)
+  where
   ppExpDec (consumed, inner) e =
     maybeComment . catMaybes $
       [exp_dec, merge_dec, ppExpDec inner $ removeExpAliases e]
diff --git a/src/Futhark/IR/Mem.hs b/src/Futhark/IR/Mem.hs
--- a/src/Futhark/IR/Mem.hs
+++ b/src/Futhark/IR/Mem.hs
@@ -100,6 +100,7 @@
 where
 
 import Control.Category
+import Control.Monad
 import Control.Monad.Except
 import Control.Monad.Reader
 import Control.Monad.State
diff --git a/src/Futhark/IR/Mem/IxFun.hs b/src/Futhark/IR/Mem/IxFun.hs
--- a/src/Futhark/IR/Mem/IxFun.hs
+++ b/src/Futhark/IR/Mem/IxFun.hs
@@ -41,9 +41,8 @@
 where
 
 import Control.Category
-import Control.Monad.Identity
+import Control.Monad
 import Control.Monad.State
-import Control.Monad.Writer
 import Data.Function (on, (&))
 import Data.List (elemIndex, partition, sort, sortBy, zip4, zipWith4)
 import Data.List.NonEmpty (NonEmpty (..))
diff --git a/src/Futhark/IR/Prop/Aliases.hs b/src/Futhark/IR/Prop/Aliases.hs
--- a/src/Futhark/IR/Prop/Aliases.hs
+++ b/src/Futhark/IR/Prop/Aliases.hs
@@ -221,11 +221,19 @@
 
 -- | Also includes the name itself.
 lookupAliases :: AliasesOf (LetDec rep) => VName -> Scope rep -> Names
-lookupAliases v scope =
-  case M.lookup v scope of
-    Just (LetName dec) ->
-      oneName v <> foldMap (`lookupAliases` scope) (namesToList (aliasesOf dec))
-    _ -> oneName v
+lookupAliases root scope =
+  -- We must be careful to handle circular aliasing properly (this
+  -- can happen due to Match and Loop).
+  expand mempty root
+  where
+    expand prev v =
+      case M.lookup v scope of
+        Just (LetName dec) ->
+          oneName v
+            <> foldMap
+              (expand (oneName v <> prev))
+              (filter (`notNameIn` prev) (namesToList (aliasesOf dec)))
+        _ -> oneName v
 
 -- | The class of operations that can produce aliasing and consumption
 -- information.
diff --git a/src/Futhark/IR/Prop/Types.hs b/src/Futhark/IR/Prop/Types.hs
--- a/src/Futhark/IR/Prop/Types.hs
+++ b/src/Futhark/IR/Prop/Types.hs
@@ -66,6 +66,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.State
 import Data.List (elemIndex, foldl')
 import Data.Map.Strict qualified as M
diff --git a/src/Futhark/IR/SOACS/SOAC.hs b/src/Futhark/IR/SOACS/SOAC.hs
--- a/src/Futhark/IR/SOACS/SOAC.hs
+++ b/src/Futhark/IR/SOACS/SOAC.hs
@@ -49,6 +49,7 @@
 where
 
 import Control.Category
+import Control.Monad
 import Control.Monad.Identity
 import Control.Monad.State.Strict
 import Control.Monad.Writer
@@ -371,7 +372,7 @@
   }
 
 -- | A mapper that simply returns the SOAC verbatim.
-identitySOACMapper :: Monad m => SOACMapper rep rep m
+identitySOACMapper :: forall rep m. Monad m => SOACMapper rep rep m
 identitySOACMapper =
   SOACMapper
     { mapOnSOACSubExp = pure,
@@ -670,12 +671,14 @@
   accargs <- mapM TC.checkArg accexps
   arrargs <- mapM lookupType arrexps
   _ <- TC.checkSOACArrayArgs size arrexps
-  let chunk = head $ lambdaParams lam
+  chunk <- case lambdaParams lam of
+    chunk : _ -> pure chunk
+    [] -> TC.bad $ TC.TypeError "Stream lambda without parameters."
   let asArg t = (t, mempty)
       inttp = Prim int64
       lamarrs' = map (`setOuterSize` Var (paramName chunk)) arrargs
-  let acc_len = length accexps
-  let lamrtp = take acc_len $ lambdaReturnType lam
+      acc_len = length accexps
+      lamrtp = take acc_len $ lambdaReturnType lam
   unless (map TC.argType accargs == lamrtp) $
     TC.bad . TC.TypeError $
       "Stream with inconsistent accumulator type in lambda."
diff --git a/src/Futhark/IR/SOACS/Simplify.hs b/src/Futhark/IR/SOACS/Simplify.hs
--- a/src/Futhark/IR/SOACS/Simplify.hs
+++ b/src/Futhark/IR/SOACS/Simplify.hs
@@ -770,7 +770,7 @@
       tell $ arrayOps (cs <> more_cs) $ lambdaBody lam
       pure lam
     walker more_cs =
-      identityWalker
+      (identityWalker @rep)
         { walkOnBody = const $ modify . (<>) . arrayOps (cs <> more_cs),
           walkOnOp = modify . (<>) . onOp more_cs
         }
@@ -780,22 +780,18 @@
 replaceArrayOps ::
   forall rep.
   (Buildable rep, BuilderOps rep, HasSOAC rep) =>
-  M.Map (Pat (LetDec rep), ArrayOp) ArrayOp ->
+  M.Map (Pat (LetDec rep)) ArrayOp ->
   Body rep ->
   Body rep
 replaceArrayOps substs (Body _ stms res) =
   mkBody (fmap onStm stms) res
   where
     onStm (Let pat aux e) =
-      let (cs', e') = onExp pat (stmAuxCerts aux) e
+      let (cs', e') =
+            maybe (mempty, mapExp mapper e) fromArrayOp $ M.lookup pat substs
        in certify cs' $ mkLet' (patIdents pat) aux e'
-    onExp pat cs e
-      | Just op <- isArrayOp cs e,
-        Just op' <- M.lookup (pat, op) substs =
-          fromArrayOp op'
-    onExp _ cs e = (cs, mapExp mapper e)
     mapper =
-      identityMapper
+      (identityMapper @rep)
         { mapOnBody = const $ pure . replaceArrayOps substs,
           mapOnOp = pure . onOp
         }
@@ -887,7 +883,7 @@
         Just
           ( arr'',
             arr_elem_param,
-            ( (pat, ArrayIndexing cs arr slice),
+            ( pat,
               ArrayIndexing cs (paramName arr_elem_param) (Slice (drop (length js + 1) (unSlice slice)))
             )
           )
@@ -913,10 +909,8 @@
                 lambdaBody = replaceArrayOps (M.fromList replacements) $ lambdaBody map_lam
               }
 
-      auxing aux $
-        letBind screma_pat $
-          Op $
-            Screma w (arrs <> more_arrs) (ScremaForm scan reduce map_lam')
+      auxing aux . letBind screma_pat . Op $
+        Screma w (arrs <> more_arrs) (ScremaForm scan reduce map_lam')
   where
     -- It is not safe to move the transform if the root array is being
     -- consumed by the Screma.  This is a bit too conservative - it's
@@ -979,7 +973,7 @@
             Just
               ( arr_transformed,
                 Param mempty arr_transformed_row (rowType arr_transformed_t),
-                ((pat, op), ArrayVar mempty arr_transformed_row)
+                (pat, ArrayVar mempty arr_transformed_row)
               )
     mapOverArr _ = pure Nothing
 moveTransformToInput _ _ _ _ =
diff --git a/src/Futhark/IR/SegOp.hs b/src/Futhark/IR/SegOp.hs
--- a/src/Futhark/IR/SegOp.hs
+++ b/src/Futhark/IR/SegOp.hs
@@ -46,10 +46,11 @@
 where
 
 import Control.Category
-import Control.Monad.Identity hiding (mapM_)
-import Control.Monad.Reader hiding (mapM_)
+import Control.Monad
+import Control.Monad.Identity
+import Control.Monad.Reader
 import Control.Monad.State.Strict
-import Control.Monad.Writer hiding (mapM_)
+import Control.Monad.Writer
 import Data.Bifunctor (first)
 import Data.Bitraversable
 import Data.Foldable (traverse_)
diff --git a/src/Futhark/IR/Syntax/Core.hs b/src/Futhark/IR/Syntax/Core.hs
--- a/src/Futhark/IR/Syntax/Core.hs
+++ b/src/Futhark/IR/Syntax/Core.hs
@@ -75,6 +75,7 @@
 where
 
 import Control.Category
+import Control.Monad
 import Control.Monad.State
 import Data.Bifoldable
 import Data.Bifunctor
@@ -246,6 +247,9 @@
   bitraverse _ g (Acc arrs ispace ts u) = Acc arrs ispace ts <$> g u
   bitraverse _ _ (Mem s) = pure $ Mem s
 
+instance Functor (TypeBase shape) where
+  fmap = second
+
 instance Bifunctor TypeBase where
   bimap = bimapDefault
 
@@ -304,7 +308,7 @@
   deriving (Eq, Ord, Show)
 
 instance Semigroup Certs where
-  Certs x <> Certs y = Certs (x <> y)
+  Certs x <> Certs y = Certs (x <> filter (`notElem` x) y)
 
 instance Monoid Certs where
   mempty = Certs mempty
diff --git a/src/Futhark/IR/Traversals.hs b/src/Futhark/IR/Traversals.hs
--- a/src/Futhark/IR/Traversals.hs
+++ b/src/Futhark/IR/Traversals.hs
@@ -66,7 +66,7 @@
   }
 
 -- | A mapper that simply returns the tree verbatim.
-identityMapper :: Monad m => Mapper rep rep m
+identityMapper :: forall rep m. Monad m => Mapper rep rep m
 identityMapper =
   Mapper
     { mapOnSubExp = pure,
@@ -247,7 +247,7 @@
   }
 
 -- | A no-op traversal.
-identityWalker :: Monad m => Walker rep m
+identityWalker :: forall rep m. Monad m => Walker rep m
 identityWalker =
   Walker
     { walkOnSubExp = const $ pure (),
diff --git a/src/Futhark/IR/TypeCheck.hs b/src/Futhark/IR/TypeCheck.hs
--- a/src/Futhark/IR/TypeCheck.hs
+++ b/src/Futhark/IR/TypeCheck.hs
@@ -42,6 +42,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State.Strict
 import Control.Parallel.Strategies
diff --git a/src/Futhark/Internalise/Bindings.hs b/src/Futhark/Internalise/Bindings.hs
--- a/src/Futhark/Internalise/Bindings.hs
+++ b/src/Futhark/Internalise/Bindings.hs
@@ -11,7 +11,8 @@
   )
 where
 
-import Control.Monad.Reader hiding (mapM)
+import Control.Monad
+import Control.Monad.Reader
 import Data.Bifunctor
 import Data.Map.Strict qualified as M
 import Data.Maybe
diff --git a/src/Futhark/Internalise/Defunctionalise.hs b/src/Futhark/Internalise/Defunctionalise.hs
--- a/src/Futhark/Internalise/Defunctionalise.hs
+++ b/src/Futhark/Internalise/Defunctionalise.hs
@@ -1,6 +1,7 @@
 -- | Defunctionalization of typed, monomorphic Futhark programs without modules.
 module Futhark.Internalise.Defunctionalise (transformProg) where
 
+import Control.Monad
 import Control.Monad.Identity
 import Control.Monad.Reader
 import Control.Monad.State
@@ -571,6 +572,7 @@
       Just sv -> pure (Project vn e0' (Info $ typeFromSV sv) loc, sv)
       Nothing -> error "Invalid record projection."
     Dynamic _ -> pure (Project vn e0' tp loc, Dynamic tp')
+    HoleSV _ hloc -> pure (Project vn e0' tp loc, HoleSV tp' hloc)
     _ -> error $ "Projection of an expression with static value " ++ show sv0
 defuncExp (AppExp (LetWith id1 id2 idxs e1 body loc) res) = do
   e1' <- defuncExp' e1
diff --git a/src/Futhark/Internalise/Entry.hs b/src/Futhark/Internalise/Entry.hs
--- a/src/Futhark/Internalise/Entry.hs
+++ b/src/Futhark/Internalise/Entry.hs
@@ -6,6 +6,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.State
 import Data.List (find)
 import Data.Map qualified as M
diff --git a/src/Futhark/Internalise/Exps.hs b/src/Futhark/Internalise/Exps.hs
--- a/src/Futhark/Internalise/Exps.hs
+++ b/src/Futhark/Internalise/Exps.hs
@@ -5,6 +5,7 @@
 -- program to a core Futhark program.
 module Futhark.Internalise.Exps (transformProg) where
 
+import Control.Monad
 import Control.Monad.Reader
 import Data.List (elemIndex, find, intercalate, intersperse, transpose)
 import Data.List.NonEmpty (NonEmpty (..))
diff --git a/src/Futhark/Internalise/Lambdas.hs b/src/Futhark/Internalise/Lambdas.hs
--- a/src/Futhark/Internalise/Lambdas.hs
+++ b/src/Futhark/Internalise/Lambdas.hs
@@ -5,6 +5,7 @@
   )
 where
 
+import Data.Maybe (listToMaybe)
 import Futhark.IR.SOACS as I
 import Futhark.Internalise.AccurateSizes
 import Futhark.Internalise.Monad
@@ -77,5 +78,6 @@
 
     lambdaWithIncrement :: I.Body SOACS -> InternaliseM (I.Body SOACS)
     lambdaWithIncrement lam_body = runBodyBuilder $ do
-      eq_class <- resSubExp . head <$> bodyBind lam_body
+      eq_class <-
+        maybe (intConst Int64 0) resSubExp . listToMaybe <$> bodyBind lam_body
       resultBody <$> mkResult eq_class 0
diff --git a/src/Futhark/Internalise/Monad.hs b/src/Futhark/Internalise/Monad.hs
--- a/src/Futhark/Internalise/Monad.hs
+++ b/src/Futhark/Internalise/Monad.hs
@@ -23,6 +23,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Except
 import Control.Monad.Reader
 import Control.Monad.State
diff --git a/src/Futhark/Internalise/Monomorphise.hs b/src/Futhark/Internalise/Monomorphise.hs
--- a/src/Futhark/Internalise/Monomorphise.hs
+++ b/src/Futhark/Internalise/Monomorphise.hs
@@ -24,10 +24,10 @@
 -- representation.
 module Futhark.Internalise.Monomorphise (transformProg) where
 
-import Control.Monad.Identity
-import Control.Monad.RWS hiding (Sum)
+import Control.Monad
+import Control.Monad.RWS (MonadReader (..), MonadWriter (..), RWST, asks, runRWST)
 import Control.Monad.State
-import Control.Monad.Writer hiding (Sum)
+import Control.Monad.Writer (runWriterT)
 import Data.Bifunctor
 import Data.Bitraversable
 import Data.Foldable
@@ -653,9 +653,8 @@
 transformPat (Wildcard (Info t) loc) = do
   t' <- transformType t
   pure (wildcard t' loc, mempty)
-transformPat (PatAscription pat td loc) = do
-  (pat', rr) <- transformPat pat
-  pure (PatAscription pat' td loc, rr)
+transformPat (PatAscription pat _ _) =
+  transformPat pat
 transformPat (PatLit e t loc) = pure (PatLit e t loc, mempty)
 transformPat (PatConstr name t all_ps loc) = do
   (all_ps', rrs) <- mapAndUnzipM transformPat all_ps
diff --git a/src/Futhark/Optimise/ArrayShortCircuiting.hs b/src/Futhark/Optimise/ArrayShortCircuiting.hs
--- a/src/Futhark/Optimise/ArrayShortCircuiting.hs
+++ b/src/Futhark/Optimise/ArrayShortCircuiting.hs
@@ -10,6 +10,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Reader
 import Data.Function ((&))
 import Data.Map qualified as M
diff --git a/src/Futhark/Optimise/ArrayShortCircuiting/ArrayCoalescing.hs b/src/Futhark/Optimise/ArrayShortCircuiting/ArrayCoalescing.hs
--- a/src/Futhark/Optimise/ArrayShortCircuiting/ArrayCoalescing.hs
+++ b/src/Futhark/Optimise/ArrayShortCircuiting/ArrayCoalescing.hs
@@ -11,6 +11,7 @@
 where
 
 import Control.Exception.Base qualified as Exc
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State.Strict
 import Data.Function ((&))
diff --git a/src/Futhark/Optimise/BlkRegTiling.hs b/src/Futhark/Optimise/BlkRegTiling.hs
--- a/src/Futhark/Optimise/BlkRegTiling.hs
+++ b/src/Futhark/Optimise/BlkRegTiling.hs
@@ -18,9 +18,9 @@
 --          the kernel produces one scalar result
 module Futhark.Optimise.BlkRegTiling (mmBlkRegTiling, doRegTiling3D) where
 
-import Control.Monad.Reader
+import Control.Monad
 import Data.List qualified as L
-import Data.List.NonEmpty (NonEmpty (..))
+import Data.List.NonEmpty qualified as NE
 import Data.Map.Strict qualified as M
 import Data.Maybe
 import Data.Sequence qualified as Seq
@@ -143,14 +143,16 @@
         | [slc_X'] <- patNames pat,
           slc_X == slc_X',
           Just ixf_fn <- M.lookup x ixfn_env,
-          (IxFun.IxFun (lmad :| []) _ _) <- ixf_fn =
-            let lmad_dims = IxFun.lmadDims lmad
-                q = length lmad_dims
-                last_perm = IxFun.ldPerm $ last lmad_dims
-                stride = IxFun.ldStride $ last lmad_dims
-                res = last_perm == q - 1 && (stride == pe64 (intConst Int64 1))
-             in res
-      isInnerCoal _ _ _ = error "TileLoops/Shared.hs: not an error, but I would like to know why!"
+          (IxFun.IxFun lmads _ _) <- ixf_fn =
+            all innerHasStride1 $ NE.toList lmads
+      isInnerCoal _ _ _ =
+        error "kkLoopBody.isInnerCoal: not an error, but I would like to know why!"
+      innerHasStride1 lmad =
+        let lmad_dims = IxFun.lmadDims lmad
+            q = length lmad_dims
+            last_perm = IxFun.ldPerm $ last lmad_dims
+            stride = IxFun.ldStride $ last lmad_dims
+         in (last_perm == q - 1) && (stride == pe64 (intConst Int64 1))
       --
       mkRedomapOneTileBody acc_merge asss bsss fits_ij = do
         -- the actual redomap.
diff --git a/src/Futhark/Optimise/CSE.hs b/src/Futhark/Optimise/CSE.hs
--- a/src/Futhark/Optimise/CSE.hs
+++ b/src/Futhark/Optimise/CSE.hs
@@ -167,6 +167,7 @@
   pure lam {lambdaBody = body'}
 
 cseInStms ::
+  forall rep a.
   (Aliased rep, CSEInOp (Op rep)) =>
   Names ->
   [Stm rep] ->
@@ -190,7 +191,7 @@
       pure stm' {stmExp = e}
 
     cse ds =
-      identityMapper
+      (identityMapper @rep)
         { mapOnBody = const $ cseInBody ds,
           mapOnOp = cseInOp
         }
diff --git a/src/Futhark/Optimise/DoubleBuffer.hs b/src/Futhark/Optimise/DoubleBuffer.hs
--- a/src/Futhark/Optimise/DoubleBuffer.hs
+++ b/src/Futhark/Optimise/DoubleBuffer.hs
@@ -71,6 +71,7 @@
 -- per iteration (and an initial one, elided above).
 module Futhark.Optimise.DoubleBuffer (doubleBufferGPU, doubleBufferMC) where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
 import Control.Monad.Writer
@@ -170,7 +171,7 @@
   oneStm . Let pat aux <$> mapExpM (optimise onOp) e
   where
     optimise onOp =
-      identityMapper
+      (identityMapper @rep)
         { mapOnBody = \_ x ->
             optimiseBody x :: DoubleBufferM rep (Body rep),
           mapOnOp = onOp
diff --git a/src/Futhark/Optimise/Fusion.hs b/src/Futhark/Optimise/Fusion.hs
--- a/src/Futhark/Optimise/Fusion.hs
+++ b/src/Futhark/Optimise/Fusion.hs
@@ -6,6 +6,7 @@
 -- Redomap Construct/).
 module Futhark.Optimise.Fusion (fuseSOACs) where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.Graph.Inductive.Graph qualified as G
diff --git a/src/Futhark/Optimise/GenRedOpt.hs b/src/Futhark/Optimise/GenRedOpt.hs
--- a/src/Futhark/Optimise/GenRedOpt.hs
+++ b/src/Futhark/Optimise/GenRedOpt.hs
@@ -16,6 +16,7 @@
 --     map-reduce, as to potentially enable tiling oportunities.
 module Futhark.Optimise.GenRedOpt (optimiseGenRed) where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.List qualified as L
diff --git a/src/Futhark/Optimise/HistAccs.hs b/src/Futhark/Optimise/HistAccs.hs
--- a/src/Futhark/Optimise/HistAccs.hs
+++ b/src/Futhark/Optimise/HistAccs.hs
@@ -3,6 +3,7 @@
 -- | Turn certain uses of accumulators into SegHists.
 module Futhark.Optimise.HistAccs (histAccsGPU) where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.Map.Strict qualified as M
diff --git a/src/Futhark/Optimise/InPlaceLowering.hs b/src/Futhark/Optimise/InPlaceLowering.hs
--- a/src/Futhark/Optimise/InPlaceLowering.hs
+++ b/src/Futhark/Optimise/InPlaceLowering.hs
@@ -65,6 +65,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.RWS
 import Data.Map.Strict qualified as M
 import Data.Ord (comparing)
@@ -377,14 +378,14 @@
           ++ prettyString name
           ++ " not found."
 
-seenVar :: VName -> ForwardingM rep ()
+seenVar :: forall rep. VName -> ForwardingM rep ()
 seenVar name = do
   aliases <-
     asks $
       maybe mempty entryAliases
         . M.lookup name
         . topDownTable
-  tell $ mempty {bottomUpSeen = oneName name <> aliases}
+  tell $ (mempty :: BottomUp rep) {bottomUpSeen = oneName name <> aliases}
 
 tapBottomUp :: ForwardingM rep a -> ForwardingM rep (a, BottomUp rep)
 tapBottomUp m = do
diff --git a/src/Futhark/Optimise/InliningDeadFun.hs b/src/Futhark/Optimise/InliningDeadFun.hs
--- a/src/Futhark/Optimise/InliningDeadFun.hs
+++ b/src/Futhark/Optimise/InliningDeadFun.hs
@@ -7,6 +7,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Identity
 import Control.Monad.State
 import Control.Parallel.Strategies
@@ -210,7 +211,7 @@
     onStm (Let pat aux e) = Let pat aux <$> mapExpM inliner e
 
     inliner =
-      identityMapper
+      (identityMapper @SOACS)
         { mapOnBody = const onBody,
           mapOnOp = onSOAC
         }
diff --git a/src/Futhark/Optimise/Simplify/Engine.hs b/src/Futhark/Optimise/Simplify/Engine.hs
--- a/src/Futhark/Optimise/Simplify/Engine.hs
+++ b/src/Futhark/Optimise/Simplify/Engine.hs
@@ -63,6 +63,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State.Strict
 import Data.Either
diff --git a/src/Futhark/Optimise/Simplify/Rep.hs b/src/Futhark/Optimise/Simplify/Rep.hs
--- a/src/Futhark/Optimise/Simplify/Rep.hs
+++ b/src/Futhark/Optimise/Simplify/Rep.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
 
 -- | Representation used by the simplification engine.  It contains
 -- aliasing information and a bit of caching for various information
@@ -116,7 +117,16 @@
 instance FreeDec BodyWisdom where
   precomputed = const . fvNames . unAliases . bodyWisdomFree
 
-instance Informing rep => RepTypes (Wise rep) where
+instance
+  ( Informing rep,
+    Ord (OpC rep (Wise rep)),
+    Eq (OpC rep (Wise rep)),
+    Show (OpC rep (Wise rep)),
+    IsOp (OpC rep (Wise rep)),
+    Pretty (OpC rep (Wise rep))
+  ) =>
+  RepTypes (Wise rep)
+  where
   type LetDec (Wise rep) = (VarWisdom, LetDec rep)
   type ExpDec (Wise rep) = (ExpWisdom, ExpDec rep)
   type BodyDec (Wise rep) = (BodyWisdom, BodyDec rep)
@@ -134,14 +144,14 @@
   scope <- asksScope removeScopeWisdom
   runReaderT m scope
 
-instance Informing rep => ASTRep (Wise rep) where
+instance (Informing rep, IsOp (OpC rep (Wise rep))) => ASTRep (Wise rep) where
   expTypesFromPat =
     withoutWisdom . expTypesFromPat . removePatWisdom
 
 instance Pretty VarWisdom where
   pretty _ = pretty ()
 
-instance Informing rep => PrettyRep (Wise rep) where
+instance (Informing rep, Pretty (OpC rep (Wise rep))) => PrettyRep (Wise rep) where
   ppExpDec (_, dec) = ppExpDec dec . removeExpWisdom
 
 instance AliasesOf (VarWisdom, dec) where
diff --git a/src/Futhark/Optimise/Simplify/Rules.hs b/src/Futhark/Optimise/Simplify/Rules.hs
--- a/src/Futhark/Optimise/Simplify/Rules.hs
+++ b/src/Futhark/Optimise/Simplify/Rules.hs
@@ -203,7 +203,7 @@
       pure $ Just x
 withAccTopDown _ _ = Skip
 
-elimUpdates :: (ASTRep rep, TraverseOpStms rep) => [VName] -> Body rep -> (Body rep, [VName])
+elimUpdates :: forall rep. (ASTRep rep, TraverseOpStms rep) => [VName] -> Body rep -> (Body rep, [VName])
 elimUpdates get_rid_of = flip runState mempty . onBody
   where
     onBody body = do
@@ -219,7 +219,7 @@
     onExp = mapExpM mapper
       where
         mapper =
-          identityMapper
+          (identityMapper :: forall m. Monad m => Mapper rep rep m)
             { mapOnOp = traverseOpStms (\_ stms -> onStms stms),
               mapOnBody = \_ body -> onBody body
             }
diff --git a/src/Futhark/Optimise/Simplify/Rules/Loop.hs b/src/Futhark/Optimise/Simplify/Rules/Loop.hs
--- a/src/Futhark/Optimise/Simplify/Rules/Loop.hs
+++ b/src/Futhark/Optimise/Simplify/Rules/Loop.hs
@@ -95,22 +95,22 @@
     ([], _, _, _) ->
       -- Nothing is invariant.
       Skip
-    (invariant, explpat', merge', res') -> Simplify $ do
+    (invariant, explpat', merge', res') -> Simplify . auxing aux $ do
       -- We have moved something invariant out of the loop.
       let loopbody' = loopbody {bodyResult = res'}
           explpat'' = map fst explpat'
-      forM_ invariant $ \(v1, v2) ->
-        letBindNames [identName v1] $ BasicOp $ SubExp v2
-      auxing aux $ letBind (Pat explpat'') $ DoLoop merge' form loopbody'
+      forM_ invariant $ \(v1, (v2, cs)) ->
+        certifying cs $ letBindNames [identName v1] $ BasicOp $ SubExp v2
+      letBind (Pat explpat'') $ DoLoop merge' form loopbody'
   where
     res = bodyResult loopbody
 
     namesOfMergeParams = namesFromList $ map (paramName . fst) merge
 
-    removeFromResult (mergeParam, mergeInit) explpat' =
+    removeFromResult cs (mergeParam, mergeInit) explpat' =
       case partition ((== paramName mergeParam) . snd) explpat' of
         ([(patelem, _)], rest) ->
-          (Just (patElemIdent patelem, mergeInit), rest)
+          (Just (patElemIdent patelem, (mergeInit, cs)), rest)
         (_, _) ->
           (Nothing, explpat')
 
@@ -119,10 +119,16 @@
       (invariant, explpat', merge', resExps)
         | isInvariant,
           -- Also do not remove the condition in a while-loop.
-          paramName mergeParam `notNameIn` freeIn form =
+          paramName mergeParam `notNameIn` freeIn form,
+          -- Certificates must be available.
+          all (`ST.elem` vtable) $ unCerts $ resCerts resExp =
             let (stm, explpat'') =
-                  removeFromResult (mergeParam, mergeInit) explpat'
-             in ( maybe id (:) stm $ (paramIdent mergeParam, mergeInit) : invariant,
+                  removeFromResult
+                    (resCerts resExp)
+                    (mergeParam, mergeInit)
+                    explpat'
+             in ( maybe id (:) stm $
+                    (paramIdent mergeParam, (mergeInit, resCerts resExp)) : invariant,
                   explpat'',
                   merge',
                   resExps
diff --git a/src/Futhark/Optimise/Simplify/Rules/Simple.hs b/src/Futhark/Optimise/Simplify/Rules/Simple.hs
--- a/src/Futhark/Optimise/Simplify/Rules/Simple.hs
+++ b/src/Futhark/Optimise/Simplify/Rules/Simple.hs
@@ -344,16 +344,18 @@
 copyScratchToScratch :: SimpleRule rep
 copyScratchToScratch defOf seType (Copy src) = do
   t <- seType $ Var src
-  if isActuallyScratch src
-    then Just (Scratch (elemType t) (arrayDims t), mempty)
-    else Nothing
+  cs <- isActuallyScratch src
+  pure (Scratch (elemType t) (arrayDims t), cs)
   where
     isActuallyScratch v =
-      case asBasicOp . fst =<< defOf v of
-        Just Scratch {} -> True
-        Just (Rearrange _ v') -> isActuallyScratch v'
-        Just (Reshape _ _ v') -> isActuallyScratch v'
-        _ -> False
+      case defOf v of
+        Just (BasicOp Scratch {}, cs) ->
+          Just cs
+        Just (BasicOp (Rearrange _ v'), cs) ->
+          (cs <>) <$> isActuallyScratch v'
+        Just (BasicOp (Reshape _ _ v'), cs) ->
+          (cs <>) <$> isActuallyScratch v'
+        _ -> Nothing
 copyScratchToScratch _ _ _ =
   Nothing
 
diff --git a/src/Futhark/Optimise/TileLoops.hs b/src/Futhark/Optimise/TileLoops.hs
--- a/src/Futhark/Optimise/TileLoops.hs
+++ b/src/Futhark/Optimise/TileLoops.hs
@@ -5,6 +5,7 @@
 -- tile primitive types, to avoid excessive local memory use.
 module Futhark.Optimise.TileLoops (tileLoops) where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.Map.Strict qualified as M
diff --git a/src/Futhark/Optimise/TileLoops/Shared.hs b/src/Futhark/Optimise/TileLoops/Shared.hs
--- a/src/Futhark/Optimise/TileLoops/Shared.hs
+++ b/src/Futhark/Optimise/TileLoops/Shared.hs
@@ -17,6 +17,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.List (foldl', zip4)
diff --git a/src/Futhark/Optimise/Unstream.hs b/src/Futhark/Optimise/Unstream.hs
--- a/src/Futhark/Optimise/Unstream.hs
+++ b/src/Futhark/Optimise/Unstream.hs
@@ -19,6 +19,7 @@
 -- kept together.
 module Futhark.Optimise.Unstream (unstreamGPU, unstreamMC) where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
 import Futhark.IR.GPU
diff --git a/src/Futhark/Pass/AD.hs b/src/Futhark/Pass/AD.hs
--- a/src/Futhark/Pass/AD.hs
+++ b/src/Futhark/Pass/AD.hs
@@ -53,7 +53,7 @@
 onStm mode scope (Let pat aux e) = oneStm . Let pat aux <$> mapExpM mapper e
   where
     mapper =
-      identityMapper
+      (identityMapper @SOACS)
         { mapOnBody = \bscope -> onBody mode (bscope <> scope),
           mapOnOp = mapSOACM soac_mapper
         }
diff --git a/src/Futhark/Pass/ExpandAllocations.hs b/src/Futhark/Pass/ExpandAllocations.hs
--- a/src/Futhark/Pass/ExpandAllocations.hs
+++ b/src/Futhark/Pass/ExpandAllocations.hs
@@ -3,6 +3,7 @@
 -- | Expand allocations inside of maps when possible.
 module Futhark.Pass.ExpandAllocations (expandAllocations) where
 
+import Control.Monad
 import Control.Monad.Except
 import Control.Monad.Reader
 import Control.Monad.State
@@ -412,7 +413,7 @@
   pure $ Just $ stm {stmExp = e}
   where
     expMapper user' =
-      identityMapper
+      (identityMapper @GPUMem)
         { mapOnBody = const $ onBody user',
           mapOnOp = onOp user'
         }
@@ -722,7 +723,7 @@
 offsetMemoryInExp e = mapExpM recurse e
   where
     recurse =
-      identityMapper
+      (identityMapper @GPUMem)
         { mapOnBody = \bscope -> localScope bscope . offsetMemoryInBody,
           mapOnBranchType = offsetMemoryInBodyReturns,
           mapOnOp = onOp
diff --git a/src/Futhark/Pass/ExplicitAllocations.hs b/src/Futhark/Pass/ExplicitAllocations.hs
--- a/src/Futhark/Pass/ExplicitAllocations.hs
+++ b/src/Futhark/Pass/ExplicitAllocations.hs
@@ -34,6 +34,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.RWS.Strict
 import Control.Monad.Reader
 import Control.Monad.State
diff --git a/src/Futhark/Pass/ExplicitAllocations/GPU.hs b/src/Futhark/Pass/ExplicitAllocations/GPU.hs
--- a/src/Futhark/Pass/ExplicitAllocations/GPU.hs
+++ b/src/Futhark/Pass/ExplicitAllocations/GPU.hs
@@ -8,6 +8,7 @@
   )
 where
 
+import Control.Monad
 import Data.Set qualified as S
 import Futhark.IR.GPU
 import Futhark.IR.GPUMem
diff --git a/src/Futhark/Pass/ExplicitAllocations/SegOp.hs b/src/Futhark/Pass/ExplicitAllocations/SegOp.hs
--- a/src/Futhark/Pass/ExplicitAllocations/SegOp.hs
+++ b/src/Futhark/Pass/ExplicitAllocations/SegOp.hs
@@ -7,6 +7,7 @@
   )
 where
 
+import Control.Monad
 import Futhark.IR.GPUMem
 import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.Pass.ExplicitAllocations
diff --git a/src/Futhark/Pass/ExtractKernels.hs b/src/Futhark/Pass/ExtractKernels.hs
--- a/src/Futhark/Pass/ExtractKernels.hs
+++ b/src/Futhark/Pass/ExtractKernels.hs
@@ -153,7 +153,7 @@
 -- single kernel.
 module Futhark.Pass.ExtractKernels (extractKernels) where
 
-import Control.Monad.Identity
+import Control.Monad
 import Control.Monad.RWS.Strict
 import Control.Monad.Reader
 import Data.Bifunctor (first)
diff --git a/src/Futhark/Pass/ExtractKernels/BlockedKernel.hs b/src/Futhark/Pass/ExtractKernels/BlockedKernel.hs
--- a/src/Futhark/Pass/ExtractKernels/BlockedKernel.hs
+++ b/src/Futhark/Pass/ExtractKernels/BlockedKernel.hs
@@ -18,7 +18,6 @@
 where
 
 import Control.Monad
-import Control.Monad.Writer
 import Futhark.Analysis.PrimExp
 import Futhark.IR
 import Futhark.IR.Aliases (AliasableRep)
diff --git a/src/Futhark/Pass/ExtractKernels/DistributeNests.hs b/src/Futhark/Pass/ExtractKernels/DistributeNests.hs
--- a/src/Futhark/Pass/ExtractKernels/DistributeNests.hs
+++ b/src/Futhark/Pass/ExtractKernels/DistributeNests.hs
@@ -28,7 +28,7 @@
 where
 
 import Control.Arrow (first)
-import Control.Monad.Identity
+import Control.Monad
 import Control.Monad.RWS.Strict
 import Control.Monad.Reader
 import Control.Monad.Trans.Maybe
diff --git a/src/Futhark/Pass/ExtractKernels/Distribution.hs b/src/Futhark/Pass/ExtractKernels/Distribution.hs
--- a/src/Futhark/Pass/ExtractKernels/Distribution.hs
+++ b/src/Futhark/Pass/ExtractKernels/Distribution.hs
@@ -38,6 +38,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.RWS.Strict
 import Control.Monad.Trans.Maybe
 import Data.Bifunctor (second)
diff --git a/src/Futhark/Pass/ExtractKernels/ISRWIM.hs b/src/Futhark/Pass/ExtractKernels/ISRWIM.hs
--- a/src/Futhark/Pass/ExtractKernels/ISRWIM.hs
+++ b/src/Futhark/Pass/ExtractKernels/ISRWIM.hs
@@ -9,7 +9,7 @@
 where
 
 import Control.Arrow (first)
-import Control.Monad.State
+import Control.Monad
 import Futhark.IR.SOACS
 import Futhark.MonadFreshNames
 import Futhark.Tools
diff --git a/src/Futhark/Pass/ExtractKernels/Interchange.hs b/src/Futhark/Pass/ExtractKernels/Interchange.hs
--- a/src/Futhark/Pass/ExtractKernels/Interchange.hs
+++ b/src/Futhark/Pass/ExtractKernels/Interchange.hs
@@ -16,7 +16,7 @@
   )
 where
 
-import Control.Monad.Identity
+import Control.Monad
 import Data.List (find)
 import Data.Maybe
 import Futhark.IR.SOACS
diff --git a/src/Futhark/Pass/ExtractKernels/Intragroup.hs b/src/Futhark/Pass/ExtractKernels/Intragroup.hs
--- a/src/Futhark/Pass/ExtractKernels/Intragroup.hs
+++ b/src/Futhark/Pass/ExtractKernels/Intragroup.hs
@@ -4,7 +4,7 @@
 -- individual kernel workgroups.
 module Futhark.Pass.ExtractKernels.Intragroup (intraGroupParallelise) where
 
-import Control.Monad.Identity
+import Control.Monad
 import Control.Monad.RWS
 import Control.Monad.Trans.Maybe
 import Data.Map.Strict qualified as M
diff --git a/src/Futhark/Pass/ExtractMulticore.hs b/src/Futhark/Pass/ExtractMulticore.hs
--- a/src/Futhark/Pass/ExtractMulticore.hs
+++ b/src/Futhark/Pass/ExtractMulticore.hs
@@ -5,6 +5,7 @@
 -- involve ad-hoc irregular nested parallelism.
 module Futhark.Pass.ExtractMulticore (extractMulticore) where
 
+import Control.Monad
 import Control.Monad.Identity
 import Control.Monad.Reader
 import Control.Monad.State
diff --git a/src/Futhark/Pass/KernelBabysitting.hs b/src/Futhark/Pass/KernelBabysitting.hs
--- a/src/Futhark/Pass/KernelBabysitting.hs
+++ b/src/Futhark/Pass/KernelBabysitting.hs
@@ -4,6 +4,7 @@
 module Futhark.Pass.KernelBabysitting (babysitKernels) where
 
 import Control.Arrow (first)
+import Control.Monad
 import Control.Monad.State.Strict
 import Data.Foldable
 import Data.List (elemIndex, isPrefixOf, sort)
@@ -139,6 +140,7 @@
   m (Maybe (VName, Slice SubExp))
 
 traverseKernelBodyArrayIndexes ::
+  forall f.
   Monad f =>
   Names ->
   Names ->
@@ -157,6 +159,7 @@
       (stmsToList kstms)
     <*> pure kres
   where
+    onLambda :: (VarianceTable, Scope GPU) -> Lambda GPU -> f (Lambda GPU)
     onLambda (variance, scope) lam =
       (\body' -> lam {lambdaBody = body'})
         <$> onBody (variance, scope') (lambdaBody lam)
@@ -188,12 +191,18 @@
     onStm (variance, scope) (Let pat dec e) =
       Let pat dec <$> mapExpM (mapper (variance, scope)) e
 
+    onOp :: (VarianceTable, Scope GPU) -> Op GPU -> f (Op GPU)
     onOp ctx (OtherOp soac) =
-      OtherOp <$> mapSOACM identitySOACMapper {mapOnSOACLambda = onLambda ctx} soac
+      OtherOp <$> mapSOACM (soacMapper ctx) soac
     onOp _ op = pure op
 
+    soacMapper ctx =
+      (identitySOACMapper @GPU)
+        { mapOnSOACLambda = onLambda ctx
+        }
+
     mapper ctx =
-      identityMapper
+      (identityMapper @GPU)
         { mapOnBody = const (onBody ctx),
           mapOnOp = onOp ctx
         }
diff --git a/src/Futhark/Pipeline.hs b/src/Futhark/Pipeline.hs
--- a/src/Futhark/Pipeline.hs
+++ b/src/Futhark/Pipeline.hs
@@ -28,7 +28,6 @@
 import Control.Monad.Except
 import Control.Monad.Reader
 import Control.Monad.State
-import Control.Monad.Writer.Strict hiding (pass)
 import Control.Parallel
 import Data.Text qualified as T
 import Data.Text.IO qualified as T
diff --git a/src/Futhark/Pkg/Solve.hs b/src/Futhark/Pkg/Solve.hs
--- a/src/Futhark/Pkg/Solve.hs
+++ b/src/Futhark/Pkg/Solve.hs
@@ -10,6 +10,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Free.Church
 import Control.Monad.State
 import Data.Map qualified as M
diff --git a/src/Futhark/Script.hs b/src/Futhark/Script.hs
--- a/src/Futhark/Script.hs
+++ b/src/Futhark/Script.hs
@@ -31,7 +31,9 @@
   )
 where
 
-import Control.Monad.Except
+import Control.Monad
+import Control.Monad.Except (MonadError (..))
+import Control.Monad.IO.Class (MonadIO, liftIO)
 import Data.Bifunctor (bimap)
 import Data.ByteString.Lazy qualified as LBS
 import Data.Char
@@ -43,6 +45,7 @@
 import Data.Set qualified as S
 import Data.Text qualified as T
 import Data.Traversable
+import Data.Vector.Storable qualified as SVec
 import Data.Void
 import Data.Word (Word8)
 import Futhark.Data.Parser qualified as V
@@ -291,6 +294,50 @@
 valueToExp (V.ValueTuple fs) =
   Tuple $ map valueToExp fs
 
+-- Decompose a type name into a rank and an element type.
+parseTypeName :: TypeName -> Maybe (Int, V.PrimType)
+parseTypeName s
+  | Just pt <- lookup s m =
+      Just (0, pt)
+  | "[]" `T.isPrefixOf` s = do
+      (d, pt) <- parseTypeName (T.drop 2 s)
+      pure (d + 1, pt)
+  | otherwise = Nothing
+  where
+    prims = [minBound .. maxBound]
+    primtexts = map (V.valueTypeText . V.ValueType []) prims
+    m = zip primtexts prims
+
+coerceValue :: TypeName -> V.Value -> Maybe V.Value
+coerceValue t v = do
+  (_, pt) <- parseTypeName t
+  case v of
+    V.I8Value shape vs ->
+      coerceInts pt shape $ map toInteger $ SVec.toList vs
+    V.I16Value shape vs ->
+      coerceInts pt shape $ map toInteger $ SVec.toList vs
+    V.I32Value shape vs ->
+      coerceInts pt shape $ map toInteger $ SVec.toList vs
+    V.I64Value shape vs ->
+      coerceInts pt shape $ map toInteger $ SVec.toList vs
+    _ ->
+      Nothing
+  where
+    coerceInts V.I8 shape =
+      Just . V.I8Value shape . SVec.fromList . map fromInteger
+    coerceInts V.I16 shape =
+      Just . V.I16Value shape . SVec.fromList . map fromInteger
+    coerceInts V.I32 shape =
+      Just . V.I32Value shape . SVec.fromList . map fromInteger
+    coerceInts V.I64 shape =
+      Just . V.I64Value shape . SVec.fromList . map fromInteger
+    coerceInts V.F32 shape =
+      Just . V.F32Value shape . SVec.fromList . map fromInteger
+    coerceInts V.F64 shape =
+      Just . V.F64Value shape . SVec.fromList . map fromInteger
+    coerceInts _ _ =
+      const Nothing
+
 -- | How to evaluate a builtin function.
 type EvalBuiltin m = T.Text -> [V.CompoundValue] -> m V.CompoundValue
 
@@ -391,6 +438,8 @@
       -- FutharkScript tuples/records to Futhark-level tuples/records,
       -- as well as maps between different names for the same
       -- tuple/record.
+      --
+      -- We also implicitly convert the types of constants.
       interValToVar :: m VarName -> TypeName -> ExpValue -> m VarName
       interValToVar _ t (V.ValueAtom v)
         | STValue t == scriptValueType v = scriptValueToVar v
@@ -407,6 +456,9 @@
           Just vt_fs <- isRecord vt types,
           vt_fs == t_fs =
             mkRecord t =<< mapM (getField v) vt_fs
+      interValToVar _ t (V.ValueAtom (SValue _ (VVal v)))
+        | Just v' <- coerceValue t v =
+            scriptValueToVar $ SValue t $ VVal v'
       interValToVar bad _ _ = bad
 
       valToInterVal :: V.CompoundValue -> ExpValue
diff --git a/src/Futhark/Test.hs b/src/Futhark/Test.hs
--- a/src/Futhark/Test.hs
+++ b/src/Futhark/Test.hs
@@ -32,7 +32,8 @@
 import Control.Exception (catch)
 import Control.Exception.Base qualified as E
 import Control.Monad
-import Control.Monad.Except
+import Control.Monad.Except (MonadError (..))
+import Control.Monad.IO.Class (MonadIO, liftIO)
 import Data.Binary qualified as Bin
 import Data.ByteString qualified as SBS
 import Data.ByteString.Lazy qualified as BS
diff --git a/src/Futhark/Tools.hs b/src/Futhark/Tools.hs
--- a/src/Futhark/Tools.hs
+++ b/src/Futhark/Tools.hs
@@ -15,7 +15,7 @@
   )
 where
 
-import Control.Monad.Identity
+import Control.Monad
 import Futhark.Analysis.PrimExp.Convert
 import Futhark.Construct
 import Futhark.IR
diff --git a/src/Futhark/Transform/FirstOrderTransform.hs b/src/Futhark/Transform/FirstOrderTransform.hs
--- a/src/Futhark/Transform/FirstOrderTransform.hs
+++ b/src/Futhark/Transform/FirstOrderTransform.hs
@@ -16,7 +16,7 @@
   )
 where
 
-import Control.Monad.Except
+import Control.Monad
 import Control.Monad.State
 import Data.List (find, zip4)
 import Data.Map.Strict qualified as M
diff --git a/src/Futhark/Util.hs b/src/Futhark/Util.hs
--- a/src/Futhark/Util.hs
+++ b/src/Futhark/Util.hs
@@ -13,8 +13,6 @@
     maxinum,
     chunk,
     chunks,
-    pairs,
-    unpairs,
     dropAt,
     takeLast,
     dropLast,
@@ -133,18 +131,6 @@
   let (bef, aft) = splitAt n xs
    in bef : chunks ns aft
 
--- | @pairs l@ chunks the list into pairs of consecutive elements,
--- ignoring any excess element.  Example: @pairs [a,b,c,d] ==
--- [(a,b),(c,d)]@.
-pairs :: [a] -> [(a, a)]
-pairs (a : b : l) = (a, b) : pairs l
-pairs _ = []
-
--- | The opposite of 'pairs': @unpairs [(a,b),(c,d)] = [a,b,c,d]@.
-unpairs :: [(a, a)] -> [a]
-unpairs [] = []
-unpairs ((a, b) : l) = a : b : unpairs l
-
 -- | Like 'maximum', but returns zero for an empty list.
 maxinum :: (Num a, Ord a, Foldable f) => f a -> a
 maxinum = foldl' max 0
@@ -435,7 +421,7 @@
 encodeAsUnicodeCharar :: Char -> EncodedString
 encodeAsUnicodeCharar c =
   'z'
-    : if isDigit (head hex_str)
+    : if maybe False isDigit $ maybeHead hex_str
       then hex_str
       else '0' : hex_str
   where
diff --git a/src/Futhark/Util/Pretty.hs b/src/Futhark/Util/Pretty.hs
--- a/src/Futhark/Util/Pretty.hs
+++ b/src/Futhark/Util/Pretty.hs
@@ -31,7 +31,6 @@
     commastack,
     commasep,
     semistack,
-    semisep,
     stack,
     parensIf,
     ppTuple',
@@ -168,17 +167,13 @@
 commastack :: [Doc a] -> Doc a
 commastack = align . vsep . punctuate comma
 
--- | Like 'semisep', but a newline after every semicolon.
+-- | Separate with semicolons and newlines.
 semistack :: [Doc a] -> Doc a
 semistack = align . vsep . punctuate semi
 
 -- | Separate with commas.
 commasep :: [Doc a] -> Doc a
 commasep = hsep . punctuate comma
-
--- | Separate with semicolons.
-semisep :: [Doc a] -> Doc a
-semisep = hsep . punctuate semi
 
 -- | Separate with linebreaks.
 stack :: [Doc a] -> Doc a
diff --git a/src/Language/Futhark/Interpreter.hs b/src/Language/Futhark/Interpreter.hs
--- a/src/Language/Futhark/Interpreter.hs
+++ b/src/Language/Futhark/Interpreter.hs
@@ -26,7 +26,7 @@
   )
 where
 
-import Control.Monad.Except
+import Control.Monad
 import Control.Monad.Free.Church
 import Control.Monad.Identity
 import Control.Monad.Reader
@@ -132,6 +132,16 @@
 getSizes :: EvalM Sizes
 getSizes = get
 
+-- | Disregard any existential sizes computed during this action.
+-- This is used so that existentials computed during one iteration of
+-- a loop or a function call are not remembered the next time around.
+localExts :: EvalM a -> EvalM a
+localExts m = do
+  s <- get
+  x <- m
+  put s
+  pure x
+
 extSizeEnv :: EvalM Env
 extSizeEnv = i64Env <$> getSizes
 
@@ -265,18 +275,14 @@
 -- | The actual type- and value environment.
 data Env = Env
   { envTerm :: M.Map VName TermBinding,
-    envType :: M.Map VName T.TypeBinding,
-    -- | A mapping from type parameters to the shapes of
-    -- the value to which they were initially bound.
-    envShapes :: M.Map VName ValueShape
+    envType :: M.Map VName T.TypeBinding
   }
 
 instance Monoid Env where
-  mempty = Env mempty mempty mempty
+  mempty = Env mempty mempty
 
 instance Semigroup Env where
-  Env vm1 tm1 sm1 <> Env vm2 tm2 sm2 =
-    Env (vm1 <> vm2) (tm1 <> tm2) (sm1 <> sm2)
+  Env vm1 tm1 <> Env vm2 tm2 = Env (vm1 <> vm2) (tm1 <> tm2)
 
 -- | An error occurred during interpretation due to an error in the
 -- user program.  Actual interpreter errors will be signaled with an
@@ -291,24 +297,21 @@
 valEnv m =
   Env
     { envTerm = M.map (uncurry TermValue) m,
-      envType = mempty,
-      envShapes = mempty
+      envType = mempty
     }
 
 modEnv :: M.Map VName Module -> Env
 modEnv m =
   Env
     { envTerm = M.map TermModule m,
-      envType = mempty,
-      envShapes = mempty
+      envType = mempty
     }
 
 typeEnv :: M.Map VName StructType -> Env
 typeEnv m =
   Env
     { envTerm = mempty,
-      envType = M.map tbind m,
-      envShapes = mempty
+      envType = M.map tbind m
     }
   where
     tbind = T.TypeAbbr Unlifted [] . RetType []
@@ -544,23 +547,16 @@
 
 -- | Expand type based on information that was not available at
 -- type-checking time (the structure of abstract types).
-evalType :: Env -> StructType -> StructType
-evalType _ (Scalar (Prim pt)) = Scalar $ Prim pt
-evalType env (Scalar (Record fs)) = Scalar $ Record $ fmap (evalType env) fs
-evalType env (Scalar (Arrow () p d t1 (RetType dims t2))) =
-  Scalar $ Arrow () p d (evalType env t1) (RetType dims (evalType env t2))
-evalType env t@(Array _ u shape _) =
+expandType :: Env -> StructType -> StructType
+expandType _ (Scalar (Prim pt)) = Scalar $ Prim pt
+expandType env (Scalar (Record fs)) = Scalar $ Record $ fmap (expandType env) fs
+expandType env (Scalar (Arrow () p d t1 (RetType dims t2))) =
+  Scalar $ Arrow () p d (expandType env t1) (RetType dims (expandType env t2))
+expandType env t@(Array _ u shape _) =
   let et = stripArray (shapeRank shape) t
-      et' = evalType env et
-      shape' = fmap evalDim shape
-   in arrayOf u shape' et'
-  where
-    evalDim (NamedSize qn)
-      | Just (TermValue _ (ValuePrim (SignedValue (Int64Value x)))) <-
-          lookupVar qn env =
-          ConstSize $ fromIntegral x
-    evalDim d = d
-evalType env t@(Scalar (TypeVar () _ tn args)) =
+      et' = expandType env et
+   in arrayOf u shape et'
+expandType env t@(Scalar (TypeVar () _ tn args)) =
   case lookupType tn env of
     Just (T.TypeAbbr _ ps (RetType _ t')) ->
       let (substs, types) = mconcat $ zipWith matchPtoA ps args
@@ -568,7 +564,7 @@
           onDim d = d
        in if null ps
             then first onDim t'
-            else evalType (Env mempty types mempty <> env) $ first onDim t'
+            else expandType (Env mempty types <> env) $ first onDim t'
     Nothing -> t
   where
     matchPtoA (TypeParamDim p _) (TypeArgDim (NamedSize qv) _) =
@@ -576,25 +572,35 @@
     matchPtoA (TypeParamDim p _) (TypeArgDim (ConstSize k) _) =
       (M.singleton p $ ConstSize k, mempty)
     matchPtoA (TypeParamType l p _) (TypeArgType t' _) =
-      let t'' = evalType env t'
+      let t'' = expandType env t'
        in (mempty, M.singleton p $ T.TypeAbbr l [] $ RetType [] t'')
     matchPtoA _ _ = mempty
-evalType env (Scalar (Sum cs)) = Scalar $ Sum $ (fmap . fmap) (evalType env) cs
+expandType env (Scalar (Sum cs)) = Scalar $ Sum $ (fmap . fmap) (expandType env) cs
 
+-- | First expand type abbreviations, then evaluate all possible
+-- sizes.
+evalType :: Env -> StructType -> EvalM StructType
+evalType outer_env t = do
+  size_env <- extSizeEnv
+  let env = size_env <> outer_env
+      evalDim (NamedSize qn)
+        | Just (TermValue _ (ValuePrim (SignedValue (Int64Value x)))) <-
+            lookupVar qn env =
+            ConstSize $ fromIntegral x
+      evalDim d = d
+  pure $ first evalDim $ expandType env t
+
 evalTermVar :: Env -> QualName VName -> StructType -> EvalM Value
 evalTermVar env qv t =
   case lookupVar qv env of
-    Just (TermPoly _ v) -> do
-      size_env <- extSizeEnv
-      v $ evalType (size_env <> env) t
+    Just (TermPoly _ v) -> v =<< evalType env t
     Just (TermValue _ v) -> pure v
     _ -> error $ "\"" <> prettyString qv <> "\" is not bound to a value."
 
 typeValueShape :: Env -> StructType -> EvalM ValueShape
 typeValueShape env t = do
-  size_env <- extSizeEnv
-  let t' = evalType (size_env <> env) t
-  case traverse dim $ typeShape mempty t' of
+  t' <- evalType env t
+  case traverse dim $ typeShape t' of
     Nothing -> error $ "typeValueShape: failed to fully evaluate type " <> prettyString t'
     Just shape -> pure shape
   where
@@ -612,24 +618,23 @@
   etaExpand [] env rettype
   where
     etaExpand vs env' (Scalar (Arrow _ _ _ pt (RetType _ rt))) =
-      pure $
-        ValueFun $ \v -> do
-          env'' <- matchPat env' (Wildcard (Info $ fromStruct pt) noLoc) v
-          etaExpand (v : vs) env'' rt
+      pure . ValueFun $ \v -> do
+        env'' <- matchPat env' (Wildcard (Info $ fromStruct pt) noLoc) v
+        etaExpand (v : vs) env'' rt
     etaExpand vs env' _ = do
-      f <- eval env' body
+      f <- localExts $ eval env' body
       foldM (apply noLoc mempty) f $ reverse vs
 evalFunction env missing_sizes (p : ps) body rettype =
-  pure $
-    ValueFun $ \v -> do
-      env' <- matchPat env p v
-      -- Fix up the last sizes, if any.
-      let p_t = evalType env $ patternStructType p
-          env''
-            | null missing_sizes = env'
-            | otherwise =
-                env' <> i64Env (resolveExistentials missing_sizes p_t (valueShape v))
-      evalFunction env'' missing_sizes ps body rettype
+  pure . ValueFun $ \v -> do
+    env' <- matchPat env p v
+    -- Fix up the last sizes, if any.
+    let p_t = expandType env $ patternStructType p
+        env''
+          | null missing_sizes =
+              env'
+          | otherwise =
+              env' <> i64Env (resolveExistentials missing_sizes p_t (valueShape v))
+    evalFunction env'' missing_sizes ps body rettype
 
 evalFunctionBinding ::
   Env ->
@@ -639,9 +644,8 @@
   Exp ->
   EvalM TermBinding
 evalFunctionBinding env tparams ps ret fbody = do
-  let ret' = evalType env $ retType ret
-      arrow (xp, d, xt) yt = Scalar $ Arrow () xp d xt $ RetType [] yt
-      ftype = foldr (arrow . patternParam) ret' ps
+  let arrow (xp, d, xt) yt = Scalar $ Arrow () xp d xt $ RetType [] yt
+      ftype = foldr (arrow . patternParam) (retType ret) ps
       retext = case ps of
         [] -> retDims ret
         _ -> []
@@ -649,21 +653,22 @@
   -- Distinguish polymorphic and non-polymorphic bindings here.
   if null tparams
     then
-      TermValue (Just $ T.BoundV [] ftype)
-        <$> (returned env (retType ret) retext =<< evalFunction env [] ps fbody ret')
-    else pure $
-      TermPoly (Just $ T.BoundV [] ftype) $ \ftype' -> do
-        let tparam_names = map typeParamName tparams
-            env' = resolveTypeParams tparam_names ftype ftype' <> env
+      fmap (TermValue (Just $ T.BoundV [] ftype))
+        . returned env (retType ret) retext
+        =<< evalFunction env [] ps fbody (retType ret)
+    else pure . TermPoly (Just $ T.BoundV [] ftype) $ \ftype' -> do
+      let tparam_names = map typeParamName tparams
+          env' = resolveTypeParams tparam_names ftype ftype' <> env
 
-            -- In some cases (abstract lifted types) there may be
-            -- missing sizes that were not fixed by the type
-            -- instantiation.  These will have to be set by looking
-            -- at the actual function arguments.
-            missing_sizes =
-              filter (`M.notMember` envTerm env') $
-                map typeParamName (filter isSizeParam tparams)
-        returned env (retType ret) retext =<< evalFunction env' missing_sizes ps fbody ret'
+          -- In some cases (abstract lifted types) there may be
+          -- missing sizes that were not fixed by the type
+          -- instantiation.  These will have to be set by looking
+          -- at the actual function arguments.
+          missing_sizes =
+            filter (`M.notMember` envTerm env') $
+              map typeParamName (filter isSizeParam tparams)
+      returned env (retType ret) retext
+        =<< evalFunction env' missing_sizes ps fbody (retType ret)
 
 evalArg :: Env -> Exp -> Maybe VName -> EvalM Value
 evalArg env e ext = do
@@ -676,10 +681,9 @@
 returned :: Env -> TypeBase Size als -> [VName] -> Value -> EvalM Value
 returned _ _ [] v = pure v
 returned env ret retext v = do
-  mapM_ (uncurry putExtSize) $
-    M.toList $
-      resolveExistentials retext (evalType env $ toStruct ret) $
-        valueShape v
+  mapM_ (uncurry putExtSize) . M.toList $
+    resolveExistentials retext (expandType env $ toStruct ret) $
+      valueShape v
   pure v
 
 evalAppExp :: Env -> StructType -> AppExp -> EvalM Value
@@ -733,7 +737,7 @@
         <> " is invalid."
 evalAppExp env t (Coerce e te loc) = do
   v <- eval env e
-  case checkShape (structTypeShape (envShapes env) t) (valueShape v) of
+  case checkShape (structTypeShape t) (valueShape v) of
     Just _ -> pure v
     Nothing ->
       bad loc env . docText $
@@ -749,7 +753,7 @@
 evalAppExp env _ (LetPat sizes p e body _) = do
   v <- eval env e
   env' <- matchPat env p v
-  let p_t = evalType env $ patternStructType p
+  let p_t = expandType env $ patternStructType p
       v_s = valueShape v
       env'' = env' <> i64Env (resolveExistentials (map sizeName sizes) p_t v_s)
   eval env'' body
@@ -826,34 +830,34 @@
     inc = (`P.doAdd` Int64Value 1)
     zero = (`P.doMul` Int64Value 0)
 
+    evalBody env' = localExts $ eval env' body
+
+    forLoopEnv iv i =
+      valEnv
+        ( M.singleton
+            iv
+            ( Just $ T.BoundV [] $ Scalar $ Prim $ Signed Int64,
+              ValuePrim (SignedValue i)
+            )
+        )
+
     forLoop iv bound i v
       | i >= bound = pure v
       | otherwise = do
           env' <- withLoopParams v
-          forLoop iv bound (inc i)
-            =<< eval
-              ( valEnv
-                  ( M.singleton
-                      iv
-                      ( Just $ T.BoundV [] $ Scalar $ Prim $ Signed Int64,
-                        ValuePrim (SignedValue i)
-                      )
-                  )
-                  <> env'
-              )
-              body
+          forLoop iv bound (inc i) =<< evalBody (forLoopEnv iv i <> env')
 
     whileLoop cond v = do
       env' <- withLoopParams v
       continue <- asBool <$> eval env' cond
       if continue
-        then whileLoop cond =<< eval env' body
+        then whileLoop cond =<< evalBody env'
         else pure v
 
     forInLoop in_pat v in_v = do
       env' <- withLoopParams v
       env'' <- matchPat env' in_pat in_v
-      eval env'' body
+      evalBody env''
 evalAppExp env _ (Match e cs _) = do
   v <- eval env e
   match v (NE.toList cs)
@@ -897,10 +901,9 @@
   v' <- eval env v
   vs' <- mapM (eval env) vs
   pure $ toArray' (valueShape v') (v' : vs')
-eval env (AppExp e (Info (AppRes t retext))) =
+eval env (AppExp e (Info (AppRes t retext))) = do
+  t' <- evalType env $ toStruct t
   returned env t' retext =<< evalAppExp env t' e
-  where
-    t' = evalType env $ toStruct t
 eval env (Var qv (Info t) _) = evalTermVar env qv (toStruct t)
 eval env (Ascript e _ _) = eval env e
 eval _ (IntLit v (Info t) _) =
@@ -959,7 +962,8 @@
 -- convenient in the interpreter.
 eval env (Lambda ps body _ (Info (_, RetType _ rt)) _) =
   evalFunction env [] ps body rt
-eval env (OpSection qv (Info t) _) = evalTermVar env qv $ toStruct t
+eval env (OpSection qv (Info t) _) =
+  evalTermVar env qv $ toStruct t
 eval env (OpSectionLeft qv _ e (Info (_, _, argext), _) (Info (RetType _ t), _) loc) = do
   v <- evalArg env e argext
   f <- evalTermVar env qv (toStruct t)
@@ -1032,8 +1036,8 @@
       (k, v) <- M.toList m
       k' <- replace k
       pure (k', f v)
-    onModule (Module (Env terms types _)) =
-      Module $ Env (replaceM onTerm terms) (replaceM onType types) mempty
+    onModule (Module (Env terms types)) =
+      Module $ Env (replaceM onTerm terms) (replaceM onType types)
     onModule (ModuleFun f) =
       ModuleFun $ \m -> onModule <$> f (substituteInModule substs m)
     onTerm (TermValue t v) = TermValue t v
@@ -1063,14 +1067,13 @@
           ]
     Just m -> pure $ Module m
 evalModExp env (ModDecs ds _) = do
-  Env terms types _ <- foldM evalDec env ds
+  Env terms types <- foldM evalDec env ds
   -- Remove everything that was present in the original Env.
   pure $
     Module $
       Env
         (terms `M.difference` envTerm env)
         (types `M.difference` envType env)
-        mempty
 evalModExp env (ModVar qv _) =
   evalModuleVar env qv
 evalModExp env (ModAscript me _ (Info substs) _) =
@@ -1105,7 +1108,7 @@
 evalDec env (LocalDec d _) = evalDec env d
 evalDec env SigDec {} = pure env
 evalDec env (TypeDec (TypeBind v l ps _ (Info (RetType dims t)) _ _)) = do
-  let abbr = T.TypeAbbr l ps . RetType dims $ evalType env t
+  let abbr = T.TypeAbbr l ps . RetType dims $ expandType env t
   pure env {envType = M.insert v abbr $ envType env}
 evalDec env (ModDec (ModBind v ps ret body _ loc)) = do
   mod <- evalModExp env $ wrapInLambda ps
@@ -1152,11 +1155,10 @@
     ( Env
         ( M.insert
             (VName (nameFromString "intrinsics") 0)
-            (TermModule (Module $ Env terms types mempty))
+            (TermModule (Module $ Env terms types))
             terms
         )
         types
-        mempty
     )
     mempty
   where
@@ -1477,13 +1479,13 @@
               | Just rowshape <- typeRowShape ret_t ->
                   toArray' rowshape <$> mapM (apply noLoc mempty f) (snd $ fromArray xs)
               | otherwise ->
-                  error $ "Bad pure type: " <> prettyString ret_t
+                  error $ "Bad return type: " <> prettyString ret_t
             _ ->
               error $
                 "Invalid arguments to map intrinsic:\n"
                   ++ unlines [prettyString t, show f, show xs]
       where
-        typeRowShape = sequenceA . structTypeShape mempty . stripArray 1
+        typeRowShape = sequenceA . structTypeShape . stripArray 1
     def s | "reduce" `isPrefixOf` s = Just $
       fun3 $ \f ne xs ->
         foldM (apply2 noLoc mempty f) ne $ snd $ fromArray xs
diff --git a/src/Language/Futhark/Interpreter/Values.hs b/src/Language/Futhark/Interpreter/Values.hs
--- a/src/Language/Futhark/Interpreter/Values.hs
+++ b/src/Language/Futhark/Interpreter/Values.hs
@@ -76,27 +76,21 @@
 emptyShape (ShapeDim d s) = d == 0 || emptyShape s
 emptyShape _ = False
 
-typeShape :: M.Map VName (Shape d) -> TypeBase d () -> Shape d
-typeShape shapes = go
-  where
-    go (Array _ _ shape et) =
-      foldr ShapeDim (go (Scalar et)) $ shapeDims shape
-    go (Scalar (Record fs)) =
-      ShapeRecord $ M.map go fs
-    go (Scalar (Sum cs)) =
-      ShapeSum $ M.map (map go) cs
-    go (Scalar (TypeVar _ _ (QualName [] v) []))
-      | Just shape <- M.lookup v shapes =
-          shape
-    go _ =
-      ShapeLeaf
+typeShape :: TypeBase d () -> Shape d
+typeShape (Array _ _ shape et) =
+  foldr ShapeDim (typeShape (Scalar et)) $ shapeDims shape
+typeShape (Scalar (Record fs)) =
+  ShapeRecord $ M.map typeShape fs
+typeShape (Scalar (Sum cs)) =
+  ShapeSum $ M.map (map typeShape) cs
+typeShape _ =
+  ShapeLeaf
 
-structTypeShape :: M.Map VName ValueShape -> StructType -> Shape (Maybe Int64)
-structTypeShape shapes = fmap dim . typeShape shapes'
+structTypeShape :: StructType -> Shape (Maybe Int64)
+structTypeShape = fmap dim . typeShape
   where
     dim (ConstSize d) = Just $ fromIntegral d
     dim _ = Nothing
-    shapes' = M.map (fmap $ ConstSize . fromIntegral) shapes
 
 -- | A fully evaluated Futhark value.
 data Value m
diff --git a/src/Language/Futhark/Parser/Monad.hs b/src/Language/Futhark/Parser/Monad.hs
--- a/src/Language/Futhark/Parser/Monad.hs
+++ b/src/Language/Futhark/Parser/Monad.hs
@@ -38,9 +38,11 @@
 
 import Control.Applicative (liftA)
 import Control.Monad
-import Control.Monad.Except
+import Control.Monad.Except (ExceptT, MonadError (..), runExceptT)
+import Control.Monad.Trans.Class (lift)
 import Control.Monad.Trans.State
 import Data.Array hiding (index)
+import Data.List.NonEmpty qualified as NE
 import Data.Monoid
 import Data.Text qualified as T
 import Futhark.Util.Loc
@@ -127,11 +129,11 @@
 arrayFromList :: [a] -> Array Int a
 arrayFromList l = listArray (0, length l - 1) l
 
-applyExp :: [UncheckedExp] -> ParserMonad UncheckedExp
-applyExp all_es@((Constr n [] _ loc1) : es) =
-  pure $ Constr n es NoInfo (srcspan loc1 (last all_es))
+applyExp :: NE.NonEmpty UncheckedExp -> ParserMonad UncheckedExp
+applyExp all_es@((Constr n [] _ loc1) NE.:| es) =
+  pure $ Constr n es NoInfo (srcspan loc1 (NE.last all_es))
 applyExp es =
-  foldM op (head es) (tail es)
+  foldM op (NE.head es) (NE.tail es)
   where
     op (AppExp (Index e is floc) _) (ArrayLit xs _ xloc) =
       parseErrorAt (srcspan floc xloc) . Just . docText $
diff --git a/src/Language/Futhark/Parser/Parser.y b/src/Language/Futhark/Parser/Parser.y
--- a/src/Language/Futhark/Parser/Parser.y
+++ b/src/Language/Futhark/Parser/Parser.y
@@ -581,11 +581,11 @@
 
      | ApplyList {% applyExp $1 }
 
-ApplyList :: { [UncheckedExp] }
-          : ApplyList Atom %prec juxtprec
-            { $1 ++ [$2] }
+ApplyList :: { NE.NonEmpty UncheckedExp }
+          : Atom ApplyList %prec juxtprec
+            { NE.cons $1 $2 }
           | Atom %prec juxtprec
-            { [$1] }
+            { NE.singleton $1 }
 
 Atom :: { UncheckedExp }
 Atom : PrimLit        { Literal (fst $1) (srclocOf (snd $1)) }
diff --git a/src/Language/Futhark/Prop.hs b/src/Language/Futhark/Prop.hs
--- a/src/Language/Futhark/Prop.hs
+++ b/src/Language/Futhark/Prop.hs
@@ -97,6 +97,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.State
 import Data.Bifoldable
 import Data.Bifunctor
diff --git a/src/Language/Futhark/Query.hs b/src/Language/Futhark/Query.hs
--- a/src/Language/Futhark/Query.hs
+++ b/src/Language/Futhark/Query.hs
@@ -88,7 +88,7 @@
         Lambda params _ _ _ _ ->
           mconcat (map patternDefs params)
         AppExp (LetFun name (tparams, params, _, Info ret, _) _ loc) _ ->
-          let name_t = foldFunType (map (undefined . patternStructType) params) ret
+          let name_t = foldFunTypeFromParams params ret
            in M.singleton name (DefBound $ BoundTerm name_t (locOf loc))
                 <> mconcat (map typeParamDefs tparams)
                 <> mconcat (map patternDefs params)
@@ -111,7 +111,7 @@
       <> expDefs (valBindBody vbind)
   where
     vbind_t =
-      foldFunType (map (undefined . patternStructType) (valBindParams vbind)) $
+      foldFunTypeFromParams (valBindParams vbind) $
         unInfo $
           valBindRetType vbind
 
diff --git a/src/Language/Futhark/Syntax.hs b/src/Language/Futhark/Syntax.hs
--- a/src/Language/Futhark/Syntax.hs
+++ b/src/Language/Futhark/Syntax.hs
@@ -286,6 +286,9 @@
 instance Bitraversable RetTypeBase where
   bitraverse f g (RetType dims t) = RetType dims <$> bitraverse f g t
 
+instance Functor (RetTypeBase dim) where
+  fmap = second
+
 instance Bifunctor RetTypeBase where
   bimap = bimapDefault
 
@@ -314,6 +317,9 @@
     Arrow <$> g als <*> pure v <*> pure d <*> bitraverse f pure t1 <*> bitraverse f g t2
   bitraverse f g (Sum cs) = Sum <$> (traverse . traverse) (bitraverse f g) cs
 
+instance Functor (ScalarTypeBase dim) where
+  fmap = second
+
 instance Bifunctor ScalarTypeBase where
   bimap = bimapDefault
 
@@ -334,6 +340,9 @@
   bitraverse f g (Scalar t) = Scalar <$> bitraverse f g t
   bitraverse f g (Array a u shape t) =
     Array <$> g a <*> pure u <*> traverse f shape <*> bitraverse f pure t
+
+instance Functor (TypeBase dim) where
+  fmap = second
 
 instance Bifunctor TypeBase where
   bimap = bimapDefault
diff --git a/src/Language/Futhark/TypeChecker.hs b/src/Language/Futhark/TypeChecker.hs
--- a/src/Language/Futhark/TypeChecker.hs
+++ b/src/Language/Futhark/TypeChecker.hs
@@ -18,10 +18,11 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Except
-import Control.Monad.Writer hiding (Sum)
 import Data.Bifunctor (first, second)
 import Data.Either
+import Data.List qualified as L
 import Data.Map.Strict qualified as M
 import Data.Maybe
 import Data.Ord
@@ -552,6 +553,15 @@
 checkTypeBind (TypeBind name l tps te NoInfo doc loc) =
   checkTypeParams tps $ \tps' -> do
     (te', svars, RetType dims t, l') <- bindingTypeParams tps' $ checkTypeExp te
+
+    let (witnessed, _) = determineSizeWitnesses t
+    case L.find (`S.notMember` witnessed) svars of
+      Just _ ->
+        typeError (locOf te) mempty . withIndexLink "anonymous-nonconstructive" $
+          "Type abbreviation contains an anonymous size not used constructively as an array size."
+      Nothing ->
+        pure ()
+
     let elab_t = RetType (svars ++ dims) t
 
     let used_dims = freeInType t
diff --git a/src/Language/Futhark/TypeChecker/Modules.hs b/src/Language/Futhark/TypeChecker/Modules.hs
--- a/src/Language/Futhark/TypeChecker/Modules.hs
+++ b/src/Language/Futhark/TypeChecker/Modules.hs
@@ -8,8 +8,7 @@
   )
 where
 
-import Control.Monad.Except
-import Control.Monad.Writer hiding (Sum)
+import Control.Monad
 import Data.Either
 import Data.List (intersect)
 import Data.Map.Strict qualified as M
diff --git a/src/Language/Futhark/TypeChecker/Monad.hs b/src/Language/Futhark/TypeChecker/Monad.hs
--- a/src/Language/Futhark/TypeChecker/Monad.hs
+++ b/src/Language/Futhark/TypeChecker/Monad.hs
@@ -49,6 +49,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Except
 import Control.Monad.Reader
 import Control.Monad.State.Strict
diff --git a/src/Language/Futhark/TypeChecker/Terms.hs b/src/Language/Futhark/TypeChecker/Terms.hs
--- a/src/Language/Futhark/TypeChecker/Terms.hs
+++ b/src/Language/Futhark/TypeChecker/Terms.hs
@@ -12,6 +12,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Except
 import Control.Monad.Reader
 import Control.Monad.State
diff --git a/src/Language/Futhark/TypeChecker/Terms/DoLoop.hs b/src/Language/Futhark/TypeChecker/Terms/DoLoop.hs
--- a/src/Language/Futhark/TypeChecker/Terms/DoLoop.hs
+++ b/src/Language/Futhark/TypeChecker/Terms/DoLoop.hs
@@ -8,7 +8,7 @@
   )
 where
 
-import Control.Monad.Except
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.Bifunctor
diff --git a/src/Language/Futhark/TypeChecker/Terms/Monad.hs b/src/Language/Futhark/TypeChecker/Terms/Monad.hs
--- a/src/Language/Futhark/TypeChecker/Terms/Monad.hs
+++ b/src/Language/Futhark/TypeChecker/Terms/Monad.hs
@@ -75,6 +75,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Except
 import Control.Monad.Reader
 import Control.Monad.State
diff --git a/src/Language/Futhark/TypeChecker/Terms/Pat.hs b/src/Language/Futhark/TypeChecker/Terms/Pat.hs
--- a/src/Language/Futhark/TypeChecker/Terms/Pat.hs
+++ b/src/Language/Futhark/TypeChecker/Terms/Pat.hs
@@ -10,7 +10,7 @@
   )
 where
 
-import Control.Monad.Except
+import Control.Monad
 import Control.Monad.State
 import Data.Bitraversable
 import Data.Either
diff --git a/src/Language/Futhark/TypeChecker/Types.hs b/src/Language/Futhark/TypeChecker/Types.hs
--- a/src/Language/Futhark/TypeChecker/Types.hs
+++ b/src/Language/Futhark/TypeChecker/Types.hs
@@ -22,6 +22,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Identity
 import Control.Monad.Reader
 import Control.Monad.State
diff --git a/src/Language/Futhark/TypeChecker/Unify.hs b/src/Language/Futhark/TypeChecker/Unify.hs
--- a/src/Language/Futhark/TypeChecker/Unify.hs
+++ b/src/Language/Futhark/TypeChecker/Unify.hs
@@ -30,6 +30,7 @@
   )
 where
 
+import Control.Monad
 import Control.Monad.Except
 import Control.Monad.State
 import Data.Bifunctor
@@ -925,7 +926,7 @@
 equalityType usage t = do
   unless (orderZero t) $
     unifyError usage mempty noBreadCrumbs $
-      "Type " <+> dquotes (pretty t) <+> "does not support equality (is higher-order)."
+      "Type " <+> dquotes (pretty t) <+> "does not support equality (may contain function)."
   mapM_ mustBeEquality $ typeVars t
   where
     mustBeEquality vn = do
