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

ReduceC.hs

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    ReduceC.hs 21.42 KiB
    {-# LANGUAGE BangPatterns #-}
    {-# LANGUAGE BlockArguments #-}
    {-# LANGUAGE ConstraintKinds #-}
    {-# LANGUAGE FlexibleContexts #-}
    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE GADTs #-}
    {-# LANGUAGE LambdaCase #-}
    {-# LANGUAGE RankNTypes #-}
    {-# LANGUAGE RecordWildCards #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE TypeFamilies #-}
    {-# LANGUAGE NoMonomorphismRestriction #-}
    
    module ReduceC (
      defaultReduceC,
      -- reduceCTranslUnit,
    
      -- * Context
      Context (..),
      defaultContext,
    
      -- * Helpers
      prettyIdent,
    ) where
    
    import Control.Monad.Reduce
    import Data.Data
    import Data.Foldable
    import Data.Function
    import Data.Functor
    import qualified Data.List as List
    import qualified Data.Map.Strict as Map
    import Data.Maybe
    import qualified Data.Set as Set
    import Data.Vector.Internal.Check (HasCallStack)
    
    -- import Debug.Trace
    --
    -- Todo stuckt names
    
    import qualified Control.Monad.IRTree as IRTree
    import Data.Monoid
    import qualified Language.C as C
    import qualified Language.C.Data.Ident as C
    
    data Context = Context
      { keywords :: !(Set.Set Keyword)
      , typeDefs :: !(Map.Map C.Ident InlineType)
      , inlineExprs :: !(Map.Map C.Ident InlineExpr)
      , fields :: !(Map.Map C.Ident (Maybe C.Ident))
      , structs :: !(Map.Map C.Ident (Maybe C.CStructUnion))
      }
      deriving (Show)
    
    data InlineType
      = ITKeep
      | ITInline ![C.CDeclarationSpecifier C.NodeInfo]
      deriving (Show, Eq)
    
    data InlineExpr
      = IEDelete
      | IEInline !C.CExpr
      | IEKeep
      deriving (Show, Eq)
    
    data Keyword
      = KeepMain
      | DoNoops
      | NoSemantics
      | AllowEmptyDeclarations
      | DisallowVariableInlining
      deriving (Show, Read, Enum, Eq, Ord)
    
    type Lab = (String, C.Position)
    
    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 #-}
    
    addTypeDefs :: [C.Ident] -> InlineType -> Context -> Context
    addTypeDefs ids cs Context{..} =
      Context
        { typeDefs =
            foldl' (\a i -> Map.insert i cs a) typeDefs ids
        , ..
        }
    
    addInlineExpr :: C.Ident -> InlineExpr -> Context -> Context
    addInlineExpr i e Context{..} =
      Context
        { inlineExprs = Map.insert i e inlineExprs
        , ..
        }
    
    addKeyword :: Keyword -> Context -> Context
    addKeyword k Context{..} =
      Context
        { keywords = Set.insert k keywords
        , ..
        }
    
    addStruct :: StructDef -> Context -> Context
    addStruct (StructDef k fs _) Context{..} =
      Context
        { structs = Map.insert k Nothing structs
        , fields = foldr (`Map.insert` Just k) fields fs
        , ..
        }
    
    removeStruct :: StructDef -> Context -> Context
    removeStruct (StructDef k fs un) Context{..} =
      Context
        { structs = Map.insert k (Just un) structs
        , fields = foldr (`Map.insert` Nothing) fields fs
        , ..
        }
    
    -- deleteKeyword :: Keyword -> Context -> Context
    -- deleteKeyword k Context{..} =
    --   Context
    --     { keywords = Set.delete k keywords
    --     , ..
    --     }
    
    defaultContext :: Context
    defaultContext =
      Context
        { keywords = Set.fromList [KeepMain]
        , typeDefs = Map.empty
        , inlineExprs = Map.fromList [(C.builtinIdent "fabsf", IEKeep), (C.builtinIdent "fabs", IEKeep)]
        , fields = Map.empty
        , structs = Map.empty
        }
    
    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)
    
    reduceCTranslUnit
      :: (MonadReduce Lab m)
      => C.CTranslationUnit C.NodeInfo
      -> Context
      -> m (C.CTranslationUnit C.NodeInfo)
    reduceCTranslUnit (C.CTranslUnit es ni) ctx = do
      es' <- foldr reduceCExternalDeclaration (\_ -> pure []) es ctx
      pure $ C.CTranslUnit es' ni
    
    reduceCExternalDeclaration
      :: (MonadReduce Lab m)
      => C.CExternalDeclaration C.NodeInfo
      -> (Context -> m [C.CExternalDeclaration C.NodeInfo])
      -> Context
      -> m [C.CExternalDeclaration C.NodeInfo]
    reduceCExternalDeclaration r cont ctx = do
      -- TODO This is slow
      case r of
        C.CFDefExt fun
          | KeepMain `isIn` ctx && maybe False (("main" ==) . C.identToString) (functionName fun) -> do
              r' <- C.CFDefExt <$> reduceCFunDef fun ctx
              (r' :) <$> cont ctx
          | otherwise ->
              case functionName fun of
                Just fid -> do
                  split
                    ("remove function " <> C.identToString fid, C.posOf r)
                    (cont (addInlineExpr fid IEDelete ctx))
                    do
                      r' <- C.CFDefExt <$> reduceCFunDef fun ctx
                      (r' :) <$> cont (addInlineExpr fid IEKeep ctx)
                Nothing -> do
                  split
                    ("remove function", C.posOf r)
                    (cont ctx)
                    do
                      r' <- C.CFDefExt <$> reduceCFunDef fun ctx
                      (r' :) <$> cont ctx
        C.CDeclExt decl -> do
          (decl', ctx') <- handleDecl decl ctx
          cont ctx' <&> \a -> case decl' of
            Just d -> C.CDeclExt d : a
            Nothing -> a
        _r -> don'tHandle r
    
    data StructDef = StructDef
      { structId :: !C.Ident
      , fieldIds :: ![C.Ident]
      , structDef :: !C.CStructUnion
      }
      deriving (Show, Eq)
    
    structIds
      :: (Foldable f)
      => f (C.CDeclarationSpecifier C.NodeInfo)
      -> [StructDef]
    structIds = concatMap \case
      C.CTypeSpec (C.CSUType (C.CStruct a (Just n) (Just ma) b c) _) ->
        [ StructDef
            n
            [ x
            | C.CDecl _ itms _ <- ma
            , C.CDeclarationItem (C.CDeclr (Just x) _ _ _ _) _ _ <- itms
            ]
            (C.CStruct a (Just n) (Just ma) b c)
        ]
      _ow -> []
    
    reduceCFunDef
      :: (MonadReduce Lab m, HasCallStack)
      => C.CFunctionDef C.NodeInfo
      -> Context
      -> m (C.CFunctionDef C.NodeInfo)
    reduceCFunDef (C.CFunDef spc dec cdecls smt ni) ctx = do
      let spc'' =
            filter
              ( \case
                  C.CStorageSpec (C.CStatic _) -> False
                  _ow -> True
              )
              spc
      spc' <-
        if spc'' /= spc
          then split ("remove static", C.posOf ni) (pure spc'') (pure spc)
          else pure spc
      smt' <- reduceCStatementOrEmptyBlock smt ctx'
      pure $
        C.CFunDef
          (inlineTypeDefsSpecs spc' ctx)
          (inlineTypeDefsCDeclarator dec ctx)
          (map (`inlineTypeDefsCDeclaration` ctx) cdecls)
          smt'
          ni
     where
      !ctx' = foldr (`addInlineExpr` IEKeep) ctx ids
      ids = params dec
    
    params :: C.CDeclarator C.NodeInfo -> [C.Ident]
    params = \case
      C.CDeclr _ [C.CFunDeclr (C.CFunParamsNew decls _) _ _] _ _ _ ->
        decls & concatMap \case
          C.CDecl _ items _ ->
            items & concatMap \case
              C.CDeclarationItem (C.CDeclr (Just idx) _ _ _ _) _ _ -> [idx]
              _ow -> []
          a -> don'tHandle a
      a -> don'tHandle a
    
    reduceCCompoundBlockItem
      :: (MonadReduce Lab m, HasCallStack)
      => C.CCompoundBlockItem C.NodeInfo
      -> (Context -> m [C.CCompoundBlockItem C.NodeInfo])
      -> Context
      -> m [C.CCompoundBlockItem C.NodeInfo]
    reduceCCompoundBlockItem r cont ctx = do
      case r of
        C.CBlockStmt smt -> do
          case reduceCStatement smt ctx of
            Just rsmt -> split ("remove statement", C.posOf r) (cont ctx) do
              smt' <- rsmt
              case smt' of
                C.CCompound [] ss _ -> do
                  split ("expand compound statment", C.posOf r) ((ss <>) <$> cont ctx) do
                    (C.CBlockStmt smt' :) <$> cont ctx
                _ow -> do
                  (C.CBlockStmt smt' :) <$> cont ctx
            Nothing -> cont ctx
        C.CBlockDecl declr -> do
          (declr', ctx') <- handleDecl declr ctx
          case declr' of
            Just d -> (C.CBlockDecl d :) <$> cont ctx'
            Nothing -> cont ctx'
        a -> don'tHandle a
    
    handleDecl
      :: (MonadReduce Lab m)
      => C.CDeclaration C.NodeInfo
      -> Context
      -> m (Maybe (C.CDeclaration C.NodeInfo), Context)
    handleDecl d ctx = case inlineTypeDefsCDeclaration d ctx of
      -- A typedef
      C.CDecl (C.CStorageSpec (C.CTypedef _) : rst) decl _ -> do
        let [ids] = identifiers decl
        split
          ("inline typedef " <> C.identToString ids, C.posOf d)
          (pure (Nothing, addTypeDefs [ids] (ITInline rst) ctx))
          (pure (Just d, addTypeDefs [ids] ITKeep ctx))
      -- A const
      C.CDecl rec decl ni' -> do
        (decl', ctx') <- foldr reduceCDeclarationItem (pure ([], ctx)) decl
        case (decl', structIds rec) of
          ([], [])
            | AllowEmptyDeclarations `isIn` ctx' ->
                split ("remove empty declaration", C.posOf d) (pure (Nothing, ctx')) do
                  pure (Just (C.CDecl rec decl' ni'), ctx')
            | otherwise -> pure (Nothing, ctx')
          ([], stcts) ->
            split
              ("remove declaration", C.posOf d)
              (pure (Nothing, foldr removeStruct ctx' stcts))
              do
                pure
                  ( Just (C.CDecl rec decl' ni')
                  , foldr addStruct ctx' stcts
                  )
          (_, stcts) ->
            pure
              ( Just (C.CDecl rec decl' ni')
              , foldr addStruct ctx' stcts
              )
      a -> don'tHandleWithPos a
    reduceCDeclarationItem
      :: (MonadReduce Lab m)
      => C.CDeclarationItem C.NodeInfo
      -> m ([C.CDeclarationItem C.NodeInfo], Context)
      -> m ([C.CDeclarationItem C.NodeInfo], Context)
    reduceCDeclarationItem d ma = case d of
      C.CDeclarationItem
        dr@(C.CDeclr (Just i) [] Nothing [] ni)
        (Just (C.CInitExpr c ni'))
        Nothing -> do
          (ds, ctx) <- ma
          c' <- fromMaybe (pure zeroExpr) (reduceCExpr c ctx)
          split
            ("inline variable " <> C.identToString i, C.posOf ni)
            (pure (ds, addInlineExpr i (IEInline c') ctx))
            ( pure
                ( inlineTypeDefsCDI (C.CDeclarationItem dr (Just (C.CInitExpr c' ni')) Nothing) ctx : ds
                , addInlineExpr i IEKeep ctx
                )
            )
      C.CDeclarationItem (C.CDeclr (Just i) _ Nothing _ ni) _ Nothing -> do
        (ds, ctx) <- ma
        split
          ("remove variable " <> C.identToString i, C.posOf ni)
          (pure (ds, addInlineExpr i IEDelete ctx))
          (pure (inlineTypeDefsCDI d ctx : ds, addInlineExpr i IEKeep ctx))
      a@(C.CDeclarationItem (C.CDeclr _ _ _ _ ni) _ _) -> do
        don'tHandleWithNodeInfo a ni
      a -> don'tHandle a
    
    reduceCStatementOrEmptyBlock
      :: (MonadReduce Lab m, HasCallStack)
      => C.CStatement C.NodeInfo
      -> Context
      -> m (C.CStatement C.NodeInfo)
    reduceCStatementOrEmptyBlock stmt ctx = do
      case reduceCStatement stmt ctx of
        Just ex -> do
          ex
        Nothing -> do
          pure emptyBlock
    
    emptyBlock :: C.CStatement C.NodeInfo
    emptyBlock = C.CCompound [] [] C.undefNode
    
    reduceCStatement
      :: (MonadReduce Lab m, HasCallStack)
      => C.CStatement C.NodeInfo
      -> Context
      -> Maybe (m (C.CStatement C.NodeInfo))
    reduceCStatement smt ctx = case smt of
      C.CCompound is cbi ni -> Just do
        cbi' <- foldr reduceCCompoundBlockItem (\_ -> pure []) cbi ctx
        pure $ C.CCompound is cbi' ni
      C.CWhile e s dow ni -> do
        rs <- reduceCStatement s ctx
        Just do
          e' <- reduceCExprOrZero e ctx
          s' <- rs
          pure $ C.CWhile e' s' dow ni
      C.CExpr me ni -> do
        case me of
          Just e -> do
            if DoNoops `isIn` ctx
              then Just do
                e' <- maybeSplit ("change to noop", C.posOf smt) $ reduceCExpr e ctx
                pure $ C.CExpr e' ni
              else do
                re <- reduceCExpr e ctx
                Just do
                  e' <- re
                  pure $ C.CExpr (Just e') ni
          Nothing ->
            Just $ pure $ C.CExpr Nothing ni
      C.CReturn me ni -> Just do
        case me of
          Just e -> do
            e' <- reduceCExprOrZero e ctx
            pure $ C.CReturn (Just e') ni
          Nothing ->
            pure $ C.CReturn Nothing ni
      C.CIf e s els ni -> Just do
        e' <- maybeSplit ("remove condition", C.posOf e) $ reduceCExpr e ctx
        els' <- case els of
          Just els' -> do
            maybeSplit ("remove else branch", C.posOf els') do
              reduceCStatement els' ctx
          Nothing -> pure Nothing
        ms' <- maybeSplit ("remove if branch", C.posOf s) do
          reduceCStatement s ctx
        case (e', ms', els') of
          (Nothing, Nothing, Nothing) -> pure emptyBlock
          (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'
      C.CFor e1 e2 e3 s ni -> Just $ do
        (me1', ctx') <- case e1 of
          C.CForDecl (C.CDecl rec decl ni') -> do
            (decl', ctx') <- foldr reduceCDeclarationItem (pure ([], ctx)) decl
            res <-
              if null decl'
                then
                  whenSplit
                    (AllowEmptyDeclarations `isIn` ctx')
                    ("remove empty declaration", C.posOf ni')
                    (pure Nothing)
                    (pure $ Just $ C.CForDecl (C.CDecl rec decl' ni'))
                else pure $ Just $ C.CForDecl (C.CDecl rec decl' ni')
            pure (res, ctx')
          C.CForInitializing e -> do
            e' <- maybeSplit ("remove initializer", C.posOf ni) (e >>= \e' -> reduceCExpr e' ctx)
            whenSplit
              (AllowEmptyDeclarations `isIn` ctx)
              ("remove empty declaration", C.posOf ni)
              (pure (Nothing, ctx))
              (pure (Just $ C.CForInitializing e', ctx))
          d -> don'tHandle d
    
        s' <- reduceCStatementOrEmptyBlock s ctx'
        case me1' of
          Nothing -> do
            split ("remove the for loop", C.posOf smt) (pure s') do
              e2' <- case e2 of
                Just e2' -> maybeSplit ("remove check", C.posOf e2') (reduceCExpr e2' ctx')
                Nothing -> pure Nothing
              e3' <- case e3 of
                Just e3' -> maybeSplit ("remove iterator", C.posOf e3') (reduceCExpr e3' ctx')
                Nothing -> pure Nothing
              pure $ C.CFor (C.CForInitializing Nothing) e2' e3' s' ni
          Just e1' -> do
            e2' <- case e2 of
              Just e2' -> maybeSplit ("remove check", C.posOf e2') (reduceCExpr e2' ctx')
              Nothing -> pure Nothing
            e3' <- case e3 of
              Just e3' -> maybeSplit ("remove iterator", C.posOf e3') (reduceCExpr e3' ctx')
              Nothing -> pure Nothing
            pure $ C.CFor e1' e2' e3' s' ni
      C.CBreak ni -> Just do
        pure (C.CBreak ni)
      C.CCont ni -> Just do
        pure (C.CCont ni)
      C.CLabel i s [] ni -> Just do
        s' <- reduceCStatementOrEmptyBlock s ctx
        pure $ C.CLabel i s' [] ni
      C.CGoto i ni -> Just do
        pure $ C.CGoto i ni
      a -> don'tHandleWithPos a
    
    -- | 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
    
    zeroExpr :: C.CExpression C.NodeInfo
    zeroExpr = C.CConst (C.CIntConst (C.cInteger 0) C.undefNode)
    
    reduceCExprOrZero :: (MonadReduce Lab m, HasCallStack) => C.CExpr -> Context -> m C.CExpr
    reduceCExprOrZero expr ctx = do
      case reduceCExpr expr ctx of
        Just ex -> do
          r <- ex
          if r == zeroExpr
            then pure r
            else split ("replace by zero", C.posOf expr) (pure zeroExpr) (pure r)
        Nothing -> do
          pure zeroExpr
    {-# INLINE reduceCExprOrZero #-}
    
    reduceCExpr :: (MonadReduce Lab m, HasCallStack) => C.CExpr -> Context -> Maybe (m C.CExpr)
    reduceCExpr expr ctx = case expr of
      C.CBinary o elhs erhs ni -> do
        case reduceCExpr elhs ctx of
          Just elhs' -> case reduceCExpr erhs ctx of
            Just erhs' -> pure do
              split ("reduce to left", C.posOf elhs) elhs' do
                split ("reduce to right", C.posOf erhs) erhs' do
                  l' <- elhs'
                  r' <- erhs'
                  pure $ C.CBinary o l' r' ni
            Nothing ->
              fail "could not reduce right hand side"
          Nothing
            | otherwise -> fail "could not reduce left hand side"
      C.CAssign o elhs erhs ni ->
        case reduceCExpr elhs (addKeyword DisallowVariableInlining ctx) of
          Just elhs' -> case reduceCExpr erhs ctx of
            Just erhs' -> pure do
              split ("reduce to left", C.posOf elhs) elhs' do
                split ("reduce to right", C.posOf erhs) erhs' do
                  l' <- elhs'
                  r' <- erhs'
                  pure $ C.CAssign o l' r' ni
            Nothing ->
              fail "could not reduce right hand side"
          Nothing
            | otherwise -> fail "could not reduce left hand side"
      C.CVar i _ ->
        case Map.lookup i . inlineExprs $ ctx of
          Just mx -> case mx of
            IEKeep -> Just (pure expr)
            IEInline mx'
              | DisallowVariableInlining `isIn` ctx -> Nothing
              | otherwise -> Just (pure mx')
            IEDelete ->
              Nothing
          Nothing -> error ("Could not find " <> show i <> " at " <> show (C.posOf expr) <> "\n" <> show (inlineExprs ctx))
      C.CConst x -> Just do
        pure $ C.CConst x
      C.CUnary o elhs ni -> do
        elhs' <- reduceCExpr elhs (addKeyword DisallowVariableInlining ctx)
        Just $ split ("reduce to operant", C.posOf expr) elhs' do
          e <- elhs'
          pure $ C.CUnary o e ni
      C.CCall e es ni -> do
        let re = reduceCExpr e (addKeyword DisallowVariableInlining ctx)
            res = map (`reduceCExpr` ctx) es
        case (re, catMaybes res) of
          (Nothing, []) -> Nothing
          (Nothing, [r]) -> Just r
          (_, _) -> Just do
            e' <- maybeSplit ("do without function", C.posOf e) re
            es' <- res & traverse (maybeSplit ("do without pram", C.posOf e))
            case (e', catMaybes es') of
              (Nothing, []) -> pure zeroExpr
              (Nothing, [e'']) -> pure e''
              (Nothing, es'') -> pure $ C.CComma es'' C.undefNode
              (Just f, _) -> pure $ C.CCall f (map (fromMaybe zeroExpr) es') ni
      C.CCond ec et ef ni -> do
        -- TODO: More fine grained reduction is possible here.
        Just $ do
          ec' <- reduceCExprOrZero ec ctx
          ef' <- reduceCExprOrZero ef ctx
          et' <- case et of
            Just et' -> Just <$> reduceCExprOrZero et' ctx
            Nothing -> pure Nothing
          pure $ C.CCond ec' et' ef' ni
      C.CCast decl e ni -> do
        re <- reduceCExpr e ctx
        Just do
          split ("don't cast", C.posOf ni) re do
            e' <- re
            pure (C.CCast (inlineTypeDefsCDeclaration decl ctx) e' ni)
      C.CIndex e1 e2 ni -> do
        -- TODO: Better reduction is posisble here.
        re1 <- reduceCExpr e1 ctx
        Just do
          e1' <- re1
          e2' <- reduceCExprOrZero e2 ctx
          pure $ C.CIndex e1' e2' ni
      C.CComma items ni -> Just do
        let Just (x, rst) = List.uncons (reverse items)
        rst' <-
          foldr
            ( \e cc -> do
                maybeSplit ("remove expression", C.posOf e) (reduceCExpr e ctx) >>= \case
                  Just e' -> (e' :) <$> cc
                  Nothing -> cc
            )
            (pure [])
            rst
        x' <- reduceCExprOrZero x ctx
        if List.null rst'
          then pure x'
          else pure $ C.CComma (reverse (x' : rst')) ni
      C.CMember e i l ni -> do
        re <- reduceCExpr e ctx
        Just do
          e' <- re
          pure (C.CMember e' i l ni)
      a -> don'tHandleWithPos a
    
    inlineTypeDefsCDeclaration :: C.CDeclaration C.NodeInfo -> Context -> C.CDeclaration C.NodeInfo
    inlineTypeDefsCDeclaration decl ctx =
      case decl of
        C.CDecl items decli ni ->
          C.CDecl (inlineTypeDefsSpecs items ctx) (map (`inlineTypeDefsCDI` ctx) decli) ni
        a -> don'tHandle a
    
    inlineTypeDefsSpecs :: [C.CDeclarationSpecifier C.NodeInfo] -> Context -> [C.CDeclarationSpecifier C.NodeInfo]
    inlineTypeDefsSpecs r ctx =
      r & concatMap \case
        a@(C.CTypeSpec (C.CTypeDef idx _)) -> do
          case Map.lookup idx . typeDefs $ ctx of
            Just ITKeep -> [a]
            Just (ITInline res) -> res
            Nothing -> error ("could not find typedef:" <> show idx)
        a@(C.CTypeSpec (C.CSUType (C.CStruct _ (Just idx) Nothing _ _) _)) ->
          case Map.lookup idx . structs $ ctx of
            Just (Just def) -> [C.CTypeSpec (C.CSUType def C.undefNode)]
            Just Nothing -> [a]
            Nothing -> error ("could not find struct:" <> show idx)
        C.CTypeSpec (C.CSUType (C.CStruct a b (Just c) d e) f) ->
          [C.CTypeSpec (C.CSUType (C.CStruct a b (Just $ map (`inlineTypeDefsCDeclaration` ctx) c) d e) f)]
        a -> [a]
    {-# NOINLINE inlineTypeDefsSpecs #-}
    
    inlineTypeDefsCDeclarator
      :: C.CDeclarator C.NodeInfo
      -> Context
      -> C.CDeclarator C.NodeInfo
    inlineTypeDefsCDeclarator (C.CDeclr idn derivedd st atr ni) ctx =
      C.CDeclr idn (map (inlineTypeDefsX ctx) derivedd) st atr ni
    
    inlineTypeDefsX :: Context -> C.CDerivedDeclarator C.NodeInfo -> C.CDerivedDeclarator C.NodeInfo
    inlineTypeDefsX ctx = \case
      C.CFunDeclr (C.CFunParamsNew x y) b c ->
        C.CFunDeclr (C.CFunParamsNew (map (`inlineTypeDefsCDeclaration` ctx) x) y) b c
      C.CArrDeclr a b c -> C.CArrDeclr a b c
      C.CPtrDeclr a b -> C.CPtrDeclr a b
      a -> don'tHandle a
    
    inlineTypeDefsCDI :: C.CDeclarationItem C.NodeInfo -> Context -> C.CDeclarationItem C.NodeInfo
    inlineTypeDefsCDI di ctx = case di of
      C.CDeclarationItem a b ni -> C.CDeclarationItem (inlineTypeDefsCDeclarator a ctx) b ni
      a -> don'tHandle a
    
    identifiers :: forall a. (Data a) => a -> [C.Ident]
    identifiers d = appEndo (go d) []
     where
      go :: forall a'. (Data a') => a' -> Endo [C.Ident]
      go d' = case cast d' of
        Just l -> Endo (l :)
        Nothing -> gmapQl (<>) mempty go d'
    
    functionName :: C.CFunctionDef C.NodeInfo -> Maybe C.Ident
    functionName = \case
      C.CFunDef _ (C.CDeclr ix _ _ _ _) _ _ _ -> ix
    
    don'tHandle :: (HasCallStack, Functor f, Show (f ())) => f C.NodeInfo -> b
    don'tHandle f = error (show (f $> ()))
    
    don'tHandleWithPos :: (HasCallStack, Functor f, Show (f ()), C.Pos (f C.NodeInfo)) => f C.NodeInfo -> b
    don'tHandleWithPos f = error (show (f $> ()) <> " at " <> show (C.posOf f))
    
    don'tHandleWithNodeInfo :: (HasCallStack, Functor f, Show (f ())) => f C.NodeInfo -> C.NodeInfo -> b
    don'tHandleWithNodeInfo f ni = error (show (f $> ()) <> " at " <> show (C.posOf ni))