[Git][ghc/ghc][wip/T22719] Document the semantics of pattern bindings a bit better
Simon Peyton Jones (@simonpj)
gitlab at gitlab.haskell.org
Tue Jan 10 22:35:47 UTC 2023
Simon Peyton Jones pushed to branch wip/T22719 at Glasgow Haskell Compiler / GHC
Commits:
8d7abaa6 by Simon Peyton Jones at 2023-01-10T22:36:08+00:00
Document the semantics of pattern bindings a bit better
This MR is in response to the discussion on #22719
- - - - -
6 changed files:
- compiler/GHC/Hs/Utils.hs
- compiler/GHC/HsToCore/Expr.hs
- docs/users_guide/exts/strict.rst
- + testsuite/tests/deSugar/should_compile/T22719.hs
- + testsuite/tests/deSugar/should_compile/T22719.stderr
- testsuite/tests/deSugar/should_compile/all.T
Changes:
=====================================
compiler/GHC/Hs/Utils.hs
=====================================
@@ -86,7 +86,7 @@ module GHC.Hs.Utils(
mkLetStmt,
-- * Collecting binders
- isUnliftedHsBind, isBangedHsBind,
+ isUnliftedHsBind, isUnliftedHsBinds, isBangedHsBind,
collectLocalBinders, collectHsValBinders, collectHsBindListBinders,
collectHsIdBinders,
@@ -905,55 +905,106 @@ to return a [Name] or [Id]. Before renaming the record punning
and wild-card mechanism makes it hard to know what is bound.
So these functions should not be applied to (HsSyn RdrName)
-Note [Unlifted id check in isUnliftedHsBind]
+Note [isUnliftedHsBind]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The function isUnliftedHsBind is used to complain if we make a top-level
-binding for a variable of unlifted type.
+The function isUnliftedHsBind tells if the binding binds a variable of
+unlifted type. e.g.
-Such a binding is illegal if the top-level binding would be unlifted;
-but also if the local letrec generated by desugaring AbsBinds would be.
-E.g.
- f :: Num a => (# a, a #)
- g :: Num a => a -> a
- f = ...g...
- g = ...g...
+ - I# x = blah
+ - Just (I# x) = blah
-The top-level bindings for f,g are not unlifted (because of the Num a =>),
-but the local, recursive, monomorphic bindings are:
+isUnliftedHsBind is used in two ways:
+* To complain if we make a top-level binding for a variable of unlifted
+ type. E.g. any of the above bindings are illegal at top level
+
+* To generate a case expression for a non-recursive local let. E.g.
+ let Just (I# x) = blah in body
+ ==>
+ case blah of Just (I# x) -> body
+ See GHC.HsToCore.Expr.dsUnliftedBind.
+
+Wrinkles:
+
+(W1) For AbsBinds we must check if the local letrec generated by desugaring
+ AbsBinds would be unlifted; so we just recurse into the abs_binds. E.g.
+ f :: Num a => (# a, a #)
+ g :: Num a => a -> a
+ f = ...g...
+ g = ...g...
+
+ The top-level bindings for f,g are not unlifted (because of the Num a =>),
+ but the local, recursive, monomorphic bindings are:
t = /\a \(d:Num a).
letrec fm :: (# a, a #) = ...g...
gm :: a -> a = ...f...
in (fm, gm)
-Here the binding for 'fm' is illegal. So generally we check the abe_mono types.
+ Here the binding for 'fm' is illegal. So we recurse into the abs_binds
+
+(W2) BUT we have a special case when abs_sig is true;
+ see Note [The abs_sig field of AbsBinds] in GHC.Hs.Binds
+
+(W3) isUnliftedHsBind returns False even if the binding itself is
+ unlifted, provided it binds only lifted variables. E.g.
+ - (# a,b #) = (# reverse xs, xs #)
+
+ - x = sqrt# y# :: Float#
+
+ - type Unl :: UnliftedType
+ data Unl = MkUnl Int
+ MkUnl z = blah
-BUT we have a special case when abs_sig is true;
- see Note [The abs_sig field of AbsBinds] in GHC.Hs.Binds
+ In each case the RHS of the "=" has unlifted type, but isUnliftedHsBind
+ returns False. Reason: see GHC Proposal #35
+ https://github.com/ghc-proposals/ghc-proposals/blob/master/
+ proposals/0035-unbanged-strict-patterns.rst
+
+(W4) In particular, (W3) applies to a pattern that binds no variables at all.
+ So { _ = sqrt# y :: Float# } returns False from isUnliftedHsBind, but
+ { x = sqrt# y :: Float# } returns True.
+ This is arguably a bit confusing (see #22719)
-}
----------------- Bindings --------------------------
-- | Should we treat this as an unlifted bind? This will be true for any
-- bind that binds an unlifted variable, but we must be careful around
--- AbsBinds. See Note [Unlifted id check in isUnliftedHsBind]. For usage
+-- AbsBinds. See Note [isUnliftedHsBind]. For usage
-- information, see Note [Strict binds checks] is GHC.HsToCore.Binds.
isUnliftedHsBind :: HsBind GhcTc -> Bool -- works only over typechecked binds
-isUnliftedHsBind bind
- | XHsBindsLR (AbsBinds { abs_exports = exports, abs_sig = has_sig }) <- bind
- = if has_sig
- then any (is_unlifted_id . abe_poly) exports
- else any (is_unlifted_id . abe_mono) exports
+isUnliftedHsBind (XHsBindsLR (AbsBinds { abs_exports = exports
+ , abs_sig = has_sig
+ , abs_binds = binds }))
+ | has_sig = any (is_unlifted_id . abe_poly) exports
+ | otherwise = isUnliftedHsBinds binds
+ -- See wrinkle (W1) and (W2) in Note [isUnliftedHsBind]
-- If has_sig is True we will never generate a binding for abe_mono,
-- so we don't need to worry about it being unlifted. The abe_poly
-- binding might not be: e.g. forall a. Num a => (# a, a #)
+ -- If has_sig is False, just recurse
- | otherwise
- = any is_unlifted_id (collectHsBindBinders CollNoDictBinders bind)
- where
- is_unlifted_id id = isUnliftedType (idType id)
- -- bindings always have a fixed RuntimeRep, so it's OK
- -- to call isUnliftedType here
+isUnliftedHsBind (FunBind { fun_id = L _ fun })
+ = is_unlifted_id fun
+
+isUnliftedHsBind (VarBind { var_id = var })
+ = is_unlifted_id var
+
+isUnliftedHsBind (PatBind { pat_lhs = pat })
+ = any is_unlifted_id (collectPatBinders CollNoDictBinders pat)
+ -- If we changed our view on (W3) you could add
+ -- || isUnliftedType pat_ty
+ -- to this check
+
+isUnliftedHsBind (PatSynBind {}) = panic "isUnliftedBind: PatSynBind"
+
+isUnliftedHsBinds :: LHsBinds GhcTc -> Bool
+isUnliftedHsBinds = anyBag (isUnliftedHsBind . unLoc)
+
+is_unlifted_id :: Id -> Bool
+is_unlifted_id id = isUnliftedType (idType id)
+ -- Bindings always have a fixed RuntimeRep, so it's OK
+ -- to call isUnliftedType here
-- | Is a binding a strict variable or pattern bind (e.g. @!x = ...@)?
isBangedHsBind :: HsBind GhcTc -> Bool
=====================================
compiler/GHC/HsToCore/Expr.hs
=====================================
@@ -197,7 +197,7 @@ dsUnliftedBind (FunBind { fun_id = L l fun
; let rhs' = core_wrap (mkOptTickBox tick rhs)
; return (bindNonRec fun rhs' body) }
-dsUnliftedBind (PatBind {pat_lhs = pat, pat_rhs = grhss
+dsUnliftedBind (PatBind { pat_lhs = pat, pat_rhs = grhss
, pat_ext = (ty, _) }) body
= -- let C x# y# = rhs in body
-- ==> case rhs of C x# y# -> body
=====================================
docs/users_guide/exts/strict.rst
=====================================
@@ -377,20 +377,35 @@ Haskell Report.
Replace the "Translation" there with the following one. Given
``let { bind1 ... bindn } in body``:
+.. admonition:: SPLIT
+
+ Given a binding ``p = e``, where ``p`` is not a variable or a bang pattern,
+ and ``x1...xn`` are the variables bound by ``p``,
+ and all these binders have lifted type:
+ replace the binding with
+ ``v = e; x1 = case v of p -> x1; ...; xn = case v of p -> xn``, where
+ ``v`` is fresh.
+ (If ``e`` is a variable, this can be optimised by not introducing a
+ fresh variable.)
+
.. admonition:: FORCE
- Replace any binding ``!p = e`` with ``v = case e of p -> (x1, ..., xn); (x1, ..., xn) = v`` and replace
+ Given a bang-pattern binding ``!p = e``, where ``x1...xn`` are the variables bound by ``p``,
+ and all these binders have lifted type:
+ replace the binding with ``v = case e of p -> (x1, ..., xn); (x1, ..., xn) = v`` and replace
``body`` with ``v seq body``, where ``v`` is fresh. This translation works fine if
``p`` is already a variable ``x``, but can obviously be optimised by not
- introducing a fresh variable ``v``.
+ introducing a fresh variable ``v``. This transformation is illegal at the top
+ level of a module (since there is no ``body``), so such bindings are rejected.
-.. admonition:: SPLIT
+.. admonition:: CASE
- Replace any binding ``p = e``, where ``p`` is not a variable, with
- ``v = e; x1 = case v of p -> x1; ...; xn = case v of p -> xn``, where
- ``v`` is fresh and ``x1``.. ``xn`` are the bound variables of ``p``.
- Again if ``e`` is a variable, this can be optimised by not introducing a
- fresh variable.
+ Given a pattern binding ``p = e``, where ``x1...xn`` are the variables bound by ``p``,
+ and any of the binders has unlifted type:
+ replace the binding with nothing at all, and replace
+ ``body`` with ``case e of p -> body``.
+ This transformation is illegal at the top
+ level of a module, so such bindings are rejected.
The result will be a (possibly) recursive set of bindings, binding
only simple variables on the left hand side. (One could go one step
@@ -463,6 +478,43 @@ Same again, only with a pattern binding: ::
The final form is just what we want: a simple case expression.
+Rule (FORCE) applies even if the pattern binds no variables::
+
+ let !(True,False) = e in body
+
+ ===> (FORCE)
+ let v = case e of (True,False) -> () in v `seq` body
+
+ ===> (inline, simplify)
+ case e of (True,False) -> body
+
+That is, we force ``e`` and check that it has the right form before proceeding with ``body``.
+
+Note that (CASE) applies only when any of the *binders* is unlifted;
+it is irrelevant whether the binding *itself* is unlifted (see
+`GHC proposal #35 <https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0035-unbanged-strict-patterns.rst>`__).
+For example (see :ref:`primitives`)::
+
+ let (# a::Int, b::Bool #) = e in body
+ ===> (SPLIT)
+ let v = case e of (# a,b #) -> (a,b)
+ a = case v of (a,b) -> a
+ b = case v of (a,b) -> b
+ in body
+
+Here is an example with an unlifted data type::
+
+ type T :: UnliftedType
+ data T = MkT Int
+ f1 x = let MkT y = blah in body1
+ f2 x = let z :: T = blah in body2
+ f3 x = let _ :: T = blah in body3
+
+In ``f1``, even though ``T`` is an unlifted type, the pattern ``MkT y`` binds a lifted
+variable ``y``, so (SPLIT) applies, and ``blah`` is not evaluated until ``body1`` evaluates ``y``.
+In contrast, in ``f2`` the pattern ``z :: T`` binds a variable ``z`` of unlifted type, so (CASE) applies
+and the let-binding is strict. In ``f3`` the pattern binds no variables, so again it is lazy like ``f1``.
+
Here is a recursive case ::
letrec xs :: [Int] -- Recursive
=====================================
testsuite/tests/deSugar/should_compile/T22719.hs
=====================================
@@ -0,0 +1,21 @@
+{-# LANGUAGE UnliftedDatatypes #-}
+{-# OPTIONS_GHC -Wall #-}
+
+module T22719 where
+
+import GHC.Exts
+
+type T :: UnliftedType
+data T = T
+
+f :: Int -> T
+f 0 = T
+f n = f (n-1)
+
+-- ex1 is lazy in (f 7)
+ex1 :: ()
+ex1 = let _ = f 7 in ()
+
+-- ex2 is strict in (f 10)
+ex2 :: ()
+ex2 = let _a = f 10 in ()
=====================================
testsuite/tests/deSugar/should_compile/T22719.stderr
=====================================
@@ -0,0 +1,30 @@
+
+==================== Tidy Core ====================
+Result size of Tidy Core
+ = {terms: 25, types: 10, coercions: 0, joins: 0/0}
+
+-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
+ex1 :: ()
+[GblId, Unf=OtherCon []]
+ex1 = GHC.Tuple.Prim.()
+
+Rec {
+-- RHS size: {terms: 15, types: 5, coercions: 0, joins: 0/0}
+f [Occ=LoopBreaker] :: Int -> T
+[GblId, Arity=1, Unf=OtherCon []]
+f = \ (ds :: Int) ->
+ case ds of wild { I# ds1 ->
+ case ds1 of {
+ __DEFAULT -> f (- @Int GHC.Num.$fNumInt wild (GHC.Types.I# 1#));
+ 0# -> T22719.T
+ }
+ }
+end Rec }
+
+-- RHS size: {terms: 6, types: 1, coercions: 0, joins: 0/0}
+ex2 :: ()
+[GblId]
+ex2 = case f (GHC.Types.I# 10#) of { T -> GHC.Tuple.Prim.() }
+
+
+
=====================================
testsuite/tests/deSugar/should_compile/all.T
=====================================
@@ -112,3 +112,4 @@ test('T16615', normal, compile, ['-ddump-ds -dsuppress-uniques'])
test('T18112', [grep_errmsg('cast')], compile, ['-ddump-ds'])
test('T19969', normal, compile, ['-ddump-simpl -dsuppress-uniques'])
test('T19883', normal, compile, [''])
+test('T22719', normal, compile, ['-ddump-simpl -dsuppress-uniques -dno-typeable-binds'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/8d7abaa6740664c62c0dbd343a202e1b7ca825e4
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/8d7abaa6740664c62c0dbd343a202e1b7ca825e4
You're receiving this email because of your account on gitlab.haskell.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20230110/1dd0d516/attachment-0001.html>
More information about the ghc-commits
mailing list