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 elf64
format:
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 Word64
)
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
, and 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_prop main
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.