Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
1d4a1229
by sheaf at 2025-11-27T17:58:02-05:00
6 changed files:
- compiler/GHC/Core/Opt/Arity.hs
- compiler/GHC/Core/SimpleOpt.hs
- compiler/GHC/Tc/Gen/App.hs
- + testsuite/tests/simplCore/should_compile/T26588.hs
- + testsuite/tests/simplCore/should_compile/T26589.hs
- testsuite/tests/simplCore/should_compile/all.T
Changes:
| ... | ... | @@ -2993,12 +2993,12 @@ pushCoValArg co |
| 2993 | 2993 | Pair tyL tyR = coercionKind co
|
| 2994 | 2994 | |
| 2995 | 2995 | pushCoercionIntoLambda
|
| 2996 | - :: HasDebugCallStack => Subst -> InVar -> InExpr -> OutCoercionR -> Maybe (OutVar, OutExpr)
|
|
| 2996 | + :: HasDebugCallStack => InScopeSet -> Var -> CoreExpr -> CoercionR -> Maybe (Var, CoreExpr)
|
|
| 2997 | 2997 | -- This implements the Push rule from the paper on coercions
|
| 2998 | 2998 | -- (\x. e) |> co
|
| 2999 | 2999 | -- ===>
|
| 3000 | 3000 | -- (\x'. e |> co')
|
| 3001 | -pushCoercionIntoLambda subst x e co
|
|
| 3001 | +pushCoercionIntoLambda in_scope x e co
|
|
| 3002 | 3002 | | assert (not (isTyVar x) && not (isCoVar x)) True
|
| 3003 | 3003 | , Pair s1s2 t1t2 <- coercionKind co
|
| 3004 | 3004 | , Just {} <- splitFunTy_maybe s1s2
|
| ... | ... | @@ -3011,9 +3011,9 @@ pushCoercionIntoLambda subst x e co |
| 3011 | 3011 | -- Should we optimize the coercions here?
|
| 3012 | 3012 | -- Otherwise they might not match too well
|
| 3013 | 3013 | x' = x `setIdType` t1 `setIdMult` w1
|
| 3014 | - in_scope' = substInScopeSet subst `extendInScopeSet` x'
|
|
| 3014 | + in_scope' = in_scope `extendInScopeSet` x'
|
|
| 3015 | 3015 | subst' =
|
| 3016 | - extendIdSubst (setInScope subst in_scope')
|
|
| 3016 | + extendIdSubst (setInScope emptySubst in_scope')
|
|
| 3017 | 3017 | x
|
| 3018 | 3018 | (mkCast (Var x') (mkSymCo co1))
|
| 3019 | 3019 | -- We substitute x' for x, except we need to preserve types.
|
| ... | ... | @@ -393,12 +393,19 @@ simple_app env e0@(Lam {}) as0@(_:_) |
| 393 | 393 | = wrapLet mb_pr $ do_beta env'' body as
|
| 394 | 394 | where (env', b') = subst_opt_bndr env b
|
| 395 | 395 | |
| 396 | - do_beta env e@(Lam b body) as@(CastIt co:rest)
|
|
| 397 | - -- See Note [Desugaring unlifted newtypes]
|
|
| 396 | + -- See Note [Eliminate casts in function position]
|
|
| 397 | + do_beta env e@(Lam b _) as@(CastIt out_co:rest)
|
|
| 398 | 398 | | isNonCoVarId b
|
| 399 | - , Just (b', body') <- pushCoercionIntoLambda (soe_subst env) b body co
|
|
| 399 | + -- Optimise the inner lambda to make it an 'OutExpr', which makes it
|
|
| 400 | + -- possible to call 'pushCoercionIntoLambda' with the 'OutCoercion' 'co'.
|
|
| 401 | + -- This is kind of horrible, as for nested casted lambdas with a big body,
|
|
| 402 | + -- we will repeatedly optimise the body (once for each binder). However,
|
|
| 403 | + -- we need to do this to avoid mixing 'InExpr' and 'OutExpr', or two
|
|
| 404 | + -- 'InExpr' with different environments (getting this wrong caused #26588 & #26589.)
|
|
| 405 | + , Lam out_b out_body <- simple_app env e []
|
|
| 406 | + , Just (b', body') <- pushCoercionIntoLambda (soeInScope env) out_b out_body out_co
|
|
| 400 | 407 | = do_beta (soeZapSubst env) (Lam b' body') rest
|
| 401 | - -- soeZapSubst: pushCoercionIntoLambda applies the substitution
|
|
| 408 | + -- soeZapSubst: we've already optimised everything (the lambda and 'rest') by now.
|
|
| 402 | 409 | | otherwise
|
| 403 | 410 | = rebuild_app env (simple_opt_expr env e) as
|
| 404 | 411 | |
| ... | ... | @@ -511,7 +518,31 @@ TL;DR: To avoid the rest of the compiler pipeline seeing these bad lambas, we |
| 511 | 518 | rely on the simple optimiser to both inline the newtype unfolding and
|
| 512 | 519 | subsequently deal with the resulting lambdas (either beta-reducing them
|
| 513 | 520 | altogether or pushing coercions into them so that they satisfy the
|
| 514 | -representation-polymorphism invariants).
|
|
| 521 | +representation-polymorphism invariants). See Note [Eliminate casts in function position].
|
|
| 522 | + |
|
| 523 | +[Alternative approach] (GHC ticket #26608)
|
|
| 524 | + |
|
| 525 | + We could instead, in the typechecker, emit a special form (a new constructor
|
|
| 526 | + of XXExprGhcTc) for instantiations of representation-polymorphic unlifted
|
|
| 527 | + newtypes (whether applied to a value argument or not):
|
|
| 528 | + |
|
| 529 | + UnliftedNT :: DataCon -> [Type] -> Coercion -> XXExprGhcTc
|
|
| 530 | + |
|
| 531 | + where "UnliftedNT nt_con [ty1, ...] co" represents the expression:
|
|
| 532 | + |
|
| 533 | + ( nt_con @ty1 ... ) |> co
|
|
| 534 | + |
|
| 535 | + The desugarer would then turn these AST nodes into appropriate Core, doing
|
|
| 536 | + what the simple optimiser does today:
|
|
| 537 | + - inline the compulsory unfolding of the newtype constructor
|
|
| 538 | + - apply it to its type arguments and beta reduce
|
|
| 539 | + - push the coercion into the resulting lambda
|
|
| 540 | + |
|
| 541 | + This would have several advantages:
|
|
| 542 | + - the desugarer would never produce "invalid" Core that needs to be
|
|
| 543 | + tidied up by the simple optimiser,
|
|
| 544 | + - the ugly and inefficient implementation described in
|
|
| 545 | + Note [Eliminate casts in function position] could be removed.
|
|
| 515 | 546 | |
| 516 | 547 | Wrinkle [Unlifted newtypes with wrappers]
|
| 517 | 548 | |
| ... | ... | @@ -717,50 +748,49 @@ rhss here. |
| 717 | 748 | |
| 718 | 749 | Note [Eliminate casts in function position]
|
| 719 | 750 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 720 | -Consider the following program:
|
|
| 751 | +Due to the current implementation strategy for representation-polymorphic
|
|
| 752 | +unlifted newtypes, as described in Note [Desugaring unlifted newtypes], we rely
|
|
| 753 | +on the simple optimiser to push coercions into lambdas, such as in the following
|
|
| 754 | +example:
|
|
| 721 | 755 | |
| 722 | 756 | type R :: Type -> RuntimeRep
|
| 723 | - type family R a where { R Float = FloatRep; R Double = DoubleRep }
|
|
| 724 | - type F :: forall (a :: Type) -> TYPE (R a)
|
|
| 725 | - type family F a where { F Float = Float# ; F Double = Double# }
|
|
| 757 | + type family R a where { R Int = IntRep }
|
|
| 758 | + type F :: forall a -> TYPE (R a)
|
|
| 759 | + type family F a where { F Int = Int# }
|
|
| 726 | 760 | |
| 727 | - type N :: forall (a :: Type) -> TYPE (R a)
|
|
| 728 | 761 | newtype N a = MkN (F a)
|
| 729 | 762 | |
| 730 | -As MkN is a newtype, its unfolding is a lambda which wraps its argument
|
|
| 731 | -in a cast:
|
|
| 732 | - |
|
| 733 | - MkN :: forall (a :: Type). F a -> N a
|
|
| 734 | - MkN = /\a \(x::F a). x |> co_ax
|
|
| 735 | - -- recall that F a :: TYPE (R a)
|
|
| 736 | - |
|
| 737 | -This is a representation-polymorphic lambda, in which the binder has an unknown
|
|
| 738 | -representation (R a). We can't compile such a lambda on its own, but we can
|
|
| 739 | -compile instantiations, such as `MkN @Float` or `MkN @Double`.
|
|
| 763 | +Now, an instantiated occurrence of 'MkN', such as 'MkN @Int' (whether applied
|
|
| 764 | +to a value argument or not) will lead, after inlining the compulsory unfolding
|
|
| 765 | +of 'MkN', to a lambda fo the form:
|
|
| 740 | 766 | |
| 741 | -Our strategy to avoid running afoul of the representation-polymorphism
|
|
| 742 | -invariants of Note [Representation polymorphism invariants] in GHC.Core is thus:
|
|
| 767 | + ( \ ( x :: F Int ) -> body ) |> co
|
|
| 743 | 768 | |
| 744 | - 1. Give the newtype a compulsory unfolding (it has no binding, as we can't
|
|
| 745 | - define lambdas with representation-polymorphic value binders in source Haskell).
|
|
| 746 | - 2. Rely on the optimiser to beta-reduce away any representation-polymorphic
|
|
| 747 | - value binders.
|
|
| 769 | + where
|
|
| 770 | + co :: ( F Int -> res ) ~# ( Int# -> res )
|
|
| 748 | 771 | |
| 749 | -For example, consider the application
|
|
| 772 | +The problem is that we now have a lambda abstraction whose binder does not have a
|
|
| 773 | +fixed RuntimeRep in the sense of Note [Fixed RuntimeRep] in GHC.Tc.Utils.Concrete.
|
|
| 750 | 774 | |
| 751 | - MkN @Float 34.0#
|
|
| 775 | +However, if we use 'pushCoercionIntoLambda', we end up with:
|
|
| 752 | 776 | |
| 753 | -After inlining MkN we'll get
|
|
| 777 | + ( \ ( x' :: Int# ) -> body' )
|
|
| 754 | 778 | |
| 755 | - ((/\a \(x:F a). x |> co_ax) @Float) |> co 34#
|
|
| 779 | +which satisfies the representation-polymorphism invariants of
|
|
| 780 | +Note [Representation polymorphism invariants] in GHC.Core.
|
|
| 756 | 781 | |
| 757 | -where co :: (F Float -> N Float) ~ (Float# ~ N Float)
|
|
| 782 | +In conclusion:
|
|
| 758 | 783 | |
| 759 | -But to actually beta-reduce that lambda, we need to push the 'co'
|
|
| 760 | -inside the `\x` with pushCoercionIntoLambda. Hence the extra
|
|
| 761 | -equation for Cast-of-Lam in simple_app.
|
|
| 784 | + 1. The simple optimiser must push casts into lambdas.
|
|
| 785 | + 2. It must also deal with a situation such as (MkN @Int) |> co, where we first
|
|
| 786 | + inline the compulsory unfolding of N. This means the simple optimiser must
|
|
| 787 | + "peel off" the casts and optimise the inner expression first, to determine
|
|
| 788 | + whether it is a lambda abstraction or not.
|
|
| 762 | 789 | |
| 763 | -This is regrettably delicate.
|
|
| 790 | +This is regrettably delicate. If we could make sure the typechecker/desugarer
|
|
| 791 | +did not produce these bad lambdas in the first place (as described in
|
|
| 792 | +[Alternative approach] in Note [Desugaring unlifted newtypes]), we could
|
|
| 793 | +get rid of this ugly logic.
|
|
| 764 | 794 | |
| 765 | 795 | Note [Preserve join-binding arity]
|
| 766 | 796 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... | ... | @@ -1673,7 +1703,7 @@ exprIsLambda_maybe ise@(ISE in_scope_set _) (Cast casted_e co) |
| 1673 | 1703 | -- this implies that x is not in scope in gamma (makes this code simpler)
|
| 1674 | 1704 | , not (isTyVar x) && not (isCoVar x)
|
| 1675 | 1705 | , assert (not $ x `elemVarSet` tyCoVarsOfCo co) True
|
| 1676 | - , Just (x',e') <- pushCoercionIntoLambda (mkEmptySubst in_scope_set) x e co
|
|
| 1706 | + , Just (x',e') <- pushCoercionIntoLambda in_scope_set x e co
|
|
| 1677 | 1707 | , let res = Just (x',e',ts)
|
| 1678 | 1708 | = --pprTrace "exprIsLambda_maybe:Cast" (vcat [ppr casted_e,ppr co,ppr res)])
|
| 1679 | 1709 | res
|
| ... | ... | @@ -749,13 +749,13 @@ tcInstFun do_ql inst_final (tc_fun, fun_ctxt) fun_sigma rn_args |
| 749 | 749 | go1 _pos acc fun_ty []
|
| 750 | 750 | | XExpr (ConLikeTc (RealDataCon dc)) <- tc_fun
|
| 751 | 751 | , isNewDataCon dc
|
| 752 | - , [Scaled _ arg_ty] <- dataConOrigArgTys dc
|
|
| 752 | + , [Scaled _ orig_arg_ty] <- dataConOrigArgTys dc
|
|
| 753 | 753 | , n_val_args == 0
|
| 754 | 754 | -- If we're dealing with an unsaturated representation-polymorphic
|
| 755 | 755 | -- UnliftedNewype, then perform a representation-polymorphism check.
|
| 756 | 756 | -- See Note [Representation-polymorphism checks for unsaturated unlifted newtypes]
|
| 757 | 757 | -- in GHC.Tc.Utils.Concrete.
|
| 758 | - , not $ typeHasFixedRuntimeRep arg_ty
|
|
| 758 | + , not $ typeHasFixedRuntimeRep orig_arg_ty
|
|
| 759 | 759 | = do { (wrap_co, arg_ty, res_ty) <-
|
| 760 | 760 | matchActualFunTy (FRRRepPolyUnliftedNewtype dc)
|
| 761 | 761 | (Just $ HsExprTcThing tc_fun)
|
| 1 | +module T26588 ( getOptionSettingFromText ) where
|
|
| 2 | + |
|
| 3 | +import Control.Applicative ( Const(..) )
|
|
| 4 | +import Data.Map (Map)
|
|
| 5 | +import qualified Data.Map.Strict as Map
|
|
| 6 | + |
|
| 7 | +------------------------------------------------------------------------
|
|
| 8 | +-- ConfigState
|
|
| 9 | + |
|
| 10 | +data ConfigLeaf
|
|
| 11 | +data ConfigTrie = ConfigTrie !(Maybe ConfigLeaf) !ConfigMap
|
|
| 12 | + |
|
| 13 | +type ConfigMap = Map Int ConfigTrie
|
|
| 14 | + |
|
| 15 | +freshLeaf :: [Int] -> ConfigLeaf -> ConfigTrie
|
|
| 16 | +freshLeaf [] l = ConfigTrie (Just l) mempty
|
|
| 17 | +freshLeaf (a:as) l = ConfigTrie Nothing (Map.singleton a (freshLeaf as l))
|
|
| 18 | + |
|
| 19 | +adjustConfigTrie :: Functor t => [Int] -> (Maybe ConfigLeaf -> t (Maybe ConfigLeaf)) -> Maybe (ConfigTrie) -> t (Maybe ConfigTrie)
|
|
| 20 | +adjustConfigTrie as f Nothing = fmap (freshLeaf as) <$> f Nothing
|
|
| 21 | +adjustConfigTrie (a:as) f (Just (ConfigTrie x m)) = Just . ConfigTrie x <$> adjustConfigMap a as f m
|
|
| 22 | +adjustConfigTrie [] f (Just (ConfigTrie x m)) = g <$> f x
|
|
| 23 | + where g Nothing | Map.null m = Nothing
|
|
| 24 | + g x' = Just (ConfigTrie x' m)
|
|
| 25 | + |
|
| 26 | +adjustConfigMap :: Functor t => Int -> [Int] -> (Maybe ConfigLeaf -> t (Maybe ConfigLeaf)) -> ConfigMap -> t ConfigMap
|
|
| 27 | +adjustConfigMap a as f = Map.alterF (adjustConfigTrie as f) a
|
|
| 28 | + |
|
| 29 | +getOptionSettingFromText :: Int -> [Int] -> ConfigMap -> IO ()
|
|
| 30 | +getOptionSettingFromText p ps = getConst . adjustConfigMap p ps f
|
|
| 31 | + where
|
|
| 32 | + f _ = Const (return ()) |
| 1 | +module T26589 ( executeTest ) where
|
|
| 2 | + |
|
| 3 | +-- base
|
|
| 4 | +import Data.Coerce ( coerce )
|
|
| 5 | +import Data.Foldable ( foldMap )
|
|
| 6 | + |
|
| 7 | +--------------------------------------------------------------------------------
|
|
| 8 | + |
|
| 9 | +newtype Traversal f = Traversal { getTraversal :: f () }
|
|
| 10 | + |
|
| 11 | +instance Applicative f => Semigroup (Traversal f) where
|
|
| 12 | + Traversal f1 <> Traversal f2 = Traversal $ f1 *> f2
|
|
| 13 | +instance Applicative f => Monoid (Traversal f) where
|
|
| 14 | + mempty = Traversal $ pure ()
|
|
| 15 | + |
|
| 16 | +newtype Seq a = Seq (FingerTree (Elem a))
|
|
| 17 | +newtype Elem a = Elem { getElem :: a }
|
|
| 18 | + |
|
| 19 | +data FingerTree a
|
|
| 20 | + = EmptyT
|
|
| 21 | + | Deep !a (FingerTree a) !a
|
|
| 22 | + |
|
| 23 | +executeTest :: Seq () -> IO ()
|
|
| 24 | +executeTest fins = destroyResources
|
|
| 25 | + where
|
|
| 26 | + destroyResources :: IO ()
|
|
| 27 | + destroyResources =
|
|
| 28 | + getTraversal $
|
|
| 29 | + flip foldMap1 fins $ \ _ ->
|
|
| 30 | + Traversal $ return ()
|
|
| 31 | + |
|
| 32 | +foldMap1 :: forall m a. Monoid m => (a -> m) -> Seq a -> m
|
|
| 33 | +foldMap1 = coerce (foldMap2 :: (Elem a -> m) -> FingerTree (Elem a) -> m)
|
|
| 34 | + |
|
| 35 | +foldMap2 :: Monoid m => (Elem a -> m) -> FingerTree (Elem a) -> m
|
|
| 36 | +foldMap2 _ EmptyT = mempty
|
|
| 37 | +foldMap2 f' (Deep pr' m' sf') = f' pr' <> foldMapTree f' m' <> f' sf'
|
|
| 38 | + where
|
|
| 39 | + foldMapTree :: Monoid m => (a -> m) -> FingerTree a -> m
|
|
| 40 | + foldMapTree _ EmptyT = mempty
|
|
| 41 | + foldMapTree f (Deep pr m sf) =
|
|
| 42 | + f pr <>
|
|
| 43 | + foldMapTree f m <>
|
|
| 44 | + f sf |
| ... | ... | @@ -544,6 +544,9 @@ test('T25883b', normal, compile_grep_core, ['']) |
| 544 | 544 | test('T25883c', normal, compile_grep_core, [''])
|
| 545 | 545 | test('T25883d', [extra_files(['T25883d_import.hs'])], multimod_compile_filter, ['T25883d', '-O -ddump-simpl -dno-typeable-binds -dsuppress-all -dsuppress-uniques', r'grep -e "y ="'])
|
| 546 | 546 | |
| 547 | +test('T26588', normal, compile, ['-package containers -O'])
|
|
| 548 | +test('T26589', normal, compile, ['-O'])
|
|
| 549 | + |
|
| 547 | 550 | test('T25976', [grep_errmsg('Dead Code')], compile, ['-O -ddump-simpl -dsuppress-uniques -dno-typeable-binds'])
|
| 548 | 551 | |
| 549 | 552 | test('T25965', normal, compile, ['-O'])
|