Sun, 22 Aug 2010
LLVM Backend for DDC : Milestone #2.
For a couple of weeks after AusHac 2010 I didn't manage to find any time to working on DDC at all, but I'm now back on it and late last week I reached the second milestone on the LLVM backend for DDC. The backend now has the ability to box and unbox 32 bit integers and perform simple arithmetic operations on valid combinations of them.
Disciple code that can currently be compiled correctly via LLVM includes basic stuff like:
identInt :: Int -> Int identInt a = a plusOneInt :: Int -> Int plusOneInt x = x + 1 addInt :: Int -> Int -> Int addInt a b = a + b addInt32U :: Int32# -> Int32# -> Int32# addInt32U a b = a + b addMixedInt :: Int32# -> Int -> Int addMixedInt a b = boxInt32 (a + unboxInt32 b) cafOneInt :: Int cafOneInt = 1 plusOne :: Int -> Int plusOne x = x + cafOneInt
where Int32# specifies an unboxed 32 bit integer and Int32 specifies the boxed version.
While writing the Haskell code for DDC, I'm finding that its easiest to generate LLVM code for a specific narrow case first and then generalize it as more cases come to light. I also found that the way I had been doing the LLVM code generation was tedious and ugly, invloving lots of concatenation of small lists. To fix this I built myself an LlvmM monad on top of the StateT monad:
type LlvmM = StateT [[LlvmStatement]] IO
Using this I can then generate a block of LLVM code as a list of LlvmStatements and add it to the monad using an addBlock function which basically pushes the blocks of code down onto a stack:
addBlock :: [LlvmStatement] -> LlvmM () addBlock code = do state <- get put (code : state)
The addBlock function is then used as the base building block for a bunch of more specific functions like these:
unboxInt32 :: LlvmVar -> LlvmM LlvmVar unboxInt32 objptr | getVarType objptr == pObj = do int32 <- lift $ newUniqueReg i32 iptr0 <- lift $ newUniqueNamedReg "iptr0" (pLift i32) iptr1 <- lift $ newUniqueNamedReg "iptr1" (pLift i32) addBlock [ Comment [ show int32 ++ " = unboxInt32 (" ++ show objptr ++ ")" ] , Assignment iptr0 (GetElemPtr True objptr [llvmWordLitVar 0, i32LitVar 0]) , Assignment iptr1 (GetElemPtr True iptr0 [llvmWordLitVar 1]) , Assignment int32 (Load iptr1) ] return int32 readSlot :: Int -> LlvmM LlvmVar readSlot 0 = do dstreg <- lift $ newUniqueNamedReg "slot.0" pObj addBlock [ Comment [ show dstreg ++ " = readSlot 0" ] , Assignment dstreg (Load localSlotBase) ] return dstreg readSlot n | n > 0 = do dstreg <- lift $ newUniqueNamedReg ("slot." ++ show n) pObj r0 <- lift $ newUniqueReg pObj addBlock [ Comment [ show dstreg ++ " = readSlot " ++ show n ] , Assignment r0 (GetElemPtr True localSlotBase [llvmWordLitVar n]) , Assignment dstreg (Load (pVarLift r0)) ] return dstreg readSlot n = panic stage $ "readSlot with slot == " ++ show n
which are finally hooked up to do things like:
llvmVarOfExp (XUnbox ty@TCon{} (XSlot v _ i)) = do objptr <- readSlot i unboxAny (toLlvmType ty) objptr llvmVarOfExp (XUnbox ty@TCon{} (XForce (XSlot _ _ i))) = do orig <- readSlot i forced <- forceObj orig unboxAny (toLlvmType ty) forced
When the code generation of a single function is complete it the list of LlvmStatement blocks is then retrieved, reversed and concatenated to produce the list of LlvmStatements for the function.
With the LlvmM monad in place converting DDC's Sea AST into LLVM code is now pretty straight forward. Its just a matter of finding and implementing all the missing pieces.
Posted at: 13:43 | Category: CodeHacking/DDC | Permalink