packages feed

llvm-codegen-0.1.0.0: tests/Test/LLVM/C/APISpec.hs

module Test.LLVM.C.APISpec
  ( module Test.LLVM.C.APISpec
  ) where

import Test.Hspec
import Foreign hiding (void)
import qualified LLVM.C.API as C
import LLVM.Codegen.Type
import LLVM.Codegen.Name
import LLVM.Codegen.Flag

-- NOTE: if it can't find libffi, you're linking against wrong libLLVM!
-- Be sure to update Setup.hs LLVM version as well to be in sync!

mkType :: ForeignPtr C.Context -> Type -> IO (Ptr C.Type)
mkType ctx = \case
  VoidType ->
    C.mkVoidType ctx
  IntType bits ->
    C.mkIntType ctx bits
  PointerType ty ->
    C.mkPointerType =<< mkType ctx ty
  StructureType packed tys -> do
    tys' <- traverse (mkType ctx) tys
    C.mkAnonStructType ctx tys' packed
  ArrayType count ty -> do
    ty' <- mkType ctx ty
    C.mkArrayType ty' count
  FunctionType retTy argTys -> do
    retTy' <- mkType ctx retTy
    argTys' <- traverse (mkType ctx) argTys
    C.mkFunctionType retTy' argTys'
  NamedTypeReference name ->
    C.getTypeByName ctx name


-- TODO: do more than checking against nullptr in first tests

spec :: Spec
spec = describe "LLVM C API" $ parallel $ do
  it "can create a LLVM context" $ do
    ctx <- C.mkContext
    withForeignPtr ctx $ \c ->
      c `shouldNotBe` nullPtr

  it "can create an empty LLVM module" $ do
    ctx <- C.mkContext
    llvmMod <- C.mkModule ctx "test"
    withForeignPtr llvmMod $ \llvmModule ->
      llvmModule `shouldNotBe` nullPtr

  it "can set the target data for a LLVM module" $ do
    ctx <- C.mkContext
    newTd <- C.mkTargetData "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"  -- WASM layout
    llvmMod <- C.mkModule ctx "test"
    C.setTargetData llvmMod newTd
    td <- C.getTargetData llvmMod
    td `shouldNotBe` nullPtr

  it "can extract the target data from a LLVM module" $ do
    ctx <- C.mkContext
    llvmMod <- C.mkModule ctx "test"
    td <- C.getTargetData llvmMod
    td `shouldNotBe` nullPtr

  let assertTypeSizes :: ((Type -> Word64 -> IO ()) -> IO ()) -> IO ()
      assertTypeSizes f = do
        ctx <- C.mkContext
        llvmMod <- C.mkModule ctx "test"
        td <- C.getTargetData llvmMod
        f $ \ty expectedSize -> do
          ty' <- mkType ctx ty
          actualSize <- C.sizeOfType td ty'
          actualSize `shouldBe` expectedSize

  it "can compute the size of an integer type" $ do
    assertTypeSizes $ \assert -> do
      assert i1 1
      assert i8 1
      assert i16 2
      assert i32 4
      assert i64 8

  -- NOTE: not allowed for "void" => has no size, causes SIGILL (due to missing return in a function in libLLVM / assert triggered)

  it "can compute the size of a pointer type" $ do
    assertTypeSizes $ \assert -> do
      assert (ptr i1) 8
      assert (ptr i32) 8
      assert (ptr i64) 8

  it "can compute the size of a struct type" $ do
    assertTypeSizes $ \assert -> do
      assert (StructureType Off [i8]) 1
      assert (StructureType Off [i8, i8]) 2
      assert (StructureType Off [i8, i16]) 4  -- padding!
      assert (StructureType On [i8, i16]) 3
      assert (StructureType On [i8, StructureType Off [i8]]) 2
      assert (StructureType Off [ArrayType 5 i32, i1]) 24  -- padding!
      assert (StructureType On [ArrayType 5 i32, i1]) 21
      assert (StructureType On [i32, i64]) 12  -- no padding, 4-byte alignment for i64?

  it "can compute the size of an array type" $ do
    assertTypeSizes $ \assert -> do
      assert (ArrayType 1 i1) 1
      assert (ArrayType 10 i1) 10
      assert (ArrayType 10 i32) 40
      assert (ArrayType 5 (StructureType Off [i32, i64])) (5 * 12)

  -- NOTE: not allowed for function type => has no size, triggers undefined behavior.

  it "returns null for an unknown named type reference" $ do
    let ty = NamedTypeReference $ Name "unknown"
    ctx <- C.mkContext
    ty' <- mkType ctx ty
    ty' `shouldBe` nullPtr

  -- TODO test for known named type ref, but need to first add to module