27

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)
Petter Friberg
  • 21,252
  • 9
  • 60
  • 109
acm
  • 12,183
  • 5
  • 39
  • 68
  • 1
    @BrettHale Apple's official clang release in XCode 6 is based on clang-3.5, and clang-3.5 supports thread_local on Darwin. I believe it did in releases earlier than clang-3.5 as well. – acm Jan 23 '15 at 20:44
  • 1
    Probably a bug: http://stackoverflow.com/a/23850891/1392778 – Thomas Jan 25 '15 at 19:30

3 Answers3

45

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.

rsfinn
  • 1,073
  • 14
  • 26
  • 40
    They don’t want to break ABI compatibility *for a specific feature* so they don’t offer the feature at all? That’s mental. – Konrad Rudolph Jun 29 '15 at 08:57
  • 8
    This is really unfortunate. It means that you *still* can't use thread_local in portable C++11, even now that VC14 supports it. If there are any engineers from Apple reading this, I really hope you plan to repair this soon. – acm Jul 28 '15 at 17:30
  • @KonradRudolph, they are working in a better feature that would be ABI-incompatible with the existing implementation, so they are not supporting the existing implementation. If they supported it and dropped it later because of their better implementation, I bet you'd be complaining even more about how they should have made up their mind and support the worse option forever. – hmijail Mar 15 '16 at 15:18
  • @hmijail Fair enough, but will this new and improved version be supported on older OS X? In other words, if I target with -mmacosx-version-min=10.6, say, will this amazing new ABI thread_local be available? If not, then yes I'd have preferred an ABI break. At least then I could use the feature, but ship separate release streams. As it stands, I can't use the feature, and I probably won't be able to for years. – acm Apr 01 '16 at 21:26
  • 1
    osx-provided clang still doesn't support `thread_local` as of may 10, 2016 :( i see no plans or horizon for support of this feature, which sucks because `thread_local` would have helped solve a performance problem for me. – ofloveandhate May 10 '16 at 19:49
  • @ofloveandhate Screw the OSX version of clang. Just use the real deal. – Navin Jun 24 '16 at 08:07
  • @Navin i would really like to do that, yet feel that expecting the users of my program who wish to install from source should not have to install a compiler beyond the XCode install from apple. Compatibility problems between libraries built with differing compilers is a headache I'd really like to avoid. any advice to this end, regarding `thread_local`, 'the real deal' clang, and dependency libraries such as Boost, MPFR, GMP, and MPI? Currently, I expect these to all be installed via a package manager such as Homebrew. – ofloveandhate Jun 27 '16 at 18:57
  • @ofloveandhate Pretty much every dev using OSX has homebrew installed, so I don't think that asking people to install clang is too much to ask for. This SO question should provide the necessary motivation to anyone who isn't already convinced that Apple's compiler and libs are crap. – Navin Jun 29 '16 at 06:18
  • 1
    @ofloveandhate Note that the clang compiler in Xcode 8 ("finally"?) includes support for `thread_local`, as announced at this year's WWDC. This provides another option if your users are either comfortable with a beta compiler or can wait until fall. – rsfinn Jun 29 '16 at 17:31
  • 4
    The way Apple finally implemented `thread_local` proves why all the critics above are wrong. They waited until they had a good implementation that they could guarantee would be stable forever. That philosophy is exactly why libSystem (Apple's "CRT") had **never** broken binary compatibility, while Microsoft has to release a new `msvcrXYZ.dll`/`vcruntimeXYZ.dll` with every compiler change. – alexchandel Aug 29 '16 at 21:05
  • 2
    i am confirming @rsfinn's note from June 29 2016. Xcode 8 **does** indeed support `thread_local`, tested Sep 20 2016. i am casually interested in what @alexchandel means about the way they implemented it, but have too much on my plate to research it fully. – ofloveandhate Sep 20 '16 at 18:08
  • I have XCode 8.1 (clang-800.0.42.1) and still get a compiler error "Thread-local storage is not supported for the current target". I am using c++14 and libc++ static runtime. Is there a minimum IOS target that's needed for it to work? I am using 6.0 as deployment target as my app does not require any newer features. – GameSalutes Nov 21 '16 at 21:04
  • The previous tests have all been for OS X (macOS) targets. I created an iOS target and by experimentation found that `thread_local` is supported for iOS 9 and later, but not for iOS 8.4 or earlier. I don't have an explanation for this; it looks like TLS support is compiled into the Mac application, but the iOS application seemingly relies on OS runtime support which is not present before iOS 9. – rsfinn Nov 21 '16 at 22:33
2

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.

sbooth
  • 16,646
  • 2
  • 55
  • 81
  • 4
    Both XCode and Homebrew clang are capable of using both libstdc++ and libc++. Since OS X only ships a GCC 4.1 vintage libstdc++, it is a non-starter for C++11. So, I'm definitely using libc++. Still, it doesn't answer the question why the homebrew clang with libc++ can use thread_local, but the XCode clang with libc++ cannot. – acm Jan 23 '15 at 13:25
  • 1
    This may have been better posted as a comment instead of an answer. I imagine @HowardHinnant knows the real reason. – sbooth Jan 23 '15 at 14:00
  • 1
    "Glendower: I can call spirits from the vasty deep. Hotspur: Why, so can I, or so can any man; But will they come when you do call for them?" – acm Jan 24 '15 at 19:44
1

It seems using __thread instead works on macOS.

Igor Skochinsky
  • 24,629
  • 2
  • 72
  • 109