Simon Peyton Jones pushed to branch wip/T27629 at Glasgow Haskell Compiler / GHC
Commits:
-
eed2f9cc
by Simon Peyton Jones at 2026-09-03T11:17:07+01:00
6 changed files:
- compiler/GHC/Core/Opt/Specialise.hs
- testsuite/tests/simplCore/should_compile/Makefile
- + testsuite/tests/simplCore/should_compile/T27629.hs
- + testsuite/tests/simplCore/should_compile/T27629.stderr
- + testsuite/tests/simplCore/should_compile/T27629Plugin.hs
- testsuite/tests/simplCore/should_compile/all.T
Changes:
| ... | ... | @@ -1713,9 +1713,7 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs |
| 1713 | 1713 | ; let spec_rhs_bndrs = spec_bndrs ++ inner_rhs_bndrs'
|
| 1714 | 1714 | (rhs_uds2, inner_dumped_dbs) = dumpUDs spec_rhs_bndrs $
|
| 1715 | 1715 | dx_binds `consDictBinds` rhs_uds
|
| 1716 | - -- dx_binds comes from the arguments to the call,
|
|
| 1717 | - -- and so can mention poly_qvars but no other local binders
|
|
| 1718 | - spec_rhs = mkLams spec_rhs_bndrs $
|
|
| 1716 | + spec_rhs = mkLams spec_rhs_bndrs $
|
|
| 1719 | 1717 | wrapDictBindsE inner_dumped_dbs rhs_body'
|
| 1720 | 1718 | rule_rhs_args = spec_bndrs
|
| 1721 | 1719 | |
| ... | ... | @@ -1784,8 +1782,7 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs |
| 1784 | 1782 | , text "rule_act" <+> ppr rule_act
|
| 1785 | 1783 | ]
|
| 1786 | 1784 | |
| 1787 | --- ; pprTrace "spec_call: rule" (vcat [ -- text "poly_qvars" <+> ppr poly_qvars
|
|
| 1788 | --- text "rule_bndrs" <+> ppr rule_bndrs
|
|
| 1785 | +-- ; pprTrace "spec_call: rule" (vcat [ text "rule_bndrs" <+> ppr rule_bndrs
|
|
| 1789 | 1786 | -- , text "rule_lhs_args" <+> ppr rule_lhs_args
|
| 1790 | 1787 | -- , text "all_call_args" <+> ppr all_call_args
|
| 1791 | 1788 | -- , ppr spec_rule ]) $
|
| ... | ... | @@ -2611,6 +2608,14 @@ specHeader subst (bndr:bndrs) (UnspecType : args) |
| 2611 | 2608 | -- a wildcard binder to match the dictionary (See Note [Specialising Calls] for
|
| 2612 | 2609 | -- the nitty-gritty), as a LHS rule and unfolding details.
|
| 2613 | 2610 | specHeader subst (bndr:bndrs) (SpecDict dict_arg : args)
|
| 2611 | + | let in_scope = getInScopeVars (substInScopeSet subst)
|
|
| 2612 | + bad_dict_fv v = isLocalVar v && not (v `elemVarSet` in_scope)
|
|
| 2613 | + , not $ isEmptyVarSet $ exprSomeFreeVars bad_dict_fv dict_arg
|
|
| 2614 | + = -- Do not specialise if `dict_arg` has any any free vars that are /not/ in scope
|
|
| 2615 | + -- See Note [Weird special case for SpecDict]
|
|
| 2616 | + specHeader mod subst (bndr:bndrs) (UnspecArg : args)
|
|
| 2617 | + |
|
| 2618 | + | otherwise
|
|
| 2614 | 2619 | = do { -- Make up a fresh binder to use in the RULE
|
| 2615 | 2620 | -- It might turn into a dict binding (via bindAuxiliaryDict) which we
|
| 2616 | 2621 | -- then float, so we use cloneIdBndr to get a completely fresh binder
|
| ... | ... | @@ -2886,13 +2891,15 @@ We could zap `k` to (Any @Type) and `a` to (Any @(Any @Type)), but that |
| 2886 | 2891 | is a lot of hard work for a very strange case.
|
| 2887 | 2892 | |
| 2888 | 2893 | So we simply refrain from specialising in this case; hence the guard
|
| 2889 | - allVarSet (`elemInScopeSet` in_scope) (exprFreeVars d)
|
|
| 2890 | -in the SpecDict cased of specHeader.
|
|
| 2891 | - |
|
| 2892 | -How did this strange polymorphic mkD arise in the first place?
|
|
| 2893 | -From GHC.Core.Opt.Utils.abstractFloats, which was abstracting
|
|
| 2894 | -over too many type variables. But that too is now fixed;
|
|
| 2895 | -see Note [Which type variables to abstract over] in that module.
|
|
| 2894 | + not $ isEmptyVarSet $ exprSomeFreeVars bad_dict_fv dict_arg
|
|
| 2895 | +in the SpecDict case of `specHeader`.
|
|
| 2896 | + |
|
| 2897 | +How did this strange polymorphic mkD arise in the first place? From
|
|
| 2898 | +GHC.Core.Opt.Utils.abstractFloats, which was abstracting over too many type
|
|
| 2899 | +variables. But that too is now fixed; see Note [Which type variables to abstract
|
|
| 2900 | +over] in that module. So we don't actually have a Haskell source program that
|
|
| 2901 | +triggers it (see #27629) -- only test T27629 that uses a plugin to inject a strange
|
|
| 2902 | +definition.
|
|
| 2896 | 2903 | -}
|
| 2897 | 2904 | |
| 2898 | 2905 | instance Outputable DictBind where
|
| ... | ... | @@ -3,6 +3,12 @@ include $(TOP)/mk/boilerplate.mk |
| 3 | 3 | include $(TOP)/mk/test.mk
|
| 4 | 4 | |
| 5 | 5 | |
| 6 | +# T27629 runs a plugin that injects a Core binding
|
|
| 7 | +# that made the specialiser crash
|
|
| 8 | +T27629:
|
|
| 9 | + '$(TEST_HC)' $(TEST_HC_OPTS) --make -package ghc -dynamic-too -c T27629Plugin.hs
|
|
| 10 | + '$(TEST_HC)' $(TEST_HC_OPTS) -c -O -fpolymorphic-specialisation -dcore-lint T27629.hs
|
|
| 11 | + |
|
| 6 | 12 | # T18815 should not have a non-recursive join-point for 'go'
|
| 7 | 13 | # Previously we ended up with
|
| 8 | 14 | # join {go_sPI w_sQ3 = case w_sQ3 of { GHC.Types.I# ww1_sQ6 ->
|
| 1 | +{-# LANGUAGE AllowAmbiguousTypes #-}
|
|
| 2 | +{-# OPTIONS_GHC -fplugin=T27629Plugin #-}
|
|
| 3 | + |
|
| 4 | +-- The plugin rewrites 'poly' to the example of
|
|
| 5 | +-- Note [Weird special case for SpecDict]:
|
|
| 6 | +--
|
|
| 7 | +-- poly = /\x. \t. split @T (mkD @x MkT) [t]
|
|
| 8 | +--
|
|
| 9 | +-- where mkD :: forall a. T -> C T is a new top-level binding
|
|
| 10 | +-- (mkD = /\a. \_. $fCT), so the tyvar x is free in the dictionary
|
|
| 11 | +-- *expression* but not in its *type* (C T).
|
|
| 12 | +module T27629 (poly, split, T (..), C (..)) where
|
|
| 13 | + |
|
| 14 | +import Data.Kind (Type)
|
|
| 15 | + |
|
| 16 | +data T = MkT
|
|
| 17 | + |
|
| 18 | +class C b where
|
|
| 19 | + meth :: b -> b
|
|
| 20 | + |
|
| 21 | +instance C T where
|
|
| 22 | + meth x = x
|
|
| 23 | + |
|
| 24 | +split :: C b => [b] -> [b]
|
|
| 25 | +split [] = []
|
|
| 26 | +split (x : xs) = meth x : split xs
|
|
| 27 | + |
|
| 28 | +poly :: forall (x :: Type). T -> [T]
|
|
| 29 | +poly t = [t] |
| 1 | +ReproPlugin: injected weird SpecDict call into poly |
| 1 | +-- Injects the example of Note [Weird special case for SpecDict] in
|
|
| 2 | +-- GHC.Core.Opt.Specialise, which cannot be written in source Haskell:
|
|
| 3 | +-- a call whose dictionary argument mentions a type variable that is
|
|
| 4 | +-- free in the dictionary expression but not in its type.
|
|
| 5 | +--
|
|
| 6 | +-- Runs before the main Core pipeline (in particular before the
|
|
| 7 | +-- Specialiser). It adds
|
|
| 8 | +--
|
|
| 9 | +-- mkD :: forall a. T -> C T {-# NOINLINE mkD #-}
|
|
| 10 | +-- mkD = /\a. \_. $fCT
|
|
| 11 | +--
|
|
| 12 | +-- and rewrites poly = /\x. \t. [t]
|
|
| 13 | +-- to poly = /\x. \t. split @T (mkD @x MkT) [t]
|
|
| 14 | +{-# LANGUAGE CPP #-}
|
|
| 15 | +module T27629Plugin (plugin) where
|
|
| 16 | + |
|
| 17 | +import GHC.Core.Make (mkCoreApps, mkListExpr)
|
|
| 18 | +import GHC.Core.Predicate (getClassPredTys_maybe)
|
|
| 19 | +import GHC.Plugins
|
|
| 20 | +#if __GLASGOW_HASKELL__ >= 1000
|
|
| 21 | +import GHC.Types.InlinePragma (neverInlinePragma)
|
|
| 22 | +#else
|
|
| 23 | +import GHC.Types.Basic (neverInlinePragma)
|
|
| 24 | +#endif
|
|
| 25 | + |
|
| 26 | +plugin :: Plugin
|
|
| 27 | +plugin =
|
|
| 28 | + defaultPlugin
|
|
| 29 | + { installCoreToDos = \_ todos ->
|
|
| 30 | + return (CoreDoPluginPass "inject-weird-specdict" pass : todos)
|
|
| 31 | + , pluginRecompile = purePlugin
|
|
| 32 | + }
|
|
| 33 | + |
|
| 34 | +pass :: ModGuts -> CoreM ModGuts
|
|
| 35 | +pass guts = do
|
|
| 36 | + let binds = mg_binds guts
|
|
| 37 | + |
|
| 38 | + dfun = case [b | b <- bindersOfBinds binds, isDFunId b] of
|
|
| 39 | + (b : _) -> b
|
|
| 40 | + [] -> panic "ReproPlugin: no dfun binding in module"
|
|
| 41 | + |
|
| 42 | + dictTy = idType dfun -- C T
|
|
| 43 | + tyT = case getClassPredTys_maybe dictTy of
|
|
| 44 | + Just (_cls, [t]) -> t
|
|
| 45 | + _ -> panic "ReproPlugin: dfun type is not C T"
|
|
| 46 | + |
|
| 47 | + mkTCon = case tyConDataCons (tyConAppTyCon tyT) of
|
|
| 48 | + (dc : _) -> dataConWorkId dc
|
|
| 49 | + [] -> panic "ReproPlugin: T has no data constructors"
|
|
| 50 | + |
|
| 51 | + findTopId nm =
|
|
| 52 | + case [b | b <- bindersOfBinds binds, occNameString (getOccName b) == nm] of
|
|
| 53 | + (b : _) -> b
|
|
| 54 | + [] -> panic ("ReproPlugin: no top-level binding " ++ nm)
|
|
| 55 | + |
|
| 56 | + splitId = findTopId "split"
|
|
| 57 | + polyId = findTopId "poly"
|
|
| 58 | + |
|
| 59 | + uMkD <- getUniqueM
|
|
| 60 | + uTv <- getUniqueM
|
|
| 61 | + uWild <- getUniqueM
|
|
| 62 | + |
|
| 63 | + let aTv = mkTyVar (mkSystemName uTv (mkTyVarOcc "a")) liftedTypeKind
|
|
| 64 | + mkDTy = mkSpecForAllTys [aTv] (mkFunctionType ManyTy tyT dictTy)
|
|
| 65 | + mkDId =
|
|
| 66 | + setInlinePragma
|
|
| 67 | + (mkExportedVanillaId (mkSystemName uMkD (mkVarOcc "mkD")) mkDTy)
|
|
| 68 | + neverInlinePragma
|
|
| 69 | + wild = mkSysLocal (fsLit "eta") uWild ManyTy tyT
|
|
| 70 | + mkDBind = NonRec mkDId (mkLams [aTv, wild] (Var dfun))
|
|
| 71 | + |
|
| 72 | + rewritePoly rhs
|
|
| 73 | + | (bndrs, _body) <- collectBinders rhs
|
|
| 74 | + , (x : _) <- reverse (filter isTyVar bndrs)
|
|
| 75 | + , (t : _) <- filter isId bndrs
|
|
| 76 | + = mkLams bndrs $
|
|
| 77 | + mkCoreApps
|
|
| 78 | + (Var splitId)
|
|
| 79 | + [ Type tyT
|
|
| 80 | + , mkCoreApps (Var mkDId) [Type (mkTyVarTy x), Var mkTCon]
|
|
| 81 | + , mkListExpr tyT [Var t]
|
|
| 82 | + ]
|
|
| 83 | + | otherwise =
|
|
| 84 | + pprPanic "ReproPlugin: unexpected shape of poly" (ppr rhs)
|
|
| 85 | + |
|
| 86 | + go (b, rhs)
|
|
| 87 | + | b == polyId = (b, rewritePoly rhs)
|
|
| 88 | + | otherwise = (b, rhs)
|
|
| 89 | + |
|
| 90 | + -- One top-level Rec, so bind order is irrelevant.
|
|
| 91 | + binds' = [Rec (map go (flattenBinds binds) ++ flattenBinds [mkDBind])]
|
|
| 92 | + |
|
| 93 | + putMsgS "ReproPlugin: injected weird SpecDict call into poly"
|
|
| 94 | + return guts {mg_binds = binds'} |
| ... | ... | @@ -611,3 +611,4 @@ test('T27296', [], makefile_test, ['T27296']) |
| 611 | 611 | test('T27296b', [], makefile_test, ['T27296b'])
|
| 612 | 612 | test('T27589', [grep_errmsg(r'wombat')], compile, ['-O -ddump-simpl -dno-typeable-binds -dsuppress-uniques'])
|
| 613 | 613 | test('T27590', [grep_errmsg(r'wombat')], compile, ['-O -ddump-simpl -dno-typeable-binds -dsuppress-uniques'])
|
| 614 | +test('T27629', only_ways(['optasm']), makefile_test, ['T27629']) |