недопустимая локальная ссылка потока на обычную ошибку символа в переменной TLS

У меня странная проблема, которая возникает во время связывания.

У меня есть заголовочный файл, содержащий следующее определение foo.hpp:

struct Foo { static __thread int x; }

И исходный файл, который ссылается на эту переменную plugin.cpp:

#include "foo.hpp"
void bar() { int y = Foo::x; }

Он отлично компилируется с:

$CXX -stdlib=libc++ -std=c++11 -fvisibility=hidden -fPIC -o plugin.cpp.o -c plugin.cpp

Но когда я пытаюсь связать динамическую библиотеку:

$CXX -stdlib=libc++ -std=c++11 -fvisibility=hidden -dynamiclib -Wl,-undefined,dynamic_lookup -o libext.dylib ext.cpp.o

Я получил:

ld: недопустимая ссылка локальной переменной потока на обычный символ __ZN3Foo1xE для архитектуры x86_64

Однако байт-код llvm подразумевает, что компилятор правильно видит Foo::x как переменную TLS.

$CXX -stdlib=libc++ -std=c++11 -fvisibility=hidden -fPIC -S -emit-llvm -o -
... omitted
@_ZN3Foo1xE = external thread_local global i32
... omitted
; Function Attrs: nounwind ssp uwtable
define hidden void @_Z3barv() #0 {
  %y = alloca i32, align 4
  %1 = load i32* @_ZN3Foo1xE, align 4
  store i32 %1, i32* %y, align 4
  ret void
}

Что может быть причиной этой проблемы с компоновщиком и есть ли обходной путь? Я не могу найти никаких сообщений об ошибках, связанных с этим.

Примечания:

  • Это чисто использование Apple LLVM 7.0.0
  • У меня нет проблем с подключением с помощью gcc 5 или gcc 4.9.3 в OS X.

EDIT Та же проблема возникает при ссылке на глобальную (вместо статического класса) переменную.

Когда я использую thread_local вместо __thread, это работает нормально, однако thread_local не работает с версией LLVM, поставляемой с Xcode.


person Joel Cornett    schedule 10.12.2015    source источник
comment
Что произойдет, если вместо этого вы сделаете Foo::x глобальной переменной? Если это работает, вы можете оставить его глобальным или сделать его локальным (статическое или анонимное пространство имен) для foo.cpp со статической функцией получения в Foo. Я просто предполагаю; возможно, у компилятора проблема со статическими переменными-членами __thread.   -  person 1201ProgramAlarm    schedule 14.12.2015
comment
@ 1201ProgramAlarm: я должен был указать это в вопросе. Использование глобальной переменной приводит к той же самой проблеме. Конечно, чтобы сделать вариант использования эквивалентным, объявление extern __thread int my_global;   -  person Joel Cornett    schedule 14.12.2015
comment
Вы только объявили переменную x, покажите нам ее определение. Доллары к пончикам, вы забыли __thread в определении. Или вы совсем его забыли и получили паршивую диагностику компоновщика.   -  person Hans Passant    schedule 14.12.2015
comment
Определение встречается в совершенно другой единице перевода, поэтому определение не включено. Это не должно иметь значения при создании динамической библиотеки. И, как я уже упоминал в вопросе, он отлично работает, используя thread_local в clang 7.   -  person Joel Cornett    schedule 14.12.2015
comment
'thread_local' требует правильной инициализации во всех контекстах, включая правильный вызов конструкторов в DLL. Предшествующие версии локального хранилища потока обычно (все?) имеют некоторый дефицит по отношению к thread_local. Если бы мне пришлось угадывать, я бы сказал, что __thread является одной из этих версий-предшественников и на самом деле не работает в контексте DLL. Если это так, сообщение об ошибке на самом деле говорит вам, что цепочка инструментов не поддерживает ваше использование.   -  person eh9    schedule 14.12.2015
comment
Цитата из open-std.org/jtc1/ sc22/wg21/docs/papers/2008/n2659.htm В настоящее время все реализации локального хранилища потока не поддерживают динамическую инициализацию (и, предположительно, нетривиальные деструкторы). На собрании в Мон-Треблане был достигнут умеренный консенсус в отношении поддержки динамической инициализации локальных переменных функций и локальных потоков. Инициализация таких переменных уже защищена и синхронна, поэтому новая технология не требуется. [...]   -  person eh9    schedule 14.12.2015
comment
[...] С другой стороны, реализация динамической инициализации переменных области пространства имен намного сложнее и может потребовать дополнительной поддержки компоновщика и операционной системы. В то время не было консенсуса в отношении поддержки динамической инициализации переменных пространства имен. Однако интервью с потенциальными пользователями показали твердое желание полной динамической инициализации переменных длительности хранения потоков. Программисты просто не хотели разделять свои типы таким образом.   -  person eh9    schedule 14.12.2015
comment
Кроме того, в том же документе есть раздел, специально посвященный реалиям реализации динамической компоновки.   -  person eh9    schedule 14.12.2015
comment
@ eh9, насколько я понимаю, переменные __thread должны быть статически инициализированы (действительно, компилятор жалуется, если конструктор constexpr не используется при инициализации переменных __thread для агрегатных типов). Чтобы дать больше информации, фактический вариант использования заключается в том, что определение переменной находится в отдельно скомпилированном двоичном файле и доступно для плагинов (которые видят объявление в файле заголовка, как в вопросе). Кажется, не имеет значения, объявлены ли переменные глобально externs или помещены в пространство имен для класса, и, как я уже говорил ранее, связывание отлично работает с использованием gcc.   -  person Joel Cornett    schedule 14.12.2015
comment
@ eh9 это может быть просто случай, когда LLVM поддерживает __thread менее обширно, чем gcc: / К сожалению, я не могу найти никакой документации, устраняющей это несоответствие.   -  person Joel Cornett    schedule 14.12.2015
comment
@JoelCornett Да, __thread в LLVM не совпадает с __thread в GCC; ни один из них не является стандартным.   -  person eh9    schedule 14.12.2015


Ответы (1)


Формат исполняемого файла Apple (я думаю, MACH-O) не позволяет использовать локальное хранилище Thread. Это боль в заднице. Вы должны создать пространство в распределении памяти библиотеки потоков и скрыть там локальные переменные потока. Это очень очень неясно.

person Careful Now    schedule 18.02.2016
comment
Ссылка, которая должна описать это. software.intel.com/en-us/forums/ intel-c-compiler/topic/498638 - person Careful Now; 19.02.2016