November 14, 2013 - Tagged as: en, haskell, asm, ffi.
I’ve been studying 64bit calling conventions for x64 family recently. To do this, I was writing some function in assembly and then calling my functions from a C program to be sure that parameter passing and returning values are working correctly.
It generally works fine, and linking C program with assembly programs are easy enough. Today I got the idea of using Haskell + QuickCheck to test my assembly programs, this way I could also be more confident that not only parameter passing/return values work correctly, but my implementation of algorithm in assembly is also correct.
Linking assembly programs with a Haskell program is surprisingly easy. In the rest of this post, I’ll explain how to compile and link power function written in assembly to test program written in Haskell.
Here’s a power function that works on 64bit unsigned numbers, implemented using x64 ABI conventions:
section .text global power power: mov rbx, rdi ; move first parameter to rbx mov rcx, rsi ; move second parameter to rcx cmp rcx, 0 ; return 1 if power is 0 je end_power_one mov rax, rbx ; move result to rax for multiplication power_loop_start: cmp rcx, 1 je end_power mul rbx dec rcx jmp power_loop_start end_power_one: mov rax, 1 end_power: ret
This is written using Netwide Assembler(best assembly syntax, IMO). Since we’re working on 64bit system, we need to compile it to
nasm -f elf64 power_lib.s -o power_lib.o
Once we have our compiled file for power function, we need to declare it in Haskell as a foreign function, using correct types. A short tour in
Foreign.C.Types library showed that
CULong type is actually a newtype wrapper around
Word64, which is 64bit unsigned number type:
ghci> :m + Foreign.C.Types ghci> :info CULong newtype CULong = CULong GHC.Word.Word64 -- Defined in `Foreign.C.Types'
(not that this part should be different on 32bit systems – eg. on 32bit system you should see
CULong GHC.Word.Word32 instead of
We also need a
Arbitrary instance for
CULong type to be able to use QuickCheck on this type. Since
CULong is just a newtype wrapper over
Word64 already has instance defined in QuickCheck library, we can have that for free. In the end, our test code is:
module Main where import Foreign.C.Types import Test.QuickCheck import ccall "power" foreign power :: CULong -> CULong -> CULong instance Arbitrary CULong where = fmap CULong arbitrary arbitrary CULong i) = map CULong $ shrink i shrink ( = power b p == b ^ p test_prop b p main :: IO () = quickCheck test_propmain
and that’s it. Very simple, 16 lines of code. To compile this, I also wrote a Makefile:
power: power_test.hs power_lib.o $^ ghc --make power_lib.o: power_lib.s $< -o $@ nasm -f elf64 clean: -rm power_lib.o -rm power_test power_test.hi power_test.o
(this may be helpful to see how compilation work)
I think this is also a good demonstration of how easy it is to interact with foreign functions in Haskell.