diff options
Diffstat (limited to 'compiler/GHC/HsToCore')
-rw-r--r-- | compiler/GHC/HsToCore/Match.hs | 20 | ||||
-rw-r--r-- | compiler/GHC/HsToCore/Match/Literal.hs | 146 | ||||
-rw-r--r-- | compiler/GHC/HsToCore/Pmc/Desugar.hs | 34 | ||||
-rw-r--r-- | compiler/GHC/HsToCore/Pmc/Solver/Types.hs | 72 |
4 files changed, 239 insertions, 33 deletions
diff --git a/compiler/GHC/HsToCore/Match.hs b/compiler/GHC/HsToCore/Match.hs index 8576197d4d..425940624b 100644 --- a/compiler/GHC/HsToCore/Match.hs +++ b/compiler/GHC/HsToCore/Match.hs @@ -902,7 +902,7 @@ data PatGroup | PgCon DataCon -- Constructor patterns (incl list, tuple) | PgSyn PatSyn [Type] -- See Note [Pattern synonym groups] | PgLit Literal -- Literal patterns - | PgN Rational -- Overloaded numeric literals; + | PgN FractionalLit -- Overloaded numeric literals; -- see Note [Don't use Literal for PgN] | PgOverS FastString -- Overloaded string literals | PgNpK Integer -- n+k patterns @@ -930,7 +930,7 @@ the invariant that value in a LitInt must be in the range of the target machine's Int# type, and an overloaded literal could meaningfully be larger. Solution: For pattern grouping purposes, just store the literal directly in -the PgN constructor as a Rational if numeric, and add a PgOverStr constructor +the PgN constructor as a FractionalLit if numeric, and add a PgOverStr constructor for overloaded strings. -} @@ -1016,6 +1016,10 @@ sameGroup (PgSyn p1 t1) (PgSyn p2 t2) = p1==p2 && eqTypes t1 t2 -- eqTypes: See Note [Pattern synonym groups] sameGroup (PgLit _) (PgLit _) = True -- One case expression sameGroup (PgN l1) (PgN l2) = l1==l2 -- Order is significant + -- Order is significant, match PgN after PgLit + -- If the exponents are small check for value equality rather than syntactic equality + -- This is implemented in the Eq instance for FractionalLit, we do this to avoid + -- computing the value of excessivly large rationals. sameGroup (PgOverS s1) (PgOverS s2) = s1==s2 sameGroup (PgNpK l1) (PgNpK l2) = l1==l2 -- See Note [Grouping overloaded literal patterns] sameGroup (PgCo t1) (PgCo t2) = t1 `eqType` t2 @@ -1162,12 +1166,12 @@ patGroup _ (WildPat {}) = PgAny patGroup _ (BangPat {}) = PgBang patGroup _ (NPat _ (L _ (OverLit {ol_val=oval})) mb_neg _) = case (oval, isJust mb_neg) of - (HsIntegral i, False) -> PgN (fromInteger (il_value i)) - (HsIntegral i, True ) -> PgN (-fromInteger (il_value i)) - (HsFractional r, False) -> PgN (fl_value r) - (HsFractional r, True ) -> PgN (-fl_value r) - (HsIsString _ s, _) -> ASSERT(isNothing mb_neg) - PgOverS s + (HsIntegral i, is_neg) -> PgN (integralFractionalLit is_neg (il_value i)) + (HsFractional f, is_neg) + | is_neg -> PgN $! negateFractionalLit f + | otherwise -> PgN f + (HsIsString _ s, _) -> ASSERT(isNothing mb_neg) + PgOverS s patGroup _ (NPlusKPat _ _ (L _ (OverLit {ol_val=oval})) _ _ _) = case oval of HsIntegral i -> PgNpK (il_value i) diff --git a/compiler/GHC/HsToCore/Match/Literal.hs b/compiler/GHC/HsToCore/Match/Literal.hs index f4021d2e29..218f2ef35b 100644 --- a/compiler/GHC/HsToCore/Match/Literal.hs +++ b/compiler/GHC/HsToCore/Match/Literal.hs @@ -52,7 +52,6 @@ import GHC.Builtin.Types import GHC.Builtin.Types.Prim import GHC.Types.Literal import GHC.Types.SrcLoc -import Data.Ratio import GHC.Utils.Outputable as Outputable import GHC.Driver.Session import GHC.Utils.Misc @@ -66,6 +65,7 @@ import Data.Int import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NEL import Data.Word +import GHC.Real ( Ratio(..), numerator, denominator ) {- ************************************************************************ @@ -101,22 +101,131 @@ dsLit l = do HsWordPrim _ w -> return (Lit (mkLitWordWrap platform w)) HsInt64Prim _ i -> return (Lit (mkLitInt64Wrap i)) HsWord64Prim _ w -> return (Lit (mkLitWord64Wrap w)) - HsFloatPrim _ f -> return (Lit (LitFloat (fl_value f))) - HsDoublePrim _ d -> return (Lit (LitDouble (fl_value d))) + + -- This can be slow for very large literals. See Note [FractionalLit representation] + -- and #15646 + HsFloatPrim _ fl -> return (Lit (LitFloat (rationalFromFractionalLit fl))) + HsDoublePrim _ fl -> return (Lit (LitDouble (rationalFromFractionalLit fl))) HsChar _ c -> return (mkCharExpr c) HsString _ str -> mkStringExprFS str HsInteger _ i _ -> return (mkIntegerExpr i) HsInt _ i -> return (mkIntExpr platform (il_value i)) - HsRat _ (FL _ _ val) ty -> - return (mkCoreConApps ratio_data_con [Type integer_ty, num, denom]) - where - num = mkIntegerExpr (numerator val) - denom = mkIntegerExpr (denominator val) + HsRat _ fl ty -> dsFractionalLitToRational fl ty + +{- +Note [FractionalLit representation] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +There is a fun wrinkle to this, we used to simply compute the value +for these literals and store it as `Rational`. While this might seem +reasonable it meant typechecking literals of extremely large numbers +wasn't possible. This happend for example in #15646. + +There a user would write in GHCi e.g. `:t 1e1234111111111111111111111` +which would trip up the compiler. The reason being we would parse it as +<Literal of value n>. Try to compute n, which would run out of memory +for truly large numbers, or take far too long for merely large ones. + +To fix this we instead now store the significand and exponent of the +literal instead. Depending on the size of the exponent we then defer +the computation of the Rational value, potentially up to runtime of the +program! There are still cases left were we might compute large rationals +but it's a lot rarer then. + +The current state of affairs for large literals is: +* Typechecking: Will produce a FractionalLit +* Desugaring a large overloaded literal to Float/Double *is* done + at compile time. So can still fail. But this only matters for values too large + to be represented as float anyway. +* Converting overloaded literals to a value of *Rational* is done at *runtime*. + If such a value is then demanded at runtime the program might hang or run out of + memory. But that is perhaps expected and acceptable. +* TH might also evaluate the literal even when overloaded. + But there a user should be able to work around #15646 by + generating a call to `mkRationalBase10/2` for large literals instead. + + +Note [FractionalLit representation] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +For fractional literals, like 1.3 or 0.79e22, we do /not/ represent +them within the compiler as a Rational. Doing so would force the +compiler to compute a huge Rational for 2.3e300000000000, at compile +time (#15646)! + +So instead we represent fractional literals as a FractionalLit, +in which we record the significand and exponent separately. Then +we can compute the huge Rational at /runtime/, by emitting code +for + mkRationalBase10 2.3 300000000000 + +where mkRationalBase10 is defined in the library GHC.Real + +The moving parts are here: + +* Parsing, renaming, typechecking: use FractionalLit, in which the + significand and exponent are represented separately. + +* Desugaring. Remember that a fractional literal like 54.4e20 has type + Fractional a => a + + - For fractional literals whose type turns out to be Float/Double, + we desugar to a Float/Double literal at /compile time/. + This conversion can still fail. But this only matters for values + too large to be represented as float anyway. See dsLit in + GHC.HsToCore.Match.Literal + + - For fractional literals whose type turns out to be Rational, we + desugar the literal to a call of `mkRationalBase10` (etc for hex + literals), so that we only compute the Rational at /run time/. If + this value is then demanded at runtime the program might hang or + run out of memory. But that is perhaps expected and acceptable. + See dsFractionalLitToRational in GHC.HsToCore.Match.Literal + + - For fractional literals whose type isn't one of the above, we just + call the typeclass method `fromRational`. But to do that we need + the rational to give to it, and we compute that at runtime, as + above. + +* Template Haskell definitions are also problematic. While the TH code + works as expected once it's spliced into a program it will compute the + value of the large literal. + But there a user should be able to work around #15646 + by having their TH code generating a call to `mkRationalBase[10/2]` for + large literals instead. + +-} + +-- | See Note [FractionalLit representation] +dsFractionalLitToRational :: FractionalLit -> Type -> DsM CoreExpr +dsFractionalLitToRational fl@FL{ fl_signi = signi, fl_exp = exp, fl_exp_base = base } ty + -- We compute "small" rationals here and now + | abs exp <= 100 + = let !val = rationalFromFractionalLit fl + !num = mkIntegerExpr (numerator val) + !denom = mkIntegerExpr (denominator val) (ratio_data_con, integer_ty) = case tcSplitTyConApp ty of (tycon, [i_ty]) -> ASSERT(isIntegerTy i_ty && tycon `hasKey` ratioTyConKey) (head (tyConDataCons tycon), i_ty) x -> pprPanic "dsLit" (ppr x) + in return $! (mkCoreConApps ratio_data_con [Type integer_ty, num, denom]) + -- Large rationals will be computed at runtime. + | otherwise + = do + let mkRationalName = case base of + Base2 -> mkRationalBase2Name + Base10 -> mkRationalBase10Name + mkRational <- dsLookupGlobalId mkRationalName + litR <- dsRational signi + let litE = mkIntegerExpr exp + return (mkCoreApps (Var mkRational) [litR, litE]) + +dsRational :: Rational -> DsM CoreExpr +dsRational (n :% d) = do + dcn <- dsLookupDataCon ratioDataConName + let cn = mkIntegerExpr n + let dn = mkIntegerExpr d + return $ mkCoreConApps dcn [Type integerTy, cn, dn] + dsOverLit :: HsOverLit GhcTc -> DsM CoreExpr -- ^ Post-typechecker, the 'HsExpr' field of an 'OverLit' contains @@ -128,6 +237,7 @@ dsOverLit (OverLit { ol_val = val, ol_ext = OverLitTc rebindable ty case shortCutLit platform val ty of Just expr | not rebindable -> dsExpr expr -- Note [Literal short cut] _ -> dsExpr witness + {- Note [Literal short cut] ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -540,15 +650,17 @@ hsLitKey :: Platform -> HsLit GhcTc -> Literal -- In the case of the fixed-width numeric types, we need to wrap here -- because Literal has an invariant that the literal is in range, while -- HsLit does not. -hsLitKey platform (HsIntPrim _ i) = mkLitIntWrap platform i -hsLitKey platform (HsWordPrim _ w) = mkLitWordWrap platform w -hsLitKey _ (HsInt64Prim _ i) = mkLitInt64Wrap i -hsLitKey _ (HsWord64Prim _ w) = mkLitWord64Wrap w -hsLitKey _ (HsCharPrim _ c) = mkLitChar c -hsLitKey _ (HsFloatPrim _ f) = mkLitFloat (fl_value f) -hsLitKey _ (HsDoublePrim _ d) = mkLitDouble (fl_value d) -hsLitKey _ (HsString _ s) = LitString (bytesFS s) -hsLitKey _ l = pprPanic "hsLitKey" (ppr l) +hsLitKey platform (HsIntPrim _ i) = mkLitIntWrap platform i +hsLitKey platform (HsWordPrim _ w) = mkLitWordWrap platform w +hsLitKey _ (HsInt64Prim _ i) = mkLitInt64Wrap i +hsLitKey _ (HsWord64Prim _ w) = mkLitWord64Wrap w +hsLitKey _ (HsCharPrim _ c) = mkLitChar c +-- This following two can be slow. See Note [FractionalLit representation] +hsLitKey _ (HsFloatPrim _ fl) = mkLitFloat (rationalFromFractionalLit fl) +hsLitKey _ (HsDoublePrim _ fl) = mkLitDouble (rationalFromFractionalLit fl) + +hsLitKey _ (HsString _ s) = LitString (bytesFS s) +hsLitKey _ l = pprPanic "hsLitKey" (ppr l) {- ************************************************************************ diff --git a/compiler/GHC/HsToCore/Pmc/Desugar.hs b/compiler/GHC/HsToCore/Pmc/Desugar.hs index 1abe0fc9dc..f69600bf04 100644 --- a/compiler/GHC/HsToCore/Pmc/Desugar.hs +++ b/compiler/GHC/HsToCore/Pmc/Desugar.hs @@ -29,6 +29,7 @@ import GHC.Types.Id import GHC.Core.ConLike import GHC.Types.Name import GHC.Builtin.Types +import GHC.Builtin.Names (rationalTyConName) import GHC.Types.SrcLoc import GHC.Utils.Outputable import GHC.Utils.Panic @@ -47,12 +48,14 @@ import GHC.Core.Type import GHC.Data.Maybe import qualified GHC.LanguageExtensions as LangExt import GHC.Utils.Monad (concatMapM) - +import GHC.Types.SourceText (FractionalLit(..)) import Control.Monad (zipWithM) import Data.List (elemIndex) import Data.List.NonEmpty ( NonEmpty(..) ) import qualified Data.List.NonEmpty as NE +-- import GHC.Driver.Ppr + -- | Smart constructor that eliminates trivial lets mkPmLetVar :: Id -> Id -> [PmGrd] mkPmLetVar x y | x == y = [] @@ -199,13 +202,34 @@ desugarPat x pat = case pat of -- short cutting in dsOverLit works properly) is overloaded iff either is. dflags <- getDynFlags let platform = targetPlatform dflags - core_expr <- case olit of + pm_lit <- case olit of OverLit{ ol_val = val, ol_ext = OverLitTc rebindable _ } | not rebindable , Just expr <- shortCutLit platform val ty - -> dsExpr expr - _ -> dsOverLit olit - let lit = expectJust "failed to detect OverLit" (coreExprAsPmLit core_expr) + -> coreExprAsPmLit <$> dsExpr expr + | not rebindable + , (HsFractional f) <- val + , negates <- if fl_neg f then 1 else 0 + -> do + rat_tc <- dsLookupTyCon rationalTyConName + let rat_ty = mkTyConTy rat_tc + return $ Just $ PmLit rat_ty (PmLitOverRat negates f) + | otherwise + -> do + dsLit <- dsOverLit olit + let !pmLit = coreExprAsPmLit dsLit :: Maybe PmLit + -- pprTraceM "desugarPat" + -- ( + -- text "val" <+> ppr val $$ + -- text "witness" <+> ppr (ol_witness olit) $$ + -- text "dsLit" <+> ppr dsLit $$ + -- text "asPmLit" <+> ppr pmLit + -- ) + return pmLit + + let lit = case pm_lit of + Just l -> l + Nothing -> pprPanic "failed to detect OverLit" (ppr olit) let lit' = case mb_neg of Just _ -> expectJust "failed to negate lit" (negatePmLit lit) Nothing -> lit diff --git a/compiler/GHC/HsToCore/Pmc/Solver/Types.hs b/compiler/GHC/HsToCore/Pmc/Solver/Types.hs index 26a2eaef79..1e4e672583 100644 --- a/compiler/GHC/HsToCore/Pmc/Solver/Types.hs +++ b/compiler/GHC/HsToCore/Pmc/Solver/Types.hs @@ -62,12 +62,17 @@ import GHC.Builtin.Types.Prim import GHC.Tc.Solver.Monad (InertSet, emptyInert) import GHC.Tc.Utils.TcType (isStringTy) import GHC.Types.CompleteMatch (CompleteMatch) +import GHC.Types.SourceText (mkFractionalLit, FractionalLit, fractionalLitFromRational, + FractionalExponentBase(..), SourceText(..)) import Numeric (fromRat) import Data.Foldable (find) import Data.Ratio +import GHC.Real (Ratio(..)) import qualified Data.Semigroup as Semi +-- import GHC.Driver.Ppr + -- -- * Normalised refinement types -- @@ -293,7 +298,7 @@ data PmLitValue -- lists | PmLitString FastString | PmLitOverInt Int {- How often Negated? -} Integer - | PmLitOverRat Int {- How often Negated? -} Rational + | PmLitOverRat Int {- How often Negated? -} FractionalLit | PmLitOverString FastString -- | Undecidable semantic equality result. @@ -523,10 +528,11 @@ overloadPmLit :: Type -> PmLit -> Maybe PmLit overloadPmLit ty (PmLit _ v) = PmLit ty <$> go v where go (PmLitInt i) = Just (PmLitOverInt 0 i) - go (PmLitRat r) = Just (PmLitOverRat 0 r) + go (PmLitRat r) = Just $! PmLitOverRat 0 $! fractionalLitFromRational r go (PmLitString s) | ty `eqType` stringTy = Just v | otherwise = Just (PmLitOverString s) + go ovRat@PmLitOverRat{} = Just ovRat go _ = Nothing pmLitAsStringLit :: PmLit -> Maybe FastString @@ -555,9 +561,30 @@ coreExprAsPmLit e = case collectArgs e of -> literalToPmLit (literalType l) l >>= overloadPmLit (exprType e) (Var x, args) -- See Note [Detecting overloaded literals with -XRebindableSyntax] + -- fromRational <expr> | is_rebound_name x fromRationalName , [r] <- dropWhile (not . is_ratio) args -> coreExprAsPmLit r >>= overloadPmLit (exprType e) + + --Rationals with large exponents + (Var x, args) + -- See Note [Detecting overloaded literals with -XRebindableSyntax] + -- See Note [Dealing with rationals with large exponents] + -- mkRationalBase* <rational> <exponent> + | Just exp_base <- is_larg_exp_ratio x + , [r, Lit exp] <- dropWhile (not . is_ratio) args + , (Var x, [_ty, Lit n, Lit d]) <- collectArgs r + , Just dc <- isDataConWorkId_maybe x + , dataConName dc == ratioDataConName + -> do + n' <- isLitValue_maybe n + d' <- isLitValue_maybe d + exp' <- isLitValue_maybe exp + let rational = (abs n') :% d' + let neg = if n' < 0 then 1 else 0 + let frac = mkFractionalLit NoSourceText False rational exp' exp_base + Just $ PmLit (exprType e) (PmLitOverRat neg frac) + (Var x, args) | is_rebound_name x fromStringName -- See Note [Detecting overloaded literals with -XRebindableSyntax] @@ -573,6 +600,7 @@ coreExprAsPmLit e = case collectArgs e of (Var x, [Lit l]) | idName x `elem` [unpackCStringName, unpackCStringUtf8Name] -> literalToPmLit stringTy l + _ -> Nothing where is_lit Lit{} = True @@ -583,6 +611,14 @@ coreExprAsPmLit e = case collectArgs e of = tyConName tc == ratioTyConName | otherwise = False + is_larg_exp_ratio x + | is_rebound_name x mkRationalBase10Name + = Just Base10 + | is_rebound_name x mkRationalBase2Name + = Just Base2 + | otherwise + = Nothing + -- See Note [Detecting overloaded literals with -XRebindableSyntax] is_rebound_name :: Id -> Name -> Bool @@ -601,6 +637,36 @@ type `String`). The same applies to other overloaded literals, such as overloaded rationals (`fromRational`)and overloaded integer literals (`fromInteger`). + +Note [Dealing with rationals with large exponents] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Rationals with large exponents are *not* desugared to +a simple rational. As that would require us to compute +their value which can be expensive. Rather they desugar +to an expression. For example 1e1000 will desugar to an +expression of the form: `mkRationalWithExponentBase10 (1 :% 1) 1000` + +Only overloaded literals desugar to this form however, so we +we can just return a overloaded rational literal. + +The most complex case is if we have RebindableSyntax enabled. +By example if we have a pattern like this: `f 3.3 = True` + +It will desugar to: + fromRational + [TYPE: Rational, mkRationalBase10 (:% @Integer 10 1) (-1)] + +The fromRational is properly detected as an overloaded Rational by +coreExprAsPmLit and it's general code for detecting overloaded rationals. +See Note [Detecting overloaded literals with -XRebindableSyntax]. + +This case then recurses into coreExprAsPmLit passing only the expression +`mkRationalBase10 (:% @Integer 10 1) (-1)`. Which is caught by rationals +with large exponents case. This will return a `PmLitOverRat` literal. + +Which is then passed to overloadPmLit which simply returns it as-is since +it's already overloaded. + -} instance Outputable PmLitValue where @@ -609,7 +675,7 @@ instance Outputable PmLitValue where ppr (PmLitChar c) = pprHsChar c ppr (PmLitString s) = pprHsString s ppr (PmLitOverInt n i) = minuses n (ppr i) - ppr (PmLitOverRat n r) = minuses n (ppr (double (fromRat r))) + ppr (PmLitOverRat n r) = minuses n (ppr r) ppr (PmLitOverString s) = pprHsString s -- Take care of negated literals |