Opened 3 years ago

Closed 3 years ago

#13286 closed bug (fixed)

Late floating of join points

Reported by: simonpj Owned by:
Priority: normal Milestone:
Component: Compiler Version: 8.0.1
Keywords: JoinPoints Cc:
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

Consider this, from GHC.Real:

GHC.Real.$w$s^1 [InlPrag=[0]] :: Int -> Int# -> Int#
GHC.Real.$w$s^1 =
  \ (w_s6xh :: Int) (ww_s6xl :: Int#) ->
    case tagToEnum# @ Bool (<# ww_s6xl 0#) of {
      False ->
        case ww_s6xl of wild1_XdK {
          __DEFAULT ->
            case w_s6xh of { I# ww2_s6xa ->
            joinrec {
              $wf_s6xg [InlPrag=[0], Occ=LoopBreaker] :: Int# -> Int# -> Int#
              [LclId[JoinId(2)], Arity=2, Str=<S,U><S,U>, Unf=OtherCon []]
              $wf_s6xg (ww3_X6Hi :: Int#) (ww4_s6xe :: Int#) =
                case remInt# ww4_s6xe 2# of {
                  __DEFAULT ->
                    case ww4_s6xe of wild3_Xe3 {
                      __DEFAULT ->
                        joinrec {
                          $wg_s6x5 [InlPrag=[0], Occ=LoopBreaker]
                            :: Int# -> Int# -> Int# -> Int#
                          [LclId[JoinId(3)], Arity=3, Str=<S,U><S,U><S,U>, Unf=OtherCon []]
                          $wg_s6x5 (ww5_s6wV :: Int#) (ww6_s6wZ :: Int#) (ww7_s6x3 :: Int#) =
                            case remInt# ww6_s6wZ 2# of {
                              __DEFAULT ->
                                case ww6_s6wZ of wild5_Xen {
                                  __DEFAULT ->
                                    jump $wg_s6x5
                                      (*# ww5_s6wV ww5_s6wV)
                                      (quotInt# (-# wild5_Xen 1#) 2#)
                                      (*# ww5_s6wV ww7_s6x3);
                                  1# -> *# ww5_s6wV ww7_s6x3
                                };
                              0# ->
                                jump $wg_s6x5
                                  (*# ww5_s6wV ww5_s6wV) (quotInt# ww6_s6wZ 2#) ww7_s6x3
                            }; } in
                        jump $wg_s6x5
                          (*# ww3_X6Hi ww3_X6Hi) (quotInt# (-# wild3_Xe3 1#) 2#) ww3_X6Hi;
                      1# -> ww3_X6Hi
                    };
                  0# -> jump $wf_s6xg (*# ww3_X6Hi ww3_X6Hi) (quotInt# ww4_s6xe 2#)
                }; } in
            jump $wf_s6xg ww2_s6xa wild1_XdK
            };
          0# -> 1#
        };
      True -> case GHC.Real.^2 of wild1_00 { }
    }

Note those two joinrecs. Neither has any free variables. So we could float them to top level, as ordinary functions, thus

$wg_s6x5 [InlPrag=[0], Occ=LoopBreaker]
  :: Int# -> Int# -> Int# -> Int#
$wg_s6x5 (ww5_s6wV :: Int#) (ww6_s6wZ :: Int#) (ww7_s6x3 :: Int#) =
  case remInt# ww6_s6wZ 2# of {
    __DEFAULT ->
      case ww6_s6wZ of wild5_Xen {
        __DEFAULT ->
          jump $wg_s6x5
            (*# ww5_s6wV ww5_s6wV)
            (quotInt# (-# wild5_Xen 1#) 2#)
            (*# ww5_s6wV ww7_s6x3);
        1# -> *# ww5_s6wV ww7_s6x3
      };
    0# ->
      $wg_s6x5 (*# ww5_s6wV ww5_s6wV) (quotInt# ww6_s6wZ 2#) ww7_s6x3

$wf_s6xg [InlPrag=[0], Occ=LoopBreaker] :: Int# -> Int# -> Int#
$wf_s6xg (ww3_X6Hi :: Int#) (ww4_s6xe :: Int#) =
  case remInt# ww4_s6xe 2# of {
    __DEFAULT ->
      case ww4_s6xe of wild3_Xe3 {
        __DEFAULT ->
          $wg_s6x5
            (*# ww3_X6Hi ww3_X6Hi) (quotInt# (-# wild3_Xe3 1#) 2#) ww3_X6Hi;
        1# -> ww3_X6Hi
      };
    0# -> $wf_s6xg (*# ww3_X6Hi ww3_X6Hi) (quotInt# ww4_s6xe 2#)

GHC.Real.$w$s^1 [InlPrag=[0]] :: Int -> Int# -> Int#
GHC.Real.$w$s^1 =
  \ (w_s6xh :: Int) (ww_s6xl :: Int#) ->
    case tagToEnum# @ Bool (<# ww_s6xl 0#) of {
      False ->
        case ww_s6xl of wild1_XdK {
          __DEFAULT ->
            case w_s6xh of { I# ww2_s6xa -> $wf_s6xg ww2_s6xa wild1_XdK  };
          0# -> 1#
        };
      True -> case GHC.Real.^2 of wild1_00 { }
    }

Is this better?

  • Better before: the nested join points don't allocate a closure; but the top-level defns do build a (never used) closure and slow entry point.
  • Better after: the externally-visible function might inline more at call sites in other modules.

So it's a bit moot. It has something of the flavour of the late lambda-lifting pass.

For now I'm doing nothing; just recording the observation. The simple thing is not to float.

Change History (4)

comment:1 Changed 3 years ago by simonpj

Another example is simplCore/should_compile/spec-inline.hs.

comment:2 Changed 3 years ago by simonpj

Another: perf/should_run/MethSharing improves if you do float to top level.

comment:3 Changed 3 years ago by David Feuer <David.Feuer@…>

In 6eb52cfc/ghc:

Improve SetLevels for join points

C.f. Trac #13286, #13236

* Never destroy a join point unless it goes to top level
  See Note [Floating join point bindings]

* Never float a MFE if it has a free join variable
  Note [Free join points]

* Stop treating nullary join points specially

* Enforce the invariant that le_join_ceil >= le_ctxt_lvl
  (Needs more thought...)

Reviewers: austin, bgamari

Subscribers: thomie

Differential Revision: https://phabricator.haskell.org/D3199

comment:4 Changed 3 years ago by simonpj

Resolution: fixed
Status: newclosed

If you read Note [Floating join point bindings] you'll see that in the end I decide that we should float join point to top level if they can go there. I got better results in a handful of cases (see the Note, and comment:1, comment:2), so it seems like a modest win.

Note: See TracTickets for help on using tickets.