Skip to content
Snippets Groups Projects
ReduceC.hs 50.9 KiB
Newer Older
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
chrg's avatar
chrg committed
  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
        )
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'
chrg's avatar
chrg committed
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
chrg's avatar
chrg committed
  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

chrg's avatar
chrg committed
  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')
chrg's avatar
chrg committed
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
chrg's avatar
chrg committed
    (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
chrg's avatar
chrg committed
      modify' (addTypeDef ix (ITInline (wrap t) rst'))
chrg's avatar
chrg committed
      exceptIf ("inline typedef " <> C.identToString ix, C.posOf ni)

chrg's avatar
chrg committed
    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.
-}
chrg's avatar
chrg committed
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

chrg's avatar
chrg committed
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

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
chrg's avatar
chrg committed
    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)

chrg's avatar
chrg committed
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 ()

chrg's avatar
chrg committed
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
      )

chrg's avatar
chrg committed
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.
chrg's avatar
chrg committed
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
chrg's avatar
chrg committed
  | 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)
 where
  go c = \case
chrg's avatar
chrg committed
    ETExactly t -> t `match` c
    ETAny -> True
    ETStructWithField ix et -> case c of
      TStruct s -> fromMaybe False do
chrg's avatar
chrg committed
        let fields = fieldsOfStruct ctx s
        (_, mt) <- liftMaybe $ List.find (\(a, _) -> ix == a) fields
        t' <- liftMaybe mt
        pure $ go t' et
      _ow -> False
    ETPointer t' ->
      case c of
        TPointer Void -> True
        TPointer (NonVoid c') -> go c' t'
        _ow -> False
chrg's avatar
chrg committed
    ETIndexable t' ->
      case c of
        TPointer Void -> True
        TPointer (NonVoid c') -> go c' t'
        TVector _ (NonVoid c') -> go c' t'
        TVector _ Void -> True
        _ow -> False
chrg's avatar
chrg committed

fieldsOfStruct :: (HasCallStack) => Context -> Either C.Ident StructType -> [(C.Ident, Maybe Type)]
chrg's avatar
chrg committed
fieldsOfStruct ctx (Left ix) =
  case lookupStruct ctx ix of
    ISKeep a -> structTypeFields a
chrg's avatar
chrg committed
    _ow -> error "Something bad happend"
chrg's avatar
chrg committed
fieldsOfStruct _ (Right a) = structTypeFields a

etUnPointer :: EType -> Maybe EType
etUnPointer t =
chrg's avatar
chrg committed
  -- pTraceWith (\t' -> "unpoint " <> show t <> " " <> show t') $
  case etSet t of
    ETPointer t' -> Just t{etSet = t'}
    ETExactly (TPointer Void) -> Just t{etSet = ETAny}
    ETExactly (TPointer (NonVoid t')) -> Just t{etSet = ETExactly t'}
    _ow -> Nothing

checkNotAssignable :: (MonadPlus m) => EType -> m ()
checkNotAssignable = guard . not . etAssignable

msplit :: (MonadReduce Lab m) => Lab -> Maybe (m a) -> Maybe (m a) -> Maybe (m a)
msplit l m1 m2 = do
  case m1 of
    Just a -> Just $ case m2 of
      Just b -> split l a b
      Nothing -> a
    Nothing -> m2
chrg's avatar
chrg committed
{-# INLINE msplit #-}
chrg's avatar
chrg committed
inferType :: Context -> C.CExpr -> Maybe Voidable
inferType ctx = \case
  C.CVar i _ -> do
chrg's avatar
chrg committed
    case lookupVariable ctx i of
      IEInline e -> inferType ctx e
      IEKeep t -> pure (NonVoid t)
      IEDelete -> Nothing
  C.CUnary i e _ -> do
    t <- inferType ctx e
    case i of
      C.CIndOp -> case t of
        NonVoid (TPointer t') -> pure t'
        Void -> pure Void
        _ow -> Nothing
      C.CAdrOp -> pure (NonVoid (TPointer t))
      _ow -> pure t
  C.CConst x -> pure . NonVoid $ case x of
    (C.CStrConst _ _) ->
      TPointer (NonVoid TNum)
    _ow ->
      TNum
  C.CIndex a x _ -> do
    t1 <- inferType ctx a
    t2 <- inferType ctx x
    case (t1, t2) of
      (NonVoid (TPointer x'), NonVoid TNum) -> pure x'
chrg's avatar
chrg committed
      (NonVoid (TVector _ x'), NonVoid TNum) -> pure x'
      _ow -> error (show ("index", a, t1, t2))
chrg's avatar
chrg committed
  C.CMember a l t _ -> do
    t1 <- inferType ctx a
    s' <- case (t1, t) of
      (NonVoid (TPointer (NonVoid (TStruct s))), True) -> pure s
      (NonVoid (TStruct s), False) -> pure s
      _ow -> error (show ("member", a, l))
chrg's avatar
chrg committed
    let fields = fieldsOfStruct ctx s'
    NonVoid <$> (join . List.lookup l $ fields)
chrg's avatar
chrg committed
  C.CBinary o lhs _ _ -> do
    if o `elem` [C.CNeqOp, C.CEqOp, C.CGeqOp, C.CLeqOp, C.CGrOp, C.CLeOp]
      then pure (NonVoid TNum)
      else inferType ctx lhs
  C.CCast decl@(C.CDecl spec items _) _ _ -> do
    -- todo is this a good handling of this?
    (bt, _) <- evalStateT (updateCDeclarationSpecifiers keepAll spec) ctx
    case items of
      [C.CDeclarationItem (C.CDeclr Nothing dd Nothing [] _) _ _] -> do
        (t, _) <- evalStateT (updateCDerivedDeclarators bt (repeat True) dd) ctx
        pure t
      [] ->
        pure bt
      _ow -> notSupportedYet' decl
  C.CCall f _ ni -> do
    ft <- inferType ctx f
    case ft of
      NonVoid (TFun (FunType rt _)) -> pure rt
      a -> do
chrg's avatar
chrg committed
        error (show ("call", a, ni))
chrg's avatar
chrg committed
  C.CAssign _ lhs _ _ -> do
    inferType ctx lhs
  -- inferType ctx rhs
  -- if t1 == t2 then pure t1 else error (show ("assign", o, t1, t2))
  C.CComma items _ -> do
    inferType ctx (List.last items)
  a -> notSupportedYet' a

chrg's avatar
chrg committed
reduceCExpr ::
  forall m.
  (MonadReduce Lab m, HasCallStack) =>
  C.CExpr ->
  EType ->
  Context ->
  Maybe (m C.CExpr)
reduceCExpr expr t ctx = case expr of
chrg's avatar
chrg committed
  C.CBinary o elhs erhs ni -> do
    msplit ("reduce to left", C.posOf elhs) (reduceCExpr elhs t ctx) do
      msplit ("reduce to right", C.posOf erhs) (reduceCExpr erhs t ctx) do
        checkNotAssignable t
chrg's avatar
chrg committed
        when (o `elem` [C.CNeqOp, C.CEqOp, C.CGeqOp, C.CLeqOp, C.CGrOp, C.CLeOp]) do
chrg's avatar
chrg committed
          checkExpectedType ctx (NonVoid TNum) t
chrg's avatar
chrg committed
        c <- inferType ctx elhs
        let t' = fromVoid etAny exactly c
        rl <- reduceCExpr elhs t' ctx
        rr <- reduceCExpr erhs t' ctx
        Just do
chrg's avatar
chrg committed
          l' <- rl
          r' <- rr
chrg's avatar
chrg committed
          let r'' = case o of
                C.CDivOp -> case r' of
                  C.CConst (C.CIntConst i _)
                    | i == C.cInteger 0 ->
                        C.CConst (C.CIntConst (C.cInteger 1) C.undefNode)
                  C.CUnary o' (C.CConst (C.CIntConst i _)) _
                    | i == C.cInteger 0 ->
                        C.CUnary o' (C.CConst (C.CIntConst (C.cInteger 1) C.undefNode)) C.undefNode
                  _ow -> r'
                _ow -> r'
          pure $ C.CBinary o l' r'' ni
chrg's avatar
chrg committed
  C.CAssign o elhs erhs ni ->
    msplit ("reduce to left", C.posOf elhs) (reduceCExpr elhs t ctx) do
      msplit ("reduce to right", C.posOf erhs) (reduceCExpr erhs t ctx) do
chrg's avatar
chrg committed
        c <- inferType ctx elhs
chrg's avatar
chrg committed
        checkExpectedType ctx c t
chrg's avatar
chrg committed
        let t' = fromVoid etAny exactly c
        -- in this case we change type, so we need to keep the operation
chrg's avatar
chrg committed
        rl <- reduceCExpr elhs t'{etAssignable = True} ctx
        rr <- reduceCExpr erhs t' ctx
        Just do
          l' <- rl
          r' <- rr
          pure $ C.CAssign o l' r' ni
chrg's avatar
chrg committed
  C.CVar i _ ->
    case lookupVariable ctx i of
      IEKeep c -> do
chrg's avatar
chrg committed
        checkExpectedType ctx (NonVoid c) t
        Just (pure expr)
      IEInline mx' -> do
chrg's avatar
chrg committed
        guard (not $ DisallowVariableInlining `isIn` ctx)
        reduceCExpr mx' t ctx
      IEDelete ->
        Nothing
  C.CConst x -> do
    case x of
      C.CStrConst _ _ -> do
        checkNotAssignable t
chrg's avatar
chrg committed
        checkExpectedType ctx (NonVoid (TPointer (NonVoid TNum))) t
chrg's avatar
chrg committed
        -- guard ( `match` etSet t)
        Just (pure expr)
      C.CIntConst (C.getCInteger -> 0) _ -> do
        checkNotAssignable t
chrg's avatar
chrg committed
        checkExpectedType ctx (NonVoid (TPointer Void)) t
          <|> checkExpectedType ctx (NonVoid TNum) t
        Just (pure expr)
      _ow -> do
        checkNotAssignable t
chrg's avatar
chrg committed
        checkExpectedType ctx (NonVoid TNum) t
        Just (pure expr)
  C.CUnary o eopr ni -> do
    msplit ("reduce to operant", C.posOf eopr) (reduceCExpr eopr t ctx) do
      case o of
        C.CIndOp -> do
chrg's avatar
chrg committed
          ropr <-
            if etSet t == ETAny
              then do
                reduceCExpr eopr t ctx
              else reduceCExpr eopr (t{etSet = ETPointer (etSet t), etAssignable = True}) ctx
          Just do
            eopr' <- ropr
            pure $ C.CUnary o eopr' ni
        C.CAdrOp -> do
          t' <- etUnPointer t
          ropr <- reduceCExpr eopr (t'{etAssignable = True}) ctx
          Just do
            eopr' <- ropr
            pure $ C.CUnary o eopr' ni
        e
          | e `List.elem` [C.CPreIncOp, C.CPreDecOp, C.CPostIncOp, C.CPostDecOp] -> do
              reduceCExpr eopr t{etAssignable = True} ctx <&> \ropr -> do
                eopr' <- ropr
                pure $ C.CUnary o eopr' ni
          | otherwise -> do
              reduceCExpr eopr t ctx <&> \ropr -> do
                eopr' <- ropr
                pure $ C.CUnary o eopr' ni
  C.CCall ef args ni -> do
    (\fn a -> foldr fn a args)
      (\e -> msplit ("reduce to expression", C.posOf e) (reduceCExpr e t ctx))
      do
chrg's avatar
chrg committed
        ct <- inferType ctx ef
        case ct of
chrg's avatar
chrg committed
          NonVoid ft@(TFun (FunType rt fargs)) -> do
            checkNotAssignable t
chrg's avatar
chrg committed
            checkExpectedType ctx rt t
            ref <- reduceCExpr ef (exactly ft) ctx
            let targs = case fargs of
                  Params targs' v ->
                    let cons = if v then repeat (Just ETAny) else []
                     in map (fmap ETExactly) targs' <> cons
                  VoidParams -> repeat (Just ETAny)
            let pargs = mapMaybe (\(ta, a) -> (,a) <$> ta) (zip targs args)
            rargs <- forM pargs \(ta, a) ->
              reduceCExpr a (EType ta False) ctx
            Just do
              ef' <- ref
              args' <- sequence rargs
              pure $ C.CCall ef' args' ni
          ow -> do
            error $
              "Original c code does not type-check: exepected function, got "
                <> show ow
                <> " at "
                <> show (C.posOf ef)
  C.CCond et (Just ec) ef ni -> do
    msplit ("reduce to true branch", C.posOf et) (reduceCExpr et t ctx) do
      msplit ("reduce to false branch", C.posOf ef) (reduceCExpr ef t ctx) do
        msplit ("reduce to condtion", C.posOf ef) (reduceCExpr ec t ctx) do
          checkNotAssignable t
          ret <- reduceCExpr et t ctx
          ref <- reduceCExpr ef t ctx
          rec <- reduceCExpr ec etAny ctx
chrg's avatar
chrg committed
          Just $ do
            et' <- ret
            ef' <- ref
            ec' <- rec
            pure $ C.CCond et' (Just ec') ef' ni
chrg's avatar
chrg committed
  C.CCast (C.CDecl spec items ni2) e ni -> do
    msplit ("do not cast", C.posOf ni) (reduceCExpr e t ctx) do
chrg's avatar
chrg committed
      (bt, spec') <- evalStateT (updateCDeclarationSpecifiers keepAll spec) ctx
      (items', re) <- case items of
        [C.CDeclarationItem (C.CDeclr Nothing dd Nothing [] a) b c] -> do
          (_, dd') <- evalStateT (updateCDerivedDeclarators bt (repeat True) dd) ctx
          ([C.CDeclarationItem (C.CDeclr Nothing dd' Nothing [] a) b c],) <$> do
            reduceCExpr e etAny ctx
chrg's avatar
chrg committed
        [] ->
          ([],) <$> case bt of
            Void ->
              reduceCExpr e etAny ctx
            NonVoid _ -> do
              -- checkExpectedType ct' t
              reduceCExpr e etAny ctx
        a -> notSupportedYet a ni
      Just do
chrg's avatar
chrg committed
        e' <- re
chrg's avatar
chrg committed
        pure (C.CCast (C.CDecl spec' items' ni2) e' ni)
chrg's avatar
chrg committed
  C.CIndex e1 e2 ni -> do
    msplit ("reduce to indexee", C.posOf e1) (reduceCExpr e1 t ctx) do
      msplit ("reduce to index", C.posOf e2) (reduceCExpr e2 t ctx) do
chrg's avatar
chrg committed
        re1 <- reduceCExpr e1 t{etSet = ETIndexable (etSet t), etAssignable = True} ctx
        Just do
          e1' <- re1
chrg's avatar
chrg committed
          e2' <- fromMaybe (pure zeroExpr) $ reduceCExpr e2 etNum ctx
          pure $ C.CIndex e1' e2' ni
chrg's avatar
chrg committed
  C.CComma items ni -> do
    (x, rst) <- List.uncons (reverse items)
    (\fn a -> foldr fn a (reverse items))
      (\e -> msplit ("reduce to expression", C.posOf e) (reduceCExpr e t ctx))
      do
        rx <- reduceCExpr x t ctx
        Just do
          rst' <- flip collect rst \e -> do
            re <- liftMaybe (reduceCExpr e (EType ETAny False) ctx)
            e' <- re
            exceptIf ("remove expression", C.posOf e)
            pure (e' :: C.CExpr)
          x' <- rx
          pure $ C.CComma (reverse (x' : rst')) ni
  C.CMember e i l ni -> do
    re <- reduceCExpr e t{etSet = ETStructWithField i (etSet t)} ctx
    Just do
      e' <- re
      pure (C.CMember e' i l ni)
  a -> notSupportedYet' a
chrg's avatar
chrg committed

chrg's avatar
chrg committed
lookupFunction :: (HasCallStack) => Context -> C.Ident -> Maybe Function
lookupFunction ctx k =
  fromMaybe (error ("could not find function " <> C.identToString k)) $
    functions ctx Map.!? k

lookupVariable :: (HasCallStack) => Context -> C.Ident -> InlineExpr
lookupVariable ctx k =
  fromMaybe (error ("could not find variable " <> C.identToString k)) $
    inlineExprs ctx Map.!? k

chrg's avatar
chrg committed
lookupStruct :: (HasCallStack) => Context -> C.Ident -> InlineStruct
chrg's avatar
chrg committed
lookupStruct ctx k =
chrg's avatar
chrg committed
  fromMaybe (error ("could not find struct " <> C.identToString k)) $
chrg's avatar
chrg committed
    structs ctx Map.!? k
chrg's avatar
chrg committed
lookupEnum :: (HasCallStack) => Context -> C.Ident -> InlineEnum
lookupEnum ctx k =
  fromMaybe (error ("could not find enum " <> C.identToString k)) $
    enums ctx Map.!? k
chrg's avatar
chrg committed

labelsOf :: C.CStatement C.NodeInfo -> [C.Ident]
labelsOf = \case
  C.CLabel i s [] _ -> i : labelsOf s
  C.CWhile _ s _ _ -> labelsOf s
  C.CCase _ s _ -> labelsOf s
  C.CDefault s _ -> labelsOf s
  C.CCompound _ ss _ ->
    ss & concatMap \case
      C.CBlockStmt s -> labelsOf s
      _ow -> []
  C.CCases _ _ s _ -> labelsOf s
  C.CIf _ l r _ -> labelsOf l <> maybe [] labelsOf r
  C.CSwitch _ s _ -> labelsOf s
  C.CFor _ _ _ s _ -> labelsOf s
  _ow -> []

chrg's avatar
chrg committed
data Context = Context
  { keywords :: !(Set.Set Keyword)
chrg's avatar
chrg committed
  , typeDefs :: !(Map.Map C.Ident InlineType)
chrg's avatar
chrg committed
  , inlineExprs :: !(Map.Map C.Ident InlineExpr)
chrg's avatar
chrg committed
  , structs :: !(Map.Map C.Ident InlineStruct)
  , enums :: !(Map.Map C.Ident InlineEnum)
chrg's avatar
chrg committed
  , functions :: !(Map.Map C.Ident (Maybe Function))
  , returnType :: !Voidable
chrg's avatar
chrg committed
  }
  deriving (Show)

data InlineType
chrg's avatar
chrg committed
  = ITKeep !Voidable
  | ITInline !Voidable ![C.CDeclarationSpecifier C.NodeInfo]
chrg's avatar
chrg committed
  | ITDelete
chrg's avatar
chrg committed
  deriving (Show, Eq)

chrg's avatar
chrg committed
data InlineStruct
  = ISKeep !StructType
  | ISDeclared !C.CStructTag
  | ISDelete
  deriving (Show, Eq)

data InlineEnum
  = INKeep
  | INDelete
  deriving (Show, Eq)

chrg's avatar
chrg committed
data InlineExpr
chrg's avatar
chrg committed
  = IEKeep !Type
chrg's avatar
chrg committed
  | IEInline !C.CExpr
chrg's avatar
chrg committed
  | IEDelete
chrg's avatar
chrg committed
  deriving (Show, Eq)

data Keyword
  = LoseMain
  | DoNoops
  | ComputeFunctionFixpoint
chrg's avatar
chrg committed
  | InlineTypeDefs
  | NoSemantics
  | AllowEmptyDeclarations
  | DisallowVariableInlining
  | AllowInfiniteForLoops
  deriving (Show, Read, Enum, Eq, Ord)

type Lab = (String, C.Position)

chrg's avatar
chrg committed
addTypeDef :: C.Ident -> InlineType -> Context -> Context
addTypeDef i cs ctx = ctx{typeDefs = Map.insert i cs $ typeDefs ctx}
chrg's avatar
chrg committed

addInlineExpr :: C.Ident -> InlineExpr -> Context -> Context
addInlineExpr i e Context{..} =
  Context{inlineExprs = Map.insert i e inlineExprs, ..}
chrg's avatar
chrg committed

chrg's avatar
chrg committed
addStruct :: C.Identifier C.NodeInfo -> InlineStruct -> Context -> Context
chrg's avatar
chrg committed
addStruct i cs ctx = ctx{structs = Map.insert i cs $ structs ctx}

chrg's avatar
chrg committed
addEnum :: C.Identifier C.NodeInfo -> InlineEnum -> Context -> Context
addEnum i cs ctx = ctx{enums = Map.insert i cs $ enums ctx}

chrg's avatar
chrg committed
defaultContext :: Context
defaultContext =
  Context
    { keywords = Set.fromList []
chrg's avatar
chrg committed
    , typeDefs = Map.fromList [(C.builtinIdent "__builtin_va_list", ITKeep (NonVoid (TPointer Void)))]
chrg's avatar
chrg committed
    , inlineExprs =
        Map.fromList
          [ (C.builtinIdent "__PRETTY_FUNCTION__", IEKeep (TPointer (NonVoid TNum)))
          , (C.builtinIdent "__FUNCTION__", IEKeep (TPointer (NonVoid TNum)))
chrg's avatar
chrg committed
          ]
    , structs = Map.empty
chrg's avatar
chrg committed
    , enums = Map.empty
chrg's avatar
chrg committed
    , functions = Map.empty
    , returnType = Void
chrg's avatar
chrg committed
    }

isIn :: Keyword -> Context -> Bool
isIn k = Set.member k . keywords

prettyIdent :: C.Identifier C.NodeInfo -> [Char]
prettyIdent (C.Ident s _ a) = s ++ " at " ++ show (C.posOfNode a)

data Struct = Struct
  { structName :: !C.Ident
chrg's avatar
chrg committed
  , structFields :: ![Maybe C.Ident]
chrg's avatar
chrg committed
  , structPosition :: !C.Position
  }
  deriving (Show, Eq)

data Function = Function
  { funName :: !C.Ident
chrg's avatar
chrg committed
  , funParams :: !(Maybe [Bool])
chrg's avatar
chrg committed
  , funIsStatic :: !Bool
  , funSize :: !Int
  , funPosition :: !C.Position
  }
  deriving (Show, Eq)

chrg's avatar
chrg committed
findFunctions ::
  (Monoid m) =>
  (Function -> m) ->
  C.CExternalDeclaration C.NodeInfo ->
  m
chrg's avatar
chrg committed
findFunctions inject = \case
chrg's avatar
chrg committed
  C.CFDefExt (C.CFunDef spec declr [] _ ni) ->
    findFunctionsInDeclarator ni spec declr
  -- # for now let's not anlyse function declarations.
  C.CFDefExt def@(C.CFunDef{}) ->
    notSupportedYet (void def) def
  C.CDeclExt (C.CDecl spec items ni) -> flip foldMap items \case
    C.CDeclarationItem declr Nothing Nothing ->
      findFunctionsInDeclarator ni spec declr
    _ow -> mempty
  C.CDeclExt a@(C.CStaticAssert{}) ->
    notSupportedYet (void a) a
  C.CAsmExt _ _ -> mempty
 where
  findFunctionsInDeclarator ni spec = \case
chrg's avatar
chrg committed
    (C.CDeclr mid (C.CFunDeclr param _ _ : _) Nothing [] _) ->
chrg's avatar
chrg committed
      case mid of
        Just funName -> inject Function{..}
         where
          funIsStatic = isStaticFromSpecs spec
          funSize = fromMaybe 0 (C.lengthOfNode ni)
          funPosition = C.posOf ni
          funParams = case param of
            C.CFunParamsNew declr var ->
              case declr of
chrg's avatar
chrg committed
                [C.CDecl [C.CTypeSpec (C.CVoidType _)] [] _] -> Nothing
chrg's avatar
chrg committed
                  | var -> Nothing
                  | otherwise -> Just [True | _ <- declr]
chrg's avatar
chrg committed
            a -> notSupportedYet (void a) ni
        Nothing -> mempty
chrg's avatar
chrg committed
    _ow -> mempty

class Named f where
  name :: f a -> Maybe (C.Identifier a)

instance Named C.CDeclarator where
  name (C.CDeclr idx _ _ _ _) = idx

instance Named C.CDeclarationItem where
  name = \case
    C.CDeclarationItem decl _ _ -> name decl
    C.CDeclarationExpr _ -> Nothing

chrg's avatar
chrg committed
data Params
  = VoidParams
  | Params ![Maybe Type] !Bool
  deriving (Show, Eq)

data FunType = FunType
  { funTypeReturn :: !Voidable
  , funTypeParams :: !Params
  }
  deriving (Show, Eq)

data StructType = StructType
  { structTypeTag :: !C.CStructTag
  , structTypeName :: !(Maybe C.Ident)
chrg's avatar
chrg committed
  , structTypeFields :: ![(C.Ident, Maybe Type)]
chrg's avatar
chrg committed
  }
  deriving (Show, Eq)

data Type
  = TNum
  | TStruct !(Either C.Ident StructType)
  | TPointer !Voidable
chrg's avatar
chrg committed
  | TVector !Int !Voidable
chrg's avatar
chrg committed
  | TFun !FunType
  deriving (Show, Eq)

data Voidable
  = Void
  | NonVoid !Type
  deriving (Show, Eq)

fromVoid :: a -> (Type -> a) -> Voidable -> a
fromVoid a fn = \case
  Void -> a
  NonVoid t -> fn t
{-# INLINE fromVoid #-}

nonVoid :: (HasCallStack) => Voidable -> Type
nonVoid = fromVoid (error "expected non void type") id
{-# INLINE nonVoid #-}

notSupportedYet :: (HasCallStack, Show a, C.Pos n) => a -> n -> b
notSupportedYet a ni = error (show a <> " at " <> show (C.posOf ni))

notSupportedYet' :: (HasCallStack, Show (a ()), Functor a, C.Pos (a C.NodeInfo)) => a C.NodeInfo -> b
notSupportedYet' a = notSupportedYet (void a) a