How to use lld linker with ghcup ghc on linux to build haskell projects?

I read that ld.lld linker is significantly faster than ld.bfd and want to try it with my own project.

I tried it on a demo project first, but got errors:

sylecn@agem10:~/fromsource/link-with-lld-example$ cabal build
Build profile: -w ghc-9.4.8 -O1
In order, the following will be built (use -v for more details):
 - link-with-lld-example-1 (lib:link-with-lld-example, exe:link-with-lld-example) (first run)
Preprocessing executable 'link-with-lld-example' for link-with-lld-example-1..
Building executable 'link-with-lld-example' for link-with-lld-example-1..
[2 of 2] Linking /home/sylecn/fromsource/link-with-lld-example/dist-newstyle/build/x86_64-linux/ghc-9.4.8/link-with-lld-example-1/build/link-with-lld-example/link-with-lld-example
ld.lld: error: relocation R_X86_64_32S cannot be used against local symbol; recompile with -fPIC
>>> defined in /home/sylecn/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/../lib/x86_64-linux-ghc-9.4.8/base-4.17.2.1/libHSbase-4.17.2.1.a(Base.o)
>>> referenced by Base.o:(base_ControlziExceptionziBase_zdfShowNonTerminationzuzdcshowsPrec_info) in archive /home/sylecn/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/../lib/x86_64-linux-ghc-9.4.8/base-4.17.2.1/libHSbase-4.17.2.1.a

ld.lld: error: relocation R_X86_64_32S cannot be used against local symbol; recompile with -fPIC
>>> defined in /home/sylecn/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/../lib/x86_64-linux-ghc-9.4.8/base-4.17.2.1/libHSbase-4.17.2.1.a(Base.o)
>>> referenced by Base.o:(base_ControlziExceptionziBase_zdfShowNonTermination1_info) in archive /home/sylecn/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/../lib/x86_64-linux-ghc-9.4.8/base-4.17.2.1/libHSbase-4.17.2.1.a

Here is the versions used during the test:

sylecn@agem10:~$ ghcup list -c installed
   Tool  Version  Tags                      Notes      
āœ“  ghc   8.10.7   base-4.14.3.0                        
āœ”āœ” ghc   9.4.8    recommended,base-4.17.2.1 hls-powered
āœ”āœ” cabal 3.10.3.0 latest,recommended                   
āœ”āœ” hls   2.7.0.0  recommended                          
āœ”āœ” stack 2.15.5   recommended                          
āœ”āœ” ghcup 0.1.22.0 latest,recommended

It seems base package is not compatible with lld linker? What is the correct way to link with lld linker? Is the ghc installed by ghcup compatible with lld?

2 Likes

Have you tried installing your bindist with $LD and $CC set to your preferences? User Guide - GHCup

I have tried to uninstall and reinstall ghc using ā€œghcup tuiā€ command, after installing lld, with

def-ghc-conf-options:
  - "--enable-ld-override"

in ~/.ghcup/config.yaml

I believe this has the same effect as setting $LD. But I don’t know how to check whether it made a difference. Is there a command I can run or a log file I can check?
The build error persist after reinstall ghc this way.

Does setting $CC make a difference? should I set $LD and $CC and reinstall ghc?

def-ghc-conf-options

I think that feature isn’t released yet. The docs are unfortunately a little bit of ā€œbleeding edgeā€.

Try: LD=ld.lld ghcup install ghc 9.4.8

Check out ~/.ghcup/ghc/9.8.2/lib/ghc-9.4.8/lib/settings. The key "C compiler command" will tell you if it’s gcc or clang.

In my case,
LD=ld.lld ghcup install ghc --force 9.4.8

That worked. Thanks.

Unfortunately, there’s another GHC issue that prevents this from working properly: #21830: Wrong defaulting linker when LD env.var. is set Ā· Issues Ā· Glasgow Haskell Compiler / GHC Ā· GitLab

So you’ll have to edit the GHC settings file manually to add -fuse-ld=lld to ā€œC compiler link flagsā€. Otherwise it will likely not actually use lld.

OK. I was wrong. When I build the demo project, I forget to revert a local change I made that drop the lld settings. When I revert the change, build still fail.

On a second look on the ghc installer tarball (used by ghcup), it seems file ghc-9.4.8-x86_64-unknown-linux/lib/x86_64-linux-ghc-9.4.8/libHSbase-4.17.2.1-ghc9.4.8.so, ghc-9.4.8-x86_64-unknown-linux/lib/x86_64-linux-ghc-9.4.8/base-4.17.2.1/libHSbase-4.17.2.1.a
is included in the tarball. Would setting $LD and -fuse-ld=lld in settings file supposed to work at all? Since the original error is about that file not compiled with -fPIC.

Append something like this to your ghcup install command:

-- \
  CC=/usr/lib/llvm-18/bin/clang \
  CONF_CC_OPTS_STAGE2="-Wno-unused-command-line-argument" \
  CONF_CXX_OPTS_STAGE2="-Wno-unused-command-line-argument" \
  CONF_GCC_LINKER_OPTS_STAGE2="--ld-path=/usr/lib/llvm-18/bin/ld.lld" \
  CXX=/usr/lib/llvm-18/bin/clang++ \
  LD="/usr/lib/llvm-18/bin/ld.lld"

kruzifix!

This is why I’m starting to think ghcup should just provide an interface to change the GHC settings file.

But given that there’s other things in the works (ghc-toolchain), I’m not sure how future proof that would be.

1 Like

tbh I’m not a big fan of editing settings directly. Sure, you need to jump through more hops to provide the right configure options, but configure does it job and checks for consistency; you are more likely to end up with a subtly broken bindist if you edit settings directly.

Well, it actually doesn’t:

And even if those things get fixed, they won’t get backported (except by me).

A ghcup interface could check for consistency on its own and would work correctly across GHC versions, regardless of bindist bugs.

Side note: Since trying lld on linux is unsuccessful, I tried to build my own project on freebsd 14, which uses clang and lld by default. The build speed was not faster than on Linux despite using lld as linker. The VM used to run the build has similar hardware spec and runs on the same host.

In fact build with a clean cache took 22m29s in Linux VM, took 22m22s in FreeBSD VM.
The project that I built can be download from

if someone want to compare build speed between ld and lld linker.

build requires stack.
just build with
stack build

1 Like

On the subject of speed, Stack’s CI was running its integration tests in an Alpine Linux Docker container on Ubuntu using ld.bfd as the linker (having built a statically-linked Stack for Linux).

Changing that to running them on Ubuntu and also specifying lld as the linker has reduced the time they take by a factor of 9.2 times. Some of that may be static v dynamic linking.

The linker detection and selection methods are tested on ghcup-ci.

This is the job testing the configuration of lld linker. direct-x86_64-linux-debian:10-lld (#2176342) Ā· Jobs Ā· Glasgow Haskell Compiler / ghcup-ci Ā· GitLab

The specification for that job is here: .gitlab/jobs.yaml Ā· master Ā· Glasgow Haskell Compiler / ghcup-ci Ā· GitLab

Which indicates the options you can use to force GHC to always use lld, as specified at configure time are:

      "CONFIGURE_ARGS": "--disable-ld-override",
      "CONF_GCC_LINKER_OPTS_STAGE2": "-fuse-ld=lld",
      "LD": "/usr/bin/ld.lld"

You can also remove the behaviour that GHC bakes in a specific linker at configure time by passing --disable-ld-override during configuration. If you pass that option then -fuse-ld will not be passed to the C compiler, and it will be free to determine under its own rules which linker to use.

2 Likes

By ā€˜at configure time’ are you referring to building GHC from source and configuring that build? (As I could not see a reference to --disable-ld-override in GHC’s User Guide or at runghc Setup.hs configure --help.)

The bindist configure takes that option (and those other env vars). In ghcup, you can do:

LD=/usr/bin/ld.lld ghcup install ghc latest -- --disable-ld-override

Or

By ā€˜bindist configure’ do you mean the configuration of building a GHC binary distribution from source? The only references to environment variables being used to configure GHC itself (once built) that I can see in its User’s Guide are at:

No, I mean the binary distribution tarball containing the compiled GHC. It also has a configure script. I’m not sure if its interface is documented at all.

2 Likes

I had previously looked at the contents of the ā€˜official’ ghc-9.12.2-x86_64-unknown-mingw32.tar.xz and had not spotted anything going to configuration of GHC.

However, I see now that there is an (undocumented,§ I think) bin\ghc-toolchain-bin.exe. I tried:

> ghc-toolchain-bin --help

unrecognized option `--help'

ghc-toolchain
  -t TRIPLE  --triple=TRIPLE                Target triple
  -T PREFIX  --target-prefix=PREFIX         A target prefix which will be added to all tool names when searching for toolchain components
             --llvm-triple=LLVM-TRIPLE      LLVM Target triple
  -v[N]      --verbose[=N]                  set output verbosity
             --keep-temp                    do not remove temporary files
  -o OUTPUT  --output=OUTPUT                The output path for the generated target toolchain configuration
             --enable-unregisterised        Enable unregisterised backend
             --disable-unregisterised       Disable unregisterised backend
             --enable-tables-next-to-code   Enable info-tables-next-to-code optimisation
             --disable-tables-next-to-code  Disable info-tables-next-to-code optimisation
             --enable-libffi-adjustors      Enable the use of libffi for adjustors, even on platforms which have support for more efficient, native adjustor implementations.
             --disable-libffi-adjustors     Disable the use of libffi for adjustors, even on platforms which have support for more efficient, native adjustor implementations.
             --enable-ld-override           Enable override gcc's default linker
             --disable-ld-override          Disable override gcc's default linker
             --enable-locally-executable    Enable the use of a target prefix which will be added to all tool names when searching for toolchain components
             --disable-locally-executable   Disable the use of a target prefix which will be added to all tool names when searching for toolchain components
             --cc=CC                        Path of C compiler
             --cc-opt=OPTS                  Flags to pass to cc
             --cpp=CPP                      Path of C preprocessor
             --cpp-opt=OPTS                 Flags to pass to cpp
             --hs-cpp=HS-CPP                Path of Haskell C preprocessor
             --hs-cpp-opt=OPTS              Flags to pass to hs-cpp
             --js-cpp=JS-CPP                Path of JavaScript C preprocessor
             --js-cpp-opt=OPTS              Flags to pass to js-cpp
             --cmm-cpp=CMM-CPP              Path of C-- C preprocessor
             --cmm-cpp-opt=OPTS             Flags to pass to cmm-cpp
             --cxx=CXX                      Path of C++ compiler
             --cxx-opt=OPTS                 Flags to pass to cxx
             --cc-link=CC-LINK              Path of C compiler for linking
             --cc-link-opt=OPTS             Flags to pass to cc-link
             --ar=AR                        Path of ar archiver
             --ar-opt=OPTS                  Flags to pass to ar
             --ranlib=RANLIB                Path of ranlib utility
             --ranlib-opt=OPTS              Flags to pass to ranlib
             --nm=NM                        Path of nm archiver
             --nm-opt=OPTS                  Flags to pass to nm
             --readelf=READELF              Path of readelf utility
             --readelf-opt=OPTS             Flags to pass to readelf
             --merge-objs=MERGE-OBJS        Path of linker for merging objects
             --merge-objs-opt=OPTS          Flags to pass to merge-objs
             --windres=WINDRES              Path of windres utility
             --windres-opt=OPTS             Flags to pass to windres
             --ld=LD                        Path of linker
             --ld-opt=OPTS                  Flags to pass to ld

§Well-Typed LLP has, however, blogged in October 2023 about ghc-toolchain: