One peculiarity with the ordering is that linkers only search static archives for existing undefined references. If the reference to `Cffi` actually comes first, then nothing should be required from it yet and *it shouldn't be linked* (absent options like GNU ld's `--whole-archive`).
This said, ghc also passes a bunch of explicitly undefined symbols (`-Wl,-u…`) to ld; if those include symbols from `-lCffi` then it will in fact be linked in. On Tue, Aug 9, 2022 at 9:59 PM Ryan Scott <ryan.gl.sc...@gmail.com> wrote: > > I believe I've figured out what is happening here. This will take a bit of > explanation, so bear with me. > > First, it's important to note that this behavior depends on whether your GHC > was configured with the `--with-system-libffi` flag or not. If it was > configured with this flag, then GHC will always link against your system's > version of libffi, and the behavior seen in this thread would not occur. I am > using a bindist provided by ghcup which was _not_ configured with > `--with-system-libffi`, so GHC bundles its own copy of libffi. Interestingly, > there appear to be at least two copies of libffi that are bundled: one > dynamic library, simply named libffi.dylib, and one static library, which is > named libCffi.a [1]. Remember the name libCffi.a, since it will come up again > later. > > Next, let's take a look at how GHC performs the final linking step when > compiling an executable. GHC will call the C compiler to perform this final > linking step, which will have roughly the following shape on macOS: > > gcc ... -L<ghc-lib-dir>/rts <Haskell-libraries> -lCffi <extra-libraries> > -Wl,-dead_strip_dylibs -o Main > > I'm omitting quite a few details here, but the important bits are that (1) > -lCffi comes before any of the C libraries listed in an `extra-libraries` > stanza in a .cabal file, and (2) GHC on macOS will pass > -Wl,-dead_strip_dylibs, which causes the linker to remove any references to > shared libraries that go unused. > > In the particular case of libffi.cabal, we have `extra-libraries: ffi`, so > the C compiler is given `-lCffi -lffi` as arguments, in that order. Recall > that -libCffi.a (i.e., -lCffi) is a statically linked copy of libffi, whereas > libffi.dylib (i.e., -lffi) is a dynamically linked version. The linker will > resolve all of its references to libffi functionality from libCffi.a because > it appears first, and since the linker never needs to resolve anything from > libffi.dylib, the -Wl,-dead_strip_dylibs will remove the reference to > libffi.dylib entirely. The result is that the final executable will > statically link against libffi. This is why this phenomenon only occurs with > libffi and not with other C libraries, since only libffi is given special > treatment within GHC like this. > > Why, then, does this only happen on macOS and not on, say, Linux? On Linux, > the final linking step looks slightly different. Instead of passing > -Wl,-dead_strip_dylibs (which is a macOS-specific option), it will pass > -Wl,--no-as-needed. Note that -Wl,--no-as-needed has the entirely _opposite_ > effect of -Wl,-dead_strip_dylibs: even if a shared library never gets > referenced, the linker will still declare a dynamic dependency on it. That is > exactly what happens here, as the -lCffi argument will still take precedence > over the -lffi argument on Linux, but there will be a useless dynamic > dependency on a dynamic libffi library due to -Wl,--no-as-needed. > > At this point, I realized that one consequence of all this is that if you > want to statically link against against libffi, and you are using a GHC > configured without ` --with-system-libffi`, then it suffices to simply > compile your program without any `extra-libraries` at all. That is because > the final link step will pass `-lCffi` no matter what, so the resulting > executable will always statically link against libffi by way of libCffi.a. > Moreover, libCffi is provided on all platforms, not just macOS, so this trick > is reasonably portable. > > ----- > > Having explained all this, I am forced to wonder if this is intended behavior > of GHC, or if it is a series of coincidences. The fact that macOS and Linux > pass -Wl,-dead_strip_dylibs and -Wl,--no-as-needed, two flags with completely > opposite behavior, is certainly a little unusual. But these choices appear to > be deliberate, as they are explained in Notes in the GHC source code here [2] > (for macOS) and here [3] (for Linux). The other possibly unusual thing is > that GHC always passes -lCffi before any of the `extra-libraries`. That could > very well be just a coincidence—I'm not sure if there's a particular reason > for -lCffi to come first. > > The reason that I'm looking so deeply into this matter is that I'm trying to > see what it would take to reliably link against libffi statically in the > Haskell bindings [4]. I've discovered a trick above to make it work for most > versions of GHC: just rely on `-lCffi`. Does this trick seem like it would > continue to work for the foreseeable future, or is this approach liable to > break in a future release? > > Ryan > ----- > [1] See https://gitlab.haskell.org/ghc/ghc/-/issues/20395 for more on this > unusual amount of library duplication. > [2] > https://gitlab.haskell.org/ghc/ghc/-/blob/d71a20514546e0befe6e238d0658cbaad5a13996/compiler/GHC/Driver/Pipeline.hs#L293-340 > [3] > https://gitlab.haskell.org/ghc/ghc/-/blob/d71a20514546e0befe6e238d0658cbaad5a13996/compiler/GHC/SysTools/Info.hs#L57-69 > [4] https://github.com/remiturk/libffi/issues/6 > > On Tue, Aug 9, 2022 at 9:58 AM Ryan Scott <ryan.gl.sc...@gmail.com> wrote: >> >> No, I'm using macOS 10.15.7 (BuildVersion 19H2) on an x86_64 machine. >> >> Ryan -- brandon s allbery kf8nh allber...@gmail.com _______________________________________________ ghc-devs mailing list ghc-devs@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs