Note [Sharing when zonking to Type]
Problem:
In GHC.Tc.Zonk.TcType.zonkTcTyVar, we short-circuit (Indirect ty) to
(Indirect zty), see Note [Sharing in zonking] in GHC.Tc.Zonk.TcType.
But we /can't/ do this when zonking a TcType to a Type (#15552, esp comment:3).
Suppose we have
alpha -> alpha
where
alpha is already unified:
alpha := T{tc-tycon} Int -> Int
and T is knot-tied
By "knot-tied" I mean that the occurrence of T is currently a TcTyCon,
but the global env contains a mapping "T" :-> T{knot-tied-tc}. See
Note [Type checking recursive type and class declarations] in
GHC.Tc.TyCl.
Now we call zonkTcTypeToType on that (alpha -> alpha). If we follow
the same path as Note [Sharing in zonking] in GHC.Tc.Zonk.TcType, we'll
update alpha to
alpha := T{knot-tied-tc} Int -> Int
But alas, if we encounter alpha for a /second/ time, we end up
looking at T{knot-tied-tc} and fall into a black hole. The whole
point of zonkTcTypeToType is that it produces a type full of
knot-tied tycons, and you must not look at the result!!
To put it another way (zonkTcTypeToType . zonkTcTypeToType) is not
the same as zonkTcTypeToType. (If we distinguished TcType from
Type, this issue would have been a type error!)
Solutions: (see #15552 for other variants)
One possible solution is simply not to do the short-circuiting.
That has less sharing, but maybe sharing is rare. And indeed,
that usually turns out to be viable from a perf point of view
But zonkTyVarOcc implements something a bit better
* ZonkEnv contains ze_meta_tv_env, which maps
from a MetaTyVar (unification variable)
to a Type (not a TcType)
* In zonkTyVarOcc, we check this map to see if we have zonked
this variable before. If so, use the previous answer; if not
zonk it, and extend the map.
* The map is of course stateful, held in a TcRef. (That is unlike
the treatment of lexically-scoped variables in ze_tv_env and
ze_id_env.)
* In zonkTyVarOcc we read the TcRef to look up the unification
variable:
- if we get a hit we use the zonked result;
- if not, in zonk_meta we see if the variable is `Indirect ty`,
zonk that, and update the map (in finish_meta)
But Nota Bene that the "update map" step must re-read the TcRef
(or, more precisely, use updTcRef) because the zonking of the
`Indirect ty` may have added lots of stuff to the map. See
#19668 for an example where this made an asymptotic difference!
Is it worth the extra work of carrying ze_meta_tv_env? Some
non-systematic perf measurements suggest that compiler allocation is
reduced overall (by 0.5% or so) but compile time really doesn't
change. But in some cases it makes a HUGE difference: see test
T9198 and #19668. So yes, it seems worth it. References 2
- Type checking recursive type and class declarations GHC.Tc.TyCl
- Sharing in zonking GHC.Tc.Zonk.TcType
Referenced by 3
- The ZonkEnv GHC.Tc.Zonk.Env
- Sharing in zonking GHC.Tc.Zonk.TcType
- GHC.Tc.Zonk.Type call site