Andreas Klebinger pushed to branch wip/apk/tag-strict-bot at Glasgow Haskell Compiler / GHC
Commits:
-
6a80ad60
by Andreas Klebinger at 2026-10-06T12:54:02+00:00
23 changed files:
- + changelog.d/T27831-bottoming-strict-field
- + changelog.d/T27842-bytecode-imported-tags
- compiler/GHC/Stg/EnforceEpt.hs
- compiler/GHC/Stg/EnforceEpt/Rewrite.hs
- compiler/GHC/Stg/EnforceEpt/Types.hs
- + testsuite/tests/bytecode/T27842.hs
- + testsuite/tests/bytecode/T27842.stdout
- + testsuite/tests/bytecode/T27842A.hs
- + testsuite/tests/bytecode/T27842N.hs
- testsuite/tests/bytecode/all.T
- testsuite/tests/simplStg/should_compile/all.T
- + testsuite/tests/simplStg/should_compile/inferTags005.hs
- + testsuite/tests/simplStg/should_compile/inferTags005.stderr
- + testsuite/tests/simplStg/should_compile/inferTags006.hs
- + testsuite/tests/simplStg/should_compile/inferTags006.stderr
- + testsuite/tests/simplStg/should_run/InferTags007_A.hs
- + testsuite/tests/simplStg/should_run/T27831.hs
- + testsuite/tests/simplStg/should_run/T27831.stdout
- + testsuite/tests/simplStg/should_run/T27857.hs
- + testsuite/tests/simplStg/should_run/T27857.stdout
- testsuite/tests/simplStg/should_run/all.T
- + testsuite/tests/simplStg/should_run/inferTags007.hs
- + testsuite/tests/simplStg/should_run/inferTags007.stdout
Changes:
| 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. |
| 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``. |
| ... | ... | @@ -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 NOT thunks.
|
|
| 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
|
| ... | ... | @@ -593,7 +601,7 @@ initSig (bndr, StgRhsClosure _ _ _ bndrs _ _) |
| 593 | 601 | |
| 594 | 602 | {- Note [Bottom functions are TagBottoming]
|
| 595 | 603 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 596 | -If we have a function with two branches with one
|
|
| 604 | +If we have a function with two branches with one branch
|
|
| 597 | 605 | being bottom, and the other returning a tagged
|
| 598 | 606 | unboxed tuple what is the result? We give it TagBottoming!
|
| 599 | 607 | To answer why consider this function:
|
| ... | ... | @@ -603,21 +611,40 @@ foo x = case x of |
| 603 | 611 | True -> (# True,True #)
|
| 604 | 612 | False -> undefined
|
| 605 | 613 | |
| 606 | -The true branch is obviously tagged. The other branch isn't.
|
|
| 607 | -We want to treat the *result* of foo as tagged as well so that
|
|
| 608 | -the combination of the branches also is tagged if all non-bottom
|
|
| 609 | -branches are tagged.
|
|
| 610 | -This is safe because the function is still always called/entered as long
|
|
| 611 | -as it's applied to arguments. Since the function will never return we can give
|
|
| 612 | -it safely any tag sig we like.
|
|
| 613 | -So we give it TagBottoming, as it allows the combined tag sig of the case expression
|
|
| 614 | -to be the combination of all non-bottoming branches.
|
|
| 614 | +The true branch obviously returns tagged components. The other branch is bottom.
|
|
| 615 | +We want to treat the components in the *result* returned by foo as tagged.
|
|
| 616 | +Because at a call site like:
|
|
| 617 | + |
|
| 618 | + case foo e of
|
|
| 619 | + (# x, y #) ->
|
|
| 620 | + case x of
|
|
| 621 | + ...
|
|
| 622 | + |
|
| 623 | +Either `foo` will have evaluated undefined or blown up. Or we will bind x and y
|
|
| 624 | +to tagged pointers. A fact we take advantage of to optimize `case x of`.
|
|
| 625 | + |
|
| 626 | +The rule is then simple. A combination of the alternatives of the same case has
|
|
| 627 | +tagged components if all non-bottom branches have tagged components.
|
|
| 628 | + |
|
| 629 | +To represent the bottoming alternatives we use TagBottoming. Which says all it's
|
|
| 630 | +components are tagged. Now the same is true if we have:
|
|
| 631 | + |
|
| 632 | +foo :: Bool -> (# Bool, Bool #)
|
|
| 633 | +foo x = case x of
|
|
| 634 | + True -> (# True,True #)
|
|
| 635 | + False -> willCrash x
|
|
| 636 | + |
|
| 637 | +Where we want to check:
|
|
| 615 | 638 | |
| 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:
|
|
| 639 | +* Is `willCrash x` bottom?
|
|
| 640 | +* If it is not are the returned components tagged?
|
|
| 618 | 641 | |
| 619 | - case bottom_id of { ...}
|
|
| 642 | +For the first question we have to be careful about the notion of bottom we use.
|
|
| 643 | +I've got this wrong in the past, because a underapplied bottoming function will
|
|
| 644 | +return a *function* as result. Rather than being bottom.
|
|
| 620 | 645 | |
| 646 | +If the function *is* bottom then we treat it the same we do undefined above. And
|
|
| 647 | +use TagBottoming as the tagInfo of the expression. Hence the title of the Note.
|
|
| 621 | 648 | -}
|
| 622 | 649 | |
| 623 | 650 | -----------------------------
|
| ... | ... | @@ -3,6 +3,7 @@ |
| 3 | 3 | |
| 4 | 4 | {-# LANGUAGE CPP #-}
|
| 5 | 5 | {-# LANGUAGE TypeFamilies #-}
|
| 6 | +{-# LANGUAGE UnboxedTuples #-}
|
|
| 6 | 7 | |
| 7 | 8 | module GHC.Stg.EnforceEpt.Rewrite (rewriteTopBinds, rewriteOpApp)
|
| 8 | 9 | where
|
| ... | ... | @@ -13,7 +14,7 @@ import GHC.Builtin.PrimOps ( PrimOp(..) ) |
| 13 | 14 | import GHC.Types.Basic ( CbvMark (..), isMarkedCbv
|
| 14 | 15 | , TopLevelFlag(..), isTopLevel )
|
| 15 | 16 | import GHC.Types.Id
|
| 16 | -import GHC.Types.Name
|
|
| 17 | +import GHC.Types.Name ( nameIsLocalOrFrom )
|
|
| 17 | 18 | import GHC.Types.Unique.Supply
|
| 18 | 19 | import GHC.Types.Unique.FM
|
| 19 | 20 | import GHC.Types.RepType
|
| ... | ... | @@ -22,10 +23,7 @@ import GHC.Unit.Types |
| 22 | 23 | |
| 23 | 24 | import GHC.Core.DataCon
|
| 24 | 25 | import GHC.Core ( AltCon(..) )
|
| 25 | -import GHC.Core.Type
|
|
| 26 | - |
|
| 27 | -import GHC.StgToCmm.Types
|
|
| 28 | -import GHC.StgToCmm.Closure (importedIdLFInfo)
|
|
| 26 | +import GHC.Core.Type ( definitelyLiftedType )
|
|
| 29 | 27 | |
| 30 | 28 | import GHC.Stg.Utils
|
| 31 | 29 | import GHC.Stg.Syntax as StgSyn
|
| ... | ... | @@ -40,9 +38,39 @@ import GHC.Utils.Misc |
| 40 | 38 | import GHC.Stg.EnforceEpt.Types
|
| 41 | 39 | |
| 42 | 40 | import Control.Monad
|
| 43 | - |
|
| 44 | -newtype RM a = RM { unRM :: (State (UniqFM Id TagSig, UniqSupply, Module, IdSet) a) }
|
|
| 45 | - deriving (Functor, Monad, Applicative)
|
|
| 41 | +import GHC.Exts (oneShot)
|
|
| 42 | + |
|
| 43 | +data RmContext = RmContext { rm_mod :: !Module, rm_interpreter :: !Bool }
|
|
| 44 | + |
|
| 45 | +data RmState = RmState { rm_us :: !UniqSupply, rm_fvs :: IdSet, rm_sig_env :: !(UniqFM Id TagSig)}
|
|
| 46 | +newtype RM a = RM { unRM :: RmContext -> RmState -> (# RmState, a #) }
|
|
| 47 | + |
|
| 48 | +instance Functor RM where
|
|
| 49 | + fmap f (RM x) = RM $ \env -> oneShot $ \state ->
|
|
| 50 | + let (# s1, x' #) = x env state
|
|
| 51 | + in (# s1, f x' #)
|
|
| 52 | + |
|
| 53 | +instance Applicative RM where
|
|
| 54 | + (RM f) <*> (RM x) = RM $ oneShot $ \env -> oneShot $ \state ->
|
|
| 55 | + let (# s1,!f' #) = f env state
|
|
| 56 | + (# s2, x' #) = x env s1
|
|
| 57 | + in (# s2, f' x' #)
|
|
| 58 | + pure x = RM $ \_ s -> (# s, x #)
|
|
| 59 | + |
|
| 60 | +-- See Note [The one-shot state monad trick] in GHC.Utils.Monad
|
|
| 61 | +instance Monad RM where
|
|
| 62 | + RM x >>= f = RM $ oneShot $ \env -> oneShot $ \state ->
|
|
| 63 | + let (# s1, x' #) = x env state
|
|
| 64 | + in case (f x') of RM r -> r env s1
|
|
| 65 | + |
|
| 66 | +-- | runRM this_mod for_bytecode us fvs tag_env
|
|
| 67 | +runRM :: Module -> Bool -> UniqSupply -> RM a -> a
|
|
| 68 | +runRM mod for_bytecode us act =
|
|
| 69 | + case (unRM act)
|
|
| 70 | + (RmContext { rm_mod = mod, rm_interpreter = for_bytecode })
|
|
| 71 | + (RmState { rm_us = us, rm_fvs = emptyVarSet, rm_sig_env = emptyUFM })
|
|
| 72 | + of
|
|
| 73 | + (# _, r #) -> r
|
|
| 46 | 74 | |
| 47 | 75 | ------------------------------------------------------------
|
| 48 | 76 | -- Add cases around strict fields where required.
|
| ... | ... | @@ -107,30 +135,29 @@ when building the free variable list. |
| 107 | 135 | --------------------------------
|
| 108 | 136 | |
| 109 | 137 | instance MonadUnique RM where
|
| 110 | - getUniqueSupplyM = RM $ do
|
|
| 111 | - (m, us, mod,lcls) <- get
|
|
| 112 | - let (us1, us2) = splitUniqSupply us
|
|
| 113 | - (put) (m,us2,mod,lcls)
|
|
| 114 | - return us1
|
|
| 138 | + getUniqueSupplyM = RM $ \_ s ->
|
|
| 139 | + case splitUniqSupply (rm_us s) of
|
|
| 140 | + (us1, us2) -> (# s { rm_us = us2 }, us1 #)
|
|
| 115 | 141 | |
| 116 | 142 | getMap :: RM (UniqFM Id TagSig)
|
| 117 | -getMap = RM $ ((\(fst,_,_,_) -> fst) <$> get)
|
|
| 143 | +getMap = RM $ \_ s -> (# s, rm_sig_env s #)
|
|
| 118 | 144 | |
| 119 | 145 | setMap :: (UniqFM Id TagSig) -> RM ()
|
| 120 | -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)
|
|
| 146 | +setMap !m = RM $ \_ s -> (# s { rm_sig_env = m }, () #)
|
|
| 126 | 147 | |
| 127 | 148 | getFVs :: RM IdSet
|
| 128 | -getFVs = RM $ ((\(_,_,_,lcls) -> lcls) <$> get)
|
|
| 149 | +getFVs = RM $ \_ s -> (# s, rm_fvs s #)
|
|
| 129 | 150 | |
| 130 | 151 | setFVs :: IdSet -> RM ()
|
| 131 | -setFVs !fvs = RM $ do
|
|
| 132 | - (tag_map,us,mod,_lcls) <- get
|
|
| 133 | - put (tag_map, us,mod,fvs)
|
|
| 152 | +setFVs !fvs = RM $ \_ s -> (# s { rm_fvs = fvs }, () #)
|
|
| 153 | + |
|
| 154 | +getMod :: RM Module
|
|
| 155 | +getMod = RM $ \env s -> (# s, rm_mod env #)
|
|
| 156 | + |
|
| 157 | +-- | When generating bytecode certain bindings might be untagged. So we have to
|
|
| 158 | +-- have a way to check for that.
|
|
| 159 | +getForBytecode :: RM Bool
|
|
| 160 | +getForBytecode = RM $ \env s -> (# s, rm_interpreter env #)
|
|
| 134 | 161 | |
| 135 | 162 | -- Rewrite the RHS(s) while making the id and it's sig available
|
| 136 | 163 | -- to determine if things are tagged/need to be captured as FV.
|
| ... | ... | @@ -238,51 +265,26 @@ indicates a bug in the tag inference implementation. |
| 238 | 265 | For this reason we assert that we are running in interactive mode if a lookup fails.
|
| 239 | 266 | -}
|
| 240 | 267 | isTagged :: Id -> RM Bool
|
| 241 | -isTagged v
|
|
| 242 | - -- See Note [Bottom functions are TagBottoming]
|
|
| 243 | - | isDeadEndId v = pure False
|
|
| 244 | - | otherwise = do
|
|
| 268 | +isTagged v = do
|
|
| 269 | + for_bytecode <- getForBytecode
|
|
| 245 | 270 | 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
|
|
| 271 | + s <- getMap
|
|
| 272 | + let get_env_sig = lookupUFM s
|
|
| 273 | + massertPpr
|
|
| 274 | + -- Assert the env is complete for local lifted binders, which means either
|
|
| 275 | + -- - The binder is present in the env
|
|
| 276 | + -- - The binder is not a local lifted binder *or* comes from a ghci context
|
|
| 277 | + (isJust (get_env_sig v)
|
|
| 278 | + -- See Note [Tag inference for interactive contexts]
|
|
| 279 | + || isInteractiveModule this_mod
|
|
| 280 | + || not (nameIsLocalOrFrom this_mod (idName v))
|
|
| 281 | + || not (definitelyLiftedType (idType v)))
|
|
| 282 | + (text "unknown Id:" <> ppr this_mod <+> ppr v)
|
|
| 283 | + -- TODO: We could refactor the whole pass such that we also set id_tagSig
|
|
| 284 | + -- for every imported id. But this likely comes with a heavy allocation
|
|
| 285 | + -- overhead so for now we just ensure to use the same logic across analysis
|
|
| 286 | + -- and rewriter.
|
|
| 287 | + pure $! isTaggedInfo $ argTagInfo for_bytecode (get_env_sig v) (StgVarArg v)
|
|
| 286 | 288 | |
| 287 | 289 | |
| 288 | 290 | isArgTagged :: StgArg -> RM Bool
|
| ... | ... | @@ -299,11 +301,10 @@ mkLocalArgId id = do |
| 299 | 301 | ---------------------------
|
| 300 | 302 | |
| 301 | 303 | |
| 302 | -rewriteTopBinds :: Module -> UniqSupply -> [GenStgTopBinding 'InferTaggedBinders] -> [TgStgTopBinding]
|
|
| 303 | -rewriteTopBinds mod us binds =
|
|
| 304 | - let doBinds = mapM rewriteTop binds
|
|
| 305 | - |
|
| 306 | - in evalState (unRM doBinds) (mempty, us, mod, mempty)
|
|
| 304 | +rewriteTopBinds :: Bool -- ^ Generating bytecode?
|
|
| 305 | + -> Module -> UniqSupply -> [GenStgTopBinding 'InferTaggedBinders] -> [TgStgTopBinding]
|
|
| 306 | +rewriteTopBinds for_bytecode mod us binds =
|
|
| 307 | + runRM mod for_bytecode us (mapM rewriteTop binds)
|
|
| 307 | 308 | |
| 308 | 309 | rewriteTop :: InferStgTopBinding -> RM TgStgTopBinding
|
| 309 | 310 | rewriteTop (StgTopStringLit v s) = return $! (StgTopStringLit v s)
|
| ... | ... | @@ -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,40 @@ 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
|
|
| 132 | + then TagDunno -- See Note [EPT enforcement for interpreted code] in GHC.Stg.EnforceEpt
|
|
| 133 | + else
|
|
| 134 | + case lf_info of
|
|
| 125 | 135 | -- Function, tagged (with arity)
|
| 126 | 136 | LFReEntrant {}
|
| 127 | 137 | -> TagEPT
|
| ... | ... | @@ -138,13 +148,12 @@ lookupInfo env (StgVarArg var) |
| 138 | 148 | -- Shouldn't be possible. I don't think we can export letNoEscapes
|
| 139 | 149 | LFLetNoEscape {} -> panic "LFLetNoEscape exported"
|
| 140 | 150 | |
| 141 | - | otherwise
|
|
| 142 | - = TagDunno
|
|
| 143 | - where
|
|
| 144 | - for_bytecode = te_bytecode env
|
|
| 145 | - |
|
| 146 | -lookupInfo _ (StgLitArg {})
|
|
| 147 | - = TagEPT
|
|
| 151 | +-- | Get tag info using the environment.
|
|
| 152 | +lookupInfo :: TagEnv p -> StgArg -> TagInfo
|
|
| 153 | +lookupInfo env arg@(StgVarArg var) =
|
|
| 154 | + let env_sig = (lookupVarEnv (te_env env) var)
|
|
| 155 | + in argTagInfo (te_bytecode env) env_sig arg
|
|
| 156 | +lookupInfo _env (StgLitArg{}) = TagEPT
|
|
| 148 | 157 | |
| 149 | 158 | isDunnoSig :: TagSig -> Bool
|
| 150 | 159 | isDunnoSig (TagVal TagDunno) = True
|
| 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) |
| 1 | +42
|
|
| 2 | +42
|
|
| 3 | +3 |
| 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 #-} |
| 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 #-} |
| ... | ... | @@ -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']) |
| ... | ... | @@ -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']) |
| 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 |
| 1 | +TagTuple[TagEPT, TagEPT]
|
|
| 2 | +TagTuple[TagEPT, TagEPT] |
| 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 |
| 1 | +TagTuple[TagEPT, TagEPT] |
| 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 #-} |
| 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 |
| 1 | +OK: not a thunk
|
|
| 2 | +1
|
|
| 3 | +OK: T27831 |
| 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 |
| 1 | +infinite=Stream
|
|
| 2 | +other=None |
| ... | ... | @@ -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, ['']) |
| 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 |
| 1 | +(2,2)
|
|
| 2 | +(42,42)
|
|
| 3 | +(2,2)
|
|
| 4 | +OK: bot called
|
|
| 5 | +OK: bot called |