[Take 2] Unexpected duplicate join points in "Core" output?

Viktor Dukhovni ietf-dane at dukhovni.org
Sat Nov 20 19:36:40 UTC 2021


On Sat, Nov 20, 2021 at 01:54:36PM -0500, Viktor Dukhovni wrote:

> Is there some way for GHC to figure out to not float out such cheap
> computations?  The 'Result' constructor is strict, so there's no cost to
> evaluating `used > 0`, and cloning the entire computation is I think
> the more unfortunate choice...

I managed to get the loop to not emit duplicate code bloat by
inserting another NOINLINE term:

        !keepGoing = acc < q || acc == q && d <= r
        {-# NOINLINE keepGoing #-}

Thus the below produces Core with no significant bloat, matching roughly
what one might (reasonably?/naively?) expect.  But I am reluctant to
actually include such work-arounds in the PR, the code that produces
more "bloated" Core is easier to understand and maintain...

    _digits :: Accum -> Accum -> BI.ByteString -> Accum -> Result                                                                     {-# INLINE _digits #-}                                                                                                            _digits !q !r !(BI.BS !fp !len) = \ !acc ->                                                                                           BI.accursedUnutterablePerformIO $                                                                                                     BI.unsafeWithForeignPtr fp $ \ptr -> do                                                                                               let end = ptr `plusPtr` len                                                                                                       go ptr end ptr acc                                                                                                      where                                                                                                                               go start end = loop                                                                                                                 where                                                                                                                               loop !ptr !acc | ptr == end                                                                                                           = return $ Result (ptr `minusPtr` start) acc                                                                                  loop !ptr !acc = getDigit >>= \ !d ->
                if | d <= 9    -> update d
                   | otherwise -> return $ Result (ptr `minusPtr` start) acc
              where
                fromDigit = \w -> fromIntegral w - 0x30 -- i.e. w - '0'
                --
                {-# NOINLINE getDigit #-}
                getDigit | ptr /= end = fromDigit <$> peek ptr
                         | otherwise  = pure 10  -- End of input
                --
                update d
                    | keepGoing = loop (ptr `plusPtr` 1) (acc * 10 + d)
                    | otherwise = return Overflow
                  where
                    {-# NOINLINE keepGoing #-}
                    !keepGoing = acc < q || acc == q && d <= r

The Core code is now, with the duplicate comparison as the only visible
inefficiency.

    -- The exit/exit3 joins could be combined but are small,
    -- ditto with exit1/exit2.

    Rec {
    -- RHS size: {terms: 190, types: 146, coercions: 0, joins: 8/10}
    $wconsume
      :: ByteString -> Int# -> Word# -> Maybe (Word64, ByteString)
    $wconsume
      = \ (w :: ByteString) (ww :: Int#) (ww1 :: Word#) ->
          case w of wild {
            Empty ->
              case ww of {
                __DEFAULT -> Just (W64# ww1, Empty);
                0# -> Nothing
              };
            Chunk dt dt1 dt2 cs ->
              let {
                end :: Addr#
                end = plusAddr# dt dt2 } in
              join {
                $s$j
                  :: Int# -> Word# -> State# RealWorld -> Maybe (Word64, ByteString)
                $s$j (sc :: Int#) (sc1 :: Word#) (sc2 :: State# RealWorld)
                  = case touch# dt1 sc2 of { __DEFAULT ->
                    case ==# sc dt2 of {
                      __DEFAULT ->
                        case ># sc 0# of {
                          __DEFAULT ->
                            case ww of {
                              __DEFAULT -> Just (W64# sc1, wild);
                              0# -> Nothing
                            };
                          1# -> Just (W64# sc1, Chunk (plusAddr# dt sc) dt1 (-# dt2 sc) cs)
                        };
                      1# -> $wconsume cs (orI# ww sc) sc1
                    }
                    } } in
              join {
                exit
                  :: Addr# -> Word# -> State# RealWorld -> Maybe (Word64, ByteString)
                exit (ww2 :: Addr#) (ww3 :: Word#) (ipv :: State# RealWorld)
                  = jump $s$j (minusAddr# ww2 dt) ww3 ipv } in
              join {
                exit1 :: State# RealWorld -> Maybe (Word64, ByteString)
                exit1 (ipv :: State# RealWorld)
                  = case touch# dt1 ipv of { __DEFAULT -> Nothing } } in
              join {
                exit2 :: State# RealWorld -> Maybe (Word64, ByteString)
                exit2 (ipv :: State# RealWorld)
                  = case touch# dt1 ipv of { __DEFAULT -> Nothing } } in
              join {
                exit3
                  :: Addr# -> Word# -> State# RealWorld -> Maybe (Word64, ByteString)
                exit3 (ww2 :: Addr#) (ww3 :: Word#) (w1 :: State# RealWorld)
                  = jump $s$j (minusAddr# ww2 dt) ww3 w1 } in
              joinrec {
                $wloop
                  :: Addr# -> Word# -> State# RealWorld -> Maybe (Word64, ByteString)
                $wloop (ww2 :: Addr#) (ww3 :: Word#) (w1 :: State# RealWorld)
                  = case eqAddr# ww2 end of {
                      __DEFAULT ->
                        join {
                          getDigit :: State# RealWorld -> Maybe (Word64, ByteString)
                          getDigit (eta :: State# RealWorld)
                            = case readWord8OffAddr# ww2 0# eta of { (# ipv, ipv1 #) ->
                              let {
                                ipv2 :: Word#
                                ipv2 = minusWord# (word8ToWord# ipv1) 48## } in
                              case leWord# ipv2 9## of {
                                __DEFAULT -> jump exit ww2 ww3 ipv;
                                1# ->
                                  join {
                                    keepGoing :: Maybe (Word64, ByteString)
                                    keepGoing
                                      = case ltWord# ww3 1844674407370955161## of {
                                          __DEFAULT ->
                                            case ww3 of {
                                              __DEFAULT -> jump exit1 ipv;
                                              1844674407370955161## ->
                                                case leWord# ipv2 5## of {
                                                  __DEFAULT -> jump exit2 ipv;
                                                  1# ->
                                                    jump $wloop
                                                      (plusAddr# ww2 1#)
                                                      (plusWord# 18446744073709551610## ipv2)
                                                      ipv
                                                }
                                            };
                                          1# ->
                                            jump $wloop
                                              (plusAddr# ww2 1#)
                                              (plusWord# (timesWord# ww3 10##) ipv2)
                                              ipv
                                        } } in
                                  jump keepGoing
                              }
                              } } in
                        jump getDigit w1;
                      1# -> jump exit3 ww2 ww3 w1
                    }; } in
              jump $wloop dt ww1 realWorld#
          }
    end Rec }

-- 
    Viktor.


More information about the ghc-devs mailing list