osa1 github about atom

Testing assembly programs using Haskell, QuickCheck (and FFI)

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

foreign import ccall "power"
    power :: CULong -> CULong -> CULong

instance Arbitrary CULong where
    arbitrary = fmap CULong arbitrary
    shrink (CULong i) = map CULong $ shrink i

test_prop b p = power b p == b ^ p

main :: IO ()
main = quickCheck test_prop

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
	nasm -f elf64 $< -o $@

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.