Below is a simple program that tests using a C++11 thread_local variable of non-POD type in a shared library.
If I use homebrew clang, this works fine:
> /usr/local/Cellar/llvm/3.5.0_2/bin/clang --version
clang version 3.5.0 (tags/RELEASE_350/final)
Target: x86_64-apple-darwin14.0.0
Thread model: posix
> cmake .. -G Ninja -DCMAKE_C_COMPILER=/usr/local/Cellar/llvm/3.5.0_2/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/Cellar/llvm/3.5.0_2/bin/clang++
-- The C compiler identification is Clang 3.5.0
-- The CXX compiler identification is Clang 3.5.0
-- Check for working C compiler using: Ninja
-- Check for working C compiler using: Ninja -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler using: Ninja
-- Check for working CXX compiler using: Ninja -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
> ninja all
...
> ./main
XXX LifeCycle::LifeCycle 0x7fedc0c04b90
X before: -17
XXX LifeCycle::LifeCycle 0x7fedc0c04c10
X before in thread: -17
X after in thread: 2
XXX LifeCycle::~LifeCycle 0x7fedc0c04c10
X after: 1
XXX LifeCycle::~LifeCycle 0x7fedc0c04b90
However, if I try to use Apple Clang, I get an error message saying that it is not supported:
> /usr/bin/clang --version
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.0.0
Thread model: posix
> cmake .. -G Ninja -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++
-- The C compiler identification is AppleClang 6.0.0.6000056
-- The CXX compiler identification is AppleClang 6.0.0.6000056
-- Check for working C compiler using: Ninja
-- Check for working C compiler using: Ninja -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler using: Ninja
-- Check for working CXX compiler using: Ninja -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to:
> ninja all
[1/4] Building CXX object CMakeFiles/lib.dir/lib.cpp.o
FAILED: /usr/bin/clang++ -Dlib_EXPORTS -Wall -std=c++11 -mmacosx-version-min=10.7 -stdlib=libc++ -fPIC -MMD -MT CMakeFiles/lib.dir/lib.cpp.o -MF CMakeFiles/lib.dir/lib.cpp.o.d -o CMakeFiles/lib.dir/lib.cpp.o -c ../lib.cpp
../lib.cpp:23:5: error: thread-local storage is unsupported for the current target
thread_local LifeCycle lc;
^
1 error generated.
ninja: build stopped: subcommand failed.
Can anyone offer any insight into why Apple's clang variant cowardly refuses to honor thread_local, despite the fact that the underlying compiler supports it, and the generated code appears to work?
lib.h:
#pragma once
int doit(int) __attribute__((__visibility__("default")));
lib.cpp:
#include "lib.h"
#include <thread>
#include <cstdlib>
#include <cstdio>
namespace {
class LifeCycle {
public:
LifeCycle()
: x(-17) {
printf("XXX LifeCycle::LifeCycle %p\n", this);
}
~LifeCycle() {
printf("XXX LifeCycle::~LifeCycle %p\n", this);
}
int x;
};
thread_local LifeCycle lc;
} // namespace
int doit(int arg) {
printf("X before: %d\n", lc.x);
lc.x = arg;
std::thread xwriter([arg]() {
if (lc.x == arg)
abort();
printf("X before in thread: %d\n", lc.x);
lc.x = arg + 1;
printf("X after in thread: %d\n", lc.x);
});
xwriter.join();
printf("X after: %d\n", lc.x);
return (lc.x == arg ? EXIT_SUCCESS : EXIT_FAILURE);
}
main.cpp:
#include "lib.h"
int main(int argc, char* argv[]) {
return doit(argc);
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.1)
set(CMAKE_CXX_FLAGS "-Wall -std=c++11 -mmacosx-version-min=10.7 -stdlib=libc++")
add_library(lib SHARED lib.cpp)
add_executable(main main.cpp)
target_link_libraries(main lib)
The clang compiler included with Xcode 8 and later supports the C++11 thread_local
keyword. This functionality was added to the Xcode 8 beta as discussed in the WWDC 2016 video "What's New in LLVM", beginning at the 5:50 mark. (external transcript)
The sample program listed in the question compiles and runs with Xcode 8 GM under OS X 10.11.6 and produces the intended output. It has subsequently been re-tested with Xcode 9.3 under macOS 10.13.4, and with Xcode 10.2.1 under macOS 10.14.4, and continues to behave as intended.
Regarding iOS, I found by experimentation that thread_local
is supported for iOS 9 and later, but not for iOS 8.4 or earlier.
For Xcode 7.x and earlier, here is an answer from 2014 from an Apple engineer on the old Apple Developer Forum (no longer accessible):
We don't support the thread_local implementation from the open-source Clang because we believe we can provide a higher-performance implementation for our platforms using various features in the dynamic linker. Such an implementation would be ABI-incompatible with the implementation in the open-source Clang, so we won't support thread_local until we get an implementation we can live with for the foreseeable future.
A subsequent post confirms that thread_local
is still not supported in Xcode 6.3.
According to http://clang.llvm.org/cxx_status.html:
thread_local support currently requires the C++ runtime library from g++-4.8 or later
I believe that the homebrew version of clang uses a different C++ runtime.
It seems using __thread
instead works on macOS.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With