Simon Peyton Jones pushed to branch wip/T26903 at Glasgow Haskell Compiler / GHC
Commits:
-
779c354e
by Simon Peyton Jones at 2026-02-11T15:04:17+00:00
7 changed files:
- compiler/GHC/Core/Opt/Simplify/Iteration.hs
- compiler/GHC/Core/Opt/WorkWrap.hs
- compiler/GHC/Driver/Config/Core/Lint.hs
- compiler/GHC/Types/Id/Info.hs
- + testsuite/tests/simplCore/should_compile/T26903.hs
- + testsuite/tests/simplCore/should_compile/T26903.stderr
- testsuite/tests/simplCore/should_compile/all.T
Changes:
| ... | ... | @@ -474,14 +474,14 @@ leaving a simpler job for demand-analysis worker/wrapper. See #19874. |
| 474 | 474 | |
| 475 | 475 | Wrinkles
|
| 476 | 476 | |
| 477 | -1. We must /not/ do cast w/w on
|
|
| 477 | +(CWW1) We must /not/ do cast w/w on
|
|
| 478 | 478 | f = g |> co
|
| 479 | 479 | otherwise it'll just keep repeating forever! You might think this
|
| 480 | 480 | is avoided because the call to tryCastWorkerWrapper is guarded by
|
| 481 | 481 | preInlineUnconditinally, but I'm worried that a loop-breaker or an
|
| 482 | 482 | exported Id might say False to preInlineUnonditionally.
|
| 483 | 483 | |
| 484 | -2. We need to be careful with inline/noinline pragmas:
|
|
| 484 | +(CWW2) We need to be careful with inline/noinline pragmas:
|
|
| 485 | 485 | rec { {-# NOINLINE f #-}
|
| 486 | 486 | f = (...g...) |> co
|
| 487 | 487 | ; g = ...f... }
|
| ... | ... | @@ -503,7 +503,7 @@ Wrinkles |
| 503 | 503 | ; g = ...f... }
|
| 504 | 504 | c.f. Note [Worker/wrapper for NOINLINE functions] in GHC.Core.Opt.WorkWrap.
|
| 505 | 505 | |
| 506 | -3. We should still do cast w/w even if `f` is INLINEABLE. E.g.
|
|
| 506 | +(CWW3) We should still do cast w/w even if `f` is INLINEABLE. E.g.
|
|
| 507 | 507 | {- f: Stable unfolding = <stable-big> -}
|
| 508 | 508 | f = (\xy. <big-body>) |> co
|
| 509 | 509 | Then we want to w/w to
|
| ... | ... | @@ -513,15 +513,29 @@ Wrinkles |
| 513 | 513 | Notice that the stable unfolding moves to the worker! Now demand analysis
|
| 514 | 514 | will work fine on $wf, whereas it has trouble with the original f.
|
| 515 | 515 | c.f. Note [Worker/wrapper for INLINABLE functions] in GHC.Core.Opt.WorkWrap.
|
| 516 | - This point also applies to strong loopbreakers with INLINE pragmas, see
|
|
| 517 | - wrinkle (4).
|
|
| 518 | 516 | |
| 519 | -4. We should /not/ do cast w/w for non-loop-breaker INLINE functions (hence
|
|
| 520 | - hasInlineUnfolding in tryCastWorkerWrapper, which responds False to
|
|
| 521 | - loop-breakers) because they'll definitely be inlined anyway, cast and
|
|
| 522 | - all. And if we do cast w/w for an INLINE function with arity zero, we get
|
|
| 517 | +(CWW4) We should /not/ do cast w/w for INLINE functions (hence `hasInlineUnfolding`
|
|
| 518 | + in `tryCastWorkerWrapper`) because they'll definitely be inlined anyway, cast
|
|
| 519 | + and all.
|
|
| 520 | + |
|
| 521 | + Moreover, if we do cast w/w for an INLINE function with arity zero, we get
|
|
| 523 | 522 | something really silly: we inline that "worker" right back into the wrapper!
|
| 524 | - Worse than a no-op, because we have then lost the stable unfolding.
|
|
| 523 | + In fact it is Much Worse than a no-op, because we have then lost the stable
|
|
| 524 | + unfolding --- aargh! See #26903.
|
|
| 525 | + |
|
| 526 | + NB: you might wonder about a loop-breaker with an INLINE pragma; after all, a
|
|
| 527 | + loop breaker won't "definitely be inlined anyway", so arguably we should not
|
|
| 528 | + disable cast w/w/ for it. But a Rec group can /look/ recursive at an early
|
|
| 529 | + stage, and subsequently /become/ non-recursive after some simplification.
|
|
| 530 | + (This is common in instance decls; see Note [Checking for INLINE loop breakers]
|
|
| 531 | + in GHC.Core.Lint.) So the danger is that we'll permanently lose that stable
|
|
| 532 | + unfolding that we specifically wanted (#26903). Simple solution: disable cast
|
|
| 533 | + w/w for /any/ INLINE function. See the defn
|
|
| 534 | + of `GHC.Types.Id.Info.hasInlineUnfolding`.
|
|
| 535 | + |
|
| 536 | + The danger is that an INLINE pragma on a genuninely-recursive function
|
|
| 537 | + will kill worker-wrapper. Well, so be it. They are pretty suspicious anyway;
|
|
| 538 | + see Note [Checking for INLINE loop breakers].
|
|
| 525 | 539 | |
| 526 | 540 | All these wrinkles are exactly like worker/wrapper for strictness analysis:
|
| 527 | 541 | f is the wrapper and must inline like crazy
|
| ... | ... | @@ -586,11 +600,11 @@ tryCastWorkerWrapper env bind_cxt old_bndr bndr (Cast rhs co) |
| 586 | 600 | | BC_Let top_lvl is_rec <- bind_cxt -- Not join points
|
| 587 | 601 | , not (isDFunId bndr) -- nor DFuns; cast w/w is no help, and we can't transform
|
| 588 | 602 | -- a DFunUnfolding in mk_worker_unfolding
|
| 589 | - , not (exprIsTrivial rhs) -- Not x = y |> co; Wrinkle 1
|
|
| 590 | - , not (hasInlineUnfolding info) -- Not INLINE things: Wrinkle 4
|
|
| 591 | - , typeHasFixedRuntimeRep work_ty -- Don't peel off a cast if doing so would
|
|
| 592 | - -- lose the underlying runtime representation.
|
|
| 593 | - -- See Note [Preserve RuntimeRep info in cast w/w]
|
|
| 603 | + , not (exprIsTrivial rhs) -- Not x = y |> co; see (CWW1)
|
|
| 604 | + , not (hasInlineUnfolding info) -- Not INLINE things: see (CWW4)
|
|
| 605 | + , typeHasFixedRuntimeRep work_ty -- Don't peel off a cast if doing so would
|
|
| 606 | + -- lose the underlying runtime representation.
|
|
| 607 | + -- See Note [Preserve RuntimeRep info in cast w/w]
|
|
| 594 | 608 | , not (isOpaquePragma (idInlinePragma old_bndr)) -- Not for OPAQUE bindings
|
| 595 | 609 | -- See Note [OPAQUE pragma]
|
| 596 | 610 | = do { uniq <- getUniqueM
|
| ... | ... | @@ -637,13 +651,13 @@ tryCastWorkerWrapper env bind_cxt old_bndr bndr (Cast rhs co) |
| 637 | 651 | `setArityInfo` work_arity
|
| 638 | 652 | -- We do /not/ want to transfer OccInfo, Rules
|
| 639 | 653 | -- Note [Preserve strictness in cast w/w]
|
| 640 | - -- and Wrinkle 2 of Note [Cast worker/wrapper]
|
|
| 654 | + -- and (CWW2) of Note [Cast worker/wrapper]
|
|
| 641 | 655 | |
| 642 | 656 | ----------- Worker unfolding -----------
|
| 643 | 657 | -- Stable case: if there is a stable unfolding we have to compose with (Sym co);
|
| 644 | 658 | -- the next round of simplification will do the job
|
| 645 | 659 | -- Non-stable case: use work_rhs
|
| 646 | - -- Wrinkle 3 of Note [Cast worker/wrapper]
|
|
| 660 | + -- See (CWW4) of Note [Cast worker/wrapper]
|
|
| 647 | 661 | mk_worker_unfolding top_lvl work_id work_rhs
|
| 648 | 662 | = case realUnfoldingInfo info of -- NB: the real one, even for loop-breakers
|
| 649 | 663 | unf@(CoreUnfolding { uf_tmpl = unf_rhs, uf_src = src })
|
| ... | ... | @@ -179,8 +179,9 @@ several liked-named Ids bouncing around at the same time---absolute |
| 179 | 179 | mischief.)
|
| 180 | 180 | |
| 181 | 181 | Notice that we refrain from w/w'ing an INLINE function even if it is
|
| 182 | -in a recursive group. It might not be the loop breaker. (We could
|
|
| 183 | -test for loop-breaker-hood, but I'm not sure that ever matters.)
|
|
| 182 | +in a recursive group. It might not be the loop breaker. (We used to
|
|
| 183 | +test for loop-breaker-hood, but see (CWW4) in Note [Cast worker/wrapper]
|
|
| 184 | +in GHC.Core.Opt.Simplify.Iteration.
|
|
| 184 | 185 | |
| 185 | 186 | Note [Worker/wrapper for INLINABLE functions]
|
| 186 | 187 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... | ... | @@ -134,8 +134,6 @@ perPassFlags dflags pass |
| 134 | 134 | -- there may be some INLINE knots still tied, which is tiresomely noisy
|
| 135 | 135 | CoreDoSimplify cfg
|
| 136 | 136 | | SimplPhase InitialPhase <- sm_phase (so_mode cfg)
|
| 137 | - -> True
|
|
| 138 | - | otherwise
|
|
| 139 | 137 | -> False
|
| 140 | 138 | _ -> True
|
| 141 | 139 |
| ... | ... | @@ -610,7 +610,12 @@ hasInlineUnfolding :: IdInfo -> Bool |
| 610 | 610 | -- ^ True of a /non-loop-breaker/ Id that has a /stable/ unfolding that is
|
| 611 | 611 | -- (a) always inlined; that is, with an `UnfWhen` guidance, or
|
| 612 | 612 | -- (b) a DFunUnfolding which never needs to be inlined
|
| 613 | -hasInlineUnfolding info = isInlineUnfolding (unfoldingInfo info)
|
|
| 613 | +--
|
|
| 614 | +-- Very important that this work with `realUnfoldingInfo` and so returns
|
|
| 615 | +-- True even for a loop-breaker that has an INLINE pragma.
|
|
| 616 | +-- See (CWW4) in Note [Cast worker/wrapper] in GHC.Core.Opt.Simplify.Iteration
|
|
| 617 | +-- for discussion, and #26903 for the dire consequences of getting this wrong.
|
|
| 618 | +hasInlineUnfolding info = isInlineUnfolding (realUnfoldingInfo info)
|
|
| 614 | 619 | |
| 615 | 620 | setArityInfo :: IdInfo -> ArityInfo -> IdInfo
|
| 616 | 621 | setArityInfo info ar =
|
| 1 | +{-# LANGUAGE DefaultSignatures #-}
|
|
| 2 | +module T26903 where
|
|
| 3 | + |
|
| 4 | +newtype T a = MkT [a]
|
|
| 5 | + |
|
| 6 | +class C a where
|
|
| 7 | + op :: [a] -> [a] -> T a
|
|
| 8 | + |
|
| 9 | + -- This default method
|
|
| 10 | + -- * Has an INLINE pragma
|
|
| 11 | + -- * Is too big to inline without a pragma
|
|
| 12 | + -- * Has arity zero
|
|
| 13 | + {-# INLINE[1] op #-}
|
|
| 14 | + default op :: Ord a => [a] -> [a] -> T a
|
|
| 15 | + op = \xs ys -> MkT $ if xs>ys then reverse (reverse (reverse (reverse xs)))
|
|
| 16 | + else reverse (reverse (reverse (reverse (xs ++ ys))))
|
|
| 17 | + |
|
| 18 | +instance C Int where {}
|
|
| 19 | + |
|
| 20 | +test :: [Int] -> T Int
|
|
| 21 | +test xs = op [] xs
|
|
| 22 | + -- We expect to see `op` inlined into the RHS of `test`
|
|
| 23 | + |
| 1 | + |
|
| 2 | +==================== Tidy Core ====================
|
|
| 3 | +Result size of Tidy Core
|
|
| 4 | + = {terms: 127, types: 130, coercions: 48, joins: 0/0}
|
|
| 5 | + |
|
| 6 | +$dmop
|
|
| 7 | + = (\ @a_ahq _ $dOrd_aQd xs_atR ys_atS ->
|
|
| 8 | + case $fOrdList_$ccompare $dOrd_aQd xs_atR ys_atS of {
|
|
| 9 | + __DEFAULT ->
|
|
| 10 | + reverse1
|
|
| 11 | + (reverse1 (reverse1 (reverse1 (++ xs_atR ys_atS) []) []) []) [];
|
|
| 12 | + GT -> reverse1 (reverse1 (reverse1 (reverse xs_atR) []) []) []
|
|
| 13 | + })
|
|
| 14 | + `cast` <Co:20> :: ...
|
|
| 15 | + |
|
| 16 | +$fCInt_$cop
|
|
| 17 | + = (\ xs_atR ys_atS ->
|
|
| 18 | + case $fOrdList_$s$ccompare xs_atR ys_atS of {
|
|
| 19 | + __DEFAULT ->
|
|
| 20 | + reverse1
|
|
| 21 | + (reverse1 (reverse1 (reverse1 (++ xs_atR ys_atS) []) []) []) [];
|
|
| 22 | + GT -> reverse1 (reverse1 (reverse1 (reverse xs_atR) []) []) []
|
|
| 23 | + })
|
|
| 24 | + `cast` <Co:11> :: ...
|
|
| 25 | + |
|
| 26 | +$fCInt1_r11s
|
|
| 27 | + = \ xs_atR ys_atS ->
|
|
| 28 | + case $fOrdList_$s$ccompare xs_atR ys_atS of {
|
|
| 29 | + __DEFAULT ->
|
|
| 30 | + reverse1
|
|
| 31 | + (reverse1 (reverse1 (reverse1 (++ xs_atR ys_atS) []) []) []) [];
|
|
| 32 | + GT -> reverse1 (reverse1 (reverse1 (reverse xs_atR) []) []) []
|
|
| 33 | + }
|
|
| 34 | + |
|
| 35 | +$fCInt = C:C ($fCInt1_r11s `cast` <Co:11> :: ...)
|
|
| 36 | + |
|
| 37 | +test4 = reverse1 [] []
|
|
| 38 | + |
|
| 39 | +test3 = reverse1 test4 []
|
|
| 40 | + |
|
| 41 | +test2 = reverse1 test3 []
|
|
| 42 | + |
|
| 43 | +test1 = reverse1 test2 []
|
|
| 44 | + |
|
| 45 | +test
|
|
| 46 | + = \ xs_aKW ->
|
|
| 47 | + case $fOrdList_$s$ccompare [] xs_aKW of {
|
|
| 48 | + __DEFAULT ->
|
|
| 49 | + (reverse1
|
|
| 50 | + (reverse1 (reverse1 (reverse1 (++ [] xs_aKW) []) []) []) [])
|
|
| 51 | + `cast` <Co:3> :: ...;
|
|
| 52 | + GT -> test1 `cast` <Co:3> :: ...
|
|
| 53 | + }
|
|
| 54 | + |
|
| 55 | + |
|
| 56 | + |
| ... | ... | @@ -581,3 +581,4 @@ test('T26722', [grep_errmsg(r'SPEC')], compile, ['-O -dno-typeable-binds']) |
| 581 | 581 | |
| 582 | 582 | test('T26805', [grep_errmsg(r'fromInteger')], compile, ['-O -dno-typeable-binds -ddump-simpl -dsuppress-uniques'])
|
| 583 | 583 | test('T26826', normal, compile, ['-O'])
|
| 584 | +test('T26903', [grep_errmsg(r'reverse')], compile, ['-O -dno-typeable-binds -ddump-simpl -dsuppress-all']) |