Note [Classifying primop effects]

GHC/Builtin/PrimOps.hs:369 compiler 1 ticket

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#, dataToTagLarge#, 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.

References 3

Referenced by 9