PseudoOps in primops.txt.pp

Sebastian Graf sgraf1337 at
Sun Aug 11 13:14:05 UTC 2019

This turned out to be rather lengthy and ambivalent, but my current TLDR;
of this is that GHC.Magic Ids could all be PseudoOps, because we don't use
their definitions anyway.


Regarding 2., the answer has been right before my eyes in the form of Note
[ghcPrimIds (aka pseudoops)] and Note [magicIds]. The most important
difference I guess is that we can give meaningful, yet forgetful
definitions for functions in GHC.Magic, whereas we can't for proper

Note [ghcPrimIds (aka pseudoops)] also answers 3.: IIUC if PseudoOps aren't
free abstractions already (proxy#), we try to inline them immediately in
Core. For example `noinline`, which is never inlined, could never be a
PseudoOp. As a side note: `noinline` seems to lack proper handling in the
demand analyser. For example, `noinline id x` should be detected as strict
in `x` by unleashing `id`s strictness signature. Not sure if we currently
do that.

The "What are PseudoOps" part of 1. is thus mostly resolved: PseudoOps are
functions with special semantics that can be lowered or erased in Core or
STG, so we will never have to think about generating code for them. We
still need to treat them specially, because we have no way to encode their
semantics at the source level. Examples (these are all current PseudoOps):

   - `seq` works on functions, but Haskell's `case` doesn't
   - `proxy#` is a symbolic inhabitant of `Proxy#`, which will be erased in
   code generation. I guess with -XUnliftedNewtypes we can finally define
   `Proxy#` in source Haskell as `newtype Proxy# a = Proxy# (# #)`
   - `unsafeCoerce#` can only be erased when going to STG, where we don't
   type check as part of linting.
   - `coerce` gets translated to casts as part of desugaring.
   - `nullAddr#` get inlined immediately to corresponding literal in Core.
   This is so that source syntax doesn't have to introduce a new literal.

Similarly, the definitions of GHC.Magic all seem to vanish after CorePrep.
In fact, I begin to think that GHC.Magic is just a subset of PseudoOps that
have semantics expressible in source Haskell (thus have a meaningful
definition). Which somewhat contradicts my observation above that
`noinline` couldn't be a PseudoOp: Clearly it could, because it is lowered
to id by the time we go to STG. This lowering (even in higher-order
situations, which is why we actually don't need the definition) seems to be
the whole point about having the compiler be aware of these special

So, for a concrete question: What are the reasons that we don't make i.e.
`lazy` a PseudoOp?

Am So., 11. Aug. 2019 um 12:42 Uhr schrieb Sebastian Graf <
sgraf1337 at>:

> Hey fellow devs,
> While implementing new PseudoOps, a couple of questions popped up:
>    1. What are PseudoOps? When do we want to declare one? There doesn't
>    seem to be any documentation around them. I only figured out that I
>    probably want a PseudoOp by comparing to PrimOps I thought would be lowered
>    at a similar stage (i.e. somewhere in Core or STG).
>    2. Why aren't GHC.Magic.{lazy,noinline,oneShot} PseudoOps?
>    3. Since we have to set all the IdInfo for `seq` and `noinline`
>    manually, why is this incomplete? I.e., I'd expect a useful strictness
>    signature and arity for both of these.
> Thanks!
> Sebastian
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <>

More information about the ghc-devs mailing list