
[Git][ghc/ghc][master] Refactor Handling of Multiple Default Declarations
by Marge Bot (@marge-bot) 17 Apr '25
by Marge Bot (@marge-bot) 17 Apr '25
17 Apr '25
Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
e0f3ff11 by Patrick at 2025-04-17T04:31:12-04:00
Refactor Handling of Multiple Default Declarations
Fixes: #25912, #25914, #25934
Previously, GHC discarded all loaded defaults (tcg_default) when local
defaults were encountered during typechecking. According to the
exportable-named-default proposal (sections 2.4.2 and 2.4.3), local
defaults should be merged into tcg_default, retaining any defaults
already present while overriding where necessary.
Key Changes:
* Introduce DefaultProvenance to track the origin of default declarations
(local, imported, or built-in), replacing the original cd_module
in ClassDefaults with cd_provenance :: DefaultProvenance.
* Rename tcDefaults to tcDefaultDecls, limiting its responsibility to only
converting renamed class defaults into ClassDefaults.
* Add extendDefaultEnvWithLocalDefaults to merge local defaults into the
environment, with proper duplication checks:
- Duplicate local defaults for a class trigger an error.
- Local defaults override imported or built-in defaults.
* Update and add related notes: Note [Builtin class defaults],
Note [DefaultProvenance].
* Add regression tests: T25912, T25914, T25934.
Thanks sam and simon for the help on this patch.
Co-authored-by: sheaf <sam.derbyshire(a)gmail.com>
- - - - -
17 changed files:
- compiler/GHC/IfaceToCore.hs
- compiler/GHC/Tc/Errors/Ppr.hs
- compiler/GHC/Tc/Errors/Types.hs
- compiler/GHC/Tc/Gen/Default.hs
- compiler/GHC/Tc/Gen/Export.hs
- compiler/GHC/Tc/Module.hs
- compiler/GHC/Tc/Utils/Env.hs
- compiler/GHC/Types/DefaultEnv.hs
- + testsuite/tests/default/T25912.hs
- + testsuite/tests/default/T25912.stdout
- + testsuite/tests/default/T25912_helper.hs
- + testsuite/tests/default/T25914.hs
- + testsuite/tests/default/T25934.hs
- testsuite/tests/default/all.T
- testsuite/tests/default/default-fail03.stderr
- testsuite/tests/linters/notes.stdout
- testsuite/tests/module/mod58.stderr
Changes:
=====================================
compiler/GHC/IfaceToCore.hs
=====================================
@@ -118,7 +118,7 @@ import GHC.Types.Var.Set
import GHC.Types.Name
import GHC.Types.Name.Set
import GHC.Types.Name.Env
-import GHC.Types.DefaultEnv ( ClassDefaults(..), DefaultEnv, mkDefaultEnv )
+import GHC.Types.DefaultEnv ( ClassDefaults(..), DefaultEnv, mkDefaultEnv, DefaultProvenance(..) )
import GHC.Types.Id
import GHC.Types.Id.Make
import GHC.Types.Id.Info
@@ -1333,7 +1333,7 @@ tcIfaceDefault this_mod IfaceDefault { ifDefaultCls = cls_name
; let warn = fmap fromIfaceWarningTxt iface_warn
; return ClassDefaults { cd_class = cls
, cd_types = tys'
- , cd_module = Just this_mod
+ , cd_provenance = DP_Imported this_mod
, cd_warn = warn } }
where
tyThingConClass :: TyThing -> Class
=====================================
compiler/GHC/Tc/Errors/Ppr.hs
=====================================
@@ -85,7 +85,7 @@ import GHC.Tc.Types.Rank (Rank(..))
import GHC.Tc.Types.TH
import GHC.Tc.Utils.TcType
-import GHC.Types.DefaultEnv (ClassDefaults(ClassDefaults, cd_types, cd_module))
+import GHC.Types.DefaultEnv (ClassDefaults(ClassDefaults, cd_types, cd_provenance), DefaultProvenance (..))
import GHC.Types.Error
import GHC.Types.Error.Codes
import GHC.Types.Hint
@@ -582,11 +582,19 @@ instance Diagnostic TcRnMessage where
TcRnMultipleDefaultDeclarations cls dup_things
-> mkSimpleDecorated $
hang (text "Multiple default declarations for class" <+> quotes (ppr cls))
- 2 (vcat (map pp dup_things))
+ 2 (pp dup_things)
where
- pp :: LDefaultDecl GhcRn -> SDoc
- pp (L locn DefaultDecl {})
- = text "here was another default declaration" <+> ppr (locA locn)
+ pp :: ClassDefaults -> SDoc
+ pp (ClassDefaults { cd_provenance = prov })
+ = case prov of
+ DP_Local { defaultDeclLoc = loc, defaultDeclH98 = isH98 }
+ -> let
+ what =
+ if isH98
+ then text "default declaration"
+ else text "named default declaration"
+ in text "conflicting" <+> what <+> text "at:" <+> ppr loc
+ _ -> empty -- doesn't happen, as local defaults override imported and built-in defaults
TcRnBadDefaultType ty deflt_clss
-> mkSimpleDecorated $
hang (text "The default type" <+> quotes (ppr ty) <+> text "is not an instance of")
@@ -7139,7 +7147,7 @@ pprPatersonCondFailure (PCF_TyFam tc) InTyFamEquation _lhs rhs =
--------------------------------------------------------------------------------
defaultTypesAndImport :: ClassDefaults -> SDoc
-defaultTypesAndImport ClassDefaults{cd_types, cd_module = Just cdm} =
+defaultTypesAndImport ClassDefaults{cd_types, cd_provenance = DP_Imported cdm} =
hang (parens $ pprWithCommas ppr cd_types)
2 (text "imported from" <+> ppr cdm)
defaultTypesAndImport ClassDefaults{cd_types} = parens (pprWithCommas ppr cd_types)
=====================================
compiler/GHC/Tc/Errors/Types.hs
=====================================
@@ -1504,7 +1504,7 @@ data TcRnMessage where
Text cases: module/mod58
-}
- TcRnMultipleDefaultDeclarations :: Class -> [LDefaultDecl GhcRn] -> TcRnMessage
+ TcRnMultipleDefaultDeclarations :: Class -> ClassDefaults -> TcRnMessage
{-| TcRnWarnClashingDefaultImports is a warning that occurs when a module imports
more than one default declaration for the same class, and they are not all
=====================================
compiler/GHC/Tc/Gen/Default.hs
=====================================
@@ -5,9 +5,10 @@
-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE LambdaCase #-}
-- | Typechecking @default@ declarations
-module GHC.Tc.Gen.Default ( tcDefaults ) where
+module GHC.Tc.Gen.Default ( tcDefaultDecls, extendDefaultEnvWithLocalDefaults ) where
import GHC.Prelude
import GHC.Hs
@@ -16,7 +17,7 @@ import GHC.Builtin.Names
import GHC.Core.Class
import GHC.Core.Predicate ( Pred (..), classifyPredType )
-import GHC.Data.Maybe ( firstJusts )
+import GHC.Data.Maybe ( firstJusts, maybeToList )
import GHC.Tc.Errors.Types
import GHC.Tc.Gen.HsType
@@ -30,20 +31,17 @@ import GHC.Tc.Utils.TcMType ( newWanted )
import GHC.Tc.Utils.TcType
import GHC.Types.Basic ( TypeOrKind(..) )
-import GHC.Types.DefaultEnv ( DefaultEnv, ClassDefaults (..), defaultEnv )
+import GHC.Types.DefaultEnv ( DefaultEnv, ClassDefaults (..), lookupDefaultEnv, insertDefaultEnv, DefaultProvenance (..) )
import GHC.Types.SrcLoc
-import GHC.Unit.Types (Module, ghcInternalUnit, moduleUnit)
+import GHC.Unit.Types (ghcInternalUnit, moduleUnit)
-import GHC.Utils.Misc (fstOf3, sndOf3)
import GHC.Utils.Outputable
import qualified GHC.LanguageExtensions as LangExt
-import Data.Function (on)
-import Data.List.NonEmpty ( NonEmpty (..), groupBy )
+import Data.List.NonEmpty ( NonEmpty (..) )
import qualified Data.List.NonEmpty as NE
-import Data.Maybe (fromMaybe)
import Data.Traversable ( for )
{- Note [Named default declarations]
@@ -86,7 +84,7 @@ The moving parts are as follows:
* The `DefaultEnv` of all defaults in scope in a module is kept in the `tcg_default`
field of `TcGblEnv`.
-* This field is populated by `GHC.Tc.Gen.Default.tcDefaults` which typechecks
+* This field is populated by `GHC.Tc.Gen.Default.tcDefaultDecls` which typechecks
any local or imported `default` declarations.
* Only a single default declaration can be in effect in any single module for
@@ -103,7 +101,7 @@ The moving parts are as follows:
in effect be `default Num (Integer, Double)` as specified by Haskell Language
Report.
- See Note [Default class defaults] in GHC.Tc.Utils.Env
+ See Note [Builtin class defaults] in GHC.Tc.Utils.Env
* Beside the defaults, the `ExtendedDefaultRules` and `OverloadedStrings`
extensions also affect the traditional `default` declarations that don't name
@@ -120,61 +118,54 @@ The moving parts are as follows:
tracked separately from `ImportAvails`, and returned separately from them by
`GHC.Rename.Names.rnImports`.
-* Class defaults are exported explicitly, as the example above shows. A module's
- exported defaults are tracked in `tcg_default_exports`, which are then
- transferred to `mg_defaults`, `md_defaults`, and `mi_defaults_`.
+* Class defaults are exported explicitly.
+ For example,
+ module M( ..., default C, ... )
+ exports the defaults for class C.
+
+ A module's exported defaults are computed by exports_from_avail,
+ tracked in tcg_default_exports, which are then transferred to mg_defaults,
+ md_defaults, and mi_defaults_.
+
+ Only defaults explicitly exported are actually exported.
+ (i.e. No defaults are exported in a module header like:
+ module M where ...)
+
See Note [Default exports] in GHC.Tc.Gen.Export
* Since the class defaults merely help the solver infer the correct types, they
leave no trace in Haskell Core.
-}
--- See Note [Named default declarations]
-tcDefaults :: [LDefaultDecl GhcRn]
- -> TcM DefaultEnv -- Defaulting types to heave
- -- into Tc monad for later use
- -- in Disambig.
-
-tcDefaults []
- = getDeclaredDefaultTys -- No default declaration, so get the
- -- default types from the envt;
- -- i.e. use the current ones
- -- (the caller will put them back there)
- -- It's important not to return defaultDefaultTys here (which
- -- we used to do) because in a TH program, tcDefaults [] is called
- -- repeatedly, once for each group of declarations between top-level
- -- splices. We don't want to carefully set the default types in
- -- one group, only for the next group to ignore them and install
- -- defaultDefaultTys
-
-tcDefaults decls
- = do { tcg_env <- getGblEnv
- ; let
- here = tcg_mod tcg_env
- is_internal_unit = moduleUnit here == ghcInternalUnit
- ; case (is_internal_unit, decls) of
- -- Some internal GHC modules contain @default ()@ to declare that no defaults can take place
- -- in the module.
- -- We shortcut the treatment of such a default declaration with no class nor types: we won't
- -- try to point 'cd_class' to 'Num' since it may not even exist yet.
- { (True, [L _ (DefaultDecl _ Nothing [])])
- -> return $ defaultEnv []
- -- Otherwise we take apart the declaration into the class constructor and its default types.
- ; _ ->
- do { h2010_dflt_clss <- getH2010DefaultClasses
- ; decls' <- mapMaybeM (declarationParts h2010_dflt_clss) decls
- ; let
- -- Find duplicate default declarations
- decl_tag (mb_cls, _, _) =
- case mb_cls of
- Nothing -> Nothing
- Just cls -> if cls `elem` h2010_dflt_clss
- then Nothing
- else Just cls
- decl_groups = groupBy ((==) `on` decl_tag) decls'
- ; decls_without_dups <- mapM (reportDuplicates here h2010_dflt_clss) decl_groups
- ; return $ defaultEnv (concat decls_without_dups)
- } } }
+-- | Typecheck a collection of default declarations. These can be either:
+--
+-- - Haskell 98 default declarations, of the form @default (Float, Double)@
+-- - Named default declarations, of the form @default Cls(Int, Char)@.
+-- See Note [Named default declarations]
+tcDefaultDecls :: [LDefaultDecl GhcRn] -> TcM [LocatedA ClassDefaults]
+tcDefaultDecls decls =
+ do
+ tcg_env <- getGblEnv
+ let here = tcg_mod tcg_env
+ is_internal_unit = moduleUnit here == ghcInternalUnit
+ case (is_internal_unit, decls) of
+ -- No default declarations
+ (_, []) -> return []
+ -- As per Remark [default () in ghc-internal] in Note [Builtin class defaults],
+ -- some modules in ghc-internal include an empty `default ()` declaration, in order
+ -- to disable built-in defaults. This is no longer necessary (see `GHC.Tc.Utils.Env.tcGetDefaultTys`),
+ -- but we must still make sure not to error if we fail to look up e.g. the 'Num'
+ -- typeclass when typechecking such a default declaration. To do this, we wrap
+ -- calls of 'tcLookupClass' in 'tryTc'.
+ (True, [L _ (DefaultDecl _ Nothing [])]) -> do
+ h2010_dflt_clss <- foldMapM (fmap maybeToList . fmap fst . tryTc . tcLookupClass) =<< getH2010DefaultNames
+ case NE.nonEmpty h2010_dflt_clss of
+ Nothing -> return []
+ Just h2010_dflt_clss' -> toClassDefaults h2010_dflt_clss' decls
+ -- Otherwise we take apart the declaration into the class constructor and its default types.
+ _ -> do
+ h2010_dflt_clss <- getH2010DefaultClasses
+ toClassDefaults h2010_dflt_clss decls
where
getH2010DefaultClasses :: TcM (NonEmpty Class)
-- All the classes subject to defaulting with a Haskell 2010 default
@@ -186,18 +177,18 @@ tcDefaults decls
-- No extensions: Num
-- OverloadedStrings: add IsString
-- ExtendedDefaults: add Show, Eq, Ord, Foldable, Traversable
- getH2010DefaultClasses
- = do { num_cls <- tcLookupClass numClassName
- ; ovl_str <- xoptM LangExt.OverloadedStrings
+ getH2010DefaultClasses = mapM tcLookupClass =<< getH2010DefaultNames
+ getH2010DefaultNames
+ = do { ovl_str <- xoptM LangExt.OverloadedStrings
; ext_deflt <- xoptM LangExt.ExtendedDefaultRules
- ; deflt_str <- if ovl_str
- then mapM tcLookupClass [isStringClassName]
- else return []
- ; deflt_interactive <- if ext_deflt
- then mapM tcLookupClass interactiveClassNames
- else return []
- ; let extra_clss = deflt_str ++ deflt_interactive
- ; return $ num_cls :| extra_clss
+ ; let deflt_str = if ovl_str
+ then [isStringClassName]
+ else []
+ ; let deflt_interactive = if ext_deflt
+ then interactiveClassNames
+ else []
+ ; let extra_clss_names = deflt_str ++ deflt_interactive
+ ; return $ numClassName :| extra_clss_names
}
declarationParts :: NonEmpty Class -> LDefaultDecl GhcRn -> TcM (Maybe (Maybe Class, LDefaultDecl GhcRn, [Type]))
declarationParts h2010_dflt_clss decl@(L locn (DefaultDecl _ mb_cls_name dflt_hs_tys))
@@ -220,20 +211,49 @@ tcDefaults decls
; return (Just cls, decl, tau_tys)
} }
- reportDuplicates :: Module -> NonEmpty Class -> NonEmpty (Maybe Class, LDefaultDecl GhcRn, [Type]) -> TcM [ClassDefaults]
- reportDuplicates here h2010_dflt_clss ((mb_cls, _, tys) :| [])
- = pure [ ClassDefaults{cd_class = c, cd_types = tys, cd_module = Just here, cd_warn = Nothing }
- | c <- case mb_cls of
- Nothing -> NE.toList h2010_dflt_clss
- Just cls -> [cls]
- ]
- -- Report an error on multiple default declarations for the same class in the same module.
- -- See Note [Disambiguation of multiple default declarations] in GHC.Tc.Module
- reportDuplicates _ (num_cls :| _) decls@((_, L locn _, _) :| _)
- = setSrcSpan (locA locn) (addErrTc $ dupDefaultDeclErr cls (sndOf3 <$> decls))
- >> pure []
+ toClassDefaults :: NonEmpty Class -> [LDefaultDecl GhcRn] -> TcM [LocatedA ClassDefaults]
+ toClassDefaults h2010_dflt_clss dfs = do
+ dfs <- mapMaybeM (declarationParts h2010_dflt_clss) dfs
+ return $ concatMap (go False) dfs
where
- cls = fromMaybe num_cls $ firstJusts (fmap fstOf3 decls)
+ go h98 = \case
+ (Nothing, rn_decl, tys) -> concatMap (go True) [(Just cls, rn_decl, tys) | cls <- NE.toList h2010_dflt_clss]
+ (Just cls, (L locn _), tys) -> [(L locn $ ClassDefaults cls tys (DP_Local (locA locn) h98) Nothing)]
+
+-- | Extend the default environment with the local default declarations
+-- and do the action in the extended environment.
+extendDefaultEnvWithLocalDefaults :: [LocatedA ClassDefaults] -> TcM a -> TcM a
+extendDefaultEnvWithLocalDefaults decls action = do
+ tcg_env <- getGblEnv
+ let default_env = tcg_default tcg_env
+ new_default_env <- insertDefaultDecls default_env decls
+ updGblEnv (\gbl -> gbl { tcg_default = new_default_env } ) $ action
+
+-- | Insert local default declarations into the default environment.
+--
+-- See 'insertDefaultDecl'.
+insertDefaultDecls :: DefaultEnv -> [LocatedA ClassDefaults] -> TcM DefaultEnv
+insertDefaultDecls = foldrM insertDefaultDecl
+-- | Insert a local default declaration into the default environment.
+--
+-- If the class already has a local default declaration in the DefaultEnv,
+-- report an error and return the original DefaultEnv. Otherwise, override
+-- any existing default declarations (e.g. imported default declarations).
+--
+-- See Note [Disambiguation of multiple default declarations] in GHC.Tc.Module
+insertDefaultDecl :: LocatedA ClassDefaults -> DefaultEnv -> TcM DefaultEnv
+insertDefaultDecl (L decl_loc new_cls_defaults ) default_env =
+ case lookupDefaultEnv default_env (className cls) of
+ Just cls_defaults
+ | DP_Local {} <- cd_provenance cls_defaults
+ -> do { setSrcSpan (locA decl_loc) (addErrTc $ TcRnMultipleDefaultDeclarations cls cls_defaults)
+ ; return default_env }
+ _ -> return $ insertDefaultEnv new_cls_defaults default_env
+ -- NB: this overrides imported and built-in default declarations
+ -- for this class, if there were any.
+ where
+ cls = cd_class new_cls_defaults
+
-- | Check that the type is an instance of at least one of the default classes.
--
@@ -289,10 +309,6 @@ simplifyDefault cls dflt_ty@(L l _)
-> Nothing
}
-dupDefaultDeclErr :: Class -> NonEmpty (LDefaultDecl GhcRn) -> TcRnMessage
-dupDefaultDeclErr cls (L _ DefaultDecl {} :| dup_things)
- = TcRnMultipleDefaultDeclarations cls dup_things
-
{- Note [Instance check for default declarations]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When we see a named default declaration, such as:
@@ -327,4 +343,4 @@ whether each type is an instance of:
- ... or the IsString class, with -XOverloadedStrings
- ... or any of the Show, Eq, Ord, Foldable, and Traversable classes,
with -XExtendedDefaultRules
--}
\ No newline at end of file
+-}
=====================================
compiler/GHC/Tc/Gen/Export.hs
=====================================
@@ -282,7 +282,7 @@ example,
would import the above `default IsString (Text, String)` declaration into the
importing module.
-The `cd_module` field of `ClassDefaults` tracks the module whence the default was
+The `cd_provenance` field of `ClassDefaults` tracks the module whence the default was
imported from, for the purpose of warning reports. The said warning report may be
triggered by `-Wtype-defaults` or by a user-defined `WARNING` pragma attached to
the default export. In the latter case the warning text is stored in the
=====================================
compiler/GHC/Tc/Module.hs
=====================================
@@ -383,6 +383,7 @@ the actual contents of the module are wired in to GHC.
-}
{- Note [Disambiguation of multiple default declarations]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
See Note [Named default declarations] in GHC.Tc.Gen.Default
@@ -1811,9 +1812,8 @@ tcTyClsInstDecls tycl_decls deriv_decls default_decls binds
--
-- But only after we've typechecked 'default' declarations.
-- See Note [Typechecking default declarations]
- defaults <- tcDefaults default_decls ;
- updGblEnv (\gbl -> gbl { tcg_default = defaults }) $ do {
-
+ defaults <- tcDefaultDecls default_decls
+ ; extendDefaultEnvWithLocalDefaults defaults $ do {
-- Careful to quit now in case there were instance errors, so that
-- the deriving errors don't pile up as well.
=====================================
compiler/GHC/Tc/Utils/Env.hs
=====================================
@@ -128,8 +128,7 @@ import GHC.Types.SourceFile
import GHC.Types.Name
import GHC.Types.Name.Set
import GHC.Types.Name.Env
-import GHC.Types.DefaultEnv ( DefaultEnv, ClassDefaults(..),
- defaultEnv, emptyDefaultEnv, lookupDefaultEnv, unitDefaultEnv )
+import GHC.Types.DefaultEnv
import GHC.Types.Error
import GHC.Types.Id
import GHC.Types.Id.Info ( RecSelParent(..) )
@@ -971,21 +970,28 @@ isBrackStage _other = False
************************************************************************
-}
-{- Note [Default class defaults]
+{- Note [Builtin class defaults]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-In absence of user-defined `default` declarations, the set of class defaults in
-effect (i.e. `DefaultEnv`) is determined by the absence or
-presence of the `ExtendedDefaultRules` and `OverloadedStrings` extensions. In their
-absence, the only rule in effect is `default Num (Integer, Double)` as specified by
-Haskell Language Report.
-
-In GHC's internal packages `DefaultEnv` is empty to minimize cross-module dependencies:
-the `Num` class or `Integer` type may not even be available in low-level modules. If
-you don't do this, attempted defaulting in package ghc-prim causes an actual crash
-(attempting to look up the `Integer` type).
-
-A user-defined `default` declaration overrides the defaults for the specified class,
-and only for that class.
+In the absence of user-defined `default` declarations, the set of class defaults in
+effect (i.e. the `DefaultEnv`) depends on whether the `ExtendedDefaultRules` and
+`OverloadedStrings` extensions are enabled. In their absence, the only rule in effect
+is `default Num (Integer, Double)`, as specified by the Haskell 2010 report.
+
+Remark [No built-in defaults in ghc-internal]
+
+ When typechecking the ghc-internal package, we **do not** include any built-in
+ defaults. This is because, in ghc-internal, types such as 'Num' or 'Integer' may
+ not even be available (they haven't been typechecked yet).
+
+Remark [default () in ghc-internal]
+
+ Historically, modules inside ghc-internal have used a single default declaration,
+ of the form `default ()`, to work around the problem described in
+ Remark [No built-in defaults in ghc-internal].
+
+ When we typecheck such a default declaration, we must also make sure not to fail
+ if e.g. 'Num' is not in scope. We thus have special treatment for this case,
+ in 'GHC.Tc.Gen.Default.tcDefaultDecls'.
-}
tcGetDefaultTys :: TcM (DefaultEnv, -- Default classes and types
@@ -997,7 +1003,7 @@ tcGetDefaultTys
-- See also #1974
builtinDefaults cls tys = ClassDefaults{ cd_class = cls
, cd_types = tys
- , cd_module = Nothing
+ , cd_provenance = DP_Builtin
, cd_warn = Nothing }
-- see Note [Named default declarations] in GHC.Tc.Gen.Default
@@ -1005,7 +1011,8 @@ tcGetDefaultTys
; this_module <- tcg_mod <$> getGblEnv
; let this_unit = moduleUnit this_module
; if this_unit == ghcInternalUnit
- -- see Note [Default class defaults]
+ -- see Remark [No built-in defaults in ghc-internal]
+ -- in Note [Builtin class defaults] in GHC.Tc.Utils.Env
then return (defaults, extended_defaults)
else do
-- not one of the built-in units
@@ -1037,6 +1044,8 @@ tcGetDefaultTys
}
-- The Num class is already user-defaulted, no need to construct the builtin default
_ -> pure emptyDefaultEnv
+ -- Supply the built-in defaults, but make the user-supplied defaults
+ -- override them.
; let deflt_tys = mconcat [ extDef, numDef, ovlStr, defaults ]
; return (deflt_tys, extended_defaults) } }
=====================================
compiler/GHC/Types/DefaultEnv.hs
=====================================
@@ -1,7 +1,9 @@
{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE LambdaCase #-}
module GHC.Types.DefaultEnv
( ClassDefaults (..)
+ , DefaultProvenance (..)
, DefaultEnv
, emptyDefaultEnv
, isEmptyDefaultEnv
@@ -12,6 +14,8 @@ module GHC.Types.DefaultEnv
, defaultList
, plusDefaultEnv
, mkDefaultEnv
+ , insertDefaultEnv
+ , isHaskell2010Default
)
where
@@ -22,6 +26,7 @@ import GHC.Tc.Utils.TcType (Type)
import GHC.Types.Name (Name, nameUnique, stableNameCmp)
import GHC.Types.Name.Env
import GHC.Types.Unique.FM (lookupUFM_Directly)
+import GHC.Types.SrcLoc (SrcSpan)
import GHC.Unit.Module.Warnings (WarningTxt)
import GHC.Unit.Types (Module)
import GHC.Utils.Outputable
@@ -37,13 +42,73 @@ import Data.Function (on)
-- NB: this includes Haskell98 default declarations, at the 'Num' key.
type DefaultEnv = NameEnv ClassDefaults
+{- Note [DefaultProvenance]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Each `ClassDefault` is annotated with its `DefaultProvenance`, which
+says where the default came from. Specifically
+* `DP_Local loc h98`: the default came from an explicit `default` declaration in the module
+ being compiled, at location `loc`, and the boolean `h98` indicates whether
+ it was from a Haskell 98 default declaration (e.g. `default (Int, Double)`).
+* `DP_Imported M`: the default was imported, it is explicitly exported by module `M`.
+* `DP_Builtin`: the default was automatically provided by GHC.
+ see Note [Builtin class defaults] in GHC.Tc.Utils.Env
+
+These annotations are used to disambiguate multiple defaults for the same class.
+For example, consider the following modules:
+
+ module M( default C ) where { default C( ... ) }
+ module M2( default C) where { import M }
+ module N( default C () where { default C(... ) }
+
+ module A where { import M2 }
+ module B where { import M2; import N }
+ module A1 where { import N; default C ( ... ) }
+ module B2 where { default C ( ... ); default C ( ... ) }
+
+When compiling N, the default for C is annotated with DP_Local loc.
+When compiling M2, the default for C is annotated with DP_Local M.
+When compiling A, the default for C is annotated with DP_Imported M2.
+
+Cases we needed to disambiguate:
+ * Compiling B, two defaults for C: DP_Imported M2, DP_Imported N.
+ * Compiling A1, two defaults for C: DP_Imported N, DP_Local loc.
+ * Compiling B2, two defaults for C: DP_Local loc1, DP_Local loc2.
+
+For how we disambiguate these cases,
+See Note [Disambiguation of multiple default declarations] in GHC.Tc.Module.
+-}
+
+-- | The provenance of a collection of default types for a class.
+-- see Note [DefaultProvenance] for more details
+data DefaultProvenance
+ -- | A locally defined default declaration.
+ = DP_Local
+ { defaultDeclLoc :: SrcSpan -- ^ The 'SrcSpan' of the default declaration
+ , defaultDeclH98 :: Bool -- ^ Is this a Haskell 98 default declaration?
+ }
+ -- | Built-in class defaults.
+ | DP_Builtin
+ -- | Imported class defaults.
+ | DP_Imported Module -- ^ The module from which the defaults were imported
+ deriving (Eq, Data)
+
+instance Outputable DefaultProvenance where
+ ppr (DP_Local loc h98) = ppr loc <> (if h98 then text " (H98)" else empty)
+ ppr DP_Builtin = text "built-in"
+ ppr (DP_Imported mod) = ppr mod
+
+isHaskell2010Default :: DefaultProvenance -> Bool
+isHaskell2010Default = \case
+ DP_Local { defaultDeclH98 = isH98 } -> isH98
+ DP_Builtin -> True
+ DP_Imported {} -> False
+
-- | Defaulting type assignments for the given class.
data ClassDefaults
= ClassDefaults { cd_class :: Class -- ^ The class whose defaults are being defined
, cd_types :: [Type]
- , cd_module :: Maybe Module
- -- ^ @Nothing@ for built-in,
- -- @Just@ the current module or the module whence the default was imported
+ , cd_provenance :: DefaultProvenance
+ -- ^ Where the defaults came from
-- see Note [Default exports] in GHC.Tc.Gen.Export
, cd_warn :: Maybe (WarningTxt GhcRn)
-- ^ Warning emitted when the default is used
@@ -70,6 +135,9 @@ defaultList :: DefaultEnv -> [ClassDefaults]
defaultList = sortBy (stableNameCmp `on` className . cd_class) . nonDetNameEnvElts
-- sortBy recovers determinism
+insertDefaultEnv :: ClassDefaults -> DefaultEnv -> DefaultEnv
+insertDefaultEnv d env = extendNameEnv env (className $ cd_class d) d
+
lookupDefaultEnv :: DefaultEnv -> Name -> Maybe ClassDefaults
lookupDefaultEnv env = lookupUFM_Directly env . nameUnique
=====================================
testsuite/tests/default/T25912.hs
=====================================
@@ -0,0 +1,14 @@
+{-# LANGUAGE NamedDefaults #-}
+
+module Main where
+
+import T25912_helper
+
+-- now we declare the default instances
+-- for the classes C again to check that
+-- it won't hide the default instances for class B
+default C (String)
+
+main :: IO ()
+main = do
+ print b
=====================================
testsuite/tests/default/T25912.stdout
=====================================
@@ -0,0 +1 @@
+"String"
=====================================
testsuite/tests/default/T25912_helper.hs
=====================================
@@ -0,0 +1,17 @@
+{-# LANGUAGE NamedDefaults #-}
+
+module T25912_helper ( default C, C(c), default B, b ) where
+
+class C a where
+ c :: a
+instance C Int where
+ c = 1
+instance C String where
+ c = "String"
+default C (String)
+
+class B a where
+ b :: a
+instance B String where
+ b = "String"
+default B (String)
=====================================
testsuite/tests/default/T25914.hs
=====================================
@@ -0,0 +1,5 @@
+{-# LANGUAGE NamedDefaults, OverloadedStrings #-}
+module NamedDefaultsNum where
+import Data.String
+default Num ()
+foo = "abc"
=====================================
testsuite/tests/default/T25934.hs
=====================================
@@ -0,0 +1,5 @@
+{-# LANGUAGE ExtendedDefaultRules #-}
+{-# LANGUAGE NamedDefaults #-}
+module T25934 where
+default Num (Int)
+default Show (Int)
=====================================
testsuite/tests/default/all.T
=====================================
@@ -39,3 +39,6 @@ test('T25858v2', [extra_files(['T25858v2_helper.hs'])], multimod_compile_and_run
test('T25858v3', [extra_files(['T25858v3_helper.hs'])], multimod_compile_and_run, ['T25858v3', ''])
test('T25858v4', normal, compile_and_run, [''])
test('T25882', normal, compile, [''])
+test('T25912', [extra_files(['T25912_helper.hs'])], multimod_compile_and_run, ['T25912', ''])
+test('T25914', normal, compile, [''])
+test('T25934', normal, compile, [''])
=====================================
testsuite/tests/default/default-fail03.stderr
=====================================
@@ -1,3 +1,4 @@
-default-fail03.hs:4:1: [GHC-99565]
+default-fail03.hs:4:1: error: [GHC-99565]
Multiple default declarations for class ‘Num’
- here was another default declaration default-fail03.hs:3:1-29
+ conflicting named default declaration at: default-fail03.hs:3:1-29
+
=====================================
testsuite/tests/linters/notes.stdout
=====================================
@@ -8,7 +8,7 @@ ref compiler/GHC/Core/Opt/Simplify/Iteration.hs:4345:8: Note [Lambda-boun
ref compiler/GHC/Core/Opt/Simplify/Utils.hs:1387:37: Note [Gentle mode]
ref compiler/GHC/Core/Opt/Specialise.hs:1761:29: Note [Arity decrease]
ref compiler/GHC/Core/TyCo/Rep.hs:1783:31: Note [What prevents a constraint from floating]
-ref compiler/GHC/Driver/DynFlags.hs:1216:52: Note [Eta-reduction in -O0]
+ref compiler/GHC/Driver/DynFlags.hs:1218:52: Note [Eta-reduction in -O0]
ref compiler/GHC/Driver/Main.hs:1901:34: Note [simpleTidyPgm - mkBootModDetailsTc]
ref compiler/GHC/Hs/Expr.hs:189:63: Note [Pending Splices]
ref compiler/GHC/Hs/Expr.hs:2194:87: Note [Lifecycle of a splice]
@@ -18,10 +18,8 @@ ref compiler/GHC/Hs/Pat.hs:151:74: Note [Lifecycle of a splice]
ref compiler/GHC/HsToCore/Pmc/Solver.hs:860:20: Note [COMPLETE sets on data families]
ref compiler/GHC/HsToCore/Quote.hs:1533:7: Note [How brackets and nested splices are handled]
ref compiler/GHC/Stg/Unarise.hs:457:32: Note [Renaming during unarisation]
-ref compiler/GHC/Tc/Gen/Default.hs:87:6: Note [Disambiguation of multiple default declarations]
-ref compiler/GHC/Tc/Gen/Default.hs:193:11: Note [Disambiguation of multiple default declarations]
ref compiler/GHC/Tc/Gen/HsType.hs:563:56: Note [Skolem escape prevention]
-ref compiler/GHC/Tc/Gen/HsType.hs:2693:7: Note [Matching a kind signature with a declaration]
+ref compiler/GHC/Tc/Gen/HsType.hs:2717:7: Note [Matching a kind signature with a declaration]
ref compiler/GHC/Tc/Gen/Pat.hs:284:20: Note [Typing patterns in pattern bindings]
ref compiler/GHC/Tc/Gen/Pat.hs:1378:7: Note [Matching polytyped patterns]
ref compiler/GHC/Tc/Gen/Sig.hs:91:10: Note [Overview of type signatures]
@@ -30,8 +28,6 @@ ref compiler/GHC/Tc/Gen/Splice.hs:543:35: Note [PendingRnSplice]
ref compiler/GHC/Tc/Gen/Splice.hs:670:7: Note [How brackets and nested splices are handled]
ref compiler/GHC/Tc/Gen/Splice.hs:909:11: Note [How brackets and nested splices are handled]
ref compiler/GHC/Tc/Instance/Family.hs:458:35: Note [Constrained family instances]
-ref compiler/GHC/Tc/Module.hs:385:3: Note [Disambiguation of multiple default declarations]
-ref compiler/GHC/Tc/Module.hs:420:7: Note [Disambiguation of multiple default declarations]
ref compiler/GHC/Tc/Solver/Rewrite.hs:1015:7: Note [Stability of rewriting]
ref compiler/GHC/Tc/TyCl.hs:1322:6: Note [Unification variables need fresh Names]
ref compiler/GHC/Tc/Types/Constraint.hs:209:9: Note [NonCanonical Semantics]
=====================================
testsuite/tests/module/mod58.stderr
=====================================
@@ -1,4 +1,4 @@
-
mod58.hs:4:1: error: [GHC-99565]
Multiple default declarations for class ‘Num’
- here was another default declaration mod58.hs:3:1-21
+ conflicting default declaration at: mod58.hs:3:1-21
+
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/e0f3ff11719f1ee343d1b5d1fd3193d…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/e0f3ff11719f1ee343d1b5d1fd3193d…
You're receiving this email because of your account on gitlab.haskell.org.
1
0

[Git][ghc/ghc][wip/T25965] Fix infelicities in the Specialiser
by Simon Peyton Jones (@simonpj) 17 Apr '25
by Simon Peyton Jones (@simonpj) 17 Apr '25
17 Apr '25
Simon Peyton Jones pushed to branch wip/T25965 at Glasgow Haskell Compiler / GHC
Commits:
3909c689 by Simon Peyton Jones at 2025-04-17T09:06:59+01:00
Fix infelicities in the Specialiser
On the way to #23109 (unary classes) I discovered some infelicities
(or maybe tiny bugs, I forget) in the type-class specialiser.
I also tripped over #25965, an outright bug in the rule matcher
Specifically:
* Refactor: I enhanced `wantCallsFor`, whih previously always said
`True`, to discard calls of class-ops, data constructors etc. This is
a bit more efficient; and it means we don't need to worry about
filtering them out later.
* Fix: I tidied up some tricky logic that eliminated redundant
specialisations. It wasn't working correctly. See the expanded
Note [Specialisations already covered], and
(MP3) in Note [Specialising polymorphic dictionaries].
See also the new top-level `alreadyCovered`
function, which now goes via `GHC.Core.Rules.ruleLhsIsMoreSpecific`
I also added a useful Note [The (CI-KEY) invariant]
* Fix #25965: fixed a tricky bug in the `go_fam_fam` in
`GHC.Core.Unify.uVarOrFam`, which allows matching to succeed
without binding all type varibles.
I enhanced Note [Apartness and type families] some more
* #25703. This ticket "just works" with -fpolymorphic-specialisation;
but I was surprised that it worked! In this MR I added documentation
to Note [Interesting dictionary arguments] to explain; and tests to
ensure it stays fixed.
- - - - -
11 changed files:
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/Rules.hs
- compiler/GHC/Core/Unify.hs
- compiler/GHC/Tc/Solver/Equality.hs
- compiler/GHC/Types/Basic.hs
- + testsuite/tests/simplCore/should_compile/T25703.hs
- + testsuite/tests/simplCore/should_compile/T25703.stderr
- + testsuite/tests/simplCore/should_compile/T25703a.hs
- + testsuite/tests/simplCore/should_compile/T25703a.stderr
- + testsuite/tests/simplCore/should_compile/T25965.hs
- testsuite/tests/simplCore/should_compile/all.T
Changes:
=====================================
compiler/GHC/Core/Opt/Specialise.hs
=====================================
@@ -1243,14 +1243,15 @@ specExpr env (Let bind body)
-- Note [Fire rules in the specialiser]
fireRewriteRules :: SpecEnv -> InExpr -> [OutExpr] -> (InExpr, [OutExpr])
fireRewriteRules env (Var f) args
- | Just (rule, expr) <- specLookupRule env f args InitialPhase (getRules (se_rules env) f)
+ | let rules = getRules (se_rules env) f
+ , Just (rule, expr) <- specLookupRule env f args activeInInitialPhase rules
, let rest_args = drop (ruleArity rule) args -- See Note [Extra args in the target]
zapped_subst = Core.zapSubst (se_subst env)
expr' = simpleOptExprWith defaultSimpleOpts zapped_subst expr
-- simplOptExpr needed because lookupRule returns
-- (\x y. rhs) arg1 arg2
- , (fun, args) <- collectArgs expr'
- = fireRewriteRules env fun (args++rest_args)
+ , (fun', args') <- collectArgs expr'
+ = fireRewriteRules env fun' (args'++rest_args)
fireRewriteRules _ fun args = (fun, args)
--------------
@@ -1620,7 +1621,7 @@ specCalls :: Bool -- True => specialising imported fn
-- This function checks existing rules, and does not create
-- duplicate ones. So the caller does not need to do this filtering.
--- See 'already_covered'
+-- See `alreadyCovered`
type SpecInfo = ( [CoreRule] -- Specialisation rules
, [(Id,CoreExpr)] -- Specialised definition
@@ -1644,15 +1645,13 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
= -- pprTrace "specCalls: some" (vcat
-- [ text "function" <+> ppr fn
- -- , text "calls:" <+> ppr calls_for_me
- -- , text "subst" <+> ppr (se_subst env) ]) $
+ -- , text "calls:" <+> ppr calls_for_me
+ -- , text "subst" <+> ppr (se_subst env) ]) $
foldlM spec_call ([], [], emptyUDs) calls_for_me
| otherwise -- No calls or RHS doesn't fit our preconceptions
- = warnPprTrace (not (exprIsTrivial rhs) && notNull calls_for_me && not (isClassOpId fn))
+ = warnPprTrace (not (exprIsTrivial rhs) && notNull calls_for_me)
"Missed specialisation opportunity for" (ppr fn $$ trace_doc) $
- -- isClassOpId: class-op Ids never inline; we specialise them
- -- through fireRewriteRules. So don't complain about missed opportunities
-- Note [Specialisation shape]
-- pprTrace "specCalls: none" (ppr fn <+> ppr calls_for_me) $
return ([], [], emptyUDs)
@@ -1664,6 +1663,10 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
fn_unf = realIdUnfolding fn -- Ignore loop-breaker-ness here
inl_prag = idInlinePragma fn
inl_act = inlinePragmaActivation inl_prag
+ is_active = isActive (beginPhase inl_act) :: Activation -> Bool
+ -- is_active: inl_act is the activation we are going to put in the new
+ -- SPEC rule; so we want to see if it is covered by another rule with
+ -- that same activation.
is_local = isLocalId fn
is_dfun = isDFunId fn
dflags = se_dflags env
@@ -1674,16 +1677,6 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
(rhs_bndrs, rhs_body) = collectBindersPushingCo rhs
-- See Note [Account for casts in binding]
- already_covered :: SpecEnv -> [CoreRule] -> [CoreExpr] -> Bool
- already_covered env new_rules args -- Note [Specialisations already covered]
- = isJust (specLookupRule env fn args (beginPhase inl_act)
- (new_rules ++ existing_rules))
- -- Rules: we look both in the new_rules (generated by this invocation
- -- of specCalls), and in existing_rules (passed in to specCalls)
- -- inl_act: is the activation we are going to put in the new SPEC
- -- rule; so we want to see if it is covered by another rule with
- -- that same activation.
-
----------------------------------------------------------
-- Specialise to one particular call pattern
spec_call :: SpecInfo -- Accumulating parameter
@@ -1717,8 +1710,12 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
-- , ppr dx_binds ]) $
-- return ()
+ ; let all_rules = rules_acc ++ existing_rules
+ -- all_rules: we look both in the rules_acc (generated by this invocation
+ -- of specCalls), and in existing_rules (passed in to specCalls)
; if not useful -- No useful specialisation
- || already_covered rhs_env2 rules_acc rule_lhs_args
+ || alreadyCovered rhs_env2 rule_bndrs fn rule_lhs_args is_active all_rules
+ -- See (SC1) in Note [Specialisations already covered]
then return spec_acc
else
do { -- Run the specialiser on the specialised RHS
@@ -1780,7 +1777,7 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
spec_fn_details
= case idDetails fn of
JoinId join_arity _ -> JoinId (join_arity - join_arity_decr) Nothing
- DFunId is_nt -> DFunId is_nt
+ DFunId unary -> DFunId unary
_ -> VanillaId
; spec_fn <- newSpecIdSM (idName fn) spec_fn_ty spec_fn_details spec_fn_info
@@ -1804,6 +1801,8 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
, ppr spec_fn <+> dcolon <+> ppr spec_fn_ty
, ppr rhs_bndrs, ppr call_args
, ppr spec_rule
+ , text "acc" <+> ppr rules_acc
+ , text "existing" <+> ppr existing_rules
]
; -- pprTrace "spec_call: rule" _rule_trace_doc
@@ -1812,19 +1811,35 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
, spec_uds `thenUDs` uds_acc
) } }
+alreadyCovered :: SpecEnv
+ -> [Var] -> Id -> [CoreExpr] -- LHS of possible new rule
+ -> (Activation -> Bool) -- Which rules are active
+ -> [CoreRule] -> Bool
+-- Note [Specialisations already covered] esp (SC2)
+alreadyCovered env bndrs fn args is_active rules
+ = case specLookupRule env fn args is_active rules of
+ Nothing -> False
+ Just (rule, _)
+ | isAutoRule rule -> -- Discard identical rules
+ -- We know that (fn args) is an instance of RULE
+ -- Check if RULE is an instance of (fn args)
+ ruleLhsIsMoreSpecific in_scope bndrs args rule
+ | otherwise -> True -- User rules dominate
+ where
+ in_scope = substInScopeSet (se_subst env)
+
-- Convenience function for invoking lookupRule from Specialise
-- The SpecEnv's InScopeSet should include all the Vars in the [CoreExpr]
specLookupRule :: SpecEnv -> Id -> [CoreExpr]
- -> CompilerPhase -- Look up rules as if we were in this phase
+ -> (Activation -> Bool) -- Which rules are active
-> [CoreRule] -> Maybe (CoreRule, CoreExpr)
-specLookupRule env fn args phase rules
+specLookupRule env fn args is_active rules
= lookupRule ropts in_scope_env is_active fn args rules
where
dflags = se_dflags env
in_scope = substInScopeSet (se_subst env)
in_scope_env = ISE in_scope (whenActiveUnfoldingFun is_active)
ropts = initRuleOpts dflags
- is_active = isActive phase
{- Note [Specialising DFuns]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2323,21 +2338,24 @@ This plan is implemented in the Rec case of specBindItself.
Note [Specialisations already covered]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We obviously don't want to generate two specialisations for the same
-argument pattern. There are two wrinkles
-
-1. We do the already-covered test in specDefn, not when we generate
-the CallInfo in mkCallUDs. We used to test in the latter place, but
-we now iterate the specialiser somewhat, and the Id at the call site
-might therefore not have all the RULES that we can see in specDefn
-
-2. What about two specialisations where the second is an *instance*
-of the first? If the more specific one shows up first, we'll generate
-specialisations for both. If the *less* specific one shows up first,
-we *don't* currently generate a specialisation for the more specific
-one. (See the call to lookupRule in already_covered.) Reasons:
- (a) lookupRule doesn't say which matches are exact (bad reason)
- (b) if the earlier specialisation is user-provided, it's
- far from clear that we should auto-specialise further
+argument pattern. Wrinkles
+
+(SC1) We do the already-covered test in specDefn, not when we generate
+ the CallInfo in mkCallUDs. We used to test in the latter place, but
+ we now iterate the specialiser somewhat, and the Id at the call site
+ might therefore not have all the RULES that we can see in specDefn
+
+(SC2) What about two specialisations where the second is an *instance*
+ of the first? It's a bit arbitrary, but here's what we do:
+ * If the existing one is user-specified, via a SPECIALISE pragma, we
+ suppress the further specialisation.
+ * If the existing one is auto-generated, we generate a second RULE
+ for the more specialised version.
+ The latter is important because we don't want the accidental order
+ of calls to determine what specialisations we generate.
+
+(SC3) Annoyingly, we /also/ eliminate duplicates in `filterCalls`.
+ See (MP3) in Note [Specialising polymorphic dictionaries]
Note [Auto-specialisation and RULES]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2800,12 +2818,10 @@ non-dictionary bindings too.
Note [Specialising polymorphic dictionaries]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Note June 2023: This has proved to be quite a tricky optimisation to get right
see (#23469, #23109, #21229, #23445) so it is now guarded by a flag
`-fpolymorphic-specialisation`.
-
Consider
class M a where { foo :: a -> Int }
@@ -2845,11 +2861,26 @@ Here are the moving parts:
function.
(MP3) If we have f :: forall m. Monoid m => blah, and two calls
- (f @(Endo b) (d :: Monoid (Endo b))
- (f @(Endo (c->c)) (d :: Monoid (Endo (c->c)))
+ (f @(Endo b) (d1 :: Monoid (Endo b))
+ (f @(Endo (c->c)) (d2 :: Monoid (Endo (c->c)))
we want to generate a specialisation only for the first. The second
is just a substitution instance of the first, with no greater specialisation.
- Hence the call to `remove_dups` in `filterCalls`.
+ Hence the use of `removeDupCalls` in `filterCalls`.
+
+ You might wonder if `d2` might be more specialised than `d1`; but no.
+ This `removeDupCalls` thing is at the definition site of `f`, and both `d1`
+ and `d2` are in scope. So `d1` is simply more polymorphic than `d2`, but
+ is just as specialised.
+
+ This distinction is sadly lost once we build a RULE, so `alreadyCovered`
+ can't be so clever. E.g if we have an existing RULE
+ forall @a (d1:Ord Int) (d2: Eq a). f @a @Int d1 d2 = ...
+ and a putative new rule
+ forall (d1:Ord Int) (d2: Eq Int). f @Int @Int d1 d2 = ...
+ we /don't/ want the existing rule to subsume the new one.
+
+ So we sadly put up with having two rather different places where we
+ eliminate duplicates: `alreadyCovered` and `removeDupCalls`.
All this arose in #13873, in the unexpected form that a SPECIALISE
pragma made the program slower! The reason was that the specialised
@@ -2947,16 +2978,29 @@ data CallInfoSet = CIS Id (Bag CallInfo)
-- The list of types and dictionaries is guaranteed to
-- match the type of f
-- The Bag may contain duplicate calls (i.e. f @T and another f @T)
- -- These dups are eliminated by already_covered in specCalls
+ -- These dups are eliminated by alreadyCovered in specCalls
data CallInfo
- = CI { ci_key :: [SpecArg] -- All arguments
+ = CI { ci_key :: [SpecArg] -- Arguments of the call
+ -- See Note [The (CI-KEY) invariant]
+
, ci_fvs :: IdSet -- Free Ids of the ci_key call
-- /not/ including the main id itself, of course
-- NB: excluding tyvars:
-- See Note [Specialising polymorphic dictionaries]
}
+{- Note [The (CI-KEY) invariant]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Invariant (CI-KEY):
+ In the `ci_key :: [SpecArg]` field of `CallInfo`,
+ * The list is non-empty
+ * The least element is always a `SpecDict`
+
+In this way the RULE has as few args as possible, which broadens its
+applicability, since rules only fire when saturated.
+-}
+
type DictExpr = CoreExpr
ciSetFilter :: (CallInfo -> Bool) -> CallInfoSet -> CallInfoSet
@@ -3045,10 +3089,7 @@ mkCallUDs' env f args
ci_key :: [SpecArg]
ci_key = dropWhileEndLE (not . isSpecDict) $
zipWith mk_spec_arg args pis
- -- Drop trailing args until we get to a SpecDict
- -- In this way the RULE has as few args as possible,
- -- which broadens its applicability, since rules only
- -- fire when saturated
+ -- Establish (CI-KEY): drop trailing args until we get to a SpecDict
mk_spec_arg :: OutExpr -> PiTyBinder -> SpecArg
mk_spec_arg arg (Named bndr)
@@ -3086,34 +3127,76 @@ site, so we only look through ticks that RULE matching looks through
-}
wantCallsFor :: SpecEnv -> Id -> Bool
-wantCallsFor _env _f = True
- -- We could reduce the size of the UsageDetails by being less eager
- -- about collecting calls for LocalIds: there is no point for
- -- ones that are lambda-bound. We can't decide this by looking at
- -- the (absence of an) unfolding, because unfoldings for local
- -- functions are discarded by cloneBindSM, so no local binder will
- -- have an unfolding at this stage. We'd have to keep a candidate
- -- set of let-binders.
- --
- -- Not many lambda-bound variables have dictionary arguments, so
- -- this would make little difference anyway.
- --
- -- For imported Ids we could check for an unfolding, but we have to
- -- do so anyway in canSpecImport, and it seems better to have it
- -- all in one place. So we simply collect usage info for imported
- -- overloaded functions.
-
-{- Note [Interesting dictionary arguments]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Consider this
- \a.\d:Eq a. let f = ... in ...(f d)...
-There really is not much point in specialising f wrt the dictionary d,
-because the code for the specialised f is not improved at all, because
-d is lambda-bound. We simply get junk specialisations.
-
-What is "interesting"? Just that it has *some* structure. But what about
-variables? We look in the variable's /unfolding/. And that means
-that we must be careful to ensure that dictionaries have unfoldings,
+-- See Note [wantCallsFor]
+wantCallsFor _env f
+ = case idDetails f of
+ RecSelId {} -> False
+ DataConWorkId {} -> False
+ DataConWrapId {} -> False
+ ClassOpId {} -> False
+ PrimOpId {} -> False
+ FCallId {} -> False
+ TickBoxOpId {} -> False
+ CoVarId {} -> False
+
+ DFunId {} -> True
+ VanillaId {} -> True
+ JoinId {} -> True
+ WorkerLikeId {} -> True
+ RepPolyId {} -> True
+
+{- Note [wantCallsFor]
+~~~~~~~~~~~~~~~~~~~~~~
+`wantCallsFor env f` says whether the Specialiser should collect calls for
+function `f`; other thing being equal, the fewer calls we collect the better. It
+is False for things we can't specialise:
+
+* ClassOpId: never inline and we don't have a defn to specialise; we specialise
+ them through fireRewriteRules.
+* PrimOpId: are never overloaded
+* Data constructors: we never specialise them
+
+We could reduce the size of the UsageDetails by being less eager about
+collecting calls for some LocalIds: there is no point for ones that are
+lambda-bound. We can't decide this by looking at the (absence of an) unfolding,
+because unfoldings for local functions are discarded by cloneBindSM, so no local
+binder will have an unfolding at this stage. We'd have to keep a candidate set
+of let-binders.
+
+Not many lambda-bound variables have dictionary arguments, so this would make
+little difference anyway.
+
+For imported Ids we could check for an unfolding, but we have to do so anyway in
+canSpecImport, and it seems better to have it all in one place. So we simply
+collect usage info for imported overloaded functions.
+
+Note [Interesting dictionary arguments]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+In `mkCallUDs` we only use `SpecDict` for dictionaries of which
+`interestingDict` holds. Otherwise we use `UnspecArg`. Two reasons:
+
+* Consider this
+ \a.\d:Eq a. let f = ... in ...(f d)...
+ There really is not much point in specialising f wrt the dictionary d,
+ because the code for the specialised f is not improved at all, because
+ d is lambda-bound. We simply get junk specialisations.
+
+* Consider this (#25703):
+ f :: (Eq a, Show b) => a -> b -> INt
+ goo :: forall x. (Eq x) => x -> blah
+ goo @x (d:Eq x) (arg:x) = ...(f @x @Int d $fShowInt)...
+ If we built a `ci_key` with a (SpecDict d) for `d`, we would end up
+ discarding the call at the `\d`. But if we use `UnspecArg` for that
+ uninteresting `d`, we'll get a `ci_key` of
+ f @x @Int UnspecArg (SpecDict $fShowInt)
+ and /that/ can float out to f's definition and specialise nicely.
+ Hooray. (NB: the call can float only if `-fpolymorphic-specialisation`
+ is on; otherwise it'll be trapped by the `\@x -> ...`.)(
+
+What is "interesting"? (See `interestingDict`.) Just that it has *some*
+structure. But what about variables? We look in the variable's /unfolding/.
+And that means that we must be careful to ensure that dictionaries /have/
+unfoldings,
* cloneBndrSM discards non-Stable unfoldings
* specBind updates the unfolding after specialisation
@@ -3159,7 +3242,7 @@ Now `f` turns into:
meth @a dc ....
When we specialise `f`, at a=Int say, that superclass selection can
-nfire (via rewiteClassOps), but that info (that 'dc' is now a
+fire (via rewiteClassOps), but that info (that 'dc' is now a
particular dictionary `C`, of type `C Int`) must be available to
the call `meth @a dc`, so that we can fire the `meth` class-op, and
thence specialise `wombat`.
@@ -3286,7 +3369,11 @@ dumpUDs :: [CoreBndr] -> UsageDetails -> (UsageDetails, OrdList DictBind)
-- Used at a lambda or case binder; just dump anything mentioning the binder
dumpUDs bndrs uds@(MkUD { ud_binds = orig_dbs, ud_calls = orig_calls })
| null bndrs = (uds, nilOL) -- Common in case alternatives
- | otherwise = -- pprTrace "dumpUDs" (ppr bndrs $$ ppr free_uds $$ ppr dump_dbs) $
+ | otherwise = -- pprTrace "dumpUDs" (vcat
+ -- [ text "bndrs" <+> ppr bndrs
+ -- , text "uds" <+> ppr uds
+ -- , text "free_uds" <+> ppr free_uds
+ -- , text "dump-dbs" <+> ppr dump_dbs ]) $
(free_uds, dump_dbs)
where
free_uds = uds { ud_binds = free_dbs, ud_calls = free_calls }
@@ -3325,20 +3412,17 @@ callsForMe fn uds@MkUD { ud_binds = orig_dbs, ud_calls = orig_calls }
calls_for_me = case lookupDVarEnv orig_calls fn of
Nothing -> []
Just cis -> filterCalls cis orig_dbs
- -- filterCalls: drop calls that (directly or indirectly)
- -- refer to fn. See Note [Avoiding loops (DFuns)]
----------------------
filterCalls :: CallInfoSet -> FloatedDictBinds -> [CallInfo]
--- Remove dominated calls (Note [Specialising polymorphic dictionaries])
--- and loopy DFuns (Note [Avoiding loops (DFuns)])
+-- Remove
+-- (a) dominated calls: (MP3) in Note [Specialising polymorphic dictionaries]
+-- (b) loopy DFuns: Note [Avoiding loops (DFuns)]
filterCalls (CIS fn call_bag) (FDB { fdb_binds = dbs })
- | isDFunId fn -- Note [Avoiding loops (DFuns)] applies only to DFuns
- = filter ok_call de_dupd_calls
- | otherwise -- Do not apply it to non-DFuns
- = de_dupd_calls -- See Note [Avoiding loops (non-DFuns)]
+ | isDFunId fn = filter ok_call de_dupd_calls -- Deals with (b)
+ | otherwise = de_dupd_calls
where
- de_dupd_calls = remove_dups call_bag
+ de_dupd_calls = removeDupCalls call_bag -- Deals with (a)
dump_set = foldl' go (unitVarSet fn) dbs
-- This dump-set could also be computed by splitDictBinds
@@ -3352,10 +3436,10 @@ filterCalls (CIS fn call_bag) (FDB { fdb_binds = dbs })
ok_call (CI { ci_fvs = fvs }) = fvs `disjointVarSet` dump_set
-remove_dups :: Bag CallInfo -> [CallInfo]
+removeDupCalls :: Bag CallInfo -> [CallInfo]
-- Calls involving more generic instances beat more specific ones.
-- See (MP3) in Note [Specialising polymorphic dictionaries]
-remove_dups calls = foldr add [] calls
+removeDupCalls calls = foldr add [] calls
where
add :: CallInfo -> [CallInfo] -> [CallInfo]
add ci [] = [ci]
@@ -3364,12 +3448,20 @@ remove_dups calls = foldr add [] calls
| otherwise = ci2 : add ci1 cis
beats_or_same :: CallInfo -> CallInfo -> Bool
+-- (beats_or_same ci1 ci2) is True if specialising on ci1 subsumes ci2
+-- That is: ci1's types are less specialised than ci2
+-- ci1 specialises on the same dict args as ci2
beats_or_same (CI { ci_key = args1 }) (CI { ci_key = args2 })
= go args1 args2
where
- go [] _ = True
+ go [] [] = True
go (arg1:args1) (arg2:args2) = go_arg arg1 arg2 && go args1 args2
- go (_:_) [] = False
+
+ -- If one or the other runs dry, the other must still have a SpecDict
+ -- because of the (CI-KEY) invariant. So neither subsumes the other;
+ -- one is more specialised (faster code) but the other is more generally
+ -- applicable.
+ go _ _ = False
go_arg (SpecType ty1) (SpecType ty2) = isJust (tcMatchTy ty1 ty2)
go_arg UnspecType UnspecType = True
=====================================
compiler/GHC/Core/Rules.hs
=====================================
@@ -9,7 +9,7 @@
-- The 'CoreRule' datatype itself is declared elsewhere.
module GHC.Core.Rules (
-- ** Looking up rules
- lookupRule, matchExprs,
+ lookupRule, matchExprs, ruleLhsIsMoreSpecific,
-- ** RuleBase, RuleEnv
RuleBase, RuleEnv(..), mkRuleEnv, emptyRuleEnv,
@@ -587,8 +587,8 @@ findBest :: InScopeSet -> (Id, [CoreExpr])
findBest _ _ (rule,ans) [] = (rule,ans)
findBest in_scope target (rule1,ans1) ((rule2,ans2):prs)
- | isMoreSpecific in_scope rule1 rule2 = findBest in_scope target (rule1,ans1) prs
- | isMoreSpecific in_scope rule2 rule1 = findBest in_scope target (rule2,ans2) prs
+ | ruleIsMoreSpecific in_scope rule1 rule2 = findBest in_scope target (rule1,ans1) prs
+ | ruleIsMoreSpecific in_scope rule2 rule1 = findBest in_scope target (rule2,ans2) prs
| debugIsOn = let pp_rule rule
= ifPprDebug (ppr rule)
(doubleQuotes (ftext (ruleName rule)))
@@ -603,15 +603,25 @@ findBest in_scope target (rule1,ans1) ((rule2,ans2):prs)
where
(fn,args) = target
-isMoreSpecific :: InScopeSet -> CoreRule -> CoreRule -> Bool
--- The call (rule1 `isMoreSpecific` rule2)
+ruleIsMoreSpecific :: InScopeSet -> CoreRule -> CoreRule -> Bool
+-- The call (rule1 `ruleIsMoreSpecific` rule2)
-- sees if rule2 can be instantiated to look like rule1
--- See Note [isMoreSpecific]
-isMoreSpecific _ (BuiltinRule {}) _ = False
-isMoreSpecific _ (Rule {}) (BuiltinRule {}) = True
-isMoreSpecific in_scope (Rule { ru_bndrs = bndrs1, ru_args = args1 })
- (Rule { ru_bndrs = bndrs2, ru_args = args2 })
- = isJust (matchExprs in_scope_env bndrs2 args2 args1)
+-- See Note [ruleIsMoreSpecific]
+ruleIsMoreSpecific in_scope rule1 rule2
+ = case rule1 of
+ BuiltinRule {} -> False
+ Rule { ru_bndrs = bndrs1, ru_args = args1 }
+ -> ruleLhsIsMoreSpecific in_scope bndrs1 args1 rule2
+
+ruleLhsIsMoreSpecific :: InScopeSet
+ -> [Var] -> [CoreExpr] -- LHS of a possible new rule
+ -> CoreRule -- An existing rule
+ -> Bool -- New one is more specific
+ruleLhsIsMoreSpecific in_scope bndrs1 args1 rule2
+ = case rule2 of
+ BuiltinRule {} -> True
+ Rule { ru_bndrs = bndrs2, ru_args = args2 }
+ -> isJust (matchExprs in_scope_env bndrs2 args2 args1)
where
full_in_scope = in_scope `extendInScopeSetList` bndrs1
in_scope_env = ISE full_in_scope noUnfoldingFun
@@ -620,9 +630,9 @@ isMoreSpecific in_scope (Rule { ru_bndrs = bndrs1, ru_args = args1 })
noBlackList :: Activation -> Bool
noBlackList _ = False -- Nothing is black listed
-{- Note [isMoreSpecific]
+{- Note [ruleIsMoreSpecific]
~~~~~~~~~~~~~~~~~~~~~~~~
-The call (rule1 `isMoreSpecific` rule2)
+The call (rule1 `ruleIsMoreSpecific` rule2)
sees if rule2 can be instantiated to look like rule1.
Wrinkle:
@@ -825,7 +835,7 @@ bound on the LHS:
The rule looks like
forall (a::*) (d::Eq Char) (x :: Foo a Char).
- f (Foo a Char) d x = True
+ f @(Foo a Char) d x = True
Matching the rule won't bind 'a', and legitimately so. We fudge by
pretending that 'a' is bound to (Any :: *).
=====================================
compiler/GHC/Core/Unify.hs
=====================================
@@ -331,35 +331,57 @@ Wrinkles
`DontBindMe`, the unifier must return `SurelyApart`, not `MaybeApart`. See
`go_fam` in `uVarOrFam`
-(ATF6) You might think that when /matching/ the um_fam_env will always be empty,
- because type-class-instance and type-family-instance heads can't include type
- families. E.g. instance C (F a) where ... -- Illegal
-
- But you'd be wrong: when "improving" type family constraint we may have a
- type family on the LHS of a match. Consider
+(ATF6) When /matching/ can we ever have a type-family application on the LHS, in
+ the template? You might think not, because type-class-instance and
+ type-family-instance heads can't include type families. E.g.
+ instance C (F a) where ... -- Illegal
+
+ But you'd be wrong: even when matching, we can see type families in the LHS template:
+ * In `checkValidClass`, in `check_dm` we check that the default method has the
+ right type, using matching, both ways. And that type may have type-family
+ applications in it. Example in test CoOpt_Singletons.
+
+ * In the specialiser: see the call to `tcMatchTy` in
+ `GHC.Core.Opt.Specialise.beats_or_same`
+
+ * With -fpolymorphic-specialsation, we might get a specialiation rule like
+ RULE forall a (d :: Eq (Maybe (F a))) .
+ f @(Maybe (F a)) d = ...
+ See #25965.
+
+ * A user-written RULE could conceivably have a type-family application
+ in the template. It might not be a good rule, but I don't think we currently
+ check for this.
+
+ In all these cases we are only interested in finding a substitution /for
+ type variables/ that makes the match work. So we simply want to recurse into
+ the arguments of the type family. E.g.
+ Template: forall a. Maybe (F a)
+ Target: Mabybe (F Int)
+ We want to succeed with substitution [a :-> Int]. See (ATF9).
+
+ Conclusion: where we enter via `tcMatchTy`, `tcMatchTys`, `tc_match_tys`,
+ etc, we always end up in `tc_match_tys_x`. There we invoke the unifier
+ but we do not distinguish between `SurelyApart` and `MaybeApart`. So in
+ these cases we can set `um_bind_fam_fun` to `neverBindFam`.
+
+(ATF7) There is one other, very special case of matching where we /do/ want to
+ bind type families in `um_fam_env`, namely in GHC.Tc.Solver.Equality, the call
+ to `tcUnifyTyForInjectivity False` in `improve_injective_wanted_top`.
+ Consider
+ of a match. Consider
type family G6 a = r | r -> a
type instance G6 [a] = [G a]
type instance G6 Bool = Int
- and the Wanted constraint [W] G6 alpha ~ [Int]. We /match/ each type instance
- RHS against [Int]! So we try
- [G a] ~ [Int]
+ and suppose we haev a Wanted constraint
+ [W] G6 alpha ~ [Int]
+. According to Section 5.2 of "Injective type families for Haskell", we /match/
+ the RHS each type instance [Int]. So we try
+ Template: [G a] Target: [Int]
and we want to succeed with MaybeApart, so that we can generate the improvement
- constraint [W] alpha ~ [beta] where beta is fresh.
- See Section 5.2 of "Injective type families for Haskell".
-
- A second place that we match with type-fams on the LHS is in `checkValidClass`.
- In `check_dm` we check that the default method has the right type, using matching,
- both ways. And that type may have type-family applications in it. Example in
- test CoOpt_Singletons.
-
-(ATF7) You might think that (ATF6) is a very special case, and in /other/ uses of
- matching, where we enter via `tc_match_tys_x` we will never see a type-family
- in the template. But actually we do see that case in the specialiser: see
- the call to `tcMatchTy` in `GHC.Core.Opt.Specialise.beats_or_same`
-
- Also: a user-written RULE could conceivably have a type-family application
- in the template. It might not be a good rule, but I don't think we currently
- check for this.
+ constraint
+ [W] alpha ~ [beta]
+ where beta is fresh. We do this by binding [G a :-> Int]
(ATF8) The treatment of type families is governed by
um_bind_fam_fun :: BindFamFun
@@ -399,6 +421,8 @@ Wrinkles
Key point: when decomposing (F tys1 ~ F tys2), we should /also/ extend the
type-family substitution.
+ (ATF11-1) All this cleverness only matters when unifying, not when matching
+
(ATF12) There is a horrid exception for the injectivity check. See (UR1) in
in Note [Specification of unification].
@@ -595,7 +619,7 @@ tc_match_tys_x :: HasDebugCallStack
-> [Type]
-> Maybe Subst
tc_match_tys_x bind_tv match_kis (Subst in_scope id_env tv_env cv_env) tys1 tys2
- = case tc_unify_tys alwaysBindFam -- (ATF7) in Note [Apartness and type families]
+ = case tc_unify_tys neverBindFam -- (ATF7) in Note [Apartness and type families]
bind_tv
False -- Matching, not unifying
False -- Not an injectivity check
@@ -1857,6 +1881,7 @@ uVarOrFam env ty1 ty2 kco
= go_fam_fam tc1 tys1 tys2 kco
-- Now check if we can bind the (F tys) to the RHS
+ -- This can happen even when matching: see (ATF7)
| BindMe <- um_bind_fam_fun env tc1 tys1 rhs
= -- ToDo: do we need an occurs check here?
do { extendFamEnv tc1 tys1 rhs
@@ -1881,11 +1906,6 @@ uVarOrFam env ty1 ty2 kco
-- go_fam_fam: LHS and RHS are both saturated type-family applications,
-- for the same type-family F
go_fam_fam tc tys1 tys2 kco
- | tcEqTyConAppArgs tys1 tys2
- -- Detect (F tys ~ F tys); otherwise we'd build an infinite substitution
- = return ()
-
- | otherwise
-- Decompose (F tys1 ~ F tys2): (ATF9)
-- Use injectivity information of F: (ATF10)
-- But first bind the type-fam if poss: (ATF11)
@@ -1902,13 +1922,19 @@ uVarOrFam env ty1 ty2 kco
(inj_tys1, noninj_tys1) = partitionByList inj tys1
(inj_tys2, noninj_tys2) = partitionByList inj tys2
- bind_fam_if_poss | BindMe <- um_bind_fam_fun env tc tys1 rhs1
- = extendFamEnv tc tys1 rhs1
- | um_unif env
- , BindMe <- um_bind_fam_fun env tc tys2 rhs2
- = extendFamEnv tc tys2 rhs2
- | otherwise
- = return ()
+ bind_fam_if_poss
+ | not (um_unif env) -- Not when matching (ATF11-1)
+ = return ()
+ | tcEqTyConAppArgs tys1 tys2 -- Detect (F tys ~ F tys);
+ = return () -- otherwise we'd build an infinite substitution
+ | BindMe <- um_bind_fam_fun env tc tys1 rhs1
+ = extendFamEnv tc tys1 rhs1
+ | um_unif env
+ , BindMe <- um_bind_fam_fun env tc tys2 rhs2
+ = extendFamEnv tc tys2 rhs2
+ | otherwise
+ = return ()
+
rhs1 = mkTyConApp tc tys2 `mkCastTy` mkSymCo kco
rhs2 = mkTyConApp tc tys1 `mkCastTy` kco
@@ -1993,7 +2019,7 @@ data UMState = UMState
-- in um_foralls; i.e. variables bound by foralls inside the types being unified
-- When /matching/ um_fam_env is usually empty; but not quite always.
- -- See (ATF6) and (ATF7) of Note [Apartness and type families]
+ -- See (ATF7) of Note [Apartness and type families]
newtype UM a
= UM' { unUM :: UMState -> UnifyResultM (UMState, a) }
=====================================
compiler/GHC/Tc/Solver/Equality.hs
=====================================
@@ -3017,6 +3017,7 @@ improve_wanted_top_fun_eqs fam_tc lhs_tys rhs_ty
improve_injective_wanted_top :: FamInstEnvs -> [Bool] -> TyCon -> [TcType] -> Xi -> TcS [TypeEqn]
-- Interact with top-level instance declarations
+-- See Section 5.2 in the Injective Type Families paper
improve_injective_wanted_top fam_envs inj_args fam_tc lhs_tys rhs_ty
= concatMapM do_one branches
where
@@ -3035,6 +3036,7 @@ improve_injective_wanted_top fam_envs inj_args fam_tc lhs_tys rhs_ty
do_one branch@(CoAxBranch { cab_tvs = branch_tvs, cab_lhs = branch_lhs_tys, cab_rhs = branch_rhs })
| let in_scope1 = in_scope `extendInScopeSetList` branch_tvs
, Just subst <- tcUnifyTyForInjectivity False in_scope1 branch_rhs rhs_ty
+ -- False: matching, not unifying
= do { let inSubst tv = tv `elemVarEnv` getTvSubstEnv subst
unsubstTvs = filterOut inSubst branch_tvs
-- The order of unsubstTvs is important; it must be
=====================================
compiler/GHC/Types/Basic.hs
=====================================
@@ -85,7 +85,7 @@ module GHC.Types.Basic (
CompilerPhase(..), PhaseNum, beginPhase, nextPhase, laterPhase,
Activation(..), isActive, competesWith,
- isNeverActive, isAlwaysActive, activeInFinalPhase,
+ isNeverActive, isAlwaysActive, activeInFinalPhase, activeInInitialPhase,
activateAfterInitial, activateDuringFinal, activeAfter,
RuleMatchInfo(..), isConLike, isFunLike,
=====================================
testsuite/tests/simplCore/should_compile/T25703.hs
=====================================
@@ -0,0 +1,7 @@
+module T25703 where
+
+f :: (Eq a, Show b) => a -> b -> Int
+f x y = f x y
+
+goo :: forall x. (Eq x) => x -> Int
+goo arg = f arg (3::Int)
=====================================
testsuite/tests/simplCore/should_compile/T25703.stderr
=====================================
@@ -0,0 +1,2 @@
+Rule fired: SPEC f @_ @Int (T25703)
+Rule fired: SPEC f @_ @Int (T25703)
=====================================
testsuite/tests/simplCore/should_compile/T25703a.hs
=====================================
@@ -0,0 +1,69 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE GADTs #-}
+
+{-# OPTIONS_GHC -O2 -fspecialise-aggressively #-}
+
+-- This pragma is just here to pretend that the function body of 'foo' is huge
+-- and should never be inlined.
+{-# OPTIONS_GHC -funfolding-use-threshold=-200 #-}
+
+module T25703a where
+
+import Data.Kind
+import Data.Type.Equality
+import Data.Proxy
+import GHC.TypeNats
+
+-- Pretend this is some big dictionary that absolutely must get
+-- specialised away for performance reasons.
+type C :: Nat -> Constraint
+class C i where
+ meth :: Proxy i -> Double
+instance C 0 where
+ meth _ = 0.1
+instance C 1 where
+ meth _ = 1.1
+instance C 2 where
+ meth _ = 2.1
+
+{-# INLINEABLE foo #-}
+foo :: forall a (n :: Nat) (m :: Nat)
+ . ( Eq a, C n, C m )
+ => a -> ( Proxy n, Proxy m ) -> Int -> Double
+-- Pretend this is a big complicated function, too big to inline,
+-- for which we absolutely must specialise away the 'C n', 'C m'
+-- dictionaries for performance reasons.
+foo a b c
+ = if a == a
+ then meth @n Proxy + fromIntegral c
+ else 2 * meth @m Proxy
+
+-- Runtime dispatch to a specialisation of 'foo'
+foo_spec :: forall a (n :: Nat) (m :: Nat)
+ . ( Eq a, KnownNat n, KnownNat m )
+ => a -> ( Proxy n, Proxy m ) -> Int -> Double
+foo_spec a b c
+ | Just Refl <- sameNat @n @0 Proxy Proxy
+ , Just Refl <- sameNat @m @0 Proxy Proxy
+ = foo @a @0 @0 a b c
+ | Just Refl <- sameNat @n @0 Proxy Proxy
+ , Just Refl <- sameNat @m @1 Proxy Proxy
+ = foo @a @0 @1 a b c
+ | Just Refl <- sameNat @n @1 Proxy Proxy
+ , Just Refl <- sameNat @m @1 Proxy Proxy
+ = foo @a @1 @1 a b c
+ | Just Refl <- sameNat @n @0 Proxy Proxy
+ , Just Refl <- sameNat @m @2 Proxy Proxy
+ = foo @a @0 @2 a b c
+ | Just Refl <- sameNat @n @1 Proxy Proxy
+ , Just Refl <- sameNat @m @2 Proxy Proxy
+ = foo @a @1 @2 a b c
+ | Just Refl <- sameNat @n @2 Proxy Proxy
+ , Just Refl <- sameNat @m @2 Proxy Proxy
+ = foo @a @2 @2 a b c
+ | otherwise
+ = error $ unlines
+ [ "f: no specialisation"
+ , "n: " ++ show (natVal @n Proxy)
+ , "m: " ++ show (natVal @m Proxy)
+ ]
=====================================
testsuite/tests/simplCore/should_compile/T25703a.stderr
=====================================
@@ -0,0 +1,6 @@
+Rule fired: SPEC foo @_ @2 @2 (T25703a)
+Rule fired: SPEC foo @_ @1 @2 (T25703a)
+Rule fired: SPEC foo @_ @0 @2 (T25703a)
+Rule fired: SPEC foo @_ @1 @1 (T25703a)
+Rule fired: SPEC foo @_ @0 @1 (T25703a)
+Rule fired: SPEC foo @_ @0 @0 (T25703a)
=====================================
testsuite/tests/simplCore/should_compile/T25965.hs
=====================================
@@ -0,0 +1,18 @@
+{-# LANGUAGE TypeFamilies #-}
+{-# OPTIONS_GHC -O -fpolymorphic-specialisation #-}
+
+module Foo where
+
+type family F a
+
+data T a = T1
+
+instance Eq (T a) where { (==) x y = False }
+
+foo :: Eq a => a -> Bool
+foo x | x==x = True
+ | otherwise = foo x
+
+bar :: forall b. b -> T (F b) -> Bool
+bar y x = foo x
+
=====================================
testsuite/tests/simplCore/should_compile/all.T
=====================================
@@ -535,3 +535,6 @@ test('T25197', [req_th, extra_files(["T25197_TH.hs"]), only_ways(['optasm'])], m
test('T25389', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dno-typeable-binds'])
test('T24359a', normal, compile, ['-O -ddump-rules'])
test('T25713', [grep_errmsg('W:::')], compile, ['-O -ddump-simpl'])
+test('T25965', normal, compile, ['-O'])
+test('T25703', [grep_errmsg(r'SPEC')], compile, ['-O -fpolymorphic-specialisation -ddump-rule-firings'])
+test('T25703a', [grep_errmsg(r'SPEC')], compile, ['-O -fpolymorphic-specialisation -ddump-rule-firings'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/3909c6899e6fb7fd95f60c73cc25f47…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/3909c6899e6fb7fd95f60c73cc25f47…
You're receiving this email because of your account on gitlab.haskell.org.
1
0

[Git][ghc/ghc][wip/T25965] Fix infelicities in the Specialiser
by Simon Peyton Jones (@simonpj) 17 Apr '25
by Simon Peyton Jones (@simonpj) 17 Apr '25
17 Apr '25
Simon Peyton Jones pushed to branch wip/T25965 at Glasgow Haskell Compiler / GHC
Commits:
241c93c0 by Simon Peyton Jones at 2025-04-17T09:03:24+01:00
Fix infelicities in the Specialiser
On the way to #23109 (unary classes) I discovered some infelicities
(or maybe tiny bugs, I forget) in the type-class specialiser.
I also tripped over #25965, an outright bug in the rule matcher
Specifically:
* Refactor: I enhanced `wantCallsFor`, whih previously always said
`True`, to discard calls of class-ops, data constructors etc. This is
a bit more efficient; and it means we don't need to worry about
filtering them out later.
* Fix: I tidied up some tricky logic that eliminated redundant
specialisations. It wasn't working correctly. See the expanded
Note [Specialisations already covered], and
(MP3) in Note [Specialising polymorphic dictionaries].
See also the new top-level `alreadyCovered`
function, which now goes via `GHC.Core.Rules.ruleLhsIsMoreSpecific`
I also added a useful Note [The (CI-KEY) invariant]
* Fix #25965: fixed a tricky bug in the `go_fam_fam` in
`GHC.Core.Unify.uVarOrFam`, which allows matching to succeed
without binding all type varibles.
I enhanced Note [Apartness and type families] some more
* #25703. This ticket "just works" with -fpolymorphic-specialisation;
but I was surprised that it worked! In this MR I added documentation
to Note [Interesting dictionary arguments] to explain; and tests to
ensure it stays fixed.
- - - - -
11 changed files:
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/Rules.hs
- compiler/GHC/Core/Unify.hs
- compiler/GHC/Tc/Solver/Equality.hs
- compiler/GHC/Types/Basic.hs
- + testsuite/tests/simplCore/should_compile/T25703.hs
- + testsuite/tests/simplCore/should_compile/T25703.stderr
- + testsuite/tests/simplCore/should_compile/T25703a.hs
- + testsuite/tests/simplCore/should_compile/T25703a.stderr
- + testsuite/tests/simplCore/should_compile/T25965.hs
- testsuite/tests/simplCore/should_compile/all.T
Changes:
=====================================
compiler/GHC/Core/Opt/Specialise.hs
=====================================
@@ -1243,14 +1243,15 @@ specExpr env (Let bind body)
-- Note [Fire rules in the specialiser]
fireRewriteRules :: SpecEnv -> InExpr -> [OutExpr] -> (InExpr, [OutExpr])
fireRewriteRules env (Var f) args
- | Just (rule, expr) <- specLookupRule env f args InitialPhase (getRules (se_rules env) f)
+ | let rules = getRules (se_rules env) f
+ , Just (rule, expr) <- specLookupRule env f args activeInInitialPhase rules
, let rest_args = drop (ruleArity rule) args -- See Note [Extra args in the target]
zapped_subst = Core.zapSubst (se_subst env)
expr' = simpleOptExprWith defaultSimpleOpts zapped_subst expr
-- simplOptExpr needed because lookupRule returns
-- (\x y. rhs) arg1 arg2
- , (fun, args) <- collectArgs expr'
- = fireRewriteRules env fun (args++rest_args)
+ , (fun', args') <- collectArgs expr'
+ = fireRewriteRules env fun' (args'++rest_args)
fireRewriteRules _ fun args = (fun, args)
--------------
@@ -1620,7 +1621,7 @@ specCalls :: Bool -- True => specialising imported fn
-- This function checks existing rules, and does not create
-- duplicate ones. So the caller does not need to do this filtering.
--- See 'already_covered'
+-- See `alreadyCovered`
type SpecInfo = ( [CoreRule] -- Specialisation rules
, [(Id,CoreExpr)] -- Specialised definition
@@ -1644,15 +1645,13 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
= -- pprTrace "specCalls: some" (vcat
-- [ text "function" <+> ppr fn
- -- , text "calls:" <+> ppr calls_for_me
- -- , text "subst" <+> ppr (se_subst env) ]) $
+ -- , text "calls:" <+> ppr calls_for_me
+ -- , text "subst" <+> ppr (se_subst env) ]) $
foldlM spec_call ([], [], emptyUDs) calls_for_me
| otherwise -- No calls or RHS doesn't fit our preconceptions
- = warnPprTrace (not (exprIsTrivial rhs) && notNull calls_for_me && not (isClassOpId fn))
+ = warnPprTrace (not (exprIsTrivial rhs) && notNull calls_for_me)
"Missed specialisation opportunity for" (ppr fn $$ trace_doc) $
- -- isClassOpId: class-op Ids never inline; we specialise them
- -- through fireRewriteRules. So don't complain about missed opportunities
-- Note [Specialisation shape]
-- pprTrace "specCalls: none" (ppr fn <+> ppr calls_for_me) $
return ([], [], emptyUDs)
@@ -1664,6 +1663,10 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
fn_unf = realIdUnfolding fn -- Ignore loop-breaker-ness here
inl_prag = idInlinePragma fn
inl_act = inlinePragmaActivation inl_prag
+ is_active = isActive (beginPhase inl_act) :: Activation -> Bool
+ -- is_active: inl_act is the activation we are going to put in the new
+ -- SPEC rule; so we want to see if it is covered by another rule with
+ -- that same activation.
is_local = isLocalId fn
is_dfun = isDFunId fn
dflags = se_dflags env
@@ -1674,16 +1677,6 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
(rhs_bndrs, rhs_body) = collectBindersPushingCo rhs
-- See Note [Account for casts in binding]
- already_covered :: SpecEnv -> [CoreRule] -> [CoreExpr] -> Bool
- already_covered env new_rules args -- Note [Specialisations already covered]
- = isJust (specLookupRule env fn args (beginPhase inl_act)
- (new_rules ++ existing_rules))
- -- Rules: we look both in the new_rules (generated by this invocation
- -- of specCalls), and in existing_rules (passed in to specCalls)
- -- inl_act: is the activation we are going to put in the new SPEC
- -- rule; so we want to see if it is covered by another rule with
- -- that same activation.
-
----------------------------------------------------------
-- Specialise to one particular call pattern
spec_call :: SpecInfo -- Accumulating parameter
@@ -1717,8 +1710,12 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
-- , ppr dx_binds ]) $
-- return ()
+ ; let all_rules = rules_acc ++ existing_rules
+ -- all_rules: we look both in the rules_acc (generated by this invocation
+ -- of specCalls), and in existing_rules (passed in to specCalls)
; if not useful -- No useful specialisation
- || already_covered rhs_env2 rules_acc rule_lhs_args
+ || alreadyCovered rhs_env2 rule_bndrs fn rule_lhs_args is_active all_rules
+ -- See (SC1) in Note [Specialisations already covered]
then return spec_acc
else
do { -- Run the specialiser on the specialised RHS
@@ -1780,7 +1777,7 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
spec_fn_details
= case idDetails fn of
JoinId join_arity _ -> JoinId (join_arity - join_arity_decr) Nothing
- DFunId is_nt -> DFunId is_nt
+ DFunId unary -> DFunId unary
_ -> VanillaId
; spec_fn <- newSpecIdSM (idName fn) spec_fn_ty spec_fn_details spec_fn_info
@@ -1804,6 +1801,8 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
, ppr spec_fn <+> dcolon <+> ppr spec_fn_ty
, ppr rhs_bndrs, ppr call_args
, ppr spec_rule
+ , text "acc" <+> ppr rules_acc
+ , text "existing" <+> ppr existing_rules
]
; -- pprTrace "spec_call: rule" _rule_trace_doc
@@ -1812,19 +1811,35 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
, spec_uds `thenUDs` uds_acc
) } }
+alreadyCovered :: SpecEnv
+ -> [Var] -> Id -> [CoreExpr] -- LHS of possible new rule
+ -> (Activation -> Bool) -- Which rules are active
+ -> [CoreRule] -> Bool
+-- Note [Specialisations already covered] esp (SC2)
+alreadyCovered env bndrs fn args is_active rules
+ = case specLookupRule env fn args is_active rules of
+ Nothing -> False
+ Just (rule, _)
+ | isAutoRule rule -> -- Discard identical rules
+ -- We know that (fn args) is an instance of RULE
+ -- Check if RULE is an instance of (fn args)
+ ruleLhsIsMoreSpecific in_scope bndrs args rule
+ | otherwise -> True -- User rules dominate
+ where
+ in_scope = substInScopeSet (se_subst env)
+
-- Convenience function for invoking lookupRule from Specialise
-- The SpecEnv's InScopeSet should include all the Vars in the [CoreExpr]
specLookupRule :: SpecEnv -> Id -> [CoreExpr]
- -> CompilerPhase -- Look up rules as if we were in this phase
+ -> (Activation -> Bool) -- Which rules are active
-> [CoreRule] -> Maybe (CoreRule, CoreExpr)
-specLookupRule env fn args phase rules
+specLookupRule env fn args is_active rules
= lookupRule ropts in_scope_env is_active fn args rules
where
dflags = se_dflags env
in_scope = substInScopeSet (se_subst env)
in_scope_env = ISE in_scope (whenActiveUnfoldingFun is_active)
ropts = initRuleOpts dflags
- is_active = isActive phase
{- Note [Specialising DFuns]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2323,21 +2338,24 @@ This plan is implemented in the Rec case of specBindItself.
Note [Specialisations already covered]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We obviously don't want to generate two specialisations for the same
-argument pattern. There are two wrinkles
-
-1. We do the already-covered test in specDefn, not when we generate
-the CallInfo in mkCallUDs. We used to test in the latter place, but
-we now iterate the specialiser somewhat, and the Id at the call site
-might therefore not have all the RULES that we can see in specDefn
-
-2. What about two specialisations where the second is an *instance*
-of the first? If the more specific one shows up first, we'll generate
-specialisations for both. If the *less* specific one shows up first,
-we *don't* currently generate a specialisation for the more specific
-one. (See the call to lookupRule in already_covered.) Reasons:
- (a) lookupRule doesn't say which matches are exact (bad reason)
- (b) if the earlier specialisation is user-provided, it's
- far from clear that we should auto-specialise further
+argument pattern. Wrinkles
+
+(SC1) We do the already-covered test in specDefn, not when we generate
+ the CallInfo in mkCallUDs. We used to test in the latter place, but
+ we now iterate the specialiser somewhat, and the Id at the call site
+ might therefore not have all the RULES that we can see in specDefn
+
+(SC2) What about two specialisations where the second is an *instance*
+ of the first? It's a bit arbitrary, but here's what we do:
+ * If the existing one is user-specified, via a SPECIALISE pragma, we
+ suppress the further specialisation.
+ * If the existing one is auto-generated, we generate a second RULE
+ for the more specialised version.
+ The latter is important because we don't want the accidental order
+ of calls to determine what specialisations we generate.
+
+(SC3) Annoyingly, we /also/ eliminate duplicates in `filterCalls`.
+ See (MP3) in Note [Specialising polymorphic dictionaries]
Note [Auto-specialisation and RULES]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2800,12 +2818,10 @@ non-dictionary bindings too.
Note [Specialising polymorphic dictionaries]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Note June 2023: This has proved to be quite a tricky optimisation to get right
see (#23469, #23109, #21229, #23445) so it is now guarded by a flag
`-fpolymorphic-specialisation`.
-
Consider
class M a where { foo :: a -> Int }
@@ -2845,11 +2861,26 @@ Here are the moving parts:
function.
(MP3) If we have f :: forall m. Monoid m => blah, and two calls
- (f @(Endo b) (d :: Monoid (Endo b))
- (f @(Endo (c->c)) (d :: Monoid (Endo (c->c)))
+ (f @(Endo b) (d1 :: Monoid (Endo b))
+ (f @(Endo (c->c)) (d2 :: Monoid (Endo (c->c)))
we want to generate a specialisation only for the first. The second
is just a substitution instance of the first, with no greater specialisation.
- Hence the call to `remove_dups` in `filterCalls`.
+ Hence the use of `removeDupCalls` in `filterCalls`.
+
+ You might wonder if `d2` might be more specialised than `d1`; but no.
+ This `removeDupCalls` thing is at the definition site of `f`, and both `d1`
+ and `d2` are in scope. So `d1` is simply more polymorphic than `d2`, but
+ is just as specialised.
+
+ This distinction is sadly lost once we build a RULE, so `alreadyCovered`
+ can't be so clever. E.g if we have an existing RULE
+ forall @a (d1:Ord Int) (d2: Eq a). f @a @Int d1 d2 = ...
+ and a putative new rule
+ forall (d1:Ord Int) (d2: Eq Int). f @Int @Int d1 d2 = ...
+ we /don't/ want the existing rule to subsume the new one.
+
+ So we sadly put up with having two rather different places where we
+ eliminate duplicates: `alreadyCovered` and `removeDupCalls`.
All this arose in #13873, in the unexpected form that a SPECIALISE
pragma made the program slower! The reason was that the specialised
@@ -2947,16 +2978,29 @@ data CallInfoSet = CIS Id (Bag CallInfo)
-- The list of types and dictionaries is guaranteed to
-- match the type of f
-- The Bag may contain duplicate calls (i.e. f @T and another f @T)
- -- These dups are eliminated by already_covered in specCalls
+ -- These dups are eliminated by alreadyCovered in specCalls
data CallInfo
- = CI { ci_key :: [SpecArg] -- All arguments
+ = CI { ci_key :: [SpecArg] -- Arguments of the call
+ -- See Note [The (CI-KEY) invariant]
+
, ci_fvs :: IdSet -- Free Ids of the ci_key call
-- /not/ including the main id itself, of course
-- NB: excluding tyvars:
-- See Note [Specialising polymorphic dictionaries]
}
+{- Note [The (CI-KEY) invariant]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Invariant (CI-KEY):
+ In the `ci_key :: [SpecArg]` field of `CallInfo`,
+ * The list is non-empty
+ * The least element is always a `SpecDict`
+
+In this way the RULE has as few args as possible, which broadens its
+applicability, since rules only fire when saturated.
+-}
+
type DictExpr = CoreExpr
ciSetFilter :: (CallInfo -> Bool) -> CallInfoSet -> CallInfoSet
@@ -3045,10 +3089,7 @@ mkCallUDs' env f args
ci_key :: [SpecArg]
ci_key = dropWhileEndLE (not . isSpecDict) $
zipWith mk_spec_arg args pis
- -- Drop trailing args until we get to a SpecDict
- -- In this way the RULE has as few args as possible,
- -- which broadens its applicability, since rules only
- -- fire when saturated
+ -- Establish (CI-KEY): drop trailing args until we get to a SpecDict
mk_spec_arg :: OutExpr -> PiTyBinder -> SpecArg
mk_spec_arg arg (Named bndr)
@@ -3086,34 +3127,75 @@ site, so we only look through ticks that RULE matching looks through
-}
wantCallsFor :: SpecEnv -> Id -> Bool
-wantCallsFor _env _f = True
- -- We could reduce the size of the UsageDetails by being less eager
- -- about collecting calls for LocalIds: there is no point for
- -- ones that are lambda-bound. We can't decide this by looking at
- -- the (absence of an) unfolding, because unfoldings for local
- -- functions are discarded by cloneBindSM, so no local binder will
- -- have an unfolding at this stage. We'd have to keep a candidate
- -- set of let-binders.
- --
- -- Not many lambda-bound variables have dictionary arguments, so
- -- this would make little difference anyway.
- --
- -- For imported Ids we could check for an unfolding, but we have to
- -- do so anyway in canSpecImport, and it seems better to have it
- -- all in one place. So we simply collect usage info for imported
- -- overloaded functions.
-
-{- Note [Interesting dictionary arguments]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Consider this
- \a.\d:Eq a. let f = ... in ...(f d)...
-There really is not much point in specialising f wrt the dictionary d,
-because the code for the specialised f is not improved at all, because
-d is lambda-bound. We simply get junk specialisations.
-
-What is "interesting"? Just that it has *some* structure. But what about
-variables? We look in the variable's /unfolding/. And that means
-that we must be careful to ensure that dictionaries have unfoldings,
+-- See Note [wantCallsFor]
+wantCallsFor _env f
+ = case idDetails f of
+ RecSelId {} -> False
+ DataConWorkId {} -> False
+ DataConWrapId {} -> False
+ ClassOpId {} -> False
+ PrimOpId {} -> False
+ FCallId {} -> False
+ TickBoxOpId {} -> False
+ CoVarId {} -> False
+
+ DFunId {} -> True
+ VanillaId {} -> True
+ JoinId {} -> True
+ WorkerLikeId {} -> True
+ RepPolyId {} -> True
+
+{- Note [wantCallsFor]
+~~~~~~~~~~~~~~~~~~~~~~
+`wantCallsFor env f` says whether the Specialiser should collect calls for
+function `f`; other thing being equal, the fewer calls we collect the better. It
+is False for things we can't specialise:
+
+* ClassOpId: never inline and we don't have a defn to specialise; we specialise
+ them through fireRewriteRules.
+* PrimOpId: are never overloaded
+* Data constructors: we never specialise them
+
+We could reduce the size of the UsageDetails by being less eager about
+collecting calls for some LocalIds: there is no point for ones that are
+lambda-bound. We can't decide this by looking at the (absence of an) unfolding,
+because unfoldings for local functions are discarded by cloneBindSM, so no local
+binder will have an unfolding at this stage. We'd have to keep a candidate set
+of let-binders.
+
+Not many lambda-bound variables have dictionary arguments, so this would make
+little difference anyway.
+
+For imported Ids we could check for an unfolding, but we have to do so anyway in
+canSpecImport, and it seems better to have it all in one place. So we simply
+collect usage info for imported overloaded functions.
+
+Note [Interesting dictionary arguments]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+In `mkCallUDs` we only use `SpecDict` for dictionaries of which
+`interestingDict` holds. Otherwise we use `UnspecArg`. Two reasons:
+
+* Consider this
+ \a.\d:Eq a. let f = ... in ...(f d)...
+ There really is not much point in specialising f wrt the dictionary d,
+ because the code for the specialised f is not improved at all, because
+ d is lambda-bound. We simply get junk specialisations.
+
+* Consider this (#25703):
+ f :: (Eq a, Show b) => a -> b -> INt
+ goo :: forall x. (Eq x) => x -> blah
+ goo @x (d:Eq x) (arg:x) = ...(f @x @Int d $fShowInt)...
+ If we built a `ci_key` with a (SpecDict d) for `d`, we would end up
+ discarding the call at the `\d`. But if we use `UnspecArg` for that
+ uninteresting `d`, we'll get a `ci_key` of
+ f @x @Int UnspecArg (SpecDict $fShowInt)
+ and /that/ can float out to f's definition and specialise nicely.
+ Hooray.
+
+What is "interesting"? (See `interestingDict`.) Just that it has *some*
+structure. But what about variables? We look in the variable's /unfolding/.
+And that means that we must be careful to ensure that dictionaries /have/
+unfoldings,
* cloneBndrSM discards non-Stable unfoldings
* specBind updates the unfolding after specialisation
@@ -3159,7 +3241,7 @@ Now `f` turns into:
meth @a dc ....
When we specialise `f`, at a=Int say, that superclass selection can
-nfire (via rewiteClassOps), but that info (that 'dc' is now a
+fire (via rewiteClassOps), but that info (that 'dc' is now a
particular dictionary `C`, of type `C Int`) must be available to
the call `meth @a dc`, so that we can fire the `meth` class-op, and
thence specialise `wombat`.
@@ -3286,7 +3368,11 @@ dumpUDs :: [CoreBndr] -> UsageDetails -> (UsageDetails, OrdList DictBind)
-- Used at a lambda or case binder; just dump anything mentioning the binder
dumpUDs bndrs uds@(MkUD { ud_binds = orig_dbs, ud_calls = orig_calls })
| null bndrs = (uds, nilOL) -- Common in case alternatives
- | otherwise = -- pprTrace "dumpUDs" (ppr bndrs $$ ppr free_uds $$ ppr dump_dbs) $
+ | otherwise = -- pprTrace "dumpUDs" (vcat
+ -- [ text "bndrs" <+> ppr bndrs
+ -- , text "uds" <+> ppr uds
+ -- , text "free_uds" <+> ppr free_uds
+ -- , text "dump-dbs" <+> ppr dump_dbs ]) $
(free_uds, dump_dbs)
where
free_uds = uds { ud_binds = free_dbs, ud_calls = free_calls }
@@ -3325,20 +3411,17 @@ callsForMe fn uds@MkUD { ud_binds = orig_dbs, ud_calls = orig_calls }
calls_for_me = case lookupDVarEnv orig_calls fn of
Nothing -> []
Just cis -> filterCalls cis orig_dbs
- -- filterCalls: drop calls that (directly or indirectly)
- -- refer to fn. See Note [Avoiding loops (DFuns)]
----------------------
filterCalls :: CallInfoSet -> FloatedDictBinds -> [CallInfo]
--- Remove dominated calls (Note [Specialising polymorphic dictionaries])
--- and loopy DFuns (Note [Avoiding loops (DFuns)])
+-- Remove
+-- (a) dominated calls: (MP3) in Note [Specialising polymorphic dictionaries]
+-- (b) loopy DFuns: Note [Avoiding loops (DFuns)]
filterCalls (CIS fn call_bag) (FDB { fdb_binds = dbs })
- | isDFunId fn -- Note [Avoiding loops (DFuns)] applies only to DFuns
- = filter ok_call de_dupd_calls
- | otherwise -- Do not apply it to non-DFuns
- = de_dupd_calls -- See Note [Avoiding loops (non-DFuns)]
+ | isDFunId fn = filter ok_call de_dupd_calls -- Deals with (b)
+ | otherwise = de_dupd_calls
where
- de_dupd_calls = remove_dups call_bag
+ de_dupd_calls = removeDupCalls call_bag -- Deals with (a)
dump_set = foldl' go (unitVarSet fn) dbs
-- This dump-set could also be computed by splitDictBinds
@@ -3352,10 +3435,10 @@ filterCalls (CIS fn call_bag) (FDB { fdb_binds = dbs })
ok_call (CI { ci_fvs = fvs }) = fvs `disjointVarSet` dump_set
-remove_dups :: Bag CallInfo -> [CallInfo]
+removeDupCalls :: Bag CallInfo -> [CallInfo]
-- Calls involving more generic instances beat more specific ones.
-- See (MP3) in Note [Specialising polymorphic dictionaries]
-remove_dups calls = foldr add [] calls
+removeDupCalls calls = foldr add [] calls
where
add :: CallInfo -> [CallInfo] -> [CallInfo]
add ci [] = [ci]
@@ -3364,12 +3447,20 @@ remove_dups calls = foldr add [] calls
| otherwise = ci2 : add ci1 cis
beats_or_same :: CallInfo -> CallInfo -> Bool
+-- (beats_or_same ci1 ci2) is True if specialising on ci1 subsumes ci2
+-- That is: ci1's types are less specialised than ci2
+-- ci1 specialises on the same dict args as ci2
beats_or_same (CI { ci_key = args1 }) (CI { ci_key = args2 })
= go args1 args2
where
- go [] _ = True
+ go [] [] = True
go (arg1:args1) (arg2:args2) = go_arg arg1 arg2 && go args1 args2
- go (_:_) [] = False
+
+ -- If one or the other runs dry, the other must still have a SpecDict
+ -- because of the (CI-KEY) invariant. So neither subsumes the other;
+ -- one is more specialised (faster code) but the other is more generally
+ -- applicable.
+ go _ _ = False
go_arg (SpecType ty1) (SpecType ty2) = isJust (tcMatchTy ty1 ty2)
go_arg UnspecType UnspecType = True
=====================================
compiler/GHC/Core/Rules.hs
=====================================
@@ -9,7 +9,7 @@
-- The 'CoreRule' datatype itself is declared elsewhere.
module GHC.Core.Rules (
-- ** Looking up rules
- lookupRule, matchExprs,
+ lookupRule, matchExprs, ruleLhsIsMoreSpecific,
-- ** RuleBase, RuleEnv
RuleBase, RuleEnv(..), mkRuleEnv, emptyRuleEnv,
@@ -587,8 +587,8 @@ findBest :: InScopeSet -> (Id, [CoreExpr])
findBest _ _ (rule,ans) [] = (rule,ans)
findBest in_scope target (rule1,ans1) ((rule2,ans2):prs)
- | isMoreSpecific in_scope rule1 rule2 = findBest in_scope target (rule1,ans1) prs
- | isMoreSpecific in_scope rule2 rule1 = findBest in_scope target (rule2,ans2) prs
+ | ruleIsMoreSpecific in_scope rule1 rule2 = findBest in_scope target (rule1,ans1) prs
+ | ruleIsMoreSpecific in_scope rule2 rule1 = findBest in_scope target (rule2,ans2) prs
| debugIsOn = let pp_rule rule
= ifPprDebug (ppr rule)
(doubleQuotes (ftext (ruleName rule)))
@@ -603,15 +603,25 @@ findBest in_scope target (rule1,ans1) ((rule2,ans2):prs)
where
(fn,args) = target
-isMoreSpecific :: InScopeSet -> CoreRule -> CoreRule -> Bool
--- The call (rule1 `isMoreSpecific` rule2)
+ruleIsMoreSpecific :: InScopeSet -> CoreRule -> CoreRule -> Bool
+-- The call (rule1 `ruleIsMoreSpecific` rule2)
-- sees if rule2 can be instantiated to look like rule1
--- See Note [isMoreSpecific]
-isMoreSpecific _ (BuiltinRule {}) _ = False
-isMoreSpecific _ (Rule {}) (BuiltinRule {}) = True
-isMoreSpecific in_scope (Rule { ru_bndrs = bndrs1, ru_args = args1 })
- (Rule { ru_bndrs = bndrs2, ru_args = args2 })
- = isJust (matchExprs in_scope_env bndrs2 args2 args1)
+-- See Note [ruleIsMoreSpecific]
+ruleIsMoreSpecific in_scope rule1 rule2
+ = case rule1 of
+ BuiltinRule {} -> False
+ Rule { ru_bndrs = bndrs1, ru_args = args1 }
+ -> ruleLhsIsMoreSpecific in_scope bndrs1 args1 rule2
+
+ruleLhsIsMoreSpecific :: InScopeSet
+ -> [Var] -> [CoreExpr] -- LHS of a possible new rule
+ -> CoreRule -- An existing rule
+ -> Bool -- New one is more specific
+ruleLhsIsMoreSpecific in_scope bndrs1 args1 rule2
+ = case rule2 of
+ BuiltinRule {} -> True
+ Rule { ru_bndrs = bndrs2, ru_args = args2 }
+ -> isJust (matchExprs in_scope_env bndrs2 args2 args1)
where
full_in_scope = in_scope `extendInScopeSetList` bndrs1
in_scope_env = ISE full_in_scope noUnfoldingFun
@@ -620,9 +630,9 @@ isMoreSpecific in_scope (Rule { ru_bndrs = bndrs1, ru_args = args1 })
noBlackList :: Activation -> Bool
noBlackList _ = False -- Nothing is black listed
-{- Note [isMoreSpecific]
+{- Note [ruleIsMoreSpecific]
~~~~~~~~~~~~~~~~~~~~~~~~
-The call (rule1 `isMoreSpecific` rule2)
+The call (rule1 `ruleIsMoreSpecific` rule2)
sees if rule2 can be instantiated to look like rule1.
Wrinkle:
@@ -825,7 +835,7 @@ bound on the LHS:
The rule looks like
forall (a::*) (d::Eq Char) (x :: Foo a Char).
- f (Foo a Char) d x = True
+ f @(Foo a Char) d x = True
Matching the rule won't bind 'a', and legitimately so. We fudge by
pretending that 'a' is bound to (Any :: *).
=====================================
compiler/GHC/Core/Unify.hs
=====================================
@@ -331,35 +331,57 @@ Wrinkles
`DontBindMe`, the unifier must return `SurelyApart`, not `MaybeApart`. See
`go_fam` in `uVarOrFam`
-(ATF6) You might think that when /matching/ the um_fam_env will always be empty,
- because type-class-instance and type-family-instance heads can't include type
- families. E.g. instance C (F a) where ... -- Illegal
-
- But you'd be wrong: when "improving" type family constraint we may have a
- type family on the LHS of a match. Consider
+(ATF6) When /matching/ can we ever have a type-family application on the LHS, in
+ the template? You might think not, because type-class-instance and
+ type-family-instance heads can't include type families. E.g.
+ instance C (F a) where ... -- Illegal
+
+ But you'd be wrong: even when matching, we can see type families in the LHS template:
+ * In `checkValidClass`, in `check_dm` we check that the default method has the
+ right type, using matching, both ways. And that type may have type-family
+ applications in it. Example in test CoOpt_Singletons.
+
+ * In the specialiser: see the call to `tcMatchTy` in
+ `GHC.Core.Opt.Specialise.beats_or_same`
+
+ * With -fpolymorphic-specialsation, we might get a specialiation rule like
+ RULE forall a (d :: Eq (Maybe (F a))) .
+ f @(Maybe (F a)) d = ...
+ See #25965.
+
+ * A user-written RULE could conceivably have a type-family application
+ in the template. It might not be a good rule, but I don't think we currently
+ check for this.
+
+ In all these cases we are only interested in finding a substitution /for
+ type variables/ that makes the match work. So we simply want to recurse into
+ the arguments of the type family. E.g.
+ Template: forall a. Maybe (F a)
+ Target: Mabybe (F Int)
+ We want to succeed with substitution [a :-> Int]. See (ATF9).
+
+ Conclusion: where we enter via `tcMatchTy`, `tcMatchTys`, `tc_match_tys`,
+ etc, we always end up in `tc_match_tys_x`. There we invoke the unifier
+ but we do not distinguish between `SurelyApart` and `MaybeApart`. So in
+ these cases we can set `um_bind_fam_fun` to `neverBindFam`.
+
+(ATF7) There is one other, very special case of matching where we /do/ want to
+ bind type families in `um_fam_env`, namely in GHC.Tc.Solver.Equality, the call
+ to `tcUnifyTyForInjectivity False` in `improve_injective_wanted_top`.
+ Consider
+ of a match. Consider
type family G6 a = r | r -> a
type instance G6 [a] = [G a]
type instance G6 Bool = Int
- and the Wanted constraint [W] G6 alpha ~ [Int]. We /match/ each type instance
- RHS against [Int]! So we try
- [G a] ~ [Int]
+ and suppose we haev a Wanted constraint
+ [W] G6 alpha ~ [Int]
+. According to Section 5.2 of "Injective type families for Haskell", we /match/
+ the RHS each type instance [Int]. So we try
+ Template: [G a] Target: [Int]
and we want to succeed with MaybeApart, so that we can generate the improvement
- constraint [W] alpha ~ [beta] where beta is fresh.
- See Section 5.2 of "Injective type families for Haskell".
-
- A second place that we match with type-fams on the LHS is in `checkValidClass`.
- In `check_dm` we check that the default method has the right type, using matching,
- both ways. And that type may have type-family applications in it. Example in
- test CoOpt_Singletons.
-
-(ATF7) You might think that (ATF6) is a very special case, and in /other/ uses of
- matching, where we enter via `tc_match_tys_x` we will never see a type-family
- in the template. But actually we do see that case in the specialiser: see
- the call to `tcMatchTy` in `GHC.Core.Opt.Specialise.beats_or_same`
-
- Also: a user-written RULE could conceivably have a type-family application
- in the template. It might not be a good rule, but I don't think we currently
- check for this.
+ constraint
+ [W] alpha ~ [beta]
+ where beta is fresh. We do this by binding [G a :-> Int]
(ATF8) The treatment of type families is governed by
um_bind_fam_fun :: BindFamFun
@@ -399,6 +421,8 @@ Wrinkles
Key point: when decomposing (F tys1 ~ F tys2), we should /also/ extend the
type-family substitution.
+ (ATF11-1) All this cleverness only matters when unifying, not when matching
+
(ATF12) There is a horrid exception for the injectivity check. See (UR1) in
in Note [Specification of unification].
@@ -595,7 +619,7 @@ tc_match_tys_x :: HasDebugCallStack
-> [Type]
-> Maybe Subst
tc_match_tys_x bind_tv match_kis (Subst in_scope id_env tv_env cv_env) tys1 tys2
- = case tc_unify_tys alwaysBindFam -- (ATF7) in Note [Apartness and type families]
+ = case tc_unify_tys neverBindFam -- (ATF7) in Note [Apartness and type families]
bind_tv
False -- Matching, not unifying
False -- Not an injectivity check
@@ -1857,6 +1881,7 @@ uVarOrFam env ty1 ty2 kco
= go_fam_fam tc1 tys1 tys2 kco
-- Now check if we can bind the (F tys) to the RHS
+ -- This can happen even when matching: see (ATF7)
| BindMe <- um_bind_fam_fun env tc1 tys1 rhs
= -- ToDo: do we need an occurs check here?
do { extendFamEnv tc1 tys1 rhs
@@ -1881,11 +1906,6 @@ uVarOrFam env ty1 ty2 kco
-- go_fam_fam: LHS and RHS are both saturated type-family applications,
-- for the same type-family F
go_fam_fam tc tys1 tys2 kco
- | tcEqTyConAppArgs tys1 tys2
- -- Detect (F tys ~ F tys); otherwise we'd build an infinite substitution
- = return ()
-
- | otherwise
-- Decompose (F tys1 ~ F tys2): (ATF9)
-- Use injectivity information of F: (ATF10)
-- But first bind the type-fam if poss: (ATF11)
@@ -1902,13 +1922,19 @@ uVarOrFam env ty1 ty2 kco
(inj_tys1, noninj_tys1) = partitionByList inj tys1
(inj_tys2, noninj_tys2) = partitionByList inj tys2
- bind_fam_if_poss | BindMe <- um_bind_fam_fun env tc tys1 rhs1
- = extendFamEnv tc tys1 rhs1
- | um_unif env
- , BindMe <- um_bind_fam_fun env tc tys2 rhs2
- = extendFamEnv tc tys2 rhs2
- | otherwise
- = return ()
+ bind_fam_if_poss
+ | not (um_unif env) -- Not when matching (ATF11-1)
+ = return ()
+ | tcEqTyConAppArgs tys1 tys2 -- Detect (F tys ~ F tys);
+ = return () -- otherwise we'd build an infinite substitution
+ | BindMe <- um_bind_fam_fun env tc tys1 rhs1
+ = extendFamEnv tc tys1 rhs1
+ | um_unif env
+ , BindMe <- um_bind_fam_fun env tc tys2 rhs2
+ = extendFamEnv tc tys2 rhs2
+ | otherwise
+ = return ()
+
rhs1 = mkTyConApp tc tys2 `mkCastTy` mkSymCo kco
rhs2 = mkTyConApp tc tys1 `mkCastTy` kco
@@ -1993,7 +2019,7 @@ data UMState = UMState
-- in um_foralls; i.e. variables bound by foralls inside the types being unified
-- When /matching/ um_fam_env is usually empty; but not quite always.
- -- See (ATF6) and (ATF7) of Note [Apartness and type families]
+ -- See (ATF7) of Note [Apartness and type families]
newtype UM a
= UM' { unUM :: UMState -> UnifyResultM (UMState, a) }
=====================================
compiler/GHC/Tc/Solver/Equality.hs
=====================================
@@ -3017,6 +3017,7 @@ improve_wanted_top_fun_eqs fam_tc lhs_tys rhs_ty
improve_injective_wanted_top :: FamInstEnvs -> [Bool] -> TyCon -> [TcType] -> Xi -> TcS [TypeEqn]
-- Interact with top-level instance declarations
+-- See Section 5.2 in the Injective Type Families paper
improve_injective_wanted_top fam_envs inj_args fam_tc lhs_tys rhs_ty
= concatMapM do_one branches
where
@@ -3035,6 +3036,7 @@ improve_injective_wanted_top fam_envs inj_args fam_tc lhs_tys rhs_ty
do_one branch@(CoAxBranch { cab_tvs = branch_tvs, cab_lhs = branch_lhs_tys, cab_rhs = branch_rhs })
| let in_scope1 = in_scope `extendInScopeSetList` branch_tvs
, Just subst <- tcUnifyTyForInjectivity False in_scope1 branch_rhs rhs_ty
+ -- False: matching, not unifying
= do { let inSubst tv = tv `elemVarEnv` getTvSubstEnv subst
unsubstTvs = filterOut inSubst branch_tvs
-- The order of unsubstTvs is important; it must be
=====================================
compiler/GHC/Types/Basic.hs
=====================================
@@ -85,7 +85,7 @@ module GHC.Types.Basic (
CompilerPhase(..), PhaseNum, beginPhase, nextPhase, laterPhase,
Activation(..), isActive, competesWith,
- isNeverActive, isAlwaysActive, activeInFinalPhase,
+ isNeverActive, isAlwaysActive, activeInFinalPhase, activeInInitialPhase,
activateAfterInitial, activateDuringFinal, activeAfter,
RuleMatchInfo(..), isConLike, isFunLike,
=====================================
testsuite/tests/simplCore/should_compile/T25703.hs
=====================================
@@ -0,0 +1,7 @@
+module T25703 where
+
+f :: (Eq a, Show b) => a -> b -> Int
+f x y = f x y
+
+goo :: forall x. (Eq x) => x -> Int
+goo arg = f arg (3::Int)
=====================================
testsuite/tests/simplCore/should_compile/T25703.stderr
=====================================
@@ -0,0 +1,2 @@
+Rule fired: SPEC f @_ @Int (T25703)
+Rule fired: SPEC f @_ @Int (T25703)
=====================================
testsuite/tests/simplCore/should_compile/T25703a.hs
=====================================
@@ -0,0 +1,69 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE GADTs #-}
+
+{-# OPTIONS_GHC -O2 -fspecialise-aggressively #-}
+
+-- This pragma is just here to pretend that the function body of 'foo' is huge
+-- and should never be inlined.
+{-# OPTIONS_GHC -funfolding-use-threshold=-200 #-}
+
+module T25703a where
+
+import Data.Kind
+import Data.Type.Equality
+import Data.Proxy
+import GHC.TypeNats
+
+-- Pretend this is some big dictionary that absolutely must get
+-- specialised away for performance reasons.
+type C :: Nat -> Constraint
+class C i where
+ meth :: Proxy i -> Double
+instance C 0 where
+ meth _ = 0.1
+instance C 1 where
+ meth _ = 1.1
+instance C 2 where
+ meth _ = 2.1
+
+{-# INLINEABLE foo #-}
+foo :: forall a (n :: Nat) (m :: Nat)
+ . ( Eq a, C n, C m )
+ => a -> ( Proxy n, Proxy m ) -> Int -> Double
+-- Pretend this is a big complicated function, too big to inline,
+-- for which we absolutely must specialise away the 'C n', 'C m'
+-- dictionaries for performance reasons.
+foo a b c
+ = if a == a
+ then meth @n Proxy + fromIntegral c
+ else 2 * meth @m Proxy
+
+-- Runtime dispatch to a specialisation of 'foo'
+foo_spec :: forall a (n :: Nat) (m :: Nat)
+ . ( Eq a, KnownNat n, KnownNat m )
+ => a -> ( Proxy n, Proxy m ) -> Int -> Double
+foo_spec a b c
+ | Just Refl <- sameNat @n @0 Proxy Proxy
+ , Just Refl <- sameNat @m @0 Proxy Proxy
+ = foo @a @0 @0 a b c
+ | Just Refl <- sameNat @n @0 Proxy Proxy
+ , Just Refl <- sameNat @m @1 Proxy Proxy
+ = foo @a @0 @1 a b c
+ | Just Refl <- sameNat @n @1 Proxy Proxy
+ , Just Refl <- sameNat @m @1 Proxy Proxy
+ = foo @a @1 @1 a b c
+ | Just Refl <- sameNat @n @0 Proxy Proxy
+ , Just Refl <- sameNat @m @2 Proxy Proxy
+ = foo @a @0 @2 a b c
+ | Just Refl <- sameNat @n @1 Proxy Proxy
+ , Just Refl <- sameNat @m @2 Proxy Proxy
+ = foo @a @1 @2 a b c
+ | Just Refl <- sameNat @n @2 Proxy Proxy
+ , Just Refl <- sameNat @m @2 Proxy Proxy
+ = foo @a @2 @2 a b c
+ | otherwise
+ = error $ unlines
+ [ "f: no specialisation"
+ , "n: " ++ show (natVal @n Proxy)
+ , "m: " ++ show (natVal @m Proxy)
+ ]
=====================================
testsuite/tests/simplCore/should_compile/T25703a.stderr
=====================================
@@ -0,0 +1,6 @@
+Rule fired: SPEC foo @_ @2 @2 (T25703a)
+Rule fired: SPEC foo @_ @1 @2 (T25703a)
+Rule fired: SPEC foo @_ @0 @2 (T25703a)
+Rule fired: SPEC foo @_ @1 @1 (T25703a)
+Rule fired: SPEC foo @_ @0 @1 (T25703a)
+Rule fired: SPEC foo @_ @0 @0 (T25703a)
=====================================
testsuite/tests/simplCore/should_compile/T25965.hs
=====================================
@@ -0,0 +1,18 @@
+{-# LANGUAGE TypeFamilies #-}
+{-# OPTIONS_GHC -O -fpolymorphic-specialisation #-}
+
+module Foo where
+
+type family F a
+
+data T a = T1
+
+instance Eq (T a) where { (==) x y = False }
+
+foo :: Eq a => a -> Bool
+foo x | x==x = True
+ | otherwise = foo x
+
+bar :: forall b. b -> T (F b) -> Bool
+bar y x = foo x
+
=====================================
testsuite/tests/simplCore/should_compile/all.T
=====================================
@@ -535,3 +535,6 @@ test('T25197', [req_th, extra_files(["T25197_TH.hs"]), only_ways(['optasm'])], m
test('T25389', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dno-typeable-binds'])
test('T24359a', normal, compile, ['-O -ddump-rules'])
test('T25713', [grep_errmsg('W:::')], compile, ['-O -ddump-simpl'])
+test('T25965', normal, compile, ['-O'])
+test('T25703', [grep_errmsg(r'SPEC')], compile, ['-O -fpolymorphic-specialisation -ddump-rule-firings'])
+test('T25703a', [grep_errmsg(r'SPEC')], compile, ['-O -fpolymorphic-specialisation -ddump-rule-firings'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/241c93c076e2ac2d2a1e2c1377c1996…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/241c93c076e2ac2d2a1e2c1377c1996…
You're receiving this email because of your account on gitlab.haskell.org.
1
0

[Git][ghc/ghc][wip/marge_bot_batch_merge_job] 2 commits: Refactor Handling of Multiple Default Declarations
by Marge Bot (@marge-bot) 16 Apr '25
by Marge Bot (@marge-bot) 16 Apr '25
16 Apr '25
Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC
Commits:
566e79de by Patrick at 2025-04-16T19:50:19-04:00
Refactor Handling of Multiple Default Declarations
Fixes: #25912, #25914, #25934
Previously, GHC discarded all loaded defaults (tcg_default) when local
defaults were encountered during typechecking. According to the
exportable-named-default proposal (sections 2.4.2 and 2.4.3), local
defaults should be merged into tcg_default, retaining any defaults
already present while overriding where necessary.
Key Changes:
* Introduce DefaultProvenance to track the origin of default declarations
(local, imported, or built-in), replacing the original cd_module
in ClassDefaults with cd_provenance :: DefaultProvenance.
* Rename tcDefaults to tcDefaultDecls, limiting its responsibility to only
converting renamed class defaults into ClassDefaults.
* Add extendDefaultEnvWithLocalDefaults to merge local defaults into the
environment, with proper duplication checks:
- Duplicate local defaults for a class trigger an error.
- Local defaults override imported or built-in defaults.
* Update and add related notes: Note [Builtin class defaults],
Note [DefaultProvenance].
* Add regression tests: T25912, T25914, T25934.
Thanks sam and simon for the help on this patch.
Co-authored-by: sheaf <sam.derbyshire(a)gmail.com>
- - - - -
5e7df774 by Teo Camarasu at 2025-04-16T19:50:20-04:00
template-haskell: Remove `addrToByteArrayName` and `addrToByteArray`
These were part of the implementation of the `Lift ByteArray` instance and were errornously exported because this module lacked an explicit export list. They have no usages on Hackage.
Resolves #24782
- - - - -
20 changed files:
- compiler/GHC/IfaceToCore.hs
- compiler/GHC/Tc/Errors/Ppr.hs
- compiler/GHC/Tc/Errors/Types.hs
- compiler/GHC/Tc/Gen/Default.hs
- compiler/GHC/Tc/Gen/Export.hs
- compiler/GHC/Tc/Module.hs
- compiler/GHC/Tc/Utils/Env.hs
- compiler/GHC/Types/DefaultEnv.hs
- libraries/template-haskell/Language/Haskell/TH/Syntax.hs
- libraries/template-haskell/changelog.md
- + testsuite/tests/default/T25912.hs
- + testsuite/tests/default/T25912.stdout
- + testsuite/tests/default/T25912_helper.hs
- + testsuite/tests/default/T25914.hs
- + testsuite/tests/default/T25934.hs
- testsuite/tests/default/all.T
- testsuite/tests/default/default-fail03.stderr
- testsuite/tests/interface-stability/template-haskell-exports.stdout
- testsuite/tests/linters/notes.stdout
- testsuite/tests/module/mod58.stderr
Changes:
=====================================
compiler/GHC/IfaceToCore.hs
=====================================
@@ -118,7 +118,7 @@ import GHC.Types.Var.Set
import GHC.Types.Name
import GHC.Types.Name.Set
import GHC.Types.Name.Env
-import GHC.Types.DefaultEnv ( ClassDefaults(..), DefaultEnv, mkDefaultEnv )
+import GHC.Types.DefaultEnv ( ClassDefaults(..), DefaultEnv, mkDefaultEnv, DefaultProvenance(..) )
import GHC.Types.Id
import GHC.Types.Id.Make
import GHC.Types.Id.Info
@@ -1333,7 +1333,7 @@ tcIfaceDefault this_mod IfaceDefault { ifDefaultCls = cls_name
; let warn = fmap fromIfaceWarningTxt iface_warn
; return ClassDefaults { cd_class = cls
, cd_types = tys'
- , cd_module = Just this_mod
+ , cd_provenance = DP_Imported this_mod
, cd_warn = warn } }
where
tyThingConClass :: TyThing -> Class
=====================================
compiler/GHC/Tc/Errors/Ppr.hs
=====================================
@@ -85,7 +85,7 @@ import GHC.Tc.Types.Rank (Rank(..))
import GHC.Tc.Types.TH
import GHC.Tc.Utils.TcType
-import GHC.Types.DefaultEnv (ClassDefaults(ClassDefaults, cd_types, cd_module))
+import GHC.Types.DefaultEnv (ClassDefaults(ClassDefaults, cd_types, cd_provenance), DefaultProvenance (..))
import GHC.Types.Error
import GHC.Types.Error.Codes
import GHC.Types.Hint
@@ -582,11 +582,19 @@ instance Diagnostic TcRnMessage where
TcRnMultipleDefaultDeclarations cls dup_things
-> mkSimpleDecorated $
hang (text "Multiple default declarations for class" <+> quotes (ppr cls))
- 2 (vcat (map pp dup_things))
+ 2 (pp dup_things)
where
- pp :: LDefaultDecl GhcRn -> SDoc
- pp (L locn DefaultDecl {})
- = text "here was another default declaration" <+> ppr (locA locn)
+ pp :: ClassDefaults -> SDoc
+ pp (ClassDefaults { cd_provenance = prov })
+ = case prov of
+ DP_Local { defaultDeclLoc = loc, defaultDeclH98 = isH98 }
+ -> let
+ what =
+ if isH98
+ then text "default declaration"
+ else text "named default declaration"
+ in text "conflicting" <+> what <+> text "at:" <+> ppr loc
+ _ -> empty -- doesn't happen, as local defaults override imported and built-in defaults
TcRnBadDefaultType ty deflt_clss
-> mkSimpleDecorated $
hang (text "The default type" <+> quotes (ppr ty) <+> text "is not an instance of")
@@ -7139,7 +7147,7 @@ pprPatersonCondFailure (PCF_TyFam tc) InTyFamEquation _lhs rhs =
--------------------------------------------------------------------------------
defaultTypesAndImport :: ClassDefaults -> SDoc
-defaultTypesAndImport ClassDefaults{cd_types, cd_module = Just cdm} =
+defaultTypesAndImport ClassDefaults{cd_types, cd_provenance = DP_Imported cdm} =
hang (parens $ pprWithCommas ppr cd_types)
2 (text "imported from" <+> ppr cdm)
defaultTypesAndImport ClassDefaults{cd_types} = parens (pprWithCommas ppr cd_types)
=====================================
compiler/GHC/Tc/Errors/Types.hs
=====================================
@@ -1504,7 +1504,7 @@ data TcRnMessage where
Text cases: module/mod58
-}
- TcRnMultipleDefaultDeclarations :: Class -> [LDefaultDecl GhcRn] -> TcRnMessage
+ TcRnMultipleDefaultDeclarations :: Class -> ClassDefaults -> TcRnMessage
{-| TcRnWarnClashingDefaultImports is a warning that occurs when a module imports
more than one default declaration for the same class, and they are not all
=====================================
compiler/GHC/Tc/Gen/Default.hs
=====================================
@@ -5,9 +5,10 @@
-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE LambdaCase #-}
-- | Typechecking @default@ declarations
-module GHC.Tc.Gen.Default ( tcDefaults ) where
+module GHC.Tc.Gen.Default ( tcDefaultDecls, extendDefaultEnvWithLocalDefaults ) where
import GHC.Prelude
import GHC.Hs
@@ -16,7 +17,7 @@ import GHC.Builtin.Names
import GHC.Core.Class
import GHC.Core.Predicate ( Pred (..), classifyPredType )
-import GHC.Data.Maybe ( firstJusts )
+import GHC.Data.Maybe ( firstJusts, maybeToList )
import GHC.Tc.Errors.Types
import GHC.Tc.Gen.HsType
@@ -30,20 +31,17 @@ import GHC.Tc.Utils.TcMType ( newWanted )
import GHC.Tc.Utils.TcType
import GHC.Types.Basic ( TypeOrKind(..) )
-import GHC.Types.DefaultEnv ( DefaultEnv, ClassDefaults (..), defaultEnv )
+import GHC.Types.DefaultEnv ( DefaultEnv, ClassDefaults (..), lookupDefaultEnv, insertDefaultEnv, DefaultProvenance (..) )
import GHC.Types.SrcLoc
-import GHC.Unit.Types (Module, ghcInternalUnit, moduleUnit)
+import GHC.Unit.Types (ghcInternalUnit, moduleUnit)
-import GHC.Utils.Misc (fstOf3, sndOf3)
import GHC.Utils.Outputable
import qualified GHC.LanguageExtensions as LangExt
-import Data.Function (on)
-import Data.List.NonEmpty ( NonEmpty (..), groupBy )
+import Data.List.NonEmpty ( NonEmpty (..) )
import qualified Data.List.NonEmpty as NE
-import Data.Maybe (fromMaybe)
import Data.Traversable ( for )
{- Note [Named default declarations]
@@ -86,7 +84,7 @@ The moving parts are as follows:
* The `DefaultEnv` of all defaults in scope in a module is kept in the `tcg_default`
field of `TcGblEnv`.
-* This field is populated by `GHC.Tc.Gen.Default.tcDefaults` which typechecks
+* This field is populated by `GHC.Tc.Gen.Default.tcDefaultDecls` which typechecks
any local or imported `default` declarations.
* Only a single default declaration can be in effect in any single module for
@@ -103,7 +101,7 @@ The moving parts are as follows:
in effect be `default Num (Integer, Double)` as specified by Haskell Language
Report.
- See Note [Default class defaults] in GHC.Tc.Utils.Env
+ See Note [Builtin class defaults] in GHC.Tc.Utils.Env
* Beside the defaults, the `ExtendedDefaultRules` and `OverloadedStrings`
extensions also affect the traditional `default` declarations that don't name
@@ -120,61 +118,54 @@ The moving parts are as follows:
tracked separately from `ImportAvails`, and returned separately from them by
`GHC.Rename.Names.rnImports`.
-* Class defaults are exported explicitly, as the example above shows. A module's
- exported defaults are tracked in `tcg_default_exports`, which are then
- transferred to `mg_defaults`, `md_defaults`, and `mi_defaults_`.
+* Class defaults are exported explicitly.
+ For example,
+ module M( ..., default C, ... )
+ exports the defaults for class C.
+
+ A module's exported defaults are computed by exports_from_avail,
+ tracked in tcg_default_exports, which are then transferred to mg_defaults,
+ md_defaults, and mi_defaults_.
+
+ Only defaults explicitly exported are actually exported.
+ (i.e. No defaults are exported in a module header like:
+ module M where ...)
+
See Note [Default exports] in GHC.Tc.Gen.Export
* Since the class defaults merely help the solver infer the correct types, they
leave no trace in Haskell Core.
-}
--- See Note [Named default declarations]
-tcDefaults :: [LDefaultDecl GhcRn]
- -> TcM DefaultEnv -- Defaulting types to heave
- -- into Tc monad for later use
- -- in Disambig.
-
-tcDefaults []
- = getDeclaredDefaultTys -- No default declaration, so get the
- -- default types from the envt;
- -- i.e. use the current ones
- -- (the caller will put them back there)
- -- It's important not to return defaultDefaultTys here (which
- -- we used to do) because in a TH program, tcDefaults [] is called
- -- repeatedly, once for each group of declarations between top-level
- -- splices. We don't want to carefully set the default types in
- -- one group, only for the next group to ignore them and install
- -- defaultDefaultTys
-
-tcDefaults decls
- = do { tcg_env <- getGblEnv
- ; let
- here = tcg_mod tcg_env
- is_internal_unit = moduleUnit here == ghcInternalUnit
- ; case (is_internal_unit, decls) of
- -- Some internal GHC modules contain @default ()@ to declare that no defaults can take place
- -- in the module.
- -- We shortcut the treatment of such a default declaration with no class nor types: we won't
- -- try to point 'cd_class' to 'Num' since it may not even exist yet.
- { (True, [L _ (DefaultDecl _ Nothing [])])
- -> return $ defaultEnv []
- -- Otherwise we take apart the declaration into the class constructor and its default types.
- ; _ ->
- do { h2010_dflt_clss <- getH2010DefaultClasses
- ; decls' <- mapMaybeM (declarationParts h2010_dflt_clss) decls
- ; let
- -- Find duplicate default declarations
- decl_tag (mb_cls, _, _) =
- case mb_cls of
- Nothing -> Nothing
- Just cls -> if cls `elem` h2010_dflt_clss
- then Nothing
- else Just cls
- decl_groups = groupBy ((==) `on` decl_tag) decls'
- ; decls_without_dups <- mapM (reportDuplicates here h2010_dflt_clss) decl_groups
- ; return $ defaultEnv (concat decls_without_dups)
- } } }
+-- | Typecheck a collection of default declarations. These can be either:
+--
+-- - Haskell 98 default declarations, of the form @default (Float, Double)@
+-- - Named default declarations, of the form @default Cls(Int, Char)@.
+-- See Note [Named default declarations]
+tcDefaultDecls :: [LDefaultDecl GhcRn] -> TcM [LocatedA ClassDefaults]
+tcDefaultDecls decls =
+ do
+ tcg_env <- getGblEnv
+ let here = tcg_mod tcg_env
+ is_internal_unit = moduleUnit here == ghcInternalUnit
+ case (is_internal_unit, decls) of
+ -- No default declarations
+ (_, []) -> return []
+ -- As per Remark [default () in ghc-internal] in Note [Builtin class defaults],
+ -- some modules in ghc-internal include an empty `default ()` declaration, in order
+ -- to disable built-in defaults. This is no longer necessary (see `GHC.Tc.Utils.Env.tcGetDefaultTys`),
+ -- but we must still make sure not to error if we fail to look up e.g. the 'Num'
+ -- typeclass when typechecking such a default declaration. To do this, we wrap
+ -- calls of 'tcLookupClass' in 'tryTc'.
+ (True, [L _ (DefaultDecl _ Nothing [])]) -> do
+ h2010_dflt_clss <- foldMapM (fmap maybeToList . fmap fst . tryTc . tcLookupClass) =<< getH2010DefaultNames
+ case NE.nonEmpty h2010_dflt_clss of
+ Nothing -> return []
+ Just h2010_dflt_clss' -> toClassDefaults h2010_dflt_clss' decls
+ -- Otherwise we take apart the declaration into the class constructor and its default types.
+ _ -> do
+ h2010_dflt_clss <- getH2010DefaultClasses
+ toClassDefaults h2010_dflt_clss decls
where
getH2010DefaultClasses :: TcM (NonEmpty Class)
-- All the classes subject to defaulting with a Haskell 2010 default
@@ -186,18 +177,18 @@ tcDefaults decls
-- No extensions: Num
-- OverloadedStrings: add IsString
-- ExtendedDefaults: add Show, Eq, Ord, Foldable, Traversable
- getH2010DefaultClasses
- = do { num_cls <- tcLookupClass numClassName
- ; ovl_str <- xoptM LangExt.OverloadedStrings
+ getH2010DefaultClasses = mapM tcLookupClass =<< getH2010DefaultNames
+ getH2010DefaultNames
+ = do { ovl_str <- xoptM LangExt.OverloadedStrings
; ext_deflt <- xoptM LangExt.ExtendedDefaultRules
- ; deflt_str <- if ovl_str
- then mapM tcLookupClass [isStringClassName]
- else return []
- ; deflt_interactive <- if ext_deflt
- then mapM tcLookupClass interactiveClassNames
- else return []
- ; let extra_clss = deflt_str ++ deflt_interactive
- ; return $ num_cls :| extra_clss
+ ; let deflt_str = if ovl_str
+ then [isStringClassName]
+ else []
+ ; let deflt_interactive = if ext_deflt
+ then interactiveClassNames
+ else []
+ ; let extra_clss_names = deflt_str ++ deflt_interactive
+ ; return $ numClassName :| extra_clss_names
}
declarationParts :: NonEmpty Class -> LDefaultDecl GhcRn -> TcM (Maybe (Maybe Class, LDefaultDecl GhcRn, [Type]))
declarationParts h2010_dflt_clss decl@(L locn (DefaultDecl _ mb_cls_name dflt_hs_tys))
@@ -220,20 +211,49 @@ tcDefaults decls
; return (Just cls, decl, tau_tys)
} }
- reportDuplicates :: Module -> NonEmpty Class -> NonEmpty (Maybe Class, LDefaultDecl GhcRn, [Type]) -> TcM [ClassDefaults]
- reportDuplicates here h2010_dflt_clss ((mb_cls, _, tys) :| [])
- = pure [ ClassDefaults{cd_class = c, cd_types = tys, cd_module = Just here, cd_warn = Nothing }
- | c <- case mb_cls of
- Nothing -> NE.toList h2010_dflt_clss
- Just cls -> [cls]
- ]
- -- Report an error on multiple default declarations for the same class in the same module.
- -- See Note [Disambiguation of multiple default declarations] in GHC.Tc.Module
- reportDuplicates _ (num_cls :| _) decls@((_, L locn _, _) :| _)
- = setSrcSpan (locA locn) (addErrTc $ dupDefaultDeclErr cls (sndOf3 <$> decls))
- >> pure []
+ toClassDefaults :: NonEmpty Class -> [LDefaultDecl GhcRn] -> TcM [LocatedA ClassDefaults]
+ toClassDefaults h2010_dflt_clss dfs = do
+ dfs <- mapMaybeM (declarationParts h2010_dflt_clss) dfs
+ return $ concatMap (go False) dfs
where
- cls = fromMaybe num_cls $ firstJusts (fmap fstOf3 decls)
+ go h98 = \case
+ (Nothing, rn_decl, tys) -> concatMap (go True) [(Just cls, rn_decl, tys) | cls <- NE.toList h2010_dflt_clss]
+ (Just cls, (L locn _), tys) -> [(L locn $ ClassDefaults cls tys (DP_Local (locA locn) h98) Nothing)]
+
+-- | Extend the default environment with the local default declarations
+-- and do the action in the extended environment.
+extendDefaultEnvWithLocalDefaults :: [LocatedA ClassDefaults] -> TcM a -> TcM a
+extendDefaultEnvWithLocalDefaults decls action = do
+ tcg_env <- getGblEnv
+ let default_env = tcg_default tcg_env
+ new_default_env <- insertDefaultDecls default_env decls
+ updGblEnv (\gbl -> gbl { tcg_default = new_default_env } ) $ action
+
+-- | Insert local default declarations into the default environment.
+--
+-- See 'insertDefaultDecl'.
+insertDefaultDecls :: DefaultEnv -> [LocatedA ClassDefaults] -> TcM DefaultEnv
+insertDefaultDecls = foldrM insertDefaultDecl
+-- | Insert a local default declaration into the default environment.
+--
+-- If the class already has a local default declaration in the DefaultEnv,
+-- report an error and return the original DefaultEnv. Otherwise, override
+-- any existing default declarations (e.g. imported default declarations).
+--
+-- See Note [Disambiguation of multiple default declarations] in GHC.Tc.Module
+insertDefaultDecl :: LocatedA ClassDefaults -> DefaultEnv -> TcM DefaultEnv
+insertDefaultDecl (L decl_loc new_cls_defaults ) default_env =
+ case lookupDefaultEnv default_env (className cls) of
+ Just cls_defaults
+ | DP_Local {} <- cd_provenance cls_defaults
+ -> do { setSrcSpan (locA decl_loc) (addErrTc $ TcRnMultipleDefaultDeclarations cls cls_defaults)
+ ; return default_env }
+ _ -> return $ insertDefaultEnv new_cls_defaults default_env
+ -- NB: this overrides imported and built-in default declarations
+ -- for this class, if there were any.
+ where
+ cls = cd_class new_cls_defaults
+
-- | Check that the type is an instance of at least one of the default classes.
--
@@ -289,10 +309,6 @@ simplifyDefault cls dflt_ty@(L l _)
-> Nothing
}
-dupDefaultDeclErr :: Class -> NonEmpty (LDefaultDecl GhcRn) -> TcRnMessage
-dupDefaultDeclErr cls (L _ DefaultDecl {} :| dup_things)
- = TcRnMultipleDefaultDeclarations cls dup_things
-
{- Note [Instance check for default declarations]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When we see a named default declaration, such as:
@@ -327,4 +343,4 @@ whether each type is an instance of:
- ... or the IsString class, with -XOverloadedStrings
- ... or any of the Show, Eq, Ord, Foldable, and Traversable classes,
with -XExtendedDefaultRules
--}
\ No newline at end of file
+-}
=====================================
compiler/GHC/Tc/Gen/Export.hs
=====================================
@@ -282,7 +282,7 @@ example,
would import the above `default IsString (Text, String)` declaration into the
importing module.
-The `cd_module` field of `ClassDefaults` tracks the module whence the default was
+The `cd_provenance` field of `ClassDefaults` tracks the module whence the default was
imported from, for the purpose of warning reports. The said warning report may be
triggered by `-Wtype-defaults` or by a user-defined `WARNING` pragma attached to
the default export. In the latter case the warning text is stored in the
=====================================
compiler/GHC/Tc/Module.hs
=====================================
@@ -383,6 +383,7 @@ the actual contents of the module are wired in to GHC.
-}
{- Note [Disambiguation of multiple default declarations]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
See Note [Named default declarations] in GHC.Tc.Gen.Default
@@ -1811,9 +1812,8 @@ tcTyClsInstDecls tycl_decls deriv_decls default_decls binds
--
-- But only after we've typechecked 'default' declarations.
-- See Note [Typechecking default declarations]
- defaults <- tcDefaults default_decls ;
- updGblEnv (\gbl -> gbl { tcg_default = defaults }) $ do {
-
+ defaults <- tcDefaultDecls default_decls
+ ; extendDefaultEnvWithLocalDefaults defaults $ do {
-- Careful to quit now in case there were instance errors, so that
-- the deriving errors don't pile up as well.
=====================================
compiler/GHC/Tc/Utils/Env.hs
=====================================
@@ -128,8 +128,7 @@ import GHC.Types.SourceFile
import GHC.Types.Name
import GHC.Types.Name.Set
import GHC.Types.Name.Env
-import GHC.Types.DefaultEnv ( DefaultEnv, ClassDefaults(..),
- defaultEnv, emptyDefaultEnv, lookupDefaultEnv, unitDefaultEnv )
+import GHC.Types.DefaultEnv
import GHC.Types.Error
import GHC.Types.Id
import GHC.Types.Id.Info ( RecSelParent(..) )
@@ -971,21 +970,28 @@ isBrackStage _other = False
************************************************************************
-}
-{- Note [Default class defaults]
+{- Note [Builtin class defaults]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-In absence of user-defined `default` declarations, the set of class defaults in
-effect (i.e. `DefaultEnv`) is determined by the absence or
-presence of the `ExtendedDefaultRules` and `OverloadedStrings` extensions. In their
-absence, the only rule in effect is `default Num (Integer, Double)` as specified by
-Haskell Language Report.
-
-In GHC's internal packages `DefaultEnv` is empty to minimize cross-module dependencies:
-the `Num` class or `Integer` type may not even be available in low-level modules. If
-you don't do this, attempted defaulting in package ghc-prim causes an actual crash
-(attempting to look up the `Integer` type).
-
-A user-defined `default` declaration overrides the defaults for the specified class,
-and only for that class.
+In the absence of user-defined `default` declarations, the set of class defaults in
+effect (i.e. the `DefaultEnv`) depends on whether the `ExtendedDefaultRules` and
+`OverloadedStrings` extensions are enabled. In their absence, the only rule in effect
+is `default Num (Integer, Double)`, as specified by the Haskell 2010 report.
+
+Remark [No built-in defaults in ghc-internal]
+
+ When typechecking the ghc-internal package, we **do not** include any built-in
+ defaults. This is because, in ghc-internal, types such as 'Num' or 'Integer' may
+ not even be available (they haven't been typechecked yet).
+
+Remark [default () in ghc-internal]
+
+ Historically, modules inside ghc-internal have used a single default declaration,
+ of the form `default ()`, to work around the problem described in
+ Remark [No built-in defaults in ghc-internal].
+
+ When we typecheck such a default declaration, we must also make sure not to fail
+ if e.g. 'Num' is not in scope. We thus have special treatment for this case,
+ in 'GHC.Tc.Gen.Default.tcDefaultDecls'.
-}
tcGetDefaultTys :: TcM (DefaultEnv, -- Default classes and types
@@ -997,7 +1003,7 @@ tcGetDefaultTys
-- See also #1974
builtinDefaults cls tys = ClassDefaults{ cd_class = cls
, cd_types = tys
- , cd_module = Nothing
+ , cd_provenance = DP_Builtin
, cd_warn = Nothing }
-- see Note [Named default declarations] in GHC.Tc.Gen.Default
@@ -1005,7 +1011,8 @@ tcGetDefaultTys
; this_module <- tcg_mod <$> getGblEnv
; let this_unit = moduleUnit this_module
; if this_unit == ghcInternalUnit
- -- see Note [Default class defaults]
+ -- see Remark [No built-in defaults in ghc-internal]
+ -- in Note [Builtin class defaults] in GHC.Tc.Utils.Env
then return (defaults, extended_defaults)
else do
-- not one of the built-in units
@@ -1037,6 +1044,8 @@ tcGetDefaultTys
}
-- The Num class is already user-defaulted, no need to construct the builtin default
_ -> pure emptyDefaultEnv
+ -- Supply the built-in defaults, but make the user-supplied defaults
+ -- override them.
; let deflt_tys = mconcat [ extDef, numDef, ovlStr, defaults ]
; return (deflt_tys, extended_defaults) } }
=====================================
compiler/GHC/Types/DefaultEnv.hs
=====================================
@@ -1,7 +1,9 @@
{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE LambdaCase #-}
module GHC.Types.DefaultEnv
( ClassDefaults (..)
+ , DefaultProvenance (..)
, DefaultEnv
, emptyDefaultEnv
, isEmptyDefaultEnv
@@ -12,6 +14,8 @@ module GHC.Types.DefaultEnv
, defaultList
, plusDefaultEnv
, mkDefaultEnv
+ , insertDefaultEnv
+ , isHaskell2010Default
)
where
@@ -22,6 +26,7 @@ import GHC.Tc.Utils.TcType (Type)
import GHC.Types.Name (Name, nameUnique, stableNameCmp)
import GHC.Types.Name.Env
import GHC.Types.Unique.FM (lookupUFM_Directly)
+import GHC.Types.SrcLoc (SrcSpan)
import GHC.Unit.Module.Warnings (WarningTxt)
import GHC.Unit.Types (Module)
import GHC.Utils.Outputable
@@ -37,13 +42,73 @@ import Data.Function (on)
-- NB: this includes Haskell98 default declarations, at the 'Num' key.
type DefaultEnv = NameEnv ClassDefaults
+{- Note [DefaultProvenance]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Each `ClassDefault` is annotated with its `DefaultProvenance`, which
+says where the default came from. Specifically
+* `DP_Local loc h98`: the default came from an explicit `default` declaration in the module
+ being compiled, at location `loc`, and the boolean `h98` indicates whether
+ it was from a Haskell 98 default declaration (e.g. `default (Int, Double)`).
+* `DP_Imported M`: the default was imported, it is explicitly exported by module `M`.
+* `DP_Builtin`: the default was automatically provided by GHC.
+ see Note [Builtin class defaults] in GHC.Tc.Utils.Env
+
+These annotations are used to disambiguate multiple defaults for the same class.
+For example, consider the following modules:
+
+ module M( default C ) where { default C( ... ) }
+ module M2( default C) where { import M }
+ module N( default C () where { default C(... ) }
+
+ module A where { import M2 }
+ module B where { import M2; import N }
+ module A1 where { import N; default C ( ... ) }
+ module B2 where { default C ( ... ); default C ( ... ) }
+
+When compiling N, the default for C is annotated with DP_Local loc.
+When compiling M2, the default for C is annotated with DP_Local M.
+When compiling A, the default for C is annotated with DP_Imported M2.
+
+Cases we needed to disambiguate:
+ * Compiling B, two defaults for C: DP_Imported M2, DP_Imported N.
+ * Compiling A1, two defaults for C: DP_Imported N, DP_Local loc.
+ * Compiling B2, two defaults for C: DP_Local loc1, DP_Local loc2.
+
+For how we disambiguate these cases,
+See Note [Disambiguation of multiple default declarations] in GHC.Tc.Module.
+-}
+
+-- | The provenance of a collection of default types for a class.
+-- see Note [DefaultProvenance] for more details
+data DefaultProvenance
+ -- | A locally defined default declaration.
+ = DP_Local
+ { defaultDeclLoc :: SrcSpan -- ^ The 'SrcSpan' of the default declaration
+ , defaultDeclH98 :: Bool -- ^ Is this a Haskell 98 default declaration?
+ }
+ -- | Built-in class defaults.
+ | DP_Builtin
+ -- | Imported class defaults.
+ | DP_Imported Module -- ^ The module from which the defaults were imported
+ deriving (Eq, Data)
+
+instance Outputable DefaultProvenance where
+ ppr (DP_Local loc h98) = ppr loc <> (if h98 then text " (H98)" else empty)
+ ppr DP_Builtin = text "built-in"
+ ppr (DP_Imported mod) = ppr mod
+
+isHaskell2010Default :: DefaultProvenance -> Bool
+isHaskell2010Default = \case
+ DP_Local { defaultDeclH98 = isH98 } -> isH98
+ DP_Builtin -> True
+ DP_Imported {} -> False
+
-- | Defaulting type assignments for the given class.
data ClassDefaults
= ClassDefaults { cd_class :: Class -- ^ The class whose defaults are being defined
, cd_types :: [Type]
- , cd_module :: Maybe Module
- -- ^ @Nothing@ for built-in,
- -- @Just@ the current module or the module whence the default was imported
+ , cd_provenance :: DefaultProvenance
+ -- ^ Where the defaults came from
-- see Note [Default exports] in GHC.Tc.Gen.Export
, cd_warn :: Maybe (WarningTxt GhcRn)
-- ^ Warning emitted when the default is used
@@ -70,6 +135,9 @@ defaultList :: DefaultEnv -> [ClassDefaults]
defaultList = sortBy (stableNameCmp `on` className . cd_class) . nonDetNameEnvElts
-- sortBy recovers determinism
+insertDefaultEnv :: ClassDefaults -> DefaultEnv -> DefaultEnv
+insertDefaultEnv d env = extendNameEnv env (className $ cd_class d) d
+
lookupDefaultEnv :: DefaultEnv -> Name -> Maybe ClassDefaults
lookupDefaultEnv env = lookupUFM_Directly env . nameUnique
=====================================
libraries/template-haskell/Language/Haskell/TH/Syntax.hs
=====================================
@@ -1,6 +1,6 @@
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
-{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE Safe #-}
{-# LANGUAGE UnboxedTuples #-}
module Language.Haskell.TH.Syntax (
@@ -190,16 +190,11 @@ module Language.Haskell.TH.Syntax (
nothingName,
rightName,
trueName,
- addrToByteArrayName,
- addrToByteArray,
)
where
-import Data.Array.Byte
import GHC.Boot.TH.Lift
import GHC.Boot.TH.Syntax
-import GHC.Exts
-import GHC.ST
import System.FilePath
-- This module completely re-exports 'GHC.Boot.TH.Syntax',
@@ -211,17 +206,3 @@ makeRelativeToProject fp | isRelative fp = do
root <- getPackageRoot
return (root </> fp)
makeRelativeToProject fp = return fp
-
--- The following two defintions are copied from 'Data.Byte.Array'
--- in order to preserve the old export list of 'TH.Syntax'.
--- They will soon be removed as part of #24782.
-
-addrToByteArrayName :: Name
-addrToByteArrayName = 'addrToByteArray
-
-addrToByteArray :: Int -> Addr# -> ByteArray
-addrToByteArray (I# len) addr = runST $ ST $
- \s -> case newByteArray# len s of
- (# s', mb #) -> case copyAddrToByteArray# addr mb 0# len s' of
- s'' -> case unsafeFreezeByteArray# mb s'' of
- (# s''', ret #) -> (# s''', ByteArray ret #)
=====================================
libraries/template-haskell/changelog.md
=====================================
@@ -13,6 +13,8 @@
* Remove the `Language.Haskell.TH.Lib.Internal` module. This module has long been deprecated, and exposes compiler internals.
Users should use `Language.Haskell.TH.Lib` instead, which exposes a stable version of this API.
+
+ * Remove `addrToByteArrayName` and `addrToByteArray` from `Language.Haskell.TH.Syntax`. These were part of the implementation of the `Lift ByteArray` instance and were errornously exported because this module lacked an explicit export list. They have no usages on Hackage.
## 2.23.0.0
=====================================
testsuite/tests/default/T25912.hs
=====================================
@@ -0,0 +1,14 @@
+{-# LANGUAGE NamedDefaults #-}
+
+module Main where
+
+import T25912_helper
+
+-- now we declare the default instances
+-- for the classes C again to check that
+-- it won't hide the default instances for class B
+default C (String)
+
+main :: IO ()
+main = do
+ print b
=====================================
testsuite/tests/default/T25912.stdout
=====================================
@@ -0,0 +1 @@
+"String"
=====================================
testsuite/tests/default/T25912_helper.hs
=====================================
@@ -0,0 +1,17 @@
+{-# LANGUAGE NamedDefaults #-}
+
+module T25912_helper ( default C, C(c), default B, b ) where
+
+class C a where
+ c :: a
+instance C Int where
+ c = 1
+instance C String where
+ c = "String"
+default C (String)
+
+class B a where
+ b :: a
+instance B String where
+ b = "String"
+default B (String)
=====================================
testsuite/tests/default/T25914.hs
=====================================
@@ -0,0 +1,5 @@
+{-# LANGUAGE NamedDefaults, OverloadedStrings #-}
+module NamedDefaultsNum where
+import Data.String
+default Num ()
+foo = "abc"
=====================================
testsuite/tests/default/T25934.hs
=====================================
@@ -0,0 +1,5 @@
+{-# LANGUAGE ExtendedDefaultRules #-}
+{-# LANGUAGE NamedDefaults #-}
+module T25934 where
+default Num (Int)
+default Show (Int)
=====================================
testsuite/tests/default/all.T
=====================================
@@ -39,3 +39,6 @@ test('T25858v2', [extra_files(['T25858v2_helper.hs'])], multimod_compile_and_run
test('T25858v3', [extra_files(['T25858v3_helper.hs'])], multimod_compile_and_run, ['T25858v3', ''])
test('T25858v4', normal, compile_and_run, [''])
test('T25882', normal, compile, [''])
+test('T25912', [extra_files(['T25912_helper.hs'])], multimod_compile_and_run, ['T25912', ''])
+test('T25914', normal, compile, [''])
+test('T25934', normal, compile, [''])
=====================================
testsuite/tests/default/default-fail03.stderr
=====================================
@@ -1,3 +1,4 @@
-default-fail03.hs:4:1: [GHC-99565]
+default-fail03.hs:4:1: error: [GHC-99565]
Multiple default declarations for class ‘Num’
- here was another default declaration default-fail03.hs:3:1-29
+ conflicting named default declaration at: default-fail03.hs:3:1-29
+
=====================================
testsuite/tests/interface-stability/template-haskell-exports.stdout
=====================================
@@ -1369,7 +1369,7 @@ module Language.Haskell.TH.Quote where
quoteFile :: QuasiQuoter -> QuasiQuoter
module Language.Haskell.TH.Syntax where
- -- Safety: Trustworthy
+ -- Safety: Safe
type AnnLookup :: *
data AnnLookup = AnnLookupModule Module | AnnLookupName Name
type AnnTarget :: *
@@ -1780,8 +1780,6 @@ module Language.Haskell.TH.Syntax where
addModFinalizer :: Q () -> Q ()
addTempFile :: GHC.Internal.Base.String -> Q GHC.Internal.IO.FilePath
addTopDecls :: [Dec] -> Q ()
- addrToByteArray :: GHC.Internal.Types.Int -> GHC.Internal.Prim.Addr# -> Data.Array.Byte.ByteArray
- addrToByteArrayName :: Name
badIO :: forall a. GHC.Internal.Base.String -> GHC.Internal.Types.IO a
bindCode :: forall (m :: * -> *) a (r :: GHC.Internal.Types.RuntimeRep) (b :: TYPE r). GHC.Internal.Base.Monad m => m a -> (a -> Code m b) -> Code m b
bindCode_ :: forall (m :: * -> *) a (r :: GHC.Internal.Types.RuntimeRep) (b :: TYPE r). GHC.Internal.Base.Monad m => m a -> Code m b -> Code m b
=====================================
testsuite/tests/linters/notes.stdout
=====================================
@@ -8,7 +8,7 @@ ref compiler/GHC/Core/Opt/Simplify/Iteration.hs:4345:8: Note [Lambda-boun
ref compiler/GHC/Core/Opt/Simplify/Utils.hs:1387:37: Note [Gentle mode]
ref compiler/GHC/Core/Opt/Specialise.hs:1761:29: Note [Arity decrease]
ref compiler/GHC/Core/TyCo/Rep.hs:1783:31: Note [What prevents a constraint from floating]
-ref compiler/GHC/Driver/DynFlags.hs:1216:52: Note [Eta-reduction in -O0]
+ref compiler/GHC/Driver/DynFlags.hs:1218:52: Note [Eta-reduction in -O0]
ref compiler/GHC/Driver/Main.hs:1901:34: Note [simpleTidyPgm - mkBootModDetailsTc]
ref compiler/GHC/Hs/Expr.hs:189:63: Note [Pending Splices]
ref compiler/GHC/Hs/Expr.hs:2194:87: Note [Lifecycle of a splice]
@@ -18,10 +18,8 @@ ref compiler/GHC/Hs/Pat.hs:151:74: Note [Lifecycle of a splice]
ref compiler/GHC/HsToCore/Pmc/Solver.hs:860:20: Note [COMPLETE sets on data families]
ref compiler/GHC/HsToCore/Quote.hs:1533:7: Note [How brackets and nested splices are handled]
ref compiler/GHC/Stg/Unarise.hs:457:32: Note [Renaming during unarisation]
-ref compiler/GHC/Tc/Gen/Default.hs:87:6: Note [Disambiguation of multiple default declarations]
-ref compiler/GHC/Tc/Gen/Default.hs:193:11: Note [Disambiguation of multiple default declarations]
ref compiler/GHC/Tc/Gen/HsType.hs:563:56: Note [Skolem escape prevention]
-ref compiler/GHC/Tc/Gen/HsType.hs:2693:7: Note [Matching a kind signature with a declaration]
+ref compiler/GHC/Tc/Gen/HsType.hs:2717:7: Note [Matching a kind signature with a declaration]
ref compiler/GHC/Tc/Gen/Pat.hs:284:20: Note [Typing patterns in pattern bindings]
ref compiler/GHC/Tc/Gen/Pat.hs:1378:7: Note [Matching polytyped patterns]
ref compiler/GHC/Tc/Gen/Sig.hs:91:10: Note [Overview of type signatures]
@@ -30,8 +28,6 @@ ref compiler/GHC/Tc/Gen/Splice.hs:543:35: Note [PendingRnSplice]
ref compiler/GHC/Tc/Gen/Splice.hs:670:7: Note [How brackets and nested splices are handled]
ref compiler/GHC/Tc/Gen/Splice.hs:909:11: Note [How brackets and nested splices are handled]
ref compiler/GHC/Tc/Instance/Family.hs:458:35: Note [Constrained family instances]
-ref compiler/GHC/Tc/Module.hs:385:3: Note [Disambiguation of multiple default declarations]
-ref compiler/GHC/Tc/Module.hs:420:7: Note [Disambiguation of multiple default declarations]
ref compiler/GHC/Tc/Solver/Rewrite.hs:1015:7: Note [Stability of rewriting]
ref compiler/GHC/Tc/TyCl.hs:1322:6: Note [Unification variables need fresh Names]
ref compiler/GHC/Tc/Types/Constraint.hs:209:9: Note [NonCanonical Semantics]
=====================================
testsuite/tests/module/mod58.stderr
=====================================
@@ -1,4 +1,4 @@
-
mod58.hs:4:1: error: [GHC-99565]
Multiple default declarations for class ‘Num’
- here was another default declaration mod58.hs:3:1-21
+ conflicting default declaration at: mod58.hs:3:1-21
+
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/e08a689a75c44fcf8cc924d30eb1c2…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/e08a689a75c44fcf8cc924d30eb1c2…
You're receiving this email because of your account on gitlab.haskell.org.
1
0

16 Apr '25
Ben Gamari pushed to branch ghc-9.10 at Glasgow Haskell Compiler / GHC
Commits:
d57c1dba by Andrew Lelechenko at 2025-04-16T19:37:25-04:00
Bump submodule deepseq to 1.5.1.0
Partial cherry-pick to ensure that bootstrapping is possible with newer
deepseq versions. We do not take the submodule bump itself to ensure
that we don't break downstream users unnecessarily.
(cherry picked from commit 8e462f4d4bdf2a6c34c249e7be8084565600d300)
- - - - -
1 changed file:
- compiler/GHC/Unit/Types.hs
Changes:
=====================================
compiler/GHC/Unit/Types.hs
=====================================
@@ -104,9 +104,9 @@ import GHC.Utils.Fingerprint
import GHC.Utils.Misc
import GHC.Settings.Config (cProjectUnitId)
-import Control.DeepSeq
+import Control.DeepSeq (NFData(..))
import Data.Data
-import Data.List (sortBy )
+import Data.List (sortBy)
import Data.Function
import Data.Bifunctor
import qualified Data.ByteString as BS
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d57c1dbaafa317f113c85b24614050f…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d57c1dbaafa317f113c85b24614050f…
You're receiving this email because of your account on gitlab.haskell.org.
1
0

[Git][ghc/ghc][ghc-9.10] 2 commits: Revert "release: copy index.html from correct directory"
by Ben Gamari (@bgamari) 16 Apr '25
by Ben Gamari (@bgamari) 16 Apr '25
16 Apr '25
Ben Gamari pushed to branch ghc-9.10 at Glasgow Haskell Compiler / GHC
Commits:
da185a48 by Zubin Duggal at 2025-04-16T15:58:54-04:00
Revert "release: copy index.html from correct directory"
This reverts commit cbfd0829cd61928976c9eb17ba4af18272466063.
- - - - -
73c1e1c6 by Ben Gamari at 2025-04-16T19:33:33-04:00
Revert "configure: Bump version"
This reverts commit fb0a1832e4826ba477ba04d3584100e55d5f591e.
- - - - -
2 changed files:
- .gitlab/rel_eng/fetch-gitlab-artifacts/fetch_gitlab.py
- configure.ac
Changes:
=====================================
.gitlab/rel_eng/fetch-gitlab-artifacts/fetch_gitlab.py
=====================================
@@ -129,7 +129,7 @@ def fetch_artifacts(release: str, pipeline_id: int,
for f in doc_files:
subprocess.run(['tar', '-xf', f, '-C', dest])
logging.info(f'extracted docs {f} to {dest}')
- index_path = destdir / 'docs' / 'index.html'
+ index_path = destdir / 'index.html'
index_path.replace(dest / 'index.html')
elif job.name == 'hackage-doc-tarball':
dest = dest_dir / 'hackage_docs'
=====================================
configure.ac
=====================================
@@ -13,7 +13,7 @@ dnl
# see what flags are available. (Better yet, read the documentation!)
#
-AC_INIT([The Glorious Glasgow Haskell Compilation System], [9.10.2], [glasgow-haskell-bugs(a)haskell.org] [ghc-AC_PACKAGE_VERSION])
+AC_INIT([The Glorious Glasgow Haskell Compilation System], [9.10.1], [glasgow-haskell-bugs(a)haskell.org] [ghc-AC_PACKAGE_VERSION])
# Version on master must be X.Y (not X.Y.Z) for ProjectVersionMunged variable
# to be useful (cf #19058). However, the version must have three components
# (X.Y.Z) on stable branches (e.g. ghc-9.2) to ensure that pre-releases are
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/ebd3a035e016d098bc39c4ab39560b…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/ebd3a035e016d098bc39c4ab39560b…
You're receiving this email because of your account on gitlab.haskell.org.
1
0

[Git][ghc/ghc][wip/T25965] Fix infelicities in the Specialiser
by Simon Peyton Jones (@simonpj) 16 Apr '25
by Simon Peyton Jones (@simonpj) 16 Apr '25
16 Apr '25
Simon Peyton Jones pushed to branch wip/T25965 at Glasgow Haskell Compiler / GHC
Commits:
e00129b9 by Simon Peyton Jones at 2025-04-16T23:51:31+01:00
Fix infelicities in the Specialiser
On the way to #23109 (unary classes) I discovered some infelicities
(or maybe tiny bugs, I forget) in the type-class specialiser.
I also tripped over #25965, an outright bug in the rule matcher
Specifically:
* Refactor: I enhanced `wantCallsFor`, whih previously always said
`True`, to discard calls of class-ops, data constructors etc. This is
a bit more efficient; and it means we don't need to worry about
filtering them out later.
* Fix: I tidied up some tricky logic that eliminated redundant
specialisations. It wasn't working correctly. See the expanded
Note [Specialisations already covered], and
(MP3) in Note [Specialising polymorphic dictionaries].
See also the new top-level `alreadyCovered`
function, which now goes via `GHC.Core.Rules.ruleLhsIsMoreSpecific`
I also added a useful Note [The (CI-KEY) invariant]
* Fix #25965: fixed a tricky bug in the `go_fam_fam` in
`GHC.Core.Unify.uVarOrFam`, which allows matching to succeed
without binding all type varibles.
I enhanced Note [Apartness and type families] some more
* #25703. This ticket "just works" with -fpolymorphic-specialisation;
but I was surprised that it worked! In this MR I added documentation
to Note [Interesting dictionary arguments] to explain; and tests to
ensure it stays fixed.
- - - - -
11 changed files:
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/Rules.hs
- compiler/GHC/Core/Unify.hs
- compiler/GHC/Tc/Solver/Equality.hs
- compiler/GHC/Types/Basic.hs
- + testsuite/tests/simplCore/should_compile/T25703.hs
- + testsuite/tests/simplCore/should_compile/T25703.stderr
- + testsuite/tests/simplCore/should_compile/T25703a..stderr
- + testsuite/tests/simplCore/should_compile/T25703a.hs
- + testsuite/tests/simplCore/should_compile/T25965.hs
- testsuite/tests/simplCore/should_compile/all.T
Changes:
=====================================
compiler/GHC/Core/Opt/Specialise.hs
=====================================
@@ -1243,14 +1243,15 @@ specExpr env (Let bind body)
-- Note [Fire rules in the specialiser]
fireRewriteRules :: SpecEnv -> InExpr -> [OutExpr] -> (InExpr, [OutExpr])
fireRewriteRules env (Var f) args
- | Just (rule, expr) <- specLookupRule env f args InitialPhase (getRules (se_rules env) f)
+ | let rules = getRules (se_rules env) f
+ , Just (rule, expr) <- specLookupRule env f args activeInInitialPhase rules
, let rest_args = drop (ruleArity rule) args -- See Note [Extra args in the target]
zapped_subst = Core.zapSubst (se_subst env)
expr' = simpleOptExprWith defaultSimpleOpts zapped_subst expr
-- simplOptExpr needed because lookupRule returns
-- (\x y. rhs) arg1 arg2
- , (fun, args) <- collectArgs expr'
- = fireRewriteRules env fun (args++rest_args)
+ , (fun', args') <- collectArgs expr'
+ = fireRewriteRules env fun' (args'++rest_args)
fireRewriteRules _ fun args = (fun, args)
--------------
@@ -1620,7 +1621,7 @@ specCalls :: Bool -- True => specialising imported fn
-- This function checks existing rules, and does not create
-- duplicate ones. So the caller does not need to do this filtering.
--- See 'already_covered'
+-- See `alreadyCovered`
type SpecInfo = ( [CoreRule] -- Specialisation rules
, [(Id,CoreExpr)] -- Specialised definition
@@ -1644,15 +1645,13 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
= -- pprTrace "specCalls: some" (vcat
-- [ text "function" <+> ppr fn
- -- , text "calls:" <+> ppr calls_for_me
- -- , text "subst" <+> ppr (se_subst env) ]) $
+ -- , text "calls:" <+> ppr calls_for_me
+ -- , text "subst" <+> ppr (se_subst env) ]) $
foldlM spec_call ([], [], emptyUDs) calls_for_me
| otherwise -- No calls or RHS doesn't fit our preconceptions
- = warnPprTrace (not (exprIsTrivial rhs) && notNull calls_for_me && not (isClassOpId fn))
+ = warnPprTrace (not (exprIsTrivial rhs) && notNull calls_for_me)
"Missed specialisation opportunity for" (ppr fn $$ trace_doc) $
- -- isClassOpId: class-op Ids never inline; we specialise them
- -- through fireRewriteRules. So don't complain about missed opportunities
-- Note [Specialisation shape]
-- pprTrace "specCalls: none" (ppr fn <+> ppr calls_for_me) $
return ([], [], emptyUDs)
@@ -1664,6 +1663,10 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
fn_unf = realIdUnfolding fn -- Ignore loop-breaker-ness here
inl_prag = idInlinePragma fn
inl_act = inlinePragmaActivation inl_prag
+ is_active = isActive (beginPhase inl_act) :: Activation -> Bool
+ -- is_active: inl_act is the activation we are going to put in the new
+ -- SPEC rule; so we want to see if it is covered by another rule with
+ -- that same activation.
is_local = isLocalId fn
is_dfun = isDFunId fn
dflags = se_dflags env
@@ -1674,16 +1677,6 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
(rhs_bndrs, rhs_body) = collectBindersPushingCo rhs
-- See Note [Account for casts in binding]
- already_covered :: SpecEnv -> [CoreRule] -> [CoreExpr] -> Bool
- already_covered env new_rules args -- Note [Specialisations already covered]
- = isJust (specLookupRule env fn args (beginPhase inl_act)
- (new_rules ++ existing_rules))
- -- Rules: we look both in the new_rules (generated by this invocation
- -- of specCalls), and in existing_rules (passed in to specCalls)
- -- inl_act: is the activation we are going to put in the new SPEC
- -- rule; so we want to see if it is covered by another rule with
- -- that same activation.
-
----------------------------------------------------------
-- Specialise to one particular call pattern
spec_call :: SpecInfo -- Accumulating parameter
@@ -1717,8 +1710,12 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
-- , ppr dx_binds ]) $
-- return ()
+ ; let all_rules = rules_acc ++ existing_rules
+ -- all_rules: we look both in the rules_acc (generated by this invocation
+ -- of specCalls), and in existing_rules (passed in to specCalls)
; if not useful -- No useful specialisation
- || already_covered rhs_env2 rules_acc rule_lhs_args
+ || alreadyCovered rhs_env2 rule_bndrs fn rule_lhs_args is_active all_rules
+ -- See (SC1) in Note [Specialisations already covered]
then return spec_acc
else
do { -- Run the specialiser on the specialised RHS
@@ -1780,7 +1777,7 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
spec_fn_details
= case idDetails fn of
JoinId join_arity _ -> JoinId (join_arity - join_arity_decr) Nothing
- DFunId is_nt -> DFunId is_nt
+ DFunId unary -> DFunId unary
_ -> VanillaId
; spec_fn <- newSpecIdSM (idName fn) spec_fn_ty spec_fn_details spec_fn_info
@@ -1804,6 +1801,8 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
, ppr spec_fn <+> dcolon <+> ppr spec_fn_ty
, ppr rhs_bndrs, ppr call_args
, ppr spec_rule
+ , text "acc" <+> ppr rules_acc
+ , text "existing" <+> ppr existing_rules
]
; -- pprTrace "spec_call: rule" _rule_trace_doc
@@ -1812,19 +1811,35 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
, spec_uds `thenUDs` uds_acc
) } }
+alreadyCovered :: SpecEnv
+ -> [Var] -> Id -> [CoreExpr] -- LHS of possible new rule
+ -> (Activation -> Bool) -- Which rules are active
+ -> [CoreRule] -> Bool
+-- Note [Specialisations already covered] esp (SC2)
+alreadyCovered env bndrs fn args is_active rules
+ = case specLookupRule env fn args is_active rules of
+ Nothing -> False
+ Just (rule, _)
+ | isAutoRule rule -> -- Discard identical rules
+ -- We know that (fn args) is an instance of RULE
+ -- Check if RULE is an instance of (fn args)
+ ruleLhsIsMoreSpecific in_scope bndrs args rule
+ | otherwise -> True -- User rules dominate
+ where
+ in_scope = substInScopeSet (se_subst env)
+
-- Convenience function for invoking lookupRule from Specialise
-- The SpecEnv's InScopeSet should include all the Vars in the [CoreExpr]
specLookupRule :: SpecEnv -> Id -> [CoreExpr]
- -> CompilerPhase -- Look up rules as if we were in this phase
+ -> (Activation -> Bool) -- Which rules are active
-> [CoreRule] -> Maybe (CoreRule, CoreExpr)
-specLookupRule env fn args phase rules
+specLookupRule env fn args is_active rules
= lookupRule ropts in_scope_env is_active fn args rules
where
dflags = se_dflags env
in_scope = substInScopeSet (se_subst env)
in_scope_env = ISE in_scope (whenActiveUnfoldingFun is_active)
ropts = initRuleOpts dflags
- is_active = isActive phase
{- Note [Specialising DFuns]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2323,21 +2338,24 @@ This plan is implemented in the Rec case of specBindItself.
Note [Specialisations already covered]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We obviously don't want to generate two specialisations for the same
-argument pattern. There are two wrinkles
-
-1. We do the already-covered test in specDefn, not when we generate
-the CallInfo in mkCallUDs. We used to test in the latter place, but
-we now iterate the specialiser somewhat, and the Id at the call site
-might therefore not have all the RULES that we can see in specDefn
-
-2. What about two specialisations where the second is an *instance*
-of the first? If the more specific one shows up first, we'll generate
-specialisations for both. If the *less* specific one shows up first,
-we *don't* currently generate a specialisation for the more specific
-one. (See the call to lookupRule in already_covered.) Reasons:
- (a) lookupRule doesn't say which matches are exact (bad reason)
- (b) if the earlier specialisation is user-provided, it's
- far from clear that we should auto-specialise further
+argument pattern. Wrinkles
+
+(SC1) We do the already-covered test in specDefn, not when we generate
+ the CallInfo in mkCallUDs. We used to test in the latter place, but
+ we now iterate the specialiser somewhat, and the Id at the call site
+ might therefore not have all the RULES that we can see in specDefn
+
+(SC2) What about two specialisations where the second is an *instance*
+ of the first? It's a bit arbitrary, but here's what we do:
+ * If the existing one is user-specified, via a SPECIALISE pragma, we
+ suppress the further specialisation.
+ * If the existing one is auto-generated, we generate a second RULE
+ for the more specialised version.
+ The latter is important because we don't want the accidental order
+ of calls to determine what specialisations we generate.
+
+(SC3) Annoyingly, we /also/ eliminate duplicates in `filterCalls`.
+ See (MP3) in Note [Specialising polymorphic dictionaries]
Note [Auto-specialisation and RULES]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2800,12 +2818,10 @@ non-dictionary bindings too.
Note [Specialising polymorphic dictionaries]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Note June 2023: This has proved to be quite a tricky optimisation to get right
see (#23469, #23109, #21229, #23445) so it is now guarded by a flag
`-fpolymorphic-specialisation`.
-
Consider
class M a where { foo :: a -> Int }
@@ -2845,11 +2861,26 @@ Here are the moving parts:
function.
(MP3) If we have f :: forall m. Monoid m => blah, and two calls
- (f @(Endo b) (d :: Monoid (Endo b))
- (f @(Endo (c->c)) (d :: Monoid (Endo (c->c)))
+ (f @(Endo b) (d1 :: Monoid (Endo b))
+ (f @(Endo (c->c)) (d2 :: Monoid (Endo (c->c)))
we want to generate a specialisation only for the first. The second
is just a substitution instance of the first, with no greater specialisation.
- Hence the call to `remove_dups` in `filterCalls`.
+ Hence the use of `removeDupCalls` in `filterCalls`.
+
+ You might wonder if `d2` might be more specialised than `d1`; but no.
+ This `removeDupCalls` thing is at the definition site of `f`, and both `d1`
+ and `d2` are in scope. So `d1` is simply more polymorphic than `d2`, but
+ is just as specialised.
+
+ This distinction is sadly lost once we build a RULE, so `alreadyCovered`
+ can't be so clever. E.g if we have an existing RULE
+ forall @a (d1:Ord Int) (d2: Eq a). f @a @Int d1 d2 = ...
+ and a putative new rule
+ forall (d1:Ord Int) (d2: Eq Int). f @Int @Int d1 d2 = ...
+ we /don't/ want the existing rule to subsume the new one.
+
+ So we sadly put up with having two rather different places where we
+ eliminate duplicates: `alreadyCovered` and `removeDupCalls`.
All this arose in #13873, in the unexpected form that a SPECIALISE
pragma made the program slower! The reason was that the specialised
@@ -2947,16 +2978,29 @@ data CallInfoSet = CIS Id (Bag CallInfo)
-- The list of types and dictionaries is guaranteed to
-- match the type of f
-- The Bag may contain duplicate calls (i.e. f @T and another f @T)
- -- These dups are eliminated by already_covered in specCalls
+ -- These dups are eliminated by alreadyCovered in specCalls
data CallInfo
- = CI { ci_key :: [SpecArg] -- All arguments
+ = CI { ci_key :: [SpecArg] -- Arguments of the call
+ -- See Note [The (CI-KEY) invariant]
+
, ci_fvs :: IdSet -- Free Ids of the ci_key call
-- /not/ including the main id itself, of course
-- NB: excluding tyvars:
-- See Note [Specialising polymorphic dictionaries]
}
+{- Note [The (CI-KEY) invariant]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Invariant (CI-KEY):
+ In the `ci_key :: [SpecArg]` field of `CallInfo`,
+ * The list is non-empty
+ * The least element is always a `SpecDict`
+
+In this way the RULE has as few args as possible, which broadens its
+applicability, since rules only fire when saturated.
+-}
+
type DictExpr = CoreExpr
ciSetFilter :: (CallInfo -> Bool) -> CallInfoSet -> CallInfoSet
@@ -3045,10 +3089,7 @@ mkCallUDs' env f args
ci_key :: [SpecArg]
ci_key = dropWhileEndLE (not . isSpecDict) $
zipWith mk_spec_arg args pis
- -- Drop trailing args until we get to a SpecDict
- -- In this way the RULE has as few args as possible,
- -- which broadens its applicability, since rules only
- -- fire when saturated
+ -- Establish (CI-KEY): drop trailing args until we get to a SpecDict
mk_spec_arg :: OutExpr -> PiTyBinder -> SpecArg
mk_spec_arg arg (Named bndr)
@@ -3086,34 +3127,75 @@ site, so we only look through ticks that RULE matching looks through
-}
wantCallsFor :: SpecEnv -> Id -> Bool
-wantCallsFor _env _f = True
- -- We could reduce the size of the UsageDetails by being less eager
- -- about collecting calls for LocalIds: there is no point for
- -- ones that are lambda-bound. We can't decide this by looking at
- -- the (absence of an) unfolding, because unfoldings for local
- -- functions are discarded by cloneBindSM, so no local binder will
- -- have an unfolding at this stage. We'd have to keep a candidate
- -- set of let-binders.
- --
- -- Not many lambda-bound variables have dictionary arguments, so
- -- this would make little difference anyway.
- --
- -- For imported Ids we could check for an unfolding, but we have to
- -- do so anyway in canSpecImport, and it seems better to have it
- -- all in one place. So we simply collect usage info for imported
- -- overloaded functions.
-
-{- Note [Interesting dictionary arguments]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Consider this
- \a.\d:Eq a. let f = ... in ...(f d)...
-There really is not much point in specialising f wrt the dictionary d,
-because the code for the specialised f is not improved at all, because
-d is lambda-bound. We simply get junk specialisations.
-
-What is "interesting"? Just that it has *some* structure. But what about
-variables? We look in the variable's /unfolding/. And that means
-that we must be careful to ensure that dictionaries have unfoldings,
+-- See Note [wantCallsFor]
+wantCallsFor _env f
+ = case idDetails f of
+ RecSelId {} -> False
+ DataConWorkId {} -> False
+ DataConWrapId {} -> False
+ ClassOpId {} -> False
+ PrimOpId {} -> False
+ FCallId {} -> False
+ TickBoxOpId {} -> False
+ CoVarId {} -> False
+
+ DFunId {} -> True
+ VanillaId {} -> True
+ JoinId {} -> True
+ WorkerLikeId {} -> True
+ RepPolyId {} -> True
+
+{- Note [wantCallsFor]
+~~~~~~~~~~~~~~~~~~~~~~
+`wantCallsFor env f` says whether the Specialiser should collect calls for
+function `f`; other thing being equal, the fewer calls we collect the better. It
+is False for things we can't specialise:
+
+* ClassOpId: never inline and we don't have a defn to specialise; we specialise
+ them through fireRewriteRules.
+* PrimOpId: are never overloaded
+* Data constructors: we never specialise them
+
+We could reduce the size of the UsageDetails by being less eager about
+collecting calls for some LocalIds: there is no point for ones that are
+lambda-bound. We can't decide this by looking at the (absence of an) unfolding,
+because unfoldings for local functions are discarded by cloneBindSM, so no local
+binder will have an unfolding at this stage. We'd have to keep a candidate set
+of let-binders.
+
+Not many lambda-bound variables have dictionary arguments, so this would make
+little difference anyway.
+
+For imported Ids we could check for an unfolding, but we have to do so anyway in
+canSpecImport, and it seems better to have it all in one place. So we simply
+collect usage info for imported overloaded functions.
+
+Note [Interesting dictionary arguments]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+In `mkCallUDs` we only use `SpecDict` for dictionaries of which
+`interestingDict` holds. Otherwise we use `UnspecArg`. Two reasons:
+
+* Consider this
+ \a.\d:Eq a. let f = ... in ...(f d)...
+ There really is not much point in specialising f wrt the dictionary d,
+ because the code for the specialised f is not improved at all, because
+ d is lambda-bound. We simply get junk specialisations.
+
+* Consider this (#25703):
+ f :: (Eq a, Show b) => a -> b -> INt
+ goo :: forall x. (Eq x) => x -> blah
+ goo @x (d:Eq x) (arg:x) = ...(f @x @Int d $fShowInt)...
+ If we built a `ci_key` with a (SpecDict d) for `d`, we would end up
+ discarding the call at the `\d`. But if we use `UnspecArg` for that
+ uninteresting `d`, we'll get a `ci_key` of
+ f @x @Int UnspecArg (SpecDict $fShowInt)
+ and /that/ can float out to f's definition and specialise nicely.
+ Hooray.
+
+What is "interesting"? (See `interestingDict`.) Just that it has *some*
+structure. But what about variables? We look in the variable's /unfolding/.
+And that means that we must be careful to ensure that dictionaries /have/
+unfoldings,
* cloneBndrSM discards non-Stable unfoldings
* specBind updates the unfolding after specialisation
@@ -3159,7 +3241,7 @@ Now `f` turns into:
meth @a dc ....
When we specialise `f`, at a=Int say, that superclass selection can
-nfire (via rewiteClassOps), but that info (that 'dc' is now a
+fire (via rewiteClassOps), but that info (that 'dc' is now a
particular dictionary `C`, of type `C Int`) must be available to
the call `meth @a dc`, so that we can fire the `meth` class-op, and
thence specialise `wombat`.
@@ -3286,7 +3368,11 @@ dumpUDs :: [CoreBndr] -> UsageDetails -> (UsageDetails, OrdList DictBind)
-- Used at a lambda or case binder; just dump anything mentioning the binder
dumpUDs bndrs uds@(MkUD { ud_binds = orig_dbs, ud_calls = orig_calls })
| null bndrs = (uds, nilOL) -- Common in case alternatives
- | otherwise = -- pprTrace "dumpUDs" (ppr bndrs $$ ppr free_uds $$ ppr dump_dbs) $
+ | otherwise = -- pprTrace "dumpUDs" (vcat
+ -- [ text "bndrs" <+> ppr bndrs
+ -- , text "uds" <+> ppr uds
+ -- , text "free_uds" <+> ppr free_uds
+ -- , text "dump-dbs" <+> ppr dump_dbs ]) $
(free_uds, dump_dbs)
where
free_uds = uds { ud_binds = free_dbs, ud_calls = free_calls }
@@ -3325,20 +3411,17 @@ callsForMe fn uds@MkUD { ud_binds = orig_dbs, ud_calls = orig_calls }
calls_for_me = case lookupDVarEnv orig_calls fn of
Nothing -> []
Just cis -> filterCalls cis orig_dbs
- -- filterCalls: drop calls that (directly or indirectly)
- -- refer to fn. See Note [Avoiding loops (DFuns)]
----------------------
filterCalls :: CallInfoSet -> FloatedDictBinds -> [CallInfo]
--- Remove dominated calls (Note [Specialising polymorphic dictionaries])
--- and loopy DFuns (Note [Avoiding loops (DFuns)])
+-- Remove
+-- (a) dominated calls: (MP3) in Note [Specialising polymorphic dictionaries]
+-- (b) loopy DFuns: Note [Avoiding loops (DFuns)]
filterCalls (CIS fn call_bag) (FDB { fdb_binds = dbs })
- | isDFunId fn -- Note [Avoiding loops (DFuns)] applies only to DFuns
- = filter ok_call de_dupd_calls
- | otherwise -- Do not apply it to non-DFuns
- = de_dupd_calls -- See Note [Avoiding loops (non-DFuns)]
+ | isDFunId fn = filter ok_call de_dupd_calls -- Deals with (b)
+ | otherwise = de_dupd_calls
where
- de_dupd_calls = remove_dups call_bag
+ de_dupd_calls = removeDupCalls call_bag -- Deals with (a)
dump_set = foldl' go (unitVarSet fn) dbs
-- This dump-set could also be computed by splitDictBinds
@@ -3352,10 +3435,10 @@ filterCalls (CIS fn call_bag) (FDB { fdb_binds = dbs })
ok_call (CI { ci_fvs = fvs }) = fvs `disjointVarSet` dump_set
-remove_dups :: Bag CallInfo -> [CallInfo]
+removeDupCalls :: Bag CallInfo -> [CallInfo]
-- Calls involving more generic instances beat more specific ones.
-- See (MP3) in Note [Specialising polymorphic dictionaries]
-remove_dups calls = foldr add [] calls
+removeDupCalls calls = foldr add [] calls
where
add :: CallInfo -> [CallInfo] -> [CallInfo]
add ci [] = [ci]
@@ -3364,12 +3447,20 @@ remove_dups calls = foldr add [] calls
| otherwise = ci2 : add ci1 cis
beats_or_same :: CallInfo -> CallInfo -> Bool
+-- (beats_or_same ci1 ci2) is True if specialising on ci1 subsumes ci2
+-- That is: ci1's types are less specialised than ci2
+-- ci1 specialises on the same dict args as ci2
beats_or_same (CI { ci_key = args1 }) (CI { ci_key = args2 })
= go args1 args2
where
- go [] _ = True
+ go [] [] = True
go (arg1:args1) (arg2:args2) = go_arg arg1 arg2 && go args1 args2
- go (_:_) [] = False
+
+ -- If one or the other runs dry, the other must still have a SpecDict
+ -- because of the (CI-KEY) invariant. So neither subsumes the other;
+ -- one is more specialised (faster code) but the other is more generally
+ -- applicable.
+ go _ _ = False
go_arg (SpecType ty1) (SpecType ty2) = isJust (tcMatchTy ty1 ty2)
go_arg UnspecType UnspecType = True
=====================================
compiler/GHC/Core/Rules.hs
=====================================
@@ -9,7 +9,7 @@
-- The 'CoreRule' datatype itself is declared elsewhere.
module GHC.Core.Rules (
-- ** Looking up rules
- lookupRule, matchExprs,
+ lookupRule, matchExprs, ruleLhsIsMoreSpecific,
-- ** RuleBase, RuleEnv
RuleBase, RuleEnv(..), mkRuleEnv, emptyRuleEnv,
@@ -587,8 +587,8 @@ findBest :: InScopeSet -> (Id, [CoreExpr])
findBest _ _ (rule,ans) [] = (rule,ans)
findBest in_scope target (rule1,ans1) ((rule2,ans2):prs)
- | isMoreSpecific in_scope rule1 rule2 = findBest in_scope target (rule1,ans1) prs
- | isMoreSpecific in_scope rule2 rule1 = findBest in_scope target (rule2,ans2) prs
+ | ruleIsMoreSpecific in_scope rule1 rule2 = findBest in_scope target (rule1,ans1) prs
+ | ruleIsMoreSpecific in_scope rule2 rule1 = findBest in_scope target (rule2,ans2) prs
| debugIsOn = let pp_rule rule
= ifPprDebug (ppr rule)
(doubleQuotes (ftext (ruleName rule)))
@@ -603,15 +603,25 @@ findBest in_scope target (rule1,ans1) ((rule2,ans2):prs)
where
(fn,args) = target
-isMoreSpecific :: InScopeSet -> CoreRule -> CoreRule -> Bool
--- The call (rule1 `isMoreSpecific` rule2)
+ruleIsMoreSpecific :: InScopeSet -> CoreRule -> CoreRule -> Bool
+-- The call (rule1 `ruleIsMoreSpecific` rule2)
-- sees if rule2 can be instantiated to look like rule1
--- See Note [isMoreSpecific]
-isMoreSpecific _ (BuiltinRule {}) _ = False
-isMoreSpecific _ (Rule {}) (BuiltinRule {}) = True
-isMoreSpecific in_scope (Rule { ru_bndrs = bndrs1, ru_args = args1 })
- (Rule { ru_bndrs = bndrs2, ru_args = args2 })
- = isJust (matchExprs in_scope_env bndrs2 args2 args1)
+-- See Note [ruleIsMoreSpecific]
+ruleIsMoreSpecific in_scope rule1 rule2
+ = case rule1 of
+ BuiltinRule {} -> False
+ Rule { ru_bndrs = bndrs1, ru_args = args1 }
+ -> ruleLhsIsMoreSpecific in_scope bndrs1 args1 rule2
+
+ruleLhsIsMoreSpecific :: InScopeSet
+ -> [Var] -> [CoreExpr] -- LHS of a possible new rule
+ -> CoreRule -- An existing rule
+ -> Bool -- New one is more specific
+ruleLhsIsMoreSpecific in_scope bndrs1 args1 rule2
+ = case rule2 of
+ BuiltinRule {} -> True
+ Rule { ru_bndrs = bndrs2, ru_args = args2 }
+ -> isJust (matchExprs in_scope_env bndrs2 args2 args1)
where
full_in_scope = in_scope `extendInScopeSetList` bndrs1
in_scope_env = ISE full_in_scope noUnfoldingFun
@@ -620,9 +630,9 @@ isMoreSpecific in_scope (Rule { ru_bndrs = bndrs1, ru_args = args1 })
noBlackList :: Activation -> Bool
noBlackList _ = False -- Nothing is black listed
-{- Note [isMoreSpecific]
+{- Note [ruleIsMoreSpecific]
~~~~~~~~~~~~~~~~~~~~~~~~
-The call (rule1 `isMoreSpecific` rule2)
+The call (rule1 `ruleIsMoreSpecific` rule2)
sees if rule2 can be instantiated to look like rule1.
Wrinkle:
@@ -825,7 +835,7 @@ bound on the LHS:
The rule looks like
forall (a::*) (d::Eq Char) (x :: Foo a Char).
- f (Foo a Char) d x = True
+ f @(Foo a Char) d x = True
Matching the rule won't bind 'a', and legitimately so. We fudge by
pretending that 'a' is bound to (Any :: *).
=====================================
compiler/GHC/Core/Unify.hs
=====================================
@@ -331,35 +331,57 @@ Wrinkles
`DontBindMe`, the unifier must return `SurelyApart`, not `MaybeApart`. See
`go_fam` in `uVarOrFam`
-(ATF6) You might think that when /matching/ the um_fam_env will always be empty,
- because type-class-instance and type-family-instance heads can't include type
- families. E.g. instance C (F a) where ... -- Illegal
-
- But you'd be wrong: when "improving" type family constraint we may have a
- type family on the LHS of a match. Consider
+(ATF6) When /matching/ can we ever have a type-family application on the LHS, in
+ the template? You might think not, because type-class-instance and
+ type-family-instance heads can't include type families. E.g.
+ instance C (F a) where ... -- Illegal
+
+ But you'd be wrong: even when matching, we can see type families in the LHS template:
+ * In `checkValidClass`, in `check_dm` we check that the default method has the
+ right type, using matching, both ways. And that type may have type-family
+ applications in it. Example in test CoOpt_Singletons.
+
+ * In the specialiser: see the call to `tcMatchTy` in
+ `GHC.Core.Opt.Specialise.beats_or_same`
+
+ * With -fpolymorphic-specialsation, we might get a specialiation rule like
+ RULE forall a (d :: Eq (Maybe (F a))) .
+ f @(Maybe (F a)) d = ...
+ See #25965.
+
+ * A user-written RULE could conceivably have a type-family application
+ in the template. It might not be a good rule, but I don't think we currently
+ check for this.
+
+ In all these cases we are only interested in finding a substitution /for
+ type variables/ that makes the match work. So we simply want to recurse into
+ the arguments of the type family. E.g.
+ Template: forall a. Maybe (F a)
+ Target: Mabybe (F Int)
+ We want to succeed with substitution [a :-> Int]. See (ATF9).
+
+ Conclusion: where we enter via `tcMatchTy`, `tcMatchTys`, `tc_match_tys`,
+ etc, we always end up in `tc_match_tys_x`. There we invoke the unifier
+ but we do not distinguish between `SurelyApart` and `MaybeApart`. So in
+ these cases we can set `um_bind_fam_fun` to `neverBindFam`.
+
+(ATF7) There is one other, very special case of matching where we /do/ want to
+ bind type families in `um_fam_env`, namely in GHC.Tc.Solver.Equality, the call
+ to `tcUnifyTyForInjectivity False` in `improve_injective_wanted_top`.
+ Consider
+ of a match. Consider
type family G6 a = r | r -> a
type instance G6 [a] = [G a]
type instance G6 Bool = Int
- and the Wanted constraint [W] G6 alpha ~ [Int]. We /match/ each type instance
- RHS against [Int]! So we try
- [G a] ~ [Int]
+ and suppose we haev a Wanted constraint
+ [W] G6 alpha ~ [Int]
+. According to Section 5.2 of "Injective type families for Haskell", we /match/
+ the RHS each type instance [Int]. So we try
+ Template: [G a] Target: [Int]
and we want to succeed with MaybeApart, so that we can generate the improvement
- constraint [W] alpha ~ [beta] where beta is fresh.
- See Section 5.2 of "Injective type families for Haskell".
-
- A second place that we match with type-fams on the LHS is in `checkValidClass`.
- In `check_dm` we check that the default method has the right type, using matching,
- both ways. And that type may have type-family applications in it. Example in
- test CoOpt_Singletons.
-
-(ATF7) You might think that (ATF6) is a very special case, and in /other/ uses of
- matching, where we enter via `tc_match_tys_x` we will never see a type-family
- in the template. But actually we do see that case in the specialiser: see
- the call to `tcMatchTy` in `GHC.Core.Opt.Specialise.beats_or_same`
-
- Also: a user-written RULE could conceivably have a type-family application
- in the template. It might not be a good rule, but I don't think we currently
- check for this.
+ constraint
+ [W] alpha ~ [beta]
+ where beta is fresh. We do this by binding [G a :-> Int]
(ATF8) The treatment of type families is governed by
um_bind_fam_fun :: BindFamFun
@@ -399,6 +421,8 @@ Wrinkles
Key point: when decomposing (F tys1 ~ F tys2), we should /also/ extend the
type-family substitution.
+ (ATF11-1) All this cleverness only matters when unifying, not when matching
+
(ATF12) There is a horrid exception for the injectivity check. See (UR1) in
in Note [Specification of unification].
@@ -595,7 +619,7 @@ tc_match_tys_x :: HasDebugCallStack
-> [Type]
-> Maybe Subst
tc_match_tys_x bind_tv match_kis (Subst in_scope id_env tv_env cv_env) tys1 tys2
- = case tc_unify_tys alwaysBindFam -- (ATF7) in Note [Apartness and type families]
+ = case tc_unify_tys neverBindFam -- (ATF7) in Note [Apartness and type families]
bind_tv
False -- Matching, not unifying
False -- Not an injectivity check
@@ -1857,6 +1881,7 @@ uVarOrFam env ty1 ty2 kco
= go_fam_fam tc1 tys1 tys2 kco
-- Now check if we can bind the (F tys) to the RHS
+ -- This can happen even when matching: see (ATF7)
| BindMe <- um_bind_fam_fun env tc1 tys1 rhs
= -- ToDo: do we need an occurs check here?
do { extendFamEnv tc1 tys1 rhs
@@ -1881,11 +1906,6 @@ uVarOrFam env ty1 ty2 kco
-- go_fam_fam: LHS and RHS are both saturated type-family applications,
-- for the same type-family F
go_fam_fam tc tys1 tys2 kco
- | tcEqTyConAppArgs tys1 tys2
- -- Detect (F tys ~ F tys); otherwise we'd build an infinite substitution
- = return ()
-
- | otherwise
-- Decompose (F tys1 ~ F tys2): (ATF9)
-- Use injectivity information of F: (ATF10)
-- But first bind the type-fam if poss: (ATF11)
@@ -1902,13 +1922,19 @@ uVarOrFam env ty1 ty2 kco
(inj_tys1, noninj_tys1) = partitionByList inj tys1
(inj_tys2, noninj_tys2) = partitionByList inj tys2
- bind_fam_if_poss | BindMe <- um_bind_fam_fun env tc tys1 rhs1
- = extendFamEnv tc tys1 rhs1
- | um_unif env
- , BindMe <- um_bind_fam_fun env tc tys2 rhs2
- = extendFamEnv tc tys2 rhs2
- | otherwise
- = return ()
+ bind_fam_if_poss
+ | not (um_unif env) -- Not when matching (ATF11-1)
+ = return ()
+ | tcEqTyConAppArgs tys1 tys2 -- Detect (F tys ~ F tys);
+ = return () -- otherwise we'd build an infinite substitution
+ | BindMe <- um_bind_fam_fun env tc tys1 rhs1
+ = extendFamEnv tc tys1 rhs1
+ | um_unif env
+ , BindMe <- um_bind_fam_fun env tc tys2 rhs2
+ = extendFamEnv tc tys2 rhs2
+ | otherwise
+ = return ()
+
rhs1 = mkTyConApp tc tys2 `mkCastTy` mkSymCo kco
rhs2 = mkTyConApp tc tys1 `mkCastTy` kco
@@ -1993,7 +2019,7 @@ data UMState = UMState
-- in um_foralls; i.e. variables bound by foralls inside the types being unified
-- When /matching/ um_fam_env is usually empty; but not quite always.
- -- See (ATF6) and (ATF7) of Note [Apartness and type families]
+ -- See (ATF7) of Note [Apartness and type families]
newtype UM a
= UM' { unUM :: UMState -> UnifyResultM (UMState, a) }
=====================================
compiler/GHC/Tc/Solver/Equality.hs
=====================================
@@ -3017,6 +3017,7 @@ improve_wanted_top_fun_eqs fam_tc lhs_tys rhs_ty
improve_injective_wanted_top :: FamInstEnvs -> [Bool] -> TyCon -> [TcType] -> Xi -> TcS [TypeEqn]
-- Interact with top-level instance declarations
+-- See Section 5.2 in the Injective Type Families paper
improve_injective_wanted_top fam_envs inj_args fam_tc lhs_tys rhs_ty
= concatMapM do_one branches
where
@@ -3035,6 +3036,7 @@ improve_injective_wanted_top fam_envs inj_args fam_tc lhs_tys rhs_ty
do_one branch@(CoAxBranch { cab_tvs = branch_tvs, cab_lhs = branch_lhs_tys, cab_rhs = branch_rhs })
| let in_scope1 = in_scope `extendInScopeSetList` branch_tvs
, Just subst <- tcUnifyTyForInjectivity False in_scope1 branch_rhs rhs_ty
+ -- False: matching, not unifying
= do { let inSubst tv = tv `elemVarEnv` getTvSubstEnv subst
unsubstTvs = filterOut inSubst branch_tvs
-- The order of unsubstTvs is important; it must be
=====================================
compiler/GHC/Types/Basic.hs
=====================================
@@ -85,7 +85,7 @@ module GHC.Types.Basic (
CompilerPhase(..), PhaseNum, beginPhase, nextPhase, laterPhase,
Activation(..), isActive, competesWith,
- isNeverActive, isAlwaysActive, activeInFinalPhase,
+ isNeverActive, isAlwaysActive, activeInFinalPhase, activeInInitialPhase,
activateAfterInitial, activateDuringFinal, activeAfter,
RuleMatchInfo(..), isConLike, isFunLike,
=====================================
testsuite/tests/simplCore/should_compile/T25703.hs
=====================================
@@ -0,0 +1,7 @@
+module T25703 where
+
+f :: (Eq a, Show b) => a -> b -> Int
+f x y = f x y
+
+goo :: forall x. (Eq x) => x -> Int
+goo arg = f arg (3::Int)
=====================================
testsuite/tests/simplCore/should_compile/T25703.stderr
=====================================
@@ -0,0 +1,2 @@
+Rule fired: SPEC f @_ @Int (T25703)
+Rule fired: SPEC f @_ @Int (T25703)
=====================================
testsuite/tests/simplCore/should_compile/T25703a..stderr
=====================================
@@ -0,0 +1,6 @@
+Rule fired: SPEC foo @_ @2 @2 (T25703a)
+Rule fired: SPEC foo @_ @1 @2 (T25703a)
+Rule fired: SPEC foo @_ @0 @2 (T25703a)
+Rule fired: SPEC foo @_ @1 @1 (T25703a)
+Rule fired: SPEC foo @_ @0 @1 (T25703a)
+Rule fired: SPEC foo @_ @0 @0 (T25703a)
=====================================
testsuite/tests/simplCore/should_compile/T25703a.hs
=====================================
@@ -0,0 +1,69 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE GADTs #-}
+
+{-# OPTIONS_GHC -O2 -fspecialise-aggressively #-}
+
+-- This pragma is just here to pretend that the function body of 'foo' is huge
+-- and should never be inlined.
+{-# OPTIONS_GHC -funfolding-use-threshold=-200 #-}
+
+module T25703a where
+
+import Data.Kind
+import Data.Type.Equality
+import Data.Proxy
+import GHC.TypeNats
+
+-- Pretend this is some big dictionary that absolutely must get
+-- specialised away for performance reasons.
+type C :: Nat -> Constraint
+class C i where
+ meth :: Proxy i -> Double
+instance C 0 where
+ meth _ = 0.1
+instance C 1 where
+ meth _ = 1.1
+instance C 2 where
+ meth _ = 2.1
+
+{-# INLINEABLE foo #-}
+foo :: forall a (n :: Nat) (m :: Nat)
+ . ( Eq a, C n, C m )
+ => a -> ( Proxy n, Proxy m ) -> Int -> Double
+-- Pretend this is a big complicated function, too big to inline,
+-- for which we absolutely must specialise away the 'C n', 'C m'
+-- dictionaries for performance reasons.
+foo a b c
+ = if a == a
+ then meth @n Proxy + fromIntegral c
+ else 2 * meth @m Proxy
+
+-- Runtime dispatch to a specialisation of 'foo'
+foo_spec :: forall a (n :: Nat) (m :: Nat)
+ . ( Eq a, KnownNat n, KnownNat m )
+ => a -> ( Proxy n, Proxy m ) -> Int -> Double
+foo_spec a b c
+ | Just Refl <- sameNat @n @0 Proxy Proxy
+ , Just Refl <- sameNat @m @0 Proxy Proxy
+ = foo @a @0 @0 a b c
+ | Just Refl <- sameNat @n @0 Proxy Proxy
+ , Just Refl <- sameNat @m @1 Proxy Proxy
+ = foo @a @0 @1 a b c
+ | Just Refl <- sameNat @n @1 Proxy Proxy
+ , Just Refl <- sameNat @m @1 Proxy Proxy
+ = foo @a @1 @1 a b c
+ | Just Refl <- sameNat @n @0 Proxy Proxy
+ , Just Refl <- sameNat @m @2 Proxy Proxy
+ = foo @a @0 @2 a b c
+ | Just Refl <- sameNat @n @1 Proxy Proxy
+ , Just Refl <- sameNat @m @2 Proxy Proxy
+ = foo @a @1 @2 a b c
+ | Just Refl <- sameNat @n @2 Proxy Proxy
+ , Just Refl <- sameNat @m @2 Proxy Proxy
+ = foo @a @2 @2 a b c
+ | otherwise
+ = error $ unlines
+ [ "f: no specialisation"
+ , "n: " ++ show (natVal @n Proxy)
+ , "m: " ++ show (natVal @m Proxy)
+ ]
=====================================
testsuite/tests/simplCore/should_compile/T25965.hs
=====================================
@@ -0,0 +1,18 @@
+{-# LANGUAGE TypeFamilies #-}
+{-# OPTIONS_GHC -O -fpolymorphic-specialisation #-}
+
+module Foo where
+
+type family F a
+
+data T a = T1
+
+instance Eq (T a) where { (==) x y = False }
+
+foo :: Eq a => a -> Bool
+foo x | x==x = True
+ | otherwise = foo x
+
+bar :: forall b. b -> T (F b) -> Bool
+bar y x = foo x
+
=====================================
testsuite/tests/simplCore/should_compile/all.T
=====================================
@@ -535,3 +535,6 @@ test('T25197', [req_th, extra_files(["T25197_TH.hs"]), only_ways(['optasm'])], m
test('T25389', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dno-typeable-binds'])
test('T24359a', normal, compile, ['-O -ddump-rules'])
test('T25713', [grep_errmsg('W:::')], compile, ['-O -ddump-simpl'])
+test('T25965', normal, compile, ['-O'])
+test('T25703', [grep_errmsg(r'SPEC')], compile, ['-O -fpolymorphic-specialisation -ddump-rule-firings'])
+test('T25703a', [grep_errmsg(r'SPEC')], compile, ['-O -fpolymorphic-specialisation -ddump-rule-firings'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/e00129b912a2eb100a29d706c759e95…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/e00129b912a2eb100a29d706c759e95…
You're receiving this email because of your account on gitlab.haskell.org.
1
0

[Git][ghc/ghc][wip/andreask/interpreter_primops] 4 commits: Fix unary ops
by Andreas Klebinger (@AndreasK) 16 Apr '25
by Andreas Klebinger (@AndreasK) 16 Apr '25
16 Apr '25
Andreas Klebinger pushed to branch wip/andreask/interpreter_primops at Glasgow Haskell Compiler / GHC
Commits:
1f7f27e6 by Andreas Klebinger at 2025-04-14T23:22:13+02:00
Fix unary ops
- - - - -
903e5965 by Andreas Klebinger at 2025-04-15T00:02:58+02:00
Fix shift op
- - - - -
a6e0e0aa by Andreas Klebinger at 2025-04-16T20:12:18+02:00
Fix macro parantheses
- - - - -
12f61803 by Andreas Klebinger at 2025-04-17T00:09:50+02:00
More testing
- - - - -
6 changed files:
- compiler/GHC/Builtin/primops.txt.pp
- compiler/GHC/StgToByteCode.hs
- rts/Interpreter.c
- testsuite/tests/numeric/should_run/foundation.hs
- utils/genprimopcode/Main.hs
- utils/genprimopcode/Syntax.hs
Changes:
=====================================
compiler/GHC/Builtin/primops.txt.pp
=====================================
@@ -147,6 +147,7 @@ defaults
fixity = Nothing
vector = []
deprecated_msg = {} -- A non-empty message indicates deprecation
+ div_like = False -- Second argument expected to be non zero - used for tests
-- Note [When do out-of-line primops go in primops.txt.pp]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -296,14 +297,18 @@ primop Int8MulOp "timesInt8#" GenPrimOp Int8# -> Int8# -> Int8#
primop Int8QuotOp "quotInt8#" GenPrimOp Int8# -> Int8# -> Int8#
with
effect = CanFail
+ div_like = True
primop Int8RemOp "remInt8#" GenPrimOp Int8# -> Int8# -> Int8#
with
effect = CanFail
+ div_like = True
+
primop Int8QuotRemOp "quotRemInt8#" GenPrimOp Int8# -> Int8# -> (# Int8#, Int8# #)
with
effect = CanFail
+ div_like = True
primop Int8SllOp "uncheckedShiftLInt8#" GenPrimOp Int8# -> Int# -> Int8#
primop Int8SraOp "uncheckedShiftRAInt8#" GenPrimOp Int8# -> Int# -> Int8#
@@ -342,14 +347,17 @@ primop Word8MulOp "timesWord8#" GenPrimOp Word8# -> Word8# -> Word8#
primop Word8QuotOp "quotWord8#" GenPrimOp Word8# -> Word8# -> Word8#
with
effect = CanFail
+ div_like = True
primop Word8RemOp "remWord8#" GenPrimOp Word8# -> Word8# -> Word8#
with
effect = CanFail
+ div_like = True
primop Word8QuotRemOp "quotRemWord8#" GenPrimOp Word8# -> Word8# -> (# Word8#, Word8# #)
with
effect = CanFail
+ div_like = True
primop Word8AndOp "andWord8#" GenPrimOp Word8# -> Word8# -> Word8#
with commutable = True
@@ -400,14 +408,17 @@ primop Int16MulOp "timesInt16#" GenPrimOp Int16# -> Int16# -> Int16#
primop Int16QuotOp "quotInt16#" GenPrimOp Int16# -> Int16# -> Int16#
with
effect = CanFail
+ div_like = True
primop Int16RemOp "remInt16#" GenPrimOp Int16# -> Int16# -> Int16#
with
effect = CanFail
+ div_like = True
primop Int16QuotRemOp "quotRemInt16#" GenPrimOp Int16# -> Int16# -> (# Int16#, Int16# #)
with
effect = CanFail
+ div_like = True
primop Int16SllOp "uncheckedShiftLInt16#" GenPrimOp Int16# -> Int# -> Int16#
primop Int16SraOp "uncheckedShiftRAInt16#" GenPrimOp Int16# -> Int# -> Int16#
@@ -446,14 +457,17 @@ primop Word16MulOp "timesWord16#" GenPrimOp Word16# -> Word16# -> Word16#
primop Word16QuotOp "quotWord16#" GenPrimOp Word16# -> Word16# -> Word16#
with
effect = CanFail
+ div_like = True
primop Word16RemOp "remWord16#" GenPrimOp Word16# -> Word16# -> Word16#
with
effect = CanFail
+ div_like = True
primop Word16QuotRemOp "quotRemWord16#" GenPrimOp Word16# -> Word16# -> (# Word16#, Word16# #)
with
effect = CanFail
+ div_like = True
primop Word16AndOp "andWord16#" GenPrimOp Word16# -> Word16# -> Word16#
with commutable = True
@@ -504,14 +518,17 @@ primop Int32MulOp "timesInt32#" GenPrimOp Int32# -> Int32# -> Int32#
primop Int32QuotOp "quotInt32#" GenPrimOp Int32# -> Int32# -> Int32#
with
effect = CanFail
+ div_like = True
primop Int32RemOp "remInt32#" GenPrimOp Int32# -> Int32# -> Int32#
with
effect = CanFail
+ div_like = True
primop Int32QuotRemOp "quotRemInt32#" GenPrimOp Int32# -> Int32# -> (# Int32#, Int32# #)
with
effect = CanFail
+ div_like = True
primop Int32SllOp "uncheckedShiftLInt32#" GenPrimOp Int32# -> Int# -> Int32#
primop Int32SraOp "uncheckedShiftRAInt32#" GenPrimOp Int32# -> Int# -> Int32#
@@ -550,14 +567,17 @@ primop Word32MulOp "timesWord32#" GenPrimOp Word32# -> Word32# -> Word32#
primop Word32QuotOp "quotWord32#" GenPrimOp Word32# -> Word32# -> Word32#
with
effect = CanFail
+ div_like = True
primop Word32RemOp "remWord32#" GenPrimOp Word32# -> Word32# -> Word32#
with
effect = CanFail
+ div_like = True
primop Word32QuotRemOp "quotRemWord32#" GenPrimOp Word32# -> Word32# -> (# Word32#, Word32# #)
with
effect = CanFail
+ div_like = True
primop Word32AndOp "andWord32#" GenPrimOp Word32# -> Word32# -> Word32#
with commutable = True
@@ -608,10 +628,12 @@ primop Int64MulOp "timesInt64#" GenPrimOp Int64# -> Int64# -> Int64#
primop Int64QuotOp "quotInt64#" GenPrimOp Int64# -> Int64# -> Int64#
with
effect = CanFail
+ div_like = True
primop Int64RemOp "remInt64#" GenPrimOp Int64# -> Int64# -> Int64#
with
effect = CanFail
+ div_like = True
primop Int64SllOp "uncheckedIShiftL64#" GenPrimOp Int64# -> Int# -> Int64#
primop Int64SraOp "uncheckedIShiftRA64#" GenPrimOp Int64# -> Int# -> Int64#
@@ -650,10 +672,12 @@ primop Word64MulOp "timesWord64#" GenPrimOp Word64# -> Word64# -> Word64#
primop Word64QuotOp "quotWord64#" GenPrimOp Word64# -> Word64# -> Word64#
with
effect = CanFail
+ div_like = True
primop Word64RemOp "remWord64#" GenPrimOp Word64# -> Word64# -> Word64#
with
effect = CanFail
+ div_like = True
primop Word64AndOp "and64#" GenPrimOp Word64# -> Word64# -> Word64#
with commutable = True
@@ -737,6 +761,7 @@ primop IntQuotOp "quotInt#" GenPrimOp
zero.
}
with effect = CanFail
+ div_like = True
primop IntRemOp "remInt#" GenPrimOp
Int# -> Int# -> Int#
@@ -744,11 +769,13 @@ primop IntRemOp "remInt#" GenPrimOp
behavior is undefined if the second argument is zero.
}
with effect = CanFail
+ div_like = True
primop IntQuotRemOp "quotRemInt#" GenPrimOp
Int# -> Int# -> (# Int#, Int# #)
{Rounds towards zero.}
with effect = CanFail
+ div_like = True
primop IntAndOp "andI#" GenPrimOp Int# -> Int# -> Int#
{Bitwise "and".}
@@ -886,19 +913,23 @@ primop WordMul2Op "timesWord2#" GenPrimOp
primop WordQuotOp "quotWord#" GenPrimOp Word# -> Word# -> Word#
with effect = CanFail
+ div_like = True
primop WordRemOp "remWord#" GenPrimOp Word# -> Word# -> Word#
with effect = CanFail
+ div_like = True
primop WordQuotRemOp "quotRemWord#" GenPrimOp
Word# -> Word# -> (# Word#, Word# #)
with effect = CanFail
+ div_like = True
primop WordQuotRem2Op "quotRemWord2#" GenPrimOp
Word# -> Word# -> Word# -> (# Word#, Word# #)
{ Takes high word of dividend, then low word of dividend, then divisor.
Requires that high word < divisor.}
with effect = CanFail
+ div_like = True
primop WordAndOp "and#" GenPrimOp Word# -> Word# -> Word#
with commutable = True
@@ -4166,6 +4197,7 @@ primop VecQuotOp "quot#" GenPrimOp
Do not expect high performance. }
with effect = CanFail
vector = INT_VECTOR_TYPES
+ div_like = True
primop VecRemOp "rem#" GenPrimOp
VECTOR -> VECTOR -> VECTOR
@@ -4175,6 +4207,8 @@ primop VecRemOp "rem#" GenPrimOp
Do not expect high performance. }
with effect = CanFail
vector = INT_VECTOR_TYPES
+ div_like = True
+
primop VecNegOp "negate#" GenPrimOp
VECTOR -> VECTOR
=====================================
compiler/GHC/StgToByteCode.hs
=====================================
@@ -857,6 +857,17 @@ doPrimOp platform op init_d s p args =
Word8AddOp -> sizedPrimOp OP_ADD
AddrAddOp -> sizedPrimOp OP_ADD
+ IntMulOp -> sizedPrimOp OP_MUL
+ Int64MulOp -> sizedPrimOp OP_MUL
+ Int32MulOp -> sizedPrimOp OP_MUL
+ Int16MulOp -> sizedPrimOp OP_MUL
+ Int8MulOp -> sizedPrimOp OP_MUL
+ WordMulOp -> sizedPrimOp OP_MUL
+ Word64MulOp -> sizedPrimOp OP_MUL
+ Word32MulOp -> sizedPrimOp OP_MUL
+ Word16MulOp -> sizedPrimOp OP_MUL
+ Word8MulOp -> sizedPrimOp OP_MUL
+
IntSubOp -> sizedPrimOp OP_SUB
WordSubOp -> sizedPrimOp OP_SUB
Int64SubOp -> sizedPrimOp OP_SUB
@@ -1009,20 +1020,20 @@ doPrimOp platform op init_d s p args =
Int16NegOp -> sizedPrimOp OP_NEG
Int8NegOp -> sizedPrimOp OP_NEG
- IntToWordOp -> no_op
- WordToIntOp -> no_op
- Int8ToWord8Op -> no_op
- Word8ToInt8Op -> no_op
- Int16ToWord16Op -> no_op
- Word16ToInt16Op -> no_op
- Int32ToWord32Op -> no_op
- Word32ToInt32Op -> no_op
- Int64ToWord64Op -> no_op
- Word64ToInt64Op -> no_op
- IntToAddrOp -> no_op
- AddrToIntOp -> no_op
- ChrOp -> no_op -- Int# and Char# are rep'd the same
- OrdOp -> no_op
+ IntToWordOp -> mk_conv (platformWordWidth platform)
+ WordToIntOp -> mk_conv (platformWordWidth platform)
+ Int8ToWord8Op -> mk_conv W8
+ Word8ToInt8Op -> mk_conv W8
+ Int16ToWord16Op -> mk_conv W16
+ Word16ToInt16Op -> mk_conv W16
+ Int32ToWord32Op -> mk_conv W32
+ Word32ToInt32Op -> mk_conv W32
+ Int64ToWord64Op -> mk_conv W64
+ Word64ToInt64Op -> mk_conv W64
+ IntToAddrOp -> mk_conv (platformWordWidth platform)
+ AddrToIntOp -> mk_conv (platformWordWidth platform)
+ ChrOp -> mk_conv (platformWordWidth platform) -- Int# and Char# are rep'd the same
+ OrdOp -> mk_conv (platformWordWidth platform)
IndexOffAddrOp_Word8 -> primOpWithRep (OP_INDEX_ADDR W8) W8
IndexOffAddrOp_Word16 -> primOpWithRep (OP_INDEX_ADDR W16) W16
@@ -1031,6 +1042,7 @@ doPrimOp platform op init_d s p args =
_ -> Nothing
where
+ primArg1Width :: StgArg -> Width
primArg1Width arg
| rep <- (stgArgRepU arg)
= case rep of
@@ -1080,43 +1092,12 @@ doPrimOp platform op init_d s p args =
let slide = mkSlideW (mkSlideWords width) (bytesToWords platform $ init_d - s) `snocOL` mkNReturn width
return $ prim_code `appOL` slide
- no_op = Just $ do
+ mk_conv :: Width -> Maybe (BcM (OrdList BCInstr))
+ mk_conv target_width = Just $ do
let width = primArg1Width (head args)
- prim_code <- terribleNoOp init_d s p undefined args
- let slide = mkSlideW (mkSlideWords width) (bytesToWords platform $ init_d - s) `snocOL` mkNReturn width
- return $ prim_code `appOL` slide
-
--- It's horrible, but still better than calling intToWord ...
-terribleNoOp
- :: StackDepth
- -> Sequel
- -> BCEnv
- -> BCInstr -- The operator
- -> [StgArg] -- Args, in *reverse* order (must be fully applied)
- -> BcM BCInstrList
-terribleNoOp orig_d _ p _ args = app_code
- where
- app_code = do
- profile <- getProfile
- let --platform = profilePlatform profile
-
- non_voids =
- addArgReps (assertNonVoidStgArgs args)
- (_, _, args_offsets) =
- mkVirtHeapOffsetsWithPadding profile StdHeader non_voids
-
- do_pushery !d (arg : args) = do
- (push, arg_bytes) <- case arg of
- (Padding l _) -> return $! pushPadding (ByteOff l)
- (FieldOff a _) -> pushConstrAtom d p (fromNonVoid a)
- more_push_code <- do_pushery (d + arg_bytes) args
- return (push `appOL` more_push_code)
- do_pushery !_d [] = do
- -- let !n_arg_words = bytesToWords platform (d - orig_d)
- return (nilOL)
-
- -- Push on the stack in the reverse order.
- do_pushery orig_d (reverse args_offsets)
+ (push_code, _bytes) <- pushAtom init_d p (head args)
+ let slide = mkSlideW (mkSlideWords width) (bytesToWords platform $ init_d - s) `snocOL` mkNReturn target_width
+ return $ push_code `appOL` slide
-- Push the arguments on the stack and emit the given instruction
-- Pushes one word per non void arg.
=====================================
rts/Interpreter.c
=====================================
@@ -249,9 +249,9 @@ See ticket #25750
#define SafeSpWP(n) \
((StgWord*) ((WITHIN_CAP_CHUNK_BOUNDS_W(n)) ? Sp_plusW(n) : slow_spw(Sp, cap->r.rCurrentTSO->stackobj, n)))
#define SafeSpBP(off_w) \
- ( (StgWord*) (WITHIN_CAP_CHUNK_BOUNDS_W(1+off_w/sizeof(StgWord))) ? \
+ ( (StgWord*) (WITHIN_CAP_CHUNK_BOUNDS_W((1+(off_w))/sizeof(StgWord))) ? \
Sp_plusB(off_w) : \
- (StgWord*) ((ptrdiff_t)(off_w % sizeof(StgWord)) + (StgWord8*)slow_spw(Sp, cap->r.rCurrentTSO->stackobj, off_w/sizeof(StgWord))))
+ (StgWord*) ((ptrdiff_t)((off_w) % sizeof(StgWord)) + (StgWord8*)slow_spw(Sp, cap->r.rCurrentTSO->stackobj, (off_w)/sizeof(StgWord))))
@@ -2270,10 +2270,10 @@ run_BCO:
#define UN_SIZED_OP(op,ty) \
{ \
if(sizeof(ty) == 8) { \
- ty r = op (*(ty*) ReadSpW64(0)); \
+ ty r = op ((ty) ReadSpW64(0)); \
SpW64(0) = (StgWord64) r; \
} else { \
- ty r = op (*(ty*) ReadSpW(0)); \
+ ty r = op ((ty) ReadSpW(0)); \
SpW(0) = (StgWord) r; \
} \
goto nextInsn; \
@@ -2293,15 +2293,30 @@ run_BCO:
goto nextInsn; \
}
+// op :: ty -> Int -> ty
+#define SIZED_BIN_OP_TY_INT(op,ty) \
+{ \
+ if(sizeof(ty) > sizeof(StgWord)) { \
+ ty r = ((ty) ReadSpW64(0)) op ((ty) ReadSpW(2)); \
+ Sp_addW(1); \
+ SpW64(0) = (StgWord64) r; \
+ } else { \
+ ty r = ((ty) ReadSpW(0)) op ((ty) ReadSpW(1)); \
+ Sp_addW(1); \
+ SpW(0) = (StgWord) r; \
+ }; \
+ goto nextInsn; \
+}
+
case bci_OP_ADD_64: SIZED_BIN_OP(+, StgInt64)
case bci_OP_SUB_64: SIZED_BIN_OP(-, StgInt64)
case bci_OP_AND_64: SIZED_BIN_OP(&, StgInt64)
case bci_OP_XOR_64: SIZED_BIN_OP(^, StgInt64)
case bci_OP_OR_64: SIZED_BIN_OP(|, StgInt64)
case bci_OP_MUL_64: SIZED_BIN_OP(*, StgInt64)
- case bci_OP_SHL_64: SIZED_BIN_OP(<<, StgWord64)
- case bci_OP_LSR_64: SIZED_BIN_OP(>>, StgWord64)
- case bci_OP_ASR_64: SIZED_BIN_OP(>>, StgInt64)
+ case bci_OP_SHL_64: SIZED_BIN_OP_TY_INT(<<, StgWord64)
+ case bci_OP_LSR_64: SIZED_BIN_OP_TY_INT(>>, StgWord64)
+ case bci_OP_ASR_64: SIZED_BIN_OP_TY_INT(>>, StgInt64)
case bci_OP_NEQ_64: SIZED_BIN_OP(!=, StgWord64)
case bci_OP_EQ_64: SIZED_BIN_OP(==, StgWord64)
@@ -2325,9 +2340,9 @@ run_BCO:
case bci_OP_XOR_32: SIZED_BIN_OP(^, StgInt32)
case bci_OP_OR_32: SIZED_BIN_OP(|, StgInt32)
case bci_OP_MUL_32: SIZED_BIN_OP(*, StgInt32)
- case bci_OP_SHL_32: SIZED_BIN_OP(<<, StgWord32)
- case bci_OP_LSR_32: SIZED_BIN_OP(>>, StgWord32)
- case bci_OP_ASR_32: SIZED_BIN_OP(>>, StgInt32)
+ case bci_OP_SHL_32: SIZED_BIN_OP_TY_INT(<<, StgWord32)
+ case bci_OP_LSR_32: SIZED_BIN_OP_TY_INT(>>, StgWord32)
+ case bci_OP_ASR_32: SIZED_BIN_OP_TY_INT(>>, StgInt32)
case bci_OP_NEQ_32: SIZED_BIN_OP(!=, StgWord32)
case bci_OP_EQ_32: SIZED_BIN_OP(==, StgWord32)
@@ -2351,9 +2366,9 @@ run_BCO:
case bci_OP_XOR_16: SIZED_BIN_OP(^, StgInt16)
case bci_OP_OR_16: SIZED_BIN_OP(|, StgInt16)
case bci_OP_MUL_16: SIZED_BIN_OP(*, StgInt16)
- case bci_OP_SHL_16: SIZED_BIN_OP(<<, StgWord16)
- case bci_OP_LSR_16: SIZED_BIN_OP(>>, StgWord16)
- case bci_OP_ASR_16: SIZED_BIN_OP(>>, StgInt16)
+ case bci_OP_SHL_16: SIZED_BIN_OP_TY_INT(<<, StgWord16)
+ case bci_OP_LSR_16: SIZED_BIN_OP_TY_INT(>>, StgWord16)
+ case bci_OP_ASR_16: SIZED_BIN_OP_TY_INT(>>, StgInt16)
case bci_OP_NEQ_16: SIZED_BIN_OP(!=, StgWord16)
case bci_OP_EQ_16: SIZED_BIN_OP(==, StgWord16)
@@ -2377,9 +2392,9 @@ run_BCO:
case bci_OP_XOR_08: SIZED_BIN_OP(^, StgInt8)
case bci_OP_OR_08: SIZED_BIN_OP(|, StgInt8)
case bci_OP_MUL_08: SIZED_BIN_OP(*, StgInt8)
- case bci_OP_SHL_08: SIZED_BIN_OP(<<, StgWord8)
- case bci_OP_LSR_08: SIZED_BIN_OP(>>, StgWord8)
- case bci_OP_ASR_08: SIZED_BIN_OP(>>, StgInt8)
+ case bci_OP_SHL_08: SIZED_BIN_OP_TY_INT(<<, StgWord8)
+ case bci_OP_LSR_08: SIZED_BIN_OP_TY_INT(>>, StgWord8)
+ case bci_OP_ASR_08: SIZED_BIN_OP_TY_INT(>>, StgInt8)
case bci_OP_NEQ_08: SIZED_BIN_OP(!=, StgWord8)
case bci_OP_EQ_08: SIZED_BIN_OP(==, StgWord8)
=====================================
testsuite/tests/numeric/should_run/foundation.hs
=====================================
@@ -1,3 +1,8 @@
+{- PARTS OF THIS FILE ARE SEMI-AUTOGENERATED.
+ You can re-generate them by invoking the genprimops utility with --foundation-tests
+ and then integrating the output in this file.
+-}
+
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
@@ -19,6 +24,7 @@ import Data.Typeable
import Data.Proxy
import GHC.Int
import GHC.Word
+import GHC.Word
import Data.Function
import GHC.Prim
import Control.Monad.Reader
@@ -108,6 +114,17 @@ arbitraryWord64 = Gen $ do
h <- ask
liftIO (randomWord64 h)
+nonZero :: (Arbitrary a, Num a, Eq a) => Gen (NonZero a)
+nonZero = do
+ x <- arbitrary
+ if x == 0 then nonZero else pure $ NonZero x
+
+newtype NonZero a = NonZero { getNonZero :: a }
+ deriving (Eq,Ord,Bounded,Show)
+
+instance (Arbitrary a, Num a, Eq a) => Arbitrary (NonZero a) where
+ arbitrary = nonZero
+
instance Arbitrary Natural where
arbitrary = integralDownsize . (`mod` 10000) . abs <$> arbitraryInt64
@@ -138,10 +155,10 @@ instance Arbitrary Int8 where
instance Arbitrary Char where
arbitrary = do
- let low = fromEnum (minBound :: Char)
- high = fromEnum (maxBound :: Char)
- x <- arbitrary
- if x >= low && x <= high then return (chr x) else arbitrary
+ let high = fromIntegral $ fromEnum (maxBound :: Char) :: Word
+ (x::Word) <- arbitrary
+ let x' = mod x high
+ return (chr $ fromIntegral x')
int64ToInt :: Int64 -> Int
int64ToInt (I64# i) = I# (int64ToInt# i)
@@ -277,9 +294,8 @@ testMultiplicative _ = Group "Multiplicative"
testDividible :: forall a . (Show a, Eq a, Integral a, Num a, Arbitrary a, Typeable a)
=> Proxy a -> Test
testDividible _ = Group "Divisible"
- [ Property "(x `div` y) * y + (x `mod` y) == x" $ \(a :: a) b ->
- if b == 0 then True === True
- else a === (a `div` b) * b + (a `mod` b)
+ [ Property "(x `div` y) * y + (x `mod` y) == x" $ \(a :: a) (NonZero b) ->
+ a === (a `div` b) * b + (a `mod` b)
]
testOperatorPrecedence :: forall a . (Show a, Eq a, Prelude.Num a, Integral a, Num a, Arbitrary a, Typeable a)
@@ -368,6 +384,9 @@ wInt64# = I64#
class TestPrimop f where
testPrimop :: String -> f -> f -> Test
+ testPrimopDivLike :: String -> f -> f -> Test
+ testPrimopDivLike _ _ _ = error "Div testing not supported for this type."
+
{-
instance TestPrimop (Int# -> Int# -> Int#) where
testPrimop s l r = Property s $ \(uInt -> a1) (uInt -> a2) -> (wInt (l a1 a2)) === wInt (r a1 a2)
@@ -383,6 +402,9 @@ instance TestPrimop (Word# -> Int# -> Word#) where
-}
+twoNonZero :: (a -> a -> b) -> a -> NonZero a -> b
+twoNonZero f x (NonZero y) = f x y
+
main = runTests (Group "ALL" [testNumberRefs, testPrimops])
-- Test an interpreted primop vs a compiled primop
@@ -400,9 +422,9 @@ testPrimops = Group "primop"
, testPrimop "plusInt8#" Primop.plusInt8# Wrapper.plusInt8#
, testPrimop "subInt8#" Primop.subInt8# Wrapper.subInt8#
, testPrimop "timesInt8#" Primop.timesInt8# Wrapper.timesInt8#
- , testPrimop "quotInt8#" Primop.quotInt8# Wrapper.quotInt8#
- , testPrimop "remInt8#" Primop.remInt8# Wrapper.remInt8#
- , testPrimop "quotRemInt8#" Primop.quotRemInt8# Wrapper.quotRemInt8#
+ , testPrimopDivLike "quotInt8#" Primop.quotInt8# Wrapper.quotInt8#
+ , testPrimopDivLike "remInt8#" Primop.remInt8# Wrapper.remInt8#
+ , testPrimopDivLike "quotRemInt8#" Primop.quotRemInt8# Wrapper.quotRemInt8#
, testPrimop "uncheckedShiftLInt8#" Primop.uncheckedShiftLInt8# Wrapper.uncheckedShiftLInt8#
, testPrimop "uncheckedShiftRAInt8#" Primop.uncheckedShiftRAInt8# Wrapper.uncheckedShiftRAInt8#
, testPrimop "uncheckedShiftRLInt8#" Primop.uncheckedShiftRLInt8# Wrapper.uncheckedShiftRLInt8#
@@ -418,9 +440,9 @@ testPrimops = Group "primop"
, testPrimop "plusWord8#" Primop.plusWord8# Wrapper.plusWord8#
, testPrimop "subWord8#" Primop.subWord8# Wrapper.subWord8#
, testPrimop "timesWord8#" Primop.timesWord8# Wrapper.timesWord8#
- , testPrimop "quotWord8#" Primop.quotWord8# Wrapper.quotWord8#
- , testPrimop "remWord8#" Primop.remWord8# Wrapper.remWord8#
- , testPrimop "quotRemWord8#" Primop.quotRemWord8# Wrapper.quotRemWord8#
+ , testPrimopDivLike "quotWord8#" Primop.quotWord8# Wrapper.quotWord8#
+ , testPrimopDivLike "remWord8#" Primop.remWord8# Wrapper.remWord8#
+ , testPrimopDivLike "quotRemWord8#" Primop.quotRemWord8# Wrapper.quotRemWord8#
, testPrimop "andWord8#" Primop.andWord8# Wrapper.andWord8#
, testPrimop "orWord8#" Primop.orWord8# Wrapper.orWord8#
, testPrimop "xorWord8#" Primop.xorWord8# Wrapper.xorWord8#
@@ -440,9 +462,9 @@ testPrimops = Group "primop"
, testPrimop "plusInt16#" Primop.plusInt16# Wrapper.plusInt16#
, testPrimop "subInt16#" Primop.subInt16# Wrapper.subInt16#
, testPrimop "timesInt16#" Primop.timesInt16# Wrapper.timesInt16#
- , testPrimop "quotInt16#" Primop.quotInt16# Wrapper.quotInt16#
- , testPrimop "remInt16#" Primop.remInt16# Wrapper.remInt16#
- , testPrimop "quotRemInt16#" Primop.quotRemInt16# Wrapper.quotRemInt16#
+ , testPrimopDivLike "quotInt16#" Primop.quotInt16# Wrapper.quotInt16#
+ , testPrimopDivLike "remInt16#" Primop.remInt16# Wrapper.remInt16#
+ , testPrimopDivLike "quotRemInt16#" Primop.quotRemInt16# Wrapper.quotRemInt16#
, testPrimop "uncheckedShiftLInt16#" Primop.uncheckedShiftLInt16# Wrapper.uncheckedShiftLInt16#
, testPrimop "uncheckedShiftRAInt16#" Primop.uncheckedShiftRAInt16# Wrapper.uncheckedShiftRAInt16#
, testPrimop "uncheckedShiftRLInt16#" Primop.uncheckedShiftRLInt16# Wrapper.uncheckedShiftRLInt16#
@@ -458,9 +480,9 @@ testPrimops = Group "primop"
, testPrimop "plusWord16#" Primop.plusWord16# Wrapper.plusWord16#
, testPrimop "subWord16#" Primop.subWord16# Wrapper.subWord16#
, testPrimop "timesWord16#" Primop.timesWord16# Wrapper.timesWord16#
- , testPrimop "quotWord16#" Primop.quotWord16# Wrapper.quotWord16#
- , testPrimop "remWord16#" Primop.remWord16# Wrapper.remWord16#
- , testPrimop "quotRemWord16#" Primop.quotRemWord16# Wrapper.quotRemWord16#
+ , testPrimopDivLike "quotWord16#" Primop.quotWord16# Wrapper.quotWord16#
+ , testPrimopDivLike "remWord16#" Primop.remWord16# Wrapper.remWord16#
+ , testPrimopDivLike "quotRemWord16#" Primop.quotRemWord16# Wrapper.quotRemWord16#
, testPrimop "andWord16#" Primop.andWord16# Wrapper.andWord16#
, testPrimop "orWord16#" Primop.orWord16# Wrapper.orWord16#
, testPrimop "xorWord16#" Primop.xorWord16# Wrapper.xorWord16#
@@ -480,9 +502,9 @@ testPrimops = Group "primop"
, testPrimop "plusInt32#" Primop.plusInt32# Wrapper.plusInt32#
, testPrimop "subInt32#" Primop.subInt32# Wrapper.subInt32#
, testPrimop "timesInt32#" Primop.timesInt32# Wrapper.timesInt32#
- , testPrimop "quotInt32#" Primop.quotInt32# Wrapper.quotInt32#
- , testPrimop "remInt32#" Primop.remInt32# Wrapper.remInt32#
- , testPrimop "quotRemInt32#" Primop.quotRemInt32# Wrapper.quotRemInt32#
+ , testPrimopDivLike "quotInt32#" Primop.quotInt32# Wrapper.quotInt32#
+ , testPrimopDivLike "remInt32#" Primop.remInt32# Wrapper.remInt32#
+ , testPrimopDivLike "quotRemInt32#" Primop.quotRemInt32# Wrapper.quotRemInt32#
, testPrimop "uncheckedShiftLInt32#" Primop.uncheckedShiftLInt32# Wrapper.uncheckedShiftLInt32#
, testPrimop "uncheckedShiftRAInt32#" Primop.uncheckedShiftRAInt32# Wrapper.uncheckedShiftRAInt32#
, testPrimop "uncheckedShiftRLInt32#" Primop.uncheckedShiftRLInt32# Wrapper.uncheckedShiftRLInt32#
@@ -498,9 +520,9 @@ testPrimops = Group "primop"
, testPrimop "plusWord32#" Primop.plusWord32# Wrapper.plusWord32#
, testPrimop "subWord32#" Primop.subWord32# Wrapper.subWord32#
, testPrimop "timesWord32#" Primop.timesWord32# Wrapper.timesWord32#
- , testPrimop "quotWord32#" Primop.quotWord32# Wrapper.quotWord32#
- , testPrimop "remWord32#" Primop.remWord32# Wrapper.remWord32#
- , testPrimop "quotRemWord32#" Primop.quotRemWord32# Wrapper.quotRemWord32#
+ , testPrimopDivLike "quotWord32#" Primop.quotWord32# Wrapper.quotWord32#
+ , testPrimopDivLike "remWord32#" Primop.remWord32# Wrapper.remWord32#
+ , testPrimopDivLike "quotRemWord32#" Primop.quotRemWord32# Wrapper.quotRemWord32#
, testPrimop "andWord32#" Primop.andWord32# Wrapper.andWord32#
, testPrimop "orWord32#" Primop.orWord32# Wrapper.orWord32#
, testPrimop "xorWord32#" Primop.xorWord32# Wrapper.xorWord32#
@@ -520,8 +542,8 @@ testPrimops = Group "primop"
, testPrimop "plusInt64#" Primop.plusInt64# Wrapper.plusInt64#
, testPrimop "subInt64#" Primop.subInt64# Wrapper.subInt64#
, testPrimop "timesInt64#" Primop.timesInt64# Wrapper.timesInt64#
- , testPrimop "quotInt64#" Primop.quotInt64# Wrapper.quotInt64#
- , testPrimop "remInt64#" Primop.remInt64# Wrapper.remInt64#
+ , testPrimopDivLike "quotInt64#" Primop.quotInt64# Wrapper.quotInt64#
+ , testPrimopDivLike "remInt64#" Primop.remInt64# Wrapper.remInt64#
, testPrimop "uncheckedIShiftL64#" Primop.uncheckedIShiftL64# Wrapper.uncheckedIShiftL64#
, testPrimop "uncheckedIShiftRA64#" Primop.uncheckedIShiftRA64# Wrapper.uncheckedIShiftRA64#
, testPrimop "uncheckedIShiftRL64#" Primop.uncheckedIShiftRL64# Wrapper.uncheckedIShiftRL64#
@@ -537,8 +559,8 @@ testPrimops = Group "primop"
, testPrimop "plusWord64#" Primop.plusWord64# Wrapper.plusWord64#
, testPrimop "subWord64#" Primop.subWord64# Wrapper.subWord64#
, testPrimop "timesWord64#" Primop.timesWord64# Wrapper.timesWord64#
- , testPrimop "quotWord64#" Primop.quotWord64# Wrapper.quotWord64#
- , testPrimop "remWord64#" Primop.remWord64# Wrapper.remWord64#
+ , testPrimopDivLike "quotWord64#" Primop.quotWord64# Wrapper.quotWord64#
+ , testPrimopDivLike "remWord64#" Primop.remWord64# Wrapper.remWord64#
, testPrimop "and64#" Primop.and64# Wrapper.and64#
, testPrimop "or64#" Primop.or64# Wrapper.or64#
, testPrimop "xor64#" Primop.xor64# Wrapper.xor64#
@@ -557,9 +579,9 @@ testPrimops = Group "primop"
, testPrimop "*#" (Primop.*#) (Wrapper.*#)
, testPrimop "timesInt2#" Primop.timesInt2# Wrapper.timesInt2#
, testPrimop "mulIntMayOflo#" Primop.mulIntMayOflo# Wrapper.mulIntMayOflo#
- , testPrimop "quotInt#" Primop.quotInt# Wrapper.quotInt#
- , testPrimop "remInt#" Primop.remInt# Wrapper.remInt#
- , testPrimop "quotRemInt#" Primop.quotRemInt# Wrapper.quotRemInt#
+ , testPrimopDivLike "quotInt#" Primop.quotInt# Wrapper.quotInt#
+ , testPrimopDivLike "remInt#" Primop.remInt# Wrapper.remInt#
+ , testPrimopDivLike "quotRemInt#" Primop.quotRemInt# Wrapper.quotRemInt#
, testPrimop "andI#" Primop.andI# Wrapper.andI#
, testPrimop "orI#" Primop.orI# Wrapper.orI#
, testPrimop "xorI#" Primop.xorI# Wrapper.xorI#
@@ -585,10 +607,9 @@ testPrimops = Group "primop"
, testPrimop "minusWord#" Primop.minusWord# Wrapper.minusWord#
, testPrimop "timesWord#" Primop.timesWord# Wrapper.timesWord#
, testPrimop "timesWord2#" Primop.timesWord2# Wrapper.timesWord2#
- , testPrimop "quotWord#" Primop.quotWord# Wrapper.quotWord#
- , testPrimop "remWord#" Primop.remWord# Wrapper.remWord#
- , testPrimop "quotRemWord#" Primop.quotRemWord# Wrapper.quotRemWord#
--- , testPrimop "quotRemWord2#" Primop.quotRemWord2# Wrapper.quotRemWord2#
+ , testPrimopDivLike "quotWord#" Primop.quotWord# Wrapper.quotWord#
+ , testPrimopDivLike "remWord#" Primop.remWord# Wrapper.remWord#
+ , testPrimopDivLike "quotRemWord#" Primop.quotRemWord# Wrapper.quotRemWord#
, testPrimop "and#" Primop.and# Wrapper.and#
, testPrimop "or#" Primop.or# Wrapper.or#
, testPrimop "xor#" Primop.xor# Wrapper.xor#
@@ -652,12 +673,15 @@ instance TestPrimop (Char# -> Int#) where
instance TestPrimop (Int# -> Int# -> Int#) where
testPrimop s l r = Property s $ \ (uInt#-> x0) (uInt#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt#-> x0) (uInt#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
instance TestPrimop (Int# -> Int# -> (# Int#,Int# #)) where
testPrimop s l r = Property s $ \ (uInt#-> x0) (uInt#-> x1) -> WTUP2(wInt#,wInt#, (l x0 x1)) === WTUP2(wInt#,wInt#, (r x0 x1))
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt#-> x0) (uInt#-> x1) -> WTUP2(wInt#,wInt#, (l x0 x1)) === WTUP2(wInt#,wInt#, (r x0 x1))
instance TestPrimop (Int# -> Int# -> (# Int#,Int#,Int# #)) where
testPrimop s l r = Property s $ \ (uInt#-> x0) (uInt#-> x1) -> WTUP3(wInt#,wInt#,wInt#, (l x0 x1)) === WTUP3(wInt#,wInt#,wInt#, (r x0 x1))
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt#-> x0) (uInt#-> x1) -> WTUP3(wInt#,wInt#,wInt#, (l x0 x1)) === WTUP3(wInt#,wInt#,wInt#, (r x0 x1))
instance TestPrimop (Int# -> Char#) where
testPrimop s l r = Property s $ \ (uInt#-> x0) -> wChar# (l x0) === wChar# (r x0)
@@ -685,12 +709,15 @@ instance TestPrimop (Int16# -> Int# -> Int16#) where
instance TestPrimop (Int16# -> Int16# -> Int#) where
testPrimop s l r = Property s $ \ (uInt16#-> x0) (uInt16#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt16#-> x0) (uInt16#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
instance TestPrimop (Int16# -> Int16# -> Int16#) where
testPrimop s l r = Property s $ \ (uInt16#-> x0) (uInt16#-> x1) -> wInt16# (l x0 x1) === wInt16# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt16#-> x0) (uInt16#-> x1) -> wInt16# (l x0 x1) === wInt16# (r x0 x1)
instance TestPrimop (Int16# -> Int16# -> (# Int16#,Int16# #)) where
testPrimop s l r = Property s $ \ (uInt16#-> x0) (uInt16#-> x1) -> WTUP2(wInt16#,wInt16#, (l x0 x1)) === WTUP2(wInt16#,wInt16#, (r x0 x1))
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt16#-> x0) (uInt16#-> x1) -> WTUP2(wInt16#,wInt16#, (l x0 x1)) === WTUP2(wInt16#,wInt16#, (r x0 x1))
instance TestPrimop (Int16# -> Int#) where
testPrimop s l r = Property s $ \ (uInt16#-> x0) -> wInt# (l x0) === wInt# (r x0)
@@ -706,12 +733,15 @@ instance TestPrimop (Int32# -> Int# -> Int32#) where
instance TestPrimop (Int32# -> Int32# -> Int#) where
testPrimop s l r = Property s $ \ (uInt32#-> x0) (uInt32#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt32#-> x0) (uInt32#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
instance TestPrimop (Int32# -> Int32# -> Int32#) where
testPrimop s l r = Property s $ \ (uInt32#-> x0) (uInt32#-> x1) -> wInt32# (l x0 x1) === wInt32# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt32#-> x0) (uInt32#-> x1) -> wInt32# (l x0 x1) === wInt32# (r x0 x1)
instance TestPrimop (Int32# -> Int32# -> (# Int32#,Int32# #)) where
testPrimop s l r = Property s $ \ (uInt32#-> x0) (uInt32#-> x1) -> WTUP2(wInt32#,wInt32#, (l x0 x1)) === WTUP2(wInt32#,wInt32#, (r x0 x1))
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt32#-> x0) (uInt32#-> x1) -> WTUP2(wInt32#,wInt32#, (l x0 x1)) === WTUP2(wInt32#,wInt32#, (r x0 x1))
instance TestPrimop (Int32# -> Int#) where
testPrimop s l r = Property s $ \ (uInt32#-> x0) -> wInt# (l x0) === wInt# (r x0)
@@ -727,9 +757,11 @@ instance TestPrimop (Int64# -> Int# -> Int64#) where
instance TestPrimop (Int64# -> Int64# -> Int#) where
testPrimop s l r = Property s $ \ (uInt64#-> x0) (uInt64#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt64#-> x0) (uInt64#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
instance TestPrimop (Int64# -> Int64# -> Int64#) where
testPrimop s l r = Property s $ \ (uInt64#-> x0) (uInt64#-> x1) -> wInt64# (l x0 x1) === wInt64# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt64#-> x0) (uInt64#-> x1) -> wInt64# (l x0 x1) === wInt64# (r x0 x1)
instance TestPrimop (Int64# -> Int#) where
testPrimop s l r = Property s $ \ (uInt64#-> x0) -> wInt# (l x0) === wInt# (r x0)
@@ -745,12 +777,15 @@ instance TestPrimop (Int8# -> Int# -> Int8#) where
instance TestPrimop (Int8# -> Int8# -> Int#) where
testPrimop s l r = Property s $ \ (uInt8#-> x0) (uInt8#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt8#-> x0) (uInt8#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
instance TestPrimop (Int8# -> Int8# -> Int8#) where
testPrimop s l r = Property s $ \ (uInt8#-> x0) (uInt8#-> x1) -> wInt8# (l x0 x1) === wInt8# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt8#-> x0) (uInt8#-> x1) -> wInt8# (l x0 x1) === wInt8# (r x0 x1)
instance TestPrimop (Int8# -> Int8# -> (# Int8#,Int8# #)) where
testPrimop s l r = Property s $ \ (uInt8#-> x0) (uInt8#-> x1) -> WTUP2(wInt8#,wInt8#, (l x0 x1)) === WTUP2(wInt8#,wInt8#, (r x0 x1))
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uInt8#-> x0) (uInt8#-> x1) -> WTUP2(wInt8#,wInt8#, (l x0 x1)) === WTUP2(wInt8#,wInt8#, (r x0 x1))
instance TestPrimop (Int8# -> Int#) where
testPrimop s l r = Property s $ \ (uInt8#-> x0) -> wInt# (l x0) === wInt# (r x0)
@@ -764,20 +799,21 @@ instance TestPrimop (Int8# -> Word8#) where
instance TestPrimop (Word# -> Int# -> Word#) where
testPrimop s l r = Property s $ \ (uWord#-> x0) (uInt#-> x1) -> wWord# (l x0 x1) === wWord# (r x0 x1)
-instance TestPrimop (Word# -> Word# -> Word# -> (# Word#,Word# #)) where
- testPrimop s l r = Property s $ \ (uWord#-> x0) (uWord#-> x1) (uWord#-> x2) -> WTUP2(wWord#,wWord#, (l x0 x1 x2)) === WTUP2(wWord#,wWord#, (r x0 x1 x2))
-
instance TestPrimop (Word# -> Word# -> Int#) where
testPrimop s l r = Property s $ \ (uWord#-> x0) (uWord#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord#-> x0) (uWord#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
instance TestPrimop (Word# -> Word# -> Word#) where
testPrimop s l r = Property s $ \ (uWord#-> x0) (uWord#-> x1) -> wWord# (l x0 x1) === wWord# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord#-> x0) (uWord#-> x1) -> wWord# (l x0 x1) === wWord# (r x0 x1)
instance TestPrimop (Word# -> Word# -> (# Word#,Int# #)) where
testPrimop s l r = Property s $ \ (uWord#-> x0) (uWord#-> x1) -> WTUP2(wWord#,wInt#, (l x0 x1)) === WTUP2(wWord#,wInt#, (r x0 x1))
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord#-> x0) (uWord#-> x1) -> WTUP2(wWord#,wInt#, (l x0 x1)) === WTUP2(wWord#,wInt#, (r x0 x1))
instance TestPrimop (Word# -> Word# -> (# Word#,Word# #)) where
testPrimop s l r = Property s $ \ (uWord#-> x0) (uWord#-> x1) -> WTUP2(wWord#,wWord#, (l x0 x1)) === WTUP2(wWord#,wWord#, (r x0 x1))
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord#-> x0) (uWord#-> x1) -> WTUP2(wWord#,wWord#, (l x0 x1)) === WTUP2(wWord#,wWord#, (r x0 x1))
instance TestPrimop (Word# -> Int#) where
testPrimop s l r = Property s $ \ (uWord#-> x0) -> wInt# (l x0) === wInt# (r x0)
@@ -802,12 +838,15 @@ instance TestPrimop (Word16# -> Int# -> Word16#) where
instance TestPrimop (Word16# -> Word16# -> Int#) where
testPrimop s l r = Property s $ \ (uWord16#-> x0) (uWord16#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord16#-> x0) (uWord16#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
instance TestPrimop (Word16# -> Word16# -> Word16#) where
testPrimop s l r = Property s $ \ (uWord16#-> x0) (uWord16#-> x1) -> wWord16# (l x0 x1) === wWord16# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord16#-> x0) (uWord16#-> x1) -> wWord16# (l x0 x1) === wWord16# (r x0 x1)
instance TestPrimop (Word16# -> Word16# -> (# Word16#,Word16# #)) where
testPrimop s l r = Property s $ \ (uWord16#-> x0) (uWord16#-> x1) -> WTUP2(wWord16#,wWord16#, (l x0 x1)) === WTUP2(wWord16#,wWord16#, (r x0 x1))
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord16#-> x0) (uWord16#-> x1) -> WTUP2(wWord16#,wWord16#, (l x0 x1)) === WTUP2(wWord16#,wWord16#, (r x0 x1))
instance TestPrimop (Word16# -> Int16#) where
testPrimop s l r = Property s $ \ (uWord16#-> x0) -> wInt16# (l x0) === wInt16# (r x0)
@@ -823,12 +862,15 @@ instance TestPrimop (Word32# -> Int# -> Word32#) where
instance TestPrimop (Word32# -> Word32# -> Int#) where
testPrimop s l r = Property s $ \ (uWord32#-> x0) (uWord32#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord32#-> x0) (uWord32#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
instance TestPrimop (Word32# -> Word32# -> Word32#) where
testPrimop s l r = Property s $ \ (uWord32#-> x0) (uWord32#-> x1) -> wWord32# (l x0 x1) === wWord32# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord32#-> x0) (uWord32#-> x1) -> wWord32# (l x0 x1) === wWord32# (r x0 x1)
instance TestPrimop (Word32# -> Word32# -> (# Word32#,Word32# #)) where
testPrimop s l r = Property s $ \ (uWord32#-> x0) (uWord32#-> x1) -> WTUP2(wWord32#,wWord32#, (l x0 x1)) === WTUP2(wWord32#,wWord32#, (r x0 x1))
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord32#-> x0) (uWord32#-> x1) -> WTUP2(wWord32#,wWord32#, (l x0 x1)) === WTUP2(wWord32#,wWord32#, (r x0 x1))
instance TestPrimop (Word32# -> Int32#) where
testPrimop s l r = Property s $ \ (uWord32#-> x0) -> wInt32# (l x0) === wInt32# (r x0)
@@ -844,9 +886,11 @@ instance TestPrimop (Word64# -> Int# -> Word64#) where
instance TestPrimop (Word64# -> Word64# -> Int#) where
testPrimop s l r = Property s $ \ (uWord64#-> x0) (uWord64#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord64#-> x0) (uWord64#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
instance TestPrimop (Word64# -> Word64# -> Word64#) where
testPrimop s l r = Property s $ \ (uWord64#-> x0) (uWord64#-> x1) -> wWord64# (l x0 x1) === wWord64# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord64#-> x0) (uWord64#-> x1) -> wWord64# (l x0 x1) === wWord64# (r x0 x1)
instance TestPrimop (Word64# -> Int64#) where
testPrimop s l r = Property s $ \ (uWord64#-> x0) -> wInt64# (l x0) === wInt64# (r x0)
@@ -862,12 +906,15 @@ instance TestPrimop (Word8# -> Int# -> Word8#) where
instance TestPrimop (Word8# -> Word8# -> Int#) where
testPrimop s l r = Property s $ \ (uWord8#-> x0) (uWord8#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord8#-> x0) (uWord8#-> x1) -> wInt# (l x0 x1) === wInt# (r x0 x1)
instance TestPrimop (Word8# -> Word8# -> Word8#) where
testPrimop s l r = Property s $ \ (uWord8#-> x0) (uWord8#-> x1) -> wWord8# (l x0 x1) === wWord8# (r x0 x1)
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord8#-> x0) (uWord8#-> x1) -> wWord8# (l x0 x1) === wWord8# (r x0 x1)
instance TestPrimop (Word8# -> Word8# -> (# Word8#,Word8# #)) where
testPrimop s l r = Property s $ \ (uWord8#-> x0) (uWord8#-> x1) -> WTUP2(wWord8#,wWord8#, (l x0 x1)) === WTUP2(wWord8#,wWord8#, (r x0 x1))
+ testPrimopDivLike s l r = Property s $ twoNonZero $ \ (uWord8#-> x0) (uWord8#-> x1) -> WTUP2(wWord8#,wWord8#, (l x0 x1)) === WTUP2(wWord8#,wWord8#, (r x0 x1))
instance TestPrimop (Word8# -> Int8#) where
testPrimop s l r = Property s $ \ (uWord8#-> x0) -> wInt8# (l x0) === wInt8# (r x0)
=====================================
utils/genprimopcode/Main.hs
=====================================
@@ -1,3 +1,4 @@
+{-# OPTIONS_GHC -Wno-x-partial #-}
------------------------------------------------------------------
-- A primop-table mangling program --
--
@@ -693,16 +694,24 @@ gen_foundation_tests (Info _ entries)
where
testable_tys = nub (sort (mapMaybe (\po -> ty po <$ mkTest po) entries))
- mkInstances ty = unlines $
- [ "instance TestPrimop (" ++ pprTy ty ++ ") where"
- , " testPrimop s l r = Property s $ \\ " ++ intercalate " " (zipWith mkArg [0..] (args ty)) ++ " -> " ++ mk_body "l" ++ " === " ++ mk_body "r" ]
-
+ mkInstances inst_ty =
+ let test_lambda = "\\ " ++ intercalate " " (zipWith mkArg [0::Int ..] (arg_tys)) ++ " -> " ++ mk_body "l" ++ " === " ++ mk_body "r"
+ in unlines $
+ [ "instance TestPrimop (" ++ pprTy inst_ty ++ ") where"
+ , " testPrimop s l r = Property s $ " ++ test_lambda ]
+ ++ (if mb_divable_tys
+ then [" testPrimopDivLike s l r = Property s $ twoNonZero $ " ++ test_lambda]
+ else [])
where
- n_args = length (args ty)
+ arg_tys = args inst_ty
+ -- eg Int -> Int -> a
+ mb_divable_tys = case arg_tys of
+ [ty1,ty2] -> ty1 == ty2 && ty1 `elem` divableTyCons
+ _ -> False
- mk_body s = res_ty ty (" (" ++ s ++ " " ++ intercalate " " vs ++ ")")
+ mk_body s = res_ty inst_ty (" (" ++ s ++ " " ++ intercalate " " vs ++ ")")
- vs = zipWith (\n _ -> "x" ++ show n) [0..] (args ty)
+ vs = zipWith (\n _ -> "x" ++ show n) [0::Int ..] (arg_tys)
mkArg n t = "(" ++ unwrapper t ++ "-> x" ++ show n ++ ")"
@@ -714,15 +723,22 @@ gen_foundation_tests (Info _ entries)
args (TyF (TyApp (TyCon c) []) t2) = c : args t2
args (TyApp {}) = []
args (TyUTup {}) = []
+ -- If you hit this you will need to handle the foundation tests to handle the
+ -- type it failed on.
+ args arg_ty = error ("Unexpected primop type:" ++ pprTy arg_ty)
res_ty (TyF _ t2) x = res_ty t2 x
res_ty (TyApp (TyCon c) []) x = wrapper c ++ x
- res_ty (TyUTup args) x =
- let wtup = case length args of
+ res_ty (TyUTup tup_tys) x =
+ let wtup = case length tup_tys of
2 -> "WTUP2"
3 -> "WTUP3"
- in wtup ++"(" ++ intercalate "," (map (\a -> res_ty a "") args ++ [x]) ++ ")"
-
+ -- Only handles primops returning unboxed tuples up to 3 args currently
+ _ -> error "Unexpected primop result type"
+ in wtup ++"(" ++ intercalate "," (map (\a -> res_ty a "") tup_tys ++ [x]) ++ ")"
+ -- If you hit this you will need to handle the foundation tests to handle the
+ -- type it failed on.
+ res_ty unexpected_ty x = error ("Unexpected primop result type:" ++ pprTy unexpected_ty ++ "," ++ x)
wrap qual nm | isLower (head nm) = qual ++ "." ++ nm
@@ -734,7 +750,10 @@ gen_foundation_tests (Info _ entries)
, poName /= "tagToEnum#"
, poName /= "quotRemWord2#"
, (testable (ty po))
- = Just $ intercalate " " ["testPrimop", "\"" ++ poName ++ "\"", wrap "Primop" poName, wrap "Wrapper" poName]
+ = let testPrimOpHow = if is_divLikeOp po
+ then "testPrimopDivLike"
+ else "testPrimop"
+ in Just $ intercalate " " [testPrimOpHow, "\"" ++ poName ++ "\"", wrap "Primop" poName, wrap "Wrapper" poName]
| otherwise = Nothing
@@ -742,13 +761,15 @@ gen_foundation_tests (Info _ entries)
testable (TyF t1 t2) = testable t1 && testable t2
testable (TyC _ t2) = testable t2
testable (TyApp tc tys) = testableTyCon tc && all testable tys
- testable (TyVar a) = False
+ testable (TyVar _a) = False
testable (TyUTup tys) = all testable tys
testableTyCon (TyCon c) =
c `elem` ["Int#", "Word#", "Word8#", "Word16#", "Word32#", "Word64#"
, "Int8#", "Int16#", "Int32#", "Int64#", "Char#"]
testableTyCon _ = False
+ divableTyCons = ["Int#", "Word#", "Word8#", "Word16#", "Word32#", "Word64#"
+ ,"Int8#", "Int16#", "Int32#", "Int64#"]
------------------------------------------------------------------
-- Create PrimOpInfo text from PrimOpSpecs -----------------------
=====================================
utils/genprimopcode/Syntax.hs
=====================================
@@ -53,6 +53,19 @@ is_primtype :: Entry -> Bool
is_primtype (PrimTypeSpec {}) = True
is_primtype _ = False
+is_divLikeOp :: Entry -> Bool
+is_divLikeOp entry = case entry of
+ PrimOpSpec{} -> has_div_like
+ PseudoOpSpec{} -> has_div_like
+ PrimVecOpSpec{} -> has_div_like
+ PrimTypeSpec{} -> False
+ PrimVecTypeSpec{} -> False
+ Section{} -> False
+ where
+ has_div_like = case lookup_attrib "div_like" (opts entry) of
+ Just (OptionTrue{}) -> True
+ _ -> False
+
-- a binding of property to value
data Option
= OptionFalse String -- name = False
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/689e4a7d802a9ee253723cb938e023…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/689e4a7d802a9ee253723cb938e023…
You're receiving this email because of your account on gitlab.haskell.org.
1
0

[Git][ghc/ghc][wip/T25965] 3 commits: driver: Use ModuleGraph for oneshot and --make mode
by Simon Peyton Jones (@simonpj) 16 Apr '25
by Simon Peyton Jones (@simonpj) 16 Apr '25
16 Apr '25
Simon Peyton Jones pushed to branch wip/T25965 at Glasgow Haskell Compiler / GHC
Commits:
d47bf776 by Matthew Pickering at 2025-04-14T16:44:41+01:00
driver: Use ModuleGraph for oneshot and --make mode
This patch uses the `hsc_mod_graph` field for both oneshot and --make
mode. Therefore, if part of the compiler requires usage of the module
graph, you do so in a uniform way for the two different modes.
The `ModuleGraph` describes the relationship between the modules in the
home package and units in external packages. The `ModuleGraph` can be
queried when information about the transitive closure of a package is
needed. For example, the primary use of the ModuleGraph from within the
compiler is in the loader, which needs to know the transitive closure of
a module so it can load all the relevant objects for evaluation.
In --make mode, downsweep computes the ModuleGraph before any
compilation starts.
In oneshot mode, a thunk is created at the start of compilation, which
when forced will compute the module graph beneath the current module.
The thunk is only forced at the moment when the user uses Template
Haskell.
Finally, there are some situations where we need to discover what
dependencies to load but haven't loaded a module graph at all. In this
case, there is a fallback which computes the transitive closure on the
fly and doesn't cache the result. Presumably if you are going to call
getLinkDeps a lot, you would compute the right ModuleGraph before you
started.
Importantly, this removes the ExternalModuleGraph abstraction. This was quite
awkward to work with since it stored information about the home package
inside the EPS.
This patch will also be very useful when implementing explicit level
imports, which requires more significant use of the module graph in
order to determine which level instances are available at.
Towards #25795
-------------------------
Metric Decrease:
MultiLayerModulesTH_Make
MultiLayerModulesTH_OneShot
-------------------------
- - - - -
395e0ad1 by sheaf at 2025-04-16T12:33:26-04:00
base: remove .Internal modules (e.g. GHC.TypeLits)
This commit removes the following internal modules from base,
as per CLC proposal 217:
- GHC.TypeNats.Internal
- GHC.TypeLits.Internal
- GHC.ExecutionStack.Internal
Fixes #25007
- - - - -
15201e7e by Simon Peyton Jones at 2025-04-16T23:00:04+01:00
Fix infelicities in the Specialiser
On the way to #23109 (unary classes) I discovered some infelicities
(or maybe tiny bugs, I forget) in the type-class specialiser.
I also tripped over #25965, an outright bug in the rule matcher
Specifically:
* I enhanced `wantCallsFor`, whih previously always said `True`,
to discard calls of class-ops, data constructors etc.
This is a bit more efficient; and it means we don't need to
worry about filtering them out later.
* I tidied up some tricky logic that eliminated redundant
specialisations. It wasn't working correctly. See the
expanded Note [Specialisations already covered], and
(MP3) in Note [Specialising polymorphic dictionaries].
And the new top-level `alreadyCovered` function, which now
goes via `GHC.Core.Rules.ruleLhsIsMoreSpecific`
I also added a useful Note [The (CI-KEY) invariant]
* I fixed a tricky bug in the `go_fam_fam` in
`GHC.Core.Unify.uVarOrFam`, which allows matching to succeed without
binding all type varibles. This caused #25965.
I enhanced Note [Apartness and type families] some more
- - - - -
42 changed files:
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/Rules.hs
- compiler/GHC/Core/Unify.hs
- compiler/GHC/Driver/Downsweep.hs
- compiler/GHC/Driver/Env.hs
- compiler/GHC/Driver/Env/Types.hs
- compiler/GHC/Driver/Errors/Ppr.hs
- compiler/GHC/Driver/Errors/Types.hs
- compiler/GHC/Driver/Main.hs
- compiler/GHC/Driver/Make.hs
- compiler/GHC/Driver/MakeAction.hs
- + compiler/GHC/Driver/Messager.hs
- compiler/GHC/Driver/Pipeline.hs-boot
- compiler/GHC/Driver/Pipeline/Execute.hs
- compiler/GHC/Iface/Load.hs
- compiler/GHC/Iface/Recomp.hs
- compiler/GHC/Linker/Deps.hs
- compiler/GHC/Linker/Loader.hs
- compiler/GHC/Runtime/Loader.hs
- compiler/GHC/Tc/Solver/Equality.hs
- compiler/GHC/Types/Basic.hs
- compiler/GHC/Unit/External.hs
- compiler/GHC/Unit/Finder.hs
- − compiler/GHC/Unit/Module/External/Graph.hs
- compiler/GHC/Unit/Module/Graph.hs
- compiler/GHC/Unit/Module/ModNodeKey.hs
- compiler/ghc.cabal.in
- libraries/base/base.cabal.in
- libraries/base/changelog.md
- − libraries/base/src/GHC/ExecutionStack/Internal.hs
- − libraries/base/src/GHC/TypeLits/Internal.hs
- − libraries/base/src/GHC/TypeNats/Internal.hs
- + testsuite/tests/ghc-api/fixed-nodes/InterfaceModuleGraph.hs
- + testsuite/tests/ghc-api/fixed-nodes/InterfaceModuleGraph.stdout
- testsuite/tests/ghc-api/fixed-nodes/all.T
- testsuite/tests/interface-stability/base-exports.stdout
- testsuite/tests/interface-stability/base-exports.stdout-javascript-unknown-ghcjs
- testsuite/tests/interface-stability/base-exports.stdout-mingw32
- testsuite/tests/interface-stability/base-exports.stdout-ws-32
- testsuite/tests/plugins/defaulting-plugin/DefaultLifted.hs
- + testsuite/tests/simplCore/should_compile/T25965.hs
- testsuite/tests/simplCore/should_compile/all.T
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/8c5dd598bba0307638c50f3d0b2e7a…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/8c5dd598bba0307638c50f3d0b2e7a…
You're receiving this email because of your account on gitlab.haskell.org.
1
0

[Git][ghc/ghc][wip/az/ghc-cpp] 106 commits: driver: Use ModuleGraph for oneshot and --make mode
by Alan Zimmerman (@alanz) 16 Apr '25
by Alan Zimmerman (@alanz) 16 Apr '25
16 Apr '25
Alan Zimmerman pushed to branch wip/az/ghc-cpp at Glasgow Haskell Compiler / GHC
Commits:
d47bf776 by Matthew Pickering at 2025-04-14T16:44:41+01:00
driver: Use ModuleGraph for oneshot and --make mode
This patch uses the `hsc_mod_graph` field for both oneshot and --make
mode. Therefore, if part of the compiler requires usage of the module
graph, you do so in a uniform way for the two different modes.
The `ModuleGraph` describes the relationship between the modules in the
home package and units in external packages. The `ModuleGraph` can be
queried when information about the transitive closure of a package is
needed. For example, the primary use of the ModuleGraph from within the
compiler is in the loader, which needs to know the transitive closure of
a module so it can load all the relevant objects for evaluation.
In --make mode, downsweep computes the ModuleGraph before any
compilation starts.
In oneshot mode, a thunk is created at the start of compilation, which
when forced will compute the module graph beneath the current module.
The thunk is only forced at the moment when the user uses Template
Haskell.
Finally, there are some situations where we need to discover what
dependencies to load but haven't loaded a module graph at all. In this
case, there is a fallback which computes the transitive closure on the
fly and doesn't cache the result. Presumably if you are going to call
getLinkDeps a lot, you would compute the right ModuleGraph before you
started.
Importantly, this removes the ExternalModuleGraph abstraction. This was quite
awkward to work with since it stored information about the home package
inside the EPS.
This patch will also be very useful when implementing explicit level
imports, which requires more significant use of the module graph in
order to determine which level instances are available at.
Towards #25795
-------------------------
Metric Decrease:
MultiLayerModulesTH_Make
MultiLayerModulesTH_OneShot
-------------------------
- - - - -
395e0ad1 by sheaf at 2025-04-16T12:33:26-04:00
base: remove .Internal modules (e.g. GHC.TypeLits)
This commit removes the following internal modules from base,
as per CLC proposal 217:
- GHC.TypeNats.Internal
- GHC.TypeLits.Internal
- GHC.ExecutionStack.Internal
Fixes #25007
- - - - -
123063e9 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
GHC-CPP: first rough proof of concept
Processes
#define FOO
#ifdef FOO
x = 1
#endif
Into
[ITcppIgnored [L loc ITcppDefine]
,ITcppIgnored [L loc ITcppIfdef]
,ITvarid "x"
,ITequal
,ITinteger (IL {il_text = SourceText "1", il_neg = False, il_value = 1})
,ITcppIgnored [L loc ITcppEndif]
,ITeof]
In time, ITcppIgnored will be pushed into a comment
- - - - -
fd78ccfb by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Tidy up before re-visiting the continuation mechanic
- - - - -
52d3b28a by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Switch preprocessor to continuation passing style
Proof of concept, needs tidying up
- - - - -
d13cf4cb by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Small cleanup
- - - - -
824da70d by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Get rid of some cruft
- - - - -
eb4129f5 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Starting to integrate.
Need to get the pragma recognised and set
- - - - -
aad083ff by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Make cppTokens extend to end of line, and process CPP comments
- - - - -
45924ee6 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Remove unused ITcppDefined
- - - - -
44e4848c by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Allow spaces between # and keyword for preprocessor directive
- - - - -
79969238 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Process CPP continuation lines
They are emited as separate ITcppContinue tokens.
Perhaps the processing should be more like a comment, and keep on
going to the end.
BUT, the last line needs to be slurped as a whole.
- - - - -
f0c652a4 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Accumulate CPP continuations, process when ready
Can be simplified further, we only need one CPP token
- - - - -
f31fe568 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Simplify Lexer interface. Only ITcpp
We transfer directive lines through it, then parse them from scratch
in the preprocessor.
- - - - -
bd6770e9 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Deal with directive on last line, with no trailing \n
- - - - -
f1e92507 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Start parsing and processing the directives
- - - - -
57711e91 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Prepare for processing include files
- - - - -
9bfc7265 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Move PpState into PreProcess
And initParserState, initPragState too
- - - - -
7cc6b013 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Process nested include files
Also move PpState out of Lexer.x, so it is easy to evolve it in a ghci
session, loading utils/check-cpp/Main.hs
- - - - -
4b52bccf by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Split into separate files
- - - - -
96e31900 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Starting on expression parser.
But it hangs. Time for Text.Parsec.Expr
- - - - -
e49afbce by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Start integrating the ghc-cpp work
From https://github.com/alanz/ghc-cpp
- - - - -
e2cc26ad by Alan Zimmerman at 2025-04-16T20:10:56+01:00
WIP
- - - - -
160c9536 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Fixup after rebase
- - - - -
88624cf4 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
WIP
- - - - -
f5926471 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Fixup after rebase, including all tests pass
- - - - -
a1367108 by Alan Zimmerman at 2025-04-16T20:10:56+01:00
Change pragma usage to GHC_CPP from GhcCPP
- - - - -
7e6498c1 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Some comments
- - - - -
e78ec5c9 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Reformat
- - - - -
9944ddb2 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Delete unused file
- - - - -
d6c5e468 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Rename module Parse to ParsePP
- - - - -
90325d92 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Clarify naming in the parser
- - - - -
d3af5ff1 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
WIP. Switching to alex/happy to be able to work in-tree
Since Parsec is not available
- - - - -
8da3fda7 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Layering is now correct
- GHC lexer, emits CPP tokens
- accumulated in Preprocessor state
- Lexed by CPP lexer, CPP command extracted, tokens concated with
spaces (to get rid of token pasting via comments)
- if directive lexed and parsed by CPP lexer/parser, and evaluated
- - - - -
a330a9f6 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
First example working
Loading Example1.hs into ghci, getting the right results
```
{-# LANGUAGE GHC_CPP #-}
module Example1 where
y = 3
x =
"hello"
"bye now"
foo = putStrLn x
```
- - - - -
e5719609 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Rebase, and all tests pass except whitespace for generated parser
- - - - -
39bc1059 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
More plumbing. Ready for testing tomorrow.
- - - - -
8e0e5340 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Proress. Renamed module State from Types
And at first blush it seems to handle preprocessor scopes properly.
- - - - -
0e43c398 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Insert basic GHC version macros into parser
__GLASGOW_HASKELL__
__GLASGOW_HASKELL_FULL_VERSION__
__GLASGOW_HASKELL_PATCHLEVEL1__
__GLASGOW_HASKELL_PATCHLEVEL2__
- - - - -
a9a9eaf0 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Re-sync check-cpp for easy ghci work
- - - - -
47dd783d by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Get rid of warnings
- - - - -
17bbdb4c by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Rework macro processing, in check-cpp
Macros kept at the top level, looked up via name, multiple arity
versions per name can be stored
- - - - -
c440cfe6 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
WIP. Can crack arguments for #define
Next step it to crack out args in an expansion
- - - - -
8667821a by Alan Zimmerman at 2025-04-16T20:10:57+01:00
WIP on arg parsing.
- - - - -
57b11c08 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Progress. Still screwing up nested parens.
- - - - -
d653f23c by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Seems to work, but has redundant code
- - - - -
e538ed75 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Remove redundant code
- - - - -
0b6d5076 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Reformat
- - - - -
fff97636 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Expand args, single pass
Still need to repeat until fixpoint
- - - - -
793a5a50 by Alan Zimmerman at 2025-04-16T20:10:57+01:00
Fixed point expansion
- - - - -
9960bc20 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Sync the playground to compiler
- - - - -
cf1ba1c3 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Working on dumping the GHC_CPP result
But We need to keep the BufSpan in a comment
- - - - -
607a90d1 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Keep BufSpan in queued comments in GHC.Parser.Lexer
- - - - -
bb62e04e by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Getting close to being able to print the combined tokens
showing what is in and what is out
- - - - -
5a94b451 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
First implementation of dumpGhcCpp.
Example output
First dumps all macros in the state, then the source, showing which
lines are in and which are out
------------------------------
- |#define FOO(A,B) A + B
- |#define FOO(A,B,C) A + B + C
- |#if FOO(1,FOO(3,4)) == 8
- |-- a comment
|x = 1
- |#else
- |x = 5
- |#endif
- - - - -
293695de by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Clean up a bit
- - - - -
f914e0ee by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Add -ddump-ghc-cpp option and a test based on it
- - - - -
5f8f7fce by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Restore Lexer.x rules, we need them for continuation lines
- - - - -
cdfd5ae3 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Lexer.x: trying to sort out the span for continuations
- We need to match on \n at the end of the line
- We cannot simply back up for it
- - - - -
fa3c0c39 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Inserts predefined macros. But does not dump properly
Because the cpp tokens have a trailing newline
- - - - -
43bc181e by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Remove unnecessary LExer rules
We *need* the ones that explicitly match to the end of the line.
- - - - -
677fbf16 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Generate correct span for ITcpp
Dump now works, except we do not render trailing `\` for continuation
lines. This is good enough for use in test output.
- - - - -
e4340715 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Reduce duplication in lexer
- - - - -
71d4bbf1 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Tweaks
- - - - -
b202952e by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Insert min_version predefined macros into state
The mechanism now works. Still need to flesh out the full set.
- - - - -
40a69c9d by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Trying my alternative pragma syntax.
It works, but dumpGhcCpp is broken, I suspect from the ITcpp token
span update.
- - - - -
4a47227d by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Pragma extraction now works, with both CPP and GHC_CPP
For the following
{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__ >= 913
{-# LANGUAGE GHC_CPP #-}
#endif
We will enable GHC_CPP only
- - - - -
0acda1b0 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Remove some tracing
- - - - -
9110a0ee by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Fix test exes for changes
- - - - -
3e1238ed by Alan Zimmerman at 2025-04-16T20:10:58+01:00
For GHC_CPP tests, normalise config-time-based macros
- - - - -
a39358b9 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
WIP
- - - - -
b1a3506d by Alan Zimmerman at 2025-04-16T20:10:58+01:00
WIP again. What is wrong?
- - - - -
404ca306 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Revert to dynflags for normal not pragma lexing
- - - - -
2c02292a by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Working on getting check-exact to work properly
- - - - -
a7b57ca5 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Passes CppCommentPlacement test
- - - - -
a0064092 by Alan Zimmerman at 2025-04-16T20:10:58+01:00
Starting on exact printing with GHC_CPP
While overriding normal CPP
- - - - -
f9fc3f3e by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Correctly store CPP ignored tokens as comments
By populating the lexeme string in it, based on the bufpos
- - - - -
37ed0ade by Alan Zimmerman at 2025-04-16T20:10:59+01:00
WIP
- - - - -
005347ce by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Simplifying
- - - - -
218df0c2 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Update the active state logic
- - - - -
03f4dc01 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Work the new logic into the mainline code
- - - - -
5dbe86c3 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Process `defined` operator
- - - - -
9597084f by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Manage lexer state while skipping tokens
There is very intricate layout-related state used when lexing. If a
CPP directive blanks out some tokens, store this state when the
blanking starts, and restore it when they are no longer being blanked.
- - - - -
5bc84360 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Track the last token buffer index, for ITCppIgnored
We need to attach the source being skipped in an ITCppIgnored token.
We cannot simply use its BufSpan as an index into the underlying
StringBuffer as it counts unicode chars, not bytes.
So we update the lexer state to store the starting StringBuffer
location for the last token, and use the already-stored length to
extract the correct portion of the StringBuffer being parsed.
- - - - -
cfcb6cbf by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Process the ! operator in GHC_CPP expressions
- - - - -
81c74e50 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Predefine a constant when GHC_CPP is being used.
- - - - -
c12468a2 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
WIP
- - - - -
ba57f7c9 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Skip lines directly in the lexer when required
- - - - -
df7d3e65 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Properly manage location when accepting tokens again
- - - - -
22027a8f by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Seems to be working now, for Example9
- - - - -
aded1557 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Remove tracing
- - - - -
1ce351d1 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Fix parsing '*' in block comments
Instead of replacing them with '-'
- - - - -
c8e3ce16 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Keep the trailing backslash in a ITcpp token
- - - - -
ed91afa3 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Deal with only enabling one section of a group.
A group is an instance of a conditional introduced by
#if/#ifdef/#ifndef,
and ending at the final #endif, including intermediate #elsif sections
- - - - -
76355312 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Replace remaining identifiers with 0 when evaluating
As per the spec
- - - - -
0cccd058 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Snapshot before rebase
- - - - -
899c3a8c by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Skip non-processed lines starting with #
- - - - -
1b186f6c by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Export generateMacros so we can use it in ghc-exactprint
- - - - -
d3e2e345 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Fix rebase
- - - - -
37a65b77 by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Expose initParserStateWithMacrosString
- - - - -
20ffbaba by Alan Zimmerman at 2025-04-16T20:10:59+01:00
Fix buggy lexer cppSkip
It was skipping all lines, not just ones prefixed by #
- - - - -
39aae9d4 by Alan Zimmerman at 2025-04-16T20:11:00+01:00
Fix evaluation of && to use the correct operator
- - - - -
608bf45b by Alan Zimmerman at 2025-04-16T20:11:00+01:00
Deal with closing #-} at the start of a line
- - - - -
eba2d8f4 by Alan Zimmerman at 2025-04-16T20:11:00+01:00
Add the MIN_VERSION_GLASGOW_HASKELL predefined macro
- - - - -
cf231960 by Alan Zimmerman at 2025-04-16T20:11:00+01:00
Include MIN_VERSION_GLASGOW_HASKELL in GhcCpp01.stderr
- - - - -
a92c63d3 by Alan Zimmerman at 2025-04-16T20:11:00+01:00
Use a strict map for macro defines
- - - - -
109 changed files:
- compiler/GHC.hs
- compiler/GHC/Cmm/Lexer.x
- compiler/GHC/Cmm/Parser.y
- compiler/GHC/Cmm/Parser/Monad.hs
- compiler/GHC/Driver/Backpack.hs
- compiler/GHC/Driver/Config/Parser.hs
- compiler/GHC/Driver/Downsweep.hs
- compiler/GHC/Driver/Env.hs
- compiler/GHC/Driver/Env/Types.hs
- compiler/GHC/Driver/Errors/Ppr.hs
- compiler/GHC/Driver/Errors/Types.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Driver/Main.hs
- compiler/GHC/Driver/Make.hs
- compiler/GHC/Driver/MakeAction.hs
- + compiler/GHC/Driver/Messager.hs
- compiler/GHC/Driver/Pipeline.hs
- compiler/GHC/Driver/Pipeline.hs-boot
- compiler/GHC/Driver/Pipeline/Execute.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/Iface/Load.hs
- compiler/GHC/Iface/Recomp.hs
- compiler/GHC/Linker/Deps.hs
- compiler/GHC/Linker/Loader.hs
- compiler/GHC/Parser.hs-boot
- compiler/GHC/Parser.y
- compiler/GHC/Parser/Annotation.hs
- compiler/GHC/Parser/HaddockLex.x
- compiler/GHC/Parser/Header.hs
- compiler/GHC/Parser/Lexer.x
- compiler/GHC/Parser/PostProcess.hs
- compiler/GHC/Parser/PostProcess/Haddock.hs
- + compiler/GHC/Parser/PreProcess.hs
- + compiler/GHC/Parser/PreProcess/Eval.hs
- + compiler/GHC/Parser/PreProcess/Lexer.x
- + compiler/GHC/Parser/PreProcess/Macro.hs
- + compiler/GHC/Parser/PreProcess/ParsePP.hs
- + compiler/GHC/Parser/PreProcess/Parser.y
- + compiler/GHC/Parser/PreProcess/ParserM.hs
- + compiler/GHC/Parser/PreProcess/State.hs
- compiler/GHC/Parser/Utils.hs
- compiler/GHC/Runtime/Loader.hs
- compiler/GHC/SysTools/Cpp.hs
- compiler/GHC/Unit/External.hs
- compiler/GHC/Unit/Finder.hs
- − compiler/GHC/Unit/Module/External/Graph.hs
- compiler/GHC/Unit/Module/Graph.hs
- compiler/GHC/Unit/Module/ModNodeKey.hs
- compiler/ghc.cabal.in
- docs/users_guide/debugging.rst
- ghc/GHCi/UI.hs
- hadrian/src/Rules/SourceDist.hs
- hadrian/stack.yaml.lock
- libraries/base/base.cabal.in
- libraries/base/changelog.md
- − libraries/base/src/GHC/ExecutionStack/Internal.hs
- − libraries/base/src/GHC/TypeLits/Internal.hs
- − libraries/base/src/GHC/TypeNats/Internal.hs
- libraries/ghc-internal/src/GHC/Internal/LanguageExtensions.hs
- testsuite/tests/count-deps/CountDepsParser.stdout
- testsuite/tests/driver/T4437.hs
- testsuite/tests/ghc-api/T11579.hs
- + testsuite/tests/ghc-api/fixed-nodes/InterfaceModuleGraph.hs
- + testsuite/tests/ghc-api/fixed-nodes/InterfaceModuleGraph.stdout
- testsuite/tests/ghc-api/fixed-nodes/all.T
- + testsuite/tests/ghc-cpp/GhcCpp01.hs
- + testsuite/tests/ghc-cpp/GhcCpp01.stderr
- + testsuite/tests/ghc-cpp/all.T
- testsuite/tests/interface-stability/base-exports.stdout
- testsuite/tests/interface-stability/base-exports.stdout-javascript-unknown-ghcjs
- testsuite/tests/interface-stability/base-exports.stdout-mingw32
- testsuite/tests/interface-stability/base-exports.stdout-ws-32
- testsuite/tests/interface-stability/template-haskell-exports.stdout
- testsuite/tests/plugins/defaulting-plugin/DefaultLifted.hs
- + testsuite/tests/printer/CppCommentPlacement.hs
- + utils/check-cpp/.ghci
- + utils/check-cpp/.gitignore
- + utils/check-cpp/Eval.hs
- + utils/check-cpp/Example1.hs
- + utils/check-cpp/Example10.hs
- + utils/check-cpp/Example11.hs
- + utils/check-cpp/Example12.hs
- + utils/check-cpp/Example13.hs
- + utils/check-cpp/Example2.hs
- + utils/check-cpp/Example3.hs
- + utils/check-cpp/Example4.hs
- + utils/check-cpp/Example5.hs
- + utils/check-cpp/Example6.hs
- + utils/check-cpp/Example7.hs
- + utils/check-cpp/Example8.hs
- + utils/check-cpp/Example9.hs
- + utils/check-cpp/Lexer.x
- + utils/check-cpp/Macro.hs
- + utils/check-cpp/Main.hs
- + utils/check-cpp/ParsePP.hs
- + utils/check-cpp/ParseSimulate.hs
- + utils/check-cpp/Parser.y
- + utils/check-cpp/ParserM.hs
- + utils/check-cpp/PreProcess.hs
- + utils/check-cpp/README.md
- + utils/check-cpp/State.hs
- + utils/check-cpp/run.sh
- utils/check-exact/Main.hs
- utils/check-exact/Parsers.hs
- utils/check-exact/Preprocess.hs
- utils/check-exact/Utils.hs
- utils/haddock/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs
- utils/haddock/haddock-api/src/Haddock/Parser.hs
- utils/haddock/haddock-api/src/Haddock/Types.hs
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/6ff34ae924b99fa7abe1f96b174192…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/6ff34ae924b99fa7abe1f96b174192…
You're receiving this email because of your account on gitlab.haskell.org.
1
0