Admin message

Due to a large amount of spam we do not allow new users to create repositories, they are "external" users. If you are a new user and want to create a repository, for example for forking GHC, open a new issue on ghc/ghc using the "get-verified" issue template

Case-of-known-constructor is broken in GHC 8.10 for floated DataCon wrappers
As the issue title states, case-of-known-constructor does *not* fire if a `case` expression scrutinizes a floated-out value built from a DataCon wrapper. This program reproduces the issue: ```haskell module KnownCon where data T = D !Bool f :: T -> T -> T f (D a) (D b) = D (a && b) {-# INLINE [100] f #-} -- so it isn’t inlined before FloatOut g :: Bool -> T g x = f (D True) (D x) ``` To see the bad behavior, we have to compile with `-dverbose-core2core`, since we need to see the output of simplifier phase 2 (or phase 1). Inspecting it shows that `D True` is floated out of `g` by FloatOut, and even though `f` is inlined, case-of-known-constructor does *not* fire! ```haskell ==================== Simplifier ==================== Max iterations = 4 SimplMode {Phase = 2 [main], inline, rules, eta-expand, case-of-case} lvl_s1gm :: T [LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=False, ConLike=False, WorkFree=False, Expandable=False, Guidance=IF_ARGS [] 20 0}] lvl_s1gm = $WD True g :: Bool -> T [LclIdX, Arity=1, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [20] 80 0}] g = \ (x_a1eV :: Bool) -> case lvl_s1gm of { D a_atX -> case x_a1eV of dt_X0 { __DEFAULT -> case a_atX of { False -> $WD False; True -> $WD dt_X0 } } } ``` This seems quite bad to me. It is a regression from GHC 8.8, since GHC 8.8 inlines DataCon wrappers much more aggressively. --- The source of this issue appears to be the `Expandable=False` in the floated-out binding’s unfolding. The root cause is `isExpandableApp`, which does not classify applications of DataCon wrappers as expandable: ```haskell isExpandableApp :: CheapAppFun isExpandableApp fn n_val_args | isWorkFreeApp fn n_val_args = True | otherwise = case idDetails fn of DataConWorkId {} -> True -- Actually handled by isWorkFreeApp RecSelId {} -> n_val_args == 1 -- See Note [Record selection] ClassOpId {} -> n_val_args == 1 PrimOpId {} -> False _ | isBottomingId fn -> False -- See Note [isExpandableApp: bottoming functions] | isConLike (idRuleMatchInfo fn) -> True | all_args_are_preds -> True | otherwise -> False ``` One point of note is that DataCon wrappers do not appear to be considered “conlike”; `isConLikeId` returns `True` on DataCon workers and ids with user-defined `CONLIKE` pragmas, but it returns `False` on DataCon wrappers. I suppose it is possible to argue in favor of this behavior, as it’s true that a DataCon wrapper is not work-free! But it is not an especially large amount of work, and the purpose of `CONLIKE` is to annotate something as worth duplicating *if and only if it exposes further optimizations*. From that perspective, DataCon wrappers certainly seem conlike to me. In any case, DataCon wrappers definitely ought to be *expandable*, even if they are not conlike. In fact, `Note [exprIsConApp_maybe on data constructors with wrappers]` assumes they are: ``` 1. Inline $WMkT on-the-fly. That's why data-constructor wrappers are marked as expandable. (See GHC.Core.Utils.isExpandableApp.) Now we have ``` So this seems clearly a mistake to me.
issue