[Git][ghc/ghc][wip/marge_bot_batch_merge_job] 3 commits: Improve documentation around IOException and ioe_filename

Marge Bot (@marge-bot) gitlab at gitlab.haskell.org
Mon Jul 31 09:00:18 UTC 2023



Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC


Commits:
42aa7fbd by Julian Ospald at 2023-07-30T17:22:01-04:00
Improve documentation around IOException and ioe_filename

See:

* https://github.com/haskell/core-libraries-committee/issues/189
* https://github.com/haskell/unix/pull/279
* https://github.com/haskell/unix/pull/289

- - - - -
db54e894 by Matthew Craven at 2023-07-31T04:58:53-04:00
Adjust and clarify handling of primop effects

The existing "can_fail" and "has_side_effects" primop attributes that
previously governed this were used in inconsistent and confusingly-
documented ways, especially with regard to raising exceptions.  This
patch replaces them with a single "effect" attribute, with four
possible values (NoEffect, CanFail, ThrowsException, ReadWriteEffect)
as described in Note [Classifying primop effects].

A substantial amount of related documentation has been re-drafted for
clarity and accuracy.

In the process of making this attribute format change for literally
every primop, several existing mis-classifications were detected and
corrected.

New primop attributes "cheap" and "work_free" were
also added, and used in the obvious places.

In view of their actual meaning and uses, `primOpOkForSideEffects` and
`exprOkForSideEffects` have been renamed to `primOpOkToDiscard` and
`exprOkToDiscard`, respectively.

- - - - -
2810170d by Sylvain Henry at 2023-07-31T04:59:04-04:00
JS: implement getMonotonicTime (fix #23687)

- - - - -


27 changed files:

- compiler/GHC/Builtin/PrimOps.hs
- compiler/GHC/Builtin/primops.txt.pp
- compiler/GHC/Core.hs
- compiler/GHC/Core/Opt/ConstantFold.hs
- compiler/GHC/Core/Opt/FloatIn.hs
- compiler/GHC/Core/Opt/SetLevels.hs
- compiler/GHC/Core/Opt/Simplify/Iteration.hs
- compiler/GHC/Core/Utils.hs
- compiler/GHC/Types/Demand.hs
- compiler/Setup.hs
- hadrian/src/Rules/Generate.hs
- hadrian/src/Rules/Lint.hs
- hadrian/src/Settings/Builders/GenPrimopCode.hs
- libraries/base/GHC/Clock.hsc
- libraries/base/GHC/Conc/POSIX.hs
- libraries/base/GHC/IO/Exception.hs
- libraries/base/Unsafe/Coerce.hs
- + libraries/base/tests/T23687.hs
- libraries/base/tests/all.T
- + testsuite/tests/ghc-api/PrimOpEffect_Sanity.hs
- testsuite/tests/ghc-api/all.T
- utils/genprimopcode/AccessOps.hs
- utils/genprimopcode/Lexer.x
- utils/genprimopcode/Main.hs
- utils/genprimopcode/Parser.y
- utils/genprimopcode/ParserM.hs
- utils/genprimopcode/Syntax.hs


Changes:

=====================================
compiler/GHC/Builtin/PrimOps.hs
=====================================
@@ -17,10 +17,12 @@ module GHC.Builtin.PrimOps (
         tagToEnumKey,
 
         primOpOutOfLine, primOpCodeSize,
-        primOpOkForSpeculation, primOpOkForSideEffects,
-        primOpIsCheap, primOpFixity, primOpDocs,
+        primOpOkForSpeculation, primOpOkToDiscard,
+        primOpIsWorkFree, primOpIsCheap, primOpFixity, primOpDocs,
         primOpIsDiv, primOpIsReallyInline,
 
+        PrimOpEffect(..), primOpEffect,
+
         getPrimOpResultInfo,  isComparisonPrimOp, PrimOpResultInfo(..),
 
         PrimCall(..)
@@ -311,221 +313,316 @@ primOpOutOfLine :: PrimOp -> Bool
 *                                                                      *
 ************************************************************************
 
-Note [Checking versus non-checking primops]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-  In GHC primops break down into two classes:
-
-   a. Checking primops behave, for instance, like division. In this
-      case the primop may throw an exception (e.g. division-by-zero)
-      and is consequently is marked with the can_fail flag described below.
-      The ability to fail comes at the expense of precluding some optimizations.
-
-   b. Non-checking primops behavior, for instance, like addition. While
-      addition can overflow it does not produce an exception. So can_fail is
-      set to False, and we get more optimisation opportunities.  But we must
-      never throw an exception, so we cannot rewrite to a call to error.
-
-  It is important that a non-checking primop never be transformed in a way that
-  would cause it to bottom. Doing so would violate Core's let-can-float invariant
-  (see Note [Core let-can-float invariant] in GHC.Core) which is critical to
-  the simplifier's ability to float without fear of changing program meaning.
-
-
-Note [PrimOp can_fail and has_side_effects]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Both can_fail and has_side_effects mean that the primop has
-some effect that is not captured entirely by its result value.
-
-----------  has_side_effects ---------------------
-A primop "has_side_effects" if it has some side effect, visible
-elsewhere, apart from the result it returns
-    - reading or writing to the world (I/O)
-    - reading or writing to a mutable data structure (writeIORef)
-    - throwing a synchronous Haskell exception
-
-Often such primops have a type like
-   State -> input -> (State, output)
-so the state token guarantees ordering.  In general we rely on
-data dependencies of the state token to enforce write-effect ordering,
-but as the notes below make clear, the matter is a bit more complicated
-than that.
-
- * NB1: if you inline unsafePerformIO, you may end up with
-   side-effecting ops whose 'state' output is discarded.
-   And programmers may do that by hand; see #9390.
-   That is why we (conservatively) do not discard write-effecting
-   primops even if both their state and result is discarded.
-
- * NB2: We consider primops, such as raiseIO#, that can raise a
-   (Haskell) synchronous exception to "have_side_effects" but not
-   "can_fail".  We must be careful about not discarding such things;
-   see the paper "A semantics for imprecise exceptions".
-
- * NB3: *Read* effects on *mutable* cells (like reading an IORef or a
-   MutableArray#) /are/ included.  You may find this surprising because it
-   doesn't matter if we don't do them, or do them more than once.  *Sequencing*
-   is maintained by the data dependency of the state token.  But see
-   "Duplication" below under
-   Note [Transformations affected by can_fail and has_side_effects]
-
-   Note that read operations on *immutable* values (like indexArray#) do not
-   have has_side_effects.   (They might be marked can_fail, however, because
-   you might index out of bounds.)
-
-   Using has_side_effects in this way is a bit of a blunt instrument.  We could
-   be more refined by splitting read and write effects (see comments with #3207
-   and #20195)
-
-----------  can_fail ----------------------------
-A primop "can_fail" if it can fail with an *unchecked* exception on
-some elements of its input domain. Main examples:
-   division (fails on zero denominator)
-   array indexing (fails if the index is out of bounds)
-
-An "unchecked exception" is one that is an outright error, (not
-turned into a Haskell exception,) such as seg-fault or
-divide-by-zero error.  Such can_fail primops are ALWAYS surrounded
-with a test that checks for the bad cases, but we need to be
-very careful about code motion that might move it out of
-the scope of the test.
-
-Note [Transformations affected by can_fail and has_side_effects]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The can_fail and has_side_effects properties have the following effect
-on program transformations.  Summary table is followed by details.
-
-            can_fail     has_side_effects
-Discard        YES           NO
-Float in       YES           YES
-Float out      NO            NO
-Duplicate      YES           NO
-
-* Discarding.   case (a `op` b) of _ -> rhs  ===>   rhs
-  You should not discard a has_side_effects primop; e.g.
-     case (writeIntArray# a i v s of (# _, _ #) -> True
-  Arguably you should be able to discard this, since the
-  returned stat token is not used, but that relies on NEVER
-  inlining unsafePerformIO, and programmers sometimes write
-  this kind of stuff by hand (#9390).  So we (conservatively)
-  never discard a has_side_effects primop.
-
-  However, it's fine to discard a can_fail primop.  For example
-     case (indexIntArray# a i) of _ -> True
-  We can discard indexIntArray#; it has can_fail, but not
-  has_side_effects; see #5658 which was all about this.
-  Notice that indexIntArray# is (in a more general handling of
-  effects) read effect, but we don't care about that here, and
-  treat read effects as *not* has_side_effects.
-
-  Similarly (a `/#` b) can be discarded.  It can seg-fault or
-  cause a hardware exception, but not a synchronous Haskell
-  exception.
-
-
-
-  Synchronous Haskell exceptions, e.g. from raiseIO#, are treated
-  as has_side_effects and hence are not discarded.
-
-* Float in.  You can float a can_fail or has_side_effects primop
-  *inwards*, but not inside a lambda (see Duplication below).
-
-* Float out.  You must not float a can_fail primop *outwards* lest
-  you escape the dynamic scope of the test.  Example:
+
+Note [Exceptions: asynchronous, synchronous, and unchecked]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+There are three very different sorts of things in GHC-Haskell that are
+sometimes called exceptions:
+
+* Haskell exceptions:
+
+  These are ordinary exceptions that users can raise with the likes
+  of 'throw' and handle with the likes of 'catch'.  They come in two
+  very different flavors:
+
+  * Asynchronous exceptions:
+    * These can arise at nearly any time, and may have nothing to do
+      with the code being executed.
+    * The compiler itself mostly doesn't need to care about them.
+    * Examples: a signal from another process, running out of heap or stack
+    * Even pure code can receive asynchronous exceptions; in this
+      case, executing the same code again may lead to different
+      results, because the exception may not happen next time.
+    * See rts/RaiseAsync.c for the gory details of how they work.
+
+  * Synchronous exceptions:
+    * These are produced by the code being executed, most commonly via
+      a call to the `raise#` or `raiseIO#` primops.
+    * At run-time, if a piece of pure code raises a synchronous
+      exception, it will always raise the same synchronous exception
+      if it is run again (and not interrupted by an asynchronous
+      exception).
+    * In particular, if an updatable thunk does some work and then
+      raises a synchronous exception, it is safe to overwrite it with
+      a thunk that /immediately/ raises the same exception.
+    * Although we are careful not to discard synchronous exceptions,
+      we are very liberal about re-ordering them with respect to other
+      operations.  See the paper "A semantics for imprecise exceptions"
+      as well as Note [Precise exceptions and strictness analysis] in
+      GHC.Types.Demand.
+
+* Unchecked exceptions:
+
+  * These are nasty failures like seg-faults or primitive Int# division
+    by zero.  They differ from Haskell exceptions in that they are
+    un-recoverable and typically bring execution to an immediate halt.
+  * We generally treat unchecked exceptions as undefined behavior, on
+    the assumption that the programmer never intends to crash the
+    program in this way.  Thus we have no qualms about replacing a
+    division-by-zero with a recoverable Haskell exception or
+    discarding an indexArray# operation whose result is unused.
+
+
+Note [Classifying primop effects]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Each primop has an associated 'PrimOpEffect', based on what that
+primop can or cannot do at runtime.  This classification is
+
+* Recorded in the 'effect' field in primops.txt.pp, and
+* Exposed to the compiler via the 'primOpEffect' function in this module.
+
+See Note [Transformations affected by primop effects] for how we make
+use of this categorisation.
+
+The meanings of the four constructors of 'PrimOpEffect' are as
+follows, in decreasing order of permissiveness:
+
+* ReadWriteEffect
+    A primop is marked ReadWriteEffect if it can
+    - read or write to the world (I/O), or
+    - read or write to a mutable data structure (e.g. readMutVar#).
+
+    Every such primop uses State# tokens for sequencing, with a type like:
+      Inputs -> State# s -> (# State# s, Outputs #)
+    The state token threading expresses ordering, but duplicating even
+    a read-only effect would defeat this.  (See "duplication" under
+    Note [Transformations affected by primop effects] for details.)
+
+    Note that operations like `indexArray#` that read *immutable*
+    data structures do not need such special sequencing-related care,
+    and are therefore not marked ReadWriteEffect.
+
+* ThrowsException
+    A primop is marked ThrowsException if
+    - it is not marked ReadWriteEffect, and
+    - it may diverge or throw a synchronous Haskell exception
+      even when used in a "correct" and well-specified way.
+
+    See also Note [Exceptions: asynchronous, synchronous, and unchecked].
+    Examples include raise#, raiseIO#, dataToTag#, and seq#.
+
+    Note that whether an exception is considered precise or imprecise
+    does not matter for the purposes of the PrimOpEffect flag.
+
+* CanFail
+    A primop is marked CanFail if
+    - it is not marked ReadWriteEffect or ThrowsException, and
+    - it can trigger a (potentially-unchecked) exception when used incorrectly.
+
+    See Note [Exceptions: asynchronous, synchronous, and unchecked].
+    Examples include quotWord# and indexIntArray#, which can fail with
+    division-by-zero and a segfault respectively.
+
+    A correct use of a CanFail primop is usually surrounded by a test
+    that screens out the bad cases such as a zero divisor or an
+    out-of-bounds array index.  We must take care never to move a
+    CanFail primop outside the scope of such a test.
+
+* NoEffect
+    A primop is marked NoEffect if it does not belong to any of the
+    other three categories.  We can very aggressively shuffle these
+    operations around without fear of changing a program's meaning.
+
+    Perhaps surprisingly, this aggressive shuffling imposes another
+    restriction: The tricky NoEffect primop uncheckedShiftLWord32# has
+    an undefined result when the provided shift amount is not between
+    0 and 31.  Thus, a call like `uncheckedShiftLWord32# x 95#` is
+    obviously invalid.  But since uncheckedShiftLWord32# is marked
+    NoEffect, we may float such an invalid call out of a dead branch
+    and speculatively evaluate it.
+
+    In particular, we cannot safely rewrite such an invalid call to a
+    runtime error; we must emit code that produces a valid Word32#.
+    (If we're lucky, Core Lint may complain that the result of such a
+    rewrite violates the let-can-float invariant (#16742), but the
+    rewrite is always wrong!)  See also Note [Guarding against silly shifts]
+    in GHC.Core.Opt.ConstantFold.
+
+    Marking uncheckedShiftLWord32# as CanFail instead of NoEffect
+    would give us the freedom to rewrite such invalid calls to runtime
+    errors, but would get in the way of optimization: When speculatively
+    executing a bit-shift prevents the allocation of a thunk, that's a
+    big win.
+
+
+Note [Transformations affected by primop effects]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The PrimOpEffect properties have the following effect on program
+transformations.  The summary table is followed by details.  See also
+Note [Classifying primop effects] for exactly what each column means.
+
+                    NoEffect    CanFail    ThrowsException    ReadWriteEffect
+Discard                YES        YES            NO                 NO
+Defer (float in)       YES        YES           SAFE               SAFE
+Speculate (float out)  YES        NO             NO                 NO
+Duplicate              YES        YES            YES                NO
+
+(SAFE means we could perform the transformation but do not.)
+
+* Discarding:   case (a `op` b) of _ -> rhs  ===>   rhs
+    You should not discard a ReadWriteEffect primop; e.g.
+       case (writeIntArray# a i v s of (# _, _ #) -> True
+    One could argue in favor of discarding this, since the returned
+    State# token is not used.  But in practice unsafePerformIO can
+    easily produce similar code, and programmers sometimes write this
+    kind of stuff by hand (#9390).  So we (conservatively) never discard
+    a ReadWriteEffect primop.
+
+      Digression: We could try to track read-only effects separately
+      from write effects to allow the former to be discarded.  But in
+      fact we want a more general rewrite for read-only operations:
+        case readOp# state# of (# newState#, _unused_result #) -> body
+        ==> case state# of newState# -> body
+      Such a rewrite is not yet implemented, but would have to be done
+      in a different place anyway.
+
+    Discarding a ThrowsException primop would also discard any exception
+    it might have thrown.  For `raise#` or `raiseIO#` this would defeat
+    the whole point of the primop, while for `dataToTag#` or `seq#` this
+    would make programs unexpectly lazier.
+
+    However, it's fine to discard a CanFail primop.  For example
+       case (indexIntArray# a i) of _ -> True
+    We can discard indexIntArray# here; this came up in #5658.  Notice
+    that CanFail primops like indexIntArray# can only trigger an
+    exception when used incorrectly, i.e. a call that might not succeed
+    is undefined behavior anyway.
+
+* Deferring (float-in):
+    See Note [Floating primops] in GHC.Core.Opt.FloatIn.
+
+    In the absence of data dependencies (including state token threading),
+    we reserve the right to re-order the following things arbitrarily:
+      * Side effects
+      * Imprecise exceptions
+      * Divergent computations (infinite loops)
+    This lets us safely float almost any primop *inwards*, but not
+    inside a (multi-shot) lambda.  (See "Duplication" below.)
+
+    However, the main reason to float-in a primop application would be
+    to discard it (by floating it into some but not all branches of a
+    case), so we actually only float-in NoEffect and CanFail operations.
+    See also Note [Floating primops] in GHC.Core.Opt.FloatIn.
+
+    (This automatically side-steps the question of precise exceptions, which
+    mustn't be re-ordered arbitrarily but need at least ThrowsException.)
+
+* Speculation (strict float-out):
+    You must not float a CanFail primop *outwards* lest it escape the
+    dynamic scope of a run-time validity test.  Example:
       case d ># 0# of
         True  -> case x /# d of r -> r +# 1
         False -> 0
-  Here we must not float the case outwards to give
+    Here we must not float the case outwards to give
       case x/# d of r ->
       case d ># 0# of
         True  -> r +# 1
         False -> 0
+    Otherwise, if this block is reached when d is zero, it will crash.
+    Exactly the same reasoning applies to ThrowsException primops.
 
-  Nor can you float out a has_side_effects primop.  For example:
+    Nor can you float out a ReadWriteEffect primop.  For example:
        if blah then case writeMutVar# v True s0 of (# s1 #) -> s1
                else s0
-  Notice that s0 is mentioned in both branches of the 'if', but
-  only one of these two will actually be consumed.  But if we
-  float out to
+    Notice that s0 is mentioned in both branches of the 'if', but
+    only one of these two will actually be consumed.  But if we
+    float out to
       case writeMutVar# v True s0 of (# s1 #) ->
       if blah then s1 else s0
-  the writeMutVar will be performed in both branches, which is
-  utterly wrong.
-
-* Duplication.  You cannot duplicate a has_side_effect primop.  You
-  might wonder how this can occur given the state token threading, but
-  just look at Control.Monad.ST.Lazy.Imp.strictToLazy!  We get
-  something like this
+    the writeMutVar will be performed in both branches, which is
+    utterly wrong.
+
+    What about a read-only operation that cannot fail, like
+    readMutVar#?  In principle we could safely float these out.  But
+    there are not very many such operations and it's not clear if
+    there are real-world programs that would benefit from this.
+
+* Duplication:
+    You cannot duplicate a ReadWriteEffect primop.  You might wonder
+    how this can occur given the state token threading, but just look
+    at Control.Monad.ST.Lazy.Imp.strictToLazy!  We get something like this
         p = case readMutVar# s v of
               (# s', r #) -> (State# s', r)
         s' = case p of (s', r) -> s'
         r  = case p of (s', r) -> r
 
-  (All these bindings are boxed.)  If we inline p at its two call
-  sites, we get a catastrophe: because the read is performed once when
-  s' is demanded, and once when 'r' is demanded, which may be much
-  later.  Utterly wrong.  #3207 is real example of this happening.
+    (All these bindings are boxed.)  If we inline p at its two call
+    sites, we get a catastrophe: because the read is performed once when
+    s' is demanded, and once when 'r' is demanded, which may be much
+    later.  Utterly wrong.  #3207 is real example of this happening.
+    Floating p into a multi-shot lambda would be wrong for the same reason.
+
+    However, it's fine to duplicate a CanFail or ThrowsException primop.
 
-  However, it's fine to duplicate a can_fail primop.  That is really
-  the only difference between can_fail and has_side_effects.
 
-Note [Implementation: how can_fail/has_side_effects affect transformations]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Note [Implementation: how PrimOpEffect affects transformations]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 How do we ensure that floating/duplication/discarding are done right
 in the simplifier?
 
-Two main predicates on primops test these flags:
-  primOpOkForSideEffects <=> not has_side_effects
-  primOpOkForSpeculation <=> not (has_side_effects || can_fail)
+Several predicates on primops test this flag:
+  primOpOkToDiscard      <=> effect < ThrowsException
+  primOpOkForSpeculation <=> effect == NoEffect && not (out_of_line)
+  primOpIsCheap          <=> cheap  -- ...defaults to primOpOkForSpeculation
+    [[But note that the raise# family and seq# are also considered cheap in
+      GHC.Core.Utils.exprIsCheap by way of being work-free]]
+
+  * The discarding mentioned above happens in
+    GHC.Core.Opt.Simplify.Iteration, specifically in rebuildCase,
+    where it is guarded by exprOkToDiscard, which in turn checks
+    primOpOkToDiscard.
 
   * The "no-float-out" thing is achieved by ensuring that we never
-    let-bind a can_fail or has_side_effects primop.  The RHS of a
-    let-binding (which can float in and out freely) satisfies
-    exprOkForSpeculation; this is the let-can-float invariant.  And
-    exprOkForSpeculation is false of can_fail and has_side_effects.
+    let-bind a saturated primop application unless it has NoEffect.
+    The RHS of a let-binding (which can float in and out freely)
+    satisfies exprOkForSpeculation; this is the let-can-float
+    invariant.  And exprOkForSpeculation is false of a saturated
+    primop application unless it has NoEffect.
 
-  * So can_fail and has_side_effects primops will appear only as the
+  * So primops that aren't NoEffect will appear only as the
     scrutinees of cases, and that's why the FloatIn pass is capable
     of floating case bindings inwards.
 
-  * The no-duplicate thing is done via primOpIsCheap, by making
-    has_side_effects things (very very very) not-cheap!
+  * Duplication via inlining and float-in of (lifted) let-binders is
+    controlled via primOpIsWorkFree and primOpIsCheap, by making
+    ReadWriteEffect things (among others) not-cheap!  (The test
+    PrimOpEffect_Sanity will complain if any ReadWriteEffect primop
+    is considered either work-free or cheap.)  Additionally, a
+    case binding is only floated inwards if its scrutinee is ok-to-discard.
 -}
 
-primOpHasSideEffects :: PrimOp -> Bool
-#include "primop-has-side-effects.hs-incl"
+primOpEffect :: PrimOp -> PrimOpEffect
+#include "primop-effects.hs-incl"
 
-primOpCanFail :: PrimOp -> Bool
-#include "primop-can-fail.hs-incl"
+data PrimOpEffect
+  -- See Note [Classifying primop effects]
+  = NoEffect
+  | CanFail
+  | ThrowsException
+  | ReadWriteEffect
+  deriving (Eq, Ord)
 
 primOpOkForSpeculation :: PrimOp -> Bool
-  -- See Note [PrimOp can_fail and has_side_effects]
+  -- See Note [Classifying primop effects]
   -- See comments with GHC.Core.Utils.exprOkForSpeculation
-  -- primOpOkForSpeculation => primOpOkForSideEffects
+  -- primOpOkForSpeculation => primOpOkToDiscard
 primOpOkForSpeculation op
-  =  primOpOkForSideEffects op
-  && not (primOpOutOfLine op || primOpCanFail op)
+  = primOpEffect op == NoEffect && not (primOpOutOfLine op)
     -- I think the "out of line" test is because out of line things can
     -- be expensive (eg sine, cosine), and so we may not want to speculate them
 
-primOpOkForSideEffects :: PrimOp -> Bool
-primOpOkForSideEffects op
-  = not (primOpHasSideEffects op)
+primOpOkToDiscard :: PrimOp -> Bool
+primOpOkToDiscard op
+  = primOpEffect op < ThrowsException
 
-{-
-Note [primOpIsCheap]
-~~~~~~~~~~~~~~~~~~~~
-
- at primOpIsCheap@, as used in GHC.Core.Opt.Simplify.Utils.  For now (HACK
-WARNING), we just borrow some other predicates for a
-what-should-be-good-enough test.  "Cheap" means willing to call it more
-than once, and/or push it inside a lambda.  The latter could change the
-behaviour of 'seq' for primops that can fail, so we don't treat them as cheap.
--}
+primOpIsWorkFree :: PrimOp -> Bool
+#include "primop-is-work-free.hs-incl"
 
 primOpIsCheap :: PrimOp -> Bool
--- See Note [PrimOp can_fail and has_side_effects]
-primOpIsCheap op = primOpOkForSpeculation op
+-- See Note [Classifying primop effects]
+#include "primop-is-cheap.hs-incl"
 -- In March 2001, we changed this to
 --      primOpIsCheap op = False
 -- thereby making *no* primops seem cheap.  But this killed eta
@@ -540,7 +637,7 @@ primOpIsCheap op = primOpOkForSpeculation op
 -- The problem that originally gave rise to the change was
 --      let x = a +# b *# c in x +# x
 -- were we don't want to inline x. But primopIsCheap doesn't control
--- that (it's exprIsDupable that does) so the problem doesn't occur
+-- that (it's primOpIsWorkFree that does) so the problem doesn't occur
 -- even if primOpIsCheap sometimes says 'True'.
 
 


=====================================
compiler/GHC/Builtin/primops.txt.pp
=====================================
@@ -136,11 +136,13 @@
 -- Int64X2#, SCALAR expands to Int64#, and VECTUPLE expands to (# Int64#, Int64# #).
 
 defaults
-   has_side_effects = False
+   effect           = NoEffect -- See Note [Classifying primop effects] in GHC.Builtin.PrimOps
+   can_fail_warning = WarnIfEffectIsCanFail
    out_of_line      = False   -- See Note [When do out-of-line primops go in primops.txt.pp]
-   can_fail         = False   -- See Note [PrimOp can_fail and has_side_effects] in PrimOp
    commutable       = False
    code_size        = { primOpCodeSizeDefault }
+   work_free        = { primOpCodeSize _thisOp == 0 }
+   cheap            = { primOpOkForSpeculation _thisOp }
    strictness       = { \ arity -> mkClosedDmdSig (replicate arity topDmd) topDiv }
    fixity           = Nothing
    llvm_only        = False
@@ -166,8 +168,7 @@ defaults
 --
 --   - No polymorphism in type
 --   - `strictness       = <default>`
---   - `can_fail         = False`
---   - `has_side_effects = True`
+--   - `effect           = ReadWriteEffect`
 --
 -- https://gitlab.haskell.org/ghc/ghc/issues/16929 tracks this issue,
 -- and has a table of which external-only primops are blocked by which
@@ -295,15 +296,15 @@ primop Int8MulOp "timesInt8#" GenPrimOp Int8# -> Int8# -> Int8#
 
 primop Int8QuotOp "quotInt8#" GenPrimOp Int8# -> Int8# -> Int8#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int8RemOp "remInt8#" GenPrimOp Int8# -> Int8# -> Int8#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int8QuotRemOp "quotRemInt8#" GenPrimOp Int8# -> Int8# -> (# Int8#, Int8# #)
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int8SllOp "uncheckedShiftLInt8#"  GenPrimOp Int8# -> Int# -> Int8#
 primop Int8SraOp "uncheckedShiftRAInt8#" GenPrimOp Int8# -> Int# -> Int8#
@@ -341,15 +342,15 @@ primop Word8MulOp "timesWord8#" GenPrimOp Word8# -> Word8# -> Word8#
 
 primop Word8QuotOp "quotWord8#" GenPrimOp Word8# -> Word8# -> Word8#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word8RemOp "remWord8#" GenPrimOp Word8# -> Word8# -> Word8#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word8QuotRemOp "quotRemWord8#" GenPrimOp Word8# -> Word8# -> (# Word8#, Word8# #)
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word8AndOp "andWord8#" GenPrimOp Word8# -> Word8# -> Word8#
    with commutable = True
@@ -399,15 +400,15 @@ primop Int16MulOp "timesInt16#" GenPrimOp Int16# -> Int16# -> Int16#
 
 primop Int16QuotOp "quotInt16#" GenPrimOp Int16# -> Int16# -> Int16#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int16RemOp "remInt16#" GenPrimOp Int16# -> Int16# -> Int16#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int16QuotRemOp "quotRemInt16#" GenPrimOp Int16# -> Int16# -> (# Int16#, Int16# #)
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int16SllOp "uncheckedShiftLInt16#"  GenPrimOp Int16# -> Int# -> Int16#
 primop Int16SraOp "uncheckedShiftRAInt16#" GenPrimOp Int16# -> Int# -> Int16#
@@ -445,15 +446,15 @@ primop Word16MulOp "timesWord16#" GenPrimOp Word16# -> Word16# -> Word16#
 
 primop Word16QuotOp "quotWord16#" GenPrimOp Word16# -> Word16# -> Word16#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word16RemOp "remWord16#" GenPrimOp Word16# -> Word16# -> Word16#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word16QuotRemOp "quotRemWord16#" GenPrimOp Word16# -> Word16# -> (# Word16#, Word16# #)
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word16AndOp "andWord16#" GenPrimOp Word16# -> Word16# -> Word16#
    with commutable = True
@@ -503,15 +504,15 @@ primop Int32MulOp "timesInt32#" GenPrimOp Int32# -> Int32# -> Int32#
 
 primop Int32QuotOp "quotInt32#" GenPrimOp Int32# -> Int32# -> Int32#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int32RemOp "remInt32#" GenPrimOp Int32# -> Int32# -> Int32#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int32QuotRemOp "quotRemInt32#" GenPrimOp Int32# -> Int32# -> (# Int32#, Int32# #)
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int32SllOp "uncheckedShiftLInt32#"  GenPrimOp Int32# -> Int# -> Int32#
 primop Int32SraOp "uncheckedShiftRAInt32#" GenPrimOp Int32# -> Int# -> Int32#
@@ -549,15 +550,15 @@ primop Word32MulOp "timesWord32#" GenPrimOp Word32# -> Word32# -> Word32#
 
 primop Word32QuotOp "quotWord32#" GenPrimOp Word32# -> Word32# -> Word32#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word32RemOp "remWord32#" GenPrimOp Word32# -> Word32# -> Word32#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word32QuotRemOp "quotRemWord32#" GenPrimOp Word32# -> Word32# -> (# Word32#, Word32# #)
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word32AndOp "andWord32#" GenPrimOp Word32# -> Word32# -> Word32#
    with commutable = True
@@ -607,11 +608,11 @@ primop Int64MulOp "timesInt64#" GenPrimOp Int64# -> Int64# -> Int64#
 
 primop Int64QuotOp "quotInt64#" GenPrimOp Int64# -> Int64# -> Int64#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int64RemOp "remInt64#" GenPrimOp Int64# -> Int64# -> Int64#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Int64SllOp "uncheckedIShiftL64#"  GenPrimOp Int64# -> Int# -> Int64#
 primop Int64SraOp "uncheckedIShiftRA64#" GenPrimOp Int64# -> Int# -> Int64#
@@ -649,11 +650,11 @@ primop Word64MulOp "timesWord64#" GenPrimOp Word64# -> Word64# -> Word64#
 
 primop Word64QuotOp "quotWord64#" GenPrimOp Word64# -> Word64# -> Word64#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word64RemOp "remWord64#" GenPrimOp Word64# -> Word64# -> Word64#
   with
-    can_fail = True
+    effect = CanFail
 
 primop Word64AndOp "and64#" GenPrimOp Word64# -> Word64# -> Word64#
    with commutable = True
@@ -736,19 +737,19 @@ primop   IntQuotOp    "quotInt#"    GenPrimOp
    {Rounds towards zero. The behavior is undefined if the second argument is
     zero.
    }
-   with can_fail = True
+   with effect = CanFail
 
 primop   IntRemOp    "remInt#"    GenPrimOp
    Int# -> Int# -> Int#
    {Satisfies @('quotInt#' x y) '*#' y '+#' ('remInt#' x y) == x at . The
     behavior is undefined if the second argument is zero.
    }
-   with can_fail = True
+   with effect = CanFail
 
 primop   IntQuotRemOp "quotRemInt#"    GenPrimOp
    Int# -> Int# -> (# Int#, Int# #)
    {Rounds towards zero.}
-   with can_fail = True
+   with effect = CanFail
 
 primop   IntAndOp   "andI#"   GenPrimOp    Int# -> Int# -> Int#
    {Bitwise "and".}
@@ -885,20 +886,20 @@ primop   WordMul2Op  "timesWord2#"   GenPrimOp
    with commutable = True
 
 primop   WordQuotOp   "quotWord#"   GenPrimOp   Word# -> Word# -> Word#
-   with can_fail = True
+   with effect = CanFail
 
 primop   WordRemOp   "remWord#"   GenPrimOp   Word# -> Word# -> Word#
-   with can_fail = True
+   with effect = CanFail
 
 primop   WordQuotRemOp "quotRemWord#" GenPrimOp
    Word# -> Word# -> (# Word#, Word# #)
-   with can_fail = True
+   with effect = CanFail
 
 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 can_fail = True
+   with effect = CanFail
 
 primop   WordAndOp   "and#"   GenPrimOp   Word# -> Word# -> Word#
    with commutable = True
@@ -1108,7 +1109,7 @@ primop   DoubleMulOp   "*##"   GenPrimOp
 
 primop   DoubleDivOp   "/##"   GenPrimOp
    Double# -> Double# -> Double#
-   with can_fail = True
+   with effect = CanFail -- Can this one really fail?
         fixity = infixl 7
 
 primop   DoubleNegOp   "negateDouble#"  GenPrimOp   Double# -> Double#
@@ -1136,13 +1137,13 @@ primop   DoubleLogOp   "logDouble#"      GenPrimOp
    Double# -> Double#
    with
    code_size = { primOpCodeSizeForeignCall }
-   can_fail = True
+   effect = CanFail
 
 primop   DoubleLog1POp   "log1pDouble#"      GenPrimOp
    Double# -> Double#
    with
    code_size = { primOpCodeSizeForeignCall }
-   can_fail = True
+   effect = CanFail
 
 primop   DoubleSqrtOp   "sqrtDouble#"      GenPrimOp
    Double# -> Double#
@@ -1168,13 +1169,13 @@ primop   DoubleAsinOp   "asinDouble#"      GenPrimOp
    Double# -> Double#
    with
    code_size = { primOpCodeSizeForeignCall }
-   can_fail = True
+   effect = CanFail
 
 primop   DoubleAcosOp   "acosDouble#"      GenPrimOp
    Double# -> Double#
    with
    code_size = { primOpCodeSizeForeignCall }
-   can_fail = True
+   effect = CanFail
 
 primop   DoubleAtanOp   "atanDouble#"      GenPrimOp
    Double# -> Double#
@@ -1263,7 +1264,7 @@ primop   FloatMulOp   "timesFloat#"      GenPrimOp
 
 primop   FloatDivOp   "divideFloat#"      GenPrimOp
    Float# -> Float# -> Float#
-   with can_fail = True
+   with effect = CanFail
 
 primop   FloatNegOp   "negateFloat#"      GenPrimOp    Float# -> Float#
 
@@ -1288,13 +1289,13 @@ primop   FloatLogOp   "logFloat#"      GenPrimOp
    Float# -> Float#
    with
    code_size = { primOpCodeSizeForeignCall }
-   can_fail = True
+   effect = CanFail
 
 primop   FloatLog1POp  "log1pFloat#"     GenPrimOp
    Float# -> Float#
    with
    code_size = { primOpCodeSizeForeignCall }
-   can_fail = True
+   effect = CanFail
 
 primop   FloatSqrtOp   "sqrtFloat#"      GenPrimOp
    Float# -> Float#
@@ -1320,13 +1321,13 @@ primop   FloatAsinOp   "asinFloat#"      GenPrimOp
    Float# -> Float#
    with
    code_size = { primOpCodeSizeForeignCall }
-   can_fail = True
+   effect = CanFail
 
 primop   FloatAcosOp   "acosFloat#"      GenPrimOp
    Float# -> Float#
    with
    code_size = { primOpCodeSizeForeignCall }
-   can_fail = True
+   effect = CanFail
 
 primop   FloatAtanOp   "atanFloat#"      GenPrimOp
    Float# -> Float#
@@ -1461,22 +1462,22 @@ primop  NewArrayOp "newArray#" GenPrimOp
     with each element containing the specified initial value.}
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  ReadArrayOp "readArray#" GenPrimOp
    MutableArray# s a_levpoly -> Int# -> State# s -> (# State# s, a_levpoly #)
    {Read from specified index of mutable array. Result is not yet evaluated.}
    with
-   has_side_effects = True
-   can_fail         = True
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  WriteArrayOp "writeArray#" GenPrimOp
    MutableArray# s a_levpoly -> Int# -> a_levpoly -> State# s -> State# s
    {Write to specified index of mutable array.}
    with
-   has_side_effects = True
-   can_fail         = True
-   code_size        = 2 -- card update too
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
+   code_size = 2 -- card update too
 
 primop  SizeofArrayOp "sizeofArray#" GenPrimOp
    Array# a_levpoly -> Int#
@@ -1496,20 +1497,20 @@ primop  IndexArrayOp "indexArray#" GenPrimOp
     heap. Avoiding these thunks, in turn, reduces references to the
     argument array, allowing it to be garbage collected more promptly.}
    with
-   can_fail         = True
+   effect = CanFail
 
 primop  UnsafeFreezeArrayOp "unsafeFreezeArray#" GenPrimOp
    MutableArray# s a_levpoly -> State# s -> (# State# s, Array# a_levpoly #)
    {Make a mutable array immutable, without copying.}
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  UnsafeThawArrayOp  "unsafeThawArray#" GenPrimOp
    Array# a_levpoly -> State# s -> (# State# s, MutableArray# s a_levpoly #)
    {Make an immutable array mutable, without copying.}
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  CopyArrayOp "copyArray#" GenPrimOp
   Array# a_levpoly -> Int# -> MutableArray# s a_levpoly -> Int# -> Int# -> State# s -> State# s
@@ -1522,8 +1523,8 @@ primop  CopyArrayOp "copyArray#" GenPrimOp
    either.}
   with
   out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
 
 primop  CopyMutableArrayOp "copyMutableArray#" GenPrimOp
   MutableArray# s a_levpoly -> Int# -> MutableArray# s a_levpoly -> Int# -> Int# -> State# s -> State# s
@@ -1536,8 +1537,8 @@ primop  CopyMutableArrayOp "copyMutableArray#" GenPrimOp
    destination regions may overlap.}
   with
   out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
 
 primop  CloneArrayOp "cloneArray#" GenPrimOp
   Array# a_levpoly -> Int# -> Int# -> Array# a_levpoly
@@ -1547,8 +1548,8 @@ primop  CloneArrayOp "cloneArray#" GenPrimOp
    range, but this is not checked.}
   with
   out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  effect = ReadWriteEffect -- assumed too expensive to duplicate?
+  can_fail_warning = YesWarnCanFail
 
 primop  CloneMutableArrayOp "cloneMutableArray#" GenPrimOp
   MutableArray# s a_levpoly -> Int# -> Int# -> State# s -> (# State# s, MutableArray# s a_levpoly #)
@@ -1558,8 +1559,8 @@ primop  CloneMutableArrayOp "cloneMutableArray#" GenPrimOp
    range, but this is not checked.}
   with
   out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
 
 primop  FreezeArrayOp "freezeArray#" GenPrimOp
   MutableArray# s a_levpoly -> Int# -> Int# -> State# s -> (# State# s, Array# a_levpoly #)
@@ -1569,8 +1570,8 @@ primop  FreezeArrayOp "freezeArray#" GenPrimOp
    range, but this is not checked.}
   with
   out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
 
 primop  ThawArrayOp "thawArray#" GenPrimOp
   Array# a_levpoly -> Int# -> Int# -> State# s -> (# State# s, MutableArray# s a_levpoly #)
@@ -1580,8 +1581,8 @@ primop  ThawArrayOp "thawArray#" GenPrimOp
    range, but this is not checked.}
   with
   out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
 
 primop CasArrayOp  "casArray#" GenPrimOp
    MutableArray# s a_levpoly -> Int# -> a_levpoly -> a_levpoly -> State# s -> (# State# s, Int#, a_levpoly #)
@@ -1599,8 +1600,8 @@ primop CasArrayOp  "casArray#" GenPrimOp
    }
    with
    out_of_line = True
-   has_side_effects = True
-   can_fail = True -- Might index out of bounds
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 
 ------------------------------------------------------------------------
@@ -1637,7 +1638,7 @@ primop  NewSmallArrayOp "newSmallArray#" GenPrimOp
     with each element containing the specified initial value.}
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  ShrinkSmallMutableArrayOp_Char "shrinkSmallMutableArray#" GenPrimOp
    SmallMutableArray# s a_levpoly -> Int# -> State# s -> State# s
@@ -1654,21 +1655,23 @@ primop  ShrinkSmallMutableArrayOp_Char "shrinkSmallMutableArray#" GenPrimOp
 
     @since 0.6.1}
    with out_of_line = True
-        has_side_effects = True
+        effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
+        -- can fail because of the "newSize <= oldSize" requirement
 
 primop  ReadSmallArrayOp "readSmallArray#" GenPrimOp
    SmallMutableArray# s a_levpoly -> Int# -> State# s -> (# State# s, a_levpoly #)
    {Read from specified index of mutable array. Result is not yet evaluated.}
    with
-   has_side_effects = True
-   can_fail         = True
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  WriteSmallArrayOp "writeSmallArray#" GenPrimOp
    SmallMutableArray# s a_levpoly -> Int# -> a_levpoly -> State# s -> State# s
    {Write to specified index of mutable array.}
    with
-   has_side_effects = True
-   can_fail         = True
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  SizeofSmallArrayOp "sizeofSmallArray#" GenPrimOp
    SmallArray# a_levpoly -> Int#
@@ -1693,20 +1696,20 @@ primop  IndexSmallArrayOp "indexSmallArray#" GenPrimOp
    {Read from specified index of immutable array. Result is packaged into
     an unboxed singleton; the result itself is not yet evaluated.}
    with
-   can_fail         = True
+   effect = CanFail
 
 primop  UnsafeFreezeSmallArrayOp "unsafeFreezeSmallArray#" GenPrimOp
    SmallMutableArray# s a_levpoly -> State# s -> (# State# s, SmallArray# a_levpoly #)
    {Make a mutable array immutable, without copying.}
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  UnsafeThawSmallArrayOp  "unsafeThawSmallArray#" GenPrimOp
    SmallArray# a_levpoly -> State# s -> (# State# s, SmallMutableArray# s a_levpoly #)
    {Make an immutable array mutable, without copying.}
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 -- The code_size is only correct for the case when the copy family of
 -- primops aren't inlined. It would be nice to keep track of both.
@@ -1721,9 +1724,9 @@ primop  CopySmallArrayOp "copySmallArray#" GenPrimOp
    be the same array in different states, but this is not checked
    either.}
   with
-  out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  out_of_line = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
 
 primop  CopySmallMutableArrayOp "copySmallMutableArray#" GenPrimOp
   SmallMutableArray# s a_levpoly -> Int# -> SmallMutableArray# s a_levpoly -> Int# -> Int# -> State# s -> State# s
@@ -1736,9 +1739,9 @@ primop  CopySmallMutableArrayOp "copySmallMutableArray#" GenPrimOp
    The regions are allowed to overlap, although this is only possible when the same
    array is provided as both the source and the destination. }
   with
-  out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  out_of_line = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
 
 primop  CloneSmallArrayOp "cloneSmallArray#" GenPrimOp
   SmallArray# a_levpoly -> Int# -> Int# -> SmallArray# a_levpoly
@@ -1748,8 +1751,8 @@ primop  CloneSmallArrayOp "cloneSmallArray#" GenPrimOp
    range, but this is not checked.}
   with
   out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  effect = ReadWriteEffect -- assumed too expensive to duplicate?
+  can_fail_warning = YesWarnCanFail
 
 primop  CloneSmallMutableArrayOp "cloneSmallMutableArray#" GenPrimOp
   SmallMutableArray# s a_levpoly -> Int# -> Int# -> State# s -> (# State# s, SmallMutableArray# s a_levpoly #)
@@ -1759,8 +1762,8 @@ primop  CloneSmallMutableArrayOp "cloneSmallMutableArray#" GenPrimOp
    range, but this is not checked.}
   with
   out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
 
 primop  FreezeSmallArrayOp "freezeSmallArray#" GenPrimOp
   SmallMutableArray# s a_levpoly -> Int# -> Int# -> State# s -> (# State# s, SmallArray# a_levpoly #)
@@ -1770,8 +1773,8 @@ primop  FreezeSmallArrayOp "freezeSmallArray#" GenPrimOp
    range, but this is not checked.}
   with
   out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
 
 primop  ThawSmallArrayOp "thawSmallArray#" GenPrimOp
   SmallArray# a_levpoly -> Int# -> Int# -> State# s -> (# State# s, SmallMutableArray# s a_levpoly #)
@@ -1781,8 +1784,8 @@ primop  ThawSmallArrayOp "thawSmallArray#" GenPrimOp
    range, but this is not checked.}
   with
   out_of_line      = True
-  has_side_effects = True
-  can_fail         = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
 
 primop CasSmallArrayOp  "casSmallArray#" GenPrimOp
    SmallMutableArray# s a_levpoly -> Int# -> a_levpoly -> a_levpoly -> State# s -> (# State# s, Int#, a_levpoly #)
@@ -1790,8 +1793,8 @@ primop CasSmallArrayOp  "casSmallArray#" GenPrimOp
     See the documentation of 'casArray#'.}
    with
    out_of_line = True
-   has_side_effects = True
-   can_fail = True -- Might index out of bounds
+   effect = ReadWriteEffect -- Might index out of bounds
+   can_fail_warning = YesWarnCanFail
 
 ------------------------------------------------------------------------
 section "Byte Arrays"
@@ -1857,20 +1860,23 @@ primop  NewByteArrayOp_Char "newByteArray#" GenPrimOp
     the specified state thread. The size of the memory underlying the
     array will be rounded up to the platform's word size.}
    with out_of_line = True
-        has_side_effects = True
+        effect = ReadWriteEffect
 
 primop  NewPinnedByteArrayOp_Char "newPinnedByteArray#" GenPrimOp
    Int# -> State# s -> (# State# s, MutableByteArray# s #)
    {Like 'newByteArray#' but GC guarantees not to move it.}
    with out_of_line = True
-        has_side_effects = True
+        effect = ReadWriteEffect
 
 primop  NewAlignedPinnedByteArrayOp_Char "newAlignedPinnedByteArray#" GenPrimOp
    Int# -> Int# -> State# s -> (# State# s, MutableByteArray# s #)
    {Like 'newPinnedByteArray#' but allow specifying an arbitrary
     alignment, which must be a power of two.}
    with out_of_line = True
-        has_side_effects = True
+        effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
+        -- can fail warning for the "power of two" requirement
+        -- TODO: Fact-check this.
 
 primop  MutableByteArrayIsPinnedOp "isMutableByteArrayPinned#" GenPrimOp
    MutableByteArray# s -> Int#
@@ -1903,7 +1909,9 @@ primop  ShrinkMutableByteArrayOp_Char "shrinkMutableByteArray#" GenPrimOp
 
     @since 0.4.0.0}
    with out_of_line = True
-        has_side_effects = True
+        effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
+        -- can fail for the "newSize <= oldSize" requirement
 
 primop  ResizeMutableByteArrayOp_Char "resizeMutableByteArray#" GenPrimOp
    MutableByteArray# s -> Int# -> State# s -> (# State# s,MutableByteArray# s #)
@@ -1921,13 +1929,14 @@ primop  ResizeMutableByteArrayOp_Char "resizeMutableByteArray#" GenPrimOp
 
     @since 0.4.0.0}
    with out_of_line = True
-        has_side_effects = True
+        effect = ReadWriteEffect
 
 primop  UnsafeFreezeByteArrayOp "unsafeFreezeByteArray#" GenPrimOp
    MutableByteArray# s -> State# s -> (# State# s, ByteArray# #)
    {Make a mutable byte array immutable, without copying.}
    with
-   has_side_effects = True
+   -- why was this has_side_effects?
+   code_size = 0
 
 primop  UnsafeThawByteArrayOp "unsafeThawByteArray#" GenPrimOp
    ByteArray# -> State# s -> (# State# s, MutableByteArray# s #)
@@ -1935,7 +1944,8 @@ primop  UnsafeThawByteArrayOp "unsafeThawByteArray#" GenPrimOp
 
     @since 0.12.0.0}
    with
-   has_side_effects = True
+   -- why was this has_side_effects?
+   code_size = 0
 
 primop  SizeofByteArrayOp "sizeofByteArray#" GenPrimOp
    ByteArray# -> Int#
@@ -1976,7 +1986,7 @@ primop  CompareByteArraysOp "compareByteArrays#" GenPrimOp
 
     @since 0.5.2.0}
    with
-   can_fail = True
+   effect = CanFail
 
 primop  CopyByteArrayOp "copyByteArray#" GenPrimOp
   ByteArray# -> Int# -> MutableByteArray# s -> Int# -> Int# -> State# s -> State# s
@@ -1989,9 +1999,9 @@ primop  CopyByteArrayOp "copyByteArray#" GenPrimOp
     either.
   }
   with
-  has_side_effects = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
   code_size = { primOpCodeSizeForeignCall + 4}
-  can_fail = True
 
 primop  CopyMutableByteArrayOp "copyMutableByteArray#" GenPrimOp
   MutableByteArray# s -> Int# -> MutableByteArray# s -> Int# -> Int# -> State# s -> State# s
@@ -2004,9 +2014,9 @@ primop  CopyMutableByteArrayOp "copyMutableByteArray#" GenPrimOp
     array is provided as both the source and the destination.
   }
   with
-  has_side_effects = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
   code_size = { primOpCodeSizeForeignCall + 4 }
-  can_fail = True
 
 primop  CopyMutableByteArrayNonOverlappingOp "copyMutableByteArrayNonOverlapping#" GenPrimOp
   MutableByteArray# s -> Int# -> MutableByteArray# s -> Int# -> Int# -> State# s -> State# s
@@ -2020,9 +2030,9 @@ primop  CopyMutableByteArrayNonOverlappingOp "copyMutableByteArrayNonOverlapping
     @since 0.11.0
   }
   with
-  has_side_effects = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
   code_size = { primOpCodeSizeForeignCall + 4 }
-  can_fail = True
 
 primop  CopyByteArrayToAddrOp "copyByteArrayToAddr#" GenPrimOp
   ByteArray# -> Int# -> Addr# -> Int# -> State# s -> State# s
@@ -2032,9 +2042,9 @@ primop  CopyByteArrayToAddrOp "copyByteArrayToAddr#" GenPrimOp
    ByteArray\# (e.g. if the ByteArray\# were pinned), but this is not checked
    either.}
   with
-  has_side_effects = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
   code_size = { primOpCodeSizeForeignCall + 4 }
-  can_fail = True
 
 primop  CopyMutableByteArrayToAddrOp "copyMutableByteArrayToAddr#" GenPrimOp
   MutableByteArray# s -> Int# -> Addr# -> Int# -> State# s -> State# s
@@ -2044,9 +2054,9 @@ primop  CopyMutableByteArrayToAddrOp "copyMutableByteArrayToAddr#" GenPrimOp
    point into the MutableByteArray\# (e.g. if the MutableByteArray\# were
    pinned), but this is not checked either.}
   with
-  has_side_effects = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
   code_size = { primOpCodeSizeForeignCall + 4 }
-  can_fail = True
 
 primop  CopyAddrToByteArrayOp "copyAddrToByteArray#" GenPrimOp
   Addr# -> MutableByteArray# s -> Int# -> Int# -> State# s -> State# s
@@ -2056,9 +2066,9 @@ primop  CopyAddrToByteArrayOp "copyAddrToByteArray#" GenPrimOp
    point into the MutableByteArray\# (e.g. if the MutableByteArray\# were pinned),
    but this is not checked either.}
   with
-  has_side_effects = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
   code_size = { primOpCodeSizeForeignCall + 4 }
-  can_fail = True
 
 primop  CopyAddrToAddrOp "copyAddrToAddr#" GenPrimOp
   Addr# -> Addr# -> Int# -> State# RealWorld -> State# RealWorld
@@ -2071,9 +2081,9 @@ primop  CopyAddrToAddrOp "copyAddrToAddr#" GenPrimOp
     @since 0.11.0
   }
   with
-  has_side_effects = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
   code_size = { primOpCodeSizeForeignCall }
-  can_fail = True
 
 primop  CopyAddrToAddrNonOverlappingOp "copyAddrToAddrNonOverlapping#" GenPrimOp
   Addr# -> Addr# -> Int# -> State# RealWorld -> State# RealWorld
@@ -2087,18 +2097,18 @@ primop  CopyAddrToAddrNonOverlappingOp "copyAddrToAddrNonOverlapping#" GenPrimOp
     @since 0.11.0
   }
   with
-  has_side_effects = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
   code_size = { primOpCodeSizeForeignCall }
-  can_fail = True
 
 primop  SetByteArrayOp "setByteArray#" GenPrimOp
   MutableByteArray# s -> Int# -> Int# -> Int# -> State# s -> State# s
   {@'setByteArray#' ba off len c@ sets the byte range @[off, off+len)@ of
    the 'MutableByteArray#' to the byte @c at .}
   with
-  has_side_effects = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
   code_size = { primOpCodeSizeForeignCall + 4 }
-  can_fail = True
 
 primop  SetAddrRangeOp "setAddrRange#" GenPrimOp
   Addr# -> Int# -> Int# -> State# RealWorld -> State# RealWorld
@@ -2111,9 +2121,9 @@ primop  SetAddrRangeOp "setAddrRange#" GenPrimOp
     @since 0.11.0
   }
   with
-  has_side_effects = True
+  effect = ReadWriteEffect
+  can_fail_warning = YesWarnCanFail
   code_size = { primOpCodeSizeForeignCall }
-  can_fail = True
 
 -- Atomic operations
 
@@ -2121,15 +2131,17 @@ primop  AtomicReadByteArrayOp_Int "atomicReadIntArray#" GenPrimOp
    MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #)
    {Given an array and an offset in machine words, read an element. The
     index is assumed to be in bounds. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  AtomicWriteByteArrayOp_Int "atomicWriteIntArray#" GenPrimOp
    MutableByteArray# s -> Int# -> Int# -> State# s -> State# s
    {Given an array and an offset in machine words, write an element. The
     index is assumed to be in bounds. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop CasByteArrayOp_Int "casIntArray#" GenPrimOp
    MutableByteArray# s -> Int# -> Int# -> Int# -> State# s -> (# State# s, Int# #)
@@ -2138,8 +2150,9 @@ primop CasByteArrayOp_Int "casIntArray#" GenPrimOp
     value if the current value matches the provided old value. Returns
     the value of the element before the operation. Implies a full memory
     barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop CasByteArrayOp_Int8 "casInt8Array#" GenPrimOp
    MutableByteArray# s -> Int# -> Int8# -> Int8# -> State# s -> (# State# s, Int8# #)
@@ -2148,8 +2161,9 @@ primop CasByteArrayOp_Int8 "casInt8Array#" GenPrimOp
     value if the current value matches the provided old value. Returns
     the value of the element before the operation. Implies a full memory
     barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop CasByteArrayOp_Int16 "casInt16Array#" GenPrimOp
    MutableByteArray# s -> Int# -> Int16# -> Int16# -> State# s -> (# State# s, Int16# #)
@@ -2158,8 +2172,9 @@ primop CasByteArrayOp_Int16 "casInt16Array#" GenPrimOp
     value if the current value matches the provided old value. Returns
     the value of the element before the operation. Implies a full memory
     barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop CasByteArrayOp_Int32 "casInt32Array#" GenPrimOp
    MutableByteArray# s -> Int# -> Int32# -> Int32# -> State# s -> (# State# s, Int32# #)
@@ -2168,8 +2183,9 @@ primop CasByteArrayOp_Int32 "casInt32Array#" GenPrimOp
     value if the current value matches the provided old value. Returns
     the value of the element before the operation. Implies a full memory
     barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop CasByteArrayOp_Int64 "casInt64Array#" GenPrimOp
    MutableByteArray# s -> Int# -> Int64# -> Int64# -> State# s -> (# State# s, Int64# #)
@@ -2178,56 +2194,63 @@ primop CasByteArrayOp_Int64 "casInt64Array#" GenPrimOp
     value if the current value matches the provided old value. Returns
     the value of the element before the operation. Implies a full memory
     barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchAddByteArrayOp_Int "fetchAddIntArray#" GenPrimOp
    MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)
    {Given an array, and offset in machine words, and a value to add,
     atomically add the value to the element. Returns the value of the
     element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchSubByteArrayOp_Int "fetchSubIntArray#" GenPrimOp
    MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)
    {Given an array, and offset in machine words, and a value to subtract,
     atomically subtract the value from the element. Returns the value of
     the element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchAndByteArrayOp_Int "fetchAndIntArray#" GenPrimOp
    MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)
    {Given an array, and offset in machine words, and a value to AND,
     atomically AND the value into the element. Returns the value of the
     element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchNandByteArrayOp_Int "fetchNandIntArray#" GenPrimOp
    MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)
    {Given an array, and offset in machine words, and a value to NAND,
     atomically NAND the value into the element. Returns the value of the
     element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchOrByteArrayOp_Int "fetchOrIntArray#" GenPrimOp
    MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)
    {Given an array, and offset in machine words, and a value to OR,
     atomically OR the value into the element. Returns the value of the
     element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchXorByteArrayOp_Int "fetchXorIntArray#" GenPrimOp
    MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)
    {Given an array, and offset in machine words, and a value to XOR,
     atomically XOR the value into the element. Returns the value of the
     element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 ------------------------------------------------------------------------
 section "Addr#"
@@ -2273,15 +2296,17 @@ primop  InterlockedExchange_Addr "atomicExchangeAddrAddr#" GenPrimOp
    Addr# -> Addr# -> State# s -> (# State# s, Addr# #)
    {The atomic exchange operation. Atomically exchanges the value at the first address
     with the Addr# given as second argument. Implies a read barrier.}
-   with has_side_effects = True
-        can_fail         = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  InterlockedExchange_Word "atomicExchangeWordAddr#" GenPrimOp
    Addr# -> Word# -> State# s -> (# State# s, Word# #)
    {The atomic exchange operation. Atomically exchanges the value at the address
     with the given value. Returns the old value. Implies a read barrier.}
-   with has_side_effects = True
-        can_fail         = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  CasAddrOp_Addr "atomicCasAddrAddr#" GenPrimOp
    Addr# -> Addr# -> Addr# -> State# s -> (# State# s, Addr# #)
@@ -2294,8 +2319,9 @@ primop  CasAddrOp_Addr "atomicCasAddrAddr#" GenPrimOp
      most architectures).
 
      Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail         = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  CasAddrOp_Word "atomicCasWordAddr#" GenPrimOp
    Addr# -> Word# -> Word# -> State# s -> (# State# s, Word# #)
@@ -2308,8 +2334,9 @@ primop  CasAddrOp_Word "atomicCasWordAddr#" GenPrimOp
      most architectures).
 
      Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail         = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  CasAddrOp_Word8 "atomicCasWord8Addr#" GenPrimOp
    Addr# -> Word8# -> Word8# -> State# s -> (# State# s, Word8# #)
@@ -2322,8 +2349,9 @@ primop  CasAddrOp_Word8 "atomicCasWord8Addr#" GenPrimOp
      most architectures).
 
      Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail         = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  CasAddrOp_Word16 "atomicCasWord16Addr#" GenPrimOp
    Addr# -> Word16# -> Word16# -> State# s -> (# State# s, Word16# #)
@@ -2336,8 +2364,9 @@ primop  CasAddrOp_Word16 "atomicCasWord16Addr#" GenPrimOp
      most architectures).
 
      Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail         = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  CasAddrOp_Word32 "atomicCasWord32Addr#" GenPrimOp
    Addr# -> Word32# -> Word32# -> State# s -> (# State# s, Word32# #)
@@ -2350,8 +2379,9 @@ primop  CasAddrOp_Word32 "atomicCasWord32Addr#" GenPrimOp
      most architectures).
 
      Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail         = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  CasAddrOp_Word64 "atomicCasWord64Addr#" GenPrimOp
    Addr# -> Word64# -> Word64# -> State# s -> (# State# s, Word64# #)
@@ -2364,68 +2394,77 @@ primop  CasAddrOp_Word64 "atomicCasWord64Addr#" GenPrimOp
      most architectures).
 
      Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail         = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchAddAddrOp_Word "fetchAddWordAddr#" GenPrimOp
    Addr# -> Word# -> State# s -> (# State# s, Word# #)
    {Given an address, and a value to add,
     atomically add the value to the element. Returns the value of the
     element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchSubAddrOp_Word "fetchSubWordAddr#" GenPrimOp
    Addr# -> Word# -> State# s -> (# State# s, Word# #)
    {Given an address, and a value to subtract,
     atomically subtract the value from the element. Returns the value of
     the element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchAndAddrOp_Word "fetchAndWordAddr#" GenPrimOp
    Addr# -> Word# -> State# s -> (# State# s, Word# #)
    {Given an address, and a value to AND,
     atomically AND the value into the element. Returns the value of the
     element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchNandAddrOp_Word "fetchNandWordAddr#" GenPrimOp
    Addr# -> Word# -> State# s -> (# State# s, Word# #)
    {Given an address, and a value to NAND,
     atomically NAND the value into the element. Returns the value of the
     element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchOrAddrOp_Word "fetchOrWordAddr#" GenPrimOp
    Addr# -> Word# -> State# s -> (# State# s, Word# #)
    {Given an address, and a value to OR,
     atomically OR the value into the element. Returns the value of the
     element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop FetchXorAddrOp_Word "fetchXorWordAddr#" GenPrimOp
    Addr# -> Word# -> State# s -> (# State# s, Word# #)
    {Given an address, and a value to XOR,
     atomically XOR the value into the element. Returns the value of the
     element before the operation. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  AtomicReadAddrOp_Word "atomicReadWordAddr#" GenPrimOp
    Addr# -> State# s -> (# State# s, Word# #)
    {Given an address, read a machine word.  Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 primop  AtomicWriteAddrOp_Word "atomicWriteWordAddr#" GenPrimOp
    Addr# -> Word# -> State# s -> State# s
    {Given an address, write a machine word. Implies a full memory barrier.}
-   with has_side_effects = True
-        can_fail = True
+   with
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 
 ------------------------------------------------------------------------
@@ -2441,18 +2480,18 @@ primop  NewMutVarOp "newMutVar#" GenPrimOp
    {Create 'MutVar#' with specified initial value in specified state thread.}
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 -- Note [Why MutVar# ops can't fail]
 -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 --
--- We don't label readMutVar# or writeMutVar# as can_fail.
+-- We don't label readMutVar# or writeMutVar# as CanFail.
 -- This may seem a bit peculiar, because they surely *could*
 -- fail spectacularly if passed a pointer to unallocated memory.
 -- But MutVar#s are always correct by construction; we never
 -- test if a pointer is valid before using it with these operations.
 -- So we never have to worry about floating the pointer reference
--- outside a validity test. At the moment, has_side_effects blocks
+-- outside a validity test. At the moment, ReadWriteEffect blocks
 -- up the relevant optimizations anyway, but we hope to draw finer
 -- distinctions soon, which should improve matters for readMutVar#
 -- at least.
@@ -2462,21 +2501,21 @@ primop  ReadMutVarOp "readMutVar#" GenPrimOp
    {Read contents of 'MutVar#'. Result is not yet evaluated.}
    with
    -- See Note [Why MutVar# ops can't fail]
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  WriteMutVarOp "writeMutVar#"  GenPrimOp
    MutVar# s a_levpoly -> a_levpoly -> State# s -> State# s
    {Write contents of 'MutVar#'.}
    with
    -- See Note [Why MutVar# ops can't fail]
-   has_side_effects = True
+   effect = ReadWriteEffect
    code_size = { primOpCodeSizeForeignCall } -- for the write barrier
 
 primop  AtomicSwapMutVarOp "atomicSwapMutVar#" GenPrimOp
    MutVar# s a_levpoly -> a_levpoly -> State# s -> (# State# s, a_levpoly #)
    {Atomically exchange the value of a 'MutVar#'.}
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 -- Note [Why not an unboxed tuple in atomicModifyMutVar2#?]
 -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2512,8 +2551,8 @@ primop  AtomicModifyMutVar2Op "atomicModifyMutVar2#" GenPrimOp
      well-typed high-level wrapper.}
    with
    out_of_line = True
-   has_side_effects = True
-   can_fail         = True
+   effect = ReadWriteEffect
+   -- Why was this previously can_fail?
    strictness  = { \ _arity -> mkClosedDmdSig [ topDmd, lazyApply1Dmd, topDmd ] topDiv }
 
 primop  AtomicModifyMutVar_Op "atomicModifyMutVar_#" GenPrimOp
@@ -2523,8 +2562,8 @@ primop  AtomicModifyMutVar_Op "atomicModifyMutVar_#" GenPrimOp
      previous contents. }
    with
    out_of_line = True
-   has_side_effects = True
-   can_fail         = True
+   effect = ReadWriteEffect
+   -- Why was this previously can_fail?
    strictness  = { \ _arity -> mkClosedDmdSig [ topDmd, lazyApply1Dmd, topDmd ] topDiv }
 
 primop  CasMutVarOp "casMutVar#" GenPrimOp
@@ -2546,7 +2585,7 @@ primop  CasMutVarOp "casMutVar#" GenPrimOp
    }
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 ------------------------------------------------------------------------
 section "Exceptions"
@@ -2582,7 +2621,9 @@ primop  CatchOp "catch#" GenPrimOp
                                                  , topDmd] topDiv }
                  -- See Note [Strictness for mask/unmask/catch]
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
+   -- Either inner computation might potentially raise an unchecked exception,
+   -- but it doesn't seem worth putting a WARNING in the haddocks over
 
 primop  RaiseOp "raise#" GenPrimOp
    a_levpoly -> b_reppoly
@@ -2591,36 +2632,37 @@ primop  RaiseOp "raise#" GenPrimOp
    -- exceptions thrown by 'raise#' are considered *imprecise*.
    -- See Note [Precise vs imprecise exceptions] in GHC.Types.Demand.
    -- Hence, it has 'botDiv', not 'exnDiv'.
-   -- For the same reasons, 'raise#' is marked as "can_fail" (which 'raiseIO#'
-   -- is not), but not as "has_side_effects" (which 'raiseIO#' is).
-   -- See Note [PrimOp can_fail and has_side_effects] in "GHC.Builtin.PrimOps".
    strictness  = { \ _arity -> mkClosedDmdSig [topDmd] botDiv }
    out_of_line = True
-   can_fail = True
+   effect = ThrowsException
+   work_free = True
 
 primop  RaiseUnderflowOp "raiseUnderflow#" GenPrimOp
    (# #) -> b_reppoly
    with
    strictness  = { \ _arity -> mkClosedDmdSig [topDmd] botDiv }
    out_of_line = True
-   can_fail = True
+   effect = ThrowsException
    code_size = { primOpCodeSizeForeignCall }
+   work_free = True
 
 primop  RaiseOverflowOp "raiseOverflow#" GenPrimOp
    (# #) -> b_reppoly
    with
    strictness  = { \ _arity -> mkClosedDmdSig [topDmd] botDiv }
    out_of_line = True
-   can_fail = True
+   effect = ThrowsException
    code_size = { primOpCodeSizeForeignCall }
+   work_free = True
 
 primop  RaiseDivZeroOp "raiseDivZero#" GenPrimOp
    (# #) -> b_reppoly
    with
    strictness  = { \ _arity -> mkClosedDmdSig [topDmd] botDiv }
    out_of_line = True
-   can_fail = True
+   effect = ThrowsException
    code_size = { primOpCodeSizeForeignCall }
+   work_free = True
 
 primop  RaiseIOOp "raiseIO#" GenPrimOp
    a_levpoly -> State# RealWorld -> (# State# RealWorld, b_reppoly #)
@@ -2629,7 +2671,8 @@ primop  RaiseIOOp "raiseIO#" GenPrimOp
    -- for why this is the *only* primop that has 'exnDiv'
    strictness  = { \ _arity -> mkClosedDmdSig [topDmd, topDmd] exnDiv }
    out_of_line = True
-   has_side_effects = True
+   effect = ThrowsException
+   work_free = True
 
 primop  MaskAsyncExceptionsOp "maskAsyncExceptions#" GenPrimOp
         (State# RealWorld -> (# State# RealWorld, a_reppoly #))
@@ -2644,7 +2687,7 @@ primop  MaskAsyncExceptionsOp "maskAsyncExceptions#" GenPrimOp
    strictness  = { \ _arity -> mkClosedDmdSig [strictOnceApply1Dmd,topDmd] topDiv }
                  -- See Note [Strictness for mask/unmask/catch]
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  MaskUninterruptibleOp "maskUninterruptible#" GenPrimOp
         (State# RealWorld -> (# State# RealWorld, a_reppoly #))
@@ -2658,7 +2701,7 @@ primop  MaskUninterruptibleOp "maskUninterruptible#" GenPrimOp
    with
    strictness  = { \ _arity -> mkClosedDmdSig [strictOnceApply1Dmd,topDmd] topDiv }
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  UnmaskAsyncExceptionsOp "unmaskAsyncExceptions#" GenPrimOp
         (State# RealWorld -> (# State# RealWorld, a_reppoly #))
@@ -2673,13 +2716,13 @@ primop  UnmaskAsyncExceptionsOp "unmaskAsyncExceptions#" GenPrimOp
    strictness  = { \ _arity -> mkClosedDmdSig [strictOnceApply1Dmd,topDmd] topDiv }
                  -- See Note [Strictness for mask/unmask/catch]
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  MaskStatus "getMaskingState#" GenPrimOp
         State# RealWorld -> (# State# RealWorld, Int# #)
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 ------------------------------------------------------------------------
 section "Continuations"
@@ -2849,7 +2892,7 @@ primop  NewPromptTagOp "newPromptTag#" GenPrimOp
    { See "GHC.Prim#continuations". }
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  PromptOp "prompt#" GenPrimOp
         PromptTag# a
@@ -2859,7 +2902,7 @@ primop  PromptOp "prompt#" GenPrimOp
    with
    strictness = { \ _arity -> mkClosedDmdSig [topDmd, strictOnceApply1Dmd, topDmd] topDiv }
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  Control0Op "control0#" GenPrimOp
         PromptTag# a
@@ -2871,7 +2914,8 @@ primop  Control0Op "control0#" GenPrimOp
    with
    strictness = { \ _arity -> mkClosedDmdSig [topDmd, lazyApply2Dmd, topDmd] topDiv }
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
+   can_fail_warning = YesWarnCanFail
 
 ------------------------------------------------------------------------
 section "STM-accessible Mutable Variables"
@@ -2886,7 +2930,7 @@ primop  AtomicallyOp "atomically#" GenPrimOp
    strictness  = { \ _arity -> mkClosedDmdSig [strictManyApply1Dmd,topDmd] topDiv }
                  -- See Note [Strictness for mask/unmask/catch]
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 -- NB: retry#'s strictness information specifies it to diverge.
 -- This lets the compiler perform some extra simplifications, since retry#
@@ -2903,7 +2947,7 @@ primop  RetryOp "retry#" GenPrimOp
    with
    strictness  = { \ _arity -> mkClosedDmdSig [topDmd] botDiv }
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  CatchRetryOp "catchRetry#" GenPrimOp
       (State# RealWorld -> (# State# RealWorld, a_levpoly #) )
@@ -2915,7 +2959,7 @@ primop  CatchRetryOp "catchRetry#" GenPrimOp
                                                  , topDmd ] topDiv }
                  -- See Note [Strictness for mask/unmask/catch]
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  CatchSTMOp "catchSTM#" GenPrimOp
       (State# RealWorld -> (# State# RealWorld, a_levpoly #) )
@@ -2927,7 +2971,7 @@ primop  CatchSTMOp "catchSTM#" GenPrimOp
                                                  , topDmd ] topDiv }
                  -- See Note [Strictness for mask/unmask/catch]
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  NewTVarOp "newTVar#" GenPrimOp
        a_levpoly
@@ -2935,7 +2979,7 @@ primop  NewTVarOp "newTVar#" GenPrimOp
    {Create a new 'TVar#' holding a specified initial value.}
    with
    out_of_line  = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  ReadTVarOp "readTVar#" GenPrimOp
        TVar# s a_levpoly
@@ -2945,7 +2989,7 @@ primop  ReadTVarOp "readTVar#" GenPrimOp
     Does not force evaluation of the result.}
    with
    out_of_line  = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop ReadTVarIOOp "readTVarIO#" GenPrimOp
        TVar# s a_levpoly
@@ -2954,7 +2998,7 @@ primop ReadTVarIOOp "readTVarIO#" GenPrimOp
    Does not force evaluation of the result.}
    with
    out_of_line      = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  WriteTVarOp "writeTVar#" GenPrimOp
        TVar# s a_levpoly
@@ -2963,7 +3007,7 @@ primop  WriteTVarOp "writeTVar#" GenPrimOp
    {Write contents of 'TVar#'.}
    with
    out_of_line      = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 
 ------------------------------------------------------------------------
@@ -2981,7 +3025,7 @@ primop  NewMVarOp "newMVar#"  GenPrimOp
    {Create new 'MVar#'; initially empty.}
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  TakeMVarOp "takeMVar#" GenPrimOp
    MVar# s a_levpoly -> State# s -> (# State# s, a_levpoly #)
@@ -2989,7 +3033,7 @@ primop  TakeMVarOp "takeMVar#" GenPrimOp
    Then remove and return its contents, and set it empty.}
    with
    out_of_line      = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  TryTakeMVarOp "tryTakeMVar#" GenPrimOp
    MVar# s a_levpoly -> State# s -> (# State# s, Int#, a_levpoly #)
@@ -2997,7 +3041,7 @@ primop  TryTakeMVarOp "tryTakeMVar#" GenPrimOp
    Otherwise, return with integer 1 and contents of 'MVar#', and set 'MVar#' empty.}
    with
    out_of_line      = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  PutMVarOp "putMVar#" GenPrimOp
    MVar# s a_levpoly -> a_levpoly -> State# s -> State# s
@@ -3005,7 +3049,7 @@ primop  PutMVarOp "putMVar#" GenPrimOp
    Then store value arg as its new contents.}
    with
    out_of_line      = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  TryPutMVarOp "tryPutMVar#" GenPrimOp
    MVar# s a_levpoly -> a_levpoly -> State# s -> (# State# s, Int# #)
@@ -3013,7 +3057,7 @@ primop  TryPutMVarOp "tryPutMVar#" GenPrimOp
     Otherwise, store value arg as 'MVar#''s new contents, and return with integer 1.}
    with
    out_of_line      = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  ReadMVarOp "readMVar#" GenPrimOp
    MVar# s a_levpoly -> State# s -> (# State# s, a_levpoly #)
@@ -3022,7 +3066,7 @@ primop  ReadMVarOp "readMVar#" GenPrimOp
    of intervention from other threads.}
    with
    out_of_line      = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  TryReadMVarOp "tryReadMVar#" GenPrimOp
    MVar# s a_levpoly -> State# s -> (# State# s, Int#, a_levpoly #)
@@ -3030,14 +3074,14 @@ primop  TryReadMVarOp "tryReadMVar#" GenPrimOp
    Otherwise, return with integer 1 and contents of 'MVar#'.}
    with
    out_of_line      = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  IsEmptyMVarOp "isEmptyMVar#" GenPrimOp
    MVar# s a_levpoly -> State# s -> (# State# s, Int# #)
    {Return 1 if 'MVar#' is empty; 0 otherwise.}
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 
 ------------------------------------------------------------------------
@@ -3055,7 +3099,7 @@ primop  NewIOPortOp "newIOPort#"  GenPrimOp
    {Create new 'IOPort#'; initially empty.}
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  ReadIOPortOp "readIOPort#" GenPrimOp
    IOPort# s a_levpoly -> State# s -> (# State# s, a_levpoly #)
@@ -3065,7 +3109,7 @@ primop  ReadIOPortOp "readIOPort#" GenPrimOp
    waiting to read this 'IOPort#'.}
    with
    out_of_line      = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  WriteIOPortOp "writeIOPort#" GenPrimOp
    IOPort# s a_levpoly -> a_levpoly -> State# s -> (# State# s, Int# #)
@@ -3075,7 +3119,7 @@ primop  WriteIOPortOp "writeIOPort#" GenPrimOp
     and return with integer 1. }
    with
    out_of_line      = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 ------------------------------------------------------------------------
 section "Delay/wait operations"
@@ -3085,21 +3129,21 @@ primop  DelayOp "delay#" GenPrimOp
    Int# -> State# s -> State# s
    {Sleep specified number of microseconds.}
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  WaitReadOp "waitRead#" GenPrimOp
    Int# -> State# s -> State# s
    {Block until input is available on specified file descriptor.}
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  WaitWriteOp "waitWrite#" GenPrimOp
    Int# -> State# s -> State# s
    {Block until output is possible on specified file descriptor.}
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 ------------------------------------------------------------------------
@@ -3127,7 +3171,7 @@ primop  ForkOp "fork#" GenPrimOp
    (State# RealWorld -> (# State# RealWorld, a_reppoly #))
    -> State# RealWorld -> (# State# RealWorld, ThreadId# #)
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
    strictness  = { \ _arity -> mkClosedDmdSig [ lazyApply1Dmd
                                               , topDmd ] topDiv }
@@ -3136,7 +3180,7 @@ primop  ForkOnOp "forkOn#" GenPrimOp
    Int# -> (State# RealWorld -> (# State# RealWorld, a_reppoly #))
    -> State# RealWorld -> (# State# RealWorld, ThreadId# #)
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
    strictness  = { \ _arity -> mkClosedDmdSig [ topDmd
                                               , lazyApply1Dmd
@@ -3145,39 +3189,39 @@ primop  ForkOnOp "forkOn#" GenPrimOp
 primop  KillThreadOp "killThread#"  GenPrimOp
    ThreadId# -> a -> State# RealWorld -> State# RealWorld
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  YieldOp "yield#" GenPrimOp
    State# RealWorld -> State# RealWorld
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  MyThreadIdOp "myThreadId#" GenPrimOp
    State# RealWorld -> (# State# RealWorld, ThreadId# #)
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop LabelThreadOp "labelThread#" GenPrimOp
    ThreadId# -> ByteArray# -> State# RealWorld -> State# RealWorld
    {Set the label of the given thread. The @ByteArray#@ should contain
     a UTF-8-encoded string.}
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  IsCurrentThreadBoundOp "isCurrentThreadBound#" GenPrimOp
    State# RealWorld -> (# State# RealWorld, Int# #)
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  NoDuplicateOp "noDuplicate#" GenPrimOp
    State# s -> State# s
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop GetThreadLabelOp "threadLabel#" GenPrimOp
    ThreadId# -> State# RealWorld -> (# State# RealWorld, Int#, ByteArray# #)
@@ -3202,7 +3246,7 @@ primop  ThreadStatusOp "threadStatus#" GenPrimOp
     @since 0.9}
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop ListThreadsOp "listThreads#" GenPrimOp
    State# RealWorld -> (# State# RealWorld, Array# ThreadId# #)
@@ -3213,7 +3257,7 @@ primop ListThreadsOp "listThreads#" GenPrimOp
      @since 0.10}
    with
    out_of_line = True
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 ------------------------------------------------------------------------
 section "Weak pointers"
@@ -3230,13 +3274,13 @@ primop  MkWeakOp "mkWeak#" GenPrimOp
      the type of @k@ must be represented by a pointer (i.e. of kind
      @'TYPE' ''LiftedRep' or @'TYPE' ''UnliftedRep'@). }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  MkWeakNoFinalizerOp "mkWeakNoFinalizer#" GenPrimOp
    a_levpoly -> b_levpoly -> State# RealWorld -> (# State# RealWorld, Weak# b_levpoly #)
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  AddCFinalizerToWeakOp "addCFinalizerToWeak#" GenPrimOp
@@ -3249,13 +3293,13 @@ primop  AddCFinalizerToWeakOp "addCFinalizerToWeak#" GenPrimOp
      @eptr@ and @ptr at . 'addCFinalizerToWeak#' returns
      1 on success, or 0 if @w@ is already dead. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  DeRefWeakOp "deRefWeak#" GenPrimOp
    Weak# a_levpoly -> State# RealWorld -> (# State# RealWorld, Int#, a_levpoly #)
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  FinalizeWeakOp "finalizeWeak#" GenPrimOp
@@ -3267,14 +3311,30 @@ primop  FinalizeWeakOp "finalizeWeak#" GenPrimOp
      action. An 'Int#' of @1@ indicates that the finalizer is valid. The
      return value @b@ from the finalizer should be ignored. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop TouchOp "touch#" GenPrimOp
    a_levpoly -> State# s -> State# s
    with
-   code_size = { 0 }
-   has_side_effects = True
+   code_size = 0
+   effect = ReadWriteEffect -- see Note [touch# has ReadWriteEffect]
+   work_free = False
+
+
+-- Note [touch# has ReadWriteEffect]
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- Although touch# emits no code, it is marked as ReadWriteEffect to
+-- prevent it from being defeated by the optimizer:
+--  * Discarding a touch# call would defeat its whole purpose.
+--  * Strictly floating a touch# call out would shorten the lifetime
+--    of the touched object, again defeating its purpose.
+--  * Duplicating a touch# call might unpredictably extend the lifetime
+--    of the touched object.  Although this would not defeat the purpose
+--    of touch#, it seems undesirable.
+--
+-- In practice, this designation probably doesn't matter in most cases,
+-- as touch# is usually tightly coupled with a "real" read or write effect.
 
 ------------------------------------------------------------------------
 section "Stable pointers and names"
@@ -3287,24 +3347,24 @@ primtype StableName# a
 primop  MakeStablePtrOp "makeStablePtr#" GenPrimOp
    a_levpoly -> State# RealWorld -> (# State# RealWorld, StablePtr# a_levpoly #)
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  DeRefStablePtrOp "deRefStablePtr#" GenPrimOp
    StablePtr# a_levpoly -> State# RealWorld -> (# State# RealWorld, a_levpoly #)
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  EqStablePtrOp "eqStablePtr#" GenPrimOp
    StablePtr# a_levpoly -> StablePtr# a_levpoly -> Int#
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
 
 primop  MakeStableNameOp "makeStableName#" GenPrimOp
    a_levpoly -> State# RealWorld -> (# State# RealWorld, StableName# a_levpoly #)
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  StableNameToIntOp "stableNameToInt#" GenPrimOp
@@ -3336,7 +3396,7 @@ primop  CompactNewOp "compactNew#" GenPrimOp
      The capacity is rounded up to a multiple of the allocator block size
      and is capped to one mega block. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  CompactResizeOp "compactResize#" GenPrimOp
@@ -3346,7 +3406,7 @@ primop  CompactResizeOp "compactResize#" GenPrimOp
      determines the capacity of each compact block in the CNF. It
      does not retroactively affect existing compact blocks in the CNF. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  CompactContainsOp "compactContains#" GenPrimOp
@@ -3388,7 +3448,7 @@ primop  CompactAllocateBlockOp "compactAllocateBlock#" GenPrimOp
      so that the address does not escape or memory will be leaked.
    }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  CompactFixupPointersOp "compactFixupPointers#" GenPrimOp
@@ -3401,7 +3461,7 @@ primop  CompactFixupPointersOp "compactFixupPointers#" GenPrimOp
      a serialized CNF. It returns the new CNF and the new adjusted
      root address. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop CompactAdd "compactAdd#" GenPrimOp
@@ -3414,7 +3474,7 @@ primop CompactAdd "compactAdd#" GenPrimOp
      enforce any mutual exclusion; the caller is expected to
      arrange this. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop CompactAddWithSharing "compactAddWithSharing#" GenPrimOp
@@ -3422,7 +3482,7 @@ primop CompactAddWithSharing "compactAddWithSharing#" GenPrimOp
    { Like 'compactAdd#', but retains sharing and cycles
    during compaction. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop CompactSize "compactSize#" GenPrimOp
@@ -3430,7 +3490,7 @@ primop CompactSize "compactSize#" GenPrimOp
    { Return the total capacity (in bytes) of all the compact blocks
      in the CNF. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 ------------------------------------------------------------------------
@@ -3442,7 +3502,8 @@ primop  ReallyUnsafePtrEqualityOp "reallyUnsafePtrEquality#" GenPrimOp
    a_levpoly -> b_levpoly -> Int#
    { Returns @1#@ if the given pointers are equal and @0#@ otherwise. }
    with
-   can_fail   = True -- See Note [reallyUnsafePtrEquality# can_fail]
+   effect = CanFail -- See Note [reallyUnsafePtrEquality# CanFail]
+   can_fail_warning = DoNotWarnCanFail
 
 -- Note [Pointer comparison operations]
 -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -3474,7 +3535,7 @@ primop  ReallyUnsafePtrEqualityOp "reallyUnsafePtrEquality#" GenPrimOp
 --
 -- (PE5) reallyUnsafePtrEquality# can't fail, but it is marked as such
 --       to prevent it from floating out.
---       See Note [reallyUnsafePtrEquality# can_fail]
+--       See Note [reallyUnsafePtrEquality# CanFail]
 --
 -- The library GHC.Prim.PtrEq (and GHC.Exts) provides
 --
@@ -3509,10 +3570,10 @@ primop  ReallyUnsafePtrEqualityOp "reallyUnsafePtrEquality#" GenPrimOp
 --
 -- These operations are all specialisations of unsafePtrEquality#.
 
--- Note [reallyUnsafePtrEquality# can_fail]
--- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- Note [reallyUnsafePtrEquality# CanFail]
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 -- reallyUnsafePtrEquality# can't actually fail, per se, but we mark it
--- can_fail anyway. Until 5a9a1738023a, GHC considered primops okay for
+-- CanFail anyway. Until 5a9a1738023a, GHC considered primops okay for
 -- speculation only when their arguments were known to be forced. This was
 -- unnecessarily conservative, but it prevented reallyUnsafePtrEquality# from
 -- floating out of places where its arguments were known to be forced.
@@ -3547,30 +3608,33 @@ primop  ParOp "par#" GenPrimOp
    with
       -- Note that Par is lazy to avoid that the sparked thing
       -- gets evaluated strictly, which it should *not* be
-   has_side_effects = True
+   effect = ReadWriteEffect
    code_size = { primOpCodeSizeForeignCall }
    deprecated_msg = { Use 'spark#' instead }
 
 primop SparkOp "spark#" GenPrimOp
    a -> State# s -> (# State# s, a #)
-   with has_side_effects = True
+   with effect = ReadWriteEffect
    code_size = { primOpCodeSizeForeignCall }
 
+-- See Note [seq# magic] in GHC.Core.Op.ConstantFold
 primop SeqOp "seq#" GenPrimOp
    a -> State# s -> (# State# s, a #)
-   -- See Note [seq# magic] in GHC.Core.Op.ConstantFold
+   with
+   effect = ThrowsException
+   work_free = True -- seq# does work iff its lifted arg does work
 
 primop GetSparkOp "getSpark#" GenPrimOp
    State# s -> (# State# s, Int#, a #)
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line = True
 
 primop NumSparks "numSparks#" GenPrimOp
    State# s -> (# State# s, Int# #)
    { Returns the number of sparks in the local spark pool. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line = True
 
 
@@ -3592,6 +3656,8 @@ primop KeepAliveOp "keepAlive#" GenPrimOp
    with
    out_of_line = True
    strictness = { \ _arity -> mkClosedDmdSig [topDmd, topDmd, strictOnceApply1Dmd] topDiv }
+   effect = ReadWriteEffect
+   -- The invoked computation may have side effects
 
 
 ------------------------------------------------------------------------
@@ -3600,16 +3666,20 @@ section "Tag to enum stuff"
         and small integers.}
 ------------------------------------------------------------------------
 
+-- See Note [dataToTag# magic] in GHC.Core.Opt.ConstantFold
 primop  DataToTagOp "dataToTag#" GenPrimOp
    a -> Int#  -- Zero-indexed; the first constructor has tag zero
    { Evaluates the argument and returns the tag of the result.
      Tags are Zero-indexed; the first constructor has tag zero. }
    with
    strictness = { \ _arity -> mkClosedDmdSig [evalDmd] topDiv }
-   -- See Note [dataToTag# magic] in GHC.Core.Opt.ConstantFold
+   effect = ThrowsException
+   cheap = True
 
 primop  TagToEnumOp "tagToEnum#" GenPrimOp
    Int# -> a
+   with
+   effect = CanFail
 
 ------------------------------------------------------------------------
 section "Bytecode operations"
@@ -3658,7 +3728,7 @@ primop  NewBCOOp "newBCO#" GenPrimOp
      encoded in @instrs@, and a static reference table usage bitmap given by
      @bitmap at . }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  UnpackClosureOp "unpackClosure#" GenPrimOp
@@ -3784,7 +3854,7 @@ primop  TraceEventOp "traceEvent#" GenPrimOp
      argument.  The event will be emitted either to the @.eventlog@ file,
      or to stderr, depending on the runtime RTS flags. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  TraceEventBinaryOp "traceBinaryEvent#" GenPrimOp
@@ -3794,7 +3864,7 @@ primop  TraceEventBinaryOp "traceBinaryEvent#" GenPrimOp
      the given length passed as the second argument. The event will be
      emitted to the @.eventlog@ file. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  TraceMarkerOp "traceMarker#" GenPrimOp
@@ -3804,14 +3874,14 @@ primop  TraceMarkerOp "traceMarker#" GenPrimOp
      argument.  The event will be emitted either to the @.eventlog@ file,
      or to stderr, depending on the runtime RTS flags. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primop  SetThreadAllocationCounter "setThreadAllocationCounter#" GenPrimOp
    Int64# -> State# RealWorld -> State# RealWorld
    { Sets the allocation counter for the current thread to the given value. }
    with
-   has_side_effects = True
+   effect = ReadWriteEffect
    out_of_line      = True
 
 primtype StackSnapshot#
@@ -3900,7 +3970,7 @@ primop VecUnpackOp "unpack#" GenPrimOp
 primop VecInsertOp "insert#" GenPrimOp
    VECTOR -> SCALAR -> Int# -> VECTOR
    { Insert a scalar at the given position in a vector. }
-   with can_fail = True
+   with effect = CanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
@@ -3927,21 +3997,21 @@ primop VecMulOp "times#" GenPrimOp
 primop VecDivOp "divide#" GenPrimOp
    VECTOR -> VECTOR -> VECTOR
    { Divide two vectors element-wise. }
-   with can_fail = True
+   with effect = CanFail
         llvm_only = True
         vector = FLOAT_VECTOR_TYPES
 
 primop VecQuotOp "quot#" GenPrimOp
    VECTOR -> VECTOR -> VECTOR
    { Rounds towards zero element-wise. }
-   with can_fail = True
+   with effect = CanFail
         llvm_only = True
         vector = INT_VECTOR_TYPES
 
 primop VecRemOp "rem#" GenPrimOp
    VECTOR -> VECTOR -> VECTOR
    { Satisfies @('quot#' x y) 'times#' y 'plus#' ('rem#' x y) == x at . }
-   with can_fail = True
+   with effect = CanFail
         llvm_only = True
         vector = INT_VECTOR_TYPES
 
@@ -3954,46 +4024,46 @@ primop VecNegOp "negate#" GenPrimOp
 primop VecIndexByteArrayOp "indexArray#" GenPrimOp
    ByteArray# -> Int# -> VECTOR
    { Read a vector from specified index of immutable array. }
-   with can_fail = True
+   with effect = CanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
 primop VecReadByteArrayOp "readArray#" GenPrimOp
    MutableByteArray# s -> Int# -> State# s -> (# State# s, VECTOR #)
    { Read a vector from specified index of mutable array. }
-   with has_side_effects = True
-        can_fail = True
+   with effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
 primop VecWriteByteArrayOp "writeArray#" GenPrimOp
    MutableByteArray# s -> Int# -> VECTOR -> State# s -> State# s
    { Write a vector to specified index of mutable array. }
-   with has_side_effects = True
-        can_fail = True
+   with effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
 primop VecIndexOffAddrOp "indexOffAddr#" GenPrimOp
    Addr# -> Int# -> VECTOR
    { Reads vector; offset in bytes. }
-   with can_fail = True
+   with effect = CanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
 primop VecReadOffAddrOp "readOffAddr#" GenPrimOp
    Addr# -> Int# -> State# s -> (# State# s, VECTOR #)
    { Reads vector; offset in bytes. }
-   with has_side_effects = True
-        can_fail = True
+   with effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
 primop VecWriteOffAddrOp "writeOffAddr#" GenPrimOp
    Addr# -> Int# -> VECTOR -> State# s -> State# s
    { Write vector; offset in bytes. }
-   with has_side_effects = True
-        can_fail = True
+   with effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
@@ -4001,46 +4071,46 @@ primop VecWriteOffAddrOp "writeOffAddr#" GenPrimOp
 primop VecIndexScalarByteArrayOp "indexArrayAs#" GenPrimOp
    ByteArray# -> Int# -> VECTOR
    { Read a vector from specified index of immutable array of scalars; offset is in scalar elements. }
-   with can_fail = True
+   with effect = CanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
 primop VecReadScalarByteArrayOp "readArrayAs#" GenPrimOp
    MutableByteArray# s -> Int# -> State# s -> (# State# s, VECTOR #)
    { Read a vector from specified index of mutable array of scalars; offset is in scalar elements. }
-   with has_side_effects = True
-        can_fail = True
+   with effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
 primop VecWriteScalarByteArrayOp "writeArrayAs#" GenPrimOp
    MutableByteArray# s -> Int# -> VECTOR -> State# s -> State# s
    { Write a vector to specified index of mutable array of scalars; offset is in scalar elements. }
-   with has_side_effects = True
-        can_fail = True
+   with effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
 primop VecIndexScalarOffAddrOp "indexOffAddrAs#" GenPrimOp
    Addr# -> Int# -> VECTOR
    { Reads vector; offset in scalar elements. }
-   with can_fail = True
+   with effect = CanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
 primop VecReadScalarOffAddrOp "readOffAddrAs#" GenPrimOp
    Addr# -> Int# -> State# s -> (# State# s, VECTOR #)
    { Reads vector; offset in scalar elements. }
-   with has_side_effects = True
-        can_fail = True
+   with effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
 primop VecWriteScalarOffAddrOp "writeOffAddrAs#" GenPrimOp
    Addr# -> Int# -> VECTOR -> State# s -> State# s
    { Write vector; offset in scalar elements. }
-   with has_side_effects = True
-        can_fail = True
+   with effect = ReadWriteEffect
+        can_fail_warning = YesWarnCanFail
         llvm_only = True
         vector = ALL_VECTOR_TYPES
 
@@ -4089,7 +4159,7 @@ section "Prefetch"
   It is important to note that while the prefetch operations will never change the
   answer to a pure computation, They CAN change the memory locations resident
   in a CPU cache and that may change the performance and timing characteristics
-  of an application. The prefetch operations are marked has_side_effects=True
+  of an application. The prefetch operations are marked as ReadWriteEffect
   to reflect that these operations have side effects with respect to the runtime
   performance characteristics of the resulting code. Additionally, if the prefetchValue
   operations did not have this attribute, GHC does a float out transformation that
@@ -4106,70 +4176,70 @@ section "Prefetch"
 ---
 primop PrefetchByteArrayOp3 "prefetchByteArray3#" GenPrimOp
   ByteArray# -> Int# ->  State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchMutableByteArrayOp3 "prefetchMutableByteArray3#" GenPrimOp
   MutableByteArray# s -> Int# -> State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchAddrOp3 "prefetchAddr3#" GenPrimOp
   Addr# -> Int# -> State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchValueOp3 "prefetchValue3#" GenPrimOp
    a -> State# s -> State# s
-   with has_side_effects =  True
+   with effect = ReadWriteEffect
 ----
 
 primop PrefetchByteArrayOp2 "prefetchByteArray2#" GenPrimOp
   ByteArray# -> Int# ->  State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchMutableByteArrayOp2 "prefetchMutableByteArray2#" GenPrimOp
   MutableByteArray# s -> Int# -> State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchAddrOp2 "prefetchAddr2#" GenPrimOp
   Addr# -> Int# ->  State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchValueOp2 "prefetchValue2#" GenPrimOp
    a ->  State# s -> State# s
-   with has_side_effects =  True
+   with effect = ReadWriteEffect
 ----
 
 primop PrefetchByteArrayOp1 "prefetchByteArray1#" GenPrimOp
    ByteArray# -> Int# -> State# s -> State# s
-   with has_side_effects =  True
+   with effect = ReadWriteEffect
 
 primop PrefetchMutableByteArrayOp1 "prefetchMutableByteArray1#" GenPrimOp
   MutableByteArray# s -> Int# -> State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchAddrOp1 "prefetchAddr1#" GenPrimOp
   Addr# -> Int# -> State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchValueOp1 "prefetchValue1#" GenPrimOp
    a -> State# s -> State# s
-   with has_side_effects =  True
+   with effect = ReadWriteEffect
 ----
 
 primop PrefetchByteArrayOp0 "prefetchByteArray0#" GenPrimOp
   ByteArray# -> Int# ->  State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchMutableByteArrayOp0 "prefetchMutableByteArray0#" GenPrimOp
   MutableByteArray# s -> Int# -> State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchAddrOp0 "prefetchAddr0#" GenPrimOp
   Addr# -> Int# -> State# s -> State# s
-  with has_side_effects =  True
+  with effect = ReadWriteEffect
 
 primop PrefetchValueOp0 "prefetchValue0#" GenPrimOp
    a -> State# s -> State# s
-   with has_side_effects =  True
+   with effect = ReadWriteEffect
 
 
 -- Note [RuntimeRep polymorphism in continuation-style primops]


=====================================
compiler/GHC/Core.hs
=====================================
@@ -427,9 +427,6 @@ which will generate a @case@ if necessary
 
 The let-can-float invariant is initially enforced by mkCoreLet in GHC.Core.Make.
 
-For discussion of some implications of the let-can-float invariant primops see
-Note [Checking versus non-checking primops] in GHC.Builtin.PrimOps.
-
 Historical Note [The let/app invariant]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 Before 2022 GHC used the "let/app invariant", which applied the let-can-float rules


=====================================
compiler/GHC/Core/Opt/ConstantFold.hs
=====================================
@@ -1099,7 +1099,7 @@ shiftRule lit_num_ty shift_op = do
     _ | shift_len == 0 -> pure e1
 
       -- See Note [Guarding against silly shifts]
-    _ | shift_len < 0 || shift_len > bit_size
+    _ | shift_len < 0 || shift_len >= bit_size
       -> pure $ Lit $ mkLitNumberWrap platform lit_num_ty 0
            -- Be sure to use lit_num_ty here, so we get a correctly typed zero.
            -- See #18589
@@ -1581,15 +1581,15 @@ as follows:
     let x = I# (error "invalid shift")
     in ...
 
-This was originally done in the fix to #16449 but this breaks the let-can-float
-invariant (see Note [Core let-can-float invariant] in GHC.Core) as noted in #16742.
-For the reasons discussed in Note [Checking versus non-checking
-primops] (in the PrimOp module) there is no safe way to rewrite the argument of I#
-such that it bottoms.
+This was originally done in the fix to #16449 but this breaks the
+let-can-float invariant (see Note [Core let-can-float invariant] in
+GHC.Core) as noted in #16742.  For the reasons discussed under
+"NoEffect" in Note [Classifying primop effects] (in GHC.Builtin.PrimOps)
+there is no safe way to rewrite the argument of I# such that it bottoms.
 
-Consequently we instead take advantage of the fact that large shifts are
-undefined behavior (see associated documentation in primops.txt.pp) and
-transform the invalid shift into an "obviously incorrect" value.
+Consequently we instead take advantage of the fact that the result of a
+large shift is unspecified (see associated documentation in primops.txt.pp)
+and transform the invalid shift into an "obviously incorrect" value.
 
 There are two cases:
 
@@ -2016,13 +2016,6 @@ Only `SeqOp` shares that property.  (Other primops do not do anything
 as fancy as argument evaluation.)  The special handling for dataToTag#
 is:
 
-* GHC.Core.Utils.exprOkForSpeculation has a special case for DataToTagOp,
-  (actually in app_ok).  Most primops with lifted arguments do not
-  evaluate those arguments, but DataToTagOp and SeqOp are two
-  exceptions.  We say that they are /never/ ok-for-speculation,
-  regardless of the evaluated-ness of their argument.
-  See GHC.Core.Utils Note [exprOkForSpeculation and SeqOp/DataToTagOp]
-
 * There is a special case for DataToTagOp in GHC.StgToCmm.Expr.cgExpr,
   that evaluates its argument and then extracts the tag from
   the returned value.
@@ -2113,12 +2106,9 @@ Implementing seq#.  The compiler has magic for SeqOp in
 
 - GHC.StgToCmm.Expr.cgExpr, and cgCase: special case for seq#
 
-- GHC.Core.Utils.exprOkForSpeculation;
-  see Note [exprOkForSpeculation and SeqOp/DataToTagOp] in GHC.Core.Utils
-
 - Simplify.addEvals records evaluated-ness for the result; see
   Note [Adding evaluatedness info to pattern-bound variables]
-  in GHC.Core.Opt.Simplify
+  in GHC.Core.Opt.Simplify.Iteration
 -}
 
 seqRule :: RuleM CoreExpr


=====================================
compiler/GHC/Core/Opt/FloatIn.hs
=====================================
@@ -423,6 +423,30 @@ motivating example was #5658: in particular, this change allows
 array indexing operations, which have a single DEFAULT alternative
 without any binders, to be floated inward.
 
+In particular, we want to be able to transform
+
+  case indexIntArray# arr i of vi {
+    __DEFAULT -> case <# j n of _ {
+      __DEFAULT -> False
+      1# -> case indexIntArray# arr j of vj {
+        __DEFAULT -> ... vi ... vj ...
+      }
+    }
+  }
+
+by floating in `indexIntArray# arr i` to produce
+
+  case <# j n of _ {
+    __DEFAULT -> False
+    1# -> case indexIntArray# arr i of vi {
+      __DEFAULT -> case indexIntArray# arr j of vj {
+        __DEFAULT -> ... vi ... vj ...
+      }
+    }
+  }
+
+...which skips the `indexIntArray# arr i` call entirely in the out-of-bounds branch.
+
 SIMD primops for unpacking SIMD vectors into an unboxed tuple of unboxed
 scalars also need to be floated inward, but unpacks have a single non-DEFAULT
 alternative that binds the elements of the tuple. We now therefore also support
@@ -431,12 +455,11 @@ floating in cases with a single alternative that may bind values.
 But there are wrinkles
 
 * Which unlifted cases do we float?
-  See Note [PrimOp can_fail and has_side_effects] in GHC.Builtin.PrimOps which
-  explains:
-   - We can float in can_fail primops (which concerns imprecise exceptions),
-     but we can't float them out.
-   - But we can float a has_side_effects primop, but NOT inside a lambda,
-     so for now we don't float them at all. Hence exprOkForSideEffects.
+  See Note [Transformations affected by primop effects] in GHC.Builtin.PrimOps
+  which explains:
+   - We can float in or discard CanFail primops, but we can't float them out.
+   - We don't want to discard a synchronous exception or side effect
+     so we don't float those at all. Hence exprOkToDiscard.
    - Throwing precise exceptions is a special case of the previous point: We
      may /never/ float in a call to (something that ultimately calls)
      'raiseIO#'.
@@ -448,7 +471,7 @@ But there are wrinkles
   ===>
     f (case a /# b of r -> F# r)
   because that creates a new thunk that wasn't there before.  And
-  because it can't be floated out (can_fail), the thunk will stay
+  because it can't be floated out (CanFail), the thunk will stay
   there.  Disaster!  (This happened in nofib 'simple' and 'scs'.)
 
   Solution: only float cases into the branches of other cases, and
@@ -477,7 +500,7 @@ bindings are:
 fiExpr platform to_drop (_, AnnCase scrut case_bndr _ [AnnAlt con alt_bndrs rhs])
   | isUnliftedType (idType case_bndr)
      -- binders have a fixed RuntimeRep so it's OK to call isUnliftedType
-  , exprOkForSideEffects (deAnnotate scrut)
+  , exprOkToDiscard (deAnnotate scrut)
       -- See Note [Floating primops]
   = wrapFloats shared_binds $
     fiExpr platform (case_float : rhs_binds) rhs


=====================================
compiler/GHC/Core/Opt/SetLevels.hs
=====================================
@@ -15,7 +15,7 @@
    outwards (@FloatOut@).
 
 2. We also let-ify many expressions (notably case scrutinees), so they
-   will have a fighting chance of being floated sensible.
+   will have a fighting chance of being floated sensibly.
 
 3. Note [Need for cloning during float-out]
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


=====================================
compiler/GHC/Core/Opt/Simplify/Iteration.hs
=====================================
@@ -3016,11 +3016,10 @@ rebuildCase env scrut case_bndr alts@[Alt _ bndrs rhs] cont
   --      a) it binds nothing (so it's really just a 'seq')
   --      b) evaluating the scrutinee has no side effects
   | is_plain_seq
-  , exprOkForSideEffects scrut
+  , exprOkToDiscard scrut
           -- The entire case is dead, so we can drop it
           -- if the scrutinee converges without having imperative
           -- side effects or raising a Haskell exception
-          -- See Note [PrimOp can_fail and has_side_effects] in GHC.Builtin.PrimOps
    = simplExprF env rhs cont
 
   -- 2b.  Turn the case into a let, if


=====================================
compiler/GHC/Core/Utils.hs
=====================================
@@ -27,7 +27,7 @@ module GHC.Core.Utils (
         exprIsDupable, exprIsTrivial, getIdFromTrivialExpr,
         getIdFromTrivialExpr_maybe,
         exprIsCheap, exprIsExpandable, exprIsCheapX, CheapAppFun,
-        exprIsHNF, exprOkForSpeculation, exprOkForSideEffects, exprOkForSpecEval,
+        exprIsHNF, exprOkForSpeculation, exprOkToDiscard, exprOkForSpecEval,
         exprIsWorkFree, exprIsConLike,
         isCheapApp, isExpandableApp, isSaturatedConApp,
         exprIsTickedString, exprIsTickedString_maybe,
@@ -1381,6 +1381,7 @@ isWorkFreeApp fn n_val_args
   | otherwise
   = case idDetails fn of
       DataConWorkId {} -> True
+      PrimOpId op _    -> primOpIsWorkFree op
       _                -> False
 
 isCheapApp :: CheapAppFun
@@ -1488,34 +1489,45 @@ it's applied only to dictionaries.
 -}
 
 -----------------------------
--- | 'exprOkForSpeculation' returns True of an expression that is:
+-- | To a first approximation, 'exprOkForSpeculation' returns True of
+-- an expression that is:
 --
 --  * Safe to evaluate even if normal order eval might not
---    evaluate the expression at all, or
+--    evaluate the expression at all, and
 --
 --  * Safe /not/ to evaluate even if normal order would do so
 --
--- It is usually called on arguments of unlifted type, but not always
--- In particular, Simplify.rebuildCase calls it on lifted types
--- when a 'case' is a plain 'seq'. See the example in
--- Note [exprOkForSpeculation: case expressions] below
+-- More specifically, this means that:
+--  * A: Evaluation of the expression reaches weak-head-normal-form,
+--  * B: soon,
+--  * C: without causing a write side effect (e.g. writing a mutable variable).
 --
--- Precisely, it returns @True@ iff:
---  a) The expression guarantees to terminate,
---  b) soon,
---  c) without causing a write side effect (e.g. writing a mutable variable)
---  d) without throwing a Haskell exception
---  e) without risking an unchecked runtime exception (array out of bounds,
---     divide by zero)
+-- In particular, an expression that may
+--  * throw a synchronous Haskell exception, or
+--  * risk an unchecked runtime exception (e.g. array
+--    out of bounds, divide by zero)
+-- is /not/ considered OK-for-speculation, as these violate condition A.
 --
--- For @exprOkForSideEffects@ the list is the same, but omitting (e).
+-- For 'exprOkToDiscard', condition A is weakened to allow expressions
+-- that might risk an unchecked runtime exception but must otherwise
+-- reach weak-head-normal-form.
+-- (Note that 'exprOkForSpeculation' implies 'exprOkToDiscard')
 --
--- Note that
---    exprIsHNF            implies exprOkForSpeculation
---    exprOkForSpeculation implies exprOkForSideEffects
+-- But in fact both functions are a bit more conservative than the above,
+-- in at least the following ways:
+--
+--  * W1: We do not take advantage of already-evaluated lifted variables.
+--        As a result, 'exprIsHNF' DOES NOT imply 'exprOkForSpeculation';
+--        if @y@ is a case-binder of lifted type, then @exprIsHNF y@ is
+--        'True', while @exprOkForSpeculation y@ is 'False'.
+--        See Note [exprOkForSpeculation and evaluated variables] for why.
+--  * W2: Read-effects on mutable variables are currently also included.
+--        See Note [Classifying primop effects] "GHC.Builtin.PrimOps".
+--  * W3: Currently, 'exprOkForSpeculation' always returns 'False' for
+--        let-expressions.  Lets can be stacked deeply, so we just give up.
+--        In any case, the argument of 'exprOkForSpeculation' is usually in
+--        a strict context, so any lets will have been floated away.
 --
--- See Note [PrimOp can_fail and has_side_effects] in "GHC.Builtin.PrimOps"
--- and Note [Transformations affected by can_fail and has_side_effects]
 --
 -- As an example of the considerations in this test, consider:
 --
@@ -1529,12 +1541,24 @@ it's applied only to dictionaries.
 -- >    in E
 -- > }
 --
--- We can only do this if the @y + 1@ is ok for speculation: it has no
+-- We can only do this if the @y# +# 1#@ is ok for speculation: it has no
 -- side effects, and can't diverge or raise an exception.
+--
+--
+-- See also Note [Classifying primop effects] in "GHC.Builtin.PrimOps"
+-- and Note [Transformations affected by primop effects].
+--
+-- 'exprOkForSpeculation' is used to define Core's let-can-float
+-- invariant.  (See Note [Core let-can-float invariant] in
+-- "GHC.Core".)  It is therefore frequently called on arguments of
+-- unlifted type, especially via 'needsCaseBinding'.  But it is
+-- sometimes called on expressions of lifted type as well.  For
+-- example, see Note [Speculative evaluation] in "GHC.CoreToStg.Prep".
+
 
-exprOkForSpeculation, exprOkForSideEffects :: CoreExpr -> Bool
+exprOkForSpeculation, exprOkToDiscard :: CoreExpr -> Bool
 exprOkForSpeculation = expr_ok fun_always_ok primOpOkForSpeculation
-exprOkForSideEffects = expr_ok fun_always_ok primOpOkForSideEffects
+exprOkToDiscard      = expr_ok fun_always_ok primOpOkToDiscard
 
 fun_always_ok :: Id -> Bool
 fun_always_ok _ = True
@@ -1564,10 +1588,7 @@ expr_ok fun_ok primop_ok (Tick tickish e)
    | otherwise             = expr_ok fun_ok primop_ok e
 
 expr_ok _ _ (Let {}) = False
-  -- Lets can be stacked deeply, so just give up.
-  -- In any case, the argument of exprOkForSpeculation is
-  -- usually in a strict context, so any lets will have been
-  -- floated away.
+-- See W3 in the Haddock comment for exprOkForSpeculation
 
 expr_ok fun_ok primop_ok (Case scrut bndr _ alts)
   =  -- See Note [exprOkForSpeculation: case expressions]
@@ -1599,16 +1620,24 @@ app_ok :: (Id -> Bool) -> (PrimOp -> Bool) -> Id -> [CoreExpr] -> Bool
 app_ok fun_ok primop_ok fun args
   | not (fun_ok fun)
   = False -- This code path is only taken for Note [Speculative evaluation]
+
+  | idArity fun > n_val_args
+  -- Partial application: just check passing the arguments is OK
+  = args_ok
+
   | otherwise
   = case idDetails fun of
-      DFunId new_type ->  not new_type
+      DFunId new_type -> not new_type
          -- DFuns terminate, unless the dict is implemented
          -- with a newtype in which case they may not
 
-      DataConWorkId {} -> True
+      DataConWorkId {} -> args_ok
                 -- The strictness of the constructor has already
                 -- been expressed by its "wrapper", so we don't need
                 -- to take the arguments into account
+                   -- Well, we thought so.  But it's definitely wrong!
+                   -- See #20749 and Note [How untagged pointers can
+                   -- end up in strict fields] in GHC.Stg.InferTags
 
       ClassOpId _ is_terminating_result
         | is_terminating_result -- See Note [exprOkForSpeculation and type classes]
@@ -1630,16 +1659,7 @@ app_ok fun_ok primop_ok fun args
               -- Often there is a literal divisor, and this
               -- can get rid of a thunk in an inner loop
 
-        | SeqOp <- op  -- See Note [exprOkForSpeculation and SeqOp/DataToTagOp]
-        -> False       --     for the special cases for SeqOp and DataToTagOp
-        | DataToTagOp <- op
-        -> False
-        | KeepAliveOp <- op
-        -> False
-
-        | otherwise
-        -> primop_ok op  -- Check the primop itself
-        && and (zipWith arg_ok arg_tys args)  -- Check the arguments
+        | otherwise -> primop_ok op && args_ok
 
       _other  -- Unlifted and terminating types;
               -- Also c.f. the Var case of exprIsHNF
@@ -1652,10 +1672,6 @@ app_ok fun_ok primop_ok fun args
                   -- (If we added unlifted function types this would change,
                   -- and we'd need to actually test n_val_args == 0.)
 
-         -- Partial applications
-         | idArity fun > n_val_args ->
-           and (zipWith arg_ok arg_tys args)  -- Check the arguments
-
          -- Functions that terminate fast without raising exceptions etc
          -- See Note [Discarding unnecessary unsafeEqualityProofs]
          | fun `hasKey` unsafeEqualityProofIdKey -> True
@@ -1669,12 +1685,14 @@ app_ok fun_ok primop_ok fun args
     n_val_args   = valArgCount args
     (arg_tys, _) = splitPiTys fun_ty
 
-    -- Used for arguments to primops and to partial applications
+    -- Even if a function call itself is OK, any unlifted
+    -- args are still evaluated eagerly and must be checked
+    args_ok = and (zipWith arg_ok arg_tys args)
     arg_ok :: PiTyVarBinder -> CoreExpr -> Bool
     arg_ok (Named _) _ = True   -- A type argument
     arg_ok (Anon ty _) arg      -- A term argument
        | definitelyLiftedType (scaledThing ty)
-       = True -- See Note [Primops with lifted arguments]
+       = True -- lifted args are not evaluated eagerly
        | otherwise
        = expr_ok fun_ok primop_ok arg
 
@@ -1821,46 +1839,16 @@ points do the job nicely.
 ------- End of historical note ------------
 
 
-Note [Primops with lifted arguments]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Is this ok-for-speculation (see #13027)?
-   reallyUnsafePtrEquality# a b
-Well, yes.  The primop accepts lifted arguments and does not
-evaluate them.  Indeed, in general primops are, well, primitive
-and do not perform evaluation.
-
-Bottom line:
-  * In exprOkForSpeculation we simply ignore all lifted arguments.
-  * In the rare case of primops that /do/ evaluate their arguments,
-    (namely DataToTagOp and SeqOp) return False; see
-    Note [exprOkForSpeculation and evaluated variables]
-
-Note [exprOkForSpeculation and SeqOp/DataToTagOp]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Most primops with lifted arguments don't evaluate them
-(see Note [Primops with lifted arguments]), so we can ignore
-that argument entirely when doing exprOkForSpeculation.
-
-But DataToTagOp and SeqOp are exceptions to that rule.
-For reasons described in Note [exprOkForSpeculation and
-evaluated variables], we simply return False for them.
-
-Not doing this made #5129 go bad.
-Lots of discussion in #15696.
-
 Note [exprOkForSpeculation and evaluated variables]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Recall that
-  seq#       :: forall a s. a -> State# s -> (# State# s, a #)
-  dataToTag# :: forall a.   a -> Int#
-must always evaluate their first argument.
-
-Now consider these examples:
+Consider these examples:
  * case x of y { DEFAULT -> ....y.... }
    Should 'y' (alone) be considered ok-for-speculation?
 
  * case x of y { DEFAULT -> ....let z = dataToTag# y... }
-   Should (dataToTag# y) be considered ok-for-spec?
+   Should (dataToTag# y) be considered ok-for-spec? Recall that
+     dataToTag# :: forall a. a -> Int#
+   must always evaluate its argument. (See also Note [dataToTag# magic].)
 
 You could argue 'yes', because in the case alternative we know that
 'y' is evaluated.  But the binder-swap transformation, which is
@@ -1874,13 +1862,16 @@ and then it really, really doesn't obey the let-can-float invariant.
 
 The solution is simple: exprOkForSpeculation does not try to take
 advantage of the evaluated-ness of (lifted) variables.  And it returns
-False (always) for DataToTagOp and SeqOp.
+False (always) for primops that perform evaluation.  We achieve the latter
+by marking the relevant primops as "ThrowsException" or
+"ReadWriteEffect"; see also Note [Classifying primop effects] in
+GHC.Builtin.PrimOps.
 
 Note that exprIsHNF /can/ and does take advantage of evaluated-ness;
 it doesn't have the trickiness of the let-can-float invariant to worry about.
 
 Note [Discarding unnecessary unsafeEqualityProofs]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 In #20143 we found
    case unsafeEqualityProof @t1 @t2 of UnsafeRefl cv[dead] -> blah
 where 'blah' didn't mention 'cv'.  We'd like to discard this
@@ -1889,7 +1880,7 @@ To do this we need to know
   (a) that cv is unused (done by OccAnal), and
   (b) that unsafeEqualityProof terminates rapidly without side effects.
 
-At the moment we check that explicitly here in exprOkForSideEffects,
+At the moment we check that explicitly here in exprOkToDiscard,
 but one might imagine a more systematic check in future.
 
 
@@ -1904,15 +1895,15 @@ but one might imagine a more systematic check in future.
 -- ~~~~~~~~~~~~~~~~
 -- | exprIsHNF returns true for expressions that are certainly /already/
 -- evaluated to /head/ normal form.  This is used to decide whether it's ok
--- to change:
+-- to perform case-to-let for lifted expressions, which changes:
 --
--- > case x of _ -> e
+-- > case x of x' { _ -> e }
 --
 --    into:
 --
--- > e
+-- > let x' = x in e
 --
--- and to decide whether it's safe to discard a 'seq'.
+-- and in so doing makes the binding lazy.
 --
 -- So, it does /not/ treat variables as evaluated, unless they say they are.
 -- However, it /does/ treat partial applications and constructor applications


=====================================
compiler/GHC/Types/Demand.hs
=====================================
@@ -1427,7 +1427,7 @@ see Note [Data-con worker strictness].
 --
 -- [n] nontermination (e.g. loops)
 -- [i] throws imprecise exception
--- [p] throws precise exceTtion
+-- [p] throws precise exception
 -- [c] converges (reduces to WHNF).
 --
 -- The different lattice elements correspond to different subsets, indicated by


=====================================
compiler/Setup.hs
=====================================
@@ -38,12 +38,13 @@ primopIncls =
     [ ("primop-data-decl.hs-incl"         , "--data-decl")
     , ("primop-tag.hs-incl"               , "--primop-tag")
     , ("primop-list.hs-incl"              , "--primop-list")
-    , ("primop-has-side-effects.hs-incl"  , "--has-side-effects")
+    , ("primop-effects.hs-incl"           , "--primop-effects")
     , ("primop-out-of-line.hs-incl"       , "--out-of-line")
     , ("primop-commutable.hs-incl"        , "--commutable")
     , ("primop-code-size.hs-incl"         , "--code-size")
-    , ("primop-can-fail.hs-incl"          , "--can-fail")
     , ("primop-strictness.hs-incl"        , "--strictness")
+    , ("primop-is-work-free.hs-incl"      , "--is-work-free")
+    , ("primop-is-cheap.hs-incl"          , "--is-cheap")
     , ("primop-fixity.hs-incl"            , "--fixity")
     , ("primop-primop-info.hs-incl"       , "--primop-primop-info")
     , ("primop-vector-uniques.hs-incl"    , "--primop-vector-uniques")


=====================================
hadrian/src/Rules/Generate.hs
=====================================
@@ -82,16 +82,17 @@ compilerDependencies = do
     stage   <- getStage
     ghcPath <- expr $ buildPath (vanillaContext stage compiler)
     pure $ (ghcPath -/-) <$>
-                  [ "primop-can-fail.hs-incl"
-                  , "primop-code-size.hs-incl"
+                  [ "primop-code-size.hs-incl"
                   , "primop-commutable.hs-incl"
                   , "primop-data-decl.hs-incl"
                   , "primop-fixity.hs-incl"
-                  , "primop-has-side-effects.hs-incl"
+                  , "primop-effects.hs-incl"
                   , "primop-list.hs-incl"
                   , "primop-out-of-line.hs-incl"
                   , "primop-primop-info.hs-incl"
                   , "primop-strictness.hs-incl"
+                  , "primop-is-work-free.hs-incl"
+                  , "primop-is-cheap.hs-incl"
                   , "primop-tag.hs-incl"
                   , "primop-vector-tycons.hs-incl"
                   , "primop-vector-tys-exports.hs-incl"


=====================================
hadrian/src/Rules/Lint.hs
=====================================
@@ -98,11 +98,12 @@ hsIncls path = [ path </> "primop-vector-tycons.hs-incl"
                , path </> "primop-tag.hs-incl"
                , path </> "primop-list.hs-incl"
                , path </> "primop-strictness.hs-incl"
+               , path </> "primop-is-work-free.hs-incl"
+               , path </> "primop-is-cheap.hs-incl"
                , path </> "primop-fixity.hs-incl"
                , path </> "primop-docs.hs-incl"
                , path </> "primop-primop-info.hs-incl"
                , path </> "primop-out-of-line.hs-incl"
-               , path </> "primop-has-side-effects.hs-incl"
-               , path </> "primop-can-fail.hs-incl"
+               , path </> "primop-effects.hs-incl"
                , path </> "primop-commutable.hs-incl"
                ]


=====================================
hadrian/src/Settings/Builders/GenPrimopCode.hs
=====================================
@@ -9,12 +9,13 @@ genPrimopCodeBuilderArgs = builder GenPrimopCode ? mconcat
     , output "//primop-data-decl.hs-incl"          ? arg "--data-decl"
     , output "//primop-tag.hs-incl"                ? arg "--primop-tag"
     , output "//primop-list.hs-incl"               ? arg "--primop-list"
-    , output "//primop-has-side-effects.hs-incl"   ? arg "--has-side-effects"
+    , output "//primop-effects.hs-incl"            ? arg "--primop-effects"
     , output "//primop-out-of-line.hs-incl"        ? arg "--out-of-line"
     , output "//primop-commutable.hs-incl"         ? arg "--commutable"
     , output "//primop-code-size.hs-incl"          ? arg "--code-size"
-    , output "//primop-can-fail.hs-incl"           ? arg "--can-fail"
     , output "//primop-strictness.hs-incl"         ? arg "--strictness"
+    , output "//primop-is-work-free.hs-incl"       ? arg "--is-work-free"
+    , output "//primop-is-cheap.hs-incl"           ? arg "--is-cheap"
     , output "//primop-fixity.hs-incl"             ? arg "--fixity"
     , output "//primop-primop-info.hs-incl"        ? arg "--primop-primop-info"
     , output "//primop-vector-uniques.hs-incl"     ? arg "--primop-vector-uniques"


=====================================
libraries/base/GHC/Clock.hsc
=====================================
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Trustworthy #-}
 {-# LANGUAGE NoImplicitPrelude #-}
 
@@ -9,17 +10,36 @@ module GHC.Clock
 import GHC.Base
 import GHC.Real
 import Data.Word
+#if defined(javascript_HOST_ARCH)
+import GHC.Num
+#endif
 
 -- | Return monotonic time in seconds, since some unspecified starting point
 --
 -- @since 4.11.0.0
 getMonotonicTime :: IO Double
-getMonotonicTime = do w <- getMonotonicTimeNSec
-                      return (fromIntegral w / 1000000000)
+getMonotonicTime = do
+#if defined(javascript_HOST_ARCH)
+  w <- getMonotonicTimeMSec
+  return (w / 1000)
+#else
+  w <- getMonotonicTimeNSec
+  return (fromIntegral w / 1000000000)
+#endif
 
 -- | Return monotonic time in nanoseconds, since some unspecified starting point
 --
 -- @since 4.11.0.0
+#if defined(javascript_HOST_ARCH)
+getMonotonicTimeNSec :: IO Word64
+getMonotonicTimeNSec = do
+  w <- getMonotonicTimeMSec
+  return (floor w * 1000000)
+
+foreign import javascript unsafe "performance.now" getMonotonicTimeMSec:: IO Double
+
+
+#else
 foreign import ccall unsafe "getMonotonicNSec"
     getMonotonicTimeNSec :: IO Word64
-
+#endif


=====================================
libraries/base/GHC/Conc/POSIX.hs
=====================================
@@ -49,6 +49,7 @@ module GHC.Conc.POSIX
 
 import Data.Bits (shiftR)
 import GHC.Base
+import GHC.Clock
 import GHC.Conc.Sync
 import GHC.Conc.POSIX.Const
 import GHC.Event.Windows.ConsoleEvent
@@ -209,13 +210,9 @@ delayTime (Delay t _) = t
 delayTime (DelaySTM t _) = t
 
 type USecs = Word64
-type NSecs = Word64
-
-foreign import ccall unsafe "getMonotonicNSec"
-  getMonotonicNSec :: IO NSecs
 
 getMonotonicUSec :: IO USecs
-getMonotonicUSec = fmap (`div` 1000) getMonotonicNSec
+getMonotonicUSec = fmap (`div` 1000) getMonotonicTimeNSec
 
 {-# NOINLINE prodding #-}
 prodding :: IORef Bool


=====================================
libraries/base/GHC/IO/Exception.hs
=====================================
@@ -329,13 +329,16 @@ type IOError = IOException
 -- flagged.
 data IOException
  = IOError {
-     ioe_handle   :: Maybe Handle,   -- the handle used by the action flagging
-                                     -- the error.
-     ioe_type     :: IOErrorType,    -- what it was.
-     ioe_location :: String,         -- location.
-     ioe_description :: String,      -- error type specific information.
-     ioe_errno    :: Maybe CInt,     -- errno leading to this error, if any.
-     ioe_filename :: Maybe FilePath  -- filename the error is related to.
+     ioe_handle   :: Maybe Handle,   -- ^ the handle used by the action flagging
+                                     --   the error.
+     ioe_type     :: IOErrorType,    -- ^ what it was.
+     ioe_location :: String,         -- ^ location.
+     ioe_description :: String,      -- ^ error type specific information.
+     ioe_errno    :: Maybe CInt,     -- ^ errno leading to this error, if any.
+     ioe_filename :: Maybe FilePath  -- ^ filename the error is related to
+                                     --   (some libraries may assume different encodings
+                                     --   when constructing this field from e.g. 'ByteString'
+                                     --   or other types)
    }
 
 -- | @since 4.1.0.0


=====================================
libraries/base/Unsafe/Coerce.hs
=====================================
@@ -78,9 +78,11 @@ floating.
 But what stops the whole (case unsafeEqualityProof of ...) from
 floating?  Answer: we never float a case on a redex that can fail
 outside a conditional.  See Primop.hs,
-Note [Transformations affected by can_fail and has_side_effects].
+Note [Transformations affected by primop effects].
 And unsafeEqualityProof (being opaque) is definitely treated as
 can-fail.
+  (Huh? It seems we have a special-case in exprOkForSpeculation (app_ok)
+   /specifically/ allowing unsafeEqualityProof.  Something smells wrong.)
 
 While unsafeCoerce is a perfectly ordinary function that needs no
 special treatment, Unsafe.Coerce.unsafeEqualityProof is magical, in


=====================================
libraries/base/tests/T23687.hs
=====================================
@@ -0,0 +1,14 @@
+module Main where
+
+import GHC.Clock
+import Control.Monad
+
+main :: IO ()
+main = do
+  a <- getMonotonicTimeNSec
+  b <- getMonotonicTimeNSec
+  when (a > b) $ putStrLn "Non-monotonic time"
+
+  c <- getMonotonicTime
+  d <- getMonotonicTime
+  when (c > d) $ putStrLn "Non-monotonic time"


=====================================
libraries/base/tests/all.T
=====================================
@@ -310,3 +310,4 @@ test('inits1tails1', normal, compile_and_run, [''])
 test('CLC149', normal, compile, [''])
 test('AtomicSwapIORef', normal, compile_and_run, [''])
 test('T23454', normal, compile_fail, [''])
+test('T23687', normal, compile_and_run, [''])


=====================================
testsuite/tests/ghc-api/PrimOpEffect_Sanity.hs
=====================================
@@ -0,0 +1,17 @@
+import GHC.Builtin.PrimOps
+import GHC.Utils.Outputable
+
+main :: IO ()
+main = let
+  errs = do
+    op <- allThePrimOps
+    case  primOpEffect op  of
+      NoEffect -> []
+      CanFail  -> []
+      ThrowsException -> []
+      ReadWriteEffect
+        -> [ppr op <+> text "has ReadWriteEffect but is work-free"
+           | primOpIsWorkFree op]
+        ++ [ppr op <+> text "has ReadWriteEffect but is cheap"
+           | primOpIsCheap op]
+  in  putStrLn $ showSDocUnsafe (vcat errs)


=====================================
testsuite/tests/ghc-api/all.T
=====================================
@@ -37,3 +37,4 @@ test('T19156', [ extra_run_opts('"' + config.libdir + '"')
 test('T20757', [unless(opsys('mingw32'), skip), exit_code(1)],
                compile_and_run,
                ['-package ghc'])
+test('PrimOpEffect_Sanity', normal, compile_and_run, ['-Wall -Werror -package ghc'])


=====================================
utils/genprimopcode/AccessOps.hs
=====================================
@@ -83,7 +83,7 @@ mkIndexByteArrayOp e = PrimOpSpec
              (elt_rep_ty e)
   , cat = GenPrimOp
   , desc = "Read " ++ elt_desc e ++ "; offset in " ++ prettyOffset e ++ "."
-  , opts = [OptionTrue "can_fail"]
+  , opts = [OptionEffect CanFail]
   }
 
 mkUnalignedIndexByteArrayOp :: ElementType -> Entry
@@ -95,7 +95,7 @@ mkUnalignedIndexByteArrayOp e = PrimOpSpec
              (elt_rep_ty e)
   , cat = GenPrimOp
   , desc = "Read " ++ elt_desc e ++ "; offset in bytes."
-  , opts = [OptionTrue "can_fail"]
+  , opts = [OptionEffect CanFail]
   }
 
 mkReadByteArrayOp :: ElementType -> Entry
@@ -107,7 +107,7 @@ mkReadByteArrayOp e = PrimOpSpec
        $ readResTy e
   , cat = GenPrimOp
   , desc = "Read " ++ elt_desc e ++ "; offset in " ++ prettyOffset e ++ "."
-  , opts = [OptionTrue "can_fail", OptionTrue "has_side_effects"]
+  , opts = [OptionEffect ReadWriteEffect, OptionCanFailWarnFlag YesWarnCanFail]
   }
 
 mkUnalignedReadByteArrayOp :: ElementType -> Entry
@@ -119,7 +119,7 @@ mkUnalignedReadByteArrayOp e = PrimOpSpec
        $ readResTy e
   , cat = GenPrimOp
   , desc = "Read " ++ elt_desc e ++ "; offset in bytes."
-  , opts = [OptionTrue "can_fail", OptionTrue "has_side_effects"]
+  , opts = [OptionEffect ReadWriteEffect, OptionCanFailWarnFlag YesWarnCanFail]
   }
 
 mkWriteByteArrayOp :: ElementType -> Entry
@@ -131,7 +131,7 @@ mkWriteByteArrayOp e = PrimOpSpec
        $ writeResTy e
   , cat = GenPrimOp
   , desc = "Write " ++ elt_desc e ++ "; offset in " ++ prettyOffset e ++ "."
-  , opts = [OptionTrue "can_fail", OptionTrue "has_side_effects"]
+  , opts = [OptionEffect ReadWriteEffect, OptionCanFailWarnFlag YesWarnCanFail]
   }
 
 mkUnalignedWriteByteArrayOp :: ElementType -> Entry
@@ -143,7 +143,7 @@ mkUnalignedWriteByteArrayOp e = PrimOpSpec
        $ writeResTy e
   , cat = GenPrimOp
   , desc = "Write " ++ elt_desc e ++ "; offset in bytes."
-  , opts = [OptionTrue "can_fail", OptionTrue "has_side_effects"]
+  , opts = [OptionEffect ReadWriteEffect, OptionCanFailWarnFlag YesWarnCanFail]
   }
 
 
@@ -168,7 +168,7 @@ mkIndexOffAddrOp e = PrimOpSpec
   , cat = GenPrimOp
   , desc = "Read " ++ elt_desc e ++ "; offset in " ++ prettyOffset e ++ ".\n\n"
            ++ getAlignWarn e
-  , opts = [OptionTrue "can_fail"]
+  , opts = [OptionEffect CanFail]
   }
 
 {-
@@ -181,7 +181,7 @@ mkUnalignedIndexOffAddrOp e = PrimOpSpec
              (elt_rep_ty e)
   , cat = GenPrimOp
   , desc = "Read " ++ elt_desc e ++ "; offset in bytes."
-  , opts = [OptionTrue "can_fail"]
+  , opts = [OptionEffect CanFail]
   }
 -}
 
@@ -195,7 +195,7 @@ mkReadOffAddrOp e = PrimOpSpec
   , cat = GenPrimOp
   , desc = "Read " ++ elt_desc e ++ "; offset in " ++ prettyOffset e ++ ".\n\n"
            ++ getAlignWarn e
-  , opts = [OptionTrue "can_fail", OptionTrue "has_side_effects"]
+  , opts = [OptionEffect ReadWriteEffect, OptionCanFailWarnFlag YesWarnCanFail]
   }
 
 {-
@@ -208,7 +208,7 @@ mkUnalignedReadOffAddrOp e = PrimOpSpec
        $ readResTy e
   , cat = GenPrimOp
   , desc = "Read " ++ elt_desc e ++ "; offset in bytes."
-  , opts = [OptionTrue "can_fail", OptionTrue "has_side_effects"]
+  , opts = [OptionEffect ReadWriteEffect, OptionCanFailWarnFlag YesWarnCanFail]
   }
 -}
 
@@ -222,7 +222,7 @@ mkWriteOffAddrOp e = PrimOpSpec
   , cat = GenPrimOp
   , desc = "Write " ++ elt_desc e ++ "; offset in " ++ prettyOffset e ++ ".\n\n"
            ++ getAlignWarn e
-  , opts = [OptionTrue "can_fail", OptionTrue "has_side_effects"]
+  , opts = [OptionEffect ReadWriteEffect, OptionCanFailWarnFlag YesWarnCanFail]
   }
 
 {-
@@ -235,7 +235,7 @@ mkUnalignedWriteOffAddrOp e = PrimOpSpec
        $ writeResTy e
   , cat = GenPrimOp
   , desc = "Write " ++ elt_desc e ++ "; offset in bytes."
-  , opts = [OptionTrue "can_fail", OptionTrue "has_side_effects"]
+  , opts = [OptionEffect ReadWriteEffect, OptionCanFailWarnFlag YesWarnCanFail]
   }
 -}
 


=====================================
utils/genprimopcode/Lexer.x
=====================================
@@ -51,6 +51,15 @@ words :-
     <0>         "infixl"            { mkT TInfixL }
     <0>         "infixr"            { mkT TInfixR }
     <0>         "Nothing"           { mkT TNothing }
+    <0>         "effect"            { mkT TEffect }
+    <0>         "NoEffect"          { mkT TNoEffect }
+    <0>         "CanFail"           { mkT TCanFail }
+    <0>         "ThrowsException"   { mkT TThrowsException }
+    <0>         "ReadWriteEffect"   { mkT TReadWriteEffect }
+    <0>         "can_fail_warning"  { mkT TCanFailWarnFlag }
+    <0>         "DoNotWarnCanFail"  { mkT TDoNotWarnCanFail }
+    <0>         "WarnIfEffectIsCanFail" { mkT TWarnIfEffectIsCanFail }
+    <0>         "YesWarnCanFail"    { mkT TYesWarnCanFail }
     <0>         "vector"            { mkT TVector }
     <0>         "bytearray_access_ops" { mkT TByteArrayAccessOps }
     <0>         "addr_access_ops"   { mkT TAddrAccessOps }


=====================================
utils/genprimopcode/Main.hs
=====================================
@@ -126,11 +126,6 @@ main = getArgs >>= \args ->
                       "--data-decl"
                          -> putStr (gen_data_decl p_o_specs)
 
-                      "--has-side-effects"
-                         -> putStr (gen_switch_from_attribs
-                                       "has_side_effects"
-                                       "primOpHasSideEffects" p_o_specs)
-
                       "--out-of-line"
                          -> putStr (gen_switch_from_attribs
                                        "out_of_line"
@@ -146,10 +141,15 @@ main = getArgs >>= \args ->
                                        "code_size"
                                        "primOpCodeSize" p_o_specs)
 
-                      "--can-fail"
+                      "--is-work-free"
+                         -> putStr (gen_switch_from_attribs
+                                       "work_free"
+                                       "primOpIsWorkFree" p_o_specs)
+
+                      "--is-cheap"
                          -> putStr (gen_switch_from_attribs
-                                       "can_fail"
-                                       "primOpCanFail" p_o_specs)
+                                       "cheap"
+                                       "primOpIsCheap" p_o_specs)
 
                       "--strictness"
                          -> putStr (gen_switch_from_attribs
@@ -161,6 +161,11 @@ main = getArgs >>= \args ->
                                        "fixity"
                                        "primOpFixity" p_o_specs)
 
+                      "--primop-effects"
+                         -> putStr (gen_switch_from_attribs
+                                       "effect"
+                                       "primOpEffect" p_o_specs)
+
                       "--primop-primop-info"
                          -> putStr (gen_primop_info p_o_specs)
 
@@ -197,13 +202,14 @@ main = getArgs >>= \args ->
 known_args :: [String]
 known_args
    = [ "--data-decl",
-       "--has-side-effects",
        "--out-of-line",
        "--commutable",
        "--code-size",
-       "--can-fail",
+       "--is-work-free",
+       "--is-cheap",
        "--strictness",
        "--fixity",
+       "--primop-effects",
        "--primop-primop-info",
        "--primop-tag",
        "--primop-list",
@@ -287,7 +293,9 @@ gen_hs_source (Info defaults entries) =
            opt (OptionString n v) = n ++ " = { " ++ v ++ "}"
            opt (OptionInteger n v) = n ++ " = " ++ show v
            opt (OptionVector _)    = ""
-           opt (OptionFixity mf) = "fixity" ++ " = " ++ show mf
+           opt (OptionFixity mf) = "fixity = " ++ show mf
+           opt (OptionEffect eff) = "effect = " ++ show eff
+           opt (OptionCanFailWarnFlag wf) = "can_fail_warning = " ++ show wf
 
            hdr s@(Section {})                                    = sec s
            hdr (PrimOpSpec { name = n })                         = wrapOp n ++ ","
@@ -341,7 +349,10 @@ gen_hs_source (Info defaults entries) =
 
            can_fail options
              = [ "can fail with an unchecked exception"
-               | Just (OptionTrue _) <- [lookup_attrib "can_fail" options] ]
+               | Just (OptionEffect eff) <- [lookup_attrib "effect" options]
+               , Just (OptionCanFailWarnFlag wflag) <- [lookup_attrib "can_fail_warning" options]
+               , wflag /= DoNotWarnCanFail
+               , wflag == YesWarnCanFail || eff == CanFail ]
 
            prim_deprecated options n
               = [ "{-# DEPRECATED " ++ wrapOp n ++ " \"" ++ msg ++ "\" #-}"
@@ -608,6 +619,8 @@ gen_switch_from_attribs attrib_name fn_name (Info defaults entries)
          getAltRhs (OptionString _ s) = s
          getAltRhs (OptionVector _) = "True"
          getAltRhs (OptionFixity mf) = show mf
+         getAltRhs (OptionEffect eff) = show eff
+         getAltRhs (OptionCanFailWarnFlag wf) = show wf
 
          mkAlt po
             = case lookup_attrib attrib_name (opts po) of
@@ -621,13 +634,13 @@ gen_switch_from_attribs attrib_name fn_name (Info defaults entries)
             Nothing -> error ("gen_switch_from: " ++ attrib_name)
             Just xx
                -> unlines alternatives
-                  ++ fn_name ++ " _ = " ++ getAltRhs xx ++ "\n"
+                  ++ fn_name ++ " _thisOp = " ++ getAltRhs xx ++ "\n"
 
 {-
 Note [GHC.Prim Docs]
 ~~~~~~~~~~~~~~~~~~~~
 For haddocks of GHC.Prim we generate a dummy haskell file (gen_hs_source) that
-contains the type signatures and the commends (but no implementations)
+contains the type signatures and the comments (but no implementations)
 specifically for consumption by haddock.
 
 GHCi's :doc command reads directly from ModIface's though, and GHC.Prim has a


=====================================
utils/genprimopcode/Parser.y
=====================================
@@ -45,6 +45,15 @@ import AccessOps
     infixl          { TInfixL }
     infixr          { TInfixR }
     nothing         { TNothing }
+    effect          { TEffect }
+    NoEffect        { TNoEffect }
+    CanFail         { TCanFail }
+    ThrowsException { TThrowsException }
+    ReadWriteEffect { TReadWriteEffect }
+    can_fail_warning { TCanFailWarnFlag }
+    DoNotWarnCanFail { TDoNotWarnCanFail }
+    WarnIfEffectIsCanFail { TWarnIfEffectIsCanFail }
+    YesWarnCanFail  { TYesWarnCanFail }
     vector          { TVector }
     SCALAR          { TSCALAR }
     VECTOR          { TVECTOR }
@@ -77,6 +86,8 @@ pOption : lowerName '=' false               { OptionFalse  $1 }
         | lowerName '=' integer             { OptionInteger $1 $3 }
         | vector    '=' pVectorTemplate     { OptionVector $3 }
         | fixity    '=' pInfix              { OptionFixity $3 }
+        | effect    '=' pEffect             { OptionEffect $3 }
+        | can_fail_warning '=' pPrimOpCanFailWarnFlag { OptionCanFailWarnFlag $3 }
 
 pInfix :: { Maybe Fixity }
 pInfix : infix  integer { Just $ Fixity NoSourceText $2 InfixN }
@@ -84,6 +95,16 @@ pInfix : infix  integer { Just $ Fixity NoSourceText $2 InfixN }
        | infixr integer { Just $ Fixity NoSourceText $2 InfixR }
        | nothing        { Nothing }
 
+pEffect :: { PrimOpEffect }
+pEffect : NoEffect                { NoEffect }
+        | CanFail                 { CanFail }
+        | ThrowsException         { ThrowsException }
+        | ReadWriteEffect         { ReadWriteEffect }
+
+pPrimOpCanFailWarnFlag :: { PrimOpCanFailWarnFlag }
+pPrimOpCanFailWarnFlag : DoNotWarnCanFail { DoNotWarnCanFail }
+                          | WarnIfEffectIsCanFail { WarnIfEffectIsCanFail }
+                          | YesWarnCanFail { YesWarnCanFail }
 
 pEntries :: { [Entry] }
 pEntries : pEntry pEntries { $1 : $2 }


=====================================
utils/genprimopcode/ParserM.hs
=====================================
@@ -111,6 +111,15 @@ data Token = TEOF
            | TInfixL
            | TInfixR
            | TNothing
+           | TEffect
+           | TNoEffect
+           | TCanFail
+           | TThrowsException
+           | TReadWriteEffect
+           | TCanFailWarnFlag
+           | TDoNotWarnCanFail
+           | TWarnIfEffectIsCanFail
+           | TYesWarnCanFail
            | TVector
            | TSCALAR
            | TVECTOR


=====================================
utils/genprimopcode/Syntax.hs
=====================================
@@ -61,6 +61,8 @@ data Option
    | OptionInteger String Int     -- name = <int>
    | OptionVector [(String,String,Int)]  -- name = [(,...),...]
    | OptionFixity (Maybe Fixity)  -- fixity = infix{,l,r} <int> | Nothing
+   | OptionEffect PrimOpEffect    -- effect = NoEffect | DoNotSpeculate | CanFail | ThrowsException | ReadWriteEffect | FallibleReadWriteEffect
+   | OptionCanFailWarnFlag PrimOpCanFailWarnFlag -- can_fail_warning = DoNotWarnCanFail | WarnIfEffectIsCanFail | YesWarnCanFail
      deriving Show
 
 -- categorises primops
@@ -109,6 +111,19 @@ data SourceText = SourceText String
                 | NoSourceText
                 deriving (Eq,Show)
 
+data PrimOpEffect
+  = NoEffect
+  | CanFail
+  | ThrowsException
+  | ReadWriteEffect
+  deriving (Eq, Show)
+
+data PrimOpCanFailWarnFlag
+  = DoNotWarnCanFail
+  | WarnIfEffectIsCanFail
+  | YesWarnCanFail
+  deriving (Eq, Show)
+
 ------------------------------------------------------------------
 -- Sanity checking -----------------------------------------------
 ------------------------------------------------------------------
@@ -131,7 +146,7 @@ sanityTop :: Info -> ()
 sanityTop (Info defs entries)
    = let opt_names = map get_attrib_name defs
          primops = filter is_primop entries
-     in  
+     in
      if   length opt_names /= length (nub opt_names)
      then error ("non-unique default attribute names: " ++ show opt_names ++ "\n")
      else myseqAll (map (sanityPrimOp opt_names) primops) ()
@@ -168,6 +183,8 @@ get_attrib_name (OptionString nm _) = nm
 get_attrib_name (OptionInteger nm _) = nm
 get_attrib_name (OptionVector _) = "vector"
 get_attrib_name (OptionFixity _) = "fixity"
+get_attrib_name (OptionEffect _) = "effect"
+get_attrib_name (OptionCanFailWarnFlag _) = "can_fail_warning"
 
 lookup_attrib :: String -> [Option] -> Maybe Option
 lookup_attrib _ [] = Nothing



View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/59c6d66624f1cafd8d6d80de6c7bfc8bf15ab46c...2810170db7d66e385adb9199c85a306a8bf1cf56

-- 
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/59c6d66624f1cafd8d6d80de6c7bfc8bf15ab46c...2810170db7d66e385adb9199c85a306a8bf1cf56
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/20230731/c25f8e0f/attachment-0001.html>


More information about the ghc-commits mailing list