From ce718ff9f42c7da092eaa01dd0242e8d5ba84713 Mon Sep 17 00:00:00 2001 From: Hans Wennborg Date: Sat, 23 Jun 2012 11:37:03 +0000 Subject: [PATCH] Extend the IL for selecting TLS models (PR9788) This allows the user/front-end to specify a model that is better than what LLVM would choose by default. For example, a variable might be declared as @x = thread_local(initialexec) global i32 42 if it will not be used in a shared library that is dlopen'ed. If the specified model isn't supported by the target, or if LLVM can make a better choice, a different model may be used. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@159077 91177308-0d34-0410-b5e6-96231b3b80d8 --- docs/BitCodeFormat.html | 14 +- docs/LangRef.html | 34 +++- include/llvm/GlobalVariable.h | 41 ++++- lib/AsmParser/LLLexer.cpp | 3 + lib/AsmParser/LLParser.cpp | 50 +++++- lib/AsmParser/LLParser.h | 3 + lib/AsmParser/LLToken.h | 3 +- lib/Bitcode/Reader/BitcodeReader.cpp | 19 +- lib/Bitcode/Writer/BitcodeWriter.cpp | 13 +- lib/Linker/LinkModules.cpp | 4 +- lib/Target/CppBackend/CPPBackend.cpp | 25 ++- lib/Target/TargetMachine.cpp | 40 ++++- lib/Transforms/IPO/GlobalOpt.cpp | 14 +- .../Instrumentation/AddressSanitizer.cpp | 2 +- .../Instrumentation/GCOVProfiling.cpp | 2 +- lib/Transforms/Utils/CloneModule.cpp | 2 +- lib/VMCore/AsmWriter.cpp | 22 ++- lib/VMCore/Core.cpp | 2 +- lib/VMCore/Globals.cpp | 51 +++++- lib/VMCore/IRBuilder.cpp | 2 +- test/Assembler/tls-models.ll | 11 ++ test/CodeGen/ARM/tls-models.ll | 117 ++++++++++++ test/CodeGen/Mips/tls-models.ll | 113 ++++++++++++ test/CodeGen/X86/tls-models.ll | 166 ++++++++++++++++++ 24 files changed, 706 insertions(+), 47 deletions(-) create mode 100644 test/Assembler/tls-models.ll create mode 100644 test/CodeGen/ARM/tls-models.ll create mode 100644 test/CodeGen/Mips/tls-models.ll create mode 100644 test/CodeGen/X86/tls-models.ll diff --git a/docs/BitCodeFormat.html b/docs/BitCodeFormat.html index 30145de5811..6a670f56b97 100644 --- a/docs/BitCodeFormat.html +++ b/docs/BitCodeFormat.html @@ -864,7 +864,7 @@ library name referenced.

MODULE_CODE_GLOBALVAR Record

-

[GLOBALVAR, pointer type, isconst, initid, linkage, alignment, section, visibility, threadlocal]

+

[GLOBALVAR, pointer type, isconst, initid, linkage, alignment, section, visibility, threadlocal, unnamed_addr]

The GLOBALVAR record (code 7) marks the declaration or definition of a global variable. The operand fields are:

@@ -915,8 +915,16 @@ encoding of the visibility of this variable: -
  • threadlocal: If present and non-zero, indicates that the variable -is thread_local
  • +
  • threadlocal: If present, an encoding of the thread local storage +mode of the variable: +
      +
    • not thread local: code 0
    • +
    • thread local; default TLS model: code 1
    • +
    • localdynamic: code 2
    • +
    • initialexec: code 3
    • +
    • localexec: code 4
    • +
    +
  • unnamed_addr: If present and non-zero, indicates that the variable has unnamed_addr
  • diff --git a/docs/LangRef.html b/docs/LangRef.html index 9cb7e6392ab..ba653dbd498 100644 --- a/docs/LangRef.html +++ b/docs/LangRef.html @@ -838,9 +838,32 @@ define i32 @main() { ; i32()*  

    Global variables define regions of memory allocated at compilation time instead of run-time. Global variables may optionally be initialized, may have an explicit section to be placed in, and may have an optional explicit - alignment specified. A variable may be defined as "thread_local", which + alignment specified.

    + +

    A variable may be defined as thread_local, which means that it will not be shared by threads (each thread will have a - separated copy of the variable). A variable may be defined as a global + separated copy of the variable). Not all targets support thread-local + variables. Optionally, a TLS model may be specified:

    + +
    +
    localdynamic:
    +
    For variables that are only used within the current shared library.
    + +
    initialexec:
    +
    For variables in modules that will not be loaded dynamically.
    + +
    localexec:
    +
    For variables defined in the executable and only used within it.
    +
    + +

    The models correspond to the ELF TLS models; see + ELF + Handling For Thread-Local Storage for more information on under which + circumstances the different models may be used. The target may choose a + different TLS model if the specified model is not supported, or if a better + choice of model can be made.

    + +

    A variable may be defined as a global "constant," which indicates that the contents of the variable will never be modified (enabling better optimization, allowing the global data to be placed in the read-only section of an executable, etc). @@ -893,6 +916,13 @@ define i32 @main() { ; i32()*   @G = addrspace(5) constant float 1.0, section "foo", align 4 +

    The following example defines a thread-local global with + the initialexec TLS model:

    + +
    +@G = thread_local(initialexec) global i32 0, align 4
    +
    +
    diff --git a/include/llvm/GlobalVariable.h b/include/llvm/GlobalVariable.h index 034ade1fb03..fbb708b26b7 100644 --- a/include/llvm/GlobalVariable.h +++ b/include/llvm/GlobalVariable.h @@ -41,24 +41,47 @@ class GlobalVariable : public GlobalValue, public ilist_node { void setParent(Module *parent); bool isConstantGlobal : 1; // Is this a global constant? - bool isThreadLocalSymbol : 1; // Is this symbol "Thread Local"? + unsigned threadLocalMode : 3; // Is this symbol "Thread Local", + // if so, what is the desired model? public: // allocate space for exactly one operand void *operator new(size_t s) { return User::operator new(s, 1); } - /// GlobalVariable ctor - If a parent module is specified, the global is - /// automatically inserted into the end of the specified modules global list. + + enum ThreadLocalMode { + NotThreadLocal = 0, + GeneralDynamicTLSModel, + LocalDynamicTLSModel, + InitialExecTLSModel, + LocalExecTLSModel + }; + + // TODO: Remove these once Clang is updated. GlobalVariable(Type *Ty, bool isConstant, LinkageTypes Linkage, Constant *Initializer = 0, const Twine &Name = "", bool ThreadLocal = false, unsigned AddressSpace = 0); + GlobalVariable(Module &M, Type *Ty, bool isConstant, + LinkageTypes Linkage, Constant *Initializer, + const Twine &Name = "", + GlobalVariable *InsertBefore = 0, bool ThreadLocal = false, + unsigned AddressSpace = 0); + + /// GlobalVariable ctor - If a parent module is specified, the global is + /// automatically inserted into the end of the specified modules global list. + // TODO: Put default param values back when ctors above are removed. + GlobalVariable(Type *Ty, bool isConstant, LinkageTypes Linkage, + Constant *Initializer, const Twine &Name, + ThreadLocalMode, unsigned AddressSpace = 0); /// GlobalVariable ctor - This creates a global and inserts it before the /// specified other global. + // TODO: Put default param values back when ctors above are removed. GlobalVariable(Module &M, Type *Ty, bool isConstant, LinkageTypes Linkage, Constant *Initializer, const Twine &Name, - GlobalVariable *InsertBefore = 0, bool ThreadLocal = false, + GlobalVariable *InsertBefore, + ThreadLocalMode, unsigned AddressSpace = 0); ~GlobalVariable() { @@ -135,8 +158,14 @@ public: void setConstant(bool Val) { isConstantGlobal = Val; } /// If the value is "Thread Local", its value isn't shared by the threads. - bool isThreadLocal() const { return isThreadLocalSymbol; } - void setThreadLocal(bool Val) { isThreadLocalSymbol = Val; } + bool isThreadLocal() const { return threadLocalMode != NotThreadLocal; } + void setThreadLocal(bool Val) { + threadLocalMode = Val ? GeneralDynamicTLSModel : NotThreadLocal; + } + void setThreadLocalMode(ThreadLocalMode Val) { threadLocalMode = Val; } + ThreadLocalMode getThreadLocalMode() const { + return static_cast(threadLocalMode); + } /// copyAttributesFrom - copy all additional attributes (those not needed to /// create a GlobalVariable) from the GlobalVariable Src to this one. diff --git a/lib/AsmParser/LLLexer.cpp b/lib/AsmParser/LLLexer.cpp index dabcbaa9e83..670c1bbe98e 100644 --- a/lib/AsmParser/LLLexer.cpp +++ b/lib/AsmParser/LLLexer.cpp @@ -474,6 +474,9 @@ lltok::Kind LLLexer::LexIdentifier() { KEYWORD(extern_weak); KEYWORD(external); KEYWORD(thread_local); + KEYWORD(localdynamic); + KEYWORD(initialexec); + KEYWORD(localexec); KEYWORD(zeroinitializer); KEYWORD(undef); KEYWORD(null); diff --git a/lib/AsmParser/LLParser.cpp b/lib/AsmParser/LLParser.cpp index fe415615d80..095b7c5f674 100644 --- a/lib/AsmParser/LLParser.cpp +++ b/lib/AsmParser/LLParser.cpp @@ -645,12 +645,13 @@ bool LLParser::ParseGlobal(const std::string &Name, LocTy NameLoc, unsigned Linkage, bool HasLinkage, unsigned Visibility) { unsigned AddrSpace; - bool ThreadLocal, IsConstant, UnnamedAddr; + bool IsConstant, UnnamedAddr; + GlobalVariable::ThreadLocalMode TLM; LocTy UnnamedAddrLoc; LocTy TyLoc; Type *Ty = 0; - if (ParseOptionalToken(lltok::kw_thread_local, ThreadLocal) || + if (ParseOptionalThreadLocal(TLM) || ParseOptionalAddrSpace(AddrSpace) || ParseOptionalToken(lltok::kw_unnamed_addr, UnnamedAddr, &UnnamedAddrLoc) || @@ -691,7 +692,8 @@ bool LLParser::ParseGlobal(const std::string &Name, LocTy NameLoc, if (GV == 0) { GV = new GlobalVariable(*M, Ty, false, GlobalValue::ExternalLinkage, 0, - Name, 0, false, AddrSpace); + Name, 0, GlobalVariable::NotThreadLocal, + AddrSpace); } else { if (GV->getType()->getElementType() != Ty) return Error(TyLoc, @@ -710,7 +712,7 @@ bool LLParser::ParseGlobal(const std::string &Name, LocTy NameLoc, GV->setConstant(IsConstant); GV->setLinkage((GlobalValue::LinkageTypes)Linkage); GV->setVisibility((GlobalValue::VisibilityTypes)Visibility); - GV->setThreadLocal(ThreadLocal); + GV->setThreadLocalMode(TLM); GV->setUnnamedAddr(UnnamedAddr); // Parse attributes on the global. @@ -858,6 +860,46 @@ bool LLParser::ParseUInt32(unsigned &Val) { return false; } +/// ParseTLSModel +/// := 'localdynamic' +/// := 'initialexec' +/// := 'localexec' +bool LLParser::ParseTLSModel(GlobalVariable::ThreadLocalMode &TLM) { + switch (Lex.getKind()) { + default: + return TokError("expected localdynamic, initialexec or localexec"); + case lltok::kw_localdynamic: + TLM = GlobalVariable::LocalDynamicTLSModel; + break; + case lltok::kw_initialexec: + TLM = GlobalVariable::InitialExecTLSModel; + break; + case lltok::kw_localexec: + TLM = GlobalVariable::LocalExecTLSModel; + break; + } + + Lex.Lex(); + return false; +} + +/// ParseOptionalThreadLocal +/// := /*empty*/ +/// := 'thread_local' +/// := 'thread_local' '(' tlsmodel ')' +bool LLParser::ParseOptionalThreadLocal(GlobalVariable::ThreadLocalMode &TLM) { + TLM = GlobalVariable::NotThreadLocal; + if (!EatIfPresent(lltok::kw_thread_local)) + return false; + + TLM = GlobalVariable::GeneralDynamicTLSModel; + if (Lex.getKind() == lltok::lparen) { + Lex.Lex(); + return ParseTLSModel(TLM) || + ParseToken(lltok::rparen, "expected ')' after thread local model"); + } + return false; +} /// ParseOptionalAddrSpace /// := /*empty*/ diff --git a/lib/AsmParser/LLParser.h b/lib/AsmParser/LLParser.h index dda88083811..257c726229e 100644 --- a/lib/AsmParser/LLParser.h +++ b/lib/AsmParser/LLParser.h @@ -171,6 +171,9 @@ namespace llvm { Loc = Lex.getLoc(); return ParseUInt32(Val); } + + bool ParseTLSModel(GlobalVariable::ThreadLocalMode &TLM); + bool ParseOptionalThreadLocal(GlobalVariable::ThreadLocalMode &TLM); bool ParseOptionalAddrSpace(unsigned &AddrSpace); bool ParseOptionalAttrs(Attributes &Attrs, unsigned AttrKind); bool ParseOptionalLinkage(unsigned &Linkage, bool &HasLinkage); diff --git a/lib/AsmParser/LLToken.h b/lib/AsmParser/LLToken.h index adf5d4f4d0f..0461e7b63af 100644 --- a/lib/AsmParser/LLToken.h +++ b/lib/AsmParser/LLToken.h @@ -44,13 +44,14 @@ namespace lltok { kw_unnamed_addr, kw_extern_weak, kw_external, kw_thread_local, + kw_localdynamic, kw_initialexec, kw_localexec, kw_zeroinitializer, kw_undef, kw_null, kw_to, kw_tail, kw_target, kw_triple, - kw_unwind, + kw_unwind, kw_deplibs, kw_datalayout, kw_volatile, diff --git a/lib/Bitcode/Reader/BitcodeReader.cpp b/lib/Bitcode/Reader/BitcodeReader.cpp index 83756daa7ae..4a20b1c9d6e 100644 --- a/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/lib/Bitcode/Reader/BitcodeReader.cpp @@ -102,6 +102,17 @@ static GlobalValue::VisibilityTypes GetDecodedVisibility(unsigned Val) { } } +static GlobalVariable::ThreadLocalMode GetDecodedThreadLocalMode(unsigned Val) { + switch (Val) { + case 0: return GlobalVariable::NotThreadLocal; + default: // Map unknown non-zero value to general dynamic. + case 1: return GlobalVariable::GeneralDynamicTLSModel; + case 2: return GlobalVariable::LocalDynamicTLSModel; + case 3: return GlobalVariable::InitialExecTLSModel; + case 4: return GlobalVariable::LocalExecTLSModel; + } +} + static int GetDecodedCastOpcode(unsigned Val) { switch (Val) { default: return -1; @@ -1544,9 +1555,10 @@ bool BitcodeReader::ParseModule(bool Resume) { GlobalValue::VisibilityTypes Visibility = GlobalValue::DefaultVisibility; if (Record.size() > 6) Visibility = GetDecodedVisibility(Record[6]); - bool isThreadLocal = false; + + GlobalVariable::ThreadLocalMode TLM = GlobalVariable::NotThreadLocal; if (Record.size() > 7) - isThreadLocal = Record[7]; + TLM = GetDecodedThreadLocalMode(Record[7]); bool UnnamedAddr = false; if (Record.size() > 8) @@ -1554,12 +1566,11 @@ bool BitcodeReader::ParseModule(bool Resume) { GlobalVariable *NewGV = new GlobalVariable(*TheModule, Ty, isConstant, Linkage, 0, "", 0, - isThreadLocal, AddressSpace); + TLM, AddressSpace); NewGV->setAlignment(Alignment); if (!Section.empty()) NewGV->setSection(Section); NewGV->setVisibility(Visibility); - NewGV->setThreadLocal(isThreadLocal); NewGV->setUnnamedAddr(UnnamedAddr); ValueList.push_back(NewGV); diff --git a/lib/Bitcode/Writer/BitcodeWriter.cpp b/lib/Bitcode/Writer/BitcodeWriter.cpp index 333d82ad4a8..5b1725f5508 100644 --- a/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -379,6 +379,17 @@ static unsigned getEncodedVisibility(const GlobalValue *GV) { llvm_unreachable("Invalid visibility"); } +static unsigned getEncodedThreadLocalMode(const GlobalVariable *GV) { + switch (GV->getThreadLocalMode()) { + case GlobalVariable::NotThreadLocal: return 0; + case GlobalVariable::GeneralDynamicTLSModel: return 1; + case GlobalVariable::LocalDynamicTLSModel: return 2; + case GlobalVariable::InitialExecTLSModel: return 3; + case GlobalVariable::LocalExecTLSModel: return 4; + } + llvm_unreachable("Invalid TLS model"); +} + // Emit top-level description of module, including target triple, inline asm, // descriptors for global variables, and function prototype info. static void WriteModuleInfo(const Module *M, const ValueEnumerator &VE, @@ -487,7 +498,7 @@ static void WriteModuleInfo(const Module *M, const ValueEnumerator &VE, GV->getVisibility() != GlobalValue::DefaultVisibility || GV->hasUnnamedAddr()) { Vals.push_back(getEncodedVisibility(GV)); - Vals.push_back(GV->isThreadLocal()); + Vals.push_back(getEncodedThreadLocalMode(GV)); Vals.push_back(GV->hasUnnamedAddr()); } else { AbbrevToUse = SimpleGVarAbbrev; diff --git a/lib/Linker/LinkModules.cpp b/lib/Linker/LinkModules.cpp index 7293f3d0e8a..afba2e8917a 100644 --- a/lib/Linker/LinkModules.cpp +++ b/lib/Linker/LinkModules.cpp @@ -684,7 +684,7 @@ bool ModuleLinker::linkAppendingVarProto(GlobalVariable *DstGV, GlobalVariable *NG = new GlobalVariable(*DstGV->getParent(), NewType, SrcGV->isConstant(), DstGV->getLinkage(), /*init*/0, /*name*/"", DstGV, - DstGV->isThreadLocal(), + DstGV->getThreadLocalMode(), DstGV->getType()->getAddressSpace()); // Propagate alignment, visibility and section info. @@ -759,7 +759,7 @@ bool ModuleLinker::linkGlobalProto(GlobalVariable *SGV) { new GlobalVariable(*DstM, TypeMap.get(SGV->getType()->getElementType()), SGV->isConstant(), SGV->getLinkage(), /*init*/0, SGV->getName(), /*insertbefore*/0, - SGV->isThreadLocal(), + SGV->getThreadLocalMode(), SGV->getType()->getAddressSpace()); // Propagate alignment, visibility and section info. copyGVAttributes(NewDGV, SGV); diff --git a/lib/Target/CppBackend/CPPBackend.cpp b/lib/Target/CppBackend/CPPBackend.cpp index cd2ebcb5087..0ea2a299dd8 100644 --- a/lib/Target/CppBackend/CPPBackend.cpp +++ b/lib/Target/CppBackend/CPPBackend.cpp @@ -130,6 +130,7 @@ namespace { private: void printLinkageType(GlobalValue::LinkageTypes LT); void printVisibilityType(GlobalValue::VisibilityTypes VisTypes); + void printThreadLocalMode(GlobalVariable::ThreadLocalMode TLM); void printCallingConv(CallingConv::ID cc); void printEscapedString(const std::string& str); void printCFP(const ConstantFP* CFP); @@ -325,6 +326,26 @@ void CppWriter::printVisibilityType(GlobalValue::VisibilityTypes VisType) { } } +void CppWriter::printThreadLocalMode(GlobalVariable::ThreadLocalMode TLM) { + switch (TLM) { + case GlobalVariable::NotThreadLocal: + Out << "GlobalVariable::NotThreadLocal"; + break; + case GlobalVariable::GeneralDynamicTLSModel: + Out << "GlobalVariable::GeneralDynamicTLSModel"; + break; + case GlobalVariable::LocalDynamicTLSModel: + Out << "GlobalVariable::LocalDynamicTLSModel"; + break; + case GlobalVariable::InitialExecTLSModel: + Out << "GlobalVariable::InitialExecTLSModel"; + break; + case GlobalVariable::LocalExecTLSModel: + Out << "GlobalVariable::LocalExecTLSModel"; + break; + } +} + // printEscapedString - Print each character of the specified string, escaping // it if it is not printable or if it is an escape char. void CppWriter::printEscapedString(const std::string &Str) { @@ -996,7 +1017,9 @@ void CppWriter::printVariableHead(const GlobalVariable *GV) { } if (GV->isThreadLocal()) { printCppName(GV); - Out << "->setThreadLocal(true);"; + Out << "->setThreadLocalMode("; + printThreadLocalMode(GV->getThreadLocalMode()); + Out << ");"; nl(Out); } if (is_inline) { diff --git a/lib/Target/TargetMachine.cpp b/lib/Target/TargetMachine.cpp index 6cdab5a266a..382571982b9 100644 --- a/lib/Target/TargetMachine.cpp +++ b/lib/Target/TargetMachine.cpp @@ -77,6 +77,24 @@ CodeModel::Model TargetMachine::getCodeModel() const { return CodeGenInfo->getCodeModel(); } +/// Get the IR-specified TLS model for Var. +static TLSModel::Model getSelectedTLSModel(const GlobalVariable *Var) { + switch (Var->getThreadLocalMode()) { + case GlobalVariable::NotThreadLocal: + llvm_unreachable("getSelectedTLSModel for non-TLS variable"); + break; + case GlobalVariable::GeneralDynamicTLSModel: + return TLSModel::GeneralDynamic; + case GlobalVariable::LocalDynamicTLSModel: + return TLSModel::LocalDynamic; + case GlobalVariable::InitialExecTLSModel: + return TLSModel::InitialExec; + case GlobalVariable::LocalExecTLSModel: + return TLSModel::LocalExec; + } + llvm_unreachable("invalid TLS model"); +} + TLSModel::Model TargetMachine::getTLSModel(const GlobalValue *GV) const { // If GV is an alias then use the aliasee for determining // thread-localness. @@ -86,22 +104,31 @@ TLSModel::Model TargetMachine::getTLSModel(const GlobalValue *GV) const { bool isLocal = Var->hasLocalLinkage(); bool isDeclaration = Var->isDeclaration(); + bool isPIC = getRelocationModel() == Reloc::PIC_; + bool isPIE = Options.PositionIndependentExecutable; // FIXME: what should we do for protected and internal visibility? // For variables, is internal different from hidden? bool isHidden = Var->hasHiddenVisibility(); - if (getRelocationModel() == Reloc::PIC_ && - !Options.PositionIndependentExecutable) { + TLSModel::Model Model; + if (isPIC && !isPIE) { if (isLocal || isHidden) - return TLSModel::LocalDynamic; + Model = TLSModel::LocalDynamic; else - return TLSModel::GeneralDynamic; + Model = TLSModel::GeneralDynamic; } else { if (!isDeclaration || isHidden) - return TLSModel::LocalExec; + Model = TLSModel::LocalExec; else - return TLSModel::InitialExec; + Model = TLSModel::InitialExec; } + + // If the user specified a more specific model, use that. + TLSModel::Model SelectedModel = getSelectedTLSModel(Var); + if (SelectedModel > Model) + return SelectedModel; + + return Model; } /// getOptLevel - Returns the optimization level: None, Less, @@ -135,4 +162,3 @@ void TargetMachine::setFunctionSections(bool V) { void TargetMachine::setDataSections(bool V) { DataSections = V; } - diff --git a/lib/Transforms/IPO/GlobalOpt.cpp b/lib/Transforms/IPO/GlobalOpt.cpp index 1668d6bc161..b192d17134e 100644 --- a/lib/Transforms/IPO/GlobalOpt.cpp +++ b/lib/Transforms/IPO/GlobalOpt.cpp @@ -517,7 +517,7 @@ static GlobalVariable *SRAGlobal(GlobalVariable *GV, const TargetData &TD) { GlobalVariable *NGV = new GlobalVariable(STy->getElementType(i), false, GlobalVariable::InternalLinkage, In, GV->getName()+"."+Twine(i), - GV->isThreadLocal(), + GV->getThreadLocalMode(), GV->getType()->getAddressSpace()); Globals.insert(GV, NGV); NewGlobals.push_back(NGV); @@ -550,7 +550,7 @@ static GlobalVariable *SRAGlobal(GlobalVariable *GV, const TargetData &TD) { GlobalVariable *NGV = new GlobalVariable(STy->getElementType(), false, GlobalVariable::InternalLinkage, In, GV->getName()+"."+Twine(i), - GV->isThreadLocal(), + GV->getThreadLocalMode(), GV->getType()->getAddressSpace()); Globals.insert(GV, NGV); NewGlobals.push_back(NGV); @@ -866,7 +866,7 @@ static GlobalVariable *OptimizeGlobalAddressOfMalloc(GlobalVariable *GV, UndefValue::get(GlobalType), GV->getName()+".body", GV, - GV->isThreadLocal()); + GV->getThreadLocalMode()); // If there are bitcast users of the malloc (which is typical, usually we have // a malloc + bitcast) then replace them with uses of the new global. Update @@ -899,7 +899,7 @@ static GlobalVariable *OptimizeGlobalAddressOfMalloc(GlobalVariable *GV, new GlobalVariable(Type::getInt1Ty(GV->getContext()), false, GlobalValue::InternalLinkage, ConstantInt::getFalse(GV->getContext()), - GV->getName()+".init", GV->isThreadLocal()); + GV->getName()+".init", GV->getThreadLocalMode()); bool InitBoolUsed = false; // Loop over all uses of GV, processing them in turn. @@ -1321,7 +1321,7 @@ static GlobalVariable *PerformHeapAllocSRoA(GlobalVariable *GV, CallInst *CI, PFieldTy, false, GlobalValue::InternalLinkage, Constant::getNullValue(PFieldTy), GV->getName() + ".f" + Twine(FieldNo), GV, - GV->isThreadLocal()); + GV->getThreadLocalMode()); FieldGlobals.push_back(NGV); unsigned TypeSize = TD->getTypeAllocSize(FieldTy); @@ -1647,7 +1647,7 @@ static bool TryToShrinkGlobalToBoolean(GlobalVariable *GV, Constant *OtherVal) { GlobalValue::InternalLinkage, ConstantInt::getFalse(GV->getContext()), GV->getName()+".b", - GV->isThreadLocal()); + GV->getThreadLocalMode()); GV->getParent()->getGlobalList().insert(GV, NewGV); Constant *InitVal = GV->getInitializer(); @@ -2054,7 +2054,7 @@ static GlobalVariable *InstallGlobalCtors(GlobalVariable *GCL, // Create the new global and insert it next to the existing list. GlobalVariable *NGV = new GlobalVariable(CA->getType(), GCL->isConstant(), GCL->getLinkage(), CA, "", - GCL->isThreadLocal()); + GCL->getThreadLocalMode()); GCL->getParent()->getGlobalList().insert(GCL, NGV); NGV->takeName(GCL); diff --git a/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/lib/Transforms/Instrumentation/AddressSanitizer.cpp index a9d08db9c60..375a2ae7b0a 100644 --- a/lib/Transforms/Instrumentation/AddressSanitizer.cpp +++ b/lib/Transforms/Instrumentation/AddressSanitizer.cpp @@ -511,7 +511,7 @@ bool AddressSanitizer::insertGlobalRedzones(Module &M) { // Create a new global variable with enough space for a redzone. GlobalVariable *NewGlobal = new GlobalVariable( M, NewTy, G->isConstant(), G->getLinkage(), - NewInitializer, "", G, G->isThreadLocal()); + NewInitializer, "", G, G->getThreadLocalMode()); NewGlobal->copyAttributesFrom(G); NewGlobal->setAlignment(RedzoneSize); diff --git a/lib/Transforms/Instrumentation/GCOVProfiling.cpp b/lib/Transforms/Instrumentation/GCOVProfiling.cpp index 6c42137b3d5..60804794f2c 100644 --- a/lib/Transforms/Instrumentation/GCOVProfiling.cpp +++ b/lib/Transforms/Instrumentation/GCOVProfiling.cpp @@ -448,7 +448,7 @@ bool GCOVProfiler::emitProfileArcs() { new GlobalVariable(*M, CounterTy, false, GlobalValue::InternalLinkage, Constant::getNullValue(CounterTy), - "__llvm_gcov_ctr", 0, false, 0); + "__llvm_gcov_ctr"); CountersBySP.push_back(std::make_pair(Counters, (MDNode*)SP)); UniqueVector ComplexEdgePreds; diff --git a/lib/Transforms/Utils/CloneModule.cpp b/lib/Transforms/Utils/CloneModule.cpp index a0e027b5f12..1dac6b5b8bc 100644 --- a/lib/Transforms/Utils/CloneModule.cpp +++ b/lib/Transforms/Utils/CloneModule.cpp @@ -53,7 +53,7 @@ Module *llvm::CloneModule(const Module *M, ValueToValueMapTy &VMap) { I->isConstant(), I->getLinkage(), (Constant*) 0, I->getName(), (GlobalVariable*) 0, - I->isThreadLocal(), + I->getThreadLocalMode(), I->getType()->getAddressSpace()); GV->copyAttributesFrom(I); VMap[I] = GV; diff --git a/lib/VMCore/AsmWriter.cpp b/lib/VMCore/AsmWriter.cpp index 4a7fde19055..9147b635126 100644 --- a/lib/VMCore/AsmWriter.cpp +++ b/lib/VMCore/AsmWriter.cpp @@ -1376,6 +1376,26 @@ static void PrintVisibility(GlobalValue::VisibilityTypes Vis, } } +static void PrintThreadLocalModel(GlobalVariable::ThreadLocalMode TLM, + formatted_raw_ostream &Out) { + switch (TLM) { + case GlobalVariable::NotThreadLocal: + break; + case GlobalVariable::GeneralDynamicTLSModel: + Out << "thread_local "; + break; + case GlobalVariable::LocalDynamicTLSModel: + Out << "thread_local(localdynamic) "; + break; + case GlobalVariable::InitialExecTLSModel: + Out << "thread_local(initialexec) "; + break; + case GlobalVariable::LocalExecTLSModel: + Out << "thread_local(localexec) "; + break; + } +} + void AssemblyWriter::printGlobal(const GlobalVariable *GV) { if (GV->isMaterializable()) Out << "; Materializable\n"; @@ -1388,8 +1408,8 @@ void AssemblyWriter::printGlobal(const GlobalVariable *GV) { PrintLinkage(GV->getLinkage(), Out); PrintVisibility(GV->getVisibility(), Out); + PrintThreadLocalModel(GV->getThreadLocalMode(), Out); - if (GV->isThreadLocal()) Out << "thread_local "; if (unsigned AddressSpace = GV->getType()->getAddressSpace()) Out << "addrspace(" << AddressSpace << ") "; if (GV->hasUnnamedAddr()) Out << "unnamed_addr "; diff --git a/lib/VMCore/Core.cpp b/lib/VMCore/Core.cpp index 30d8a9b12f8..972db3cb86e 100644 --- a/lib/VMCore/Core.cpp +++ b/lib/VMCore/Core.cpp @@ -1210,7 +1210,7 @@ LLVMValueRef LLVMAddGlobalInAddressSpace(LLVMModuleRef M, LLVMTypeRef Ty, unsigned AddressSpace) { return wrap(new GlobalVariable(*unwrap(M), unwrap(Ty), false, GlobalValue::ExternalLinkage, 0, Name, 0, - false, AddressSpace)); + GlobalVariable::NotThreadLocal, AddressSpace)); } LLVMValueRef LLVMGetNamedGlobal(LLVMModuleRef M, const char *Name) { diff --git a/lib/VMCore/Globals.cpp b/lib/VMCore/Globals.cpp index 4254fb29e88..a34ea7040ee 100644 --- a/lib/VMCore/Globals.cpp +++ b/lib/VMCore/Globals.cpp @@ -80,14 +80,16 @@ bool GlobalValue::isDeclaration() const { // GlobalVariable Implementation //===----------------------------------------------------------------------===// +// TODO: Remove once clang is updated. GlobalVariable::GlobalVariable(Type *Ty, bool constant, LinkageTypes Link, Constant *InitVal, const Twine &Name, bool ThreadLocal, unsigned AddressSpace) - : GlobalValue(PointerType::get(Ty, AddressSpace), + : GlobalValue(PointerType::get(Ty, AddressSpace), Value::GlobalVariableVal, OperandTraits::op_begin(this), InitVal != 0, Link, Name), - isConstantGlobal(constant), isThreadLocalSymbol(ThreadLocal) { + isConstantGlobal(constant), + threadLocalMode(ThreadLocal ? GeneralDynamicTLSModel : NotThreadLocal) { if (InitVal) { assert(InitVal->getType() == Ty && "Initializer should be the same type as the GlobalVariable!"); @@ -97,16 +99,59 @@ GlobalVariable::GlobalVariable(Type *Ty, bool constant, LinkageTypes Link, LeakDetector::addGarbageObject(this); } +// TODO: Remove once clang is updated. GlobalVariable::GlobalVariable(Module &M, Type *Ty, bool constant, LinkageTypes Link, Constant *InitVal, const Twine &Name, GlobalVariable *Before, bool ThreadLocal, unsigned AddressSpace) + : GlobalValue(PointerType::get(Ty, AddressSpace), + Value::GlobalVariableVal, + OperandTraits::op_begin(this), + InitVal != 0, Link, Name), + isConstantGlobal(constant), + threadLocalMode(ThreadLocal ? GeneralDynamicTLSModel : NotThreadLocal) { + if (InitVal) { + assert(InitVal->getType() == Ty && + "Initializer should be the same type as the GlobalVariable!"); + Op<0>() = InitVal; + } + + LeakDetector::addGarbageObject(this); + + if (Before) + Before->getParent()->getGlobalList().insert(Before, this); + else + M.getGlobalList().push_back(this); +} + +GlobalVariable::GlobalVariable(Type *Ty, bool constant, LinkageTypes Link, + Constant *InitVal, const Twine &Name, + ThreadLocalMode TLMode, unsigned AddressSpace) + : GlobalValue(PointerType::get(Ty, AddressSpace), + Value::GlobalVariableVal, + OperandTraits::op_begin(this), + InitVal != 0, Link, Name), + isConstantGlobal(constant), threadLocalMode(TLMode) { + if (InitVal) { + assert(InitVal->getType() == Ty && + "Initializer should be the same type as the GlobalVariable!"); + Op<0>() = InitVal; + } + + LeakDetector::addGarbageObject(this); +} + +GlobalVariable::GlobalVariable(Module &M, Type *Ty, bool constant, + LinkageTypes Link, Constant *InitVal, + const Twine &Name, + GlobalVariable *Before, ThreadLocalMode TLMode, + unsigned AddressSpace) : GlobalValue(PointerType::get(Ty, AddressSpace), Value::GlobalVariableVal, OperandTraits::op_begin(this), InitVal != 0, Link, Name), - isConstantGlobal(constant), isThreadLocalSymbol(ThreadLocal) { + isConstantGlobal(constant), threadLocalMode(TLMode) { if (InitVal) { assert(InitVal->getType() == Ty && "Initializer should be the same type as the GlobalVariable!"); diff --git a/lib/VMCore/IRBuilder.cpp b/lib/VMCore/IRBuilder.cpp index b45923489af..4fc33ff070c 100644 --- a/lib/VMCore/IRBuilder.cpp +++ b/lib/VMCore/IRBuilder.cpp @@ -28,7 +28,7 @@ Value *IRBuilderBase::CreateGlobalString(StringRef Str, const Twine &Name) { Module &M = *BB->getParent()->getParent(); GlobalVariable *GV = new GlobalVariable(M, StrConstant->getType(), true, GlobalValue::PrivateLinkage, - StrConstant, "", 0, false); + StrConstant); GV->setName(Name); GV->setUnnamedAddr(true); return GV; diff --git a/test/Assembler/tls-models.ll b/test/Assembler/tls-models.ll new file mode 100644 index 00000000000..42f24962aed --- /dev/null +++ b/test/Assembler/tls-models.ll @@ -0,0 +1,11 @@ +; RUN: llvm-as < %s | llvm-dis | llvm-as | llvm-dis | FileCheck %s + +; CHECK: @a = thread_local global i32 0 +; CHECK: @b = thread_local(localdynamic) global i32 0 +; CHECK: @c = thread_local(initialexec) global i32 0 +; CHECK: @d = thread_local(localexec) global i32 0 + +@a = thread_local global i32 0 +@b = thread_local(localdynamic) global i32 0 +@c = thread_local(initialexec) global i32 0 +@d = thread_local(localexec) global i32 0 diff --git a/test/CodeGen/ARM/tls-models.ll b/test/CodeGen/ARM/tls-models.ll new file mode 100644 index 00000000000..a5f3c9005af --- /dev/null +++ b/test/CodeGen/ARM/tls-models.ll @@ -0,0 +1,117 @@ +; RUN: llc -march=arm -mtriple=arm-linux-gnueabi < %s | FileCheck -check-prefix=CHECK-NONPIC %s +; RUN: llc -march=arm -mtriple=arm-linux-gnueabi -relocation-model=pic < %s | FileCheck -check-prefix=CHECK-PIC %s + + +@external_gd = external thread_local global i32 +@internal_gd = internal thread_local global i32 42 + +@external_ld = external thread_local(localdynamic) global i32 +@internal_ld = internal thread_local(localdynamic) global i32 42 + +@external_ie = external thread_local(initialexec) global i32 +@internal_ie = internal thread_local(initialexec) global i32 42 + +@external_le = external thread_local(localexec) global i32 +@internal_le = internal thread_local(localexec) global i32 42 + +; ----- no model specified ----- + +define i32* @f1() { +entry: + ret i32* @external_gd + + ; Non-PIC code can use initial-exec, PIC code has to use general dynamic. + ; CHECK-NONPIC: f1: + ; CHECK-NONPIC: external_gd(gottpoff) + ; CHECK-PIC: f1: + ; CHECK-PIC: external_gd(tlsgd) +} + +define i32* @f2() { +entry: + ret i32* @internal_gd + + ; Non-PIC code can use local exec, PIC code can use local dynamic, + ; but that is not implemented, so falls back to general dynamic. + ; CHECK-NONPIC: f2: + ; CHECK-NONPIC: internal_gd(tpoff) + ; CHECK-PIC: f2: + ; CHECK-PIC: internal_gd(tlsgd) +} + + +; ----- localdynamic specified ----- + +define i32* @f3() { +entry: + ret i32* @external_ld + + ; Non-PIC code can use initial exec, PIC should use local dynamic, + ; but that is not implemented, so falls back to general dynamic. + ; CHECK-NONPIC: f3: + ; CHECK-NONPIC: external_ld(gottpoff) + ; CHECK-PIC: f3: + ; CHECK-PIC: external_ld(tlsgd) +} + +define i32* @f4() { +entry: + ret i32* @internal_ld + + ; Non-PIC code can use local exec, PIC code can use local dynamic, + ; but that is not implemented, so it falls back to general dynamic. + ; CHECK-NONPIC: f4: + ; CHECK-NONPIC: internal_ld(tpoff) + ; CHECK-PIC: f4: + ; CHECK-PIC: internal_ld(tlsgd) +} + + +; ----- initialexec specified ----- + +define i32* @f5() { +entry: + ret i32* @external_ie + + ; Non-PIC and PIC code will use initial exec as specified. + ; CHECK-NONPIC: f5: + ; CHECK-NONPIC: external_ie(gottpoff) + ; CHECK-PIC: f5: + ; CHECK-PIC: external_ie(gottpoff) +} + +define i32* @f6() { +entry: + ret i32* @internal_ie + + ; Non-PIC code can use local exec, PIC code use initial exec as specified. + ; CHECK-NONPIC: f6: + ; CHECK-NONPIC: internal_ie(tpoff) + ; CHECK-PIC: f6: + ; CHECK-PIC: internal_ie(gottpoff) +} + + +; ----- localexec specified ----- + +define i32* @f7() { +entry: + ret i32* @external_le + + ; Non-PIC and PIC code will use local exec as specified. + ; CHECK-NONPIC: f7: + ; CHECK-NONPIC: external_le(tpoff) + ; CHECK-PIC: f7: + ; CHECK-PIC: external_le(tpoff) +} + +define i32* @f8() { +entry: + ret i32* @internal_le + + ; Non-PIC and PIC code will use local exec as specified. + ; CHECK-NONPIC: f8: + ; CHECK-NONPIC: internal_le(tpoff) + ; CHECK-PIC: f8: + ; CHECK-PIC: internal_le(tpoff) +} diff --git a/test/CodeGen/Mips/tls-models.ll b/test/CodeGen/Mips/tls-models.ll new file mode 100644 index 00000000000..8f5789ec799 --- /dev/null +++ b/test/CodeGen/Mips/tls-models.ll @@ -0,0 +1,113 @@ +; RUN: llc -march=mipsel < %s | FileCheck -check-prefix=CHECK-PIC %s +; RUN: llc -march=mipsel -relocation-model=static < %s | FileCheck -check-prefix=CHECK-NONPIC %s + +@external_gd = external thread_local global i32 +@internal_gd = internal thread_local global i32 42 + +@external_ld = external thread_local(localdynamic) global i32 +@internal_ld = internal thread_local(localdynamic) global i32 42 + +@external_ie = external thread_local(initialexec) global i32 +@internal_ie = internal thread_local(initialexec) global i32 42 + +@external_le = external thread_local(localexec) global i32 +@internal_le = internal thread_local(localexec) global i32 42 + +; ----- no model specified ----- + +define i32* @f1() { +entry: + ret i32* @external_gd + + ; Non-PIC code can use initial-exec, PIC code has to use general dynamic. + ; CHECK-NONPIC: f1: + ; CHECK-NONPIC: %gottprel + ; CHECK-PIC: f1: + ; CHECK-PIC: %tlsgd +} + +define i32* @f2() { +entry: + ret i32* @internal_gd + + ; Non-PIC code can use local exec, PIC code can use local dynamic. + ; CHECK-NONPIC: f2: + ; CHECK-NONPIC: %tprel_hi + ; CHECK-PIC: f2: + ; CHECK-PIC: %tlsldm +} + + +; ----- localdynamic specified ----- + +define i32* @f3() { +entry: + ret i32* @external_ld + + ; Non-PIC code can use initial exec, PIC should use local dynamic. + ; CHECK-NONPIC: f3: + ; CHECK-NONPIC: %gottprel + ; CHECK-PIC: f3: + ; CHECK-PIC: %tlsldm +} + +define i32* @f4() { +entry: + ret i32* @internal_ld + + ; Non-PIC code can use local exec, PIC code can use local dynamic. + ; CHECK-NONPIC: f4: + ; CHECK-NONPIC: %tprel_hi + ; CHECK-PIC: f4: + ; CHECK-PIC: %tlsldm +} + + +; ----- initialexec specified ----- + +define i32* @f5() { +entry: + ret i32* @external_ie + + ; Non-PIC and PIC code will use initial exec as specified. + ; CHECK-NONPIC: f5: + ; CHECK-NONPIC: %gottprel + ; CHECK-PIC: f5: + ; CHECK-PIC: %gottprel +} + +define i32* @f6() { +entry: + ret i32* @internal_ie + + ; Non-PIC code can use local exec, PIC code use initial exec as specified. + ; CHECK-NONPIC: f6: + ; CHECK-NONPIC: %tprel_hi + ; CHECK-PIC: f6: + ; CHECK-PIC: %gottprel +} + + +; ----- localexec specified ----- + +define i32* @f7() { +entry: + ret i32* @external_le + + ; Non-PIC and PIC code will use local exec as specified. + ; CHECK-NONPIC: f7: + ; CHECK-NONPIC: %tprel_hi + ; CHECK-PIC: f7: + ; CHECK-PIC: %tprel_hi +} + +define i32* @f8() { +entry: + ret i32* @internal_le + + ; Non-PIC and PIC code will use local exec as specified. + ; CHECK-NONPIC: f8: + ; CHECK-NONPIC: %tprel_hi + ; CHECK-PIC: f8: + ; CHECK-PIC: %tprel_hi +} diff --git a/test/CodeGen/X86/tls-models.ll b/test/CodeGen/X86/tls-models.ll new file mode 100644 index 00000000000..7c527e210a9 --- /dev/null +++ b/test/CodeGen/X86/tls-models.ll @@ -0,0 +1,166 @@ +; RUN: llc < %s -march=x86-64 -mtriple=x86_64-linux-gnu | FileCheck -check-prefix=X64 %s +; RUN: llc < %s -march=x86-64 -mtriple=x86_64-linux-gnu -relocation-model=pic | FileCheck -check-prefix=X64_PIC %s +; RUN: llc < %s -march=x86 -mtriple=i386-linux-gnu | FileCheck -check-prefix=X32 %s +; RUN: llc < %s -march=x86 -mtriple=i386-linux-gnu -relocation-model=pic | FileCheck -check-prefix=X32_PIC %s + +; Darwin always uses the same model. +; RUN: llc < %s -march=x86-64 -mtriple=x86_64-apple-darwin | FileCheck -check-prefix=DARWIN %s + +@external_gd = external thread_local global i32 +@internal_gd = internal thread_local global i32 42 + +@external_ld = external thread_local(localdynamic) global i32 +@internal_ld = internal thread_local(localdynamic) global i32 42 + +@external_ie = external thread_local(initialexec) global i32 +@internal_ie = internal thread_local(initialexec) global i32 42 + +@external_le = external thread_local(localexec) global i32 +@internal_le = internal thread_local(localexec) global i32 42 + +; ----- no model specified ----- + +define i32* @f1() { +entry: + ret i32* @external_gd + + ; Non-PIC code can use initial-exec, PIC code has to use general dynamic. + ; X64: f1: + ; X64: external_gd@GOTTPOFF + ; X32: f1: + ; X32: external_gd@INDNTPOFF + ; X64_PIC: f1: + ; X64_PIC: external_gd@TLSGD + ; X32_PIC: f1: + ; X32_PIC: external_gd@TLSGD + ; DARWIN: f1: + ; DARWIN: _external_gd@TLVP +} + +define i32* @f2() { +entry: + ret i32* @internal_gd + + ; Non-PIC code can use local exec, PIC code can use local dynamic. + ; X64: f2: + ; X64: internal_gd@TPOFF + ; X32: f2: + ; X32: internal_gd@NTPOFF + ; X64_PIC: f2: + ; X64_PIC: internal_gd@TLSLD + ; X32_PIC: f2: + ; X32_PIC: internal_gd@TLSLDM + ; DARWIN: f2: + ; DARWIN: _internal_gd@TLVP +} + + +; ----- localdynamic specified ----- + +define i32* @f3() { +entry: + ret i32* @external_ld + + ; Non-PIC code can use initial exec, PIC code use local dynamic as specified. + ; X64: f3: + ; X64: external_ld@GOTTPOFF + ; X32: f3: + ; X32: external_ld@INDNTPOFF + ; X64_PIC: f3: + ; X64_PIC: external_ld@TLSLD + ; X32_PIC: f3: + ; X32_PIC: external_ld@TLSLDM + ; DARWIN: f3: + ; DARWIN: _external_ld@TLVP +} + +define i32* @f4() { +entry: + ret i32* @internal_ld + + ; Non-PIC code can use local exec, PIC code can use local dynamic. + ; X64: f4: + ; X64: internal_ld@TPOFF + ; X32: f4: + ; X32: internal_ld@NTPOFF + ; X64_PIC: f4: + ; X64_PIC: internal_ld@TLSLD + ; X32_PIC: f4: + ; X32_PIC: internal_ld@TLSLDM + ; DARWIN: f4: + ; DARWIN: _internal_ld@TLVP +} + + +; ----- initialexec specified ----- + +define i32* @f5() { +entry: + ret i32* @external_ie + + ; Non-PIC and PIC code will use initial exec as specified. + ; X64: f5: + ; X64: external_ie@GOTTPOFF + ; X32: f5: + ; X32: external_ie@INDNTPOFF + ; X64_PIC: f5: + ; X64_PIC: external_ie@GOTTPOFF + ; X32_PIC: f5: + ; X32_PIC: external_ie@GOTNTPOFF + ; DARWIN: f5: + ; DARWIN: _external_ie@TLVP +} + +define i32* @f6() { +entry: + ret i32* @internal_ie + + ; Non-PIC code can use local exec, PIC code use initial exec as specified. + ; X64: f6: + ; X64: internal_ie@TPOFF + ; X32: f6: + ; X32: internal_ie@NTPOFF + ; X64_PIC: f6: + ; X64_PIC: internal_ie@GOTTPOFF + ; X32_PIC: f6: + ; X32_PIC: internal_ie@GOTNTPOFF + ; DARWIN: f6: + ; DARWIN: _internal_ie@TLVP +} + + +; ----- localexec specified ----- + +define i32* @f7() { +entry: + ret i32* @external_le + + ; Non-PIC and PIC code will use local exec as specified. + ; X64: f7: + ; X64: external_le@TPOFF + ; X32: f7: + ; X32: external_le@NTPOFF + ; X64_PIC: f7: + ; X64_PIC: external_le@TPOFF + ; X32_PIC: f7: + ; X32_PIC: external_le@NTPOFF + ; DARWIN: f7: + ; DARWIN: _external_le@TLVP +} + +define i32* @f8() { +entry: + ret i32* @internal_le + + ; Non-PIC and PIC code will use local exec as specified. + ; X64: f8: + ; X64: internal_le@TPOFF + ; X32: f8: + ; X32: internal_le@NTPOFF + ; X64_PIC: f8: + ; X64_PIC: internal_le@TPOFF + ; X32_PIC: f8: + ; X32_PIC: internal_le@NTPOFF + ; DARWIN: f8: + ; DARWIN: _internal_le@TLVP +} -- 2.34.1