Why does Apple clang disallow C++11 thread_local when 'official' clang supports it
Asked Answered
P

3

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)
Prager answered 22/1, 2015 at 17:16 Comment(2)
@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.Prager
Probably a bug: https://mcmap.net/q/506042/-c-thread-local-storage-clang-503-0-40-mac-osxExpedient
E
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.

Elbertine answered 28/4, 2015 at 21:7 Comment(13)
They don’t want to break ABI compatibility for a specific feature so they don’t offer the feature at all? That’s mental.Masera
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.Prager
@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.Cameleer
@Cameleer 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.Prager
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.Panacea
@Panacea Screw the OSX version of clang. Just use the real deal.Dinse
@Dinse 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.Panacea
@Panacea 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.Dinse
@Panacea 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.Elbertine
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.Anneliese
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 @Anneliese means about the way they implemented it, but have too much on my plate to research it fully.Panacea
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.Banksia
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.Elbertine
H
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.

Heroic answered 23/1, 2015 at 2:24 Comment(3)
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.Prager
This may have been better posted as a comment instead of an answer. I imagine @HowardHinnant knows the real reason.Heroic
"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?"Prager
D
1

It seems using __thread instead works on macOS.

Dorris answered 18/9, 2021 at 20:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.