Skip to content

CoreLint and StgCmm disagree on safety of unsafeCoercions

CoreLint has this Note on bad unsafe coercions:

Note [Bad unsafe coercion]
~~~~~~~~~~~~~~~~~~~~~~~~~~
For discussion see https://gitlab.haskell.org/ghc/ghc/wikis/bad-unsafe-coercions
Linter introduces additional rules that checks improper coercion between
different types, called bad coercions. Following coercions are forbidden:

  (a) coercions between boxed and unboxed values;
  (b) coercions between unlifted values of the different sizes, here
      active size is checked, i.e. size of the actual value but not
      the space allocated for value;
  (c) coercions between floating and integral boxed values, this check
      is not yet supported for unboxed tuples, as no semantics were
      specified for that;
  (d) coercions from / to vector type
  (e) If types are unboxed tuples then tuple (# A_1,..,A_n #) can be
      coerced to (# B_1,..,B_m #) if n=m and for each pair A_i, B_i rules
      (a-e) holds.

(b) is implemented as this check

checkWarnL (isUnBoxed rep1 == isUnBoxed rep2)
           (report "between unboxed and boxed value")
checkWarnL (TyCon.primRepSizeB dflags rep1 == TyCon.primRepSizeB dflags rep2)
           (report "between unboxed values of different size")

where

isUnBoxed :: PrimRep -> Bool
isUnBoxed = not . isGcPtrRep

This check accepts coercions from Int# to Int64# on a 64-bit system as those have the same size on 64-bit. However StgCmmExpr.cgCase has a more strict version of this check, when compiling a code like

case (foo :: Int64#) of (bndr :: Int#) { ... }

The check:

cgCase (StgApp v []) bndr alt_type@(PrimAlt _) alts
  | isUnliftedType (idType v)  -- Note [Dodgy unsafeCoerce 1]
  || reps_compatible
  = -- assignment suffices for unlifted types
    do { dflags <- getDynFlags
       ; unless reps_compatible $
           pprPanic "cgCase: reps do not match, perhaps a dodgy unsafeCoerce?"
                    (pp_bndr v $$ pp_bndr bndr)
       ; ...
       }
  where
    reps_compatible = ((==) `on` (primRepSlot . idPrimRep)) v bndr

The problem is idPrimReps of an Int# and an Int64# are different even on a 64-bit system: one gets IntRep and the other one gets Int64Rep. Then primRepSlot maps these to two different slots. (reminder: primRepSlot was introduced for unboxed sums -- two types with same slots can use the same memory location in an unboxed sum type)


What to do? Unfortunately idPrimRep is not documented so it's not clear what the intent is, but it's used during Cmm generation. Given that Int# and Int64# are literally the same thing on a 64-bit system, I think it makes sense to return the same PrimRep for these types. A simple fix would be to just update idPrimRep to take a DynFlags argument and return the same PrimRep.

I'm not sure what's the refactoring for this change though -- idPrimRep calls runtimeRepPrimRep, which calls the RuntimeRep function returned by tyConRuntimeRepInfo, but int64PrimTyCon and intPrimTyCon are PrimTyCons, so they don't have a promDcRepInfo ... I don't understand how this works yet. I suspect something in TysWiredIn will have to change.

(CC @simonpj)

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information