Andreas Klebinger pushed to branch wip/apk/tag-strict-bot at Glasgow Haskell Compiler / GHC

Commits:

23 changed files:

Changes:

  • changelog.d/T27831-bottoming-strict-field
    1
    +section: compiler
    
    2
    +issues: #27831
    
    3
    +mrs: !16719
    
    4
    +synopsis:
    
    5
    +  Fix a segfault that could occur when a bottoming function was stored in a strict field.
    
    6
    +description:
    
    7
    +  This was happening when a function that always diverges (e.g. ``\_ -> error "..."``) was stored in a strict field, and the constructor was pattern matched on
    
    8
    +  in "nearby" code where the structure of the constructor application was still visible to GHC.

  • changelog.d/T27842-bytecode-imported-tags
    1
    +section: bytecode
    
    2
    +issues: #27842
    
    3
    +mrs: !16719
    
    4
    +synopsis:
    
    5
    +  Fix a segfault caused by reusing interface files generated with
    
    6
    +  ``-fbyte-code-and-object-code`` for interpreter sessions using ``-fprefer-byte-code``.

  • compiler/GHC/Stg/EnforceEpt.hs
    ... ... @@ -334,9 +334,15 @@ a) Tags for let-bindings:
    334 334
     b) When referencing ids from other modules the Cmm backend will try to put a
    
    335 335
        proper tag on these references through various means. When doing analysis we
    
    336 336
        usually predict these cases to improve precision of the analysis.
    
    337
    -   But to my knowledge the bytecode generator makes no such attempts so we must
    
    338
    -   not infer imported bindings as tagged.
    
    339
    -   This is handled in GHC.Stg.EnforceEpt.Types.lookupInfo
    
    337
    +   The bytecode generator gives fewer guarantees about what it tags.
    
    338
    +   So we simply assume it fails to tag binders, even in the cases where it does.
    
    339
    +
    
    340
    +   This is handled in GHC.Stg.EnforceEpt.Types.argTagInfo
    
    341
    +
    
    342
    + Eventually we will want to update the Analysis and bco code gen to uphold and
    
    343
    + take advantage of tag inference at least to some degree. But for now it's
    
    344
    + not a perf benefit for interpreted code, and essentially only run to uphold
    
    345
    + the "strict field invariant"/EPT Invariant described in EPT enforcement.
    
    340 346
     
    
    341 347
     
    
    342 348
     -}
    
    ... ... @@ -359,7 +365,7 @@ enforceEpt ppr_opts !for_bytecode logger this_mod stg_binds = do
    359 365
     
    
    360 366
         -- Rewrite STG to uphold the strict field invariant
    
    361 367
         us_t <- mkSplitUniqSupply StgTag
    
    362
    -    let rewritten_binds = {-# SCC "StgEptRewrite" #-} rewriteTopBinds this_mod us_t stg_binds_w_tags :: [TgStgTopBinding]
    
    368
    +    let rewritten_binds = {-# SCC "StgEptRewrite" #-} rewriteTopBinds for_bytecode this_mod us_t stg_binds_w_tags :: [TgStgTopBinding]
    
    363 369
     
    
    364 370
         return (rewritten_binds,export_tag_info)
    
    365 371
     
    
    ... ... @@ -414,12 +420,14 @@ inferTagExpr env (StgApp fun args)
    414 420
              | isDeadEndAppSig (idDmdSig fun) (length args)
    
    415 421
              = TagBottoming
    
    416 422
     
    
    417
    -         | fun_arity == 0 -- Unknown arity => Thunk or unknown call
    
    423
    +         | not (isJoinId fun) -- zero arity join ids behave like functions.
    
    424
    +         , fun_arity == 0 -- Unknown arity => Thunk or unknown call
    
    418 425
              = TagDunno
    
    419 426
     
    
    420 427
              -- Imported function with known return tag
    
    421 428
              | Just (TagFun res_info) <- tagSigInfo (idInfo fun)
    
    422 429
              , fun_arity == length args  -- Saturated
    
    430
    +         , not (te_bytecode env) -- #27842
    
    423 431
              = res_info
    
    424 432
     
    
    425 433
              -- Local function with known return tag
    
    ... ... @@ -613,8 +621,8 @@ it safely any tag sig we like.
    613 621
     So we give it TagBottoming, as it allows the combined tag sig of the case expression
    
    614 622
     to be the combination of all non-bottoming branches.
    
    615 623
     
    
    616
    -NB: After the analysis is done we go back to treating bottoming functions as
    
    617
    -untagged to ensure they are evaluated as expected in code like:
    
    624
    +NB: We now distinguish functions from values. Allowing us to treat bottom functions
    
    625
    +as tagged, while still doing the right thing for bottom values in code like:
    
    618 626
     
    
    619 627
       case bottom_id of { ...}
    
    620 628
     
    

  • compiler/GHC/Stg/EnforceEpt/Rewrite.hs
    ... ... @@ -13,7 +13,7 @@ import GHC.Builtin.PrimOps ( PrimOp(..) )
    13 13
     import GHC.Types.Basic     ( CbvMark (..), isMarkedCbv
    
    14 14
                                , TopLevelFlag(..), isTopLevel )
    
    15 15
     import GHC.Types.Id
    
    16
    -import GHC.Types.Name
    
    16
    +import GHC.Types.Name ( nameIsLocalOrFrom )
    
    17 17
     import GHC.Types.Unique.Supply
    
    18 18
     import GHC.Types.Unique.FM
    
    19 19
     import GHC.Types.RepType
    
    ... ... @@ -22,10 +22,7 @@ import GHC.Unit.Types
    22 22
     
    
    23 23
     import GHC.Core.DataCon
    
    24 24
     import GHC.Core            ( AltCon(..) )
    
    25
    -import GHC.Core.Type
    
    26
    -
    
    27
    -import GHC.StgToCmm.Types
    
    28
    -import GHC.StgToCmm.Closure (importedIdLFInfo)
    
    25
    +import GHC.Core.Type       ( definitelyLiftedType )
    
    29 26
     
    
    30 27
     import GHC.Stg.Utils
    
    31 28
     import GHC.Stg.Syntax as StgSyn
    
    ... ... @@ -41,7 +38,7 @@ import GHC.Stg.EnforceEpt.Types
    41 38
     
    
    42 39
     import Control.Monad
    
    43 40
     
    
    44
    -newtype RM a = RM { unRM :: (State (UniqFM Id TagSig, UniqSupply, Module, IdSet) a) }
    
    41
    +newtype RM a = RM { unRM :: (State (UniqFM Id TagSig, UniqSupply, Module, IdSet, Bool) a) }
    
    45 42
         deriving (Functor, Monad, Applicative)
    
    46 43
     
    
    47 44
     ------------------------------------------------------------
    
    ... ... @@ -108,29 +105,34 @@ when building the free variable list.
    108 105
     
    
    109 106
     instance MonadUnique RM where
    
    110 107
         getUniqueSupplyM = RM $ do
    
    111
    -        (m, us, mod,lcls) <- get
    
    108
    +        (m, us, mod,lcls,for_bytecode) <- get
    
    112 109
             let (us1, us2) = splitUniqSupply us
    
    113
    -        (put) (m,us2,mod,lcls)
    
    110
    +        (put) (m,us2,mod,lcls,for_bytecode)
    
    114 111
             return us1
    
    115 112
     
    
    116 113
     getMap :: RM (UniqFM Id TagSig)
    
    117
    -getMap = RM $ ((\(fst,_,_,_) -> fst) <$> get)
    
    114
    +getMap = RM $ ((\(fst,_,_,_,_) -> fst) <$> get)
    
    118 115
     
    
    119 116
     setMap :: (UniqFM Id TagSig) -> RM ()
    
    120 117
     setMap !m = RM $ do
    
    121
    -    (_,us,mod,lcls) <- get
    
    122
    -    put (m, us,mod,lcls)
    
    123
    -
    
    124
    -getMod :: RM Module
    
    125
    -getMod = RM $ ( (\(_,_,thrd,_) -> thrd) <$> get)
    
    118
    +    (_,us,mod,lcls,for_bytecode) <- get
    
    119
    +    put (m, us,mod,lcls,for_bytecode)
    
    126 120
     
    
    127 121
     getFVs :: RM IdSet
    
    128
    -getFVs = RM $ ((\(_,_,_,lcls) -> lcls) <$> get)
    
    122
    +getFVs = RM $ ((\(_,_,_,lcls,_) -> lcls) <$> get)
    
    129 123
     
    
    130 124
     setFVs :: IdSet -> RM ()
    
    131 125
     setFVs !fvs = RM $ do
    
    132
    -    (tag_map,us,mod,_lcls) <- get
    
    133
    -    put (tag_map, us,mod,fvs)
    
    126
    +    (tag_map,us,mod,_lcls,for_bytecode) <- get
    
    127
    +    put (tag_map, us,mod,fvs,for_bytecode)
    
    128
    +
    
    129
    +getMod :: RM Module
    
    130
    +getMod = RM $ ( (\(_,_,c,_,_) -> c) <$> get)
    
    131
    +
    
    132
    +-- | When generating bytecode certain bindings might be untagged. So we have to
    
    133
    +-- have a way to check for that.
    
    134
    +getForBytecode :: RM Bool
    
    135
    +getForBytecode = RM $ ((\(_,_,_,_,for_bytecode) -> for_bytecode) <$> get)
    
    134 136
     
    
    135 137
     -- Rewrite the RHS(s) while making the id and it's sig available
    
    136 138
     -- to determine if things are tagged/need to be captured as FV.
    
    ... ... @@ -238,51 +240,26 @@ indicates a bug in the tag inference implementation.
    238 240
     For this reason we assert that we are running in interactive mode if a lookup fails.
    
    239 241
     -}
    
    240 242
     isTagged :: Id -> RM Bool
    
    241
    -isTagged v
    
    242
    -    -- See Note [Bottom functions are TagBottoming]
    
    243
    -    | isDeadEndId v = pure False
    
    244
    -    | otherwise = do
    
    243
    +isTagged v = do
    
    244
    +    for_bytecode <- getForBytecode
    
    245 245
         this_mod <- getMod
    
    246
    -    -- See Note [Tag inference for interactive contexts]
    
    247
    -    let lookupDefault v = assertPpr (isInteractiveModule this_mod)
    
    248
    -                                    (text "unknown Id:" <> ppr this_mod <+> ppr v)
    
    249
    -                                    (TagVal TagDunno)
    
    250
    -    case nameIsLocalOrFrom this_mod (idName v) of
    
    251
    -        True
    
    252
    -            | definitelyUnliftedType (idType v)
    
    253
    -              -- NB: v might be the Id of a representation-polymorphic join point,
    
    254
    -              -- so we shouldn't use isUnliftedType here. See T22212.
    
    255
    -            -> return True
    
    256
    -            | otherwise -> do -- Local binding
    
    257
    -                !s <- getMap
    
    258
    -                let !sig = lookupWithDefaultUFM s (lookupDefault v) v
    
    259
    -                return $ case sig of
    
    260
    -                    TagFun _             -> True  -- function closure is tagged
    
    261
    -                    TagVal TagDunno      -> False
    
    262
    -                    TagVal TagEPT        -> True
    
    263
    -                    TagVal TagBottoming  -> True
    
    264
    -                    TagVal (TagTuple _)  -> True  -- Consider unboxed tuples tagged.
    
    265
    -        -- Imported
    
    266
    -        False -> return $!
    
    267
    -                -- Determine whether it is tagged from the LFInfo of the imported id.
    
    268
    -                -- See Note [The LFInfo of Imported Ids]
    
    269
    -                case importedIdLFInfo v of
    
    270
    -                    -- Function, applied not entered.
    
    271
    -                    LFReEntrant {}
    
    272
    -                        -> True
    
    273
    -                    -- Thunks need to be entered.
    
    274
    -                    LFThunk {}
    
    275
    -                        -> False
    
    276
    -                    -- LFCon means we already know the tag, and it's tagged.
    
    277
    -                    LFCon {}
    
    278
    -                        -> True
    
    279
    -                    LFUnknown {}
    
    280
    -                        -> False
    
    281
    -                    LFUnlifted {}  -- Unboxed, tagging irrelevant
    
    282
    -                        -> True
    
    283
    -                    LFLetNoEscape {}
    
    284
    -                    -- Shouldn't be possible. I don't think we can export letNoEscapes
    
    285
    -                        -> True
    
    246
    +    s <- getMap
    
    247
    +    let get_env_sig = lookupUFM s
    
    248
    +    massertPpr
    
    249
    +        -- Assert the env is complete for local lifted binders, which means either
    
    250
    +        -- - The binder is present in the env
    
    251
    +        -- - The binder is not a local lifted binder *or* comes from a ghci context
    
    252
    +        (isJust (get_env_sig v)
    
    253
    +            -- See Note [Tag inference for interactive contexts]
    
    254
    +            || isInteractiveModule this_mod
    
    255
    +            || not (nameIsLocalOrFrom this_mod (idName v))
    
    256
    +            || not (definitelyLiftedType (idType v)))
    
    257
    +               (text "unknown Id:" <> ppr this_mod <+> ppr v)
    
    258
    +    -- TODO: We could refactor the whole pass such that we also set id_tagSig
    
    259
    +    -- for every imported id. But this likely comes with a heavy allocation
    
    260
    +    -- overhead so for now we just ensure to use the same logic across analysis
    
    261
    +    -- and rewriter.
    
    262
    +    pure $! isTaggedInfo $ argTagInfo for_bytecode (get_env_sig v) (StgVarArg v)
    
    286 263
     
    
    287 264
     
    
    288 265
     isArgTagged :: StgArg -> RM Bool
    
    ... ... @@ -299,11 +276,12 @@ mkLocalArgId id = do
    299 276
     ---------------------------
    
    300 277
     
    
    301 278
     
    
    302
    -rewriteTopBinds :: Module -> UniqSupply -> [GenStgTopBinding 'InferTaggedBinders] -> [TgStgTopBinding]
    
    303
    -rewriteTopBinds mod us binds =
    
    279
    +rewriteTopBinds :: Bool -- ^ Generating bytecode?
    
    280
    +                -> Module -> UniqSupply -> [GenStgTopBinding 'InferTaggedBinders] -> [TgStgTopBinding]
    
    281
    +rewriteTopBinds for_bytecode mod us binds =
    
    304 282
         let doBinds = mapM rewriteTop binds
    
    305 283
     
    
    306
    -    in evalState (unRM doBinds) (mempty, us, mod, mempty)
    
    284
    +    in evalState (unRM doBinds) (mempty, us, mod, mempty, for_bytecode)
    
    307 285
     
    
    308 286
     rewriteTop :: InferStgTopBinding -> RM TgStgTopBinding
    
    309 287
     rewriteTop (StgTopStringLit v s) = return $! (StgTopStringLit v s)
    

  • compiler/GHC/Stg/EnforceEpt/Types.hs
    ... ... @@ -12,7 +12,7 @@ where
    12 12
     import GHC.Prelude
    
    13 13
     
    
    14 14
     import GHC.Core.DataCon
    
    15
    -import GHC.Core.Type (isUnliftedType)
    
    15
    +import GHC.Core.Type (isUnliftedType, definitelyUnliftedType)
    
    16 16
     import GHC.Types.Id
    
    17 17
     import GHC.Stg.Syntax
    
    18 18
     import GHC.Stg.EnforceEpt.TagSig as TagSig
    
    ... ... @@ -22,6 +22,7 @@ import GHC.Utils.Misc( zipWithEqual )
    22 22
     import GHC.Utils.Panic
    
    23 23
     
    
    24 24
     import GHC.StgToCmm.Types
    
    25
    +import GHC.StgToCmm.Closure (importedIdLFInfo)
    
    25 26
     
    
    26 27
     {- *********************************************************************
    
    27 28
     *                                                                      *
    
    ... ... @@ -97,31 +98,39 @@ lookupReturnInfo env fun = case lookupVarEnv (te_env env) fun of
    97 98
       Just (TagVal _)        -> Nothing
    
    98 99
       Nothing                -> Nothing
    
    99 100
     
    
    100
    --- | Look up a value-level tag for an argument: either from the env (where
    
    101
    --- a function-typed argument flattens to TagEPT — its closure is tagged)
    
    102
    --- or derived from information on the variable itself.
    
    103
    -lookupInfo :: TagEnv p -> StgArg -> TagInfo
    
    104
    -lookupInfo env (StgVarArg var)
    
    101
    +-- | Look up a value-level tag.
    
    102
    +--
    
    103
    +-- If the tag info is completely determined by Id info we use that tag.
    
    104
    +-- If the tag info is dependent on the analysis/call site use the passed TagSig.
    
    105
    +-- If the passed TagSig is Nothing we fall back to safe tag info based on id properties.
    
    106
    +argTagInfo :: Bool -> (Maybe TagSig) -> StgArg -> TagInfo
    
    107
    +{-# INLINE argTagInfo #-} -- Specialize for the call site.
    
    108
    +argTagInfo _for_bytecode _env_sig (StgLitArg{}) = TagEPT
    
    109
    +argTagInfo for_bytecode env_sig (StgVarArg var)
    
    105 110
       -- Nullary data constructors like True, False
    
    106 111
       | Just dc <- isDataConWorkId_maybe var
    
    107 112
       , isNullaryRepDataCon dc
    
    108 113
       , not for_bytecode
    
    109 114
       = TagEPT
    
    110 115
     
    
    111
    -  | isUnliftedType (idType var)
    
    116
    +  -- NB: v might be the Id of a representation-polymorphic join point,
    
    117
    +  -- so we shouldn't use isUnliftedType here. See T22212.
    
    118
    +  | definitelyUnliftedType (idType var)
    
    112 119
       = TagEPT
    
    113 120
     
    
    114 121
       -- Variables in the environment. A function binding flattens to TagEPT
    
    115 122
       -- since a function closure pointer is properly tagged; its return info
    
    116 123
       -- is not relevant when the function is used as a value.
    
    117
    -  | Just sig <- lookupVarEnv (te_env env) var
    
    124
    +  | Just sig <- env_sig
    
    118 125
       = case sig of
    
    119 126
           TagVal info -> info
    
    120 127
           TagFun _    -> TagEPT
    
    121 128
     
    
    122
    -  | Just lf_info <- idLFInfo_maybe var
    
    123
    -  , not for_bytecode
    
    124
    -  =   case lf_info of
    
    129
    +-- Always save defaults.
    
    130
    +  | lf_info <- importedIdLFInfo var -- will make up LFInfo if not present.
    
    131
    +  = if for_bytecode then TagDunno
    
    132
    +    else
    
    133
    +        case lf_info of
    
    125 134
               -- Function, tagged (with arity)
    
    126 135
               LFReEntrant {}
    
    127 136
                   -> TagEPT
    
    ... ... @@ -138,13 +147,12 @@ lookupInfo env (StgVarArg var)
    138 147
               -- Shouldn't be possible. I don't think we can export letNoEscapes
    
    139 148
               LFLetNoEscape {} -> panic "LFLetNoEscape exported"
    
    140 149
     
    
    141
    -  | otherwise
    
    142
    -  = TagDunno
    
    143
    -  where
    
    144
    -    for_bytecode = te_bytecode env
    
    145
    -
    
    146
    -lookupInfo _ (StgLitArg {})
    
    147
    -  = TagEPT
    
    150
    +-- | Get tag info using the environment.
    
    151
    +lookupInfo :: TagEnv p -> StgArg -> TagInfo
    
    152
    +lookupInfo env arg@(StgVarArg var) =
    
    153
    +    let env_sig = (lookupVarEnv (te_env env) var)
    
    154
    +    in  argTagInfo (te_bytecode env) env_sig arg
    
    155
    +lookupInfo _env (StgLitArg{}) = TagEPT
    
    148 156
     
    
    149 157
     isDunnoSig :: TagSig -> Bool
    
    150 158
     isDunnoSig (TagVal TagDunno) = True
    

  • testsuite/tests/bytecode/T27842.hs
    1
    +{-# LANGUAGE UnboxedTuples #-}
    
    2
    +{-# OPTIONS_GHC -fno-worker-wrapper -fno-cpr-anal #-}
    
    3
    +-- Regression test for #27842.
    
    4
    +--
    
    5
    +-- The basic rundown is this:
    
    6
    +-- We produce one interface file, however we store information dependent on the
    
    7
    +-- code generation method in the interface file.
    
    8
    +--
    
    9
    +-- We happen to pick the native code information to store in the interface file.
    
    10
    +--
    
    11
    +-- When we compile a module M this way, and then generate byte code for code
    
    12
    +-- depending on M based on M.hi we will assume certain exported definitions are EPT
    
    13
    +-- tagged, despite the interpreter doing no such thing.
    
    14
    +--
    
    15
    +-- This then led to crashes in the interpreter.
    
    16
    +--
    
    17
    +-- We test three kinds of imported definitions:
    
    18
    +-- * mk uses fun, a function whose TagSig says its result is tagged.
    
    19
    +-- * bar uses just_con, a value whose LFInfo says it is a constructor.
    
    20
    +-- * baz uses gt_con, the same for a nullary constructor.
    
    21
    +module Main where
    
    22
    +
    
    23
    +import T27842A (fun, just_con, gt_con)
    
    24
    +import T27842N (S(..), use_S, M(..), use_M, N(..), use_N)
    
    25
    +
    
    26
    +mk :: Int -> S
    
    27
    +mk x = case fun x of (# xs, _ #) -> S xs
    
    28
    +{-# NOINLINE mk #-}
    
    29
    +
    
    30
    +bar :: M
    
    31
    +bar = M just_con
    
    32
    +{-# NOINLINE bar #-}
    
    33
    +
    
    34
    +baz :: N
    
    35
    +baz = N gt_con
    
    36
    +{-# NOINLINE baz #-}
    
    37
    +
    
    38
    +main :: IO ()
    
    39
    +main = do
    
    40
    +  print (use_S (mk 42))
    
    41
    +  print (use_M bar)
    
    42
    +  print (use_N baz)

  • testsuite/tests/bytecode/T27842.stdout
    1
    +42
    
    2
    +42
    
    3
    +3

  • testsuite/tests/bytecode/T27842A.hs
    1
    +{-# LANGUAGE UnboxedTuples #-}
    
    2
    +{-# OPTIONS_GHC -fno-worker-wrapper -fno-cpr-anal #-}
    
    3
    +module T27842A (fun, just_con, gt_con) where
    
    4
    +
    
    5
    +-- The native EPT run gives fun the TagSig TagFun[TagTuple[TagEPT, TagDunno]]:
    
    6
    +fun :: Int -> (# [Int], Int #)
    
    7
    +fun x = let xs = x : xs in (# xs, x #)
    
    8
    +{-# NOINLINE fun #-}
    
    9
    +
    
    10
    +-- In native code a static constructor, in bytecode an unevaluated closure.
    
    11
    +just_con :: Maybe Int
    
    12
    +just_con = Just 42
    
    13
    +{-# NOINLINE just_con #-}
    
    14
    +
    
    15
    +-- The same for a nullary constructor. Without its pointer tag, the object code
    
    16
    +-- in T27842N would take the LT alternative, so GT shows the difference.
    
    17
    +gt_con :: Ordering
    
    18
    +gt_con = GT
    
    19
    +{-# NOINLINE gt_con #-}

  • testsuite/tests/bytecode/T27842N.hs
    1
    +{-# OPTIONS_GHC -fno-byte-code-and-object-code #-}
    
    2
    +-- We compile this module as native code, depending on A's interface file.
    
    3
    +-- A itself is loaded into GHCi, despite it's interface file being generated based
    
    4
    +-- on native code generation.
    
    5
    +module T27842N (S(..), use_S, M(..), use_M, N(..), use_N) where
    
    6
    +
    
    7
    +data S = S ![Int]
    
    8
    +
    
    9
    +-- We take apart the strict constructor value and branch on the value in its field.
    
    10
    +-- If the field content is not EPT this causes undefined behaviour.
    
    11
    +
    
    12
    +use_S :: S -> Int
    
    13
    +use_S (S xs) = case xs of { (y:_) -> y; [] -> -1 }
    
    14
    +{-# NOINLINE use_S #-}
    
    15
    +
    
    16
    +data M = M !(Maybe Int)
    
    17
    +
    
    18
    +use_M :: M -> Int
    
    19
    +use_M (M m) = case m of { Just x -> x; Nothing -> -1 }
    
    20
    +{-# NOINLINE use_M #-}
    
    21
    +
    
    22
    +data N = N !Ordering
    
    23
    +
    
    24
    +use_N :: N -> Int
    
    25
    +use_N (N o) = case o of { LT -> 1; EQ -> 2; GT -> 3 }
    
    26
    +{-# NOINLINE use_N #-}

  • testsuite/tests/bytecode/all.T
    ... ... @@ -17,3 +17,12 @@ test('T26216', extra_files(["T26216_aux.hs"]), ghci_script, ['T26216.script'])
    17 17
     test('T27001', [extra_files(['T27001.hs']), req_interp],
    
    18 18
          run_command,
    
    19 19
          ['{compiler} -e main -O -fno-unoptimized-core-for-interpreter T27001.hs'])
    
    20
    +
    
    21
    +# Bytecode must not trust tag information from imported interfaces, which
    
    22
    +# comes from native code (#27842): neither the TagSig of an imported function
    
    23
    +# nor the LFInfo of an imported value (with and without constructor fields).
    
    24
    +test('T27842', [extra_files(['T27842A.hs', 'T27842N.hs']), req_interp],
    
    25
    +     run_command,
    
    26
    +     ['{compiler} -e main -O -fno-unoptimized-core-for-interpreter '
    
    27
    +      '-fobject-code -fbyte-code-and-object-code -fwrite-byte-code -fprefer-byte-code '
    
    28
    +      'T27842.hs'])

  • testsuite/tests/simplStg/should_compile/all.T
    ... ... @@ -25,5 +25,17 @@ test('inferTags003', [ only_ways(['optasm']),
    25 25
                          ], compile, ['-ddump-cmm -dno-typeable-binds -O'])
    
    26 26
     test('inferTags004', normal, compile, ['-O -ddump-stg-tags -dno-typeable-binds -dsuppress-uniques'])
    
    27 27
     test('T27005b', normal, compile, ['-O -ddump-stg-tags -dno-typeable-binds -dsuppress-uniques'])
    
    28
    +# Both f and the join point j should have a signature TagTuple[TagEPT, TagEPT]
    
    29
    +test('inferTags005',
    
    30
    +     grep_errmsg(r'(TagTuple\[TagEPT, TagEPT\])', [1]),
    
    31
    +     compile,
    
    32
    +     ['-O -fno-worker-wrapper -fno-cpr-anal -ddump-stg-tags '
    
    33
    +      '-dno-typeable-binds -dsuppress-uniques'])
    
    34
    +# The function needs to have a signature TagTuple[TagEPT, TagEPT]
    
    35
    +test('inferTags006',
    
    36
    +     grep_errmsg(r'(TagTuple\[TagEPT, TagEPT\])', [1]),
    
    37
    +     compile,
    
    38
    +     ['-O -fno-worker-wrapper -fno-cpr-anal -fno-full-laziness -ddump-stg-tags '
    
    39
    +      '-dno-typeable-binds -dsuppress-uniques'])
    
    28 40
     
    
    29 41
     test('T24806', grep_errmsg('^\\(T24806\\.\\$wgo'), compile, ['-O -ddump-stg-tags -dno-typeable-binds -dsuppress-uniques'])

  • testsuite/tests/simplStg/should_compile/inferTags005.hs
    1
    +{-# LANGUAGE UnboxedTuples #-}
    
    2
    +module EnforceEpt005 where
    
    3
    +
    
    4
    +-- If we have
    
    5
    +-- f x y =
    
    6
    +--   join j =
    
    7
    +--       <rhs> -- <TagTuple[TagEPT, TagEPT]]>
    
    8
    +--   in case x of
    
    9
    +--       1 -> j
    
    10
    +--       2 -> j
    
    11
    +--       _ ->
    
    12
    +--         <something> -- <TagTuple[TagEPT, TagEPT]]>
    
    13
    +
    
    14
    +-- Consider the case where rhs and something both represent a tag value of <TagTuple[TagEPT, TagEPT]]>
    
    15
    +-- In this case we want `f` to also give rise to TagFun [TagTuple[TagEPT, TagEPT]]]
    
    16
    +
    
    17
    +-- To achieve this we have to recognize j as a zero-arity function equivalent
    
    18
    +-- which is what this test checks.
    
    19
    +
    
    20
    +-- The payoff is in `g` `r` is then known to be tagged, so `S r` needs no eval.
    
    21
    +-- Only an unboxed tuple result exposes this.
    
    22
    +
    
    23
    +-- We disable worker/wrapper and CPR so `f` matches more closely the tests source.
    
    24
    +
    
    25
    +data T = A Int Int Int Int Int Int | B
    
    26
    +
    
    27
    +f :: Int -> Int -> (# T, T #)
    
    28
    +f x y = case x of
    
    29
    +  0 -> j
    
    30
    +  1 -> j
    
    31
    +  _ -> (# B, B #)
    
    32
    +  where
    
    33
    +    j = (# A (y+1) (y*2) (y-3) (y*y) (y+x) (y*x), B #)
    
    34
    +{-# NOINLINE f #-}
    
    35
    +
    
    36
    +data S = S !T
    
    37
    +
    
    38
    +g :: Int -> Int -> S
    
    39
    +g x y = case f x y of (# r, _ #) -> S r

  • testsuite/tests/simplStg/should_compile/inferTags005.stderr
    1
    +TagTuple[TagEPT, TagEPT]
    
    2
    +TagTuple[TagEPT, TagEPT]

  • testsuite/tests/simplStg/should_compile/inferTags006.hs
    1
    +{-# LANGUAGE UnboxedTuples #-}
    
    2
    +module EnforceEpt006 where
    
    3
    +
    
    4
    +-- If we have
    
    5
    +-- f x y =
    
    6
    +--   join j =
    
    7
    +--       <rhs> -- TagBottoming
    
    8
    +--   in case x of
    
    9
    +--       1 -> j
    
    10
    +--       2 -> j
    
    11
    +--       _ ->
    
    12
    +--         <something> -- <TagTuple[TagEPT, TagEPT]]>
    
    13
    +
    
    14
    +-- Similar to inferTage005 we want to give `f` the signature: TagFun <TagTuple[TagEPT, TagEPT]]>
    
    15
    +-- This requires us to give `j` a `TagFun[TagBottoming]` signature, which is safe because
    
    16
    +-- either its execution will diverge, or the resulting tuple *will* have two
    
    17
    +-- tagged values.
    
    18
    +
    
    19
    +data T = A Int Int Int Int Int Int | B
    
    20
    +
    
    21
    +f :: Int -> Int -> (# T, T #)
    
    22
    +-- This case-of-case is just here to be optimized into a nullary join point.
    
    23
    +f x y = case (case x of { 0 -> False; 1 -> False; _ -> True }) of
    
    24
    +  False -> error ("f: bad input " ++ show x ++ " " ++ show y ++ " " ++ show (x*y))
    
    25
    +  True  -> (# A (y+1) (y*2) (y-3) (y*y) (y+x) (y*x), B #)
    
    26
    +{-# NOINLINE f #-}
    
    27
    +
    
    28
    +data S = S !T
    
    29
    +
    
    30
    +g :: Int -> Int -> S
    
    31
    +g x y = case f x y of (# r, _ #) -> S r

  • testsuite/tests/simplStg/should_compile/inferTags006.stderr
    1
    +TagTuple[TagEPT, TagEPT]

  • testsuite/tests/simplStg/should_run/InferTags007_A.hs
    1
    +-- We suppress arity and LfInfo with the option.
    
    2
    +{-# OPTIONS_GHC -fomit-interface-pragmas #-}
    
    3
    +module InferTags007_A (fun, caf, bot, con) where
    
    4
    +
    
    5
    +fun :: Int -> Int
    
    6
    +fun x = x + 1
    
    7
    +{-# NOINLINE fun #-}
    
    8
    +
    
    9
    +-- A CAF/thunk
    
    10
    +caf :: [Int]
    
    11
    +caf = map (* 2) [1 .. 10]
    
    12
    +{-# NOINLINE caf #-}
    
    13
    +
    
    14
    +bot :: Int -> Int
    
    15
    +bot _ = error "bot called"
    
    16
    +{-# NOINLINE bot #-}
    
    17
    +
    
    18
    +con :: Maybe Int
    
    19
    +con = Just 42
    
    20
    +{-# NOINLINE con #-}

  • testsuite/tests/simplStg/should_run/T27831.hs
    1
    +-- Regression test for #27831: a bottoming function stored in a strict
    
    2
    +-- constructor field.
    
    3
    +--
    
    4
    +-- Different parts of the tag inference machinery didn't agree on weither or not
    
    5
    +-- a bottoming function should be treated as a EPT value, leading to a segfault.
    
    6
    +module Main (main) where
    
    7
    +
    
    8
    +import Control.Exception
    
    9
    +import GHC.Exts.Heap (GenClosure(..), getClosureData)
    
    10
    +import System.Exit
    
    11
    +
    
    12
    +data T = MkT Int !(Int -> Int)
    
    13
    +
    
    14
    +t :: T
    
    15
    +t = MkT 1 (\_ -> error "T27831")
    
    16
    +{-# NOINLINE t #-}
    
    17
    +
    
    18
    +f :: () -> T
    
    19
    +f _ = t
    
    20
    +{-# OPAQUE f #-}
    
    21
    +
    
    22
    +main :: IO ()
    
    23
    +main = do
    
    24
    +    c <- getClosureData t
    
    25
    +    case c of
    
    26
    +        ThunkClosure{} -> putStrLn "FAIL: ThunkClosure" >> exitFailure
    
    27
    +        APClosure{}    -> putStrLn "FAIL: APClosure" >> exitFailure
    
    28
    +        _              -> putStrLn "OK: not a thunk"
    
    29
    +
    
    30
    +    case f () of MkT n _ -> print n
    
    31
    +
    
    32
    +    r <- try (evaluate (case t of MkT _ g -> g 1))
    
    33
    +    case r of
    
    34
    +        Left (ErrorCall msg) -> putStrLn ("OK: " ++ msg)
    
    35
    +        Right v              -> putStrLn ("FAIL: got " ++ show v) >> exitFailure

  • testsuite/tests/simplStg/should_run/T27831.stdout
    1
    +OK: not a thunk
    
    2
    +1
    
    3
    +OK: T27831

  • testsuite/tests/simplStg/should_run/T27857.hs
    1
    +{-# LANGUAGE StrictData #-}
    
    2
    +-- Regression test from #27857: We store a function that loops indefinitely
    
    3
    +-- in a strict field.
    
    4
    +module Main (main) where
    
    5
    +import Control.Monad
    
    6
    +
    
    7
    +data Body
    
    8
    +    = BNone
    
    9
    +    | BStream ((Int -> IO ()) -> IO () -> IO ())
    
    10
    +    | BIface (Int -> IO ())
    
    11
    +    | BBuilder String
    
    12
    +    | BFile Int
    
    13
    +
    
    14
    +data Obj = Obj [String] Body Int
    
    15
    +
    
    16
    +newtype Resp = Resp Obj
    
    17
    +
    
    18
    +stream :: Int -> [String] -> ((Int -> IO ()) -> IO () -> IO ()) -> Resp
    
    19
    +stream st hdr b = Resp $ Obj (show st : hdr) (BStream b) 0
    
    20
    +
    
    21
    +noBody :: Int -> [String] -> Resp
    
    22
    +noBody st hdr = Resp $ Obj (show st : hdr) BNone 0
    
    23
    +
    
    24
    +infinite :: Resp
    
    25
    +infinite = stream 200 ["text/plain"] body
    
    26
    +  where
    
    27
    +    body :: (Int -> IO ()) -> IO () -> IO ()
    
    28
    +    body write flush = do
    
    29
    +        let go n = write n *> flush *> go (succ n)
    
    30
    +        go (0 :: Int)
    
    31
    +
    
    32
    +other :: Resp
    
    33
    +other = noBody 404 []
    
    34
    +
    
    35
    +name :: Body -> String
    
    36
    +name b = case b of
    
    37
    +    BNone -> "None"
    
    38
    +    BStream _ -> "Stream"
    
    39
    +    BIface _ -> "Iface"
    
    40
    +    BBuilder _ -> "Builder"
    
    41
    +    BFile _ -> "File"
    
    42
    +
    
    43
    +main :: IO ()
    
    44
    +main = forM_ [("infinite", infinite), ("other", other)] $ \(n, Resp (Obj _ b _)) ->
    
    45
    +    putStrLn $ n ++ "=" ++ name b

  • testsuite/tests/simplStg/should_run/T27857.stdout
    1
    +infinite=Stream
    
    2
    +other=None

  • testsuite/tests/simplStg/should_run/all.T
    ... ... @@ -3,13 +3,15 @@
    3 3
     #	extra run flags
    
    4 4
     #	expected process return value, if not zero
    
    5 5
     
    
    6
    -# Only compile with optimisation
    
    6
    +# Only run optimised code, native and interpreted:
    
    7
    +# We now generate bytecode from STG so we should test native and bytecode.
    
    7 8
     def f( name, opts ):
    
    8
    -  opts.only_ways = ['optasm']
    
    9
    +  opts.only_ways = ['optasm', 'ghci-opt']
    
    9 10
     
    
    10 11
     setTestOpts(f)
    
    11 12
     
    
    12
    -test('T9291', normal, compile_and_run, [''])
    
    13
    +# Breakpoint ticks would break CSE which this tests.
    
    14
    +test('T9291', [omit_ways(['ghci']), extra_hc_opts('-fno-break-points')], compile_and_run, [''])
    
    13 15
     test('T13536', normal, compile_and_run, [''])
    
    14 16
     
    
    15 17
     test('T13536a',
    
    ... ... @@ -20,6 +22,16 @@ test('T13536a',
    20 22
     
    
    21 23
     test('inferTags001', normal, multimod_compile_and_run, ['inferTags001', 'inferTags001_a'])
    
    22 24
     test('T22042', [extra_files(['T22042a.hs']),only_ways('normal'),unless(have_dynamic(), skip)], makefile_test, ['T22042'])
    
    23
    -test('T23783', normal, multimod_compile_and_run, ['T23783', '-O -v0'])
    
    24
    -test('unpack_enum', normal, compile_and_run, [''])
    
    25
    -test('T27005a', normal, compile_and_run, [''])
    25
    +
    
    26
    +# Depends on tag inference being similarly effective for bytecode which it is not yet.
    
    27
    +test('T23783', omit_ghci, multimod_compile_and_run, ['T23783', '-O -v0'])
    
    28
    +
    
    29
    +# Requires UNPACK which needs -O
    
    30
    +test('unpack_enum', omit_ways(['ghci']), compile_and_run, [''])
    
    31
    +
    
    32
    +# Tests that tag inference optimizes well, which is not the case for bytecode or
    
    33
    +# when we insert profiling ticks.
    
    34
    +test('T27005a', omit_ways(ghci_ways + prof_ways), compile_and_run, [''])
    
    35
    +test('T27831', omit_ways(ghci_ways + prof_ways), compile_and_run, [''])
    
    36
    +test('inferTags007', [extra_files(['InferTags007_A.hs'])], multimod_compile_and_run, ['inferTags007', '-O -v0'])
    
    37
    +test('T27857', normal, compile_and_run, [''])

  • testsuite/tests/simplStg/should_run/inferTags007.hs
    1
    +{-# LANGUAGE MagicHash #-}
    
    2
    +-- This test sanity checks the tag info we generate for imported binders which
    
    3
    +-- have no real info in the .hi file.
    
    4
    +--
    
    5
    +-- This is important to avoid running into cases where we optimistically
    
    6
    +-- generated better information based on things like type than the
    
    7
    +-- compiled code actually provided. For example GHCi doesn't ensure tagging
    
    8
    +-- of constructors.
    
    9
    +module Main (main) where
    
    10
    +
    
    11
    +import Control.Exception
    
    12
    +import Control.Monad
    
    13
    +import GHC.Exts (Addr#)
    
    14
    +import GHC.Exts.Heap (GenClosure(..), getClosureData)
    
    15
    +import GHC.Internal.Control.Exception.Base (patError)
    
    16
    +import System.Exit
    
    17
    +
    
    18
    +import qualified InferTags007_A as A
    
    19
    +
    
    20
    +data S a = S !a
    
    21
    +
    
    22
    +data P = MkP !(Addr# -> Int)
    
    23
    +
    
    24
    +-- Put the imported values into a strict field of a top level Con
    
    25
    +con_top_fun :: S (Int -> Int)
    
    26
    +con_top_fun = S A.fun
    
    27
    +{-# NOINLINE con_top_fun #-}
    
    28
    +
    
    29
    +con_top_caf :: S [Int]
    
    30
    +con_top_caf = S A.caf
    
    31
    +{-# NOINLINE con_top_caf #-}
    
    32
    +
    
    33
    +con_top_bot :: S (Int -> Int)
    
    34
    +con_top_bot = S A.bot
    
    35
    +{-# NOINLINE con_top_bot #-}
    
    36
    +
    
    37
    +con_top_con :: S (Maybe Int)
    
    38
    +con_top_con = S A.con
    
    39
    +{-# NOINLINE con_top_con #-}
    
    40
    +
    
    41
    +con_top_pat :: P
    
    42
    +con_top_pat = MkP patError
    
    43
    +{-# NOINLINE con_top_pat #-}
    
    44
    +
    
    45
    +-- Put the imported values into a strict field of a Con inside a rhs
    
    46
    +expr_fun :: () -> S (Int -> Int)
    
    47
    +expr_fun _ = S A.fun
    
    48
    +{-# OPAQUE expr_fun #-}
    
    49
    +
    
    50
    +expr_caf :: () -> S [Int]
    
    51
    +expr_caf _ = S A.caf
    
    52
    +{-# OPAQUE expr_caf #-}
    
    53
    +
    
    54
    +expr_bot :: () -> S (Int -> Int)
    
    55
    +expr_bot _ = S A.bot
    
    56
    +{-# OPAQUE expr_bot #-}
    
    57
    +
    
    58
    +expr_con :: () -> S (Maybe Int)
    
    59
    +expr_con _ = S A.con
    
    60
    +{-# OPAQUE expr_con #-}
    
    61
    +
    
    62
    +-- Actually use the field.
    
    63
    +use_S_head :: S [Int] -> Int
    
    64
    +use_S_head (S xs) = case xs of { (y:_) -> y; [] -> -1 }
    
    65
    +{-# NOINLINE use_S_head #-}
    
    66
    +
    
    67
    +use_S_fromJust :: S (Maybe Int) -> Int
    
    68
    +use_S_fromJust (S m) = case m of { Just x -> x; Nothing -> -1 }
    
    69
    +{-# NOINLINE use_S_fromJust #-}
    
    70
    +
    
    71
    +use_S_apply :: S (Int -> Int) -> Int
    
    72
    +use_S_apply (S f) = f 1
    
    73
    +{-# NOINLINE use_S_apply #-}
    
    74
    +
    
    75
    +checkNotThunk :: String -> a -> IO ()
    
    76
    +checkNotThunk name x = do
    
    77
    +    c <- getClosureData x
    
    78
    +    case c of
    
    79
    +        ThunkClosure{} -> putStrLn ("FAIL: " ++ name ++ " is a thunk") >> exitFailure
    
    80
    +        APClosure{}    -> putStrLn ("FAIL: " ++ name ++ " is an AP") >> exitFailure
    
    81
    +        _              -> pure ()
    
    82
    +
    
    83
    +-- Pattern matching binds the field itself, so no selector thunk is involved.
    
    84
    +checkField :: String -> S a -> IO ()
    
    85
    +checkField name (S x) = checkNotThunk name x
    
    86
    +{-# NOINLINE checkField #-}
    
    87
    +
    
    88
    +main :: IO ()
    
    89
    +main = do
    
    90
    +    -- Check if the pointer in the strict field is a thunk.
    
    91
    +    checkField "con_top_caf" con_top_caf
    
    92
    +    checkField "con_top_fun" con_top_fun
    
    93
    +    checkField "con_top_bot" con_top_bot
    
    94
    +    checkField "con_top_con" con_top_con
    
    95
    +    case con_top_pat of MkP f -> checkNotThunk "con_top_pat" f
    
    96
    +    checkField "expr_caf" (expr_caf ())
    
    97
    +    checkField "expr_fun" (expr_fun ())
    
    98
    +    checkField "expr_bot" (expr_bot ())
    
    99
    +    checkField "expr_con" (expr_con ())
    
    100
    +
    
    101
    +    -- Actually execute a case on the contents of the field.
    
    102
    +    print (use_S_head con_top_caf, use_S_head (expr_caf ()))
    
    103
    +    print (use_S_fromJust con_top_con, use_S_fromJust (expr_con ()))
    
    104
    +    print (use_S_apply con_top_fun, use_S_apply (expr_fun ()))
    
    105
    +    forM_ [con_top_bot, expr_bot ()] $ \s -> do
    
    106
    +        r <- try (evaluate (use_S_apply s))
    
    107
    +        case r of
    
    108
    +            Left (ErrorCall msg) -> putStrLn ("OK: " ++ msg)
    
    109
    +            Right v              -> putStrLn ("FAIL: got " ++ show v) >> exitFailure

  • testsuite/tests/simplStg/should_run/inferTags007.stdout
    1
    +(2,2)
    
    2
    +(42,42)
    
    3
    +(2,2)
    
    4
    +OK: bot called
    
    5
    +OK: bot called