Sebastian Graf pushed to branch wip/sg/enter-data at Glasgow Haskell Compiler / GHC

Commits:

5 changed files:

Changes:

  • changelog.d/27594
    1
    +section: codegen
    
    2
    +synopsis: Returning lifted, already evaluated data no longer enters its closure
    
    3
    +issues: #27594
    
    4
    +mrs: !16445
    
    5
    +description:
    
    6
    +  In ``f (Just x) = x``, GHC must evaluate ``x`` before returning it, and used
    
    7
    +  to do so by entering ``x``'s closure unconditionally. It now tests ``x``'s
    
    8
    +  pointer tag first and returns ``x`` directly when it is evaluated already,
    
    9
    +  entering the closure only otherwise. Scrutinising a value, as in
    
    10
    +  ``case x of ...``, has always tested the tag this way.

  • compiler/GHC/StgToCmm/Expr.hs
    ... ... @@ -36,6 +36,8 @@ import GHC.Cmm.BlockId
    36 36
     import GHC.Cmm hiding ( succ )
    
    37 37
     import GHC.Cmm.Info
    
    38 38
     import GHC.Cmm.Utils ( cmmTagMask, mkWordCLit )
    
    39
    +import GHC.Cmm.CLabel ( mkCmmCodeLabel )
    
    40
    +import GHC.Unit ( rtsUnitId )
    
    39 41
     import GHC.Platform.Tag ( mAX_PTR_TAG )
    
    40 42
     import GHC.Core
    
    41 43
     import GHC.Core.DataCon
    
    ... ... @@ -1183,6 +1185,47 @@ cgIdApp fun_id args = do
    1183 1185
     --     DynFlags, then passed to StgToCmmConfig for this phase.
    
    1184 1186
     
    
    1185 1187
     
    
    1188
    +{- Note [Forcing lifted data]
    
    1189
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    1190
    +Consider
    
    1191
    +
    
    1192
    +  f :: Maybe Bool -> Bool
    
    1193
    +  f (Just x) = x
    
    1194
    +
    
    1195
    +After `f` pattern-matches on the `Just` it must then evaluate `x` and
    
    1196
    +return it to the caller.  It could do so by unconditionally entering the
    
    1197
    +code in x's closure, but we don't want to do so for two reasons:
    
    1198
    +
    
    1199
    +* If the RHS had been `case x of (a,b) -> blah`, GHC generates an inline test
    
    1200
    +  for EPT; for non-tagged pointers, it then pushes a return address and
    
    1201
    +  enters it (see the AssignTo case of emitEnter, below).  It would be
    
    1202
    +  consistent to do the same for tail calls too.
    
    1203
    +
    
    1204
    +* Finally, if we adopt the design described in #23173, if x is untagged, its
    
    1205
    +  entry code might warn or panic.  In that case we definitely should not to enter
    
    1206
    +  it.
    
    1207
    +
    
    1208
    +Where are these decisions taken?  getCallMethod (GHC.StgToCmm.Closure)
    
    1209
    +classifies the application and cgIdApp dispatches on the result.
    
    1210
    +Since `Bool` is not a function type and `x` is not statically EPT
    
    1211
    +(See Note [EPT enforcement]), we land in the EnterIt case under a Return
    
    1212
    +sequel. In that case, emitEnter emits a jump to the RTS stub stg_enter_data
    
    1213
    +(rts/Apply.cmm), which tests the tag, returns the pointer if it is set and
    
    1214
    +enters the closure otherwise.
    
    1215
    +
    
    1216
    +Why a stub here, when the AssignTo case inlines it?  There the continuation is
    
    1217
    +a known address that the code can directly jump to. If we were creating a stub
    
    1218
    +as well, it would become a register-indirect jump that would be difficult to
    
    1219
    +predict by the CPU.
    
    1220
    +Under Return, the continuation's address is unknown, so inlining the stub has
    
    1221
    +no specialisation effect, yet incur more code to generate and place in
    
    1222
    +potentially tight loops.
    
    1223
    +That costs 2.6% more compiler allocation on eval-heavy T13960 and about 19 bytes
    
    1224
    +per site, while nofib stays within +-0.2% runtime either way.
    
    1225
    +
    
    1226
    +See #27594.
    
    1227
    +-}
    
    1228
    +
    
    1186 1229
     emitEnter :: CmmExpr -> FCode ReturnKind
    
    1187 1230
     emitEnter fun = do
    
    1188 1231
       { platform <- getPlatform
    
    ... ... @@ -1190,22 +1233,14 @@ emitEnter fun = do
    1190 1233
       ; adjustHpBackwards
    
    1191 1234
       ; sequel      <- getSequel
    
    1192 1235
       ; updfr_off   <- getUpdFrameOff
    
    1193
    -  ; align_check <- stgToCmmAlignCheck <$> getStgToCmmConfig
    
    1194 1236
       ; case sequel of
    
    1195
    -      -- For a return, we have the option of generating a tag-test or
    
    1196
    -      -- not.  If the value is tagged, we can return directly, which
    
    1197
    -      -- is quicker than entering the value.  This is a code
    
    1198
    -      -- size/speed trade-off: when optimising for speed rather than
    
    1199
    -      -- size we could generate the tag test.
    
    1200
    -      --
    
    1201
    -      -- Right now, we do what the old codegen did, and omit the tag
    
    1202
    -      -- test, just generating an enter.
    
    1237
    +      -- For a return we jump to stg_enter_data, which returns an already
    
    1238
    +      -- tagged pointer to our caller and enters an untagged one.
    
    1239
    +      -- See Note [Forcing lifted data]
    
    1203 1240
           Return -> do
    
    1204
    -        { let entry = entryCode platform
    
    1205
    -                $ closureInfoPtr platform align_check
    
    1206
    -                $ CmmReg (nodeReg platform)
    
    1207
    -        ; emit $ mkJump profile NativeNodeCall entry
    
    1208
    -                        [cmmUntag platform fun] updfr_off
    
    1241
    +        { let enter_data = CmmLit (CmmLabel
    
    1242
    +                             (mkCmmCodeLabel rtsUnitId (fsLit "stg_enter_data")))
    
    1243
    +        ; emit $ mkJump profile NativeNodeCall enter_data [fun] updfr_off
    
    1209 1244
             ; return AssignedDirectly
    
    1210 1245
             }
    
    1211 1246
     
    

  • rts/Apply.cmm
    ... ... @@ -47,6 +47,21 @@ import CLOSURE stg_apply_interp_info;
    47 47
     import CLOSURE stg_restore_cccs_eval_info;
    
    48 48
     #endif
    
    49 49
     
    
    50
    +/* Force a lifted closure whose type rules out functions: return it if it is already
    
    51
    + * tagged, enter it otherwise.  The code generator jumps here to evaluate
    
    52
    + * lifted data in tail position; see Note [Forcing lifted data] in
    
    53
    + * GHC.StgToCmm.Expr for why.
    
    54
    + * NB: Function-typed closures must go through the more general stg_ap_0_fast.
    
    55
    + */
    
    56
    +
    
    57
    +stg_enter_data ( P_ x )
    
    58
    +{
    
    59
    +    if (GETTAG(x) != 0) {
    
    60
    +        return (x);
    
    61
    +    }
    
    62
    +    jump %GET_ENTRY(x) (x);
    
    63
    +}
    
    64
    +
    
    50 65
     /* ----------------------------------------------------------------------------
    
    51 66
      * Evaluate a closure and return it.
    
    52 67
      *
    

  • rts/RtsSymbols.c
    ... ... @@ -842,6 +842,7 @@ extern char **environ;
    842 842
           SymI_HasDataProto(stg_ap_ppppp_info)                                  \
    
    843 843
           SymI_HasDataProto(stg_ap_pppppp_info)                                 \
    
    844 844
           SymI_HasDataProto(stg_ap_0_fast)                                      \
    
    845
    +      SymI_HasDataProto(stg_enter_data)                                     \
    
    845 846
           SymI_HasDataProto(stg_ap_v_fast)                                      \
    
    846 847
           SymI_HasDataProto(stg_ap_f_fast)                                      \
    
    847 848
           SymI_HasDataProto(stg_ap_d_fast)                                      \
    

  • rts/include/stg/MiscClosures.h
    ... ... @@ -300,6 +300,7 @@ RTS_RET(stg_ap_ppppp);
    300 300
     RTS_RET(stg_ap_pppppp);
    
    301 301
     
    
    302 302
     RTS_FUN_DECL(stg_ap_0_fast);
    
    303
    +RTS_FUN_DECL(stg_enter_data);
    
    303 304
     RTS_FUN_DECL(stg_ap_v_fast);
    
    304 305
     RTS_FUN_DECL(stg_ap_f_fast);
    
    305 306
     RTS_FUN_DECL(stg_ap_d_fast);