Skip to content

GHC always tries to link FFI symbols during build after #23415

Summary

With GHC 9.12.1, 9.10.2 and 9.8.3, cabal build leads to a linkage error when symbols referred in foreign import were not found.

I maintain module https://github.com/lyokha/nginx-log-plugin which expects linkage against a dynamic C library when it gets loaded in runtime. Starting from the mentioned versions of GHC, the build fails as soon as compiler tries to link the symbols in compile time. Note that I deliberately want GHC to be unaware of what the referred symbols are.

Here is how the symbols get declared:

type CLogType = Ptr () -> CUIntPtr -> CString -> CSize -> IO ()

-- Some tools such as hls, haddock, and ghci run interactive linking against C
-- functions plugin_ngx_http_haskell_log() and plugin_ngx_http_haskell_log_r()
-- when loading Log.hs. In Log.hs, TH declarations from Log/Gen.hs which make
-- calls to those C functions, get instantiated. Obviously, linking fails as
-- soon as we don't have a library to expose the functions because such a
-- library is built by Nginx and we don't want to use Nginx at this step.
--
-- In this workaround, the C functions get replaced by stubs when running by
-- hls or haddock. This prevents interactive linking in Log.hs. It's easy to
-- detect that the code is being run by hls or haddock: the tools define their
-- own C macro declarations __GHCIDE__ and __HADDOCK_VERSION__ respectively. To
-- prevent interactive linking in ghci, pass one of the two macro declarations
-- in an appropriate option, e.g.
--
-- cabal repl --ghc-options=-D__GHCIDE__ --repl-options=-fobject-code

#if defined(__GHCIDE__) || defined(__HADDOCK_VERSION__)
#define C_LOG_STUB(f) f :: CLogType; f _ _ _ _ = return ()
#endif

#if defined(__GHCIDE__) || defined(__HADDOCK_VERSION__)
C_LOG_STUB(c_log)
#else
foreign import ccall unsafe "plugin_ngx_http_haskell_log" c_log :: CLogType
#endif

#if defined(__GHCIDE__) || defined(__HADDOCK_VERSION__)
C_LOG_STUB(c_log_r)
#else
foreign import ccall unsafe "plugin_ngx_http_haskell_log_r" c_log_r :: CLogType
#endif

So, I expect that plugin_ngx_http_haskell_log and plugin_ngx_http_haskell_log_r should only be linked in runtime. But instead I get errors after interactive linking in compile time:

Resolving dependencies...
Build profile: -w ghc-9.12.2 -O1
In order, the following will be built (use -v for more details):
 - ngx-export-log-1.5.2 (lib) (first run)
Configuring library for ngx-export-log-1.5.2...
Preprocessing library for ngx-export-log-1.5.2...
Building library for ngx-export-log-1.5.2...
[1 of 4] Compiling NgxExport.Log.CLog ( NgxExport/Log/CLog.hs, dist-newstyle/build/x86_64-linux/ghc-9.12.2/ngx-export-log-1.5.2/build/NgxExport/Log/CLog.o, dist-newstyle/build/x86_64-linux/ghc-9.12.2/ngx-export-log-1.5.2/build/NgxExport/Log/CLog.dyn_o )
[2 of 4] Compiling NgxExport.Log.Base ( NgxExport/Log/Base.hs, dist-newstyle/build/x86_64-linux/ghc-9.12.2/ngx-export-log-1.5.2/build/NgxExport/Log/Base.o, dist-newstyle/build/x86_64-linux/ghc-9.12.2/ngx-export-log-1.5.2/build/NgxExport/Log/Base.dyn_o )
[3 of 4] Compiling NgxExport.Log.Gen ( NgxExport/Log/Gen.hs, dist-newstyle/build/x86_64-linux/ghc-9.12.2/ngx-export-log-1.5.2/build/NgxExport/Log/Gen.o, dist-newstyle/build/x86_64-linux/ghc-9.12.2/ngx-export-log-1.5.2/build/NgxExport/Log/Gen.dyn_o )
[4 of 4] Compiling NgxExport.Log    ( NgxExport/Log.hs, dist-newstyle/build/x86_64-linux/ghc-9.12.2/ngx-export-log-1.5.2/build/NgxExport/Log.o, dist-newstyle/build/x86_64-linux/ghc-9.12.2/ngx-export-log-1.5.2/build/NgxExport/Log.dyn_o )
<no location info>: error:
    
GHC.Linker.Loader.dynLoadObjs: Loading temp shared object failed
During interactive linking, GHCi couldn't find the following symbol:
  /tmp/ghc705193_tmp_0/libghc_tmp_8_0.so: undefined symbol: plugin_ngx_http_haskell_log
This may be due to you not asking GHCi to load extra object files,
archives or DLLs needed by your current session.  Restart GHCi, specifying
the missing library using the -L/path/to/object/dir and -lmissinglibname
flags, or simply by naming the relevant files on the GHCi command line.
Alternatively, this link failure might indicate a bug in GHCi.
If you suspect the latter, please report this as a GHC bug:
  https://www.haskell.org/ghc/reportabug


Error: [Cabal-7125]
Failed to build ngx-export-log-1.5.2.

There is no errors in GHC 9.10.1, 9.8.2 and older (see https://github.com/lyokha/nginx-log-plugin/actions/runs/15717215931). After looking into the release notes, I figured out that the issue could be regarded to #23415 (closed) (but I'm not 100% sure).

It seems that currently there is no workaround for this except cabal build --ghc-options='-L... -l...' but this approach is not an option in my case.

I also found that in GHC 9.14.1 there will be a new RTS option --optimistic-linking. Will it be possible to use this in my case (and how)? Or is this unrelated?

Steps to reproduce

See Summary

Expected behavior

A module with unbound FFI symbols gets built.

Environment

  • GHC version used: 9.12.2, 9.10.2, 9.8.3

Optional:

  • Operating System: Linux, Fedora42
  • System Architecture: x86_64
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information