Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell LLVM binding Ambiguous Type

Tags:

haskell

llvm

ffi

I am trying to get started with the LLVM binding for Haskell. A great place to start is Hello World.

The following is from a blog by the author of the binding.

bldGreet :: CodeGenModule (Function (IO ()))
bldGreet = do
    puts <- newNamedFunction ExternalLinkage "puts" :: TFunction (Ptr Word8 -> IO Word32)
    greetz <- createStringNul "Hello, World!"
    func <- createFunction ExternalLinkage $ do
      tmp <- getElementPtr greetz (0::Word32, (0::Word32, ()))
      call puts tmp -- Throw away return value.
      ret ()
    return func

It does not compile.
Instead I get "Ambiguous type variable n0' in the constraint: (type-level-0.2.4:Data.TypeLevel.Num.Sets.NatI n0) arising from a use ofgetElementPtr0' Probable fix: add a type signature that fixes these type variable(s)"

Here is a variation that does work

llvmModule :: TFunction (IO Word32)
llvmModule = 
    withStringNul "Hello world!" $ \s -> do 
    puts <- newNamedFunction ExternalLinkage "puts" :: TFunction (Ptr Word8 -> IO Word32)
    main <- newNamedFunction ExternalLinkage "main" :: TFunction (IO Word32)
    defineFunction main $ do
      tmp <- getElementPtr0 s (0::Word32, ())
      _ <- call puts tmp
      ret (0::Word32)
    return main

The first seems more natural. The question I have is what is the ambiguity in the first, and how do I fix it. The second question I have is why is the second not ambiguous.

like image 466
Jonathan Gallagher Avatar asked Apr 12 '13 18:04

Jonathan Gallagher


2 Answers

Okay. So I solved the problem. It is indeed a typeclass thing. And it has only confused me more. However, I do have an answer to the solution. But do feel free to help my understanding out. First, some digging. The function createStringNul has type

createString :: String -> TGlobal (Array n Word8)

Fine. The problem the compiler was having is that the "n" in the Array type is ambiguous. It could literally be anything in the world. Lookup, Array, and you see

newtype Array n a

Now it is not so obvious, but upon a little digging, in particular wrt the call to getElementPtr, one finds that the n, really should be a Nat n, which is a type-level way to fix the size of the array. Now, the definition of Array n a, does not actually care it is really just a type synonym for [a]. So you can use D0, or D9 or whatever you want from the Data.TypeLevel.Num.Reps package. Fixing the size of the array, while a good idea was not actually taken into consideration by this function. But anyways, changing greetz <- createStringNul "Hello, World!" to greetz <- createStringNul "Hello, World!" :: TGlobal (Array D0 Word8) works.

Here is the interesting part... I did not expect it to work. D0 is supposed to be 0, so I did not understand why it was allowing me to store so many characters in a 0 size "Array" However, if you look at the source code, it is immediately clear that the type restriction is not actually payed attention to.

Fine, whatever, upon compiling one realizes that the createStringNul is deprecated, and withStringNul is preferred instead. Except I don't completely understand how withStringNul's types work.

like image 58
Jonathan Gallagher Avatar answered Nov 15 '22 08:11

Jonathan Gallagher


createStringNul is marked as deprecated in the current version of llvm (3.0.1.0). Use withStringNul:

import Data.Word
import LLVM.Core

bldGreet :: CodeGenModule (Function (IO ()))
bldGreet = do
    puts <- newNamedFunction ExternalLinkage "puts" :: TFunction (Ptr Word8 -> IO Word32)
    func <- withStringNul "Hello, World!" $ \greetz ->
      createFunction ExternalLinkage $ do
        tmp <- getElementPtr greetz (0::Word32, (0::Word32, ()))
        _ <- call puts tmp -- Throw away return value.
        ret ()
    return func

As to what causes the error in the first example, it's related to the fact that withStringNul has a more informative type: withStringNul :: String -> (forall n . Nat n => Global (Array n Word8) -> a) -> a -- cf. to createStringNul :: String -> TGlobal (Array n Word8). The function argument of withStringNul has a higher-rank type - it means that the function works for all n where n is a natural number.

If you really want to use createStringNul, you can make the first example compile by adding an explicit type signature for greetz:

{-# LANGUAGE TypeOperators #-}
module Test
       where

import Data.Word
import Data.TypeLevel.Num.Reps
import LLVM.Core

bldGreet :: CodeGenModule (Function (IO ()))
bldGreet = do
    puts <- newNamedFunction ExternalLinkage "puts" :: TFunction (Ptr Word8 -> IO Word32)
    greetz <- createStringNul "Hello, World!"
    func <- createFunction ExternalLinkage $ do
      tmp <- getElementPtr (greetz :: Global (Array (D1 :* D3) Word8)) (0::Word32, (0::Word32, ()))
      call puts tmp -- Throw away return value.
      ret ()
    return func

The :* type constructor comes from the type-level package and is used to construct type-level numbers. D1 :* D3 means that the array has size 13.

like image 45
Mikhail Glushenkov Avatar answered Nov 15 '22 06:11

Mikhail Glushenkov