Terribly sad loss of strictness and unboxing due to `throw`
Consider this heavily-used code in GHC.Core.TyCo.Subst
```
extendTCvSubst :: Subst -> TyCoVar -> Type -> Subst
extendTCvSubst subst v ty
| isTyVar v
= extendTvSubst subst v ty
| CoercionTy co <- ty
= extendCvSubst subst v co
| otherwise
= pprPanic "extendTCvSubst" (ppr v <+> text "|->" <+> ppr ty)
```
Would you not expect that take `Subst` as an unboxed argument? And you'd expect it to
have the CPR property. Similarly:
```
zipTCvSubst :: HasDebugCallStack => [TyCoVar] -> [Type] -> Subst
zipTCvSubst tcvs tys
= zip_tcvsubst tcvs tys $
mkEmptySubst $ mkInScopeSet $ shallowTyCoVarsOfTypes tys
where zip_tcvsubst :: [TyCoVar] -> [Type] -> Subst -> Subst
zip_tcvsubst (tv:tvs) (ty:tys) subst
= zip_tcvsubst tvs tys (extendTCvSubst subst tv ty)
zip_tcvsubst [] [] subst = subst -- empty case
zip_tcvsubst _ _ _ = pprPanic "zipTCvSubst: length mismatch"
(ppr tcvs <+> ppr tys)
```
Would you not expect that `zip_tcvsubst` loop to carry the `Subst` argument around unboxed, rather than reboxing it in every loop iteration? And to have the CPR property.
Of course you would! **But it doesn't in either case**. Result: terribly bad code for these functions.
(I tripped over this when doing some performance debugging in GHC (in !12893), but it will affect lots of calls -- `pprPanic` is used a lot!)
## Diagnosis
I tried this, as a standalone reproducer
```
type Var = Int
type MyType = Int
data Subst = Subst (Map Var MyType) (Map Var MyType) (Map Var MyType)
{-# NOINLINE isTyVar #-}
{-# NOINLINE isCoVar #-}
isTyVar v = v>200
isCoVar v = v<0
extendTvSubst (Subst ids tvs cvs) v ty
= Subst ids (insert v ty tvs) cvs
extendCvSubst (Subst ids tvs cvs) v ty
= Subst ids tvs (insert v ty cvs)
extendTCvSubst :: Subst -> Var -> MyType -> Subst
extendTCvSubst subst v ty
| isTyVar v
= extendTvSubst subst v ty
| isCoVar v
= extendCvSubst subst v ty
| otherwise
= error ("extendTCvSubst" ++ show v ++ show ty)
```
And lo! The `Subst` argument is unboxed as I expect. The difference seems to be this:
* The strictness signature of `error` is `Strictness: <S><S>b, CPR: b`
* The strictness signature of `pprPanic` is `Strictness: <L><L><LC(S,L)>x, CPR: b]`
Notice the `b` vs the `x`. Here `b` means "bottom" (good), but `x`
means "might return a precise exception in the I/O monad" which is
very bad.
And why does that happen? `pprPanic` ultimately calls `throw` which is defined thus,
in `ghc-internal:GHC.Internal.Exceptoin`:
```
throw :: forall (r :: RuntimeRep). forall (a :: TYPE r). forall e.
(HasCallStack, Exception e) => e -> a
throw e =
let !se = unsafePerformIO (toExceptionWithBacktrace e)
in raise# se
```
whereas `error` is defined like this in `ghc-internal:GHC.Internal.Err`:
```
error :: forall (r :: RuntimeRep). forall (a :: TYPE r).
HasCallStack => [Char] -> a
error s = raise# (errorCallWithCallStackException s ?callStack)
```
Sure enough, the strictness of `throw` is `Strictness: <ML><SP(A,A,LC(S,L),A,A,SC(S,L))><L>x, CPR: b]`
I think this means that **any call to `throw` will defeat strictness analysis**. That's pretty bad.
issue