Skip to content
Snippets Groups Projects
ReduceC.hs 50.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • chrg's avatar
    chrg committed
    {-# LANGUAGE BlockArguments #-}
    
    {-# LANGUAGE ConstraintKinds #-}
    
    chrg's avatar
    chrg committed
    {-# LANGUAGE FlexibleContexts #-}
    
    {-# LANGUAGE FlexibleInstances #-}
    
    chrg's avatar
    chrg committed
    {-# LANGUAGE GADTs #-}
    {-# LANGUAGE LambdaCase #-}
    
    {-# LANGUAGE RankNTypes #-}
    
    {-# LANGUAGE RecordWildCards #-}
    
    {-# LANGUAGE ScopedTypeVariables #-}
    
    chrg's avatar
    chrg committed
    {-# LANGUAGE TupleSections #-}
    
    chrg's avatar
    chrg committed
    {-# LANGUAGE TypeFamilies #-}
    
    chrg's avatar
    chrg committed
    {-# LANGUAGE TypeOperators #-}
    
    chrg's avatar
    chrg committed
    {-# LANGUAGE ViewPatterns #-}
    
    chrg's avatar
    chrg committed
    {-# LANGUAGE NoMonomorphismRestriction #-}
    
    
    chrg's avatar
    chrg committed
    module ReduceC (
      defaultReduceC,
    
    chrg's avatar
    chrg committed
      defaultReduceCWithKeywords,
    
    chrg's avatar
    chrg committed
      -- reduceCTranslUnit,
    
    chrg's avatar
    chrg committed
    
      -- * Context
      Context (..),
      defaultContext,
    
      -- * Helpers
      prettyIdent,
    ) where
    
    chrg's avatar
    chrg committed
    
    
    import Control.Applicative (Alternative (empty, (<|>)))
    import Control.Monad (
      MonadPlus (mzero),
      foldM,
      forM,
      forM_,
      guard,
      join,
      mapAndUnzipM,
      unless,
      void,
      when,
     )
    
    chrg's avatar
    chrg committed
    import qualified Control.Monad.IRTree as IRTree
    
    import Control.Monad.Reduce (
      MonadReduce (split),
      collect,
      exceptIf,
      liftMaybe,
     )
    import Control.Monad.State (
      MonadState (get, state),
      MonadTrans (lift),
      State,
      StateT (runStateT),
      evalState,
      evalStateT,
      gets,
      modify',
      runState,
     )
    import Control.Monad.Trans.Maybe (MaybeT (runMaybeT))
    import Data.Function ((&))
    import Data.Functor (($>), (<&>))
    
    chrg's avatar
    chrg committed
    import qualified Data.List as List
    
    chrg's avatar
    chrg committed
    import qualified Data.List.NonEmpty as NonEmpty
    
    import qualified Data.Map.Strict as Map
    
    import Data.Maybe (
      catMaybes,
      fromMaybe,
      isJust,
      isNothing,
      mapMaybe,
     )
    
    chrg's avatar
    chrg committed
    import qualified Data.Set as Set
    import Data.Vector.Internal.Check (HasCallStack)
    
    chrg's avatar
    chrg committed
    import qualified Language.C as C
    
    chrg's avatar
    chrg committed
    import qualified Language.C.Data.Ident as C
    
    chrg's avatar
    chrg committed
    import qualified Language.C.Data.Node as C
    
    chrg's avatar
    chrg committed
    
    
    defaultReduceCWithKeywords :: (MonadReduce (String, C.Position) m) => [Keyword] -> C.CTranslUnit -> m C.CTranslUnit
    defaultReduceCWithKeywords keywords a = reduceCTranslUnit a (defaultContext{keywords = Set.fromList keywords})
    {-# SPECIALIZE defaultReduceCWithKeywords :: [Keyword] -> C.CTranslUnit -> IRTree.IRTree (String, C.Position) C.CTranslUnit #-}
    
    defaultReduceC :: (MonadReduce (String, C.Position) m) => C.CTranslUnit -> m C.CTranslUnit
    defaultReduceC a = reduceCTranslUnit a defaultContext
    {-# SPECIALIZE defaultReduceC :: C.CTranslUnit -> IRTree.IRTree (String, C.Position) C.CTranslUnit #-}
    
    
    reduceCTranslUnit ::
      (MonadReduce Lab m) =>
      C.CTranslationUnit C.NodeInfo ->
      Context ->
      m (C.CTranslationUnit C.NodeInfo)
    
    chrg's avatar
    chrg committed
    reduceCTranslUnit (C.CTranslUnit es ni) ctx = do
    
    chrg's avatar
    chrg committed
      let _functions = foldMap (findFunctions (: [])) es
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
      let funmap :: [(C.Ident, Maybe Function)] =
            List.sortOn (maybe 0 (negate . funSize) . snd)
              . Map.toList
              . Map.fromListWith const
              . map (\f -> (funName f, Just f))
              . List.sortOn funSize
              $ _functions
    
      let reduce funcs = forM funcs \(k, mf) ->
            (k,) <$> runMaybeT do
              f <- liftMaybe mf
              let fstr = C.identToString (funName f)
              when (C.identToString (funName f) /= "main" || LoseMain `isIn` ctx) do
    
                exceptIf ("remove function " <> fstr <> " (" <> show (funSize f) <> ")", funPosition f)
    
    chrg's avatar
    chrg committed
              isStatic <-
                if funIsStatic f
                  then
                    split
                      ("remove static from " <> fstr, funPosition f)
                      (pure False)
                      (pure True)
                  else pure False
              pure f{funIsStatic = isStatic}
    
      -- try remove static
    
      functions2 <- do
        funmap' <- reduce funmap
        if ComputeFunctionFixpoint `isIn` ctx
          then reduce funmap
          else pure funmap'
    
    chrg's avatar
    chrg committed
    
      functions3 <- forM functions2 \(k, mf) ->
        (k,) <$> runMaybeT do
          f <- liftMaybe mf
    
    chrg's avatar
    chrg committed
          if C.identToString (funName f) /= "main" || LoseMain `isIn` ctx
            then do
              params <- case funParams f of
                Just params -> do
                  Just <$> forM (zip [1 :: Int ..] params) \(i, p) ->
                    if p
                      then split ("remove parameter " <> show i <> " from " <> C.identToString (funName f), funPosition f) (pure False) (pure True)
                      else pure False
                ow -> pure ow
              pure f{funParams = params}
            else do
              pure f
    
    chrg's avatar
    chrg committed
    
      let builtins =
            [ ("fabsf", FunType (NonVoid TNum) (Params [Just TNum] False))
            , ("fabs", FunType (NonVoid TNum) (Params [Just TNum] False))
    
            , ("__builtin_abort", FunType Void (Params [] False))
    
    chrg's avatar
    chrg committed
    
      let functions''' =
            Map.fromList $
    
    chrg's avatar
    chrg committed
              [ ( funName
                , Just $
                    Function
                      { funIsStatic = False
                      , funPosition = C.posOf funName
                      , funSize = 0
                      , funParams = case funTypeParams funType of
                          VoidParams -> Nothing
                          Params _ True -> Nothing
                          Params fx False -> Just [isJust f | f <- fx]
                      , ..
                      }
                )
              | (C.builtinIdent -> funName, funType) <- builtins
              ]
                <> functions3
    
      let ctx' =
            ctx
              { functions = functions'''
              , inlineExprs =
                  inlineExprs ctx
                    <> Map.fromList
                      [(C.builtinIdent f, IEKeep (TFun ft)) | (f, ft) <- builtins]
              }
    
    chrg's avatar
    chrg committed
      res' <- evalStateT (mapM reduceCExternalDeclaration es) ctx'
      pure $ C.CTranslUnit (catMaybes res') ni
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
    newtype SpecifierFilter = SpecifierFilter
    
    chrg's avatar
    chrg committed
      { sfKeepStatic :: Bool
      }
    
    keepAll :: SpecifierFilter
    keepAll = SpecifierFilter{sfKeepStatic = True}
    
    {- | Update the CDeclarationSpecifier's to match the context. Specifically, update
    the typedefs and the structs. Alos return a base type.
    -}
    
    updateCDeclarationSpecifiers ::
      ( MonadState Context m
      , MonadPlus m
      ) =>
      SpecifierFilter ->
      [C.CDeclarationSpecifier C.NodeInfo] ->
      m (Voidable, [C.CDeclarationSpecifier C.NodeInfo])
    
    chrg's avatar
    chrg committed
    updateCDeclarationSpecifiers sf spec = do
      ctx <- get
      spec' <- concat <$> mapM (updateSpec ctx) spec
      bt <- baseType ctx spec'
      pure (bt, spec')
     where
    
      baseType ::
        (MonadPlus m) =>
        Context ->
        [C.CDeclarationSpecifier C.NodeInfo] ->
        m Voidable
    
    chrg's avatar
    chrg committed
      baseType ctx =
    
    chrg's avatar
    chrg committed
        liftMaybe
    
    chrg's avatar
    chrg committed
          . exactlyOne
          . map \case
            C.CVoidType _ -> Just Void
            C.CSUType c _ -> NonVoid . TStruct <$> structId c
            C.CCharType _ -> Just $ NonVoid TNum
            C.CShortType _ -> Just $ NonVoid TNum
            C.CIntType _ -> Just $ NonVoid TNum
            C.CFloatType _ -> Just $ NonVoid TNum
            C.CDoubleType _ -> Just $ NonVoid TNum
            C.CSignedType _ -> Just $ NonVoid TNum
            C.CUnsigType _ -> Just $ NonVoid TNum
            C.CBoolType _ -> Just $ NonVoid TNum
            C.CLongType _ -> Just $ NonVoid TNum
            C.CInt128Type _ -> Just $ NonVoid TNum
            C.CFloatNType{} -> Just $ NonVoid TNum
    
    chrg's avatar
    chrg committed
            C.CEnumType (C.CEnum (Just ix) _ _ _) _ ->
              NonVoid TNum
                <$ guard (lookupEnum ctx ix == INKeep)
            C.CEnumType (C.CEnum Nothing _ _ _) _ -> Just $ NonVoid TNum
    
    chrg's avatar
    chrg committed
            C.CTypeDef idx _ ->
              case Map.lookup idx (typeDefs ctx) of
    
    chrg's avatar
    chrg committed
                Just (ITKeep t') -> Just t'
                Just ITDelete -> Nothing
                Just (ITInline t' _) -> Just t'
                Nothing -> error "error"
    
    chrg's avatar
    chrg committed
            a -> notSupportedYet (void a) a
          . typeSpecs
       where
        typeSpecs = mapMaybe \case
          C.CTypeSpec ts -> Just ts
          _ow -> Nothing
    
        exactlyOne =
          maybe
            (error "no type in type-specs")
            ( \case
                (t, []) -> NonEmpty.head t
                (t, rs) -> error ("more than one type in type-specs: " <> show (t : rs))
    
    chrg's avatar
    chrg committed
            . List.uncons
            . NonEmpty.group
    
        structId (C.CStruct t mi md _ ni) =
          case mi of
    
    chrg's avatar
    chrg committed
            Just ix -> case lookupStruct ctx ix of
              ISDelete -> Nothing
              _ow -> Just $ Left ix
    
    chrg's avatar
    chrg committed
            Nothing ->
              let p' =
                    maybe
                      (error $ "invalid struct at" <> show (C.posOf ni))
                      (concatMap namesAndTypeOf)
                      md
    
    chrg's avatar
    chrg committed
               in pure $ Right (StructType t Nothing p')
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
        -- structTypeOf (C.CStruct t mi md _ ni) =
        --   case mi of
        --     Just ix -> lookupStruct ctx ix
        --     Nothing ->
        --       let p' = maybe (error $ "invalid struct at" <> show (C.posOf ni)) (concatMap namesAndTypeOf) md
        --        in Just $ StructType t mi (Just p')
    
    chrg's avatar
    chrg committed
    
        namesAndTypeOf = \case
          C.CDecl spec2 items ni ->
            flip map items \case
              C.CDeclarationItem decl@(C.CDeclr (Just ix) _ _ _ _) _ _ ->
                (ix, nonVoid <$> typeOf spec2 decl)
              a -> notSupportedYet (void a) ni
          a -> notSupportedYet' a
    
        typeOf spec2 decl = baseType ctx spec2 >>= extendTypeWith decl
    
        extendTypeWith (C.CDeclr _ dd _ _ _) t =
          foldr applyDD (Just t) dd
         where
          applyDD = \case
            C.CPtrDeclr _ _ -> fmap (NonVoid . TPointer)
            C.CArrDeclr{} -> fmap (NonVoid . TPointer)
            C.CFunDeclr params _ ni -> \c ->
              case params of
                C.CFunParamsNew params' varadic -> do
                  c' <- c
                  Just $ NonVoid $ TFun (FunType c' (findParams varadic params'))
                b -> notSupportedYet b ni
    
          findParams varadic = \case
            [C.CDecl [C.CTypeSpec (C.CVoidType _)] [] _] -> VoidParams
            rst -> flip Params varadic $ flip map rst \case
              C.CDecl spec' [] _ ->
                nonVoid <$> baseType ctx spec'
              C.CDecl spec' [C.CDeclarationItem decl _ _] _ ->
                nonVoid <$> typeOf spec' decl
              a -> notSupportedYet' a
    
    chrg's avatar
    chrg committed
    
      updateSpec ctx a = case a of
        C.CTypeSpec t -> case t of
          C.CSUType (C.CStruct st (Just i) (Just declrs) attr x) b -> do
    
    chrg's avatar
    chrg committed
            fields <- case lookupStruct ctx i of
              ISDelete -> empty
              ISDeclared _ -> empty
              ISKeep s -> do
                pure $ structTypeFields s
    
    chrg's avatar
    chrg committed
            let declrs' :: [C.CDeclaration C.NodeInfo] = filterStruct ctx fields declrs
            pure [C.CTypeSpec (C.CSUType (C.CStruct st (Just i) (Just declrs') attr x) b)]
          C.CTypeDef idx _ -> do
            case Map.lookup idx . typeDefs $ ctx of
              Just (ITKeep _) -> pure [C.CTypeSpec t]
              Just (ITInline _ res) -> pure res
              Just ITDelete -> mzero
              Nothing -> error ("could not find typedef: " <> show idx)
          _ow -> pure [C.CTypeSpec t]
        C.CStorageSpec (C.CStatic _) -> pure [a | sfKeepStatic sf]
        C.CFunSpec (C.CInlineQual _) -> pure [a | sfKeepStatic sf]
        _ow -> pure [a]
    
    
    chrg's avatar
    chrg committed
      filterStruct ctx fields declrs =
        flip evalState fields do
          declrs' <- forM declrs $ \case
            C.CDecl spec2 items l -> runMaybeT do
              items' <- forM items $ \case
                C.CDeclarationItem (C.CDeclr mid dd sl attr ni2) enit ni1 -> runMaybeT do
                  _ <- liftMaybe =<< state (\((_, t) : tps) -> (t, tps))
                  (_, dd') <- liftMaybe (evalStateT (updateCDerivedDeclarators Void (repeat True) dd) ctx)
                  pure (C.CDeclarationItem (C.CDeclr mid dd' sl attr ni2) enit ni1)
                a' -> notSupportedYet a' l
              (_, spec2') <- liftMaybe (evalStateT (updateCDeclarationSpecifiers keepAll spec2) ctx)
              let items'' = catMaybes items'
              guard $ not (List.null items'')
              pure (C.CDecl spec2' items'' l)
            a' -> notSupportedYet' a'
          pure $ catMaybes declrs'
    
    updateCDerivedDeclarators ::
      forall m.
      ( MonadState Context m
      , MonadPlus m
      ) =>
      Voidable ->
      [Bool] ->
      [C.CDerivedDeclarator C.NodeInfo] ->
      m (Voidable, [C.CDerivedDeclarator C.NodeInfo])
    
    chrg's avatar
    chrg committed
    updateCDerivedDeclarators bt ff dd = do
      foldM applyDD (bt, []) (reverse dd)
     where
    
      applyDD ::
        (r ~ (Voidable, [C.CDerivedDeclarator C.NodeInfo])) =>
        r ->
        C.CDerivedDeclarator C.NodeInfo ->
        m r
    
    chrg's avatar
    chrg committed
      applyDD (t, dd') d = case d of
        C.CPtrDeclr _ _ -> do
          pure (NonVoid . TPointer $ t, d : dd')
        C.CArrDeclr{} ->
          pure (NonVoid . TPointer $ t, d : dd')
        C.CFunDeclr params arr ni -> do
          case params of
            C.CFunParamsNew params' varadic -> do
              (tp, params'') <- state (runState (findParams varadic params'))
              let t' = NonVoid $ TFun (FunType t tp)
              pure (t', C.CFunDeclr (C.CFunParamsNew params'' varadic) arr ni : dd')
            b -> notSupportedYet b ni
    
    
      findParams ::
        Bool ->
        [C.CDeclaration C.NodeInfo] ->
        State Context (Params, [C.CDeclaration C.NodeInfo])
    
    chrg's avatar
    chrg committed
      findParams varadic decls = case decls of
        [C.CDecl [C.CTypeSpec (C.CVoidType _)] [] _] ->
          pure (VoidParams, decls)
        _ow -> flip evalStateT ff do
          result <-
            forM decls $ \case
              C.CDecl spec items ni -> do
                keep <- state (\(t : tps) -> (t, tps))
                lift . runMaybeT $ do
    
    chrg's avatar
    chrg committed
                  markDeleted items
    
    chrg's avatar
    chrg committed
                  (bt', spec') <- updateCDeclarationSpecifiers keepAll spec
                  (t, items') <- case items of
                    [] -> do
                      guard keep
                      pure (nonVoid bt', [])
                    [C.CDeclarationItem (C.CDeclr mid dd2 Nothing [] ni3) Nothing ni2] -> do
                      (t, dd2') <- case mid of
                        Just ix -> do
                          (nonVoid -> t, dd2') <- updateCDerivedDeclarators bt' [] dd2
                          guard keep
                          modify' (addInlineExpr ix (IEKeep t))
                          pure (t, dd2')
                        Nothing -> do
                          (nonVoid -> t, dd2') <- updateCDerivedDeclarators bt' [] dd2
                          guard keep
                          pure (t, dd2')
                      pure (t, [C.CDeclarationItem (C.CDeclr mid dd2' Nothing [] ni3) Nothing ni2])
                    _ow -> notSupportedYet items ni
                  pure (t, C.CDecl spec' items' ni)
              a -> notSupportedYet' a
    
    chrg's avatar
    chrg committed
          let (ts, decls') = unzip $ flip map result \case
                Just (t, d') -> (Just t, [d'])
                Nothing -> (Nothing, [])
          pure (Params ts varadic, concat decls')
    
    reduceCExternalDeclaration ::
      (HasCallStack, MonadReduce Lab m) =>
      C.CExternalDeclaration C.NodeInfo ->
      StateT Context m (Maybe (C.CExternalDeclaration C.NodeInfo))
    
    chrg's avatar
    chrg committed
    reduceCExternalDeclaration r = case r of
      C.CFDefExt (C.CFunDef spec declr [] stmt ni) -> runMaybeT do
        ctx <- get
    
        let C.CDeclr mid dd Nothing [] ni2 = declr
    
    
    chrg's avatar
    chrg committed
        mfun <- case mid of
    
    chrg's avatar
    chrg committed
          Just fid -> do
    
            modify' (addInlineExpr fid IEDelete)
    
    chrg's avatar
    chrg committed
            Just <$> liftMaybe (lookupFunction ctx fid)
          Nothing ->
            pure Nothing
    
        let keepStatic = maybe True funIsStatic mfun
    
    chrg's avatar
    chrg committed
        -- TODO handle this edgecase (struct declared in function declaration)
        _ <- reduceStructDeclaration spec
        (bt, spec') <- updateCDeclarationSpecifiers keepAll{sfKeepStatic = keepStatic} spec
    
    chrg's avatar
    chrg committed
        ((nonVoid -> t@(TFun (FunType rt _)), dd'), ctx') <-
          runStateT
            (updateCDerivedDeclarators bt (fromMaybe (repeat True) (mfun >>= funParams)) dd)
            ctx
    
        case mfun of
          Just fun -> do
            modify' (addInlineExpr (funName fun) (IEKeep t))
    
    chrg's avatar
    chrg committed
          Nothing -> do
            exceptIf ("remove function", C.posOf r)
    
        labs <- flip collect (labelsOf stmt) \l -> do
          exceptIf ("remove label" <> show l, C.posOf l)
          pure l
    
        stmt' <-
    
    chrg's avatar
    chrg committed
          reduceCStatementOrEmptyBlock stmt StmtContext{stmtLabels = labs, stmtInLoop = False} $
    
    chrg's avatar
    chrg committed
            ctx'{returnType = rt}
    
    chrg's avatar
    chrg committed
    
        pure . C.CFDefExt $
    
    chrg's avatar
    chrg committed
          C.CFunDef spec' (C.CDeclr mid dd' Nothing [] ni2) [] stmt' ni
    
    chrg's avatar
    chrg committed
    
      -- Type definitions
    
    chrg's avatar
    chrg committed
      C.CDeclExt (C.CDecl (C.CStorageSpec (C.CTypedef nif) : rst) [item] ni) -> runMaybeT do
    
        (ix, dd, wrap) <- case item of
          C.CDeclarationItem (C.CDeclr (Just ix) dd Nothing extras _) Nothing Nothing ->
            case extras of
              [] -> pure (ix, dd, id)
              [C.CAttr (C.Ident "__vector_size__" _ _) [a] _] -> do
                case a of
                  C.CBinary C.CMulOp (C.CConst (C.CIntConst (C.CInteger n _ _) _)) (C.CSizeofType _ _) _ ->
                    -- todo assuming this is a checked size
                    pure
                      ( ix
                      , dd
                      , NonVoid . TVector (fromInteger n)
                      )
                  _ -> notSupportedYet a ni
              a -> notSupportedYet (map void a) ni
    
    chrg's avatar
    chrg committed
          i -> notSupportedYet (void i) ni
    
    chrg's avatar
    chrg committed
    
        modify' (addTypeDef ix ITDelete)
    
    
    chrg's avatar
    chrg committed
        keep <- reduceStructDeclaration rst
        (bt, rst') <- updateCDeclarationSpecifiers keepAll rst
    
        (t, _) <- updateCDerivedDeclarators bt (repeat True) dd
    
    chrg's avatar
    chrg committed
    
        unless keep do
    
          modify' (addTypeDef ix (ITInline (wrap t) rst'))
    
    chrg's avatar
    chrg committed
          exceptIf ("inline typedef " <> C.identToString ix, C.posOf ni)
    
    
        modify' (addTypeDef ix (ITKeep (wrap t)))
    
    chrg's avatar
    chrg committed
        pure $ C.CDeclExt $ C.CDecl (C.CStorageSpec (C.CTypedef nif) : rst') [item] ni
    
    chrg's avatar
    chrg committed
    
      -- The rest.
      C.CDeclExt (C.CDecl spec items ni) -> runMaybeT do
        ctx <- get
    
    
    chrg's avatar
    chrg committed
        markDeleted items
    
        -- TODO: Actually we should split it up here
        let isStatic = flip any items \case
              (C.CDeclarationItem (C.CDeclr (Just fid) (C.CFunDeclr{} : _) _ _ _) _ _) -> do
                maybe True funIsStatic (lookupFunction ctx fid)
              _ow -> True
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
        keep <- reduceStructDeclaration spec
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
        (bt, spec') <- updateCDeclarationSpecifiers keepAll{sfKeepStatic = isStatic} spec
    
    chrg's avatar
    chrg committed
        -- Try to remove each declaration item
    
    chrg's avatar
    chrg committed
        items' <-
          flip collect items \case
    
    chrg's avatar
    chrg committed
            di@(C.CDeclarationItem (C.CDeclr mid dd Nothing [] ni2) einit size) -> do
              case dd of
    
    chrg's avatar
    chrg committed
                C.CFunDeclr{} : _ -> do
                  mfun <- case mid of
                    Just fid ->
                      Just <$> liftMaybe (lookupFunction ctx fid)
                    Nothing ->
                      pure Nothing
                  let ff = fromMaybe (repeat True) (mfun >>= funParams)
                  (nonVoid -> t, dd') <-
                    evalStateT (updateCDerivedDeclarators bt ff dd) ctx
                  case mid of
    
    chrg's avatar
    chrg committed
                    Just fid -> do
    
    chrg's avatar
    chrg committed
                      modify' (addInlineExpr fid IEDelete)
                      exceptIf ("remove function declaration", C.posOf ni2)
    
    chrg's avatar
    chrg committed
                      modify' (addInlineExpr fid (IEKeep t))
    
    chrg's avatar
    chrg committed
                    Nothing -> do
                      exceptIf ("remove function", C.posOf ni2)
    
    chrg's avatar
    chrg committed
                  pure (C.CDeclarationItem (C.CDeclr mid dd' Nothing [] ni2) einit size)
                _dd -> reduceCDeclarationItem bt di
    
    chrg's avatar
    chrg committed
            a -> notSupportedYet (a $> ()) ni
    
    chrg's avatar
    chrg committed
    
        -- Somtimes we just declare a struct or a typedef.
        when (not keep && List.null items') do
    
    chrg's avatar
    chrg committed
          guard (AllowEmptyDeclarations `isIn` ctx || List.null items)
    
    chrg's avatar
    chrg committed
          exceptIf ("remove declaration", C.posOf ni)
    
    
    chrg's avatar
    chrg committed
        pure $ C.CDeclExt $ C.CDecl spec' items' ni
    
      _r -> notSupportedYet' r
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
    wrapCCompound :: C.CStatement C.NodeInfo -> C.CStatement C.NodeInfo
    wrapCCompound = \case
      s@(C.CCompound{}) -> s
      s -> C.CCompound [] [C.CBlockStmt s] C.undefNode
    
    isStaticFromSpecs :: [C.CDeclarationSpecifier C.NodeInfo] -> Bool
    isStaticFromSpecs = any \case
      (C.CStorageSpec (C.CStatic _)) -> True
      _ow -> False
    
    
    chrg's avatar
    chrg committed
    {- | This checks the current declaration and reduces any new struct found here.
    Returns true if the specifier is requried.
    -}
    
    reduceStructDeclaration ::
      ( MonadReduce Lab m
      , MonadState Context m
      , MonadPlus m
      ) =>
      [C.CDeclarationSpecifier C.NodeInfo] ->
      m Bool
    
    chrg's avatar
    chrg committed
    reduceStructDeclaration =
    
    chrg's avatar
    chrg committed
      fmap or . mapM \case
    
    chrg's avatar
    chrg committed
        C.CTypeSpec (C.CEnumType (C.CEnum mid mf _ _) ni) -> do
          case mid of
            Just eid -> do
              case mf of
                Just times -> forM_ times \(C.CEnumVar ix _) -> do
                  modify' (addInlineExpr ix IEDelete)
                Nothing -> pure ()
              modify' (addEnum eid INDelete)
              exceptIf ("delete enum " <> C.identToString eid, C.posOf ni)
              modify' (addEnum eid INKeep)
              case mf of
                Just times -> forM_ times \(C.CEnumVar ix _) -> do
                  modify' (addInlineExpr ix (IEKeep TNum))
                Nothing -> pure ()
    
              pure True
            Nothing -> do
              pure False
    
    chrg's avatar
    chrg committed
        C.CTypeSpec (C.CSUType (C.CStruct tag mid mfields _ _) ni) -> case mid of
    
    chrg's avatar
    chrg committed
          Just sid -> do
            struct <- gets (Map.lookup sid . structs)
    
    chrg's avatar
    chrg committed
            let reduce fields = do
                  exceptIf ("remove struct " <> C.identToString sid, C.posOf ni)
                  modify' (addStruct sid (ISDeclared tag))
                  (ft, _) <- mapAndUnzipM (structField sid) fields
                  modify' (addStruct sid (ISKeep (StructType tag (Just sid) (concat ft))))
                  pure True
    
    chrg's avatar
    chrg committed
            case struct of
    
    chrg's avatar
    chrg committed
              Just (ISDeclared _) ->
                case mfields of
                  Just fields -> reduce fields
                  Nothing -> pure False
              Just (ISKeep _) -> do
    
    chrg's avatar
    chrg committed
                pure False
    
    chrg's avatar
    chrg committed
              Just ISDelete -> do
    
    chrg's avatar
    chrg committed
                case mfields of
    
    chrg's avatar
    chrg committed
                  Just fields -> reduce fields
                  Nothing -> pure True
              Nothing -> do
                modify' (addStruct sid ISDelete)
                case mfields of
                  Just fields -> reduce fields
    
    chrg's avatar
    chrg committed
                  Nothing -> do
    
    chrg's avatar
    chrg committed
                    exceptIf ("remove struct declaration " <> C.identToString sid, C.posOf ni)
                    modify' (addStruct sid (ISDeclared tag))
    
    chrg's avatar
    chrg committed
                    pure True
          Nothing -> pure False
        _ow -> pure False
    
    chrg's avatar
    chrg committed
     where
    
      structField sid = \case
    
    chrg's avatar
    chrg committed
        C.CDecl spec items ni -> do
    
          res <- runMaybeT $ updateCDeclarationSpecifiers keepAll spec
          case res of
            Just (bt, spec') -> do
              (fields, items') <- flip mapAndUnzipM items \case
                (C.CDeclarationItem (C.CDeclr mid dd Nothing [] ni3) ini ni2) -> do
                  let fid = fromMaybe (error "all struct fields should be named") mid
                  res' <- runMaybeT $ do
                    (nonVoid -> t, dd') <- updateCDerivedDeclarators bt (repeat True) dd
                    exceptIf ("remove field " <> C.identToString sid <> "." <> C.identToString fid, C.posOf ni)
                    pure (t, dd')
                  case res' of
                    Nothing -> pure ((fid, Nothing), Nothing)
                    Just (t, dd') -> pure ((fid, Just t), Just $ C.CDeclarationItem (C.CDeclr mid dd' Nothing [] ni3) ini ni2)
                a -> notSupportedYet a ni
              case catMaybes items' of
                [] -> pure (fields, Nothing)
                items'' -> pure (fields, Just (C.CDecl spec' items'' ni))
            Nothing ->
              pure
                ( map (\i -> (fromMaybe (error "all struct fields should be named") (name i), Nothing)) items
                , Nothing
                )
    
    chrg's avatar
    chrg committed
        a@(C.CStaticAssert{}) -> notSupportedYet' a
    
    
    reduceCDeclarationItem ::
      ( MonadReduce Lab m
      , MonadState Context m
      , MonadPlus m
      ) =>
      Voidable ->
      C.CDeclarationItem C.NodeInfo ->
      m (C.CDeclarationItem C.NodeInfo)
    
    chrg's avatar
    chrg committed
    reduceCDeclarationItem bt = \case
      di@(C.CDeclarationItem (C.CDeclr mid dd Nothing [] ni) einit Nothing) -> do
    
        ctx <- get
        case mid of
          Just vid -> do
    
    chrg's avatar
    chrg committed
            (nonVoid -> t, dd') <- evalStateT (updateCDerivedDeclarators bt (repeat True) dd) ctx
    
            einit' <- case einit of
    
    chrg's avatar
    chrg committed
              Just einit2 -> do
                (einit', inlinable) <- reduceCInitializer t einit2 ctx
                case inlinable of
                  Just e' -> do
                    modify' (addInlineExpr vid (IEInline e'))
                    exceptIf ("inline variable " <> C.identToString vid, C.posOf ni)
                  Nothing -> do
                    exceptIf ("delete variable", C.posOf ni)
                pure (Just einit')
    
              Nothing -> do
    
    chrg's avatar
    chrg committed
                exceptIf ("delete uninitilized variable", C.posOf ni)
    
    chrg's avatar
    chrg committed
                whenSplit
                  (t == TNum)
                  ("initilize variable", C.posOf ni)
                  (pure . Just $ C.CInitExpr zeroExpr C.undefNode)
                  (pure Nothing)
    
    chrg's avatar
    chrg committed
            modify' (addInlineExpr vid (IEKeep t))
            let decl' = C.CDeclr mid dd' Nothing [] ni
            pure (C.CDeclarationItem decl' einit' Nothing)
    
          Nothing -> do
            exceptIf ("remove unnamed declaration item", C.posOf ni)
            pure di
      a -> notSupportedYet a C.undefNode
    
    chrg's avatar
    chrg committed
    
    
    reduceCInitializer ::
      (MonadReduce Lab m) =>
      Type ->
      C.CInitializer C.NodeInfo ->
      Context ->
      m (C.CInitializer C.NodeInfo, Maybe C.CExpr)
    
    chrg's avatar
    chrg committed
    reduceCInitializer t einit ctx = case einit of
      C.CInitExpr e ni2 -> do
    
        let me = reduceCExpr e (exactly t) ctx
        case (me, t) of
          (Just es, _) -> do
            e' <- es
            pure
              ( C.CInitExpr e' ni2
              , case e' of
                  C.CConst _ -> Just e'
                  C.CVar _ _ -> Just e'
                  _ow -> Nothing
              )
          (Nothing, TVector n _) -> do
            let items' = [([], C.CInitExpr zeroExpr ni2) | _ <- replicate n ()]
            pure (C.CInitList (C.CInitializerList items') ni2, Nothing)
          (Nothing, _) -> do
            let e' = zeroExpr
            pure
              ( C.CInitExpr e' ni2
              , case e' of
                  C.CConst _ -> Just e'
                  C.CVar _ _ -> Just e'
                  _ow -> Nothing
              )
    
    chrg's avatar
    chrg committed
      C.CInitList (C.CInitializerList items) ni2 -> do
        items' <- case t of
          TStruct stct -> do
    
    chrg's avatar
    chrg committed
            let fields = fieldsOfStruct ctx stct
            let i'' = catMaybes $ zipWith (\(_, t') i -> (i,) <$> t') fields items
    
    chrg's avatar
    chrg committed
            forM i'' \((p, r), t') -> do
              (r', _) <- reduceCInitializer t' r ctx
              pure (p, r')
          TPointer (NonVoid t') -> do
            forM items \(p, r) -> do
              (r', _) <- reduceCInitializer t' r ctx
              pure (p, r')
    
    chrg's avatar
    chrg committed
          _ow ->
            -- "Unexpected type of init list: " <> show t <> " at " <> show (C.posOf ni2)
            pure items
    
    chrg's avatar
    chrg committed
        pure (C.CInitList (C.CInitializerList items') ni2, Nothing)
    
    
    reduceCCompoundBlockItem ::
      (MonadReduce Lab m, HasCallStack) =>
      StmtContext ->
      C.CCompoundBlockItem C.NodeInfo ->
      StateT Context m [C.CCompoundBlockItem C.NodeInfo]
    
    chrg's avatar
    chrg committed
    reduceCCompoundBlockItem lab r = do
    
    chrg's avatar
    chrg committed
      case r of
        C.CBlockStmt smt -> do
    
    chrg's avatar
    chrg committed
          ctx <- get
          msmt <- runMaybeT $ reduceCStatement smt lab ctx
          case msmt of
            Just smt' -> do
    
    chrg's avatar
    chrg committed
              case smt' of
    
    chrg's avatar
    chrg committed
                C.CCompound [] ss _ ->
    
    chrg's avatar
    chrg committed
                  whenSplit
                    (all (\case C.CBlockStmt _ -> True; _ow -> False) ss)
    
    chrg's avatar
    chrg committed
                    ("expand compound statment", C.posOf r)
                    (pure ss)
                    (pure [C.CBlockStmt smt'])
                _ow -> pure [C.CBlockStmt smt']
            Nothing -> pure []
    
    chrg's avatar
    chrg committed
        C.CBlockDecl (C.CDecl spec items ni) -> fmap (fromMaybe []) . runMaybeT $ do
    
    chrg's avatar
    chrg committed
          ctx <- get
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
          markDeleted items
    
    
    chrg's avatar
    chrg committed
          keep <- reduceStructDeclaration spec
          (bt, spec') <- updateCDeclarationSpecifiers keepAll spec
    
    chrg's avatar
    chrg committed
    
          -- Try to remove each declaration item
    
    chrg's avatar
    chrg committed
          items' <- collect (reduceCDeclarationItem bt) items
    
    chrg's avatar
    chrg committed
    
          -- Somtimes we just declare a struct or a typedef.
          when (not keep && List.null items') do
    
    chrg's avatar
    chrg committed
            guard (AllowEmptyDeclarations `isIn` ctx || List.null items)
    
    chrg's avatar
    chrg committed
            exceptIf ("remove declaration", C.posOf ni)
    
    
    chrg's avatar
    chrg committed
          pure [C.CBlockDecl (C.CDecl spec' items' ni)]
    
        a -> notSupportedYet' a
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
    markDeleted :: (MonadState Context m) => [C.CDeclarationItem C.NodeInfo] -> m ()
    markDeleted = mapM_ \case
      C.CDeclarationItem (name -> Just ix) _ _ -> do
        modify' (addInlineExpr ix IEDelete)
      _a -> pure ()
    
    
    reduceCStatementOrEmptyBlock ::
      (MonadReduce Lab m, HasCallStack) =>
      C.CStatement C.NodeInfo ->
      StmtContext ->
      Context ->
      m (C.CStatement C.NodeInfo)
    
    chrg's avatar
    chrg committed
    reduceCStatementOrEmptyBlock stmt ids ctx = do
    
    chrg's avatar
    chrg committed
      fromMaybe emptyBlock
        <$> runMaybeT
          ( wrapCCompound <$> reduceCStatement stmt ids ctx
          )
    
    
    reduceCStatementOrEmptyExpr ::
      (MonadReduce Lab m, HasCallStack) =>
      C.CStatement C.NodeInfo ->
      StmtContext ->
      Context ->
      m (C.CStatement C.NodeInfo)
    
    chrg's avatar
    chrg committed
    reduceCStatementOrEmptyExpr stmt ids ctx = do
      fromMaybe (C.CExpr Nothing C.undefNode)
        <$> runMaybeT (reduceCStatement stmt ids ctx)
    
    chrg's avatar
    chrg committed
    
    emptyBlock :: C.CStatement C.NodeInfo
    emptyBlock = C.CCompound [] [] C.undefNode
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
    data StmtContext = StmtContext
      { stmtLabels :: ![C.Ident]
      , stmtInLoop :: !Bool
      }
      deriving (Show, Eq)
    
    
    etAny :: EType
    etAny = EType ETAny False
    
    etNum :: EType
    etNum = EType (ETExactly TNum) False
    
    exactly :: Type -> EType
    exactly c = EType (ETExactly c) False
    
    
    chrg's avatar
    chrg committed
    -- | Reduce given a list of required labels reduce a c statement, possibly into nothingness.
    
    reduceCStatement ::
      forall m.
      (MonadReduce Lab m, HasCallStack) =>
      C.CStatement C.NodeInfo ->
      StmtContext ->
      Context ->
      MaybeT m (C.CStatement C.NodeInfo)
    
    chrg's avatar
    chrg committed
    reduceCStatement smt labs ctx = case smt of
      C.CCompound is cbi ni -> do
        cbi' <- lift $ evalStateT (mapM (reduceCCompoundBlockItem labs) cbi) ctx
    
    chrg's avatar
    chrg committed
        pure (C.CCompound is (concat cbi') ni)
      C.CWhile e s dow ni -> split
        ("remove while loop", C.posOf ni)
        do
          reduceCStatement s labs ctx
        do
          s' <- reduceCStatement s labs{stmtInLoop = True} ctx
    
          e' <- fromMaybe (pure zeroExpr) (reduceCExpr e etNum ctx)
    
    chrg's avatar
    chrg committed
          pure $ C.CWhile e' s' dow ni
    
    chrg's avatar
    chrg committed
      C.CExpr me ni -> do
        case me of
          Just e -> do
            if DoNoops `isIn` ctx
    
    chrg's avatar
    chrg committed
              then do
    
                e' <-
                  maybeSplit ("change to noop", C.posOf smt) $
                    reduceCExpr e etAny ctx
    
    chrg's avatar
    chrg committed
                pure $ C.CExpr e' ni
              else do
    
                re' <- liftMaybe $ reduceCExpr e etAny ctx
    
    chrg's avatar
    chrg committed
                exceptIf ("remove expr statement", C.posOf smt)
                e' <- re'
                pure $ C.CExpr (Just e') ni
    
    chrg's avatar
    chrg committed
          Nothing -> do
            exceptIf ("remove expr statement", C.posOf smt)
    
    chrg's avatar
    chrg committed
            pure $ C.CExpr Nothing ni
    
    chrg's avatar
    chrg committed
      C.CReturn me ni -> do
    
        re :: m (Maybe C.CExpr) <- case me of
    
    chrg's avatar
    chrg committed
          Just e -> do
    
            case returnType ctx of
              NonVoid rt -> do
                res :: (m C.CExpr) <- liftMaybe (reduceCExpr e (exactly rt) ctx)
                pure (Just <$> res)
              Void -> pure (pure Nothing)
          Nothing -> pure (pure Nothing)
        exceptIf ("remove return statement", C.posOf smt)
        e <- lift re
        pure $ C.CReturn e ni
    
    chrg's avatar
    chrg committed
      C.CIf e s els ni -> do
    
        e' <- maybeSplit ("remove condition", C.posOf e) $ reduceCExpr e etNum ctx
    
    chrg's avatar
    chrg committed
        els' <- lift . runMaybeT $ do
          els' <- liftMaybe els
    
    chrg's avatar
    chrg committed
          exceptIf ("remove else branch", C.posOf e)
    
    chrg's avatar
    chrg committed
          reduceCStatement els' labs ctx
    
    chrg's avatar
    chrg committed
        ms' <- lift . runMaybeT $ do
          exceptIf ("remove if branch", C.posOf e)
          reduceCStatement s labs ctx
    
    chrg's avatar
    chrg committed
        case (e', ms', els') of
    
    chrg's avatar
    chrg committed
          (Nothing, Nothing, Nothing) -> empty
    
    chrg's avatar
    chrg committed
          (Just e'', Just s', Nothing) -> pure $ C.CIf e'' s' Nothing ni
          (Nothing, Just s', Just x) -> pure $ C.CIf zeroExpr s' (Just x) ni
          (Just e'', Just s', Just x) -> pure $ C.CIf e'' s' (Just x) ni
          (Just e'', Nothing, Nothing) -> pure $ C.CExpr (Just e'') C.undefNode
          (Nothing, Nothing, Just x) -> pure x
          (Just e'', Nothing, Just x) -> pure $ C.CIf e'' emptyBlock (Just x) ni
          (Nothing, Just s', Nothing) -> pure s'
    
    chrg's avatar
    chrg committed
      C.CFor e1 e2 e3 s ni -> case e1 of
        C.CForDecl d@(C.CDecl spec items ni') -> split
          ("remove the for loop", C.posOf ni)
          (reduceCStatement (C.CCompound [] [C.CBlockDecl d, C.CBlockStmt s] C.undefNode) labs ctx)
          do
    
    chrg's avatar
    chrg committed
            (bt, spec') <- evalStateT (updateCDeclarationSpecifiers keepAll spec) ctx
    
    chrg's avatar
    chrg committed
            (items', ctx') <- flip runStateT ctx do
              markDeleted items
              collect (reduceCDeclarationItem bt) items
    
    chrg's avatar
    chrg committed
            e2' <- runMaybeT do
              e2' <- liftMaybe e2
    
              re2' <- liftMaybe (reduceCExpr e2' etAny ctx')
    
    chrg's avatar
    chrg committed
              exceptIf ("remove check", C.posOf e2')
              re2'
            e3' <- runMaybeT do
              e3' <- liftMaybe e3
    
              re3' <- liftMaybe (reduceCExpr e3' etAny ctx')
    
    chrg's avatar
    chrg committed
              exceptIf ("remove iterator", C.posOf e3')
              re3'
            let e2'' =
                  if AllowInfiniteForLoops `isIn` ctx || isNothing e2
                    then e2'
                    else e2' <|> Just zeroExpr
            s' <- reduceCStatementOrEmptyExpr s labs{stmtInLoop = True} ctx'
            pure $ C.CFor (C.CForDecl (C.CDecl spec' items' ni')) e2'' e3' s' ni
    
    chrg's avatar
    chrg committed
        C.CForInitializing e -> split
          ("remove the for loop", C.posOf ni)
          ( reduceCStatement
              ( C.CCompound
                  []
                  [C.CBlockStmt (C.CExpr e C.undefNode), C.CBlockStmt s]
                  C.undefNode
              )
              labs
              ctx
          )
          do
            e' <-
              maybeSplit ("remove initializer", C.posOf ni) $
                e >>= \e' ->
                  reduceCExpr e' etAny ctx
            e2' <- runMaybeT do
              e2' <- liftMaybe e2
              re2' <- liftMaybe (reduceCExpr e2' etNum ctx)
              exceptIf ("remove check", C.posOf e2')
              re2'
            e3' <- runMaybeT do
              e3' <- liftMaybe e3
              re3' <- liftMaybe (reduceCExpr e3' etAny ctx)
              exceptIf ("remove iterator", C.posOf e3')
              re3'
            let e2'' =
                  if AllowInfiniteForLoops `isIn` ctx || isNothing e2
                    then e2'
                    else e2' <|> Just zeroExpr
            s' <- reduceCStatementOrEmptyExpr s labs{stmtInLoop = True} ctx
            pure $ C.CFor (C.CForInitializing e') e2'' e3' s' ni
        d -> notSupportedYet d ni
    
    chrg's avatar
    chrg committed
      C.CLabel i s [] ni -> do
    
    chrg's avatar
    chrg committed
        if i `List.elem` stmtLabels labs
    
    chrg's avatar
    chrg committed
          then do
    
    chrg's avatar
    chrg committed
            s' <- lift $ reduceCStatementOrEmptyExpr s labs ctx
    
    chrg's avatar
    chrg committed
            pure $ C.CLabel i s' [] ni
          else do
            empty
      C.CGoto i ni ->
    
    chrg's avatar
    chrg committed
        if i `List.elem` stmtLabels labs
          then do
            exceptIf ("remove goto", C.posOf smt)
            pure $ C.CGoto i ni
          else empty
      C.CBreak n ->
        if stmtInLoop labs
          then do
            exceptIf ("remove break", C.posOf smt)
            pure $ C.CBreak n
          else empty
      C.CCont n ->
        if stmtInLoop labs
          then do
            exceptIf ("remove continue", C.posOf smt)
            pure $ C.CCont n
    
    chrg's avatar
    chrg committed
          else empty
    
      a -> notSupportedYet' a
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
    -- | If the condition is statisfied try to reduce to the a.
    whenSplit :: (MonadReduce Lab m) => Bool -> Lab -> m a -> m a -> m a
    whenSplit cn lab a b
      | cn = split lab a b
      | otherwise = b
    
    maybeSplit :: (MonadReduce Lab m) => Lab -> Maybe (m a) -> m (Maybe a)
    maybeSplit lab = \case
      Just r -> do
        split lab (pure Nothing) (Just <$> r)
      Nothing -> do
        pure Nothing
    
    
    chrg's avatar
    chrg committed
    zeroExpr :: C.CExpression C.NodeInfo
    zeroExpr = C.CConst (C.CIntConst (C.cInteger 0) C.undefNode)
    
    
    -- | The expected type
    data EType = EType
      { etSet :: !ETSet
      , etAssignable :: !Bool
      }
      deriving (Show, Eq)
    
    data ETSet
      = ETExactly !Type
      | ETStructWithField !C.Ident !ETSet
      | ETPointer !ETSet
    
      | ETIndexable !ETSet
    
      | ETAny
      deriving (Show, Eq)
    
    
    chrg's avatar
    chrg committed
    checkExpectedType :: (MonadPlus m) => Context -> Voidable -> EType -> m ()
    checkExpectedType ctx (NonVoid t) et = guard $ isExpectedType ctx t et
    checkExpectedType _ Void _ = pure ()
    
    chrg's avatar
    chrg committed
    
    match :: Type -> Type -> Bool
    match = curry \case
      (TPointer Void, TPointer _) -> True
      (TPointer _, TPointer Void) -> True
      (TPointer (NonVoid a), TPointer (NonVoid b)) -> a `match` b
      (t1, t2) -> t1 == t2
    
    chrg's avatar
    chrg committed
    isExpectedType :: Context -> Type -> EType -> Bool
    isExpectedType ctx = \c et ->
    
    chrg's avatar
    chrg committed
      -- pTraceWith (\a -> "check " <> show a <> " " <> show c <> " " <> show et) $
    
      go c (etSet et)