Things that amuse me

Friday, June 29, 2007

Generating more code with Harpy
After a slight detour into expression representation I'm back to generating code again.
To recap, here's how I'm going to do my embedded expressions in Haskell. For now I'll stick to ugly looking boolean operators. Fixing that is a whole other story.

So the Exp type is the internal representation of expressions. It's just constants, variables, and some primitive operations.
The M (for machine) type is the phantom type that the DSL user will see. The module M contains all the user visible functions. Note that it handles booleans by represented the C way, by an int that is 0 or 1. There's a conditional function, cond, and a corresponding primitive that will serve as our if.
So now we need to generate code for these. This is all very similar to what I did in a previous posting, I have just refactored some of the code generation. The invariant (that I arbitrarily decided on) is that each block of code that implements a primitive operation will may clobber EAX but must leave all other registers intact.
This is mostly just a chunk of rather boring code. Division is causing problems as usual, since the IA32 instructions don't allow you to specify a destination register. One point worth a look is the code generation for cond. It has to test a boolean and then select one of two code blocks, but that is just what you'd expect.

Given all this, we can generate code for a function and call it from Haskell code.
But that's rather boring since we have only primitive functions and no way to call DSL functions from within the DSL. So we have no recursion. And as we all know, recursion is where the fun starts.
So we need to add expressions and code generation for calling functions. First we extend the Exp module.

data Fun
= FPrimOp PrimOp
| Func FuncNo
newtype FuncNo = FuncNo Int

The idea being that our DSL functions will be represented by a FuncNo which is an Int.
Next, we will extend the code generation. While generating code we will keep an environment that maps function numbers to the corresponding labels to call.

type Env = [(FuncNo, Label)]
...
cgFun (Func f) n = do
e
To call a function we look up its label and generate a call instruction to that label. Then we pop off the arguments that we pushed before the call, and finally we push EAX (where the return value is). Simple, huh?
But where did the environment come from? Well, it's time to be a little inventive and introduce a monad. Functions are represented by unique integers in the expression type, so we'll need a state monad to generate them.

This code actually has some interesting points.
First, the monad, G, keeps a list of defined functions. The list has the function number and the CodeGen block that will generate code for it.
Second, the fun function is the one that creates a new function number. It also calls compileFun to get a CodeGen block that will generate code for the function. Note that no code is generated at this point. The G monad is just a simple state monad, not the IO based codeGen monad. Also note how fun returns an expression that is of the same type as the argument, but it now uses an App to call the function.
Finally, the runG function generates all the code. It uses runState to obtain the list of defined function and the result to return. Now code generation starts. For each function we generate a new label. Pairing up the function number and the label forms our environment, funMap. With this environment installed we start code generation for the functions, first the return value, then all the defined function; each with its label attached.
Well, that's it. Let's wrap it all up.

And, of course, a test. Here we hit a snag. We want recursion, but monadic bindings (do) are not recursive.
Luckily, ghc does implement recursive do called mdo (why that name, I have no idea) for any monad that is in the class MonadFix. And the state monad is, so we are in luck.

{-# OPTIONS_GHC -fglasgow-exts #-}
module Main where
import Compile
main = do
let g = mdo
fib cond (n .< 2) 1 (fib(n-1) + fib(n-2))
return $ fib
test
It even runs and produces the right answer, 165580141. Running time for this example is about 4.0s on my machine. Running fib compiled with 'ghc -O' takes about 3.4s, so we're in the same ballpark.
Oh, and if anyone wonders what the code looks like for this example, here it is. This is really, really bad code.

Thursday, June 28, 2007

Representing DSL expressions in Haskell
When you want to embed a DSL in Haskell you will almost certainly be faced with having some kind of expressions in your DSL. If you are lucky you can get away with using the Haskell types as the DSL type (i.e., use the meta types as object types). E.g., if you DSL needs integer expressions you can use the Haskell type Integer.
But sometimes you're not that lucky, so you need to represent the DSL expressions as a Haskell data type. What are the options for representing expressions in Haskell?
A normal data type
Let's start with the most obvious one, an ordinary data type with constructors for the different kinds of expressions. As an example I'll have a simple expression language that has constants (of type Int), addition, less-or-equal, and conditional.

prints ValI 3
This is OK, but look at all the error cases in the evaluator. Remember that these are expressions in a DSL, so we are going to have expression fragments in our Haskell code. If we make a mistake, like Cond (ConI 1) (ConI 1) (ConI 1) this is not going to be caught by the Haskell type checker since everything is of type Exp. Instead it is going to cause some error when the program is run. Being advocates of static type checking (why else use Haskell?), this is rather disgusting.
GADTs
So let's try a new and shiny feature in GHC, namely GADTs. (GADTs are new in ghc, but the idea is old; it's been well known in constructive type theory for ages, Kent Petersson and I suggested it as an addition to Haskell almost 15 years ago.) With a GADT you can specify more precise types for the constructors.

GADTs is really the way to go, but it has the disadvantage of not being standard Haskell. It can also get somewhat cumbersome when we have variables; the evaluator now needs a typed environment. It's certainly doable, but starting to look less nice.
Phantom types
Let's explore a third way that in some sense combines the previous two. The idea is to have an untyped representation, like Exp, but to use an abstract data type on top of it that only allows constructing well typed expressions.
We start with the original type, but rename it to Exp'

The functions conI, add, le, and cond are the only ones the DSL user will see. Note that they have the same types as the constructors in the GADT. To ensure the DSL user can only use these we need to put it all in a module and export the right things.

Phantom types gets its name from the fact that the type variable in the definition of the Exp type doesn't occur anywhere in the constructors; so it's a phantom. Now and then people suggest implementing a dynamically typed version of Haskell. Phantom types is one of the things that makes such an implementation difficult. There are no values that carry the type information at runtime, the types only exist at compile time.

A simple compiler
In my last post I played a little with Harpy to generate code from what looked like assembly language. So the next logical step is to make a compiler. And I mean a real machine-code compiler, not some wimpy byte-code nonsense.
To make life simple (it is a simple compiler, after all) I'm going to start by generating code that uses the stack all the time. So all operands will live on the stack and all operations on them will push and pop the stack. Of course, the code generated from such a compiler will make any serious compiler writer weep.
The CodeGen type in Harpy is the monad used during code generation. It allows you to keep some extra information around besides the generated code; it gives you access to a state monad. I will use the state to keep track of the current stack depth. (We will see why soon.)

type StackDepth = Int
type Gen = CodeGen () StackDepth ()
addDepth :: StackDepth -> Gen
addDepth i = do
d
The addDepth function changes the current stack depth by grabbing the old one, adding the argument and storing it back. The getState and setState functions don't generate any code, they just manipulate the state available in the CodeGen monad.
With that out of the way, let's implement code generation for addition.

It pops the two operands off the stack, adds them, and pushes the result. The net effect on the stack is that it has one word less on it (I count my stack depth in words), so there's also a call to addDepth.
Subtraction and multiplication are very similar.

On the i386 (signed) division and remainder is computed with the idiv instruction. It divides the EDX:EAX 64 bit number with the given operand. So we must convert a 32 signed number to a 64 bit signed number, this is simply done by moving EAX to EDX and then shifting right 31 steps. This will copy the sign bit into every bit of EDX. Depending of if we want the quotient or remainder we need to push EAX or EDX.

OK, so now for something more interesting. Assuming we are generating code for a function, we also want to access the arguments to the function. Where are the arguments? Well, according to the IA32 calling conventions the caller pushes the arguments on the stack, so we'll follow those. First we have a bunch things on the stack, how many is kept track of in the stack depth in the CodeGen monad, and then the arguments follow in order, pushed right-to-left. So to get an argument we compute the offset, convert it to a byte offset, and push that word on the stack.

-- Get the Nth argument
gargN :: Int -> Gen
gargN n = do
d
When generating code for a function we should not clobber any callee-save registers, so to be on the safe side we save all used registers on function entry and restore them on function exit. On function exit we also return the result in EAX.

testGen = conv_Word32ToWord32 () (length savedRegs + 1) $ do
gprologue
gargN 0
gconst 1
gadd
gepilogue
main = do
test
The testGen function generates the prologue, push argument, push 1, add, and the epilogue. The conv_Word32ToWord32 (from my previous post) converts the machine code to a Haskell function. We also have to give the start value of the stack depth. The stack initially contains the return address and the saved registers, so that's the number we pass.
Running this gives 11, as it should.
OK, so let's actually write a compiler and not just a code generator. Here is a data type for integer expressions.

main = do
let fun x = (x+1) * x `quot` 2
test
And this prints 55, as expected.
Not bad, a compiler from (a tiny subset of) Haskell functions to machine code in a few pages. But I do admit being embarrassed about generating such incredibly poor code. But there's always future blog posts to rectify that.

Monday, June 25, 2007

Playing with Harpy
Recently there was an announcement of the Harpy package for Haskell. Harpy is a very cool package that lets you generate machine code from a Haskell program into a memory buffer. Using the Haskell FFI interface you can then turn the machine code into a Haskell function and call it. The way that you generate machine code from Harpy looks very much like assembly code.
Oh, btw, apologies to those who are not using x86 machines, this blog post is very machine dependent, since Harpy generate x86 machine code.
A small Harpy example
First, some sample code (stolen from the Harpy tutorial). It might look like assembly code, but it's actually Haskell.

asm_fac = do
loopTest
Just some comments: This is (obviously) monadic code. The newLabel operation creates a new label that later has to be defined and that can be used as a jump target. The ensureBufferSize is a tedious function to make sure there is enough space to emit the instructions. I hope the next version of Harpy will not require this, since it's really the computers job to count bytes, not mine (and it's not hard to do). Apart from that, most of the code should be obvious for anyone who has done assembly programming on an x86.
To generate code and then convert the generated code we need some utility functions.

The important function is conv_Word32ToWord32 which takes a block like asm_fac and turns it into a Haskell function. The e and s arguments we can ignore for now, they are for more advanced uses of the machine code generator. Note that there is no type safety here. The asm_fac block has nothing that says what kind of function it might implement, so it's all up to us to use it correctly. (Once you use FFI Haskell isn't any safer than C.)
Finally, to use it all:

main = do
fac
Pretty nifty, huh? Calling conv_Word32ToWord32 with the asm_fac argument will emit machine code for the factorial function into a memory buffer and then wrap it up so it can be called as a regular Haskell function.
Running the program will print (120,3628800) as expected.
Final thoughts
Harpy is really cool and uses Haskell type classes in a clever way to make Harpy "assembly" code look almost like normal assembly code. But there is room for improvement. Currently you have no control over the buffer handling; Harpy uses mallocBytes internally. I would like to see the buffer handling (mallocBytes, peek, the IO monad) abstracted out. That way I don't have to generate code into a memory buffer if I don't want to. I could generate code in, e.g., a list. Or I could not generate code at all, but just count bytes. Or I could allocate buffers with something better than mallocBytes.
On the whole, I think Harpy is great for experimenting with code generation. More about that soon.