Skip to content

Commit

Permalink
Merge pull request #157 from luc-tielen/test-runtime-btree
Browse files Browse the repository at this point in the history
Add tests for btree in runtime
  • Loading branch information
luc-tielen authored Oct 5, 2023
2 parents 583e2c4 + 45bff9c commit 3378e32
Show file tree
Hide file tree
Showing 9 changed files with 947 additions and 144 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ eclair.hp
perf.data
perf.data.old
perf.svg

TODO*
4 changes: 4 additions & 0 deletions eclair-lang.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ test-suite eclair-test
Test.Eclair.ArgParserSpec
Test.Eclair.JSONSpec
Test.Eclair.LLVM.Allocator.MallocSpec
Test.Eclair.LLVM.Allocator.Utils
Test.Eclair.LLVM.BTreeSpec
Test.Eclair.LLVM.HashSpec
Test.Eclair.LSP.HandlersSpec
Test.Eclair.LSP.JSONSpec
Expand Down Expand Up @@ -293,6 +295,7 @@ test-suite eclair-test
cxx-options: -std=c++17 -D__EMBEDDED_SOUFFLE__
build-depends:
, algebraic-graphs <1
, array >=0.5 && <1
, base >=4.7 && <5
, bytestring >=0.11 && <0.12
, comonad >=5 && <6
Expand All @@ -318,6 +321,7 @@ test-suite eclair-test
, parser-combinators >=1.3 && <1.4
, prettyprinter >=1.7 && <1.8
, prettyprinter-ansi-terminal >=1 && <2
, random >=1.2 && <2
, recursion-schemes >=5 && <6
, relude >=1.2 && <1.3
, rock >=0.3 && <0.4
Expand Down
11 changes: 5 additions & 6 deletions lib/Eclair/LLVM/BTree/Bounds.hs
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,11 @@ mkBtreeLowerBound isEmptyTree iterInit iterInitEnd searchLowerBound compareValue
retVoid

isNotLast <- pos `ne` last
-- Can the following be done with just pointer comparisons?
matchesVal' <- (int8 0 `eq`) =<< call compareValues [pos, val]
matchFound <- isNotLast `and` matchesVal'
if' matchFound $ do
_ <- call iterInit [result, current, idx]
retVoid
if' isNotLast $ do
matchFound' <- (int8 0 `eq`) =<< call compareValues [pos, val]
if' matchFound' $ do
_ <- call iterInit [result, current, idx]
retVoid

if' isNotLast $ do
call iterInit [res, current, idx]
Expand Down
10 changes: 5 additions & 5 deletions lib/Eclair/LLVM/BTree/Find.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ mkBtreeFind isEmptyTree searchLowerBound compareValues iterInit iterInitEnd = do

-- Can the following equality check be done using just pointers?
foundMatch <- pos `ult` last
matchesVal <- (int8 0 `eq`) =<< call compareValues [pos, val]
foundValue <- foundMatch `and` matchesVal
if' foundValue $ do
_ <- call iterInit [result, current, idx]
retVoid
if' foundMatch $ do
matchesVal <- (int8 0 `eq`) =<< call compareValues [pos, val]
if' matchesVal $ do
_ <- call iterInit [result, current, idx]
retVoid

isLeaf <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` leafNodeTypeVal)
if' isLeaf $ do
Expand Down
37 changes: 20 additions & 17 deletions lib/Eclair/LLVM/BTree/Insert.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ mkSplit nodeNew nodeSplitPoint growParent = mdo
ty <- deref (metaOf ->> nodeTypeOf) n
-- Create a new sibling node and move some of the data to sibling
sibling <- call nodeNew [ty]

jPtr <- allocate i16 (int16 0)
loopFor splitPoint' (`ult` numberOfKeys) (add (int16 1)) $ \i -> mdo
j <- load jPtr 0
Expand Down Expand Up @@ -85,11 +86,8 @@ mkGrowParent nodeNew insertInner = mdo

assign (metaOf ->> parentOf) n newRoot
assign (metaOf ->> parentOf) sibling newRoot
-- TODO: why missing in souffle code? happens in another function?
-- also: why is num elements of n not decremented?
-- assign (metaOf ->> posInParentOf) n (int16 0)

-- update (metaOf ->> numElemsOf) n (`sub` (int16 1))
-- assign (metaOf ->> posInParentOf) n (int16 0) -- Not needed, root already has position 0
assign (metaOf ->> posInParentOf) sibling (int16 1)
store root 0 newRoot
retVoid
Expand Down Expand Up @@ -143,7 +141,7 @@ mkInsertInner rebalanceOrSplit = mdo
numElems'' <- deref (metaOf ->> numElemsOf) n
startIdx <- sub numElems'' (int16 1)
pos' <- load posPtr 0
loopFor startIdx (`uge` pos') (`sub` int16 1) $ \i -> mdo
loopFor startIdx (`sge` pos') (`sub` int16 1) $ \i -> mdo
j <- add i (int16 1)
k <- add i (int16 2)
assign (valueAt j) n =<< deref (valueAt i) n
Expand All @@ -152,7 +150,6 @@ mkInsertInner rebalanceOrSplit = mdo
increment int16 (metaOf ->> posInParentOf) childK

-- TODO: assert(i_n->children[pos] == predecessor);

-- Insert new element
assign (valueAt pos') n =<< load key 0
pos'' <- add pos' (int16 1)
Expand Down Expand Up @@ -224,15 +221,21 @@ mkRebalanceOrSplit splitFn = mdo
leftNumElems' <- deref (metaOf ->> numElemsOf) left
leftPos <- add leftNumElems' (int16 1) >>= add i
assign (childAt leftPos) iLeft =<< deref (childAt i) iN
leftChild <- deref (childAt leftPos) iLeft
assign (metaOf ->> parentOf) leftChild left
assign (metaOf ->> posInParentOf) leftChild leftPos

-- Shift child pointer to the left + update position
-- Update moved children
loopFor (int16 0) (`ult` leftSlotsOpen) (add (int16 1)) $ \i -> do
leftNumElems' <- deref (metaOf ->> numElemsOf) left
leftPos <- add leftNumElems' (int16 1) >>= add i
child <- deref (childAt i) iN
assign (metaOf ->> parentOf) child left
assign (metaOf ->> posInParentOf) child leftPos

-- Shift child pointer to the left
endIdx <- sub numElemsN leftSlotsOpen >>= add (int16 1)
loopFor (int16 0) (`ult` endIdx) (add (int16 1)) $ \i -> do
j <- add i leftSlotsOpen
assign (childAt i) iN =<< deref (childAt j) iN
-- Update position of children
child <- deref (childAt i) iN
assign (metaOf ->> posInParentOf) child i

Expand Down Expand Up @@ -310,9 +313,9 @@ mkBtreeInsertValue nodeNew compareValues searchLowerBound searchUpperBound isEmp
pos <- call searchLowerBound [val, first, last]
idx <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize))
notLast <- pos `ne` last
isEqual <- (int8 0 `eq`) =<< call compareValues [pos, val] -- Can we do a weak compare just by using pointers here?
alreadyInserted <- notLast `and` isEqual
condBr alreadyInserted noInsert continueInsert
if' notLast $ do
alreadyInserted <- (int8 0 `eq`) =<< call compareValues [pos, val]
condBr alreadyInserted noInsert continueInsert

continueInsert <- blockNamed "inner_continue_insert"
let iCurrent = ptrcast innerNode current
Expand All @@ -332,10 +335,10 @@ mkBtreeInsertValue nodeNew compareValues searchLowerBound searchUpperBound isEmp
distance <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize))
idxPtr <- allocate i16 distance
notFirst <- pos `ne` first
valueAtPrevPos <- gep pos [int32 (-1)]
isEqual <- (int8 0 `eq`) =<< call compareValues [valueAtPrevPos, val] -- Can we do a weak compare just by using pointers here?
alreadyInserted <- notFirst `and` isEqual
condBr alreadyInserted noInsert continueInsert
if' notFirst $ do
valueAtPrevPos <- gep pos [int32 (-1)]
alreadyInserted <- (int8 0 `eq`) =<< call compareValues [valueAtPrevPos, val]
condBr alreadyInserted noInsert continueInsert

continueInsert <- blockNamed "leaf_continue_insert"
nodeIsFull <- numElems `uge` numberOfKeys
Expand Down
2 changes: 1 addition & 1 deletion tests/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ if [ "$?" == "0" ]; then
exit 1
fi

grep -rE "pending" tests/eclair/Test
grep -rE "(\sxit|pending)" tests/eclair/Test
if [ "$?" == "0" ]; then
echo "Found pending tests, aborting!"
exit 1
Expand Down
167 changes: 52 additions & 115 deletions tests/eclair/Test/Eclair/LLVM/Allocator/MallocSpec.hs
Original file line number Diff line number Diff line change
@@ -1,140 +1,77 @@
{-# OPTIONS_GHC -Wno-deprecations -Wno-incomplete-uni-patterns #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
module Test.Eclair.LLVM.Allocator.MallocSpec
( module Test.Eclair.LLVM.Allocator.MallocSpec
) where

import Prelude hiding (void)
import Control.Monad.Morph
import Eclair.LLVM.Allocator.Malloc
import Eclair.LLVM.Allocator.Common
import Eclair.LLVM.Codegen hiding (retVoid)
import System.FilePath
import Eclair.LLVM.Codegen hiding (retVoid, nullPtr)
import System.Directory.Extra
import System.Process.Extra
import System.Posix.DynamicLinker
import Foreign.LibFFI
import Test.Hspec
import Control.Exception (bracket)
import Control.Exception
import Foreign.Ptr
import Foreign.C
import Foreign (Storable(peek, poke))

import Foreign hiding (void)
import Test.Eclair.LLVM.Allocator.Utils
import Test.Hspec

type I8 = CUChar

data Mallocator

data Bindings
= Bindings
{ dynamicLib :: DL
, withAlloc :: (Ptr Mallocator -> IO ()) -> IO ()
, fnAlloc :: Ptr Mallocator -> CSize -> IO (Ptr I8)
, fnFree :: Ptr Mallocator -> Ptr I8 -> CSize -> IO ()
, fnInit :: Ptr Mallocator -> IO ()
, fnDestroy :: Ptr Mallocator -> IO ()
}

spec :: Spec
spec = describe "Mallocator" $
aroundAll (setupAndTeardown testDir) $ parallel $ do
it "can be initialized and destroyed" $ \bindings ->
withAlloc bindings $ \obj -> do
fnInit bindings obj
fnDestroy bindings obj

it "can allocate and free memory" $ \bindings -> do
let numBytes = 1
value = 42
withAlloc bindings $ \obj -> do
fnInit bindings obj
memory <- fnAlloc bindings obj numBytes
poke memory value
value' <- peek memory
fnFree bindings obj memory numBytes
fnDestroy bindings obj
value' `shouldBe` value

setupAndTeardown :: FilePath -> ActionWith Bindings -> IO ()
spec = describe "Mallocator" $ aroundAll (setupAndTeardown testDir) $ parallel $ do
it "can be initialized and destroyed" $ \bindings ->
withAlloc bindings $ \obj -> do
fnInit bindings obj
fnDestroy bindings obj

it "can allocate and free memory" $ \bindings -> do
let numBytes = 1
value = 42
withAlloc bindings $ \obj -> do
fnInit bindings obj
memory <- fnAlloc bindings obj numBytes
memory `shouldNotBe` nullPtr
poke memory value
value' <- peek memory
fnFree bindings obj memory numBytes
fnDestroy bindings obj
value' `shouldBe` value

setupAndTeardown :: FilePath -> ActionWith (Bindings Mallocator) -> IO ()
setupAndTeardown dir =
bracket (setup dir) teardown

setup :: FilePath -> IO Bindings
setup :: FilePath -> IO (Bindings Mallocator)
setup dir = do
createDirectoryIfMissing False dir
compileAllocatorCode dir
loadNativeCode dir

teardown :: Bindings -> IO ()
teardown (Bindings lib _ _ _ _ _) =
dlclose lib

compileAllocatorCode :: FilePath -> IO ()
compileAllocatorCode dir = do
llvmIR <- runModuleBuilderT $ do
mallocFn <- extern "malloc" [i32] (ptr i8)
freeFn <- extern "free" [ptr i8] void
let exts = Externals mallocFn freeFn notUsed notUsed notUsed notUsed notUsed
cgBlueprint = flip evalStateT exts $ cgAlloc "mallocator" allocator
Blueprint ty _ _ _ _ <- hoist intoIO cgBlueprint
-- Helper test code for initializing and freeing a struct from native code:
_ <- function "mallocator_new" [] (ptr ty) $ \[] ->
ret =<< call mallocFn [int32 1]
_ <- function "mallocator_delete" [(ptr ty, "allocator")] void $ \[alloc] ->
call freeFn [alloc]
pass
let llvmIRText = ppllvm llvmIR
writeFileText (llFile dir) llvmIRText
callProcess "clang" ["-fPIC", "-shared", "-O0", "-o", soFile dir, llFile dir]
compileAllocatorCode allocator prefix cgExternals cgTestCode dir
loadNativeCode prefix dir

teardown :: Bindings Mallocator -> IO ()
teardown =
dlclose . dynamicLib

cgExternals :: ModuleBuilderT IO Externals
cgExternals = do
mallocFn <- extern "malloc" [i32] (ptr i8)
freeFn <- extern "free" [ptr i8] void
pure $ Externals mallocFn freeFn notUsed notUsed notUsed notUsed notUsed

-- Helper test code for initializing and freeing a struct from native code:
cgTestCode :: Type -> Operand -> Operand -> ModuleBuilderT IO ()
cgTestCode ty mallocFn freeFn = do
_ <- function "mallocator_new" [] (ptr ty) $ \[] ->
ret =<< call mallocFn [int32 1]
_ <- function "mallocator_delete" [(ptr ty, "allocator")] void $ \[alloc] ->
call freeFn [alloc]
pass

prefix :: Text
prefix = "mallocator"

testDir :: FilePath
testDir = "/tmp/eclair-mallocator"

llFile, soFile :: FilePath -> FilePath
llFile dir = dir </> "allocator.ll"
soFile dir = dir </> "allocator.so"

loadNativeCode :: FilePath -> IO Bindings
loadNativeCode dir = do
lib <- dlopen (soFile dir) [RTLD_LAZY]
newFn <- dlsym lib "mallocator_new"
deleteFn <- dlsym lib "mallocator_delete"
allocFn <- dlsym lib "mallocator_alloc"
freeFn <- dlsym lib "mallocator_free"
initFn <- dlsym lib "mallocator_init"
destroyFn <- dlsym lib "mallocator_destroy"
pure $ Bindings
{ dynamicLib = lib
, withAlloc = mkWithAlloc newFn deleteFn
, fnAlloc = mkAlloc allocFn
, fnFree = mkFree freeFn
, fnInit = mkInit initFn
, fnDestroy = mkDestroy destroyFn
}
where
mkAlloc fn mallocator numBytes =
callFFI fn (retPtr retCUChar)
[ argPtr mallocator
, argCSize $ fromIntegral numBytes
]
mkFree fn mallocator memory numBytes =
callFFI fn retVoid
[ argPtr mallocator
, argPtr memory
, argCSize $ fromIntegral numBytes
]
mkInit fn mallocator =
callFFI fn retVoid [argPtr mallocator]
mkDestroy fn mallocator =
callFFI fn retVoid [argPtr mallocator]
mkNew fn =
callFFI fn (retPtr retVoid) []
mkDelete fn mallocator =
callFFI fn retVoid [argPtr mallocator]
mkWithAlloc newFn deleteFn =
bracket (castPtr <$> mkNew newFn) (mkDelete deleteFn)

notUsed :: a
notUsed = undefined

intoIO :: Identity a -> IO a
intoIO = pure . runIdentity
notUsed = panic "Not used"
Loading

0 comments on commit 3378e32

Please sign in to comment.