Monads to Machine Code (Part 1)
stephendiehl.com
stephendiehl.com
So if you have a fragment like this:
factorial :: Int64 -> X86 ()
factorial n = do
mov rcx (I n)
mov rax (I 1)
l1 <- label
mul rcx
loop l1
ret
you can just compose this into a bigger program: two_factorials :: Int64 -> Int64 -> X86 ()
two_factorials n1 n2 = do
factorial n1
factorial n2
Notice how the labels won't get mixed up. This concept (representing computation by values) is incredibly powerful: one can compose, combine, abstract values into bigger values. For example, compare the compositional properties of RethinkDB's query language to SQL. (html
(head
(title "hello"))
(body
(p "world")))
into "<html><head><title>hello</title></head><body><p>world</p></body></html>"? Except this takes assembly keywords and outputs machine code.Lisp macros are not bound by types, so they can be combined irregardless of a program semantics, potentially producing any kind of incorrect code as the result. Such errors get especially tricky with several layers of quasiquotation.
In Haskell, the program meaning is partially encoded into types, so the compiler can understand your intention much better and assists you in programming it right. Also, getting some kind of inter-part optimization is much easier in Haskell: you know exactly what kind of pieces you are gluing up.
Here are some other examples.
This one uses indexed monads to keep the instruction stream correct:
https://intoverflow.wordpress.com/2010/05/21/announcing-pote...
And this one is very similar to the OP but from 2013, and emits 6502 assembly: http://wall.org/~lewis/2013/10/15/asm-monad.html (of course without the JIT portion).
I'm scared this has the potential to be information overload for people who aren't familiar with an assembly language and Haskell, but for me I thought this was a terribly neat (both in the "interesting" as well as "structurally well done") article. As usual, fantastic work by Steven. Up there with Bartosz and Yang in quality of write-ups.
Seems a reasonably complete assembler too with one notable exception: Forward jumps, conditionals specifically. These may be a tad tricky to fit in to a monad because they require either knowing the future (eg. how far away the jump target is) or rewriting the past (eg. leave a hole and fill it in later).
I'd really like to see both approaches in Haskell, since they're kinda different. My guess is that in the former case, you'd have nested/delimited monads with buffers. However, I don't know Haskell enough to figure out how to accomplish the hole-filling approach.
EDIT: On second thought, I guess both approaches (traditionally "two-pass" vs "one-pass" assemblers) can be accomplished by changing labels to be opaque rather than addresses and storing a symbol table in either JITMem or the two-types that would replace it for a two-pass approach.
The state in the monad in the quoted paper flows backward. The part of assembler that works with label's addresses can use that trick from 1992.