Skip to content

Mutually dependent modules with orphan instances causes missing symbols with single-shot compilation

Alan Zimmerman encountered this nasty bug in his Trees That Grow branch:

Consider we have the following types,

module Types where

data Expr p = Var String
            | App (Expr p) (Expr p)
            | ADecl (Decl p)

data Decl p = Bind String (Expr p)

Now imagine that for some reason we want to define orphan instances for some class (Show, for instance) for these types in two separate modules. We would have:

-- Instances1.hs
module Instances1 where

import {-# SOURCE #-} Instances2 ()
import Types

deriving instance Show (Decl p)


-- Instances1.hs-boot
module Instances1 where
import Types
instance Show (Decl p)


-- Instances2.hs
module Instances2 where

import {-# SOURCE #-} Instances1 ()
import Types

deriving instance Show (Expr p)


-- Instances2.hs-boot
module Instances2 where
import Types
instance Show (Expr p)

Now, for instance, say we have some program that uses this whole mess,

-- Main.hs
module Main where
import Types

-- Use SOURCE import to ensure GHC doesn't grab dictionary from unfolding in
-- interface file
import {-# SOURCE #-} Instances2

main = putStrLn $ show $ (Var "hi" :: Expr Int)

With --make mode we can compile Main.hs with no trouble:

$ ghc --make Main.hs
[1 of 6] Compiling Types            ( Types.hs, Types.o )
[2 of 6] Compiling Instances2[boot] ( Instances2.hs-boot, Instances2.o-boot )
[3 of 6] Compiling Main             ( Main.hs, Main.o )
[4 of 6] Compiling Instances1[boot] ( Instances1.hs-boot, Instances1.o-boot )
[5 of 6] Compiling Instances2       ( Instances2.hs, Instances2.o )
[6 of 6] Compiling Instances1       ( Instances1.hs, Instances1.o )
Linking Main ...
$ ./Main
Var "hi"

However, if we instead use single-shot mode, we end up never producing object code for one of the boot DFuns,

$ ghc -c Types.hs
$ ghc -c Instances1.hs-boot
$ ghc -c Instances2.hs
$ ghc -c Instances2.hs-boot
$ ghc -c Instances1.hs
$ ghc -c Main.hs
$ ghc -o test Types.o Instances1.o Instances2.o Main.o
Main.o:s1lN_info: error: undefined reference to 'Instances2_zdfxShowExpr_closure'
Main.o(.data.rel.ro+0x8): error: undefined reference to 'Instances2_zdfxShowExpr_closure'
collect2: error: ld returned 1 exit status
`gcc' failed in phase `Linker'. (Exit code: 1)

In the case of --make mode the symbol in question is emitted in the object code for Instances2. However, when we use single-shot mode the hi-boot file for Instances2 doesn't exist when the hs file is compiled. It seems that this makes the DFun impedance matching logic in TcRnDriver.checkBootIface' not fire.

Trac metadata
Trac field Value
Version 8.2.1
Type Bug
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component Compiler (Type checker)
Test case
Differential revisions
BlockedBy
Related
Blocking
CC alanz, ezyang
Operating system
Architecture
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information