stgi-1: test/Testsuite/Test/Machine/GarbageCollection.hs
{-# LANGUAGE NumDecimals #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Test.Machine.GarbageCollection (tests) where
import Control.DeepSeq
import qualified Data.List.NonEmpty as NE
import qualified Data.Map as M
import Data.Monoid
import qualified Data.Set as S
import Data.Text (Text)
import qualified Data.Text as T
import Stg.Language.Prettyprint
import Stg.Machine
import Stg.Machine.GarbageCollection.Common
import Stg.Machine.Types
import Stg.Marshal
import Stg.Parser.QuasiQuoter
import qualified Stg.Prelude as Stg
import Test.Orphans ()
import Test.Tasty
import Test.Tasty.HUnit
tests :: TestTree
tests = testGroup "Garbage collection"
[ gcTests "Tri-state tracing" triStateTracing
, gcTests "Two space copying" twoSpaceCopying ]
gcTests :: Text -> GarbageCollectionAlgorithm -> TestTree
gcTests name algorithm = testGroup (T.unpack name)
[ test algorithm | test <- [ splitHeapTest
, fibonacciSumTest ]]
prettyIndented :: Pretty a => a -> Text
prettyIndented = T.unlines . map (" " <>) . T.lines . prettyprint
splitHeapTest :: GarbageCollectionAlgorithm -> TestTree
splitHeapTest algorithm = testGroup "Split heap in dead/alive"
[ deadAddressesFound
, deadAndAliveContainAll ]
where
addr ~> closure = (MemAddr addr, HClosure closure)
dirtyHeap = Heap
[ 0 ~> Closure [stg| \(cyclic used1) -> Used cyclic used1 |]
[Addr (MemAddr addr) | addr <- [4,1] ]
, 1 ~> Closure [stg| \(used2 prim) -> Used used2 prim |]
[Addr (MemAddr 2), PrimInt 1]
, 2 ~> Closure [stg| \ -> Used |] []
, 3 ~> Closure [stg| \ -> Unused |] []
, 4 ~> Closure [stg| \(cyclic) -> Used cyclic |]
[Addr (MemAddr 4)] ]
globals = Globals [("main", Addr (MemAddr 0))]
expectedDead = [MemAddr 3]
dummyState = StgState
{ stgCode = ReturnInt 1
, stgStack = mempty
, stgHeap = dirtyHeap
, stgGlobals = globals
, stgSteps = 0
, stgInfo = Info GarbageCollection [] }
errorMsg = T.unlines
[ "Globals:"
, prettyIndented globals
, "Dirty heap:"
, prettyIndented dirtyHeap
, "Clean heap:"
, prettyIndented cleanHeap ]
(deadAddrs, _forwards, StgState{stgHeap = cleanHeap})
= splitHeapWith algorithm dummyState
deadAddressesFound = testCase "Dead addresses are found" test
where
test = assertEqual (T.unpack errorMsg)
expectedDead
deadAddrs
deadAndAliveContainAll = testCase "Heap shrinks by number of dead addresses" test
where
expectedNewHeapSize = let Heap old = dirtyHeap
in M.size old - S.size deadAddrs
actualNewHeapSize = let Heap new = cleanHeap
in M.size new
test = assertEqual (T.unpack errorMsg)
expectedNewHeapSize
actualNewHeapSize
fibonacciSumTest :: GarbageCollectionAlgorithm -> TestTree
fibonacciSumTest algorithm = testCase "Long-running program" test
where
-- This program choked on the new copying GC (ran into an infinite loop),
-- so it is added as a test case. It's much rather a sanity test than a
-- minimal example displaying the actual issue, however.
source = mconcat
[ Stg.add
, toStg "zero" (0 :: Int)
, Stg.foldl'
, Stg.zipWith ] <> [stg|
flipConst = \x y -> y;
main = \ =>
letrec
fibo = \ =>
letrec
fib0 = \(fib1) -> Cons zero fib1;
fib1 = \(fib2) =>
let one = \ -> Int# 1#
in Cons one fib2;
fib2 = \(fib0 fib1) => zipWith add fib0 fib1
in fib0
in foldl' flipConst zero fibo
|]
prog = initialState "main" source
states = NE.take 1e3 (evalsUntil (RunForMaxSteps 1e10)
(HaltIf (const False))
(PerformGc (const (Just algorithm)))
prog )
test = rnf states `seq` pure ()