What's cool about monads is that the computation (assembly code in this particular case) is just a plain old value.<p>So if you have a fragment like this:<p><pre><code> factorial :: Int64 -> X86 ()
factorial n = do
mov rcx (I n)
mov rax (I 1)
l1 <- label
mul rcx
loop l1
ret
</code></pre>
you can just compose this into a bigger program:<p><pre><code> two_factorials :: Int64 -> Int64 -> X86 ()
two_factorials n1 n2 = do
factorial n1
factorial n2
</code></pre>
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.
Always fun to see this type of thing implemented in Haskell.<p>Here are some other examples.<p>This one uses indexed monads to keep the instruction stream correct:<p><a href="https://intoverflow.wordpress.com/2010/05/21/announcing-potential-x86-64-assembler-as-a-haskell-edsl/" rel="nofollow">https://intoverflow.wordpress.com/2010/05/21/announcing-pote...</a><p>And this one is very similar to the OP but from 2013, and emits 6502 assembly: <a href="http://wall.org/~lewis/2013/10/15/asm-monad.html" rel="nofollow">http://wall.org/~lewis/2013/10/15/asm-monad.html</a> (of course without the JIT portion).
Awesome article!<p>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).<p>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.<p>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.
You can do even more with Arrows. This very short presentation goes from Monads to Arrows, and explains why:
<a href="https://www.dropbox.com/s/e3la5wueg1zsid7/Arrows%20for%20CPU%20Emulation.pdf?dl=0" rel="nofollow">https://www.dropbox.com/s/e3la5wueg1zsid7/Arrows%20for%20CPU...</a>