Skip to content

Unarise fails to split TupleRep RUBBISH argument, resulting in panic during code generation

The following pair of modules triggers a panic when compiled with -O -prof:

{-# LANGUAGE BlockArguments, MagicHash, UnboxedTuples #-}
module A where

import Control.Exception (Exception, throwIO)
import GHC.Exts (RealWorld, State#)
import GHC.Types (IO(..))

type Registers = (# (), () #)
data Result a = Result Registers a

newtype EVM a = EVM { unEVM :: Registers -> IO (Result a) }

pureEVM :: a -> EVM a
pureEVM a = EVM \rs -> pure $ Result rs a

data Capture where
  Capture :: ((b -> EVM c) -> EVM d) -> Capture

instance Show Capture where
  show _ = "Capture"
instance Exception Capture

newtype Handle i r a = Handle { runHandle :: EVM a }

handle :: forall eff a r. (forall b. eff b -> Handle a r b) -> EVM a -> EVM r
handle f = handleVM \_ e rs1 s1 ->
  let IO f' = unEVM (runHandle (f e)) rs1 in
  case f' s1 of (# _, _ #) -> (# #)
{-# INLINE handle #-}

handleVM :: (forall b. Registers -> eff b -> Registers -> State# RealWorld -> (# #)) -> EVM a -> EVM r
handleVM f (EVM g) = EVM \rs -> do
  let !_ = f rs
  Result _ _ <- g rs
  undefined

control0 :: ((a -> EVM i) -> EVM r) -> Handle i r a
control0 f = controlWithMode f
{-# SCC control0 #-}

controlWithMode :: ((a -> EVM i) -> EVM r) -> Handle i r a
controlWithMode f = Handle (EVM \_ -> throwIO $! Capture f)
{-# SCC controlWithMode #-}
{-# LANGUAGE BlockArguments, GADTs, LambdaCase #-}
module B where
import A

data Coroutine i a where
  Yield :: Coroutine i i

data Status i a = Yielded !(i -> EVM a)

runCoroutine :: forall i a. EVM a -> EVM (Status i a)
runCoroutine = handle @(Coroutine i) \case
  Yield -> control0 \k -> pureEVM $! Yielded k
$ ghc -O -prof A B
[1 of 2] Compiling A                ( A.hs, A.o )
[2 of 2] Compiling B                ( B.hs, B.o )

<no location info>: error:
    panic! (the 'impossible' happened)
  GHC version 9.6.2:
	typePrimRep1
  forall {k :: TYPE ('TupleRep '[LiftedRep, LiftedRep])}. k
  [LiftedRep, LiftedRep]
  Call stack:
      CallStack (from HasCallStack):
        callStackDoc, called at compiler/GHC/Utils/Panic.hs:189:37 in ghc:GHC.Utils.Panic
        pprPanic, called at compiler/GHC/Types/RepType.hs:559:12 in ghc:GHC.Types.RepType

The test case can almost certainly be reduced much further, but it’s surprisingly fiddly.

Some analysis

If we look at the output of -ddump-stg-final for B.hs, we see the following definition:

$wlvl =
    CCS_DONT_CARE \r [void_0E void_0E]
        let {
          f_s239 =
              CCCS \r [k_s23a us_g23v us_g23w void_0E]
                  case k_s23a of conrep_s23d {
                  __DEFAULT ->
                  let { sat_s23e = CCCS Yielded! [conrep_s23d]; } in
                  let { sat_s23f = CCCS Result! [us_g23v us_g23w sat_s23e];
                  } in  Solo# [sat_s23f];
                  };
        } in 
          case
              let {
                sat_s23i = CCCS \r [us_g23y us_g23z void_0E] control3 void# f_s239;
              } in  sat_s23i<TagProper>
          of
          sat_s23j
          {
          __DEFAULT ->
          case sat_s23j RUBBISH('TupleRep '[LiftedRep, LiftedRep]) void# of {
          Solo# _ -> (##) [];
          };
          };

Note in particular the presence of RUBBISH('TupleRep '[LiftedRep, LiftedRep]), and also note that sat_s23j is applied to the wrong number of arguments (and this is not caught by -dlint). This argument like it should probably have been split into two arguments by unarise.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information