Simon Jakobi pushed to branch wip/sjakobi/T25181 at Glasgow Haskell Compiler / GHC

Commits:

4 changed files:

Changes:

  • compiler/GHC/Core/Opt/Simplify/Iteration.hs
    ... ... @@ -3218,6 +3218,35 @@ rebuildCase, reallyRebuildCase
    3218 3218
        -> SimplCont
    
    3219 3219
        -> SimplM (SimplFloats, OutExpr)
    
    3220 3220
     
    
    3221
    +--------------------------------------------------
    
    3222
    +--      0. ANF-ise a manifest-value seq# argument
    
    3223
    +--------------------------------------------------
    
    3224
    +
    
    3225
    +rebuildCase (saf,env) scrut case_bndr alts@[Alt _ [_token, res] rhs] cont
    
    3226
    +  -- ANF-ise `case seq# (Con e1 .. en) s of (# s', r #) -> case r of ...`
    
    3227
    +  -- to `let a1 = e1 .. in case seq# (Con a1 .. an) s of ...`, so that
    
    3228
    +  -- simplAlt can give `r` an *expandable* unfolding `Con a1 .. an` and
    
    3229
    +  -- case-of-known-constructor can see through the seq#.
    
    3230
    +  -- Guarded on `r` being scrutinised immediately, which guarantees that the
    
    3231
    +  -- unfolding pays off; otherwise the let-floats would just be inlined back
    
    3232
    +  -- on the next iteration. See (SEQ5) in Note [seq# magic] in GHC.Types.Id.Make
    
    3233
    +  | (Var f, [ty_a, ty_s, arg, tok]) <- collectArgs scrut
    
    3234
    +  , f `hasKey` seqHashKey
    
    3235
    +  , scrutinises_res rhs
    
    3236
    +  , exprIsManifestValue arg
    
    3237
    +  , (_, con_args) <- collectArgs arg
    
    3238
    +  , any (\a -> isValArg a && not (exprIsTrivial a)) con_args
    
    3239
    +  = do { (anf_floats, arg') <- prepareRhs env NotTopLevel (fsLit "sat") arg
    
    3240
    +       ; let floats = emptyFloats env `addLetFloats` anf_floats
    
    3241
    +             env'   = env `setInScopeFromF` floats
    
    3242
    +             scrut' = Var f `mkApps` [ty_a, ty_s, arg', tok]
    
    3243
    +       ; (floats2, expr') <- rebuildCase (saf,env') scrut' case_bndr alts cont
    
    3244
    +       ; return (floats `addFloats` floats2, expr') }
    
    3245
    +  where
    
    3246
    +    scrutinises_res (Case (Var v) _ _ _) = v == res
    
    3247
    +    scrutinises_res (Tick _ e)           = scrutinises_res e
    
    3248
    +    scrutinises_res _                    = False
    
    3249
    +
    
    3221 3250
     --------------------------------------------------
    
    3222 3251
     --      1. Eliminate the case if there's a known constructor
    
    3223 3252
     --------------------------------------------------
    
    ... ... @@ -3292,7 +3321,21 @@ rebuildCase (saf,env) scrut case_bndr alts@[Alt _ bndrs rhs] cont
    3292 3321
            ; (floats2, expr') <- simplExprF env' rhs cont
    
    3293 3322
            ; return (floats1 `addFloats` floats2, expr') }
    
    3294 3323
     
    
    3295
    -  -- 2c. Try the seq rules if
    
    3324
    +  -- 2c. Discard a seq# on a manifest value if its result is dead:
    
    3325
    +  --       case seq# <manifest-value> s of (# s', _ #) -> rhs ==> rhs[s/s']
    
    3326
    +  --     See (SEQ5) in Note [seq# magic] in GHC.Types.Id.Make
    
    3327
    +  | [token_out, res] <- bndrs
    
    3328
    +  , isDeadBinder res, isDeadBinder case_bndr
    
    3329
    +  , (Var f, [_ty1, _ty2, arg, Var token_in]) <- collectArgs scrut
    
    3330
    +  , f `hasKey` seqHashKey
    
    3331
    +  , exprIsManifestValue arg
    
    3332
    +  , exprOkToDiscard arg
    
    3333
    +  = do { tick (CaseElim case_bndr)
    
    3334
    +       ; (floats1, env')  <- simplAuxBind (saf,env) token_out (Var token_in)
    
    3335
    +       ; (floats2, expr') <- simplExprF env' rhs cont
    
    3336
    +       ; return (floats1 `addFloats` floats2, expr') }
    
    3337
    +
    
    3338
    +  -- 2d. Try the seq rules if
    
    3296 3339
       --     a) it binds only the case binder
    
    3297 3340
       --     b) a rule for seq applies
    
    3298 3341
       -- See Note [User-defined RULES for seq] in GHC.Types.Id.Make
    
    ... ... @@ -3317,6 +3360,24 @@ rebuildCase (saf,env) scrut case_bndr alts cont
    3317 3360
       | otherwise
    
    3318 3361
       = reallyRebuildCase (saf,env) scrut case_bndr alts cont
    
    3319 3362
     
    
    3363
    +exprIsManifestValue :: OutExpr -> Bool
    
    3364
    +-- True of expressions that are values no matter what the context:
    
    3365
    +-- literals, lambdas, and (saturated or partial) constructor applications.
    
    3366
    +-- Unlike exprIsHNF, never true for a variable that is merely *known* to be
    
    3367
    +-- evaluated: for the seq#-discarding transformations that use this
    
    3368
    +-- predicate, such a variable carries a dependency on its binding site that
    
    3369
    +-- must not be lost. See (SEQ5) in Note [seq# magic] in GHC.Types.Id.Make.
    
    3370
    +exprIsManifestValue e = go e
    
    3371
    +  where
    
    3372
    +    go (Cast e _) = go e
    
    3373
    +    go (Tick t e) = not (tickishCounts t) && go e
    
    3374
    +    go (Lit _)    = True
    
    3375
    +    go (Lam b e)  = isRuntimeVar b || go e
    
    3376
    +    go (Var f)    = isDataConWorkId f
    
    3377
    +    go e@(App {}) | (Var f, _) <- collectArgs e
    
    3378
    +                  = isDataConWorkId f && exprIsHNF e
    
    3379
    +    go _          = False
    
    3380
    +
    
    3320 3381
     doCaseToLet :: OutExpr          -- Scrutinee
    
    3321 3382
                 -> InId             -- Case binder
    
    3322 3383
                 -> Bool
    
    ... ... @@ -3590,7 +3651,21 @@ simplAlt (saf,env) scrut' _ case_bndr' bndr_swap' cont' (Alt (DataAlt con) vs rh
    3590 3651
                   con_app = mkConApp2 con inst_tys' vs'
    
    3591 3652
                   env''   = addAltUnfoldings env' case_bndr' bndr_swap' con_app
    
    3592 3653
     
    
    3593
    -        ; rhs' <- simplAltExprC (saf,env'') rhs cont'
    
    3654
    +              -- If the scrutinee is (seq# <manifest-value> s), record that
    
    3655
    +              -- value as the unfolding of the result binder, so that
    
    3656
    +              -- case-of-known-constructor can see through the seq#.
    
    3657
    +              -- See (SEQ5) in Note [seq# magic] in GHC.Types.Id.Make
    
    3658
    +              env_seq | Just scr <- scrut'
    
    3659
    +                      , isUnboxedTupleDataCon con
    
    3660
    +                      , [_token, res] <- vs'
    
    3661
    +                      , (Var f, [_ty1, _ty2, arg, _]) <- collectArgs scr
    
    3662
    +                      , f `hasKey` seqHashKey
    
    3663
    +                      , exprIsManifestValue arg
    
    3664
    +                      = addBinderUnfolding env'' res $
    
    3665
    +                        mkSimpleUnfolding (seUnfoldingOpts env) arg
    
    3666
    +                      | otherwise = env''
    
    3667
    +
    
    3668
    +        ; rhs' <- simplAltExprC (saf,env_seq) rhs cont'
    
    3594 3669
             ; return (Alt (DataAlt con) vs' rhs') }
    
    3595 3670
     
    
    3596 3671
     {- Note [Adding evaluatedness info to pattern-bound variables]
    

  • compiler/GHC/Types/Id/Make.hs
    ... ... @@ -2516,6 +2516,43 @@ Things to note
    2516 2516
       example in #24334 (immortalised as test T24334) for why it should be done in
    
    2517 2517
       CorePrep.
    
    2518 2518
     
    
    2519
    +(SEQ5)
    
    2520
    +  T25181 shows that we additionally want to unbox through seq# when its
    
    2521
    +  argument is a manifest value:
    
    2522
    +
    
    2523
    +      case seq# (f x, y) s of (# s', p #) ->
    
    2524
    +      case p of (a, b) -> rhs
    
    2525
    +
    
    2526
    +  Evaluating a manifest value (a literal, lambda, or constructor application;
    
    2527
    +  see exprIsManifestValue in GHC.Core.Opt.Simplify.Iteration) is a no-op, so
    
    2528
    +  here seq# guarantees nothing beyond ordinary data dependencies; the only
    
    2529
    +  remaining cost is the allocation of the value itself, which we want the
    
    2530
    +  Simplifier to eliminate where possible. Hence three cooperating
    
    2531
    +  transformations in GHC.Core.Opt.Simplify.Iteration:
    
    2532
    +
    
    2533
    +  * rebuildCase (0) ANF-ises the seq# argument when the result is scrutinised
    
    2534
    +    immediately: `let sat = f x in case seq# (sat, y) s of ...`. Without this
    
    2535
    +    the unfolding below would be a con-app with non-trivial fields, which is
    
    2536
    +    not *expandable*, so exprIsConApp_maybe would refuse to expand it and
    
    2537
    +    case-of-known-constructor would never fire.
    
    2538
    +
    
    2539
    +  * simplAlt gives `p` an unfolding `(sat, y)`, so that
    
    2540
    +    case-of-known-constructor rewrites `case p of (a, b) -> rhs` to
    
    2541
    +    `let a = sat; b = y in rhs`, typically making `p` dead.
    
    2542
    +
    
    2543
    +  * rebuildCase (2c) then discards the seq# when its result is dead:
    
    2544
    +      case seq# <manifest-value> s of (# s', _ #) -> rhs[s']  ==>  rhs[s]
    
    2545
    +
    
    2546
    +  Soundness: unlike the late, unsound seqRule of #24334, all transformations
    
    2547
    +  demand a *manifest* value, not a variable that is merely known to be
    
    2548
    +  evaluated (hence exprIsManifestValue, not exprIsHNF). Discarding
    
    2549
    +  `seq# x s` for an evaluated variable `x` would lose the syntactic
    
    2550
    +  dependency of the continuation on `x`, allowing lazy computations to float
    
    2551
    +  past x's binding site and thereby evaluate too early; see the `pseq`
    
    2552
    +  example in #24334. A manifest value, by contrast, converges unconditionally
    
    2553
    +  and depends on nothing except its own (pure, discardable) field
    
    2554
    +  expressions, so no ordering constraint is lost.
    
    2555
    +
    
    2519 2556
     Implementing seq#.  The compiler has magic for `seq#` in
    
    2520 2557
     
    
    2521 2558
     - GHC.CoreToStg.Prep.cpeRhsE: Implement (SEQ4).
    

  • testsuite/tests/perf/should_run/T25181.hs
    1
    +-- T25181
    
    2
    +import Control.Exception (evaluate)
    
    3
    +
    
    4
    +import Prelude hiding (repeat)
    
    5
    +
    
    6
    +repeat :: a -> [a]
    
    7
    +repeat a = res
    
    8
    +  where res = a : res
    
    9
    +{-# NOINLINE repeat #-}
    
    10
    +
    
    11
    +silly :: [(Int, Int)] -> IO ()
    
    12
    +silly = foldr go (pure ())
    
    13
    +  where
    
    14
    +    go p r = do
    
    15
    +      (x, y) <- evaluate p
    
    16
    +      (x', y') <- evaluate (x + 3, y)
    
    17
    +      evaluate y'
    
    18
    +      r
    
    19
    +
    
    20
    +main :: IO ()
    
    21
    +-- 10,000,000 repetitions take only a twentieth of a second,
    
    22
    +-- but allocations go up dramatically if the result is not
    
    23
    +-- known evaluated.
    
    24
    +main = silly $ take 10000000 $ repeat (1,1)

  • testsuite/tests/perf/should_run/all.T
    ... ... @@ -367,6 +367,12 @@ test('T15226a',
    367 367
         compile_and_run,
    
    368 368
         ['-O'])
    
    369 369
     
    
    370
    +test('T25181',
    
    371
    +    [collect_stats('bytes allocated',5),
    
    372
    +     only_ways(['normal'])],
    
    373
    +    compile_and_run,
    
    374
    +    ['-O'])
    
    375
    +
    
    370 376
     test('T15263',
    
    371 377
         [collect_stats('bytes allocated', 4),
    
    372 378
          only_ways(['normal'])],