Not enough boxing in demand analysis
In GHC.Types.Demand I see
plusSubDmd :: SubDemand -> SubDemand -> SubDemand
-- Shortcuts for neutral and absorbing elements.
-- Below we assume that Boxed always wins.
But in the code compiled for GHC.Core.Map.Type, I found some terribly sub-optimal code, like this:
$wgo_s7Ti [InlPrag=[2], Occ=LoopBreaker]
:: CmEnv -> Type -> CmEnv -> Type -> TypeEquality
[LclId[StrictWorker([])],
Arity=4,
Str=<SL><SL><L><L>,
Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
WorkFree=True, Expandable=True, Guidance=NEVER}]
$wgo_s7Ti
= \ (ww_s7T9 [Dmd=SL] :: CmEnv)
(ww_s7Ta [Dmd=SL] :: Type)
(ww_s7Te :: CmEnv)
(ww_s7Tf :: Type) ->
case ww_s7Te of ww_X1 { CME ipv_s817 ipv_s818 ->
case ww_s7T9 of wild_s77i { CME bx_s77j ds_s77k ->
... many cases
gos_s77g wild_s77i ww_X1 tys_a4kW tys'_a4kY
... many cases
That is, $wgo
is strict in both CmEnv
arguments, and takes them apart,
but still requires them boxed. (This is after strictness analysis and worker/wrapper.)
Why? After faffing around for quite a while I found one of the many recursive
calls inside $wgo
was this, which uses the boxed values.
gos_r8bK wild_s77i ww_X1 tys_a4kW tys'_a4kY
And gos
looks like this:
gos_s77g [Occ=LoopBreaker]
:: CmEnv -> CmEnv -> [KindOrType] -> [KindOrType] -> TypeEquality
[LclId,
Arity=4,
Str=<L><L><SL><SL> ]
gos_r8bK
= \ (ds_s8n8 :: GHC.Core.Map.Type.CmEnv)
(ds1_s8n9 :: GHC.Core.Map.Type.CmEnv)
(ds2_s8na [Occ=Once1!] :: [GHC.Core.TyCo.Rep.KindOrType])
(ds3_s8nb [Occ=Once2!] :: [GHC.Core.TyCo.Rep.KindOrType]) ->
case ds2_s8na of {
[] ->
case ds3_s8nb of {
[] -> GHC.Core.Map.Type.TEQ;
: _ [Occ=Dead] _ [Occ=Dead] -> GHC.Core.Map.Type.TNEQ
};
: ty1_s8ng [Occ=Once1] tys1_s8nh [Occ=Once2] ->
case ds3_s8nb of {
[] -> GHC.Core.Map.Type.TNEQ;
: ty2_s8nj [Occ=Once1] tys2_s8nk [Occ=Once2] ->
case GHC.Core.Map.Type.$wgo ds_s8n8 ty1_s8ng ds1_s8n9 ty2_s8nj of {
GHC.Core.Map.Type.TNEQ -> GHC.Core.Map.Type.TNEQ;
GHC.Core.Map.Type.TEQ ->
gos_r8bK ds_s8n8 ds1_s8n9 tys1_s8nh tys2_s8nk;
GHC.Core.Map.Type.TEQX ->
case gos_r8bK ds_s8n8 ds1_s8n9 tys1_s8nh tys2_s8nk
of wild3_s8nm [Occ=Once1] {
__DEFAULT -> wild3_s8nm;
GHC.Core.Map.Type.TEQ -> GHC.Core.Map.Type.TEQX
}
}
}
}
Aha. It isn't strict on the CMEnv
arguments, so they are passed boxed.
It happens that this is a cold path of $wgo
. Moreover, all of the calls to gos
are in the RHS of $wgo
.
So it would really be better to pass the arguments to gos
unboxed.
However even SpecConstr doesn't catch this, because even though all the calls to gos
are explicit constructors, gos
itself does not take them apart.
There is clearly a lost opportunity here. I'm not sure how to grasp it.