crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.43k stars 1.62k forks source link

Detecting `libstdc++.so` in the interpreter #14398

Open HertzDevil opened 7 months ago

HertzDevil commented 7 months ago

As mentioned in #14386, typically libstdc++.so doesn't exist on any standard locations on a Linux system, and instead only libstdc++.so.6 can be found. This means LibLLVM, which currently uses @[Link("stdc++")] on non-Windows targets, will fail to link in interpreted code. The actual library without a version suffix is part of a GCC installation:

$ find / -name libstdc++.so 2>/dev/null
/usr/lib/gcc/x86_64-linux-gnu/13/libstdc++.so
/usr/lib/gcc/x86_64-linux-gnu/12/libstdc++.so

Both of them are symlinks to ../../../x86_64-linux-gnu/libstdc++.so.6, i.e. /lib/x86_64-linux-gnu/libstdc++.so.6. If GCC is the compiler for non-interpreted code, obviously it has access to its own installation prefix; if Clang is used, apparently it has an entire file dedicated to detecting GCC toolchains, in order to pick up the above directories and pass them to the underlying linker (ld or ld.lld).

I could see a few options if we want this to work:

straight-shoota commented 7 months ago

I think your test for stdc++ in an empty file is flawed. The name of the linked executable is ./test, not a.out (which is probably an artifact from building something else). An empty file has no business linking libstdc++ for no reason.

$ crystal build --prelude=empty --stdin-filename=test <<< ""
$ ldd test
        linux-vdso.so.1 (0x00007fff38e2f000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f09201a9000)
        /home/linuxbrew/.linuxbrew/lib/ld.so => /lib64/ld-linux-x86-64.so.2 (0x00007f09203e5000)

Nevertheless, even without the explicit stdc++ lib mention, a Crystal program linking libllvm builds just fine:

$ bin/crystal build spec/std/llvm/llvm_spec.cr
$ ldd llvm_spec
        linux-vdso.so.1 (0x00007ffc2d19e000)
        libLLVM-17.so => /home/linuxbrew/.linuxbrew/lib/libLLVM-17.so (0x00007f2de2fd0000)
        libpcre2-8.so.0 => /home/linuxbrew/.linuxbrew/lib/libpcre2-8.so.0 (0x00007f2de2f2d000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2de2e39000)
        libgc.so.1 => /home/linuxbrew/.linuxbrew/lib/libgc.so.1 (0x00007f2de2cec000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f2de2ce5000)
        libevent-2.1.so.7 => /home/linuxbrew/.linuxbrew/lib/libevent-2.1.so.7 (0x00007f2de2c89000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f2de2c84000)
        libgcc_s.so.1 => /home/linuxbrew/.linuxbrew/lib/gcc/current/libgcc_s.so.1 (0x00007f2de2c5d000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2de2a35000)
        /home/linuxbrew/.linuxbrew/lib/ld.so => /lib64/ld-linux-x86-64.so.2 (0x00007f2dec1cf000)
        libffi.so.8 => /home/linuxbrew/.linuxbrew/lib/../lib/libffi.so.8 (0x00007f2de2a22000)
        libedit.so.0 => /home/linuxbrew/.linuxbrew/lib/../lib/libedit.so.0 (0x00007f2de29e0000)
        libz3.so.4.12 => /home/linuxbrew/.linuxbrew/lib/../lib/libz3.so.4.12 (0x00007f2de1993000)
        libz.so.1 => /home/linuxbrew/.linuxbrew/lib/../lib/libz.so.1 (0x00007f2de1978000)
        libzstd.so.1 => /home/linuxbrew/.linuxbrew/lib/../lib/libzstd.so.1 (0x00007f2de1865000)
        libncursesw.so.6 => /home/linuxbrew/.linuxbrew/lib/../lib/libncursesw.so.6 (0x00007f2de17e8000)
        libstdc++.so.6 => /home/linuxbrew/.linuxbrew/lib/../lib/libstdc++.so.6 (0x00007f2de1532000)

libstdc++ is a dependency of libllvm so I'd suspect it should be part of the lib configuration, but apparently it isn't:

$ /home/linuxbrew/.linuxbrew/bin/llvm-config --libs --system-libs --ldflags
-L/home/linuxbrew/.linuxbrew/Cellar/llvm/17.0.6_1/lib
-lLLVM-17

So it seems to be indeed automatically linked with gcc when needed, even without an explicit lib specification.

I guess that leaves the first option open.

Replicate what Clang does to detect the GNU toolchain. Probably not worth the effort.

This might be relevant in the context of #14332, though.

HertzDevil commented 2 days ago

If cc is indeed GCC or Clang, then cc -print-search-dirs gives you where to look for the compiler-specific libraries:

`#{ARGV.shift? || ENV["CC"]? || "cc"} -print-search-dirs`.each_line do |line|
  libraries = line.lchop?("libraries: =") || next
  libraries.split(Process::PATH_DELIMITER)
    .map { |dir| File.expand_path(dir) }
    .uniq!
    .each { |dir| puts dir }
end

On Debian this gives the following:

/usr/lib/gcc/x86_64-linux-gnu/14/
/usr/x86_64-linux-gnu/lib/x86_64-linux-gnu/14/
/usr/x86_64-linux-gnu/lib/x86_64-linux-gnu/
/usr/x86_64-linux-gnu/lib/
/usr/lib/x86_64-linux-gnu/14/
/usr/lib/x86_64-linux-gnu/
/usr/lib/
/lib/x86_64-linux-gnu/14/
/lib/x86_64-linux-gnu/
/lib/

with clang-15:

/usr/lib/llvm-15/lib/clang/15.0.7
/usr/lib/gcc/x86_64-linux-gnu/14
/usr/lib64
/lib/x86_64-linux-gnu
/lib64
/usr/lib/x86_64-linux-gnu
/lib
/usr/lib

and on MSYS2:

C:\msys64\ucrt64\lib\gcc\x86_64-w64-mingw32\14.2.0\
C:\msys64\ucrt64\lib\gcc\
C:\msys64\ucrt64\x86_64-w64-mingw32\lib\x86_64-w64-mingw32\14.2.0\
C:\msys64\ucrt64\x86_64-w64-mingw32\lib\
C:\msys64\ucrt64\lib\x86_64-w64-mingw32\14.2.0\
C:\msys64\ucrt64\lib\
D:\a\msys64\ucrt64\lib\x86_64-w64-mingw32\14.2.0\
D:\a\msys64\ucrt64\lib\