Invoking the linker from the compiler directly

Hello guys,

I have been going through the Kaleidoscope tutorial and I do really like it so far. I am curious - how does the clang binary invoke the linker with all the appropriate inputs? As far as I can see the tutorial just produces an object file but I would like to figure out how to do it properly.

Is it as simple as invoking the lld through exec?


It depends on a number of things, such as your operating system, some of the options you pass to the compiler driver, etc. If you want to see exactly what clang is going to do, use -### (only shows the commands) or -v (shows the commands while executing them):

$ uname -rs

$ clang -### helloworld.c -o helloworld
FreeBSD clang version 15.0.7 ( llvmorg-15.0.7-0-g8dfdcc7b7bf6)
Target: x86_64-unknown-freebsd14.0
Thread model: posix
InstalledDir: /usr/bin
 "/usr/bin/clang" "-cc1" "-triple" "x86_64-unknown-freebsd14.0" "-emit-obj" "-mrelax-all" "--mrelax-relocations" "-disable-free" "-clear-ast-before-backend" "-main-file-name" "helloworld.c" "-mrelocation-model" "static" "-mframe-pointer=all" "-ffp-contract=on" "-fno-rounding-math" "-mconstructor-aliases" "-funwind-tables=2" "-target-cpu" "x86-64" "-tune-cpu" "generic" "-mllvm" "-treat-scalable-fixed-error-as-warning" "-debugger-tuning=gdb" "-fcoverage-compilation-dir=/home/dim" "-resource-dir" "/usr/lib/clang/15.0.7" "-fdebug-compilation-dir=/home/dim" "-ferror-limit" "19" "-fgnuc-version=4.2.1" "-fcolor-diagnostics" "-faddrsig" "-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o" "/tmp/helloworld-371409.o" "-x" "c" "helloworld.c"
 "/usr/bin/ld" "--eh-frame-hdr" "-dynamic-linker" "/libexec/" "--hash-style=both" "--enable-new-dtags" "-o" "helloworld" "/usr/lib/crt1.o" "/usr/lib/crti.o" "/usr/lib/crtbegin.o" "-L/usr/lib" "/tmp/helloworld-371409.o" "-lgcc" "--as-needed" "-lgcc_s" "--no-as-needed" "-lc" "-lgcc" "--as-needed" "-lgcc_s" "--no-as-needed" "/usr/lib/crtend.o" "/usr/lib/crtn.o"

On Linux:

$ clang-15 -### helloworld.c -o helloworld
Ubuntu clang version 15.0.7
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
 "/usr/lib/llvm-15/bin/clang" "-cc1" "-triple" "x86_64-pc-linux-gnu" "-emit-obj" "-mrelax-all" "--mrelax-relocations" "-disable-free" "-clear-ast-before-backend" "-disable-llvm-verifier" "-discard-value-names" "-main-file-name" "helloworld.c" "-mrelocation-model" "pic" "-pic-level" "2" "-pic-is-pie" "-mframe-pointer=all" "-fmath-errno" "-ffp-contract=on" "-fno-rounding-math" "-mconstructor-aliases" "-funwind-tables=2" "-target-cpu" "x86-64" "-tune-cpu" "generic" "-mllvm" "-treat-scalable-fixed-error-as-warning" "-debugger-tuning=gdb" "-fcoverage-compilation-dir=/home/dim/src/misc" "-resource-dir" "/usr/lib/llvm-15/lib/clang/15.0.7" "-internal-isystem" "/usr/lib/llvm-15/lib/clang/15.0.7/include" "-internal-isystem" "/usr/local/include" "-internal-isystem" "/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include" "-internal-externc-isystem" "/usr/include/x86_64-linux-gnu" "-internal-externc-isystem" "/include" "-internal-externc-isystem" "/usr/include" "-fdebug-compilation-dir=/home/dim/src/misc" "-ferror-limit" "19" "-fgnuc-version=4.2.1" "-fcolor-diagnostics" "-faddrsig" "-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o" "/tmp/helloworld-bcf7a1.o" "-x" "c" "helloworld.c"
 "/usr/bin/ld" "-pie" "-z" "relro" "--hash-style=gnu" "--build-id" "--eh-frame-hdr" "-m" "elf_x86_64" "-dynamic-linker" "/lib64/" "-o" "helloworld" "/lib/x86_64-linux-gnu/Scrt1.o" "/lib/x86_64-linux-gnu/crti.o" "/usr/bin/../lib/gcc/x86_64-linux-gnu/12/crtbeginS.o" "-L/usr/bin/../lib/gcc/x86_64-linux-gnu/12" "-L/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../lib64" "-L/lib/x86_64-linux-gnu" "-L/lib/../lib64" "-L/usr/lib/x86_64-linux-gnu" "-L/usr/lib/../lib64" "-L/lib" "-L/usr/lib" "/tmp/helloworld-bcf7a1.o" "-lgcc" "--as-needed" "-lgcc_s" "--no-as-needed" "-lc" "-lgcc" "--as-needed" "-lgcc_s" "--no-as-needed" "/usr/bin/../lib/gcc/x86_64-linux-gnu/12/crtendS.o" "/lib/x86_64-linux-gnu/crtn.o"

Oooh, that sounds great!

Is there any way to figure out these arguments from the C++/LLVM side? They are very platform-specific, so if I want to invoke ld I will need to do quite a lot of figuring out.

I found llvm/Linker/Linker.h which sounds suspiciously close to what I may need.

True LLVM Linker.h is unlikely to handle the platform specific things like “which runtime to link in” or “what is the system path to find this library”, that is all encoded in the clang driver (which is written in c++ but quite coupled to clang use case as far as I can remember).

So it all depends on your exact use case and what you’re trying to achieve, in detail.

I am just trying to simply achieve linking object files into an executable without the need for a manual linking step…

So to be crystal clear: you don’t need any runtime? You don’t need any intrinsics from compiler-rt? You won’t have calls to libc? Or libc++?

llvm/include/llvm/Linker/Linker.h is only for linking LLVM bytecode files together I believe, there is the lld project that is a full-fledged linker though. Invoking it to “just” link object files is likely not complicated.

For the starts I will have calls to libc for sure. That’s why I will need to figure out what is necessary from the long list of arguments.

I am on Apple Arch, so the arguments list is different.

 "/usr/bin/ld" "-demangle" "-lto_library" "/opt/homebrew/Cellar/llvm/16.0.1/lib/libLTO.dylib" "-no_deduplicate" "-dynamic" "-arch" "arm64" "-platform_version" "macos" "13.0.0" "13.0.0" "-syslibroot" "/Library/Developer/CommandLineTools/SDKs/MacOSX13.sdk" "-o" "hello" "/var/folders/j7/41tjt1_s557bltzxntjp7kd40000gn/T/main-af6f7b.o" "-lSystem" "/opt/homebrew/Cellar/llvm/16.0.1/lib/clang/16/lib/darwin/libclang_rt.osx.a"

This is why I asked you in detail what you needed, and it turns out you need more than “simply achieve linking object files into an executable” : so you’re back to the clang driver.

there is the lld project that is a full-fledged linker though. Invoking it to “just” link object files is likely not complicated.

I will look into how to invoke lld correctly. Or do you think figuring out what arguments go into ld is too much hassle?

lld is meant to be a drop-in replacement for the existing system linker. The common flags that are used in linking should be recognized by the LLVM linker.

Curious, what’s the purpose of needing to figure out the arguments that go into the linker? Is using the clang driver not enough to figure out those inputs? I guess more to the point, what’s the goal you are trying to achieve?

I never used the aforementioned clang driver. Not sure I fully follow what that is.

You did. It is a library in LLVM:

It is used by clang and flang.

> clang foo.c -o foo.exe

It will do two steps: (a) compile foo.c to foo.o and (b) invoke the linker to generate the foo.exe. The clang driver will orchestrate the steps. The clang executable uses the driver library to find all the paths, flags, and executables on the different platforms. The command will work on Fedora Linux and OpenBSD. Therefore, flang, the fortran compiler, also uses the driver library.

ok, this seems to be more difficult than I imagined. So I will leave it at that.