Skip to content
Snippets Groups Projects
Select Git revision
  • 686e89181244d6dc7b2912501d05d5c9c9059077
  • main default protected
  • simp
3 results

Main.hs

Blame
  • Christian Gram Kalhauge's avatar
    chrg authored
    dc3e512b
    History
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    Main.hs 7.64 KiB
    {-# LANGUAGE ApplicativeDo #-}
    {-# LANGUAGE BlockArguments #-}
    {-# LANGUAGE DerivingVia #-}
    {-# LANGUAGE FlexibleContexts #-}
    {-# LANGUAGE ImportQualifiedPost #-}
    {-# LANGUAGE OverloadedStrings #-}
    
    import Control.Monad.IRTree qualified as IRTree
    import ReduceC qualified
    
    import Colog
    import Control.Applicative
    import Control.Monad
    import Control.Monad.State
    import Data.Foldable
    import Data.Function
    import Data.Functor
    import Data.IORef (atomicModifyIORef', atomicWriteIORef, newIORef)
    import Data.List.NonEmpty qualified as NE
    import Data.RPath qualified as RPath
    import Data.Text qualified as Text
    import Data.Time (getCurrentTime)
    import Data.Time qualified as Time
    import Data.Time.Format.ISO8601 (iso8601Show)
    import GHC.Stack
    import Language.C qualified as C
    import Options.Applicative
    import System.Directory
    import System.Exit
    import System.FilePath
    import System.IO
    import System.IO.Error (tryIOError)
    import System.IO.Temp
    import System.Process.Typed
    import Text.Pretty.Simple
    import Text.PrettyPrint qualified as P
    import Prelude hiding (log)
    
    main :: IO ()
    main =
      join
        . execParser
        $ info (run <**> helper)
          . fold
        $ []
    
    process :: (WithLog env (Msg sev) m, MonadIO m) => sev -> Text.Text -> m a -> m a
    process sev p ma = do
      msg "Started "
      a <- ma
      msg "Done "
      pure a
     where
      s = withFrozenCallStack callStack
      msg t = Colog.logMsg (Msg sev s (t <> p))
    
    data Mode
      = Lin
      | Exp
      deriving (Show, Read, Eq, Ord, Enum)
    
    run :: (HasCallStack) => Parser (IO ())
    run = do
      mode <-
        option auto $
          fold
            [ long "mode"
            , help "search mode (Lin, Exp)"
            , value Exp
            ]
    
      checkmode <-
        flag False True $
          fold
            [ long "dry-run"
            , short 'n'
            , help "don't do any reduction"
            ]
    
      keywords <-
        many . option auto $
          fold
            [ long "keyword"
            , short 'k'
            , help "special versions of the code"
            ]
    
      validity <-
        optional $
          option auto $
            fold
              [ long "validity"
              , short 'V'
              , help "check the output for the validity code and output debug information"
              ]
    
      debug <-
        flag False True $
          fold
            [ long "debug"
            , short 'D'
            , help "keep debug versions of all copies"
            ]
    
      fixpoint <-
        flag False True $
          fold
            [ long "fixpoint"
            , short 'F'
            , help "run the computation to a fixpoint"
            ]
    
      pedandic <-
        flag False True $
          fold
            [ long "pedandic"
            , short 'P'
            , help "when checking for validity, throw error if command fails"
            ]
    
      cmd <- strArgument $ fold [metavar "COMMAND"]
    
      file <- strArgument $ fold [metavar "FILE"]
    
      pure do
        time <- getCurrentTime
        let
          fmt m = do
            t' <- getCurrentTime
            pure
              ( Text.pack
                  ( Time.formatTime
                      Time.defaultTimeLocale
                      "%_3m:%04ES "
                      (t' `Time.diffUTCTime` time)
                  )
                  <> fmtMessage m
              )
    
        usingLoggerT (filterBySeverity Info msgSeverity $ cmapM fmt logTextStderr) do
          counter <- liftIO (newIORef (0 :: Int))
    
          let
            test f = process D ("test " <> Text.pack f) do
              err <- liftIO $ runProcess (proc cmd [])
              log D (Text.pack $ show err)
              pure err
    
            output f c = process D ("writing " <> Text.pack f) do
              let x = P.render (C.pretty (c $> C.undefNode))
              liftIO $ writeFile f x
    
            cleanup f = do
              logError "Cleaning up"
              liftIO do
                copyFile (f <.> "bak") f
                removeFile (f <.> "bak")
              liftIO exitFailure
    
            check
              :: FilePath
              -> [RPath.AnnotatedChoice (String, C.Position)]
              -> C.CTranslationUnit C.NodeInfo
              -> LoggerT (Msg Severity) IO Bool
            check f l c = process D "Checking predictate" do
              let xs = NE.nonEmpty (filter ((RPath.Yes ==) . RPath.choice) l)
              let decided = length l - RPath.numberOfUndecided l
    
              count <- liftIO (atomicModifyIORef' counter (\a -> (a + 1, a)))
              logInfo
                ( "Checking (num: "
                    <> Text.pack (show count)
                    <> ") D="
                    <> Text.pack (show decided <> "/" <> show (length l))
                    <> ": "
                    <> Text.pack (maybe "-" ((\(r, p) -> r <> " at " <> show p) . RPath.label . NE.last) xs)
                )
              output f c
              res <- test f
              when debug $ liftIO do
                copyFile f ("case-" <> show count <.> "c")
    
              ret <- case res of
                ExitSuccess -> do
                  liftIO $ copyFile f (f <.> "last")
                  pure True
                ExitFailure x
                  | Just x == validity -> do
                      folder <- liftIO do
                        temp <- getCanonicalTemporaryDirectory
                        createTempDirectory temp "rtree-c"
                      logWarning $ "Produced invalid code, see " <> Text.pack folder <> " for details"
                      liftIO do
                        withFile (folder </> "reduction.path") WriteMode \h -> do
                          forM (takeWhile ((/= RPath.Undecided) . RPath.choice) l) \(RPath.AnnotatedChoice c' (msg, pos)) -> do
                            hPutStr h [RPath.debugShowChoice c']
                            hPutStr h " "
                            hPutStr h msg
                            hPutStr h " at "
                            hPrint h pos
                        copyFile cmd (folder </> takeFileName cmd)
                        copyFile f (folder </> takeFileName f)
                        tryIOError do
                          copyFile (f <.> "last") (folder </> takeFileName (f <.> "last"))
                        pure folder
                      when pedandic do
                        logError "Pedandic, stoping now."
                        liftIO exitFailure
                      pure False
                  | otherwise -> pure False
              logInfo ("Predicate was " <> Text.pack (show ret))
              pure ret
    
          let bak = file <.> "bak"
    
          process D "backing up file" do
            liftIO $ copyFile file bak
    
          process I "check predicate" do
            t <- test file
            unless (t == ExitSuccess) do
              logError "did not satisify predicate"
              liftIO exitFailure
    
          c <- process D "parsing file" do
            parseCFile file
    
          output file c
    
          process I "sanity checks" do
            c' <- parseCFile file
            unless (void c' == void c) do
              liftIO do
                withFile "error.1.hs" WriteMode (`pHPrint` void c)
                withFile "error.2.hs" WriteMode (`pHPrint` void c')
              logError "Outputted a different file than I read... Please report original file and error.{1,2}.hs"
              liftIO exitFailure
    
            let rtree = ReduceC.defaultReduceCWithKeywords keywords c
            t <- check file [] (IRTree.extract rtree)
    
            unless t do
              logError "file did not satisfy predicate"
              cleanup file
    
          when checkmode do
            logInfo "In checking mode, stopping now"
            liftIO exitSuccess
    
          l <-
            c & fix \rec prevc -> do
              let rtree = ReduceC.defaultReduceCWithKeywords keywords prevc
              c' <- case mode of
                Lin -> IRTree.reduce (check file) rtree
                Exp -> IRTree.reduceAdaptive (check file) rtree
    
              if fixpoint && (c' $> ()) /= (prevc $> ())
                then do
                  logInfo "Running again until fixpoint"
                  rec c'
                else pure c'
    
          output file l
          logInfo "Everything went well"
     where
      parseCFile file = do
        res <- liftIO $ C.parseCFilePre file
        case res of
          Right c -> pure c
          Left err -> do
            logError (Text.pack (show err))
            liftIO exitFailure