packages feed

crucible-0.10: test/helpers/SymSequence/Reverse.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE RankNTypes #-}

module SymSequence.Reverse (tests) where

import Data.Parameterized.Nonce qualified as Nonce
import Data.Parameterized.Some (Some(Some))
import Lang.Crucible.Backend (SomeBackend(SomeBackend), backendGetSym)
import Lang.Crucible.Backend.Simple (newSimpleBackend)
import Lang.Crucible.Simulator.SymSequence (SymSequence)
import Lang.Crucible.Simulator.SymSequence qualified as S
import System.Timeout (timeout)
import Test.Tasty qualified as TT
import Test.Tasty.HUnit qualified as TTU
import What4.Expr (EmptyExprBuilderState(EmptyExprBuilderState))
import What4.Expr.Builder (newExprBuilder)
import What4.FloatMode (FloatModeRepr(FloatIEEERepr))
import What4.Interface qualified as WI

---------------------------------------------------------------------
-- Tests

tests :: TT.TestTree
tests = TT.testGroup "reverse performance"

  -- Pure cons-list (no branching, no appends):
  --
  --   Cons -> Cons -> ... -> Cons -> Nil
  --
  [ reverseTest "reverse large cons-list" $ \sym ->
      buildSeq sym 1500000 $ \s _ acc ->
        S.consSymSequence s True acc

  -- Cons-list with a Mux node every 100 elements.
  -- At each Mux both branches point to the same shared tail:
  --
  --                                 /--true---\
  --   Cons -> ... -> Cons -> Mux ->            Cons -> ... -> Nil
  --                                 \--false--/
  --
  , reverseTest "reverse cons-list with periodic muxes" $ \sym ->
      buildSeq sym 250000 $ \s i acc -> do
        acc' <-
          if i `mod` 100 == 0
          then do
            t <- S.consSymSequence s True acc
            f <- S.consSymSequence s False acc
            S.muxSymSequence s (WI.truePred s) t f
          else pure acc
        S.consSymSequence s True acc'

  -- Left-nested chain of Appends:
  --
  --   Append -> Append -> ... -> Append -> Nil
  --
  , reverseTest "reverse left-nested appends" $ \sym ->
      buildSeq sym 250000 $ \s _ acc -> do
        singleton <- S.consSymSequence s True S.SymSequenceNil
        S.appendSymSequence s acc singleton

  -- Right-nested chain of Appends:
  --
  --   Append -> Append -> ... -> Append -> Nil
  --
  , reverseTest "reverse right-nested appends" $ \sym ->
      buildSeq sym 250000 $ \s _ acc -> do
        singleton <- S.consSymSequence s True S.SymSequenceNil
        S.appendSymSequence s singleton acc

  -- Left-deep chain of Muxes (true branch grows):
  --
  --   Mux -> Mux -> Mux -> ... -> Mux -> Nil
  --
  , reverseTest "reverse left-deep mux chain" $ \sym ->
      buildSeq sym 250000 $ \s _ acc -> do
        singleton <- S.consSymSequence s True S.SymSequenceNil
        S.muxSymSequence s (WI.truePred s) acc singleton

  -- Right-deep chain of Muxes (true branch is a leaf):
  --
  --   Mux -> Mux -> Mux -> ... -> Mux -> Nil
  --
  , reverseTest "reverse right-deep mux chain" $ \sym ->
      buildSeq sym 250000 $ \s _ acc -> do
        singleton <- S.consSymSequence s True S.SymSequenceNil
        S.muxSymSequence s (WI.truePred s) singleton acc

  -- Balanced binary tree of Appends over singletons:
  --
  --            Append
  --           /      \
  --       Append    Append
  --       /    \    /    \
  --     [1]  [2]  [3]  [4] ...
  --
  , reverseTest "reverse balanced appends" $ \sym ->
      buildBalanced sym 250000 $ \s acc1 acc2 ->
        S.appendSymSequence s acc1 acc2

  -- Balanced binary tree of Muxes over singletons:
  --
  --            Mux
  --           /   \
  --        Mux    Mux
  --       /   \  /   \
  --     [1] [2] [3] [4] ...
  --
  , reverseTest "reverse balanced muxes" $ \sym ->
      buildBalanced sym 250000 $ \s acc1 acc2 ->
        S.muxSymSequence s (WI.truePred s) acc1 acc2
  ]

---------------------------------------------------------------------
-- Helpers

mkBackend :: IO (Some SomeBackend)
mkBackend = do
  sym <- newExprBuilder FloatIEEERepr EmptyExprBuilderState Nonce.globalNonceGenerator
  Some . SomeBackend <$> newSimpleBackend sym

-- | Build a sequence, reverse it, and check that length is preserved.
--   Must complete within 5 seconds.
reverseTest ::
  String ->
  (forall sym. WI.IsExprBuilder sym => sym -> IO (SymSequence sym Bool)) ->
  TT.TestTree
reverseTest name build = TTU.testCase name $ do
  Some (SomeBackend bak) <- mkBackend
  let sym = backendGetSym bak
  s <- build sym
  origLen <- S.lengthSymSequence sym s
  result <- timeout (5 * 1000000) $ do
    r <- S.reverseSymSequence sym s
    S.lengthSymSequence sym r
  case result of
    Nothing -> TTU.assertFailure (name ++ " timed out (>5s)")
    Just revLen ->
      TTU.assertEqual "length preserved"
        (WI.asInteger (WI.natToIntegerPure origLen))
        (WI.asInteger (WI.natToIntegerPure revLen))

-- | Iterate a step function n times starting from nil.
buildSeq ::
  WI.IsExprBuilder sym =>
  sym ->
  Int ->
  (sym -> Int -> SymSequence sym Bool -> IO (SymSequence sym Bool)) ->
  IO (SymSequence sym Bool)
buildSeq sym n step = go n S.SymSequenceNil
  where
    go 0 acc = pure acc
    go i acc = go (i - 1) =<< step sym i acc

-- | Build a balanced binary tree of n singletons combined with the given
--   binary operation (e.g. appendSymSequence or muxSymSequence).
buildBalanced ::
  WI.IsExprBuilder sym =>
  sym ->
  Int ->
  (sym -> SymSequence sym Bool -> SymSequence sym Bool -> IO (SymSequence sym Bool)) ->
  IO (SymSequence sym Bool)
buildBalanced sym n combine = do
  leaves <- mapM (\_ -> S.consSymSequence sym True S.SymSequenceNil) [1..n]
  reduce leaves
  where
    reduce [] = pure S.SymSequenceNil
    reduce [x] = pure x
    reduce xs = reduce =<< pairUp xs
    pairUp [] = pure []
    pairUp [x] = pure [x]
    pairUp (x:y:rest) = do
      combined <- combine sym x y
      (combined :) <$> pairUp rest