Calling C from Haskell

2017-04-02*
programming, haskell

Yesterday I made a minimal working example of calling C from Haskell, where I call a simple C function to compute the greatest common denominator, or “GCD”. The Haskell portion only serves as a wrapper around the C function. This post is a brief look at the whole setup.

The Setup

I used ghc 8.0.1, and gcc 5.4.0. 1

Folder Structure

2017-04-02-calling-c-from-haskell
├── build.sh
├── c
│   ├── gcd.c
│   └── gcd.h
└── hs
    ├── ffi.hs
    └── GCD.hs

2 directories, 5 files

File Contents

1
int gcd(int m, int r);
1
2
3
4
5
6
7
8
9
10
#include "gcd.h"

/* Find greatest common divisor. */
int gcd(int m, int r)
{
	if (r == 0)
		return m;

	return gcd(r, m % r);
}
1
2
3
4
5
6
7
8
9
10
module GCD where

import Foreign
import Foreign.C.Types

foreign import ccall "gcd"
  c_gcd :: CInt -> CInt -> CInt

f_gcd :: Int -> Int -> Int
f_gcd a b = fromIntegral $ c_gcd (fromIntegral a) (fromIntegral b)
1
2
3
4
import GCD

main :: IO ()
main = mapM_ (print . uncurry f_gcd) [(8, 12), (30, 105), (24, 108)]
1
2
3
4
5
6
7
8
9
#!/usr/bin/env bash

pushd c
gcc -c -o gcd.o gcd.c
popd

pushd hs
ghc --make ffi.hs ../c/gcd.o
popd

Discussion

To compile the example, run the build.sh script. Here is the expected output of the built executable:

 $ ./hs/ffi
4
15
12

. The gcd() C function is easy to work with because it is a pure function without side effects. You can run the ffi binary against valgrind to make sure that we are not leaking any memory (sample output below).

 $ valgrind --error-exitcode=1 --leak-check=yes ./hs/ffi
==14582== Memcheck, a memory error detector
==14582== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==14582== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==14582== Command: ./hs/ffi
==14582==
==14582== Warning: set address range perms: large range [0x4200000000, 0x14200100000) (noaccess)
4
15
12
==14582==
==14582== HEAP SUMMARY:
==14582==     in use at exit: 0 bytes in 0 blocks
==14582==   total heap usage: 48 allocs, 48 frees, 60,006 bytes allocated
==14582==
==14582== All heap blocks were freed -- no leaks are possible
==14582==
==14582== For counts of detected and suppressed errors, rerun with: -v
==14582== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Caveats

Below are some things I tried, but could not get to work.

 $ ghc --make ffi.hs ../c/gcd.o
[1 of 2] Compiling GCD              ( GCD.hs, GCD.o )
[2 of 2] Compiling Main             ( ffi.hs, ffi.o )
Linking ffi ...
../c/gcd.o: file not recognized: File format not recognized
collect2: error: ld returned 1 exit status
`cc' failed in phase `Linker'. (Exit code: 1)
 $ ghc --make ffi.hs ../c/gcd.o ../c/gcd_other.o
[1 of 2] Compiling GCD              ( GCD.hs, GCD.o )
[2 of 2] Compiling Main             ( ffi.hs, ffi.o )
Linking ffi ...
../c/gcd_other.o: In function `gcd':
gcd_other.c:(.text+0x0): multiple definition of `gcd'
../c/gcd.o:gcd.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
`cc' failed in phase `Linker'. (Exit code: 1)

  1. The version of gcc should not matter at all – actually, any decent C compiler should work.