Closed mppf closed 1 year ago
I'm not sure what to make of this problem, but I'm pretty sure your problem isn't due to mixed libc++
linkage, for (at least) four reasons:
llvm@11
as well: libLLVM
links with /usr/lib/libc++.1.dylib
but linking with libLLVM
will typically lead to linkage with a Homebrew-provided libc++
as well.libLLVM
and a Homebrew-provided libc++
with no issues./usr/lib/libc++.1.dylib
. This is because every binary must link with /usr/lib/libSystem.B.dylib
, and libSystem
actually has a library dependency on /usr/lib/libc++.1.dylib
. This means, in particular, that any Homebrew libc++
itself depends on /usr/lib/libc++.1.dylib
.My guess is that you've run into a different LLVM bug.
You have this linking behaviour with llvm@11 as well: libLLVM links with /usr/lib/libc++.1.dylib but linking with libLLVM will typically lead to linkage with a Homebrew-provided libc++ as well.
Yes, my hypothesis here is that the Mac OS X libc++ happens to be compatible with the one from llvm@11 for the versions used here.
I'm not sure what to make of this problem, but I'm pretty sure your problem isn't due to mixed libc++ linkage, for (at least) four reasons:
Did you try the reproducer? I think the otool -L
output is a pretty strong sign that the libc++ versions are different. The fact that the problem goes away with the -L
path adjustment (and also makes the libc++ versions the same) seems pretty conclusive to me that, at the very least, mixing libc++ versions is part of the problem. Of course there might be another solution.
Many other formulae link with both libLLVM and a Homebrew-provided libc++ with no issues.
Yes and some things appeared to work in our project. I boiled down to a reproducer above a pattern that was not working. Edit -- it might also matter if static or dynamic linking with clang/llvm libraries is used.
My guess is that you've run into a different LLVM bug.
Note that the clang from llvm@13 is not involved in compiling my example at all.
Yes, my hypothesis here is that the Mac OS X libc++ happens to be compatible with the one from llvm@11 for the versions used here.
If this hypothesis were true, then I would expect to see
libc++
versioning, but given that they update the rest of the toolchain on newer macOS versions, it would stand to reason that they update libc++
too.I think the
otool -L
output is a pretty strong sign that the libc++ versions are different.
Yes, I agree with you that the libc++
versions differ. However,
The fact that the problem goes away with the
-L
path adjustment (and also makes the libc++ versions the same) seems pretty conclusive to me that, at the very least, mixing libc++ versions is part of the problem.
I'm still not convinced.
Note that the clang from llvm@13 is not involved in compiling my example at all.
Sure, but I don't think this is relevant here. At the very least, this isn't relevant to any claims I've previously made.
I'm still not convinced.
Well, feel free to investigate further. I've already spent about a week on this and the workaround I show here is the best I could come up with.
Note that the clang from llvm@13 is not involved in compiling my example at all.
Does the behaviour change if the clang from llvm@13 is used?
Note that the clang from llvm@13 is not involved in compiling my example at all.
Does the behaviour change if the clang from llvm@13 is used?
Not for me, anyway
Do you mind reporting this upstream (i.e. LLVM)? I think this is an upstream bug.
Linking libLLVM
with /usr/lib/libc++.1.dylib
is very difficult to avoid. LLVM's build system requires that you build libLLVM
(or, at least, its constituent static libraries) before you build libc++
. Thus, any standard build of LLVM will have libLLVM
linked against /usr/lib/libc++.1.dylib
.
If your hypothesis is correct, then it is impossible to link against libLLVM
and non-Apple libc++
simultaneously in a standard shared library build, and fixing it would require building LLVM at least twice: once to build a libc++
to link libLLVM
against, and another to build the libLLVM
you eventually install. (You would still load /usr/lib/libc++.1.dylib
indirectly anyway, though, so this might not be a complete fix.)
@carlocab - the reason that I have posted it here and not upstream is that I was unable to reproduce the issue when not doing a Homebrew build. (I.e. if I build and install LLVM somewhere myself, manually, outside of homebrew, everything works OK). AFAIK that is due to the -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk
arguments that the Homebrew build is using. (AFAIK, this argument comes both from something in the cmake
from the Homebrew formula and from superenv
).
Additionally I wanted to ask if it was clear to you why building with headers from one version of a library and then linking with a different version of the same library is a problem, in general. I could explain that more / find a reference to link here, if not. We could certainly ask upstream if they expect that to work for libc++ specifically - but I'm not so sure they can even answer the question of whether it is expected to work between Apple's libc++ and the open source one. I think we would have to involve somebody from Apple to answer that. Apple's libc++ could have any number of changes that break compatibility with the open source version.
Linking
libLLVM
with/usr/lib/libc++.1.dylib
is very difficult to avoid. LLVM's build system requires that you buildlibLLVM
(or, at least, its constituent static libraries) before you buildlibc++
. Thus, any standard build of LLVM will havelibLLVM
linked against/usr/lib/libc++.1.dylib
.If your hypothesis is correct ... fixing it would require building LLVM at least twice: once to build a
libc++
to linklibLLVM
against, and another to build thelibLLVM
you eventually install. (You would still load/usr/lib/libc++.1.dylib
indirectly anyway, though, so this might not be a complete fix.)
Right, that is one way to fix it. Including that, I can think of several ways that Homebrew could fix it:
clang
that links with the built libc++
. This solution makes the least sense to me of these options. However, I do not see why this would be a show-stopper. To me it looks like the formula already builds clang multiple times. https://github.com/Homebrew/homebrew-core/blob/14af399c3d901c51bdfc932ff594f116318c9033/Formula/llvm.rb#L178-L179Relevant to the last option: Why does the LLVM formula install libc++ at all? Does it need to continue to do that?
~In the llvm.rb formula I can see that -DLLVM_ENABLE_LIBCXX
is only set to ON
for Mac OS X builds but not for linux builds. Why is that? It seems to be an odd choice.~ (Edit: I was confused about this flag; it just adds stdlib=libc++
)
There is also a comment about the system libc++ being to old to build LLVM but that no longer seems to be the case, at least not with recent Mac OS X versions. (Edit: I think this is unrelated to building libc++ here, though) https://github.com/Homebrew/homebrew-core/blob/14af399c3d901c51bdfc932ff594f116318c9033/Formula/llvm.rb#L80-L81
It looks to me like building LLVM's libc++ started in 856509e662fb1c75cd2d00ef7a8a56ee8f6d1b16 as a way to resolve missing system libraries (but we have better ways to handle that now - with things like isysroot) (I might be wrong about the history here).
Also issue https://github.com/Homebrew/legacy-homebrew/issues/47149 suggested it will cause problems to install libc++
with LLVM - and that issue is certainly related to this one. That issue suggested not installing libc++
in the OP but then went off in a different direction (to do with libc++abi vs libc++ which is a distinction I don't yet understand). And the first comment included this:
Since libc++ and libc++abi aren't installed by default, there's no immediate harm for most Homebrew users.
But now it is installed by default? When/why did that happen? Was it intentional?
Do you already know the answer? Why does homebrew install libc++ with LLVM by default?
I am almost certainly confused -DLLVM_ENABLE_LIBCXX
vs -DLLVM_ENABLE_RUNTIMES
including libcxx
. Also https://github.com/Homebrew/linuxbrew-core/issues/13332 seems related.
@carlocab - the reason that I have posted it here and not upstream is that I was unable to reproduce the issue when not doing a Homebrew build. (I.e. if I build and install LLVM somewhere myself, manually, outside of homebrew, everything works OK). AFAIK that is due to the
-isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk
arguments that the Homebrew build is using. (AFAIK, this argument comes both from something in thecmake
from the Homebrew formula and fromsuperenv
).
Which version of LLVM did you build? How did you configure it?
Additionally I wanted to ask if it was clear to you why building with headers from one version of a library and then linking with a different version of the same library is a problem, in general. I could explain that more / find a reference to link here, if not.
I'm aware that, in general, this is a recipe for ABI breakage. It just seems unlikely that this is what's happening here.
We know that this doesn't occur with LLVM 11 but does with LLVM 13, and you've suggested that Apple libc++
is ABI compatible with LLVM 11 libc++
. This means that LLVM 13 has broken the libc++
ABI, and hasn't bumped the library version accordingly. This strikes me as extremely unlikely, even setting aside the fact that the lead libc++
developer works at Apple. Still, supposing this were the case: this would then really need to be reported upstream.
We also know that this problem does not go away with Homebrew LLVM 13 clang++
. This is important because using Homebrew clang++
will make use of Homebrew libc++
headers when compiling your reproduction. (You can check by passing -H
to your invocation of clang++
.) Thus, the problem can be reproduced when using headers that match the linked library exactly.
We can also try the reverse exercise: use Homebrew LLVM 13 clang++
(and therefore LLVM 13 libc++
headers) but insist on linkage against Apple libc++
. This configuration leads to the correct result:
❯ ./a.out
EC.message is No such file or directory
EC == no_such_file_or_directory is 1
If there was indeed ABI breakage in libc++
that is exposed by your example, then it's relatively likely that we should get the wrong result here. But that's not what we're seeing.
Right, that is one way to fix it. Including that, I can think of several ways that Homebrew could fix it:
- as you said, it could build LLVM multiple times in order to create a
clang
that links with the builtlibc++
. This solution makes the least sense to me of these options. However, I do not see why this would be a show-stopper. To me it looks like the formula already builds clang multiple times. https://github.com/Homebrew/homebrew-core/blob/14af399c3d901c51bdfc932ff594f116318c9033/Formula/llvm.rb#L178-L179- package libc++ separately and install it to a separate directory. (Ubuntu does not do this; while libc++-13-dev and llvm-13-dev are separate packages, they both install libraries to /usr/lib/llvm-13/lib. But the issue is not a big deal on Ubuntu since by default the compilers will use the gnu libstdc++). This would address the problem because people would have to opt in to linking with the non-system libc++, somehow.
- just don't provide a package that installs a different libc++ from the system one. AFAIK this is the approach FreeBSD uses - and that is relevant because FreeBSD is another system that uses this libc++ as the system C++ library.
At this stage, all of these solutions are show-stoppers because they're not the right solutions. They are predicated on a diagnosis that doesn't seem to be correct.
Relevant to the last option: Why does the LLVM formula install libc++ at all?
A few reasons:
Those are just the one off the top of my head. There's likely more.
Does it need to continue to do that?
Yes, unless we have compelling reasons to do otherwise. I don't think we have any so far.
Which version of LLVM did you build? How did you configure it?
I built LLVM 12 with the Homebrew patches applied. I tried to make it as similar as possible to the Homebrew build but was having problems with the lldb build so disabled that. Here is the cmake command I used to configure it:
We know that this doesn't occur with LLVM 11 but does with LLVM 13, and you've suggested that Apple
libc++
is ABI compatible with LLVM 11libc++
.
I don't know that any version of Apple's libc++
is ABI compatible with any version of LLVM's libc++
. It could be that there were minor problems that we never ran in to.
This means that LLVM 13 has broken the
libc++
ABI, and hasn't bumped the library version accordingly. This strikes me as extremely unlikely, even setting aside the fact that the leadlibc++
developer works at Apple. Still, supposing this were the case: this would then really need to be reported upstream.
Do you know how to query the library version? The one reported by otool -L
I showed above is always 1
for the Homebrew-built libc++ but the Mac OS X libc++
has something that seems like a real version number (1200.3.0):
/usr/local/opt/llvm/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1200.3.0)
Is this the same version number you are talking about?
We also know that this problem does not go away with Homebrew LLVM 13
clang++
. This is important because using Homebrewclang++
will make use of Homebrewlibc++
headers when compiling your reproduction. (You can check by passing-H
to your invocation ofclang++
.) Thus, the problem can be reproduced when using headers that match the linked library exactly.
That is interesting. However I don't think it necessarily rules out header vs library version differences. In particular, the reproducer uses some of clang in a library (and links statically with it). That library is compiled as part of the Homebrew package and so it does not use the Homebrew libc++ today. So, in other words, to do that experiment properly, you would need a version of the Homebrew LLVM 13 clang libraries was compiled with the Homebrew LLVM 13 libc++ at include-time. I bet that will make the reproducer no longer show a problem. Is that an experiment you are able to try?
Duplicate of #
@Efecan2 :
Duplicate of #
Of what?
I don't know that any version of Apple's
libc++
is ABI compatible with any version of LLVM'slibc++
.
This seems to backpedal from your earlier claim that
Yes, my hypothesis here is that the Mac OS X libc++ happens to be compatible with the one from llvm@11 for the versions used here.
It could be that there were minor problems that we never ran in to.
It could, but this is still predicated on one of Apple or upstream LLVM breaking the ABI (both unlikely). In any case, any minor problems, if they exist, appear to be orthogonal to the issue here. They haven't been exposed previously, and are not exposed by your reproducer in LLVM 11.
Do you know how to query the library version? The one reported by
otool -L
I showed above is always1
for the Homebrew-built libc++ but the Mac OS Xlibc++
has something that seems like a real version number (1200.3.0):/usr/local/opt/llvm/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1.0.0) /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1200.3.0)
Is this the same version number you are talking about?
What is important is the compatibility version, which is the same.
That is interesting. However I don't think it necessarily rules out header vs library version differences. In particular, the reproducer uses some of clang in a library (and links statically with it). That library is compiled as part of the Homebrew package and so it does not use the Homebrew libc++ today. So, in other words, to do that experiment properly, you would need a version of the Homebrew LLVM 13 clang libraries was compiled with the Homebrew LLVM 13 libc++ at include-time. I bet that will make the reproducer no longer show a problem. Is that an experiment you are able to try?
You would bet wrong.
❯ ./a.out
EC.message is No such file or directory
EC == no_such_file_or_directory is 0
This is built from LLVM 13.0.1 (llvm/llvm-project@75e33f71c2dae584b13a7d1186ae0a038ba98838). I invoked CMake using
``` cmake -S llvm -B testbuild \ -DCMAKE_INSTALL_PREFIX=/tmp/llvm \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_OSX_SYSROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk \ -DLLVM_ENABLE_PROJECTS=clang \ -DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi" \ -DLLVM_BUILD_EXTERNAL_COMPILER_RT=ON \ -DLLVM_LINK_LLVM_DYLIB=ON \ -DLLVM_ENABLE_EH=ON \ -DLLVM_ENABLE_FFI=ON \ -DLLVM_ENABLE_RTTI=ON \ -DLLVM_INCLUDE_DOCS=OFF \ -DLLVM_INCLUDE_TESTS=OFF \ -DLLVM_ENABLE_Z3_SOLVER=OFF \ -DLLVM_OPTIMIZED_TABLEGEN=ON \ -DLLVM_TARGETS_TO_BUILD=Native \ -DCLANG_PYTHON_BINDINGS_VERSIONS="3.10;3.9;3.8" \ -DFFI_INCLUDE_DIR=/Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk/usr/include/ffi \ -DFFI_LIBRARY_DIR=/Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk/usr/lib \ -DLLVM_BUILD_LLVM_C_DYLIB=ON \ -DLLVM_ENABLE_LIBCXX=ON \ -DRUNTIMES_CMAKE_ARGS=-DCMAKE_INSTALL_RPATH="@loader_path/../lib;/opt/homebrew/opt/llvm/lib" \ -DDEFAULT_SYSROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk \ -DCMAKE_C_COMPILER=/opt/homebrew/opt/llvm/bin/clang \ -DCMAKE_CXX_COMPILER=/opt/homebrew/opt/llvm/bin/clang++ \ -DCMAKE_PREFIX_PATH=/opt/homebrew \ -DCMAKE_INSTALL_RPATH="@loader_path/../lib;/opt/homebrew/opt/llvm/lib" ```
The object file was compiled with
clang++ -c pretend-in-llvm.cpp -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk -c -fno-rtti -I/tmp/llvm/include -std=c++14 -stdlib=libc++ -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
and linked with
clang++ pretend-in-llvm.o -o a.out -lclangFrontend -lclangSerialization -lclangDriver -lclangCodeGen -lclangParse -lclangSema -lclangAnalysis -lclangEdit -lclangASTMatchers -lclangAST -lclangLex -lclangBasic -Wl,-rpath,/tmp/llvm/lib -L/tmp/llvm/lib -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM -L/tmp/llvm/lib
We can check that this links with the libc++
that we want:
❯ otool -L ./a.out
./a.out:
@rpath/libLLVM.dylib (compatibility version 1.0.0, current version 13.0.1)
@rpath/libc++.1.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)
❯ otool -l ./a.out | rg -A2 LC_RPATH
cmd LC_RPATH
cmdsize 32
path /tmp/llvm/lib (offset 12)
It also loads our just-built libc++
:
❯ DYLD_PRINT_LIBRARIES=1 ./a.out 1> /dev/null 2>&1 | rg 'libc[^A-Za-z]'
dyld[43551]: <355E121E-260B-3931-9825-E8033D109D3F> /private/tmp/llvm/lib/libc++.1.0.dylib
dyld[43551]: <B6B73FA9-4438-3B3D-A3B7-3B8CFA594B16> /opt/homebrew/Cellar/llvm/13.0.1_1/lib/libc++.1.0.dylib
dyld[43551]: <8F8963C1-403D-3282-AE5E-4F6E0E85F5A6> /usr/lib/libc++abi.dylib
dyld[43551]: <0EB3A986-B1BC-3F18-B7CC-51E2E5E00DC3> /usr/lib/libc++.1.dylib
dyld[43551]: <0D566764-7528-3C4B-9526-1C8DA0A9F59B> /opt/homebrew/Cellar/llvm/13.0.1_1/lib/libc++abi.1.0.dylib
This does still load Apple libc++
, but I've already explained previously how there is no way to avoid this: every process you run loads Apple libc++
. What is important is that it loads our just-built libc++
first.
We can also check that libLLVM
has the right linkage:
❯ otool -L /tmp/llvm/lib/libLLVM.dylib
/tmp/llvm/lib/libLLVM.dylib:
@rpath/libLLVM.dylib (compatibility version 1.0.0, current version 13.0.1)
/usr/lib/libffi.dylib (compatibility version 1.0.0, current version 27.0.0)
/usr/lib/libedit.3.dylib (compatibility version 2.0.0, current version 3.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
/opt/homebrew/opt/ncurses/lib/libncursesw.6.dylib (compatibility version 6.0.0, current version 6.0.0)
/usr/lib/libxml2.2.dylib (compatibility version 10.0.0, current version 10.9.0)
/opt/homebrew/opt/llvm/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1.0.0)
So, even if we compile libLLVM
using Homebrew libc++
(headers included), we still get the wrong output with your reproducer.
To be sure, I did check that I could reproduce the same results as your initial example when using Homebrew LLVM (i.e. not one that I just built locally) and linking with Homebrew libc++
vs Apple libc++
.
To try to remove any doubt about mixing libc++
headers and libraries, I used my just-built clang to compile your example:
❯ /tmp/llvm/bin/clang++ -c pretend-in-llvm.cpp -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk -c -fno-rtti -I/tmp/llvm/include -std=c++14 -stdlib=libc++ -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
❯ /tmp/llvm/bin/clang++ pretend-in-llvm.o -o a.out -lclangFrontend -lclangSerialization -lclangDriver -lclangCodeGen -lclangParse -lclangSema -lclangAnalysis -lclangEdit -lclangASTMatchers -lclangAST -lclangLex -lclangBasic -Wl,-rpath,/tmp/llvm/lib -L/tmp/llvm/lib -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM -L/tmp/llvm/lib
This should guarantee that I am using my just-installed libc++
headers and my just-built libc++
.
Here's what I get:
❯ ./a.out
EC.message is No such file or directory
EC == no_such_file_or_directory is 0
OK, at this point you have more information than I do that would be useful to LLVM developers in an upstream bug report. In particular, the bug report to give them is ideally one that does not use Homebrew at all.
Wait, no, the cmake you did uses the Homebrew clang in order to get it to build with that libc++. So it is still not a demonstrator independent of Homebrew.
Just noting that for those who are only following along by email notifications (as I have been) that Michael's latest comment has been edited on the GitHub issue to include an additional observation.
I've been able to create some scripts to reproduce that we can use as a bug report to LLVM. These scripts don't use Homebrew at all. (I haven't tried it on a system without Homebrew installed at all but I didn't have any llvm package installed).
I have created this llvm project issue: https://github.com/llvm/llvm-project/issues/54438
NB:
% otool -L a.out
a.out:
/usr/local/opt/llvm/lib/libLLVM.dylib (compatibility version 1.0.0, current version 13.0.1)
/usr/local/opt/llvm/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)
% ./a.out
EC.message is No such file or directory
EC == no_such_file_or_directory is 0
% install_name_tool -change /usr/lib/libc++.1.dylib /usr/local/opt/llvm/lib/libc++.1.dylib /usr/local/opt/llvm/lib/libLLVM.dylib
% ./a.out
EC.message is No such file or directory
EC == no_such_file_or_directory is 1
% install_name_tool -change /usr/local/opt/llvm/lib/libc++.1.dylib /usr/lib/libc++.1.dylib /usr/local/opt/llvm/lib/libLLVM.dylib
% ./a.out
EC.message is No such file or directory
EC == no_such_file_or_directory is 0
Mixing libc++ versions is unsupported upstream.
@kencu As I mentioned in the discussion above, I don't think this is a consequence of mixing libc++ versions.
You will get the erroneous behaviour if you build libc++ first and then link libLLVM against your just-built libc++.
If you see what I did with install_name_tool, you can make the error come and go at will just by by forcing mixing or not mixing of libc++ versions…
Of course, it’s possible some other factor could be involved too…toolchains are like that!
Yes, I can see that.
However, that does not imply that the bug is caused by mixing libc++ versions. There being a bug in LLVM 13 libc++ is consistent with the behaviour from install_name_tool
that you've demonstrated. Your example does not rule that out.
In particular:
You will get the erroneous behaviour if you build libc++ first and then link libLLVM against your just-built libc++.
This means that you also get the bug if you don't mix libc++ versions by linking only with LLVM 13 libc++. Unless you are saying that there is still mixed libc++ linkage here?
@carlocab - have you been able to review the discussion in https://groups.google.com/g/llvm-dev/c/6ZZ0FjiueDY/m/zVwhWzoOBQAJ as suggested in https://github.com/llvm/llvm-project/issues/54438#issuecomment-1087974558 ? From that discussion, it looks to me like it is not reasonable to expect the clang libraries to link with a custom-built libc++ on Mac OS X. Quoting from the discussion:
is it possible to ship a custom libc++ in a .app archive for older versions of macOS in order to use C++17?
Generally, I would say this is a tricky thing to do. As you mention, the problem is that if you link against any other library that does link against the system-provided libc++.dylib, you'll have ODR violations because some symbols will be found in both libraries. As a result, I believe you could also end up in a situation where you end up using globals from one library when the globals from the other library has been initialized. So basically, nothing good happens.
If you can ensure that your application doesn't pull in the system libc++.dylib (even transitively), then this would in theory be safe to do. Due to the brittleness of the solution, my immediate reaction would be to not recommend that.
So, to me, it sounds like supporting that kind of configuration requires a lot of effort (possibly, heroic effort) on the part of the application and/or library developers. Which I don't think the clang project would want to do.
If you have remaining doubts, would you be able to ask the LLVM developers for more clarification? (And one way to do so, if we need more people to see the issue, is to post about it to the LLVM Discourse). Thanks.
The solution towards this is a bit complicated.
A partial install of libc++ is needed for at least the headers, on systems using a SDK earlier than 11.3. Since LLVM 12, Clang first looks for C++ headers beside it in the toolchain and then if it doesn't exist it looks into the SDK - though we have backported this particular change all the way back to LLVM 8 however, since it's needed in order to actually be able to compile anything with newer SDKs. So for the macOS 11.3 SDK and later, we can omit libc++ headers entirely but since we technically support as far back as 10.10 so we will not do that.
But that's fine because newer headers and an older system library is a supported use case. It has to be because this is exactly what Xcode does half the time.
Linking to /usr/lib/libc++.dylib
is already the default behaviour unless that has changed recently (there were actually upstream proposals to make it default to LLVM's libc++ but this hasn't happened yet). The problem comes when you are adding llvm-config --libs
flags because unfortunately libc++.dylib
is in the same directory as libLLVM and co.
You're right though that /usr/lib/libc++.dylib
should always be preferred.
So what do we do about it? Well, to cover a few ideas, we could:
llvm-config
and CMake help there).-stdlib=llvm-libc++
.-L/usr/lib
first
llvm upstream has been clear for years that they will not support mixing libc++ versions.
It works sometimes, and fails sometimes, randomly, like this example. I don't believe it is a bug. As the system libc++ and the lllvm libc++ diverge in age, it gets worse.
-stdlib=llvm-libc++
will not work without hacking clang's build. It's not too hard, we have done it to support using gcc11's libstdc++ with clang on macOS, but it's not trivial either.
Of course you should always use the current clang headers.
So ship libc++ if you want, it works sometimes, just tell people that is is likely fragile, they have to test, linking in the static libc++ might work better, they should not count on it working, etc.
llvm upstream has been clear for years that they will not support mixing libc++ versions.
At no point in my comment did I debate against that. I'm proposing solutions that make that mixing no longer happen.
At no point in my comment did I debate against that. I'm proposing solutions that make that mixing no longer happen.
Others said it was a bug.
It is very very very hard to avoid mixing libc++ versions if you are going to try to use a newer llvm libc++ , because everything in the system is linked against the system libc++.dylib.
Best of luck!
@Bo98, @kencu, @carlocab: Thanks for all the thoughts and data here so far.
I wanted to give this issue a bump by asking some high-level questions:
Thanks
Is there an obvious owner of this issue from the homebrew team going forward?
We don't have owners of issues. Whoever wants something fixed can fix it.
Is there a suggested best practice workaround for projects like ours that rely on the LLVM homebrew formula to compile and link things across multiple Mac OS X versions?
This won't be possible with Homebrew AFAIK since formula only ever build for a single OS.
As far as I can tell, our current best option would be to stop using homebrew LLVM and have our formula build and use the one that we bundle with Chapel, but that would have the obvious downsides of wasted build times, larger bottle sizes, and not following the spirit of homebrew.
Not to mention being against the requirements for formulae: https://docs.brew.sh/Acceptable-Formulae#stuff-that-requires-vendored-versions-of-homebrew-formulae
Apologies for being MIA here -- my day job has been quite busy as of late.
I've yet to catch up with the newer posts here since I last commented (will do so soon), but here's a possible (but slightly hacky) solution that at least has the benefit of being less invasive, and should seem invisible to most users.
We can try changing the install name of
/usr/lib/libc++.1.dylib
in libLLVM.dylib
to
@rpath/libc++.1.dylib
The idea is to make it so that when you link with LLVM libc++
, so does libLLVM
. Conversely, if you link with system libc++
, you also have libLLVM
using the system libc++
.
This is actually inspired by @kencu's observation above. I'll need to think a bit more about whether this is a reasonable fix, but at the very least this will fix the reproduction case we have here.
I have a feeling that for any serious work using a newer libc++ on older sytems, you would have to copy what Google does and link your application to libc++.a statically. Then, at least, you just have to make certain you don't purposefully share any objects with the system frameworks (and I would not pretend I know how to ensure this, but presumably some of you do know how), and the chances of random error would then seem to be minimized. Perhaps I will see if I can rework our example here to do that.
As I understand it, Chrome sets "-nostdlibs" and then adds in the link to libc++.a manually, and everything "just works".
Nothing in Chrome seems to link to libc++.dylib, anyway.
% otool -L "Google Chrome"
Google Chrome:
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)
% otool -L "Google Chrome Framework"
Google Chrome Framework:
@executable_path/../Frameworks/Google Chrome Framework.framework/Versions/100.0.4896.127/Google Chrome Framework (compatibility version 4896.0.127, current version 4896.0.127)
/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate (compatibility version 1.0.0, current version 4.0.0)
/System/Library/Frameworks/AudioUnit.framework/Versions/A/AudioUnit (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/AVFoundation.framework/Versions/A/AVFoundation (compatibility version 1.0.0, current version 2.0.0)
/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
/System/Library/Frameworks/ImageCaptureCore.framework/Versions/A/ImageCaptureCore (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore (compatibility version 1.2.0, current version 1.11.0)
/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo (compatibility version 1.2.0, current version 1.5.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/System/Library/Frameworks/SecurityInterface.framework/Versions/A/SecurityInterface (compatibility version 1.0.0, current version 55159.0.0)
/System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 60157.60.19)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices (compatibility version 1.0.0, current version 56.0.0)
/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics (compatibility version 64.0.0, current version 1557.3.2)
/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 1141.1.0)
/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork (compatibility version 1.0.0, current version 1327.0.4)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1856.105.0)
/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 2113.20.111)
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1856.105.0)
/System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement (compatibility version 1.0.0, current version 2235.60.3)
/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration (compatibility version 1.0.0, current version 1163.60.3)
/System/Library/Frameworks/Metal.framework/Versions/A/Metal (compatibility version 1.0.0, current version 258.17.0, weak)
/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon (compatibility version 2.0.0, current version 165.0.0)
/System/Library/Frameworks/Quartz.framework/Versions/A/Quartz (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 23.0.0)
/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox (compatibility version 1.0.0, current version 1000.0.0)
/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/VideoToolbox.framework/Versions/A/VideoToolbox (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/LocalAuthentication.framework/Versions/A/LocalAuthentication (compatibility version 1.0.0, current version 984.60.3)
/System/Library/Frameworks/IOBluetooth.framework/Versions/A/IOBluetooth (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/CoreBluetooth.framework/Versions/A/CoreBluetooth (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/CoreMIDI.framework/Versions/A/CoreMIDI (compatibility version 1.0.0, current version 69.0.0)
/System/Library/Frameworks/MediaAccessibility.framework/Versions/A/MediaAccessibility (compatibility version 1.0.0, current version 62.0.0)
/System/Library/Frameworks/ForceFeedback.framework/Versions/A/ForceFeedback (compatibility version 1.0.0, current version 1.0.2)
/System/Library/Frameworks/GameController.framework/Versions/A/GameController (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/CoreWLAN.framework/Versions/A/CoreWLAN (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/CoreLocation.framework/Versions/A/CoreLocation (compatibility version 1.0.0, current version 2665.0.9)
/System/Library/Frameworks/AuthenticationServices.framework/Versions/A/AuthenticationServices (compatibility version 1.0.0, current version 612.3.6, weak)
/System/Library/Frameworks/SafariServices.framework/Versions/A/SafariServices (compatibility version 535.0.0, current version 612.3.6, weak)
/System/Library/Frameworks/UserNotifications.framework/Versions/A/UserNotifications (compatibility version 1.0.0, current version 1.0.0, weak)
/System/Library/Frameworks/MetalKit.framework/Versions/A/MetalKit (compatibility version 1.0.0, current version 151.0.0, weak)
/System/Library/Frameworks/MediaPlayer.framework/Versions/A/MediaPlayer (compatibility version 1.0.0, current version 1.0.0, weak)
/System/Library/Frameworks/Vision.framework/Versions/A/Vision (compatibility version 1.0.0, current version 5.0.71, weak)
/System/Library/Frameworks/ScreenTime.framework/Versions/A/ScreenTime (compatibility version 1.0.0, current version 500.6.1, weak)
/usr/lib/libcups.2.dylib (compatibility version 2.0.0, current version 2.14.0)
/usr/lib/libbsm.0.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libpmenergy.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libpmsample.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libsandbox.1.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libbz2.1.0.dylib (compatibility version 1.0.0, current version 1.0.8)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
If possible and appropriate, I'd like to see this remain open as it remains a concern for us, and it sounded as though @carlocab, @Bo98, and others had been having productive thoughts about it on #97618.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thanks for keeping this alive, @Bo98!
I am exploring solutions to this at #106925.
I experimented with removing the libc++
and libc++abi
builds from the llvm
formula. Results are at https://github.com/Homebrew/homebrew-core/actions/runs/2897386665.
A few notes:
libc++
instead of the one in /usr/lib
. I'm still suspicious that Chapel is the only one reporting issues with this.libc++[abi]
breaks the Catalina build. More specifically, it fails at building compiler-rt
. I already have the right flags to pass to the build to make it succeed, but this also means that users must pass these flags in order to use the compiler that ships with the llvm
formula. That effectively means shipping a broken clang
, unless we add some other fix that makes it work out-of-the box. Some workarounds suggested in this thread. although those projects/runtimes (libcxx/libcxxabi) need to be enabled for the build, there are toggles to allow you to install the libraries or not:
you probably already knew this, of course.
Yes, I vaguely recalled some LIBCXX_INSTALL_*
flags, but I wasn't sure what the exact form was.
However: if we don't want to link with different versions of libc++
, then we probably don't want to mix headers from one but use the library of the other either.
we at the unnamed always install and use the latest headers that match the compiler version, but (usually) don’t install libcxx/libcxxabi. It has worked generally well for us; you might find the same. MacOS is designed to deploy on older targets, so the newer headers have guards in them for the capabilities of the libcxx you are deploying to.
this install technique was originally set up by one of the Apple compiler engineers who previously was involved way back when — I just continued that along. At one point I installed a newer version of libcxx/libcxxabi as well that can be specifically optionally used during builds on older systems, but there are conflicts sometimes as you have found too…
best of luck! you’re doing great work here!
@carlocab -
- I'm still suspicious that Chapel is the only one reporting issues with this.
I just wanted to point out some ways that the Chapel package might be unique:
20+ other formulae link with LLVM's libc++ instead of the one in /usr/lib. I'm still suspicious that Chapel is the only one reporting issues with this.
I just wanted to point out some ways that the Chapel package might be unique...
@carlocab: Following up on Michael's hypothesis, could you share the list of 20+ formulae you're referring to above? I'm curious whether (through inspection or reasoning or inquiries) we could confirm or deny the guess.
Following up on Michael's hypothesis, could you share the list of 20+ formulae you're referring to above? I'm curious whether (through inspection or reasoning or inquiries) we could confirm or deny the guess.
Here's a list:
A number of these also link with libclang-cpp
, but I haven't checked them all to see if none link with libclang
.
- we use libclang and llvm instead of just llvm or just libclang
- we use llvm-config to find llvm dependencies (vs cmake stuff)
I notice that brew linkage
(or, equivalently, otool
) does not report linkage with libclang
. Does Chapel dlopen
libclang
? If so, that may explain the issues you're seeing.
Why would using llvm-config
make a difference?
Another odd thing about Chapel is that we have some relatively strange workarounds for this issue...
I notice that
brew linkage
(or, equivalently,otool
) does not report linkage withlibclang
. Does Chapeldlopen
libclang
? If so, that may explain the issues you're seeing.Why would using
llvm-config
make a difference?
We are statically linking with clang and LLVM libraries on Mac OS X because of bugs in llvm-config
on Mac OS X.
The static linking is a workaround. From a PR message talking about it ( https://github.com/chapel-lang/chapel/pull/19272 ) :
Note that we statically link both LLVM and clang on Mac OS X (since PR #18727). This PR leaves it this way. Historically, there have been problems with upstream LLVM and Homebrew in building a dynamic library that works with
llvm-config
(in particular the versioned file, likelibLLVM-11.dylib
, is missing). See this LLVM bug report. I expect that we will be able to allow dynamic linking on Mac OS X once we are able to establish that the issue is resolved.
It's my understanding that projects using cmake
to find clang do not have this issue, but I could be wrong. So, perhaps statically linking with LLVM and clang libraries something strange that we are doing that other projects do not.
Not sure if doing away with static linking will help, but you can definitely do that with llvm@1{2,3,4}
:
❯ /usr/local/opt/llvm/bin/llvm-config --link-shared; echo $?
0
❯ /usr/local/opt/llvm/bin/llvm-config --libs
-lLLVM-14
❯ /usr/local/opt/llvm/bin/llvm-config --libfiles
/usr/local/Cellar/llvm/14.0.6_1/lib/libLLVM-14.dylib
In particular, you should no longer see the issue reported at llvm/llvm-project#39599 using a recent enough LLVM packaged with Homebrew.
I think a good next step would be to see if the reproducer in this issues original post still has the problem if it is changed to dynamically link.
I've reproduced the problem in the OP with static linking and the current LLVM 14 Homebrew package on my system:
$ clang++ -c pretend-in-llvm.cpp -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk -c -fno-rtti -I/usr/local/Cellar/llvm/14.0.6_1/include -std=c++14 -stdlib=libc++ -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
$ clang++ pretend-in-llvm.o -o a.out -lclangFrontend -lclangSerialization -lclangDriver -lclangCodeGen -lclangParse -lclangSema -lclangAnalysis -lclangEdit -lclangASTMatchers -lclangAST -lclangLex -lclangBasic -Wl,-rpath,/usr/local/Cellar/llvm/14.0.6_1/lib -L/usr/local/Cellar/llvm/14.0.6_1/lib -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM-14 -L/usr/local/lib
$ ./a.out
EC.message is No such file or directory
EC == no_such_file_or_directory is 0
(it is supposed to print out "is 1").
However, I'm still seeing the problem with dynamic linking:
$ clang++ -c pretend-in-llvm.cpp -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk -c -fno-rtti -I/usr/local/Cellar/llvm/14.0.6_1/include -std=c++14 -stdlib=libc++ -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
$ clang++ pretend-in-llvm.o -o a.out -lclang-cpp -Wl,-rpath,/usr/local/Cellar/llvm/14.0.6_1/lib -L/usr/local/Cellar/llvm/14.0.6_1/lib -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM-14 -L/usr/local/lib
$ ./a.out (git)-[remotes/upstream/main]
EC.message is No such file or directory
EC == no_such_file_or_directory is 0
The workaround (adding -L/Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk/usr/lib
early in the link command) still seems to work.
So, unfortunately, it does not seem that switching Chapel to dynamic linking will help with this problem. Nonetheless, I'd like to look at doing that anyway, to make it less unique in this regard, and to better match what we do on linux.
Chapel should no longer be linking with LLVM libc++
. From the latest version of Chapel distributed in Homebrew (1.28.0_1
):
System libraries:
/usr/lib/libSystem.B.dylib
/usr/lib/libc++.1.dylib
/usr/lib/libncurses.5.4.dylib
Homebrew libraries:
/usr/local/opt/gmp/lib/libgmp.10.dylib (gmp)
/usr/local/opt/llvm@14/lib/libLLVM.dylib (llvm@14)
/usr/local/opt/llvm@14/lib/libclang-cpp.dylib (llvm@14)
Compare this with the bottle built at #110783:
System libraries:
/usr/lib/libSystem.B.dylib
/usr/lib/libc++.1.dylib
/usr/lib/libncurses.5.4.dylib
Homebrew libraries:
/usr/local/opt/gmp/lib/libgmp.10.dylib (gmp)
/usr/local/opt/llvm/lib/libLLVM.dylib (llvm)
/usr/local/opt/llvm/lib/libc++.1.dylib (llvm)
/usr/local/opt/llvm/lib/libclang-cpp.dylib (llvm)
Incidentally, this may be what distinguishes Chapel from the other formulae that use LLVM libc++
referenced above: I don't think any of them have brew linkage
reporting both of /usr/lib/libc++.1.dylib
and /usr/local/opt/llvm/lib/libc++.1.dylib
.
Incidentally, something weird I noticed, in the ARM bottles:
System libraries:
/usr/lib/libSystem.B.dylib
/usr/lib/libc++.1.dylib
Homebrew libraries:
/opt/homebrew/opt/gmp/lib/libgmp.10.dylib (gmp)
/opt/homebrew/opt/llvm/lib/libLLVM.dylib (llvm)
/opt/homebrew/opt/llvm/lib/libc++.1.dylib (llvm)
/opt/homebrew/opt/llvm/lib/libclang-cpp.dylib (llvm)
There is no linkage with ncurses
, whereas the Intel bottles have it.
Not sure if this is an issue.
brew gist-logs <formula>
link ORbrew config
ANDbrew doctor
outputVerification
brew update
and am still able to reproduce my issue.brew doctor
and that did not fix my problem.What were you trying to do (and why)?
I'm one of the developers for the upstream Chapel project. Chapel was working with
llvm@11
but we were running into strange problems withllvm@12
andllvm@13
(see https://github.com/chapel-lang/chapel/issues/19217 ).It took me a long time but I eventually figured out that the problem was due to mixing libc++ versions. The Chapel compiler,
chpl
was linking with the libc++ included in the Homebrew LLVM package; but the LLVM and Clang libraries were linking with a libc++ provided by the system. This will occur with any program that is linking with llvm or clang libraries using the usual commands to add a-L
path for/usr/local/Cellar/llvm/13.0.1_1/lib
, including using the output of/usr/local/opt/llvm@13/bin/llvm-config --ldflags
.I have included a small reproducer example in the step-by-step section below.
What happened (include all command output)?
With the test program provided below, I get this output
What did you expect to happen?
I expected this output from the test program provided below (without the workaround):
Step-by-step reproduction instructions (by running
brew
commands)Then, I have saved the following to
pretend-in-llvm.cpp
and within it are the commands to reproduce this problem.