Sebastian Graf pushed to branch wip/sg/enter-data at Glasgow Haskell Compiler / GHC
Commits:
-
ad8cf2db
by Sebastian Graf at 2026-08-03T12:14:11+02:00
4 changed files:
Changes:
| ... | ... | @@ -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
|
| ... | ... | @@ -1190,22 +1192,16 @@ emitEnter fun = do |
| 1190 | 1192 | ; adjustHpBackwards
|
| 1191 | 1193 | ; sequel <- getSequel
|
| 1192 | 1194 | ; updfr_off <- getUpdFrameOff
|
| 1193 | - ; align_check <- stgToCmmAlignCheck <$> getStgToCmmConfig
|
|
| 1194 | 1195 | ; 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.
|
|
| 1196 | + -- For a return we jump to stg_enter_data, which returns a tagged
|
|
| 1197 | + -- (hence evaluated) value to our caller and enters an untagged
|
|
| 1198 | + -- closure. getCallMethod picks EnterIt only for values whose type
|
|
| 1199 | + -- rules out functions, which is what that stub is specialised to.
|
|
| 1200 | + -- See Note [Evaluating a value in tail position] in rts/Apply.cmm.
|
|
| 1203 | 1201 | 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
|
|
| 1202 | + { let enter_data = CmmLit (CmmLabel
|
|
| 1203 | + (mkCmmCodeLabel rtsUnitId (fsLit "stg_enter_data")))
|
|
| 1204 | + ; emit $ mkJump profile NativeNodeCall enter_data [fun] updfr_off
|
|
| 1209 | 1205 | ; return AssignedDirectly
|
| 1210 | 1206 | }
|
| 1211 | 1207 |
| ... | ... | @@ -47,6 +47,55 @@ import CLOSURE stg_apply_interp_info; |
| 47 | 47 | import CLOSURE stg_restore_cccs_eval_info;
|
| 48 | 48 | #endif
|
| 49 | 49 | |
| 50 | +/* Note [Evaluating a value in tail position]
|
|
| 51 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 52 | + * stg_enter_data evaluates a closure that is a thunk, a constructor, or an
|
|
| 53 | + * indirection to one, and returns it to the caller's continuation. This is
|
|
| 54 | + * stg_ap_0_fast (below) specialised to that domain: the FUN/PAP/BCO arms of
|
|
| 55 | + * the generic evaluation are unreachable there, and an indirection's own
|
|
| 56 | + * entry code follows the indirection, so what remains is the tag test and an
|
|
| 57 | + * entry jump. emitEnter (GHC.StgToCmm.Expr) jumps here for a tail evaluation,
|
|
| 58 | + * which getCallMethod reaches only for values whose type rules out functions.
|
|
| 59 | + *
|
|
| 60 | + * Three shapes evaluate such a value. Measured on nofib imaginary+spectral
|
|
| 61 | + * (74 benchmarks, cachegrind instruction counts, both sides built with
|
|
| 62 | + * -fproc-alignment=64 so that code placement is held fixed):
|
|
| 63 | + *
|
|
| 64 | + * - Enter unconditionally, reading the entry code out of the closure. This
|
|
| 65 | + * reaches the entry code of an evaluated value, which the enter-taggable
|
|
| 66 | + * invariant rules out. It also costs a load of the info pointer feeding
|
|
| 67 | + * an indirect jump, plus the entry code's own indirect jump to the
|
|
| 68 | + * continuation, whenever the value is already evaluated.
|
|
| 69 | + * - Jump to stg_ap_0_fast. Adds a load of the closure type and a
|
|
| 70 | + * bounds-checked switch on it, both dead on this domain: +0.19%
|
|
| 71 | + * instructions over this stub.
|
|
| 72 | + * - Jump to this stub. +0.07% instructions over entering unconditionally,
|
|
| 73 | + * allocations unchanged. The tag test mostly fails, because values whose
|
|
| 74 | + * tag is statically known are routed to InferedReturnIt by tag inference
|
|
| 75 | + * before they reach here, leaving mostly genuine thunks: fft2 +0.36%,
|
|
| 76 | + * fft +0.24%.
|
|
| 77 | + *
|
|
| 78 | + * Cycles move in both directions and by more than the instruction counts,
|
|
| 79 | + * because what the test replaces is a load feeding an indirect jump rather
|
|
| 80 | + * than straight-line work. Where the value is usually evaluated, primetest
|
|
| 81 | + * runs -1.5% and exact-reals -1.0%; where it is usually a thunk, fft2 runs
|
|
| 82 | + * +0.9% and fft +0.4%.
|
|
| 83 | + *
|
|
| 84 | + * The stub keeps the test out of the code generator's per-site output. Open
|
|
| 85 | + * coding it at each call site costs a temporary, two basic blocks and an
|
|
| 86 | + * explicit return there, which compiling T13960 pays for at around +3%
|
|
| 87 | + * allocations; through the stub the compiler emits one jump, as it did when
|
|
| 88 | + * it entered unconditionally.
|
|
| 89 | + */
|
|
| 90 | + |
|
| 91 | +stg_enter_data ( P_ x )
|
|
| 92 | +{
|
|
| 93 | + if (GETTAG(x) != 0) {
|
|
| 94 | + return (x);
|
|
| 95 | + }
|
|
| 96 | + jump %GET_ENTRY(x) (x);
|
|
| 97 | +}
|
|
| 98 | + |
|
| 50 | 99 | /* ----------------------------------------------------------------------------
|
| 51 | 100 | * Evaluate a closure and return it.
|
| 52 | 101 | *
|
| ... | ... | @@ -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) \
|
| ... | ... | @@ -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);
|