Skip to content

Teaspot-Studio/inline-c

 
 

Repository files navigation

inline-c

Build Status

inline-c lets you seamlessly call C libraries and embed high-performance inline C code in Haskell modules. Haskell and C can be freely intermixed in the same source file, and data passed to and from code in either language with minimal overhead. No FFI required.

inline-c is Haskell's escape hatch (or one of) to the wild world of legacy code and high-performance numerical and system libraries. It has other uses too: you can also think of inline-c as to Haskell what inline Assembly is to C — a convenient means to eke out a little bit of extra performance in those rare cases where C still beats Haskell.

Build instructions are reserved for the last section. You'll need to compile the examples below and try them out.

Getting started

Let's say we want to compute the cosine of a number using C from Haskell. inline-c let's you write this function call inline, without any need for a binding to the foreign function:

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}

import qualified Language.C.Inline as C

C.include "<math.h>"

main :: IO ()
main = do
  x <- [C.exp| double{ cos(1) } |]
  print x

inline-c leverages the quasiquotation language extension implemented in GHC. Template Haskell is also required. Importing the Language.C.Inline module brings in scope most required Haskell definitions. C.include "<math.h>" brings into scope the foreign function cos() that we wish to call. Finally, in the main function, [C.exp| double { cos(1) } |] denotes an inline C expression of type double. cexp stands for "C expression". It is a custom quasiquoter provided by inline-c.

A C.exp quasiquotation always includes a type annotation for the inline C expression. This annotation determines the type of the quasiquotation in Haskell. Out of the box, inline-c knows how to map many common C types to Haskell type. In this case,

[C.exp| double { cos(1) } |] :: IO CDouble

For pure C expression like these we also provide C.pure, which works exactly the same but without the IO:

[C.pure| double { cos(1) } |] :: CDouble

Obviously extra care must be taken when using C.pure: the embedded C code must be referentially transparent.

Multiple statements

inline-c allows embedding arbitrary C code, not just expressions, in the form of a sequence of statements, using the c quasiquoter:

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}

import qualified Language.C.Inline as C

C.include "<stdio.h>"

main :: IO ()
main = do
  x <- [C.block| int {
      // Read and sum 5 integers
      int i, sum = 0, tmp;
      for (i = 0; i < 5; i++) {
        scanf("%d", &tmp);
        sum += tmp;
      }
      return sum;
    } |]
  print x

Just as with C.exp, we need a type annotation on the entire C block. The annotation specifies the return type. That is, the type of the expression in any return statement.

Capturing Haskell variables -- parameter declaration

inline-c allows referring to Haskell variables inside C expressions and code blocks. We do so by "anti-quoting" them.

Let's say that we wanted to parameterize the function we wrote above by how many numbers we should read. We can do so by defining a Haskell function whose parameter we can refer to from within C:

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
import qualified Language.C.Inline as C
import           Foreign.C.Types

C.include "<stdio.h>"

-- | @readAndSum n@ reads @n@ numbers from standard input and returns
-- their sum.
readAndSum :: CInt -> IO CInt
readAndSum n  = [C.block| int {
    // Read and sum n integers
    int i, sum = 0, tmp;
    for (i = 0; i < $(int n); i++) {
      scanf("%d", &tmp);
      sum += tmp;
    }
    return sum;
  } |]

main :: IO ()
main = do
  x <- readAndSum 5
  print x

Here, the Haskell variable n is captured right where we need it using $(int n). Standard anti-quotation (we'll talk about additional ones later) consists of a $ followed by a C declaration in parenthesis. Note that any valid Haskell identifiers can be used when anti-quoting, including ones including constructors, qualified names, names containing unicode, etc.

For each anti-quotation, a variable with a matching type is expected in the Haskell environment. In this case inline-c expects a variable named n of type CInt, which is the case.

What can be captured and returned?

All C types correspond to exactly one Haskell type. Basic types (int, long, double, float, etc.) get converted to their Haskell equivalents CInt, CLong, CDouble, CFloat. Pointers and arrays get converted to Ptr. Function pointers get converted to FunPtr.

inline-c can also handle user-defined structs and enums, provided that they are instances of Storable and that you tell inline-c about them using contexts.

Contexts

Everything beyond the base functionality provided by inline-c is specified in a structure that we call "Context". From a user perspective, if we want to use anything but the default context (C.baseCtx), we must set the C.Context explicitly using the C.context function. The next two sections include several examples.

The C.Context allows to extend inline-c to support

C.Contexts can be composed using their Monoid instance.

Ideally a C.Context will be provided for each C library that should be used with inline-c. The user can then combine multiple contexts together if multiple libraries are to be used in the same program. See the inline-c-nag package for an example of using a C.Context tailored for a library.

For information regarding how to define C.Contexts, see the Haddock-generated API documentation for Language.C.Inline.Context.

More anti-quoters

Besides the basic anti-quoter, which captures variables as they are, some more anti-quoters are provided with additional functionality. As mentioned, inline-c can easily be extended with anti-quoters defined by the user, using contexts.

Vectors

The vec-len and vec-ptr anti-quoters in the C.vecCtx context let us easily use Haskell vectors in C. Continuing along the "summing" theme, we can write code that sums Haskell vectors in C:

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
import qualified Language.C.Inline as C
import qualified Data.Vector.Storable as V
import qualified Data.Vector.Storable.Mutable as VM
import           Data.Monoid ((<>))
import           Foreign.C.Types

-- To use the vector anti-quoters, we need the 'C.vecCtx' along with the
-- 'C.baseCtx'.
C.context (C.baseCtx <> C.vecCtx)

sumVec :: VM.IOVector CDouble -> IO CDouble
sumVec vec = [C.block| double {
    double sum = 0;
    int i;
    for (i = 0; i < $vec-len:vec; i++) {
      sum += $vec-ptr:(double *vec)[i];
    }
    return sum;
  } |]

main :: IO ()
main = do
  x <- sumVec =<< V.thaw (V.fromList [1,2,3])
  print x

The vec-len anti-quoter is used simply by specifying the vector we want to get the length of (in our case, vec). To use the vec-ptr anti-quoter it is also required to specify the pointer type we want. Since vec is a vector of CDoubles, we want a pointer to doubles.

ByteStrings

The bs-len and bs-ptr ant-quoters in the C.bsCtx context work exactly the same as the vec-len and vec-ptr counterparts, but with strict ByteStrings. The only difference is that it is no necessary to specify the type of the pointer from C -- it is always going to be char *:

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
import qualified Data.ByteString as BS
import           Data.Monoid ((<>))
import           Foreign.C.Types
import qualified Language.C.Inline as C

C.context (C.baseCtx <> C.bsCtx)

-- | Count the number of set bits in a 'BS.ByteString'.
countSetBits :: BS.ByteString -> IO CInt
countSetBits bs = [C.block|
    int {
      int i, bits = 0;
      for (i = 0; i < $bs-len:bs; i++) {
        char ch = $bs-ptr:bs[i];
        bits += (ch * 01001001001ULL & 042104210421ULL) % 017;
      }
      return bits;
    }
  |]

Function pointers

Using the fun anti-quoter, present in the C.funCtx context, we can easily turn Haskell function into function pointers.

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
import qualified Language.C.Inline as C

-- To use the function pointer anti-quoter, we need the 'C.funCtx along with
-- the 'C.baseCtx'.
C.context (C.baseCtx <> C.funCtx)

ackermann :: CLong -> CLong -> CLong
ackermann m n
  | m == 0 = n + 1
  | m > 0 && n == 0 = ackermann (m - 1) 1
  | otherwise = ackermann (m - 1) (ackermann m (n - 1))

main :: IO ()
main = do
  let ackermannIO m n = return $ ackermann m n
  let x = 3
  let y = 4
  z <- [C.exp| long{
      $fun:(long (*ackermannIO)(long, long))($(long x), $(long y))
    } |]
  print z

In this example, we capture a Haskell function of type CLong -> CLong -> IO CLong, ackermannIO, to a function pointer in C, using the fun anti-quoter. Note how we need to specify the function pointer type when we capture ackermannIO, using standard C declaration syntax. Also note that the fun anti-quoter works with IO functions, and so we needed to modify ackermann to make it have the right type.

In general, when anti-quoting, if the type can be inferred (like in the case of vec-len), only the Haskell identifier appears. If it can't, the target C type and the Haskell identifier are mentioned using C declaration syntax.

How to build

Each module that uses at least one of the inline-c functions gets a C file associated to it, where the filename of said file will be the same as the module but with a C extension. This C file must be built after the Haskell code and linked appropriately.

If you use cabal, you must manually declare each associated C file in the c-sources section of the .cabal file and you are good.

For example we might have

executable foo
  main-is:             Main.hs, Foo.hs, Bar.hs
  hs-source-dirs:      src

  -- IMPORTANT!
  -- Here the corresponding C sources must be listed for every module
  -- that uses C code.  In this example, Main.hs and Bar.hs do, but
  -- Foo.hs does not.
  c-sources:           src/Main.c, src/Bar.c

  -- These flags will be passed to the C compiler
  cc-options:          -Wall -O2
  -- Libraries to link the code with.
  extra-libraries:     m
  ...

Note that currently cabal repl is not supported, because the C code is not compiled and linked appropriately. Type-checking will still be performed, so cabal repl can still be used to develop.

See sample-cabal-project for a working example.

If we were to compile the above manually we could do:

$ ghc -c Main.hs
$ cc -c Main.c -o Main_c.o
$ ghc Foo.hs
$ ghc Bar.hs
$ cc -c Bar.c -o Bar_c.o
$ ghc Main.o Foo.o Bar.o Main_c.o Bar_c.o -lm -o Main

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Haskell 100.0%