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#, 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
- Exceptions: asynchronous, synchronous, and unchecked GHC.Builtin.PrimOps
- Transformations affected by primop effects GHC.Builtin.PrimOps
- Guarding against silly shifts GHC.Core.Opt.ConstantFold
Referenced by 9
- GHC.Builtin.PrimOps call site ×3
- GHC.Core.Utils call site ×2
- Transformations affected by primop effects GHC.Builtin.PrimOps
- Guarding against silly shifts GHC.Core.Opt.ConstantFold
- exprOkForSpeculation and evaluated variables GHC.Core.Utils
- GHC.CoreToStg.Prep call site