Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
7fe4f2ec
by Luite Stegeman at 2026-06-17T05:35:09-04:00
22 changed files:
- + changelog.d/tag-inference-27005
- compiler/GHC/Core/Utils.hs
- compiler/GHC/Stg/EnforceEpt.hs
- compiler/GHC/Stg/EnforceEpt/Rewrite.hs
- compiler/GHC/Stg/EnforceEpt/TagSig.hs
- compiler/GHC/Stg/EnforceEpt/Types.hs
- testsuite/tests/core-to-stg/T14895.stderr
- testsuite/tests/simplCore/should_compile/T4201.stdout
- + testsuite/tests/simplCore/should_run/T27005.hs
- + testsuite/tests/simplCore/should_run/T27005.stdout
- + testsuite/tests/simplCore/should_run/T27005_aux.hs
- testsuite/tests/simplCore/should_run/all.T
- testsuite/tests/simplStg/should_compile/T24806.hs
- testsuite/tests/simplStg/should_compile/T24806.stderr
- + testsuite/tests/simplStg/should_compile/T27005b.hs
- + testsuite/tests/simplStg/should_compile/T27005b.stderr
- testsuite/tests/simplStg/should_compile/all.T
- testsuite/tests/simplStg/should_compile/inferTags004.hs
- testsuite/tests/simplStg/should_compile/inferTags004.stderr
- + testsuite/tests/simplStg/should_run/T27005a.hs
- + testsuite/tests/simplStg/should_run/T27005a.stdout
- testsuite/tests/simplStg/should_run/all.T
Changes:
| 1 | +section: compiler
|
|
| 2 | +synopsis: Eliminate redundant thunks introduced by tag inference.
|
|
| 3 | +description: {
|
|
| 4 | + Tag inference is now more precise in tracking function results, eliminating
|
|
| 5 | + redundant thunks for certain functions that return an unboxed tuple.
|
|
| 6 | +}
|
|
| 7 | +issues: #27005
|
|
| 8 | +mrs: !15688 |
| ... | ... | @@ -3252,7 +3252,7 @@ That is in STG after tag inference we get: |
| 3252 | 3252 | Str=<1L><ML>,
|
| 3253 | 3253 | Unf=OtherCon []] =
|
| 3254 | 3254 | {} \r [x y]
|
| 3255 | - case x<TagProper> of x' [Occ=Once1] {
|
|
| 3255 | + case x<TagVal[TagEPT]> of x' [Occ=Once1] {
|
|
| 3256 | 3256 | __DEFAULT ->
|
| 3257 | 3257 | case y of y' [Occ=Once1] {
|
| 3258 | 3258 | __DEFAULT ->
|
| ... | ... | @@ -3277,8 +3277,8 @@ But if we add an extra eval on `y` during worker/wrapper we this this: |
| 3277 | 3277 | Str=<1L><!L>,
|
| 3278 | 3278 | Unf=OtherCon []] =
|
| 3279 | 3279 | {} \r [x y]
|
| 3280 | - case y<TagProper> of y' [Occ=Once1] { __DEFAULT ->
|
|
| 3281 | - case x<TagProper> of x' [Occ=Once1] {
|
|
| 3280 | + case y<TagVal[TagEPT]> of y' [Occ=Once1] { __DEFAULT ->
|
|
| 3281 | + case x<TagVal[TagEPT]> of x' [Occ=Once1] {
|
|
| 3282 | 3282 | __DEFAULT ->
|
| 3283 | 3283 | case Find.$wmyPred y' of pred_y [Occ=Once1] {
|
| 3284 | 3284 | __DEFAULT ->
|
| ... | ... | @@ -40,9 +40,6 @@ A pointer is Evaluated and Properly Tagged (EPT) when the pointer |
| 40 | 40 | |
| 41 | 41 | A binder is EPT when all the runtime pointers it binds are EPT.
|
| 42 | 42 | |
| 43 | -Note that a lifted EPT pointer will never point to a thunk, nor will it be
|
|
| 44 | -tagged `000` (meaning "might be a thunk").
|
|
| 45 | - |
|
| 46 | 43 | See https://gitlab.haskell.org/ghc/ghc/-/wikis/commentary/rts/haskell-execution/pointer-tagging
|
| 47 | 44 | for more information on pointer tagging.
|
| 48 | 45 | |
| ... | ... | @@ -57,11 +54,15 @@ Examples: |
| 57 | 54 | * In practice, GHC also guarantees that strict fields (and others) are EPT;
|
| 58 | 55 | see Note [EPT enforcement].
|
| 59 | 56 | |
| 60 | -Caveat:
|
|
| 61 | -Currently, the proper tag for builtin *unlifted* data types such as `Array#` is
|
|
| 62 | -not `001` but `000`, which is not a proper tag for lifted data.
|
|
| 63 | -This means that UnliftedRep is not a proper sub-rep of LiftedRep.
|
|
| 64 | -SG thinks it would be good to fix this; see #21792.
|
|
| 57 | +Wrinkles:
|
|
| 58 | + |
|
| 59 | +EPT1: The proper tag for builtin *unlifted* data types such as `Array#` is
|
|
| 60 | + not `001` but `000`, which is not a proper tag for lifted data.
|
|
| 61 | + This means that UnliftedRep is not a proper sub-rep of LiftedRep.
|
|
| 62 | + SG thinks it would be good to fix this; see #21792.
|
|
| 63 | +EPT2: The proper tag for PAPs is `000`, so there are values from lifted data types
|
|
| 64 | + that are EPT with a zero tag.
|
|
| 65 | +EPT3: Non-pointer values don't have a tag at all, but we still treat them as EPT.
|
|
| 65 | 66 | |
| 66 | 67 | Note [EPT enforcement]
|
| 67 | 68 | ~~~~~~~~~~~~~~~~~~~~~~
|
| ... | ... | @@ -261,18 +262,39 @@ might destroy the EPT Invariant, hence we need to enforce the EPT invariant |
| 261 | 262 | |
| 262 | 263 | {- Note [TagInfo of functions]
|
| 263 | 264 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 264 | -The purpose of tag inference is really to figure out when we don't have to enter
|
|
| 265 | -value closures. There the meaning of the tag is fairly obvious.
|
|
| 266 | -For functions we never make use of the tag info so we have two choices:
|
|
| 267 | -* Treat them as TagDunno
|
|
| 268 | -* Treat them as TagProper (as they *are* tagged with their arity) and be really
|
|
| 269 | - careful to make sure we still enter them when needed.
|
|
| 270 | -As it makes little difference for runtime performance I've treated functions as TagDunno in a few places where
|
|
| 271 | -it made the code simpler. But besides implementation complexity there isn't any reason
|
|
| 272 | -why we couldn't be more rigorous in dealing with functions.
|
|
| 273 | - |
|
| 274 | -NB: It turned in #21193 that PAPs get tag zero, so the tag check can't be omitted for functions.
|
|
| 275 | -So option two isn't really an option without reworking this anyway.
|
|
| 265 | +'TagFun' represents an evaluated function and carries the return-value TagInfo
|
|
| 266 | +for saturated calls. See Note [TagSig and TagInfo] in GHC.Stg.EnforceEpt.TagSig.
|
|
| 267 | + |
|
| 268 | +Join points (StgLetNoEscape) are treated like functions: they are jumped to,
|
|
| 269 | +never entered via a closure pointer. Even nullary join points get TagFun
|
|
| 270 | +rather than TagVal TagDunno, so their return-value info flows through.
|
|
| 271 | + |
|
| 272 | +Additionally we also special case bottoming functions.
|
|
| 273 | +See Note [Bottom functions are TagBottoming].
|
|
| 274 | + |
|
| 275 | +Function values when properly tagged have either their arity, 1 for very large
|
|
| 276 | +arities, or zero for PAPs (#21193) as tag. However the runtime currently doesn't
|
|
| 277 | +maintain the EPT status of function pointers. So while we keep track of
|
|
| 278 | +evaluated function binders using 'TagFun' this has a weaker guarantee than for
|
|
| 279 | +values. For functions it only represents a pointer to an evaluated function, but
|
|
| 280 | +doesn't give any guarantee about the function's pointer tag.
|
|
| 281 | + |
|
| 282 | +Sadly because PAPs get tag zero (#21193), other issues in the RTS (#21193) and
|
|
| 283 | +the potential of over/underapplication of arguments, even evaluated function
|
|
| 284 | +binders still require inspection of their closure before they can be applied.
|
|
| 285 | +That is, even if we have
|
|
| 286 | + |
|
| 287 | + case f of f' -> ... f' x ...
|
|
| 288 | + |
|
| 289 | +the code for `f' x` can't be made meaningfully more efficient by knowing `f'` is
|
|
| 290 | +already evaluated.
|
|
| 291 | + |
|
| 292 | +However tracking evaluation status for functions is still worthwhile, as we rely
|
|
| 293 | +on the EPT pass to force strict constructor fields. For an application
|
|
| 294 | +`StrictJust f` we would usually insert an eval of `f` around the constructor
|
|
| 295 | +application. However if we infer `f` to be already evaluated we can avoid this,
|
|
| 296 | +which was a significant performance benefit in #27005. Similarly we can still
|
|
| 297 | +turn redundant eval-cases on functions into a no-op.
|
|
| 276 | 298 | |
| 277 | 299 | Note [EPT enforcement debugging]
|
| 278 | 300 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... | ... | @@ -361,7 +383,6 @@ type InferExtEq i = ( XLet i ~ XLet 'InferTaggedBinders |
| 361 | 383 | |
| 362 | 384 | inferTags :: Bool -> [GenStgTopBinding 'CodeGen] -> [GenStgTopBinding 'InferTaggedBinders]
|
| 363 | 385 | inferTags for_bytecode binds =
|
| 364 | - -- pprTrace "Binds" (pprGenStgTopBindings shortStgPprOpts $ binds) $
|
|
| 365 | 386 | snd (mapAccumL inferTagTopBind (initEnv for_bytecode) binds)
|
| 366 | 387 | |
| 367 | 388 | -----------------------
|
| ... | ... | @@ -389,18 +410,20 @@ inferTagExpr env (StgApp fun args) |
| 389 | 410 | !fun_arity = idArity fun
|
| 390 | 411 | info
|
| 391 | 412 | -- It's important that we check for bottoms before all else.
|
| 392 | - -- See Note [Bottom functions are TagTagged] and #24806 for why.
|
|
| 413 | + -- See Note [Bottom functions are TagBottoming] and #24806 for why.
|
|
| 393 | 414 | | isDeadEndAppSig (idDmdSig fun) (length args)
|
| 394 | - = TagTagged
|
|
| 415 | + = TagBottoming
|
|
| 395 | 416 | |
| 396 | 417 | | fun_arity == 0 -- Unknown arity => Thunk or unknown call
|
| 397 | 418 | = TagDunno
|
| 398 | 419 | |
| 399 | - | Just (TagSig res_info) <- tagSigInfo (idInfo fun)
|
|
| 420 | + -- Imported function with known return tag
|
|
| 421 | + | Just (TagFun res_info) <- tagSigInfo (idInfo fun)
|
|
| 400 | 422 | , fun_arity == length args -- Saturated
|
| 401 | 423 | = res_info
|
| 402 | 424 | |
| 403 | - | Just (TagSig res_info) <- lookupSig env fun
|
|
| 425 | + -- Local function with known return tag
|
|
| 426 | + | Just res_info <- lookupReturnInfo env fun
|
|
| 404 | 427 | , fun_arity == length args -- Saturated
|
| 405 | 428 | = res_info
|
| 406 | 429 | |
| ... | ... | @@ -412,7 +435,7 @@ inferTagExpr env (StgConApp con cn args tys) |
| 412 | 435 | = (inferConTag env con args, StgConApp con cn args tys)
|
| 413 | 436 | |
| 414 | 437 | inferTagExpr _ (StgLit l)
|
| 415 | - = (TagTagged, StgLit l)
|
|
| 438 | + = (TagEPT, StgLit l)
|
|
| 416 | 439 | |
| 417 | 440 | inferTagExpr env (StgTick tick body)
|
| 418 | 441 | = (info, StgTick tick body')
|
| ... | ... | @@ -446,7 +469,7 @@ inferTagExpr in_env (StgCase scrut bndr ty alts) |
| 446 | 469 | mk_bndr :: BinderP p -> TagInfo -> (Id, TagSig)
|
| 447 | 470 | mk_bndr tup_bndr tup_info =
|
| 448 | 471 | -- pprTrace "mk_ubx_bndr_info" ( ppr bndr <+> ppr info ) $
|
| 449 | - (getBinderId in_env tup_bndr, TagSig tup_info)
|
|
| 472 | + (getBinderId in_env tup_bndr, TagVal tup_info)
|
|
| 450 | 473 | -- no case binder in alt_env here, unboxed tuple binders are dead after unarise
|
| 451 | 474 | alt_env = extendSigEnv in_env bndrs'
|
| 452 | 475 | (info, rhs') = inferTagExpr alt_env rhs
|
| ... | ... | @@ -478,16 +501,16 @@ inferTagExpr in_env (StgCase scrut bndr ty alts) |
| 478 | 501 | , let (alt_env,bndrs') = addAltBndrInfo case_env con bndrs
|
| 479 | 502 | (info, rhs') = inferTagExpr alt_env rhs
|
| 480 | 503 | ]
|
| 481 | - alt_info = foldr combineAltInfo TagTagged infos
|
|
| 504 | + alt_info = foldr combineAltInfo TagBottoming infos
|
|
| 482 | 505 | in ( alt_info, StgCase scrut' bndr' ty alts')
|
| 483 | 506 | where
|
| 484 | 507 | -- Single unboxed tuple alternative
|
| 485 | 508 | scrut_infos bndrs = case scrut_info of
|
| 486 | - TagTagged -> Just $ replicate (length bndrs) TagProper
|
|
| 509 | + TagBottoming -> Just $ replicate (length bndrs) TagBottoming
|
|
| 487 | 510 | TagTuple infos -> Just infos
|
| 488 | 511 | _ -> Nothing
|
| 489 | 512 | (scrut_info, scrut') = inferTagExpr in_env scrut
|
| 490 | - bndr' = (getBinderId in_env bndr, TagSig TagProper)
|
|
| 513 | + bndr' = (getBinderId in_env bndr, TagVal TagEPT)
|
|
| 491 | 514 | |
| 492 | 515 | -- Compute binder sigs based on the constructors strict fields.
|
| 493 | 516 | -- NB: Not used if we have tuple info from the scrutinee.
|
| ... | ... | @@ -503,7 +526,7 @@ addAltBndrInfo env (DataAlt con) bndrs |
| 503 | 526 | mk_bndr :: (BinderP p -> StrictnessMark -> (Id, TagSig))
|
| 504 | 527 | mk_bndr bndr mark
|
| 505 | 528 | | isUnliftedType (idType id) || isMarkedStrict mark
|
| 506 | - = (id, TagSig TagProper)
|
|
| 529 | + = (id, TagVal TagEPT)
|
|
| 507 | 530 | | otherwise
|
| 508 | 531 | = noSig env bndr
|
| 509 | 532 | where
|
| ... | ... | @@ -523,7 +546,7 @@ inferTagBind in_env (StgNonRec bndr rhs) |
| 523 | 546 | (env', StgNonRec (id, out_sig) rhs')
|
| 524 | 547 | where
|
| 525 | 548 | id = getBinderId in_env bndr
|
| 526 | - (in_sig,rhs') = inferTagRhs id in_env rhs
|
|
| 549 | + (in_sig, rhs') = inferTagRhs id in_env rhs
|
|
| 527 | 550 | out_sig = mkLetSig in_env in_sig
|
| 528 | 551 | env' = extendSigEnv in_env [(id, out_sig)]
|
| 529 | 552 | |
| ... | ... | @@ -536,13 +559,12 @@ inferTagBind in_env (StgRec pairs) |
| 536 | 559 | init_sigs = map (initSig) $ zip in_ids rhss
|
| 537 | 560 | (out_env, pairs') = go in_env init_sigs rhss
|
| 538 | 561 | |
| 539 | - go :: forall q. (OutputableInferPass q , InferExtEq q) => TagEnv q -> [TagSig] -> [GenStgRhs q]
|
|
| 540 | - -> (TagSigEnv, [((Id,TagSig), GenStgRhs 'InferTaggedBinders)])
|
|
| 562 | + go :: forall q. (OutputableInferPass q , InferExtEq q)
|
|
| 563 | + => TagEnv q -> [TagSig] -> [GenStgRhs q]
|
|
| 564 | + -> (TagSigEnv, [((Id,TagSig), GenStgRhs 'InferTaggedBinders)])
|
|
| 541 | 565 | go go_env in_sigs go_rhss
|
| 542 | - -- | pprTrace "go" (ppr in_ids $$ ppr in_sigs $$ ppr out_sigs $$ ppr rhss') False
|
|
| 543 | - -- = undefined
|
|
| 544 | 566 | | in_sigs == out_sigs = (te_env rhs_env, out_bndrs `zip` rhss')
|
| 545 | - | otherwise = go env' out_sigs rhss'
|
|
| 567 | + | otherwise = go env' out_sigs rhss'
|
|
| 546 | 568 | where
|
| 547 | 569 | in_bndrs = in_ids `zip` in_sigs
|
| 548 | 570 | out_bndrs = map updateBndr in_bndrs -- TODO: Keeps in_ids alive
|
| ... | ... | @@ -552,25 +574,28 @@ inferTagBind in_env (StgRec pairs) |
| 552 | 574 | |
| 553 | 575 | anaRhs :: Id -> GenStgRhs q -> (TagSig, GenStgRhs 'InferTaggedBinders)
|
| 554 | 576 | anaRhs bnd rhs =
|
| 555 | - let (sig_rhs,rhs') = inferTagRhs bnd rhs_env rhs
|
|
| 577 | + let (sig_rhs, rhs') = inferTagRhs bnd rhs_env rhs
|
|
| 556 | 578 | in (mkLetSig go_env sig_rhs, rhs')
|
| 557 | 579 | |
| 558 | 580 | |
| 559 | 581 | updateBndr :: (Id,TagSig) -> (Id,TagSig)
|
| 560 | 582 | updateBndr (v,sig) = (setIdTagSig v sig, sig)
|
| 561 | 583 | |
| 584 | +-- Initial signature for the fixpoint loop.
|
|
| 562 | 585 | initSig :: forall p. (Id, GenStgRhs p) -> TagSig
|
| 563 | --- Initial signature for the fixpoint loop
|
|
| 564 | -initSig (_bndr, StgRhsCon {}) = TagSig TagTagged
|
|
| 565 | -initSig (bndr, StgRhsClosure _ _ _ _ _ _) =
|
|
| 566 | - fromMaybe defaultSig (idTagSig_maybe bndr)
|
|
| 567 | - where defaultSig = (TagSig TagTagged)
|
|
| 568 | - |
|
| 569 | -{- Note [Bottom functions are TagTagged]
|
|
| 586 | +initSig (_bndr, StgRhsCon {})
|
|
| 587 | + = TagVal TagBottoming
|
|
| 588 | +initSig (bndr, StgRhsClosure _ _ _ bndrs _ _)
|
|
| 589 | + | notNull bndrs || isJoinId bndr
|
|
| 590 | + = fromMaybe (TagFun TagBottoming) (idTagSig_maybe bndr)
|
|
| 591 | + | otherwise -- thunk
|
|
| 592 | + = fromMaybe (TagVal TagBottoming) (idTagSig_maybe bndr)
|
|
| 593 | + |
|
| 594 | +{- Note [Bottom functions are TagBottoming]
|
|
| 570 | 595 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 571 | 596 | If we have a function with two branches with one
|
| 572 | 597 | being bottom, and the other returning a tagged
|
| 573 | -unboxed tuple what is the result? We give it TagTagged!
|
|
| 598 | +unboxed tuple what is the result? We give it TagBottoming!
|
|
| 574 | 599 | To answer why consider this function:
|
| 575 | 600 | |
| 576 | 601 | foo :: Bool -> (# Bool, Bool #)
|
| ... | ... | @@ -585,7 +610,7 @@ branches are tagged. |
| 585 | 610 | This is safe because the function is still always called/entered as long
|
| 586 | 611 | as it's applied to arguments. Since the function will never return we can give
|
| 587 | 612 | it safely any tag sig we like.
|
| 588 | -So we give it TagTagged, as it allows the combined tag sig of the case expression
|
|
| 613 | +So we give it TagBottoming, as it allows the combined tag sig of the case expression
|
|
| 589 | 614 | to be the combination of all non-bottoming branches.
|
| 590 | 615 | |
| 591 | 616 | NB: After the analysis is done we go back to treating bottoming functions as
|
| ... | ... | @@ -603,13 +628,20 @@ inferTagRhs :: forall p. |
| 603 | 628 | -> GenStgRhs p -- ^
|
| 604 | 629 | -> (TagSig, GenStgRhs 'InferTaggedBinders)
|
| 605 | 630 | inferTagRhs bnd_id in_env (StgRhsClosure ext cc upd bndrs body typ)
|
| 606 | - | isDeadEndId bnd_id && (notNull) bndrs
|
|
| 607 | - -- See Note [Bottom functions are TagTagged]
|
|
| 608 | - = (TagSig TagTagged, StgRhsClosure ext cc upd out_bndrs body' typ)
|
|
| 609 | - | otherwise
|
|
| 610 | - = --pprTrace "inferTagRhsClosure" (ppr (_top, _grp_ids, env,info')) $
|
|
| 611 | - (TagSig info', StgRhsClosure ext cc upd out_bndrs body' typ)
|
|
| 631 | + | isDeadEndId bnd_id && is_fun_or_join
|
|
| 632 | + -- See Note [Bottom functions are TagBottoming]
|
|
| 633 | + = ( TagFun TagBottoming
|
|
| 634 | + , StgRhsClosure ext cc upd out_bndrs body' typ)
|
|
| 635 | + | is_fun_or_join
|
|
| 636 | + = ( TagFun info
|
|
| 637 | + , StgRhsClosure ext cc upd out_bndrs body' typ)
|
|
| 638 | + | otherwise -- thunk (never a join point)
|
|
| 639 | + = ( TagVal TagDunno
|
|
| 640 | + , StgRhsClosure ext cc upd out_bndrs body' typ)
|
|
| 612 | 641 | where
|
| 642 | + -- Join points are treated like functions. See Note [TagInfo of functions]
|
|
| 643 | + is_fun_or_join = notNull bndrs || isJoinId bnd_id
|
|
| 644 | + |
|
| 613 | 645 | out_bndrs
|
| 614 | 646 | | Just marks <- idCbvMarks_maybe bnd_id
|
| 615 | 647 | -- Sometimes an we eta-expand foo with additional arguments after ww, and we also trim
|
| ... | ... | @@ -620,37 +652,29 @@ inferTagRhs bnd_id in_env (StgRhsClosure ext cc upd bndrs body typ) |
| 620 | 652 | |
| 621 | 653 | env' = extendSigEnv in_env out_bndrs
|
| 622 | 654 | (info, body') = inferTagExpr env' body
|
| 623 | - info'
|
|
| 624 | - -- It's a thunk
|
|
| 625 | - | null bndrs
|
|
| 626 | - = TagDunno
|
|
| 627 | - -- TODO: We could preserve tuple fields for thunks
|
|
| 628 | - -- as well. But likely not worth the complexity.
|
|
| 629 | - |
|
| 630 | - | otherwise = info
|
|
| 631 | 655 | |
| 632 | 656 | mkArgSig :: BinderP p -> CbvMark -> (Id,TagSig)
|
| 633 | 657 | mkArgSig bndp mark =
|
| 634 | 658 | let id = getBinderId in_env bndp
|
| 635 | 659 | tag = case mark of
|
| 636 | - MarkedCbv -> TagProper
|
|
| 660 | + MarkedCbv -> TagEPT
|
|
| 637 | 661 | _
|
| 638 | - | isUnliftedType (idType id) -> TagProper
|
|
| 662 | + | isUnliftedType (idType id) -> TagEPT
|
|
| 639 | 663 | | otherwise -> TagDunno
|
| 640 | - in (id, TagSig tag)
|
|
| 664 | + in (id, TagVal tag)
|
|
| 641 | 665 | |
| 642 | 666 | inferTagRhs _ env _rhs@(StgRhsCon cc con cn ticks args typ)
|
| 643 | 667 | -- Constructors, which have untagged arguments to strict fields
|
| 644 | 668 | -- become thunks. We encode this by giving changing RhsCon nodes the info TagDunno
|
| 645 | 669 | = --pprTrace "inferTagRhsCon" (ppr grp_ids) $
|
| 646 | - (TagSig (inferConTag env con args), StgRhsCon cc con cn ticks args typ)
|
|
| 670 | + (TagVal (inferConTag env con args), StgRhsCon cc con cn ticks args typ)
|
|
| 647 | 671 | |
| 648 | 672 | -- Adjust let semantics to the targeted backend.
|
| 649 | 673 | -- See Note [EPT enforcement for interpreted code]
|
| 650 | 674 | mkLetSig :: TagEnv p -> TagSig -> TagSig
|
| 651 | 675 | mkLetSig env in_sig
|
| 652 | - | for_bytecode = TagSig TagDunno
|
|
| 653 | - | otherwise = in_sig
|
|
| 676 | + | for_bytecode = TagVal TagDunno
|
|
| 677 | + | otherwise = in_sig
|
|
| 654 | 678 | where
|
| 655 | 679 | for_bytecode = te_bytecode env
|
| 656 | 680 | |
| ... | ... | @@ -658,52 +682,53 @@ mkLetSig env in_sig |
| 658 | 682 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 659 | 683 | @inferConTag@ will infer the proper tag signature for a binding who's RHS is a constructor
|
| 660 | 684 | or a StgConApp expression.
|
| 661 | -Usually these will simply be TagProper. But there are exceptions.
|
|
| 685 | +Usually these will simply be TagEPT. But there are exceptions.
|
|
| 662 | 686 | If any of the fields in the constructor are strict, but any argument to these
|
| 663 | 687 | fields is not tagged then we will have to case on the argument before storing
|
| 664 | 688 | in the constructor. Which means for let bindings the RHS turns into a thunk
|
| 665 | 689 | which obviously is no longer properly tagged.
|
| 666 | 690 | For example we might start with:
|
| 667 | 691 | |
| 668 | - let x<TagDunno> = f ...
|
|
| 669 | - let c<TagProper> = StrictPair x True
|
|
| 692 | + let x<TagVal[TagDunno]> = f ...
|
|
| 693 | + let c<TagVal[TagEPT]> = StrictPair x True
|
|
| 670 | 694 | |
| 671 | 695 | But we know during the rewrite stage x will need to be evaluated in the RHS
|
| 672 | 696 | of `c` so we will infer:
|
| 673 | 697 | |
| 674 | - let x<TagDunno> = f ...
|
|
| 675 | - let c<TagDunno> = StrictPair x True
|
|
| 698 | + let x<TagVal[TagDunno]> = f ...
|
|
| 699 | + let c<TagVal[TagDunno]> = StrictPair x True
|
|
| 676 | 700 | |
| 677 | 701 | Which in the rewrite stage will then be rewritten into:
|
| 678 | 702 | |
| 679 | - let x<TagDunno> = f ...
|
|
| 680 | - let c<TagDunno> = case x of x' -> StrictPair x' True
|
|
| 703 | + let x<TagVal[TagDunno]> = f ...
|
|
| 704 | + let c<TagVal[TagDunno]> = case x of x' -> StrictPair x' True
|
|
| 681 | 705 | |
| 682 | 706 | The other exception is unboxed tuples. These will get a TagTuple
|
| 683 | 707 | signature with a list of TagInfo about their individual binders
|
| 684 | 708 | as argument. As example:
|
| 685 | 709 | |
| 686 | - let c<TagProper> = True
|
|
| 687 | - let x<TagDunno> = ...
|
|
| 688 | - let f<?> z = case z of z'<TagProper> -> (# c, x #)
|
|
| 710 | + let c<TagVal[TagEPT]> = True
|
|
| 711 | + let x<TagVal[TagDunno]> = ...
|
|
| 712 | + let f<?> z = case z of z'<TagVal[TagEPT]> -> (# c, x #)
|
|
| 689 | 713 | |
| 690 | -Here we will infer for f the Signature <TagTuple[TagProper,TagDunno]>.
|
|
| 714 | +Here we will infer for f the Signature <TagFun[TagTuple[TagEPT,TagDunno]]>.
|
|
| 691 | 715 | This information will be used if we scrutinize a saturated application of
|
| 692 | 716 | `f` in order to determine the taggedness of the result.
|
| 693 | 717 | That is for `case f x of (# r1,r2 #) -> rhs` we can infer
|
| 694 | -r1<TagProper> and r2<TagDunno> which allows us to skip all tag checks on `r1`
|
|
| 718 | +r1<TagVal[TagEPT]> and r2<TagVal[TagDunno]> which allows us to skip all tag checks on `r1`
|
|
| 695 | 719 | in `rhs`.
|
| 696 | 720 | |
| 697 | 721 | Things get a bit more complicated with nesting:
|
| 698 | 722 | |
| 699 | - let closeFd<TagTuple[...]> = ...
|
|
| 723 | + let closeFd<TagFun[...]> = ...
|
|
| 700 | 724 | let f x = ...
|
| 701 | 725 | case x of
|
| 702 | 726 | _ -> Solo# closeFd
|
| 703 | 727 | |
| 704 | -The "natural" signature for the Solo# branch in `f` would be <TagTuple[TagTuple[...]]>.
|
|
| 705 | -But we flatten this out to <TagTuple[TagDunno]> for the time being as it improves compile
|
|
| 706 | -time and there doesn't seem to huge benefit to doing differently.
|
|
| 728 | +We only keep track of function return types for bindings, not values:
|
|
| 729 | +TagInfo has no constructor for "function with return info". Therefore the
|
|
| 730 | +signature of `f` becomes <TagFun[TagTuple[TagEPT]]>. We lose the information
|
|
| 731 | +about the return type of `closeFd`.
|
|
| 707 | 732 | |
| 708 | 733 | -}
|
| 709 | 734 | |
| ... | ... | @@ -721,7 +746,7 @@ inferConTag env con args |
| 721 | 746 | -- text "info:" <> ppr info) $
|
| 722 | 747 | info
|
| 723 | 748 | where
|
| 724 | - info = if any arg_needs_eval strictArgs then TagDunno else TagProper
|
|
| 749 | + info = if any arg_needs_eval strictArgs then TagDunno else TagEPT
|
|
| 725 | 750 | strictArgs = zipEqual args (dataConRuntimeRepStrictness con) :: ([(StgArg, StrictnessMark)])
|
| 726 | 751 | arg_needs_eval (arg,strict)
|
| 727 | 752 | -- lazy args
|
| ... | ... | @@ -730,10 +755,10 @@ inferConTag env con args |
| 730 | 755 | -- banged args need to be tagged, or require eval
|
| 731 | 756 | = not (isTaggedInfo tag)
|
| 732 | 757 | |
| 733 | - flatten_arg_tag (TagTagged) = TagProper
|
|
| 734 | - flatten_arg_tag (TagProper ) = TagProper
|
|
| 735 | - flatten_arg_tag (TagTuple _) = TagDunno -- See Note [Constructor TagSigs]
|
|
| 736 | - flatten_arg_tag (TagDunno) = TagDunno
|
|
| 758 | + flatten_arg_tag TagBottoming = TagEPT
|
|
| 759 | + flatten_arg_tag TagEPT = TagEPT
|
|
| 760 | + flatten_arg_tag (TagTuple _) = TagDunno -- See Note [Constructor TagSigs]
|
|
| 761 | + flatten_arg_tag TagDunno = TagDunno
|
|
| 737 | 762 | |
| 738 | 763 | |
| 739 | 764 | collectExportInfo :: [GenStgTopBinding 'InferTaggedBinders] -> NameEnv TagSig
|
| ... | ... | @@ -746,15 +771,14 @@ collectExportInfo binds = |
| 746 | 771 | collect (StgTopLifted bnd) =
|
| 747 | 772 | case bnd of
|
| 748 | 773 | StgNonRec (id,sig) _rhs
|
| 749 | - | TagSig TagDunno <- sig -> []
|
|
| 774 | + | TagVal TagDunno <- sig -> []
|
|
| 775 | + | TagVal TagBottoming <- sig -> []
|
|
| 750 | 776 | | otherwise -> [(idName id,sig)]
|
| 751 | 777 | StgRec bnds -> collectRec bnds
|
| 752 | 778 | |
| 753 | 779 | collectRec :: [(BinderP 'InferTaggedBinders, rhs)] -> [(Name,TagSig)]
|
| 754 | 780 | collectRec [] = []
|
| 755 | - collectRec (bnd:bnds)
|
|
| 756 | - | (p,_rhs) <- bnd
|
|
| 757 | - , (id,sig) <- p
|
|
| 758 | - , TagSig TagDunno <- sig
|
|
| 759 | - = (idName id,sig) : collectRec bnds
|
|
| 760 | - | otherwise = collectRec bnds |
|
| 781 | + collectRec (((id,sig), _rhs) : bnds)
|
|
| 782 | + | TagVal TagDunno <- sig = collectRec bnds
|
|
| 783 | + | TagVal TagBottoming <- sig = collectRec bnds
|
|
| 784 | + | otherwise = (idName id, sig) : collectRec bnds |
| ... | ... | @@ -239,14 +239,14 @@ For this reason we assert that we are running in interactive mode if a lookup fa |
| 239 | 239 | -}
|
| 240 | 240 | isTagged :: Id -> RM Bool
|
| 241 | 241 | isTagged v
|
| 242 | - -- See Note [Bottom functions are TagTagged]
|
|
| 242 | + -- See Note [Bottom functions are TagBottoming]
|
|
| 243 | 243 | | isDeadEndId v = pure False
|
| 244 | 244 | | otherwise = do
|
| 245 | 245 | this_mod <- getMod
|
| 246 | 246 | -- See Note [Tag inference for interactive contexts]
|
| 247 | 247 | let lookupDefault v = assertPpr (isInteractiveModule this_mod)
|
| 248 | 248 | (text "unknown Id:" <> ppr this_mod <+> ppr v)
|
| 249 | - (TagSig TagDunno)
|
|
| 249 | + (TagVal TagDunno)
|
|
| 250 | 250 | case nameIsLocalOrFrom this_mod (idName v) of
|
| 251 | 251 | True
|
| 252 | 252 | | definitelyUnliftedType (idType v)
|
| ... | ... | @@ -257,12 +257,11 @@ isTagged v |
| 257 | 257 | !s <- getMap
|
| 258 | 258 | let !sig = lookupWithDefaultUFM s (lookupDefault v) v
|
| 259 | 259 | return $ case sig of
|
| 260 | - TagSig info ->
|
|
| 261 | - case info of
|
|
| 262 | - TagDunno -> False
|
|
| 263 | - TagProper -> True
|
|
| 264 | - TagTagged -> True
|
|
| 265 | - TagTuple _ -> True -- Consider unboxed tuples tagged.
|
|
| 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.
|
|
| 266 | 265 | -- Imported
|
| 267 | 266 | False -> return $!
|
| 268 | 267 | -- Determine whether it is tagged from the LFInfo of the imported id.
|
| ... | ... | @@ -279,7 +278,7 @@ isTagged v |
| 279 | 278 | -> True
|
| 280 | 279 | LFUnknown {}
|
| 281 | 280 | -> False
|
| 282 | - LFUnlifted {}
|
|
| 281 | + LFUnlifted {} -- Unboxed, tagging irrelevant
|
|
| 283 | 282 | -> True
|
| 284 | 283 | LFLetNoEscape {}
|
| 285 | 284 | -- Shouldn't be possible. I don't think we can export letNoEscapes
|
| ... | ... | @@ -385,7 +384,7 @@ rewriteArg (lit@StgLitArg{}) = return lit |
| 385 | 384 | rewriteId :: Id -> RM Id
|
| 386 | 385 | rewriteId v = do
|
| 387 | 386 | !is_tagged <- isTagged v
|
| 388 | - if is_tagged then return $! setIdTagSig v (TagSig TagProper)
|
|
| 387 | + if is_tagged then return $! setIdTagSig v (TagVal TagEPT)
|
|
| 389 | 388 | else return v
|
| 390 | 389 | |
| 391 | 390 | rewriteExpr :: GenStgExpr 'InferTaggedBinders -> RM (GenStgExpr 'CodeGen)
|
| ... | ... | @@ -16,65 +16,119 @@ import GHC.Types.Name.Env( NameEnv ) |
| 16 | 16 | import GHC.Utils.Outputable
|
| 17 | 17 | import GHC.Utils.Binary
|
| 18 | 18 | import GHC.Utils.Panic.Plain
|
| 19 | -import Data.Coerce
|
|
| 20 | 19 | |
| 21 | 20 | -- | Information to be exposed in interface files which is produced
|
| 22 | 21 | -- by the stg2stg passes.
|
| 23 | 22 | type StgCgInfos = NameEnv TagSig
|
| 24 | 23 | |
| 25 | -newtype TagSig -- The signature for each binding, this is a newtype as we might
|
|
| 26 | - -- want to track more information in the future.
|
|
| 27 | - = TagSig TagInfo
|
|
| 24 | +-- Note [TagSig and TagInfo]
|
|
| 25 | +-- ~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 26 | +-- 'TagSig' describes a *binding*; 'TagInfo' describes a *runtime value*.
|
|
| 27 | +--
|
|
| 28 | +-- A binding denotes either a value or a function:
|
|
| 29 | +--
|
|
| 30 | +-- * 'TagVal' i — the binding holds a value whose runtime shape is 'i'.
|
|
| 31 | +-- 'i' covers thunks (TagDunno), evaluated/tagged heap
|
|
| 32 | +-- pointers (TagEPT), unboxed tuples (TagTuple), and
|
|
| 33 | +-- bottoming computations (TagBottoming).
|
|
| 34 | +--
|
|
| 35 | +-- * 'TagFun' i — the binding is a function or join point. A function
|
|
| 36 | +-- closure is always EPT. 'i' describes what a
|
|
| 37 | +-- *saturated call* returns.
|
|
| 38 | +-- See Note [TagInfo of functions] in GHC.Stg.EnforceEpt.
|
|
| 39 | +--
|
|
| 40 | +-- This split keeps 'combineAltInfo' at the value level: case alternatives
|
|
| 41 | +-- combine 'TagInfo', not 'TagSig'. An alternative that returns a function
|
|
| 42 | +-- closure gets 'TagEPT' (closure pointer is tagged) with no tracked return
|
|
| 43 | +-- info — the return info only matters once the function is applied, and at
|
|
| 44 | +-- that point it's looked up from the binding's 'TagSig' via
|
|
| 45 | +-- 'lookupReturnInfo'.
|
|
| 46 | + |
|
| 47 | +-- | The signature attached to a binding.
|
|
| 48 | +data TagSig
|
|
| 49 | + = TagVal TagInfo -- ^ A value binding (thunk, constructor, etc.)
|
|
| 50 | + | TagFun TagInfo -- ^ A function/join-point binding; carries the
|
|
| 51 | + -- TagInfo of saturated-call return values.
|
|
| 52 | + -- See Note [TagInfo of functions].
|
|
| 28 | 53 | deriving (Eq)
|
| 29 | 54 | |
| 55 | +-- Note [TagInfo lattice]
|
|
| 56 | +-- ~~~~~~~~~~~~~~~~~~~~~~
|
|
| 57 | +-- The TagInfo lattice describes what we know about whether a runtime value
|
|
| 58 | +-- is properly tagged (pointer tag bits set for heap pointers):
|
|
| 59 | +--
|
|
| 60 | +-- TagBottoming (bottom) ⊑ {TagEPT, TagTuple _} ⊑ TagDunno (top)
|
|
| 61 | +--
|
|
| 62 | +-- TagBottoming is the identity element for 'combineAltInfo': it is used as
|
|
| 63 | +-- the initial signature in fixpoint loops and for dead-end (bottoming)
|
|
| 64 | +-- computations, since their return value can be given any tag — they never
|
|
| 65 | +-- actually return.
|
|
| 66 | + |
|
| 67 | +-- | What we know about a runtime value.
|
|
| 30 | 68 | data TagInfo
|
| 31 | - = TagDunno -- We don't know anything about the tag.
|
|
| 32 | - | TagTuple [TagInfo] -- Represents a function/thunk which when evaluated
|
|
| 33 | - -- will return a Unboxed tuple whos components have
|
|
| 34 | - -- the given TagInfos.
|
|
| 35 | - | TagProper -- Heap pointer to properly-tagged value
|
|
| 36 | - | TagTagged -- Bottom of the domain.
|
|
| 69 | + = TagDunno -- ^ We don't know anything about the tag.
|
|
| 70 | + | TagTuple [TagInfo] -- ^ An unboxed tuple with taginfo for each element.
|
|
| 71 | + | TagEPT -- ^ An evaluated and properly tagged value.
|
|
| 72 | + -- See Note [Evaluated and Properly Tagged].
|
|
| 73 | + | TagBottoming -- ^ Bottom of the domain.
|
|
| 74 | + -- See Note [Bottom functions are TagBottoming] in GHC.Stg.EnforceEpt.
|
|
| 37 | 75 | deriving (Eq)
|
| 38 | 76 | |
| 39 | 77 | instance Outputable TagInfo where
|
| 40 | - ppr TagTagged = text "TagTagged"
|
|
| 41 | - ppr TagDunno = text "TagDunno"
|
|
| 42 | - ppr TagProper = text "TagProper"
|
|
| 43 | - ppr (TagTuple tis) = text "TagTuple" <> brackets (pprWithCommas ppr tis)
|
|
| 78 | + ppr TagBottoming = text "TagBottoming"
|
|
| 79 | + ppr TagDunno = text "TagDunno"
|
|
| 80 | + ppr TagEPT = text "TagEPT"
|
|
| 81 | + ppr (TagTuple tis) = text "TagTuple" <> brackets (pprWithCommas ppr tis)
|
|
| 44 | 82 | |
| 45 | 83 | instance Binary TagInfo where
|
| 46 | - put_ bh TagDunno = putByte bh 1
|
|
| 84 | + put_ bh TagDunno = putByte bh 1
|
|
| 47 | 85 | put_ bh (TagTuple flds) = putByte bh 2 >> put_ bh flds
|
| 48 | - put_ bh TagProper = putByte bh 3
|
|
| 49 | - put_ bh TagTagged = putByte bh 4
|
|
| 86 | + put_ bh TagEPT = putByte bh 3
|
|
| 87 | + put_ bh TagBottoming = putByte bh 4
|
|
| 50 | 88 | |
| 51 | 89 | get bh = do tag <- getByte bh
|
| 52 | 90 | case tag of 1 -> return TagDunno
|
| 53 | 91 | 2 -> TagTuple <$> get bh
|
| 54 | - 3 -> return TagProper
|
|
| 55 | - 4 -> return TagTagged
|
|
| 92 | + 3 -> return TagEPT
|
|
| 93 | + 4 -> return TagBottoming
|
|
| 56 | 94 | _ -> panic ("get TagInfo " ++ show tag)
|
| 57 | 95 | |
| 58 | 96 | instance Outputable TagSig where
|
| 59 | - ppr (TagSig ti) = char '<' <> ppr ti <> char '>'
|
|
| 97 | + ppr (TagVal ti) = char '<' <> text "TagVal" <> brackets (ppr ti) <> char '>'
|
|
| 98 | + ppr (TagFun ti) = char '<' <> text "TagFun" <> brackets (ppr ti) <> char '>'
|
|
| 99 | + |
|
| 60 | 100 | instance OutputableBndr (Id,TagSig) where
|
| 61 | 101 | pprInfixOcc = ppr
|
| 62 | 102 | pprPrefixOcc = ppr
|
| 63 | 103 | |
| 64 | 104 | instance Binary TagSig where
|
| 65 | - put_ bh (TagSig sig) = put_ bh sig
|
|
| 66 | - get bh = pure TagSig <*> get bh
|
|
| 105 | + put_ bh (TagVal ti) = putByte bh 1 >> put_ bh ti
|
|
| 106 | + put_ bh (TagFun ti) = putByte bh 2 >> put_ bh ti
|
|
| 107 | + get bh = do tag <- getByte bh
|
|
| 108 | + case tag of 1 -> TagVal <$> get bh
|
|
| 109 | + 2 -> TagFun <$> get bh
|
|
| 110 | + _ -> panic ("get TagSig " ++ show tag)
|
|
| 67 | 111 | |
| 112 | +-- | Is the given binding known to be properly tagged (or irrelevant, as for
|
|
| 113 | +-- unboxed values and bottoming computations)?
|
|
| 68 | 114 | isTaggedSig :: TagSig -> Bool
|
| 69 | -isTaggedSig (TagSig TagProper) = True
|
|
| 70 | -isTaggedSig (TagSig TagTagged) = True
|
|
| 71 | -isTaggedSig _ = False
|
|
| 115 | +isTaggedSig (TagFun _) = True
|
|
| 116 | +isTaggedSig (TagVal ti) = isTaggedInfo ti
|
|
| 117 | + |
|
| 118 | +-- | Is the given value-level tag known to be properly tagged?
|
|
| 119 | +-- NB: unboxed tuples are *not* treated as tagged here; they are handled
|
|
| 120 | +-- specially by the rewriter (which considers them already evaluated).
|
|
| 121 | +isTaggedInfo :: TagInfo -> Bool
|
|
| 122 | +isTaggedInfo TagEPT = True
|
|
| 123 | +isTaggedInfo TagBottoming = True
|
|
| 124 | +isTaggedInfo _ = False
|
|
| 72 | 125 | |
| 73 | 126 | seqTagSig :: TagSig -> ()
|
| 74 | -seqTagSig = coerce seqTagInfo
|
|
| 127 | +seqTagSig (TagVal ti) = seqTagInfo ti
|
|
| 128 | +seqTagSig (TagFun ti) = seqTagInfo ti
|
|
| 75 | 129 | |
| 76 | 130 | seqTagInfo :: TagInfo -> ()
|
| 77 | -seqTagInfo TagTagged = ()
|
|
| 131 | +seqTagInfo TagBottoming = ()
|
|
| 78 | 132 | seqTagInfo TagDunno = ()
|
| 79 | -seqTagInfo TagProper = ()
|
|
| 80 | -seqTagInfo (TagTuple tis) = foldl' (\_unit sig -> seqTagSig (coerce sig)) () tis |
|
| 133 | +seqTagInfo TagEPT = ()
|
|
| 134 | +seqTagInfo (TagTuple tis) = foldl' (\_unit info -> seqTagInfo info) () tis |
| ... | ... | @@ -35,15 +35,23 @@ type InferStgExpr = GenStgExpr 'InferTaggedBinders |
| 35 | 35 | type InferStgRhs = GenStgRhs 'InferTaggedBinders
|
| 36 | 36 | type InferStgAlt = GenStgAlt 'InferTaggedBinders
|
| 37 | 37 | |
| 38 | +-- | Combine TagInfo from the alternatives of a case expression.
|
|
| 39 | +-- Note that this operates at the value level: case alternatives return
|
|
| 40 | +-- values. See Note [TagSig and TagInfo].
|
|
| 38 | 41 | combineAltInfo :: TagInfo -> TagInfo -> TagInfo
|
| 39 | -combineAltInfo TagDunno _ = TagDunno
|
|
| 40 | -combineAltInfo _ TagDunno = TagDunno
|
|
| 41 | -combineAltInfo (TagTuple {}) TagProper = TagDunno -- This can happen with rep-polymorphic result, see #26107
|
|
| 42 | -combineAltInfo TagProper (TagTuple {}) = TagDunno -- This can happen with rep-polymorphic result, see #26107
|
|
| 43 | -combineAltInfo TagProper TagProper = TagProper
|
|
| 42 | +combineAltInfo TagBottoming ti = ti
|
|
| 43 | +combineAltInfo ti TagBottoming = ti
|
|
| 44 | +combineAltInfo TagDunno TagDunno = TagDunno
|
|
| 45 | +combineAltInfo TagDunno TagEPT = TagDunno
|
|
| 46 | +combineAltInfo TagDunno (TagTuple {}) = TagDunno
|
|
| 47 | +combineAltInfo TagEPT TagDunno = TagDunno
|
|
| 48 | +combineAltInfo (TagTuple {}) TagDunno = TagDunno
|
|
| 49 | +combineAltInfo TagEPT TagEPT = TagEPT
|
|
| 50 | +-- TagEPT/TagTuple are incompatible (can arise with rep-polymorphic
|
|
| 51 | +-- results, see #26107); fall through to TagDunno.
|
|
| 52 | +combineAltInfo TagEPT (TagTuple {}) = TagDunno
|
|
| 53 | +combineAltInfo (TagTuple {}) TagEPT = TagDunno
|
|
| 44 | 54 | combineAltInfo (TagTuple is1) (TagTuple is2) = TagTuple (zipWithEqual combineAltInfo is1 is2)
|
| 45 | -combineAltInfo (TagTagged) ti = ti
|
|
| 46 | -combineAltInfo ti TagTagged = ti
|
|
| 47 | 55 | |
| 48 | 56 | type TagSigEnv = IdEnv TagSig
|
| 49 | 57 | data TagEnv p = TE { te_env :: TagSigEnv
|
| ... | ... | @@ -75,48 +83,58 @@ makeTagged env = TE { te_env = te_env env |
| 75 | 83 | |
| 76 | 84 | noSig :: TagEnv p -> BinderP p -> (Id, TagSig)
|
| 77 | 85 | noSig env bndr
|
| 78 | - | isUnliftedType (idType var) = (var, TagSig TagProper)
|
|
| 79 | - | otherwise = (var, TagSig TagDunno)
|
|
| 86 | + | isUnliftedType (idType var) = (var, TagVal TagEPT)
|
|
| 87 | + | otherwise = (var, TagVal TagDunno)
|
|
| 80 | 88 | where
|
| 81 | 89 | var = getBinderId env bndr
|
| 82 | 90 | |
| 83 | --- | Look up a sig in the given env
|
|
| 84 | -lookupSig :: TagEnv p -> Id -> Maybe TagSig
|
|
| 85 | -lookupSig env fun = lookupVarEnv (te_env env) fun
|
|
| 86 | - |
|
| 87 | --- | Look up a sig in the env or derive it from information
|
|
| 88 | --- in the arg itself.
|
|
| 91 | +-- | Look up the return-value tag of a function for saturated call analysis.
|
|
| 92 | +-- Returns 'Just retInfo' if the function binding has a 'TagFun' signature,
|
|
| 93 | +-- 'Nothing' otherwise.
|
|
| 94 | +lookupReturnInfo :: TagEnv p -> Id -> Maybe TagInfo
|
|
| 95 | +lookupReturnInfo env fun = case lookupVarEnv (te_env env) fun of
|
|
| 96 | + Just (TagFun ret_info) -> Just ret_info
|
|
| 97 | + Just (TagVal _) -> Nothing
|
|
| 98 | + Nothing -> Nothing
|
|
| 99 | + |
|
| 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.
|
|
| 89 | 103 | lookupInfo :: TagEnv p -> StgArg -> TagInfo
|
| 90 | 104 | lookupInfo env (StgVarArg var)
|
| 91 | 105 | -- Nullary data constructors like True, False
|
| 92 | 106 | | Just dc <- isDataConWorkId_maybe var
|
| 93 | 107 | , isNullaryRepDataCon dc
|
| 94 | 108 | , not for_bytecode
|
| 95 | - = TagProper
|
|
| 109 | + = TagEPT
|
|
| 96 | 110 | |
| 97 | 111 | | isUnliftedType (idType var)
|
| 98 | - = TagProper
|
|
| 112 | + = TagEPT
|
|
| 99 | 113 | |
| 100 | - -- Variables in the environment.
|
|
| 101 | - | Just (TagSig info) <- lookupVarEnv (te_env env) var
|
|
| 102 | - = info
|
|
| 114 | + -- Variables in the environment. A function binding flattens to TagEPT
|
|
| 115 | + -- since a function closure pointer is properly tagged; its return info
|
|
| 116 | + -- is not relevant when the function is used as a value.
|
|
| 117 | + | Just sig <- lookupVarEnv (te_env env) var
|
|
| 118 | + = case sig of
|
|
| 119 | + TagVal info -> info
|
|
| 120 | + TagFun _ -> TagEPT
|
|
| 103 | 121 | |
| 104 | 122 | | Just lf_info <- idLFInfo_maybe var
|
| 105 | 123 | , not for_bytecode
|
| 106 | 124 | = case lf_info of
|
| 107 | 125 | -- Function, tagged (with arity)
|
| 108 | 126 | LFReEntrant {}
|
| 109 | - -> TagProper
|
|
| 127 | + -> TagEPT
|
|
| 110 | 128 | -- Thunks need to be entered.
|
| 111 | 129 | LFThunk {}
|
| 112 | 130 | -> TagDunno
|
| 113 | 131 | -- Constructors, already tagged.
|
| 114 | 132 | LFCon {}
|
| 115 | - -> TagProper
|
|
| 133 | + -> TagEPT
|
|
| 116 | 134 | LFUnknown {}
|
| 117 | 135 | -> TagDunno
|
| 118 | 136 | LFUnlifted {}
|
| 119 | - -> TagProper
|
|
| 137 | + -> TagEPT
|
|
| 120 | 138 | -- Shouldn't be possible. I don't think we can export letNoEscapes
|
| 121 | 139 | LFLetNoEscape {} -> panic "LFLetNoEscape exported"
|
| 122 | 140 | |
| ... | ... | @@ -126,19 +144,13 @@ lookupInfo env (StgVarArg var) |
| 126 | 144 | for_bytecode = te_bytecode env
|
| 127 | 145 | |
| 128 | 146 | lookupInfo _ (StgLitArg {})
|
| 129 | - = TagProper
|
|
| 147 | + = TagEPT
|
|
| 130 | 148 | |
| 131 | 149 | isDunnoSig :: TagSig -> Bool
|
| 132 | -isDunnoSig (TagSig TagDunno) = True
|
|
| 133 | -isDunnoSig (TagSig TagProper) = False
|
|
| 134 | -isDunnoSig (TagSig TagTuple{}) = False
|
|
| 135 | -isDunnoSig (TagSig TagTagged{}) = False
|
|
| 136 | - |
|
| 137 | -isTaggedInfo :: TagInfo -> Bool
|
|
| 138 | -isTaggedInfo TagProper = True
|
|
| 139 | -isTaggedInfo TagTagged = True
|
|
| 140 | -isTaggedInfo _ = False
|
|
| 150 | +isDunnoSig (TagVal TagDunno) = True
|
|
| 151 | +isDunnoSig _ = False
|
|
| 141 | 152 | |
| 153 | +-- | Extend the tag environment.
|
|
| 142 | 154 | extendSigEnv :: TagEnv p -> [(Id,TagSig)] -> TagEnv p
|
| 143 | 155 | extendSigEnv env@(TE { te_env = sig_env }) bndrs
|
| 144 | 156 | = env { te_env = extendVarEnvList sig_env bndrs } |
| ... | ... | @@ -8,7 +8,7 @@ T14895.go |
| 8 | 8 | [GblId, Arity=2, Str=<MC(1,L)><1L>, Unf=OtherCon []] =
|
| 9 | 9 | {} \r [f ds]
|
| 10 | 10 | case ds of wild {
|
| 11 | - GHC.Internal.Data.Either.Left e [Occ=Once1] -> wild<TagProper>;
|
|
| 11 | + GHC.Internal.Data.Either.Left e [Occ=Once1] -> wild<TagVal[TagEPT]>;
|
|
| 12 | 12 | GHC.Internal.Data.Either.Right a1 [Occ=Once1] ->
|
| 13 | 13 | let {
|
| 14 | 14 | go_sat [Occ=Once1] :: b
|
| 1 | 1 | lift :: Foo -> T
|
| 2 | - [HasNoCafRefs, TagSig: <TagProper>, LambdaFormInfo: LFReEntrant 1,
|
|
| 3 | - Arity: 1, Strictness: <1!A>, CPR: 1,
|
|
| 2 | + [HasNoCafRefs, TagSig: <TagFun[TagEPT]>,
|
|
| 3 | + LambdaFormInfo: LFReEntrant 1, Arity: 1, Strictness: <1!A>, CPR: 1,
|
|
| 4 | 4 | Unfolding: Core: <vanilla> bof `cast` (Sym N:Foo ->_R <T>_R)] |
| 1 | +module Main (main) where
|
|
| 2 | +import GHC.Exts.Heap (GenClosure(..), getClosureData)
|
|
| 3 | +import T27005_aux (T(..), S(..), f)
|
|
| 4 | +import System.Exit
|
|
| 5 | + |
|
| 6 | +main :: IO ()
|
|
| 7 | +main = do
|
|
| 8 | + let !t = MkT (MkS (pure ()) 0)
|
|
| 9 | + dup <- f t
|
|
| 10 | + c <- getClosureData dup
|
|
| 11 | + case c of
|
|
| 12 | + ThunkClosure{} -> putStrLn "FAIL" >> exitFailure
|
|
| 13 | + APClosure{} -> putStrLn "FAIL" >> exitFailure
|
|
| 14 | + _ -> putStrLn "OK" |
| 1 | +OK |
| 1 | +module T27005_aux (T(..), S(..), f) where
|
|
| 2 | +import GHC.Stack
|
|
| 3 | + |
|
| 4 | +class Monad m => C m
|
|
| 5 | +instance C IO
|
|
| 6 | + |
|
| 7 | +data S = MkS !(IO ()) Int
|
|
| 8 | +data T = MkT !S
|
|
| 9 | + |
|
| 10 | +{-# SPECIALISE f :: HasCallStack => T -> IO T #-}
|
|
| 11 | +f :: (C m, HasCallStack) => T -> m T
|
|
| 12 | +f (MkT a) = do () <- pure (); pure $! MkT a |
| ... | ... | @@ -122,3 +122,4 @@ test('AppIsHNF', normal, compile_and_run, ['-O']) |
| 122 | 122 | test('T24359b', normal, compile_and_run, ['-O'])
|
| 123 | 123 | test('T23429', normal, compile_and_run, ['-O'])
|
| 124 | 124 | test('T27071', normal, compile_and_run, ['-O -fworker-wrapper-cbv'])
|
| 125 | +test('T27005', [], multimod_compile_and_run, ['T27005', '-O']) |
| ... | ... | @@ -7,10 +7,10 @@ data Tup2 a b = Tup2 !a !b |
| 7 | 7 | -- All branches of go return either two properly tagged values *or* are bottom.
|
| 8 | 8 | -- This means we should see something like:
|
| 9 | 9 | --
|
| 10 | --- (T24806.$wgo, <TagTuple[TagProper, TagProper]>) =
|
|
| 10 | +-- (T24806.$wgo, <TagFun[TagTuple[TagEPT, TagEPT]]>) =
|
|
| 11 | 11 | --
|
| 12 | 12 | -- in the dump output.
|
| 13 | --- See Note [Bottom functions are TagTagged] for details why.
|
|
| 13 | +-- See Note [Bottom functions are TagBottoming] for details why.
|
|
| 14 | 14 | go :: List a1 -> List a2 -> Tup2 (List a2) (List a2)
|
| 15 | 15 | go Nil ys = Tup2 ys Nil
|
| 16 | 16 | go (Cons _ xs) ys = case ys of
|
| 1 | 1 | |
| 2 | 2 | ==================== CodeGenAnal STG: ====================
|
| 3 | -lvl6 :: GHC.Prim.Addr#
|
|
| 3 | +lvl6 :: GHC.Internal.Prim.Addr#
|
|
| 4 | 4 | [GblId, Unf=OtherCon []] =
|
| 5 | 5 | "T24806.hs"#;
|
| 6 | 6 | |
| 7 | -lvl4 :: GHC.Prim.Addr#
|
|
| 7 | +lvl4 :: GHC.Internal.Prim.Addr#
|
|
| 8 | 8 | [GblId, Unf=OtherCon []] =
|
| 9 | 9 | "T24806"#;
|
| 10 | 10 | |
| 11 | -lvl2 :: GHC.Prim.Addr#
|
|
| 11 | +lvl2 :: GHC.Internal.Prim.Addr#
|
|
| 12 | 12 | [GblId, Unf=OtherCon []] =
|
| 13 | 13 | "main"#;
|
| 14 | 14 | |
| 15 | -lvl :: GHC.Prim.Addr#
|
|
| 15 | +lvl :: GHC.Internal.Prim.Addr#
|
|
| 16 | 16 | [GblId, Unf=OtherCon []] =
|
| 17 | 17 | "undefined"#;
|
| 18 | 18 | |
| 19 | -(T24806.$WTup2, <TagProper>) =
|
|
| 20 | - {} \r [(conrep, <TagDunno>) (conrep1, <TagDunno>)]
|
|
| 21 | - case conrep of (conrep2, <TagProper>) {
|
|
| 22 | - __DEFAULT ->
|
|
| 23 | - case conrep1 of (conrep3, <TagProper>) {
|
|
| 24 | - __DEFAULT -> T24806.Tup2 [conrep2 conrep3];
|
|
| 25 | - };
|
|
| 26 | - };
|
|
| 27 | - |
|
| 28 | -(T24806.$WCons, <TagProper>) =
|
|
| 29 | - {} \r [(conrep, <TagDunno>) (conrep1, <TagDunno>)]
|
|
| 30 | - case conrep1 of (conrep2, <TagProper>) {
|
|
| 31 | - __DEFAULT -> T24806.Cons [conrep conrep2];
|
|
| 32 | - };
|
|
| 19 | +(T24806.Tup2, <TagFun[TagDunno]>) =
|
|
| 20 | + {} \r [(eta, <TagVal[TagDunno]>) (eta, <TagVal[TagDunno]>)] T24806.Tup2 [eta eta];
|
|
| 33 | 21 | |
| 34 | -(lvl1, <TagDunno>) = {} \u [] GHC.CString.unpackCString# lvl;
|
|
| 22 | +(T24806.Nil, <TagVal[TagEPT]>) = T24806.Nil! [];
|
|
| 35 | 23 | |
| 36 | -(lvl3, <TagDunno>) = {} \u [] GHC.CString.unpackCString# lvl2;
|
|
| 24 | +(T24806.Cons, <TagFun[TagDunno]>) =
|
|
| 25 | + {} \r [(eta, <TagVal[TagDunno]>) (eta, <TagVal[TagDunno]>)] T24806.Cons [eta eta];
|
|
| 37 | 26 | |
| 38 | -(lvl5, <TagDunno>) = {} \u [] GHC.CString.unpackCString# lvl4;
|
|
| 27 | +(lvl1, <TagVal[TagDunno]>) =
|
|
| 28 | + {} \u [] GHC.Internal.CString.unpackCString# lvl;
|
|
| 39 | 29 | |
| 40 | -(lvl7, <TagDunno>) = {} \u [] GHC.CString.unpackCString# lvl6;
|
|
| 30 | +(lvl3, <TagVal[TagDunno]>) =
|
|
| 31 | + {} \u [] GHC.Internal.CString.unpackCString# lvl2;
|
|
| 41 | 32 | |
| 42 | -(lvl8, <TagProper>) = GHC.Types.I#! [17#];
|
|
| 33 | +(lvl5, <TagVal[TagDunno]>) =
|
|
| 34 | + {} \u [] GHC.Internal.CString.unpackCString# lvl4;
|
|
| 43 | 35 | |
| 44 | -(lvl9, <TagProper>) = GHC.Types.I#! [12#];
|
|
| 36 | +(lvl7, <TagVal[TagDunno]>) =
|
|
| 37 | + {} \u [] GHC.Internal.CString.unpackCString# lvl6;
|
|
| 45 | 38 | |
| 46 | -(lvl10, <TagProper>) = GHC.Types.I#! [21#];
|
|
| 39 | +(lvl8, <TagVal[TagEPT]>) =
|
|
| 40 | + GHC.Internal.Stack.Types.SrcLoc! [lvl3 lvl5 lvl7 17# 12# 17# 21#];
|
|
| 47 | 41 | |
| 48 | -(lvl11, <TagProper>) =
|
|
| 49 | - GHC.Internal.Stack.Types.SrcLoc! [lvl3
|
|
| 50 | - lvl5
|
|
| 51 | - lvl7
|
|
| 52 | - lvl8
|
|
| 53 | - lvl9
|
|
| 54 | - lvl8
|
|
| 55 | - lvl10];
|
|
| 56 | - |
|
| 57 | -(lvl12, <TagProper>) =
|
|
| 42 | +(lvl9, <TagVal[TagEPT]>) =
|
|
| 58 | 43 | GHC.Internal.Stack.Types.PushCallStack! [lvl1
|
| 59 | - lvl11
|
|
| 44 | + lvl8
|
|
| 60 | 45 | GHC.Internal.Stack.Types.EmptyCallStack];
|
| 61 | 46 | |
| 62 | -(lvl13, <TagDunno>) = {} \u [] GHC.Internal.Err.undefined lvl12;
|
|
| 63 | - |
|
| 64 | -(T24806.Tup2, <TagDunno>) =
|
|
| 65 | - {} \r [(eta, <TagDunno>) (eta, <TagDunno>)] T24806.Tup2 [eta eta];
|
|
| 66 | - |
|
| 67 | -(T24806.Nil, <TagProper>) = T24806.Nil! [];
|
|
| 47 | +(lvl10, <TagVal[TagDunno]>) = {} \u [] GHC.Internal.Err.undefined lvl9;
|
|
| 68 | 48 | |
| 69 | 49 | Rec {
|
| 70 | -(T24806.$wgo, <TagTuple[TagProper, TagProper]>) =
|
|
| 71 | - {} \r [(ds, <TagProper>) (ys, <TagProper>)]
|
|
| 72 | - case ds of (wild, <TagProper>) {
|
|
| 50 | +(T24806.$wgo, <TagFun[TagTuple[TagEPT, TagEPT]]>) =
|
|
| 51 | + {} \r [(ds, <TagVal[TagEPT]>) (ys, <TagVal[TagEPT]>)]
|
|
| 52 | + case ds of (wild, <TagVal[TagEPT]>) {
|
|
| 73 | 53 | T24806.Nil ->
|
| 74 | - case ys of (conrep, <TagProper>) {
|
|
| 75 | - __DEFAULT -> (#,#) [conrep T24806.Nil];
|
|
| 54 | + case ys of (wild1, <TagVal[TagEPT]>) {
|
|
| 55 | + __DEFAULT -> (#,#) [wild1 T24806.Nil];
|
|
| 76 | 56 | };
|
| 77 | - T24806.Cons (ds1, <TagDunno>) (xs, <TagProper>) ->
|
|
| 78 | - case ys of (wild1, <TagProper>) {
|
|
| 79 | - T24806.Nil -> lvl13;
|
|
| 80 | - T24806.Cons (y, <TagDunno>) (ys', <TagProper>) ->
|
|
| 81 | - case T24806.$wgo xs ys' of (wild2, <TagProper>) {
|
|
| 82 | - (#,#) (ww, <TagProper>) (ww1, <TagProper>) ->
|
|
| 83 | - let { (sat, <TagProper>) = T24806.Cons! [y ww1];
|
|
| 84 | - } in (#,#) [ww sat];
|
|
| 57 | + T24806.Cons (ds1, <TagVal[TagDunno]>) (xs, <TagVal[TagEPT]>) ->
|
|
| 58 | + case ys of (wild1, <TagVal[TagEPT]>) {
|
|
| 59 | + T24806.Nil -> lvl10;
|
|
| 60 | + T24806.Cons (y, <TagVal[TagDunno]>) (ys', <TagVal[TagEPT]>) ->
|
|
| 61 | + case T24806.$wgo xs ys' of (wild2, <TagVal[TagEPT]>) {
|
|
| 62 | + (#,#) (ww, <TagVal[TagEPT]>) (ww1, <TagVal[TagEPT]>) ->
|
|
| 63 | + let { ($wgo_sat, <TagVal[TagEPT]>) = T24806.Cons! [y ww1];
|
|
| 64 | + } in (#,#) [ww $wgo_sat];
|
|
| 85 | 65 | };
|
| 86 | 66 | };
|
| 87 | 67 | };
|
| 88 | 68 | end Rec }
|
| 89 | 69 | |
| 90 | -(T24806.go, <TagProper>) =
|
|
| 91 | - {} \r [(ds, <TagDunno>) (ys, <TagDunno>)]
|
|
| 92 | - case T24806.$wgo ds ys of (wild, <TagProper>) {
|
|
| 93 | - (#,#) (ww, <TagProper>) (ww1, <TagProper>) -> T24806.Tup2 [ww ww1];
|
|
| 70 | +(T24806.go, <TagFun[TagEPT]>) =
|
|
| 71 | + {} \r [(ds, <TagVal[TagDunno]>) (ys, <TagVal[TagDunno]>)]
|
|
| 72 | + case T24806.$wgo ds ys of (wild, <TagVal[TagEPT]>) {
|
|
| 73 | + (#,#) (ww, <TagVal[TagEPT]>) (ww1, <TagVal[TagEPT]>) ->
|
|
| 74 | + T24806.Tup2 [ww ww1];
|
|
| 94 | 75 | };
|
| 95 | 76 | |
| 96 | -(T24806.Cons, <TagDunno>) =
|
|
| 97 | - {} \r [(eta, <TagDunno>) (eta, <TagDunno>)] T24806.Cons [eta eta];
|
|
| 98 | - |
|
| 99 | 77 |
| 1 | +{-# LANGUAGE BangPatterns, UnboxedTuples #-}
|
|
| 2 | +module T27005b where
|
|
| 3 | + |
|
| 4 | +-- A function returning an unboxed tuple whose single component is itself a
|
|
| 5 | +-- function. With the TagFun/TagVal split, `f`'s tag signature records that the
|
|
| 6 | +-- returned component is properly tagged (TagEPT), so when `g` scrutinises the
|
|
| 7 | +-- result of `f h` the binder `x` is already known to be evaluated. Hence the
|
|
| 8 | +-- pair `(x, x)` is built without re-evaluating `x`.
|
|
| 9 | +f :: (Bool -> Bool) -> (# Bool -> Bool #)
|
|
| 10 | +f !x = (# x #)
|
|
| 11 | +{-# NOINLINE f #-}
|
|
| 12 | + |
|
| 13 | +g :: (Bool -> Bool) -> (Bool -> Bool, Bool -> Bool)
|
|
| 14 | +g h =
|
|
| 15 | + case f h of
|
|
| 16 | + (# !x #) -> (x, x) |
| 1 | + |
|
| 2 | +==================== CodeGenAnal STG: ====================
|
|
| 3 | +(T27005b.f, <TagFun[TagTuple[TagEPT]]>) =
|
|
| 4 | + {} \r [(x, <TagVal[TagDunno]>)]
|
|
| 5 | + case x of (x1, <TagVal[TagEPT]>) {
|
|
| 6 | + __DEFAULT -> GHC.Internal.Types.MkSolo# [x1];
|
|
| 7 | + };
|
|
| 8 | + |
|
| 9 | +(T27005b.g, <TagFun[TagEPT]>) =
|
|
| 10 | + {} \r [(h, <TagVal[TagDunno]>)]
|
|
| 11 | + case T27005b.f h of (ds, <TagVal[TagEPT]>) {
|
|
| 12 | + GHC.Internal.Types.MkSolo# (ipv, <TagVal[TagEPT]>) ->
|
|
| 13 | + case ipv of (x, <TagVal[TagEPT]>) { __DEFAULT -> (,) [x x]; };
|
|
| 14 | + };
|
|
| 15 | + |
|
| 16 | + |
| ... | ... | @@ -24,5 +24,6 @@ test('inferTags003', [ only_ways(['optasm']), |
| 24 | 24 | grep_errmsg(r'(call stg\_ap\_0)', [1])
|
| 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 | +test('T27005b', normal, compile, ['-O -ddump-stg-tags -dno-typeable-binds -dsuppress-uniques'])
|
|
| 27 | 28 | |
| 28 | 29 | test('T24806', grep_errmsg('^\\(T24806\\.\\$wgo'), compile, ['-O -ddump-stg-tags -dno-typeable-binds -dsuppress-uniques']) |
| ... | ... | @@ -7,5 +7,5 @@ x = x |
| 7 | 7 | f :: a -> (# Int, a #)
|
| 8 | 8 | -- Adapted from a TODO in EnforceEpt.
|
| 9 | 9 | -- f's tag signature should indicate that the second component
|
| 10 | --- of its result is properly tagged: TagTuple[TagDunno,TagProper]
|
|
| 10 | +-- of its result is properly tagged: TagTuple[TagDunno,TagEPT]
|
|
| 11 | 11 | f g = case g of !g' -> (# x, g' #) |
| 1 | 1 | |
| 2 | 2 | ==================== CodeGenAnal STG: ====================
|
| 3 | 3 | Rec {
|
| 4 | -(EnforceEpt004.x, <TagDunno>) = {} \u [] EnforceEpt004.x;
|
|
| 4 | +(EnforceEpt004.x, <TagVal[TagDunno]>) = {} \u [] EnforceEpt004.x;
|
|
| 5 | 5 | end Rec }
|
| 6 | 6 | |
| 7 | -(EnforceEpt004.f, <TagTuple[TagDunno, TagProper]>) =
|
|
| 8 | - {} \r [(g, <TagDunno>)]
|
|
| 9 | - case g of (g', <TagProper>) {
|
|
| 7 | +(EnforceEpt004.f, <TagFun[TagTuple[TagDunno, TagEPT]]>) =
|
|
| 8 | + {} \r [(g, <TagVal[TagDunno]>)]
|
|
| 9 | + case g of (g', <TagVal[TagEPT]>) {
|
|
| 10 | 10 | __DEFAULT -> (#,#) [EnforceEpt004.x g'];
|
| 11 | 11 | };
|
| 12 | 12 |
| 1 | +{-# LANGUAGE UnboxedTuples, MagicHash #-}
|
|
| 2 | +-- Test for EnforceEpt mixing up tag signatures between functions and their return values
|
|
| 3 | +module Main (main) where
|
|
| 4 | + |
|
| 5 | +import GHC.Exts
|
|
| 6 | +import GHC.Exts.Heap (GenClosure(..), getClosureData)
|
|
| 7 | +import System.Exit
|
|
| 8 | + |
|
| 9 | +-- an evaluated function that returns a tuple
|
|
| 10 | +g :: State# RealWorld -> (# State# RealWorld, () #)
|
|
| 11 | +g s = (# s, () #)
|
|
| 12 | +{-# NOINLINE g #-}
|
|
| 13 | + |
|
| 14 | +data S = MkS !(State# RealWorld -> (# State# RealWorld, () #)) Int
|
|
| 15 | +data T = MkT !S
|
|
| 16 | + |
|
| 17 | +s :: S
|
|
| 18 | +s = MkS g 0
|
|
| 19 | +{-# NOINLINE s #-}
|
|
| 20 | + |
|
| 21 | +t :: T
|
|
| 22 | +t = MkT s
|
|
| 23 | + |
|
| 24 | +main :: IO ()
|
|
| 25 | +main = do
|
|
| 26 | + c <- getClosureData t
|
|
| 27 | + case c of
|
|
| 28 | + ThunkClosure{} -> putStrLn "FAIL: ThunkClosure" >> exitFailure
|
|
| 29 | + APClosure{} -> putStrLn "FAIL: APClosure" >> exitFailure
|
|
| 30 | + _ -> putStrLn "OK" |
| 1 | +OK |
| ... | ... | @@ -21,4 +21,5 @@ test('T13536a', |
| 21 | 21 | test('inferTags001', normal, multimod_compile_and_run, ['inferTags001', 'inferTags001_a'])
|
| 22 | 22 | test('T22042', [extra_files(['T22042a.hs']),only_ways('normal'),unless(have_dynamic(), skip)], makefile_test, ['T22042'])
|
| 23 | 23 | test('T23783', normal, multimod_compile_and_run, ['T23783', '-O -v0'])
|
| 24 | -test('unpack_enum', normal, compile_and_run, ['']) |
|
| \ No newline at end of file | ||
| 24 | +test('unpack_enum', normal, compile_and_run, [''])
|
|
| 25 | +test('T27005a', normal, compile_and_run, ['']) |