Zubin pushed to branch wip/25924 at Glasgow Haskell Compiler / GHC Commits: 56d39f72 by Zubin Duggal at 2026-06-30T18:02:12+05:30 Don't make absent fillers for predicates In #25924 we discovered that we could speculatively evaluate absent fillers for dictionaries, trying to project fields (superclass selectors) out of then, resulting in segfaults. Solution: Never make absent fillers or rubbish literals for predicate types like dictionaries - - - - - 5 changed files: - changelog.d/fix-absent-dict-projection - compiler/GHC/Core/Make.hs - compiler/GHC/Core/Opt/Specialise.hs - compiler/GHC/Core/Opt/WorkWrap/Utils.hs - compiler/GHC/Types/Literal.hs Changes: ===================================== changelog.d/fix-absent-dict-projection ===================================== @@ -1,5 +1,8 @@ section: compiler -synopsis: Fix a CorePrep miscompilation that could project a field out of an absent dictionary, resulting in a segfault. +synopsis: Fix a miscompilation that could project a field out of an absent dictionary, resulting in a segfault. issues: #25924 mrs: !16219 -description: We no longer speculatively evaluate bindings that we have already discovered are absent. +description: + We no longer make an absent filler (a rubbish literal or error thunk) for an + absent dictionary or other predicate. We also no longer speculatively evaluate + a binding once we have discovered that it is absent. ===================================== compiler/GHC/Core/Make.hs ===================================== @@ -66,7 +66,7 @@ import GHC.Types.Unique.Supply import GHC.Core import GHC.Core.Utils ( exprType, mkSingleAltCase, bindNonRec, mkCast, mkTick ) import GHC.Core.Type -import GHC.Core.Predicate ( scopedSort, isEqPred ) +import GHC.Core.Predicate ( scopedSort, isPredTy ) import GHC.Core.TyCo.Compare ( eqType ) import GHC.Core.Coercion ( isCoVar, mkRepReflCo, mkForAllVisCos ) import GHC.Core.DataCon ( DataCon, dataConWorkId, dataConWrapId ) @@ -226,13 +226,15 @@ mkLitRubbish :: Type -> Maybe CoreExpr -- Make a rubbish-literal CoreExpr of the given type. -- Fail (returning Nothing) if -- * the RuntimeRep of the Type is not monomorphic; --- * the type is (a ~# b), the type of coercion +-- * the type is a predicate (isPredTy). This used to apply only to +-- equalities (a ~# b) but was expanded to all predicates. See +-- Note [Don't make fillers for predicates] in GHC.Core.Opt.WorkWrap.Utils -- See INVARIANT 1 and 2 of item (2) in Note [Rubbish literals] -- in GHC.Types.Literal mkLitRubbish ty | not (noFreeVarsOfType rep) = Nothing -- Satisfy INVARIANT 1 - | isEqPred ty + | isPredTy ty = Nothing -- Satisfy INVARIANT 2 | otherwise = Just (Lit (LitRubbish torc rep) `mkTyApps` [ty]) ===================================== compiler/GHC/Core/Opt/Specialise.hs ===================================== @@ -2639,6 +2639,10 @@ specHeader subst (bndr:bndrs) (_ : args) | isDeadBinder bndr , let (subst1, bndr') = Core.substBndr subst (zapIdOccInfo bndr) , Just rubbish_lit <- mkLitRubbish (idType bndr') + -- NB: mkLitRubbish returns Nothing for a predicate (e.g. a dictionary), so + -- this guard fails and we fall through, keeping the argument instead of + -- dropping it. See Note [Don't make fillers for predicates] in + -- GHC.Core.Opt.WorkWrap.Utils = -- See Note [Drop dead args from specialisations] do { (useful, subst2, rule_bs, rule_es, spec_bs, dx, spec_args) <- specHeader subst1 bndrs args ; pure ( useful, subst2 ===================================== compiler/GHC/Core/Opt/WorkWrap/Utils.hs ===================================== @@ -30,7 +30,7 @@ import GHC.Core.Subst import GHC.Core.Type import GHC.Core.Multiplicity import GHC.Core.Coercion -import GHC.Core.Predicate( isDictTy ) +import GHC.Core.Predicate( isPredTy ) import GHC.Core.Reduction import GHC.Core.FamInstEnv import GHC.Core.Predicate( isEqualityClass ) @@ -1074,7 +1074,7 @@ mkAbsentFiller opts arg str -- if we are wrong (like we were in #11126). Otherwise we fall through to the -- less-desirable mkLitRubbish case. | mightBeLiftedType arg_ty - , not (isDictTy arg_ty) -- See (AF4) in Note [Absent fillers] + , not (isPredTy arg_ty) -- See (AF4) in Note [Absent fillers] , not (isStrictDmd (idDemandInfo arg)) -- See (AF2) , not (isMarkedStrict str) -- in Note [Absent fillers] = Just (mkAbsentErrorApp arg_ty msg) @@ -1311,9 +1311,14 @@ Needless to say, there are some wrinkles: dictionary is non-bottom. (If -XDictsStrict is on, the invariant is even more important.) - Simple solution: never use an error thunk for a dictionary; instead fall - through to mkRubbishLit. (The only downside is that we lose the compiler - debugging advantages of (AF1).) + We used to fix this by never using an error thunk for a dictionary, and + falling through to mkLitRubbish instead. But that is not enough either. + #25924 shows that selecting a superclass out of a rubbish dictionary also + segfaults. So now we make no filler at all for a predicate. mkLitRubbish + returns Nothing for predicates, and the error thunk branch (AF1) is guarded + by `not (isPredTy arg_ty)`, so mkAbsentFiller returns Nothing and + worker/wrapper keeps the real argument. + See Note [Don't make fillers for predicates]. This is quite delicate. @@ -1337,6 +1342,43 @@ fragile because `MkT` is strict in its Int# argument, so we get an absentError exception when we shouldn't. Very annoying! +Note [Don't make fillers for predicates] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +We never make an absent filler, error thunk or rubbish literal, for a predicate +(isPredTy). That includes dictionaries, equalities, quantified constraints and +implicit parameters. + +GHC relies on a dictionary value never being bottom (see Note [NON-BOTTOM-DICTS +invariant] in GHC.Core). Because of that, speculative evaluation is happy to +evaluate a dictionary, and a superclass selection is happy to project a field +out of it. + +But an absent filler is the wrong thing to find in such a position. An error +thunk is bottom, so if it is ever evaluated we get "Entered absent arg" +(#24934). A rubbish literal is not a real dictionary. It is compiled to the unit +value (), so if we project a superclass out of it we read a field that is not +there and get a segfault (see #25924). + +We used to paper over this. !13233 replaced the error thunk for an absent +dictionary with a rubbish literal, so that it could at least be evaluated +without complaint. But #25924 showed that this is not enough, because we do not +only evaluate the absent dictionary, we also select a superclass from it. + +We could instead teach speculation to leave absent bindings alone, and we do +that too (see Note [Speculative evaluation] in GHC.CoreToStg.Prep). But that is +not a guarantee. After optimisation a binding that holds an absent filler may no +longer be marked absent, so we cannot rely on the demand to protect us. + +So the safe thing is to make no filler at all for a predicate. Then there is no +bogus dictionary to evaluate or project from. mkLitRubbish returns Nothing for +any predicate (INVARIANT 2 of Note [Rubbish literals] in GHC.Types.Literal), and +both of its callers cope with the Nothing. mkAbsentFiller returns Nothing, so +worker/wrapper keeps the real argument. Specialise.specHeader likewise keeps the +dead dictionary argument rather than dropping it for a rubbish literal. + +We use isPredTy rather than isDictTy so that this also covers equalities, +quantified constraints and implicit parameters, not just ordinary dictionaries. + Note [Unboxing through unboxed tuples] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We should not to a worker/wrapper split just for unboxing the components of ===================================== compiler/GHC/Types/Literal.hs ===================================== @@ -994,15 +994,21 @@ data type. Here are the moving parts: substitutions and free variable finders over Literal. The rules around levity/runtime-rep polymorphism naturally uphold this invariant. - INVARIANT 2: we never make a rubbish literal of type (a ~# b). Reason: - see Note [Core type and coercion invariant] in GHC.Core. We can't substitute - a LitRubbish inside a coercion, so it's best not to make one. They are zero - width anyway, so passing absent ones around costs nothing. If we wanted - an absent filler of type (a ~# b) we should use (Coercion (UnivCo ...)), - but it doesn't seem worth making a new UnivCoProvenance for this purpose. + INVARIANT 2: we never make a rubbish literal of a predicate type (isPredTy). + + For an equality (a ~# b): see Note [Core type and coercion invariant] in + GHC.Core. We can't substitute a LitRubbish inside a coercion, so it's best + not to make one. They are zero width anyway, so passing absent ones around + costs nothing. If we wanted an absent filler of type (a ~# b) we should use + (Coercion (UnivCo ...)), but it doesn't seem worth making a new + UnivCoProvenance for this purpose. This is sad, though: see #18983. + For a dictionary: a rubbish literal is not a real dictionary, so evaluating + it or selecting a field from it crashes. See Note [Don't make fillers for + predicates] in GHC.Core.Opt.WorkWrap.Utils. + 3. STG: The type app in `RUBBISH[IntRep] @Int# :: Int#` is erased and we get the (untyped) 'StgLit' `RUBBISH[IntRep] :: Int#` in STG. View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/56d39f720dbf6d9766faf9049540e740... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/56d39f720dbf6d9766faf9049540e740... You're receiving this email because of your account on gitlab.haskell.org.