Skip to content

Make everything BoxedRep have enter code

The recent UnliftedDatatypes extension has turned up an annoying wart in our runtime system: While every lifted heap object I know of has enter code, primitive unlifted heap objects such as MutVar# do not. Here's an example:

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE UnliftedDatatypes #-}
{-# LANGUAGE MagicHash, UnboxedTuples #-}

import Unsafe.Coerce
import Data.Kind
import Data.IORef
import GHC.Exts
import GHC.IO

sek :: forall (a :: UnliftedType) b. a -> b -> b
sek = unsafeCoerce seq
{-# OPAQUE sek #-}

data T :: UnliftedType where
  MkT :: Int -> T

main = do
  let t = MkT 42
  t `sek` return ()
  IO $ \s -> case newMutVar# (42 :: Int) s of -- can't use IO for non-lifted return types, hence unpacked here
    (# s', mv #) -> case mv `sek` () of
      () -> (# s', () #)

While the sek on t works perfectly fine, the sek on mv will crash today:

test: internal error: MUT_VAR_DIRTY object (0x4200404240) entered!
Stack trace:
test: Failed to get stack frames of current process: no matching address range: Invalid argument
                  0x7a7f42    set_initial_registers (rts/Libdw.c:294.5)
            0x7fc63df84f98    dwfl_thread_getframes (/nix/store/7rnw4vh83qd4nza3n7ffsicwg5rbkz0m-elfutils-0.186/lib/libdw-0.186.so)
            0x7fc63df84adb    get_one_thread_cb (/nix/store/7rnw4vh83qd4nza3n7ffsicwg5rbkz0m-elfutils-0.186/lib/libdw-0.186.so)
            0x7fc63df84e02    dwfl_getthreads (/nix/store/7rnw4vh83qd4nza3n7ffsicwg5rbkz0m-elfutils-0.186/lib/libdw-0.186.so)
            0x7fc63df85347    dwfl_getthread_frames (/nix/store/7rnw4vh83qd4nza3n7ffsicwg5rbkz0m-elfutils-0.186/lib/libdw-0.186.so)
                  0x7a85d3    libdwGetBacktrace (rts/Libdw.c:265.5)
                  0x77a00d    rtsFatalInternalErrorFn (rts/RtsMessages.c:176.22)
                  0x77a1ca    barf (rts/RtsMessages.c:49.4)
                  0x79d5f5    stg_MUT_VAR_DIRTY_info (/tmp/test)

    (GHC version 9.5.20220606 for x86_64_unknown_linux)
    Please report this as a GHC bug:  https://www.haskell.org/ghc/reportabug
[3]    594217 abort (core dumped)  ./test

You might be saying "but you used unsafeCoerce here, no wonder it falls apart!" and you are right. But I'd like to see #15532 fixed one day. That would allow the definition of sek without any unsafeCoerce. So after #15532, the observed crash would have been an actual bug.

Proposal

So I propose to add trivial enter code for the primitive MutVar#, Array#, etc.

Costs

You might think that code size would increase quite a bit. But

  1. There often are many more function definitions than primitive data types
  2. All primitive types can share their one implementation of enter code! Just tag the pointer and return.

Additionally, the panicking enter code is a useful sanity check, according to @bgamari and @AndreasK. We'd lose that if we replace it with something non-panicky.

Benefits

In return we get

  1. One step closer to #15532. Also it would be safe to use e.g., Lazy (MutVar# a) in #21693.
  2. The ability to make Array#, MutVar#, etc. levity polymorphic. We could return them from actual thunks while saving the additional indirection that is Array, IORef, etc. Those can just become newtypes over the Lifted version of Array#, MutVar#.
  3. Less confusion in #20849 (comment 439624)
  4. Less special cases in general. Primitive unlifted types become just like agebraic types where we don't know the RHS (same situation can arise for ADT declarations in .hs-boot files).
Edited by Sebastian Graf
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information