Floatyboat
A language and compiler for array calculations.
One of my first ideas for Floatyboat was being able to replay an execution and audit it, giving it the same inputs and getting the same result bits, whichever computer runs it. Then follow how those results were produced.
That is still a large part of why I'm building it. I want an answer I can inspect.
This grew alongside my interpreter work. In Pipit I'm implementing Go's behaviour. It's a lot of fun, but the Go team has already made the language decisions.
I have to follow them. There are plenty of smart decisions in there. It feels like I'm missing out on a ton of fun.
Here I can choose the rules. I can pick arbitrary requirements and work backwards from them, then see what fits.
One early rule was that every value would be a float. Floats all the way down.... That's where Floatyboat came from.
That rule didn't last, and it's the only invariant I've deleted so far.
The frontend can present different types, and I can lower calculations to other types underneath. The name still feels right to me. So much of the interesting work keeps bringing me back to floats. Especially determinism.
I'm working towards an embedded language for computational kernels. Those are the array calculations a larger application hands over to it.
The application can keep its general-purpose language, which gives me room to make demanding rules here.
The rules I keep coming back to
My starting point was to treat the language as a CI/CD DAG, a graph of tasks and their dependencies, like a build pipeline. I learnt there's a whole paradigm for that idea, called dataflow. A program describes values and the calculations that depend on them. Each reader sees the same value. The dependencies tell the compiler what has to happen before another calculation can run. I'm exploring a language without user pointers or shared mutation, with fewer ways to hide another reader or change a value behind its back. Adding restrictions can give the compiler more confidence to optimise. Knowing every reader also helps it reason about when storage is finished with.
I get to choose those restrictions. Then I have to find out whether I still enjoy using the language.
Keep the data flowing
I keep picturing a combine harvester. Data comes in, passes through several operations and comes out processed. Its width is how much we handle in one step of the calculation, and I picture a tile as the length of crop moving through the machine. The next tile starts as the last finishes, reusing space as old values stop being needed. Stopping after every stage to put a whole array down interrupts that route. I don't want to stop my combine harvester to fish a rock out. The rock is an operation which needs the whole intermediate array before it can run. Now I have to collect the tiles back into memory, do that job, and start feeding the machine again.
A conditional with only a whole-array implementation can do that. Keeping choices as masks or selections lets the compiler keep working inside a tile.
Branches and divergence are part of what I want to avoid in the generated work. Can I build a useful language around keeping that route open?
That is what I'm trying to figure out.
The source can still give each stage a useful name, so in a Fahrenheit to Celsius calculation, I remove the offset, then scale the shifted values.
Those can be two readable stages without a stored array between them, when no other reader needs that intermediate. The compiler has to track those readers too.
For that reason, I think the language can look a bit like Go. It doesn't need a COMPLETELY unfamiliar syntax.
If I had to summarise it in one sentence, it is “Nothing happens next”. The source describes dependencies instead of directing a program counter.
Those dependencies decide which work is ready. The compiler has to arrange it, including the parts that must wait.
Floatyboat is in active development. I plan to open-source it when it is ready. There isn't a public installation or stable language reference yet.
Experiments and design notes
What if a language just didn't?
What design room opens when language features are omitted?
When the graph knows a value is finished
Can the compiler handle an array kernel's memory, from finding space to reusing it for the next value?
Keep the data moving
Why I want Floatyboat to keep tiles moving, and what happens when an operation needs a whole array.
It's arrays all the way down
Can arrays represent trees, graphs and other complex data structures efficiently, without user pointers?
Terms and conditions apply
What am I promising when I call a calculation reproducible? I'm working out which choices belong to the implementation, and which belong in its contract.
When doing the work twice saves memory
I want a faster program, even if that means doing the arithmetic twice. A Floatyboat experiment saves 32 MiB and finishes much sooner.