verismith-1.1.0: src/Verismith/Fuzz.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell #-}
-- |
-- Module : Verismith.Fuzz
-- Description : Environment to run the simulator and synthesisers in a matrix.
-- Copyright : (c) 2019, Yann Herklotz
-- License : GPL-3
-- Maintainer : yann [at] yannherklotz [dot] com
-- Stability : experimental
-- Portability : POSIX
--
-- Environment to run the simulator and synthesisers in a matrix.
module Verismith.Fuzz
( Fuzz (..),
FuzzOpts (..),
fuzz,
fuzzInDir,
fuzzMultiple,
fuzzMultipleEMI,
runFuzz,
sampleSeed,
-- * Helpers
make,
pop,
)
where
import Control.DeepSeq (force)
import Control.Exception.Lifted (finally)
import Control.Lens hiding ((<.>))
import Control.Monad (forM, replicateM)
import Control.Monad.IO.Class
import Control.Monad.Reader
import Control.Monad.State.Strict
import Control.Monad.Trans.Control (MonadBaseControl)
import Data.ByteString (ByteString)
import Data.List (nubBy, sort)
import Data.Maybe (catMaybes, fromMaybe, isNothing)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time
import Data.Tuple (swap)
import Hedgehog (Gen)
import qualified Hedgehog.Internal.Gen as Hog
import Hedgehog.Internal.Seed (Seed)
import qualified Hedgehog.Internal.Seed as Hog
import qualified Hedgehog.Internal.Tree as Hog
import Shelly hiding (get, sub)
import Shelly.Lifted (MonadSh, liftSh, sub)
import System.FilePath.Posix (takeBaseName)
import Verismith.Config
import Verismith.CounterEg (CounterEg (..))
import Verismith.EMI
import Verismith.Reduce
import Verismith.Report
import Verismith.Result
import Verismith.Tool.Icarus
import Verismith.Tool.Internal
import Verismith.Tool.Yosys
import Verismith.Utils
import Verismith.Verilog.AST
import Verismith.Verilog.CodeGen
import Prelude hiding (FilePath)
data FuzzOpts = FuzzOpts
{ _fuzzOptsOutput :: !(Maybe FilePath),
_fuzzOptsForced :: !Bool,
_fuzzOptsKeepAll :: !Bool,
_fuzzOptsIterations :: {-# UNPACK #-} !Int,
_fuzzOptsNoSim :: !Bool,
_fuzzOptsNoEquiv :: !Bool,
_fuzzOptsNoReduction :: !Bool,
_fuzzOptsConfig :: {-# UNPACK #-} !Config,
_fuzzDataDir :: !FilePath,
_fuzzOptsCrossCheck :: !Bool,
_fuzzOptsChecker :: !(Maybe Text)
}
deriving (Show, Eq)
$(makeLenses ''FuzzOpts)
defaultFuzzOpts :: FuzzOpts
defaultFuzzOpts =
FuzzOpts
{ _fuzzOptsOutput = Nothing,
_fuzzOptsForced = False,
_fuzzOptsKeepAll = False,
_fuzzOptsIterations = 1,
_fuzzOptsNoSim = False,
_fuzzOptsNoEquiv = False,
_fuzzOptsNoReduction = False,
_fuzzOptsConfig = defaultConfig,
_fuzzDataDir = fromText ".",
_fuzzOptsCrossCheck = False,
_fuzzOptsChecker = Nothing
}
data FuzzEnv = FuzzEnv
{ _getSynthesisers :: ![SynthTool],
_getSimulators :: ![SimTool],
_yosysInstance :: {-# UNPACK #-} !Yosys,
_fuzzEnvOpts :: {-# UNPACK #-} !FuzzOpts
}
deriving (Eq, Show)
$(makeLenses ''FuzzEnv)
data FuzzState = FuzzState
{ _fuzzSynthResults :: ![SynthResult],
_fuzzSimResults :: ![SimResult],
_fuzzSynthStatus :: ![SynthStatus]
}
deriving (Eq, Show)
$(makeLenses ''FuzzState)
type Frequency a = [(Seed, a)] -> [(Int, Gen (Seed, a))]
-- | The main type for the fuzzing, which contains an environment that can be
-- read from and the current state of all the results.
type Fuzz m = StateT FuzzState (ReaderT FuzzEnv m)
type MonadFuzz m = (MonadBaseControl IO m, MonadIO m, MonadSh m)
runFuzz :: (MonadIO m) => FuzzOpts -> Yosys -> Fuzz Sh a -> m a
runFuzz fo yos m = shelly $ runFuzz' fo yos m
runFuzz' :: (Monad m) => FuzzOpts -> Yosys -> Fuzz m b -> m b
runFuzz' fo yos m =
runReaderT
(evalStateT m (FuzzState [] [] []))
( FuzzEnv
{ _getSynthesisers =
( force $
defaultIdentitySynth :
(descriptionToSynth <$> conf ^. configSynthesisers)
),
_getSimulators = (force $ descriptionToSim <$> conf ^. configSimulators),
_yosysInstance = yos,
_fuzzEnvOpts = fo
}
)
where
conf = _fuzzOptsConfig fo
askConfig :: (Monad m) => Fuzz m Config
askConfig = asks (_fuzzOptsConfig . _fuzzEnvOpts)
askOpts :: (Monad m) => Fuzz m FuzzOpts
askOpts = asks _fuzzEnvOpts
genMethod conf seed gen =
case T.toLower $ conf ^. configProperty . propSampleMethod of
"hat" -> do
logT "Using the hat function"
sv hatFreqs
"mean" -> do
logT "Using the mean function"
sv meanFreqs
"median" -> do
logT "Using the median function"
sv medianFreqs
_ -> do
logT "Using first seed"
sampleSeed seed gen
where
sv a = sampleVerilog a (conf ^. configProperty . propSampleSize) seed gen
relativeFuzzReport :: (MonadSh m) => FuzzReport -> m FuzzReport
relativeFuzzReport fr@(FuzzReport dir _ _ _ _ _ _ _) = liftSh $ do
newPath <- relPath dir
return $ (fuzzDir .~ newPath) fr
filterSynth :: SynthResult -> Bool
filterSynth (SynthResult _ _ (Pass _) _) = True
filterSynth _ = False
filterSim :: SimResult -> Bool
filterSim (SimResult _ _ _ (Pass _) _) = True
filterSim _ = False
filterSynthStat :: SynthStatus -> Bool
filterSynthStat (SynthStatus _ (Pass _) _) = True
filterSynthStat _ = False
passedFuzz :: FuzzReport -> Bool
passedFuzz (FuzzReport _ synth sim synthstat _ _ _ _) =
(passedSynth + passedSim + passedSynthStat) == 0
where
passedSynth = length $ filter (not . filterSynth) synth
passedSim = length $ filter (not . filterSim) sim
passedSynthStat = length $ filter (not . filterSynthStat) synthstat
synthesisers :: (Monad m) => Fuzz m [SynthTool]
synthesisers = lift $ asks _getSynthesisers
-- simulators :: (Monad m) => Fuzz () m [SimTool]
-- simulators = lift $ asks getSimulators
combinations :: [a] -> [b] -> [(a, b)]
combinations l1 l2 = [(x, y) | x <- l1, y <- l2]
logT :: (MonadSh m) => Text -> m ()
logT = liftSh . logger
timeit :: (MonadIO m, MonadSh m) => m a -> m (NominalDiffTime, a)
timeit a = do
start <- liftIO getCurrentTime
result <- a
end <- liftIO getCurrentTime
return (diffUTCTime end start, result)
synthesis :: (MonadBaseControl IO m, MonadSh m, Show ann) => (SourceInfo ann) -> Fuzz m ()
synthesis src = do
synth <- synthesisers
resTimes <- liftSh $ mapM exec synth
fuzzSynthStatus
.= applyList (uncurry . SynthStatus <$> synth) (fmap swap resTimes)
liftSh $ inspect resTimes
where
exec a = toolRun ("synthesis with " <> toText a) . runResultT $ do
liftSh . mkdir_p . fromText $ toText a
pop (fromText $ toText a) $ runSynth a src
passedSynthesis :: (MonadSh m) => Fuzz m [SynthTool]
passedSynthesis = fmap toSynth . filter passed . _fuzzSynthStatus <$> get
where
passed (SynthStatus _ (Pass _) _) = True
passed _ = False
toSynth (SynthStatus s _ _) = s
failedSynthesis :: (MonadSh m) => Fuzz m [SynthTool]
failedSynthesis = fmap toSynth . filter failed . _fuzzSynthStatus <$> get
where
failed (SynthStatus _ (Fail SynthFail) _) = True
failed _ = False
toSynth (SynthStatus s _ _) = s
make :: (MonadSh m) => FilePath -> m ()
make f = liftSh $ mkdir_p f
pop :: (MonadBaseControl IO m, MonadSh m) => FilePath -> m a -> m a
pop f a = do
dir <- liftSh pwd
finally (liftSh (cd f) >> a) . liftSh $ cd dir
applyList :: [a -> b] -> [a] -> [b]
applyList a b = apply' <$> zip a b where apply' (a', b') = a' b'
applyLots :: (a -> b -> c -> d -> e) -> [(a, b)] -> [(c, d)] -> [e]
applyLots func a b = applyList (uncurry . uncurry func <$> a) b
toSynthResult ::
[(SynthTool, SynthTool)] ->
[(NominalDiffTime, Result Failed ())] ->
[SynthResult]
toSynthResult a b = applyLots SynthResult a $ fmap swap b
toSimResult ::
SimTool ->
[ByteString] ->
[SynthTool] ->
[(NominalDiffTime, Result Failed ByteString)] ->
[SimResult]
toSimResult sima bs as b =
applyList
( applyList
(repeat uncurry)
(applyList (applyList (SimResult <$> as) (repeat sima)) (repeat bs))
)
$ fmap swap b
toolRun :: (MonadIO m, MonadSh m, Show a) => Text -> m a -> m (NominalDiffTime, a)
toolRun t m = do
logT $ "Running " <> t
s <- timeit m
logT $ "Finished " <> t <> " " <> showT s
return s
equivalence :: (MonadBaseControl IO m, MonadSh m, Show ann) => (SourceInfo ann) -> Fuzz m ()
equivalence src = do
doCrossCheck <- fmap _fuzzOptsCrossCheck askOpts
datadir <- fmap _fuzzDataDir askOpts
checker <- fmap _fuzzOptsChecker askOpts
synth <- passedSynthesis
conf <- fmap _fuzzOptsConfig askOpts
let synthComb =
if doCrossCheck
then nubBy tupEq . filter (uncurry (/=)) $ combinations synth synth
else nubBy tupEq . filter (uncurry (/=)) $ (,) defaultIdentitySynth <$> synth
resTimes <- liftSh $ mapM (uncurry (equiv (conf ^. configProperty . propDefaultYosys) checker datadir)) synthComb
fuzzSynthResults .= toSynthResult synthComb resTimes
liftSh $ inspect resTimes
where
tupEq (a, b) (a', b') = (a == a' && b == b') || (a == b' && b == a')
equiv yosysloc checker datadir a b =
toolRun ("equivalence check for " <> toText a <> " and " <> toText b)
. runResultT
$ do
make dir
pop dir $ do
liftSh $ do
cp
( fromText ".."
</> fromText (toText a)
</> synthOutput a
)
$ synthOutput a
cp
( fromText ".."
</> fromText (toText b)
</> synthOutput b
)
$ synthOutput b
writefile "rtl.v" $ genSource src
sub $ do
maybe (return ()) (liftSh . prependToPath . fromText) yosysloc
runEquiv checker datadir a b src
where
dir = fromText $ "equiv_" <> toText a <> "_" <> toText b
simulation :: (MonadIO m, MonadSh m, Show ann) => (SourceInfo ann) -> Fuzz m ()
simulation src = do
datadir <- fmap _fuzzDataDir askOpts
synth <- passedSynthesis
counterEgs <- failEquivWithIdentityCE
vals <- liftIO $ generateByteString Nothing 32 20
ident <- liftSh $ sim datadir vals Nothing defaultIdentitySynth
resTimes <- liftSh $ mapM (sim datadir vals (justPass $ snd ident)) synth
resTimes2 <- liftSh $ mapM (simCounterEg datadir) counterEgs
fuzzSimResults .= toSimResult defaultIcarusSim vals synth resTimes
liftSh
. inspect
$ (\(_, r) -> bimap show (T.unpack . T.take 10 . showBS) r)
<$> (ident : resTimes)
where
sim datadir b i a = toolRun ("simulation for " <> toText a) . runResultT $ do
make dir
pop dir $ do
liftSh $ do
cp (fromText ".." </> fromText (toText a) </> synthOutput a) $
synthOutput a
writefile "rtl.v" $ genSource src
runSimIc datadir defaultIcarus a src b i
where
dir = fromText $ "simulation_" <> toText a
simCounterEg datadir (a, Nothing) = toolRun ("counter-example simulation for " <> toText a) . return $ Fail EmptyFail
simCounterEg datadir (a, Just b) = toolRun ("counter-example simulation for " <> toText a) . runResultT $ do
make dir
pop dir $ do
liftSh $ do
cp (fromText ".." </> fromText (toText a) </> synthOutput a) $ synthOutput a
writefile "syn_identity.v" $ genSource src
ident <- runSimIcEC datadir defaultIcarus defaultIdentitySynth src b Nothing
runSimIcEC datadir defaultIcarus a src b (Just ident)
where
dir = fromText $ "countereg_sim_" <> toText a
simulationEMI :: (MonadIO m, MonadSh m, Show ann) => (SourceInfo (EMIInputs ann)) -> Fuzz m ()
simulationEMI src = do
datadir <- fmap _fuzzDataDir askOpts
synth <- passedSynthesis
counterEgs <- failEquivWithIdentityCE
vals <- liftIO $ generateByteString Nothing 32 100
ident <- liftSh $ sim datadir vals Nothing defaultIdentitySynth
resTimes <- liftSh $ mapM (sim datadir vals (justPass $ snd ident)) synth
fuzzSimResults .= toSimResult defaultIcarusSim vals synth resTimes
liftSh
. inspect
$ (\(_, r) -> bimap show (T.unpack . T.take 10 . showBS) r)
<$> (ident : resTimes)
where
sim datadir b i a = toolRun ("simulation for " <> toText a) . runResultT $ do
make dir
pop dir $ do
liftSh $ do
cp (fromText ".." </> fromText (toText a) </> synthOutput a) $
synthOutput a
writefile "rtl.v" $ genSource src
runSimIcEMI (getTopEMIIdent src) datadir defaultIcarus a (clearAnn src) b i
where
dir = fromText $ "emi_sim_" <> toText a
failEquivWithIdentity :: (MonadSh m) => Fuzz m [SynthResult]
failEquivWithIdentity = filter withIdentity . _fuzzSynthResults <$> get
where
withIdentity (SynthResult (IdentitySynth _) _ (Fail (EquivFail _)) _) = True
withIdentity (SynthResult _ (IdentitySynth _) (Fail (EquivFail _)) _) = True
withIdentity _ = False
failEquivWithIdentityCE :: (MonadSh m) => Fuzz m [(SynthTool, Maybe CounterEg)]
failEquivWithIdentityCE = catMaybes . fmap withIdentity . _fuzzSynthResults <$> get
where
withIdentity (SynthResult (IdentitySynth _) s (Fail (EquivFail c)) _) = Just (s, c)
withIdentity (SynthResult s (IdentitySynth _) (Fail (EquivFail c)) _) = Just (s, c)
withIdentity _ = Nothing
failedSimulations :: (MonadSh m) => Fuzz m [SimResult]
failedSimulations = filter failedSim . _fuzzSimResults <$> get
where
failedSim (SimResult _ _ _ (Fail (SimFail _)) _) = True
failedSim _ = False
passEquiv :: (MonadSh m) => Fuzz m [SynthResult]
passEquiv = filter withIdentity . _fuzzSynthResults <$> get
where
withIdentity (SynthResult _ _ (Pass _) _) = True
withIdentity _ = False
-- | Always reduces with respect to 'Identity'.
reduction :: (MonadSh m) => SourceInfo ann -> Fuzz m ()
reduction rsrc = do
datadir <- fmap _fuzzDataDir askOpts
checker <- fmap _fuzzOptsChecker askOpts
fails <- failEquivWithIdentity
synthFails <- failedSynthesis
simFails <- failedSimulations
_ <- liftSh $ mapM (red checker datadir) fails
_ <- liftSh $ mapM redSynth synthFails
_ <- liftSh $ mapM (redSim datadir) simFails
return ()
where
red checker datadir (SynthResult a b _ _) = do
r <- reduceSynth checker datadir a b src
writefile (fromText $ "reduce_" <> toText a <> "_" <> toText b <> ".v") $ genSource r
redSynth a = do
r <- reduceSynthesis a src
writefile (fromText $ "reduce_" <> toText a <> ".v") $ genSource r
redSim datadir (SimResult t _ bs _ _) = do
r <- reduceSimIc datadir bs t src
writefile (fromText $ "reduce_sim_" <> toText t <> ".v") $ genSource r
src = clearAnn rsrc
titleRun ::
(MonadIO m, MonadSh m) => Text -> Fuzz m a -> Fuzz m (NominalDiffTime, a)
titleRun t f = do
logT $ "### Starting " <> t <> " ###"
(diff, res) <- timeit f
logT $ "### Finished " <> t <> " (" <> showT diff <> ") ###"
return (diff, res)
whenMaybe :: (Applicative m) => Bool -> m a -> m (Maybe a)
whenMaybe b x = if b then Just <$> x else pure Nothing
getTime :: (Num n) => Maybe (n, a) -> n
getTime = maybe 0 fst
generateSample ::
(MonadIO m, MonadSh m, Show ann) =>
Fuzz m (Seed, (SourceInfo ann)) ->
Fuzz m (Seed, (SourceInfo ann))
generateSample f = do
logT "Sampling Verilog from generator"
(t, v@(s, _)) <- timeit f
logT $ "Chose " <> showT s
logT $ "Generated Verilog (" <> showT t <> ")"
return v
verilogSize :: (Source a) => a -> Int
verilogSize = length . lines . T.unpack . genSource
sampleVerilog ::
(MonadSh m, MonadIO m, Source a, Ord a) =>
Frequency a ->
Int ->
Maybe Seed ->
Gen a ->
m (Seed, a)
sampleVerilog _ _ seed@(Just _) gen = sampleSeed seed gen
sampleVerilog freq n Nothing gen = do
res <- replicateM n $ sampleSeed Nothing gen
let sizes = fmap getSize res
let samples = fmap snd . sort $ zip sizes res
liftIO $ Hog.sample . Hog.frequency $ freq samples
where
getSize (_, s) = verilogSize s
hatFreqs :: Frequency a
hatFreqs l = zip hat (return <$> l)
where
h = length l `div` 2
hat = (+ h) . negate . abs . (h -) <$> [1 .. length l]
meanFreqs :: (Source a) => Frequency a
meanFreqs l = zip hat (return <$> l)
where
hat = calc <$> sizes
calc i = if abs (mean - i) == min_ then 1 else 0
mean = sum sizes `div` length l
min_ = minimum $ abs . (mean -) <$> sizes
sizes = verilogSize . snd <$> l
medianFreqs :: Frequency a
medianFreqs l = zip hat (return <$> l)
where
h = length l `div` 2
hat = set_ <$> [1 .. length l]
set_ n = if n == h then 1 else 0
fuzz :: (MonadFuzz m, Ord ann, Show ann) => Gen (SourceInfo ann) -> Fuzz m FuzzReport
fuzz gen = do
conf <- askConfig
opts <- askOpts
let seed = conf ^. configProperty . propSeed
(seed', src) <- generateSample $ genMethod conf seed gen
let size = length . lines . T.unpack $ genSource src
liftSh
. writefile "config.toml"
. encodeConfig
$ conf
& configProperty
. propSeed
?~ seed'
(tsynth, _) <- titleRun "Synthesis" $ synthesis src
(tequiv, _) <-
if (_fuzzOptsNoEquiv opts)
then return (0, mempty)
else titleRun "Equivalence Check" $ equivalence src
(_, _) <-
if (_fuzzOptsNoSim opts)
then return (0, mempty)
else titleRun "Simulation" $ simulation src
fails <- failEquivWithIdentity
failedSim <- failedSimulations
synthFails <- failedSynthesis
redResult <-
whenMaybe
( not (null failedSim && null fails && null synthFails)
&& not (_fuzzOptsNoReduction opts)
)
. titleRun "Reduction"
$ reduction src
state_ <- get
currdir <- liftSh pwd
let vi = flip view state_
let report =
FuzzReport
currdir
(vi fuzzSynthResults)
(vi fuzzSimResults)
(vi fuzzSynthStatus)
size
tsynth
tequiv
(getTime redResult)
return report
fuzzInDirG ::
(MonadFuzz m, Ord ann, Show ann) =>
(Gen (SourceInfo ann) -> Fuzz m FuzzReport) ->
Gen (SourceInfo ann) ->
Fuzz m FuzzReport
fuzzInDirG f src = do
fuzzOpts <- askOpts
let fp = fromMaybe "fuzz" $ _fuzzOptsOutput fuzzOpts
make fp
res <- pop fp $ f src
liftSh $ do
writefile (fp <.> "html") $ printResultReport (bname fp) res
when (passedFuzz res && not (_fuzzOptsKeepAll fuzzOpts)) $ rm_rf fp
relativeFuzzReport res
where
bname = T.pack . takeBaseName . T.unpack . toTextIgnore
fuzzMultipleG ::
(MonadFuzz m, Ord ann, Show ann) =>
(Gen (SourceInfo ann) -> Fuzz m FuzzReport) ->
Gen (SourceInfo ann) ->
Fuzz m [FuzzReport]
fuzzMultipleG f src = do
fuzzOpts <- askOpts
let seed = (_fuzzOptsConfig fuzzOpts) ^. configProperty . propSeed
x <- case _fuzzOptsOutput fuzzOpts of
Nothing -> do
ct <- liftIO getZonedTime
return
. fromText
. T.pack
$ "output_"
<> formatTime defaultTimeLocale "%Y-%m-%d_%H-%M-%S" ct
Just f -> return f
make x
pop x $ do
results <-
if isNothing seed
then forM [1 .. (_fuzzOptsIterations fuzzOpts)] fuzzDir'
else (: []) <$> fuzzDir' (1 :: Int)
liftSh . writefile (fromText "index" <.> "html") $
printSummary
"Fuzz Summary"
results
return results
where
fuzzDir' n' =
local
( fuzzEnvOpts . fuzzOptsOutput
.~ (Just . fromText $ "fuzz_" <> showT n')
)
$ fuzzInDirG f src
sampleSeed :: (MonadSh m) => Maybe Seed -> Gen a -> m (Seed, a)
sampleSeed s gen =
liftSh $
let loop n =
if n <= 0
then
error
"Hedgehog.Gen.sample: too many discards, could not generate a sample"
else do
seed <- maybe Hog.random return s
case Hog.evalGen 30 seed gen of
Nothing ->
loop (n - 1)
Just x ->
pure (seed, Hog.treeValue x)
in loop (100 :: Int)
fuzzEMI :: (MonadFuzz m, Ord ann, Show ann) => Gen (SourceInfo (EMIInputs ann)) -> Fuzz m FuzzReport
fuzzEMI gen = do
conf <- askConfig
opts <- askOpts
let seed = conf ^. configProperty . propSeed
(seed', src) <- generateSample $ genMethod conf seed gen
let size = length . lines . T.unpack $ genSource src
liftSh
. writefile "config.toml"
. encodeConfig
$ conf
& configProperty
. propSeed
?~ seed'
(tsynth, _) <- titleRun "Synthesis" $ synthesis src
(_, _) <-
if (_fuzzOptsNoSim opts)
then return (0, mempty)
else titleRun "Simulation" $ simulationEMI src
fails <- failEquivWithIdentity
failedSim <- failedSimulations
synthFails <- failedSynthesis
state_ <- get
currdir <- liftSh pwd
let vi = flip view state_
let report =
FuzzReport
currdir
(vi fuzzSynthResults)
(vi fuzzSimResults)
(vi fuzzSynthStatus)
size
tsynth
0
0
return report
fuzzInDir :: (MonadFuzz m, Ord ann, Show ann) => Gen (SourceInfo ann) -> Fuzz m FuzzReport
fuzzInDir = fuzzInDirG fuzz
fuzzInDirEMI :: (MonadFuzz m, Ord ann, Show ann) => Gen (SourceInfo (EMIInputs ann)) -> Fuzz m FuzzReport
fuzzInDirEMI = fuzzInDirG fuzzEMI
fuzzMultiple :: (MonadFuzz m, Ord ann, Show ann) => Gen (SourceInfo ann) -> Fuzz m [FuzzReport]
fuzzMultiple = fuzzMultipleG fuzz
fuzzMultipleEMI :: (MonadFuzz m, Ord ann, Show ann) => Gen (SourceInfo (EMIInputs ann)) -> Fuzz m [FuzzReport]
fuzzMultipleEMI = fuzzMultipleG fuzzEMI