Skip to content

GHC makes unsound references in object code

To reproduce the bug run script run.sh from the attached archive.

It will:

  1. install FooPackage
  Resolving dependencies...
  Configuring FooPackage-0.1...
  Building FooPackage-0.1...
  Preprocessing library FooPackage-0.1...
  [1 of 1] Compiling FooPackage       ( src\FooPackage.hs, dist\build\FooPackage.o )
  In-place registering FooPackage-0.1...
  Installing library in
  C:\Users\Anton\AppData\Roaming\cabal\i386-windows-ghc-7.6.3\FooPackage-0.1
  Registering FooPackage-0.1...
  Installed FooPackage-0.1
  }}}

2. compile executable `Client1` which depends on `FooPackage`

  {{{
  [1 of 3] Compiling QuxClient        ( QuxClient.hs, obj\QuxClient.o )
  [2 of 3] Compiling BarClient        ( BarClient.hs, obj\BarClient.o )
  [3 of 3] Compiling Client1          ( Client1.hs, obj\Client1.o )
  Linking exes/Client1.exe ...
  }}}

3. compile executable `Client2` which doesn't depend on `FooPackage`

  At the third step GHC will fall with linker error:

  {{{
  [2 of 2] Compiling Client2          ( Client2.hs, obj\Client2.o )
  Linking exes/Client2.exe ...
  obj\BarClient.o:fake:(.text+0x83): undefined reference to `FooPackagezm0zi1_FooPackage_zdsinsertzuzdsgo5_info'
  obj\BarClient.o:fake:(.data+0x10): undefined reference to `FooPackagezm0zi1_FooPackage_zdsinsertzuzdsgo5_closure'
  collect2: ld returned 1 exit status
  }}}

Both `Client1` and `Client2` import `BarClient` module that doesn't depends on `FooPackage`. 
`Client1` imports `QuxClient` that imports `FooPackage`:

{{{
module QuxClient where

import FooPackage(foo)

import Data.Set

qux :: Set String -> Set String
qux = foo
module BarClient where

import Data.Set

bar :: Set String -> Set String
bar s = insert "bar" s

FooPackage uses function Data.Set.insert which is marked at Data.Set as INLINABLE:

module FooPackage where

import Data.Set

foo :: Set String -> Set String
foo s = insert "foo" s

GHC emphasizes in interface file, that FooPackage.o contains specialized version of Data.Set.insert:

> ghc --show-iface FooPackage.hi
...
"SPEC Data.Set.Base.insert [GHC.Base.String]" [ALWAYS] forall $dOrd :: GHC.Classes.Ord
                                                                           GHC.Base.String
  Data.Set.Base.insert @ [GHC.Types.Char] $dOrd = FooPackage.$sinsert
...

Later GHC see again the use of Data.Set.insert@String at BarClient and decides to apply this specialise rule, so that BarClient now has reference to FooPackage:

>  ghc --show-iface BarClient.hi
...
  bar :: Data.Set.Base.Set GHC.Base.String
         -> Data.Set.Base.Set GHC.Base.String
    {- Arity: 1, Strictness: S,
       Unfolding: (\ s :: Data.Set.Base.Set GHC.Base.String ->
                   FooPackage.$sinsert_$sgo5 BarClient.bar1 s) -}
...

Client2 doesn't depend on FooPackage, thus linker throws an error.

There are plenty of workarounds here, but all of them are ad hoc (1,2,3) or could affect performance (4):

  1. Change the order of BarClient and QuxClient imports in Client1 module.
  2. Compile Client1 and Client2 in opposite order.
  3. Add fake import at Client2 module:

import FooPackage()

  1. Build FooPackage with ghc-option -fomit-interface-pragmas. That will eraise from FooPackage.hi all specialisation rules as well as information about strictness and inlining.

Some information about the environment:

> ghc-pkg list 
...
  containers-0.5.0.0
  base-4.6.0.1
...
> ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.6.3
> cabal --version
cabal-install version 1.18.0.3
using version 1.18.1.3 of the Cabal library
Trac metadata
Trac field Value
Version 7.6.3
Type Bug
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component Compiler
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system
Architecture
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information