Fork me on GitHub

The End of FutharkScript

Posted on October 8, 2026

This post is about replacing FutharkScript, a bespoke and very limited language for talking to compiled Futhark programs, with actual Futhark. The main challenges are the interaction between interpreted and compiled Futhark code, and how to shoehorn IO into Futhark.

Background

Futhark is not a general-purpose language. Instead, a Futhark program is compiled to a library, which can then be invoked from other languages. Since it would be very awkward to write programs in other languages whenever you want to test, benchmark, or demonstrate Futhark code, we have some tools that can directly talk to compiled Futhark code. One of these is futhark literate, which is intended for writing documents that mix prose and the evaluation of Futhark code. For example, we can define a simple function:

def linspace (n: i64) (start: f64) (end: f64) : [n]f64 =
  tabulate n (\i -> start + f64.i64 i * ((end - start) / f64.i64 n))

And then use “directives” to show the result of evaluating the code on some input:

> linspace 5 1 20
[1.0f64, 4.8f64, 8.6f64, 12.399999999999999f64, 16.2f64]

In fact, this very blog post is a Literate Futhark program that has been automatically converted to Markdown by futhark literate, with the result of evaluation spliced in where appropriate.

Other directives allow us to interpret the results of Futhark code as images:

def linspace_2d n start end : [n][n](f64, f64) =
  map (\x -> map (\y -> (x, y)) (linspace n start end))
      (linspace n start end)

def spirals n v : [n][n]f64 =
  let f (x, y) = f64.sgn (f64.cos (f64.sqrt (x ** 2 + y ** 2)))
  in map (map f) (linspace_2d n (-v) v)
> :img spirals 200 30

This is a lovely feature that I use often. However, it does have some quirks. The largest quirk is that the code that you put in the directives (like spirals 200 30) is not actually real Futhark code, but a different language called FutharkScript, which looks so much like Futhark that it causes occasional confusion. It is an extremely austere language, without such features as arithmetic, or really any way of manipulating values. All FutharkScript can be used for is calling (compiled) Futhark entry point functions, and passing the results on to other entry points or return them from directives. FutharkScript is even dynamically typed, very much unlike Futhark itself.

On the upside, FutharkScript also has some features not found in Futhark itself, such as a magical $loaddata function that accepts a file name, interprets the file contents using Futhark’s binary data format, and returns the resulting values. This of course makes no sense in Futhark itself, but it is convenient for writing demonstrations or tests that operate on data stored in files - certainly more so than writing a small C or Python program that does the IO on Futhark’s behalf.

That said, being limited to FutharkScript sucks. It is very annoying that the evaluation directives used in futhark literate cannot show the evaluation of arbitrary Futhark code. The workaround is to put them in a normal definition, which we can then reference from FutharkScript, but this can get awkward, and the whole point of futhark literate is to produce documents that are nice to read.

It would be much nicer if directives could contain normal Futhark code. However, this runs into a constraint that I don’t want futhark literate to generate code - as far as the Futhark compiler is concerned, the directives are written as comments, and the program is compiled without reference to them. This is in order to ensure that the view that futhark literate has of a program is the same as that a normal program would have. This means that directives have to be interpreted. That is not a problem at first glance, since Futhark of course has an interpreter, but because our interpreter is so slow, we still want the ability to invoke compiled Futhark programs. Basically, this is the architecture of futhark literate:

  1. Compile the Futhark program in exactly the same way we would compile any Futhark program intended to be invoked by other programs, in particular not paying attention to directives.

  2. futhark literate loads the entire program into the interpreter, again not caring about directives.

  3. futhark literate now evaluates directives, dispatching any mentions of entry points to the compiled Futhark program.

Note that step 3 could also be “read and interpret Futhark expressions provided interactively by the user” - so this can also help make Futhark’s REPL more effective.

To replace FutharkScript with real Futhark, we need to figure out how to replace the only features FutharkScript possesses:

  1. How can the Futhark interpreter invoke compiled code?

  2. How do we allow interpreted Futhark code to perform IO?

How the Futhark interpreter invokes compiled code

This problem was worked on by Marcus Jensen, an MSc student here at DIKU. The basic idea is not so difficult: the Futhark interpreter has access to a compiled Futhark program, and it can invoke its entry points. Since we assume that we also have access to the source code, we essentially just intercept some function calls, namely the ones corresponding to entry points in the compiled program, and handle them differently. For example, suppose a Futhark program defines functions foo and bar where the latter is defined as an entry point, then compiling that program will produce a compiled version of bar. If we then in the context of the program execute foo (bar x), then bar will be run with compiled code, and the result passed on to foo, which will be interpreted. Our main interest is of course the case where the interpreted part comes from directives, or perhaps even directly from the user, in a REPL-like situation.

The main challenge is how to translate between the interpreter value representation and the compiled code value representation. We had to make a bunch of extensions to the C API and Futhark server protocol, because it turned out that we did not provide enough facilities for decomposing truly arbitrary Futhark values into their constituent parts, but now we do. (Except for an edge case related to arrays of sum types.) I consider it a testament to the strength of value-oriented programming that this kind of translation, although tedious, is ultimately not hard.

Another challenge, which is more thorny than really difficult, is bridging the essential manual memory management of Futhark’s external interface with the garbage-collection-based memory management of Futhark’s interpreter, which is written in Haskell. As previously covered on this channel, Futhark’s interpreter is explicitly written to be straightforward, so we did not want to rewrite it to use manual memory management through reference counting. Ultimately the solution is careful use of Haskell’s finalizers in a way that is clever, but ultimately just an implementation detail. For those curious about these details, I expect that Marcus will hand in his thesis one of these days. (The main challenge is that when a finalizer runs, the compiled Futhark program may not be in a state where it allows us to free objects, so instead we push it onto a work-queue for later.)

How we allow interpreted Futhark code to perform IO

The most unusual feature of FutharkScript compared to Futhark is the availability of functions for reading files. Now, Futhark really is pure with no escape, but the reason we cannot compromise on that is because it would make the aggressive optimisation we do in the Futhark compiler much more difficult to perform. Since the interpreter is specifically intended to evaluate the program in a “natural” way, it is actually possible to shoehorn in magical impure functions. In a way we already support that, as the trace function prints a message to the terminal. In FutharkScript, the impure functions are denoted by a leading $, but in order to not modify the syntax, I decided to expose this functionality to interpreted Futhark as a prelude module called io, which provides these functions, where [k]u8 should be be seen as a file name:

module io
: {
    -- | Return the contents of the given file as a byte array.
    val loadbytes [k] : [k]u8 -> ?[n].*[n]u8


    -- | Reads an image from the given file and returns it as a row-major
    -- array, with each pixel encoded as ARGB.
    val loadimg [k] : [k]u8 -> ?[n][m].*[n][m]u32


    -- | Read audio from the given file and returns it as a ``[][]f64``, where
    -- each row corresponds to a channel of the original soundfile.
    val loadaudio [k] : [k]u8 -> ?[n][m].*[n][m]f64


    -- | Load a Futhark value of known type (including size!) from the given
    -- file. If the type is a tuple, the file must contain one value for
    -- each element.
    val loadvalue 'a [k] : [k]u8 -> *a
  }

These correspond to what was available in FutharkScript, with one important change. In FutharkScript we have access to $loaddata, which returns the contents of a Futhark data file. Since the type of the result depends on the file contents, this does not fit Futhark’s static type discipline. Therefore, the replacement io.loadvalue function requires the type including the size to be known in advance. This is awkward in practice: if we want to read an array of integers from a file, we must know exactly how big the array is expected to be in advance. For example: io.loadvalue "array.data" : [100]i32. The root limitation is that Futhark’s size types do not allow a type parameter to be instantiated with an “existentially unknown size”. If we simply did io.loadvalue "array.data" : []i32, the type checker would complain about the size being ambiguous, except of course if something else constrains the return size in some way.

We did use $loaddata in various places for writing benchmarks, and so I had to change for example this input directive for a benchmark:

{ (256i64, $loaddata "data/randomSeq_100M_256.in") }

to instead read like so:

{ (256i64, io.loadvalue "data/randomSeq_100M_256.in" : [100000000]i32) }

Is this the end of the world? Not really - it is just a bit more of a hassle. We will have to see whether it is intolerable.

The functions in the io module are of course only usable in interpreted code, and if you attempt to use them in compiled Futhark code, the compiler will refuse to compile your program. There is actually a detail here that I am somewhat dissatisfied with, because the error is not detected by the type checker (which has no idea whether the code is “compiled” or “interpreted”), but far later in the compilation pipeline, which is contrary to our policy of compiling anything that passes the type checker. We may clean that up in the future, although I do not have much appetite for adding much complexity just to handle this edge case in an ideologically more pure way.

What happens next

The above is implemented as part of this pull request, which remains unmerged as of this writing. I intend to ruminate a bit more on the changes, but I will likely merge them soon. Apart from making futhark literate a bit more flexible, these changes also allow futhark repl to interpret expressions in the context of a compiled program, running all entry points at full compiled speed, while allowing the user to enter and run arbitrary interpreted Futhark code.