VK_PPC_GAS_LO16, // symbol@l
VK_PPC_TPREL16_HA, // symbol@tprel@ha
VK_PPC_TPREL16_LO, // symbol@tprel@l
+ VK_PPC_DTPREL16_HA, // symbol@dtprel@ha
+ VK_PPC_DTPREL16_LO, // symbol@dtprel@l
VK_PPC_TOC16_HA, // symbol@toc@ha
VK_PPC_TOC16_LO, // symbol@toc@l
VK_PPC_GOT_TPREL16_DS, // symbol@got@tprel
VK_PPC_GOT_TLSGD16_HA, // symbol@got@tlsgd@ha
VK_PPC_GOT_TLSGD16_LO, // symbol@got@tlsgd@l
VK_PPC_TLSGD, // symbol@tlsgd
+ VK_PPC_GOT_TLSLD16_HA, // symbol@got@tlsld@ha
+ VK_PPC_GOT_TLSLD16_LO, // symbol@got@tlsld@l
+ VK_PPC_TLSLD, // symbol@tlsld
VK_Mips_GPREL,
VK_Mips_GOT_CALL,
R_PPC64_TOC16_DS = 63,
R_PPC64_TOC16_LO_DS = 64,
R_PPC64_TLS = 67,
+ R_PPC64_DTPREL16_LO = 75,
+ R_PPC64_DTPREL16_HA = 77,
R_PPC64_GOT_TLSGD16_LO = 80,
R_PPC64_GOT_TLSGD16_HA = 82,
+ R_PPC64_GOT_TLSLD16_LO = 84,
+ R_PPC64_GOT_TLSLD16_HA = 86,
R_PPC64_GOT_TPREL16_DS = 87,
- R_PPC64_TLSGD = 107
+ R_PPC64_TLSGD = 107,
+ R_PPC64_TLSLD = 108
};
// ARM Specific e_flags
case VK_PPC_GAS_LO16: return "l";
case VK_PPC_TPREL16_HA: return "tprel@ha";
case VK_PPC_TPREL16_LO: return "tprel@l";
+ case VK_PPC_DTPREL16_HA: return "dtprel@ha";
+ case VK_PPC_DTPREL16_LO: return "dtprel@l";
case VK_PPC_TOC16_HA: return "toc@ha";
case VK_PPC_TOC16_LO: return "toc@l";
case VK_PPC_GOT_TPREL16_DS: return "got@tprel";
case VK_PPC_TLS: return "tls";
case VK_PPC_GOT_TLSGD16_HA: return "got@tlsgd@ha";
case VK_PPC_GOT_TLSGD16_LO: return "got@tlsgd@l";
+ case VK_PPC_GOT_TLSLD16_HA: return "got@tlsld@ha";
+ case VK_PPC_GOT_TLSLD16_LO: return "got@tlsld@l";
case VK_PPC_TLSGD: return "tlsgd";
+ case VK_PPC_TLSLD: return "tlsld";
case VK_Mips_GPREL: return "GPREL";
case VK_Mips_GOT_CALL: return "GOT_CALL";
case VK_Mips_GOT16: return "GOT16";
case FK_Data_8:
case PPC::fixup_ppc_toc:
case PPC::fixup_ppc_tlsreg:
- case PPC::fixup_ppc_tlsgd:
+ case PPC::fixup_ppc_nofixup:
return Value;
case PPC::fixup_ppc_lo14:
case PPC::fixup_ppc_toc16_ds:
{ "fixup_ppc_toc16", 16, 16, 0 },
{ "fixup_ppc_toc16_ds", 16, 14, 0 },
{ "fixup_ppc_tlsreg", 0, 0, 0 },
- { "fixup_ppc_tlsgd", 0, 0, 0 }
+ { "fixup_ppc_nofixup", 0, 0, 0 }
};
if (Kind < FirstTargetFixupKind)
case MCSymbolRefExpr::VK_PPC_TPREL16_HA:
Type = ELF::R_PPC_TPREL16_HA;
break;
+ case MCSymbolRefExpr::VK_PPC_DTPREL16_HA:
+ Type = ELF::R_PPC64_DTPREL16_HA;
+ break;
case MCSymbolRefExpr::VK_None:
Type = ELF::R_PPC_ADDR16_HA;
break;
case MCSymbolRefExpr::VK_PPC_GOT_TLSGD16_HA:
Type = ELF::R_PPC64_GOT_TLSGD16_HA;
break;
+ case MCSymbolRefExpr::VK_PPC_GOT_TLSLD16_HA:
+ Type = ELF::R_PPC64_GOT_TLSLD16_HA;
+ break;
}
break;
case PPC::fixup_ppc_lo16:
case MCSymbolRefExpr::VK_PPC_TPREL16_LO:
Type = ELF::R_PPC_TPREL16_LO;
break;
+ case MCSymbolRefExpr::VK_PPC_DTPREL16_LO:
+ Type = ELF::R_PPC64_DTPREL16_LO;
+ break;
case MCSymbolRefExpr::VK_None:
Type = ELF::R_PPC_ADDR16_LO;
break;
case MCSymbolRefExpr::VK_PPC_GOT_TLSGD16_LO:
Type = ELF::R_PPC64_GOT_TLSGD16_LO;
break;
+ case MCSymbolRefExpr::VK_PPC_GOT_TLSLD16_LO:
+ Type = ELF::R_PPC64_GOT_TLSLD16_LO;
+ break;
}
break;
case PPC::fixup_ppc_lo14:
case PPC::fixup_ppc_tlsreg:
Type = ELF::R_PPC64_TLS;
break;
- case PPC::fixup_ppc_tlsgd:
- Type = ELF::R_PPC64_TLSGD;
+ case PPC::fixup_ppc_nofixup:
+ switch (Modifier) {
+ default: llvm_unreachable("Unsupported Modifier");
+ case MCSymbolRefExpr::VK_PPC_TLSGD:
+ Type = ELF::R_PPC64_TLSGD;
+ break;
+ case MCSymbolRefExpr::VK_PPC_TLSLD:
+ Type = ELF::R_PPC64_TLSLD;
+ break;
+ }
break;
case FK_Data_8:
switch (Modifier) {
/// fixup_ppc_tlsreg - Insert thread-pointer register number.
fixup_ppc_tlsreg,
- /// fixup_ppc_tlsgd - Not a true fixup, but ties a symbol to a call
- /// to __tls_get_addr for the TLS global dynamic model.
- fixup_ppc_tlsgd,
+ /// fixup_ppc_nofixup - Not a true fixup, but ties a symbol to a call
+ /// to __tls_get_addr for the TLS general and local dynamic models.
+ fixup_ppc_nofixup,
// Marker
LastTargetFixupKind,
SmallVectorImpl<MCFixup> &Fixups) const {
uint64_t Bits = getBinaryCodeForInstr(MI, Fixups);
- // BL8_NOPELF and BLA8_NOP_ELF is both size of 8 bacause of the
+ // BL8_NOP_ELF, BLA8_NOP_ELF, etc., all have a size of 8 because of the
// following 'nop'.
unsigned Size = 4; // FIXME: Have Desc.getSize() return the correct value!
unsigned Opcode = MI.getOpcode();
- if (Opcode == PPC::BL8_NOP_ELF || Opcode == PPC::BLA8_NOP_ELF)
+ if (Opcode == PPC::BL8_NOP_ELF || Opcode == PPC::BLA8_NOP_ELF ||
+ Opcode == PPC::BL8_NOP_ELF_TLSGD || Opcode == PPC::BL8_NOP_ELF_TLSLD)
Size = 8;
// Output the constant in big endian byte order.
(MCFixupKind)PPC::fixup_ppc_br24));
// For special TLS calls, add another fixup for the symbol. Apparently
- // BL8_NOP_ELF and BL8_NOP_ELF_TLSGD are sufficiently similar that TblGen
- // will not generate a separate case for the latter, so this is the only
- // way to get the extra fixup generated.
- if (MI.getOpcode() == PPC::BL8_NOP_ELF_TLSGD) {
+ // BL8_NOP_ELF, BL8_NOP_ELF_TLSGD, and BL8_NOP_ELF_TLSLD are sufficiently
+ // similar that TblGen will not generate a separate case for the latter
+ // two, so this is the only way to get the extra fixup generated.
+ unsigned Opcode = MI.getOpcode();
+ if (Opcode == PPC::BL8_NOP_ELF_TLSGD || Opcode == PPC::BL8_NOP_ELF_TLSLD) {
const MCOperand &MO2 = MI.getOperand(OpNo+1);
Fixups.push_back(MCFixup::Create(0, MO2.getExpr(),
- (MCFixupKind)PPC::fixup_ppc_tlsgd));
+ (MCFixupKind)PPC::fixup_ppc_nofixup));
}
return 0;
}
.addExpr(SymVar));
return;
}
+ case PPC::ADDIStlsldHA: {
+ // Transform: %Xd = ADDIStlsldHA %X2, <ga:@sym>
+ // Into: %Xd = ADDIS8 %X2, sym@got@tlsld@ha
+ assert(Subtarget.isPPC64() && "Not supported for 32-bit PowerPC");
+ const MachineOperand &MO = MI->getOperand(2);
+ const GlobalValue *GValue = MO.getGlobal();
+ MCSymbol *MOSymbol = Mang->getSymbol(GValue);
+ const MCExpr *SymGotTlsLD =
+ MCSymbolRefExpr::Create(MOSymbol, MCSymbolRefExpr::VK_PPC_GOT_TLSLD16_HA,
+ OutContext);
+ OutStreamer.EmitInstruction(MCInstBuilder(PPC::ADDIS8)
+ .addReg(MI->getOperand(0).getReg())
+ .addReg(PPC::X2)
+ .addExpr(SymGotTlsLD));
+ return;
+ }
+ case PPC::ADDItlsldL: {
+ // Transform: %Xd = ADDItlsldL %Xs, <ga:@sym>
+ // Into: %Xd = ADDI8L %Xs, sym@got@tlsld@l
+ assert(Subtarget.isPPC64() && "Not supported for 32-bit PowerPC");
+ const MachineOperand &MO = MI->getOperand(2);
+ const GlobalValue *GValue = MO.getGlobal();
+ MCSymbol *MOSymbol = Mang->getSymbol(GValue);
+ const MCExpr *SymGotTlsLD =
+ MCSymbolRefExpr::Create(MOSymbol, MCSymbolRefExpr::VK_PPC_GOT_TLSLD16_LO,
+ OutContext);
+ OutStreamer.EmitInstruction(MCInstBuilder(PPC::ADDI8L)
+ .addReg(MI->getOperand(0).getReg())
+ .addReg(MI->getOperand(1).getReg())
+ .addExpr(SymGotTlsLD));
+ return;
+ }
+ case PPC::GETtlsldADDR: {
+ // Transform: %X3 = GETtlsldADDR %X3, <ga:@sym>
+ // Into: BL8_NOP_ELF_TLSLD __tls_get_addr(sym@tlsld)
+ assert(Subtarget.isPPC64() && "Not supported for 32-bit PowerPC");
+
+ StringRef Name = "__tls_get_addr";
+ MCSymbol *TlsGetAddr = OutContext.GetOrCreateSymbol(Name);
+ const MCSymbolRefExpr *TlsRef =
+ MCSymbolRefExpr::Create(TlsGetAddr, MCSymbolRefExpr::VK_None, OutContext);
+ const MachineOperand &MO = MI->getOperand(2);
+ const GlobalValue *GValue = MO.getGlobal();
+ MCSymbol *MOSymbol = Mang->getSymbol(GValue);
+ const MCExpr *SymVar =
+ MCSymbolRefExpr::Create(MOSymbol, MCSymbolRefExpr::VK_PPC_TLSLD,
+ OutContext);
+ OutStreamer.EmitInstruction(MCInstBuilder(PPC::BL8_NOP_ELF_TLSLD)
+ .addExpr(TlsRef)
+ .addExpr(SymVar));
+ return;
+ }
+ case PPC::ADDISdtprelHA: {
+ // Transform: %Xd = ADDISdtprelHA %X3, <ga:@sym>
+ // Into: %Xd = ADDIS8 %X3, sym@dtprel@ha
+ assert(Subtarget.isPPC64() && "Not supported for 32-bit PowerPC");
+ const MachineOperand &MO = MI->getOperand(2);
+ const GlobalValue *GValue = MO.getGlobal();
+ MCSymbol *MOSymbol = Mang->getSymbol(GValue);
+ const MCExpr *SymDtprel =
+ MCSymbolRefExpr::Create(MOSymbol, MCSymbolRefExpr::VK_PPC_DTPREL16_HA,
+ OutContext);
+ OutStreamer.EmitInstruction(MCInstBuilder(PPC::ADDIS8)
+ .addReg(MI->getOperand(0).getReg())
+ .addReg(PPC::X3)
+ .addExpr(SymDtprel));
+ return;
+ }
+ case PPC::ADDIdtprelL: {
+ // Transform: %Xd = ADDIdtprelL %Xs, <ga:@sym>
+ // Into: %Xd = ADDI8L %Xs, sym@dtprel@l
+ assert(Subtarget.isPPC64() && "Not supported for 32-bit PowerPC");
+ const MachineOperand &MO = MI->getOperand(2);
+ const GlobalValue *GValue = MO.getGlobal();
+ MCSymbol *MOSymbol = Mang->getSymbol(GValue);
+ const MCExpr *SymDtprel =
+ MCSymbolRefExpr::Create(MOSymbol, MCSymbolRefExpr::VK_PPC_DTPREL16_LO,
+ OutContext);
+ OutStreamer.EmitInstruction(MCInstBuilder(PPC::ADDI8L)
+ .addReg(MI->getOperand(0).getReg())
+ .addReg(MI->getOperand(1).getReg())
+ .addExpr(SymDtprel));
+ return;
+ }
case PPC::MFCRpseud:
case PPC::MFCR8pseud:
// Transform: %R3 = MFCRpseud %CR7
return CurDAG->getMachineNode(PPC::GETtlsADDR, dl, MVT::i64,
N->getOperand(0), N->getOperand(1));
}
+ case PPCISD::ADDIS_TLSLD_HA: {
+ assert (PPCSubTarget.isPPC64() && "Only supported for 64-bit ABI");
+ return CurDAG->getMachineNode(PPC::ADDIStlsldHA, dl, MVT::i64,
+ N->getOperand(0), N->getOperand(1));
+ }
+ case PPCISD::ADDI_TLSLD_L: {
+ assert (PPCSubTarget.isPPC64() && "Only supported for 64-bit ABI");
+ return CurDAG->getMachineNode(PPC::ADDItlsldL, dl, MVT::i64,
+ N->getOperand(0), N->getOperand(1));
+ }
+ case PPCISD::GET_TLSLD_ADDR: {
+ assert (PPCSubTarget.isPPC64() && "Only supported for 64-bit ABI");
+ return CurDAG->getMachineNode(PPC::GETtlsldADDR, dl, MVT::i64,
+ N->getOperand(0), N->getOperand(1));
+ }
+ case PPCISD::ADDIS_DTPREL_HA: {
+ assert (PPCSubTarget.isPPC64() && "Only supported for 64-bit ABI");
+ return CurDAG->getMachineNode(PPC::ADDISdtprelHA, dl, MVT::i64,
+ N->getOperand(0), N->getOperand(1),
+ N->getOperand(2));
+ }
+ case PPCISD::ADDI_DTPREL_L: {
+ assert (PPCSubTarget.isPPC64() && "Only supported for 64-bit ABI");
+ return CurDAG->getMachineNode(PPC::ADDIdtprelL, dl, MVT::i64,
+ N->getOperand(0), N->getOperand(1));
+ }
}
return SelectCode(N);
case PPCISD::ADDIS_TLSGD_HA: return "PPCISD::ADDIS_TLSGD_HA";
case PPCISD::ADDI_TLSGD_L: return "PPCISD::ADDI_TLSGD_L";
case PPCISD::GET_TLS_ADDR: return "PPCISD::GET_TLS_ADDR";
+ case PPCISD::ADDIS_TLSLD_HA: return "PPCISD::ADDIS_TLSLD_HA";
+ case PPCISD::ADDI_TLSLD_L: return "PPCISD::ADDI_TLSLD_L";
+ case PPCISD::GET_TLSLD_ADDR: return "PPCISD::GET_TLSLD_ADDR";
+ case PPCISD::ADDIS_DTPREL_HA: return "PPCISD::ADDIS_DTPREL_HA";
+ case PPCISD::ADDI_DTPREL_L: return "PPCISD::ADDI_DTPREL_L";
}
}
SDValue ParmReg = DAG.getRegister(PPC::X3, MVT::i64);
SDValue TLSAddr = DAG.getNode(PPCISD::GET_TLS_ADDR, dl,
PtrVT, ParmReg, TGA);
- // The call to GET_TLS_ADDR really is in X3 already, but
- // some hacks are needed here to tie everything together.
- // The extra copies dissolve during subsequent transforms.
+ // The return value from GET_TLS_ADDR really is in X3 already, but
+ // some hacks are needed here to tie everything together. The extra
+ // copies dissolve during subsequent transforms.
Chain = DAG.getCopyToReg(Chain, dl, PPC::X3, TLSAddr);
return DAG.getCopyFromReg(Chain, dl, PPC::X3, PtrVT);
}
- llvm_unreachable("local-dynamic TLS mode is not yet supported");
+ if (Model == TLSModel::LocalDynamic) {
+ SDValue TGA = DAG.getTargetGlobalAddress(GV, dl, PtrVT, 0, 0);
+ SDValue GOTReg = DAG.getRegister(PPC::X2, MVT::i64);
+ SDValue GOTEntryHi = DAG.getNode(PPCISD::ADDIS_TLSLD_HA, dl, PtrVT,
+ GOTReg, TGA);
+ SDValue GOTEntry = DAG.getNode(PPCISD::ADDI_TLSLD_L, dl, PtrVT,
+ GOTEntryHi, TGA);
+
+ // We need a chain node, and don't have one handy. The underlying
+ // call has no side effects, so using the function entry node
+ // suffices.
+ SDValue Chain = DAG.getEntryNode();
+ Chain = DAG.getCopyToReg(Chain, dl, PPC::X3, GOTEntry);
+ SDValue ParmReg = DAG.getRegister(PPC::X3, MVT::i64);
+ SDValue TLSAddr = DAG.getNode(PPCISD::GET_TLSLD_ADDR, dl,
+ PtrVT, ParmReg, TGA);
+ // The return value from GET_TLSLD_ADDR really is in X3 already, but
+ // some hacks are needed here to tie everything together. The extra
+ // copies dissolve during subsequent transforms.
+ Chain = DAG.getCopyToReg(Chain, dl, PPC::X3, TLSAddr);
+ SDValue DtvOffsetHi = DAG.getNode(PPCISD::ADDIS_DTPREL_HA, dl, PtrVT,
+ ParmReg, TGA, Chain);
+ return DAG.getNode(PPCISD::ADDI_DTPREL_L, dl, PtrVT, DtvOffsetHi, TGA);
+ }
+
+ llvm_unreachable("Unknown TLS model!");
}
SDValue PPCTargetLowering::LowerGlobalAddress(SDValue Op,
/// model, produces a call to __tls_get_addr(sym@tlsgd).
GET_TLS_ADDR,
+ /// G8RC = ADDIS_TLSLD_HA %X2, Symbol - For the local-dynamic TLS
+ /// model, produces an ADDIS8 instruction that adds the GOT base
+ /// register to sym@got@tlsld@ha.
+ ADDIS_TLSLD_HA,
+
+ /// G8RC = ADDI_TLSLD_L G8RReg, Symbol - For the local-dynamic TLS
+ /// model, produces an ADDI8 instruction that adds G8RReg to
+ /// sym@got@tlsld@l.
+ ADDI_TLSLD_L,
+
+ /// G8RC = GET_TLSLD_ADDR %X3, Symbol - For the local-dynamic TLS
+ /// model, produces a call to __tls_get_addr(sym@tlsld).
+ GET_TLSLD_ADDR,
+
+ /// G8RC = ADDIS_DTPREL_HA %X3, Symbol, Chain - For the
+ /// local-dynamic TLS model, produces an ADDIS8 instruction
+ /// that adds X3 to sym@dtprel@ha. The Chain operand is needed
+ /// to tie this in place following a copy to %X3 from the result
+ /// of a GET_TLSLD_ADDR.
+ ADDIS_DTPREL_HA,
+
+ /// G8RC = ADDI_DTPREL_L G8RReg, Symbol - For the local-dynamic TLS
+ /// model, produces an ADDI8 instruction that adds G8RReg to
+ /// sym@got@dtprel@l.
+ ADDI_DTPREL_L,
+
/// STD_32 - This is the STD instruction for use with "32-bit" registers.
STD_32 = ISD::FIRST_TARGET_MEMORY_OPCODE,
(outs), (ins calltarget:$func, tlsgd:$sym),
"bl $func($sym)\n\tnop", BrB, []>;
+ let isCodeGenOnly = 1 in
+ def BL8_NOP_ELF_TLSLD : IForm_and_DForm_4_zero<18, 0, 1, 24,
+ (outs), (ins calltarget:$func, tlsgd:$sym),
+ "bl $func($sym)\n\tnop", BrB, []>;
+
def BLA8_ELF : IForm<18, 1, 1,
(outs), (ins aaddr:$func),
"bla $func", BrB, [(PPCcall_SVR4 (i64 imm:$func))]>;
[(set G8RC:$rD,
(PPCgetTlsAddr G8RC:$reg, tglobaladdr:$sym))]>,
isPPC64;
+def ADDIStlsldHA: Pseudo<(outs G8RC:$rD), (ins G8RC:$reg, symbolHi64:$disp),
+ "#ADDIStlsldHA",
+ [(set G8RC:$rD,
+ (PPCaddisTlsldHA G8RC:$reg, tglobaladdr:$disp))]>,
+ isPPC64;
+def ADDItlsldL : Pseudo<(outs G8RC:$rD), (ins G8RC:$reg, symbolLo64:$disp),
+ "#ADDItlsldL",
+ [(set G8RC:$rD,
+ (PPCaddiTlsldL G8RC:$reg, tglobaladdr:$disp))]>,
+ isPPC64;
+def GETtlsldADDR : Pseudo<(outs G8RC:$rD), (ins G8RC:$reg, tlsgd:$sym),
+ "#GETtlsldADDR",
+ [(set G8RC:$rD,
+ (PPCgetTlsldAddr G8RC:$reg, tglobaladdr:$sym))]>,
+ isPPC64;
+def ADDISdtprelHA: Pseudo<(outs G8RC:$rD), (ins G8RC:$reg, symbolHi64:$disp),
+ "#ADDISdtprelHA",
+ [(set G8RC:$rD,
+ (PPCaddisDtprelHA G8RC:$reg, tglobaladdr:$disp))]>,
+ isPPC64;
+def ADDIdtprelL : Pseudo<(outs G8RC:$rD), (ins G8RC:$reg, symbolLo64:$disp),
+ "#ADDIdtprelL",
+ [(set G8RC:$rD,
+ (PPCaddiDtprelL G8RC:$reg, tglobaladdr:$disp))]>,
+ isPPC64;
let PPC970_Unit = 2 in {
// Truncating stores.
def PPCaddisTlsgdHA : SDNode<"PPCISD::ADDIS_TLSGD_HA", SDTIntBinOp>;
def PPCaddiTlsgdL : SDNode<"PPCISD::ADDI_TLSGD_L", SDTIntBinOp>;
def PPCgetTlsAddr : SDNode<"PPCISD::GET_TLS_ADDR", SDTIntBinOp>;
+def PPCaddisTlsldHA : SDNode<"PPCISD::ADDIS_TLSLD_HA", SDTIntBinOp>;
+def PPCaddiTlsldL : SDNode<"PPCISD::ADDI_TLSLD_L", SDTIntBinOp>;
+def PPCgetTlsldAddr : SDNode<"PPCISD::GET_TLSLD_ADDR", SDTIntBinOp>;
+def PPCaddisDtprelHA : SDNode<"PPCISD::ADDIS_DTPREL_HA", SDTIntBinOp,
+ [SDNPHasChain]>;
+def PPCaddiDtprelL : SDNode<"PPCISD::ADDI_DTPREL_L", SDTIntBinOp>;
def PPCvperm : SDNode<"PPCISD::VPERM", SDT_PPCvperm, []>;
--- /dev/null
+; RUN: llc -mcpu=pwr7 -O0 -filetype=obj -relocation-model=pic %s -o - | \
+; RUN: elf-dump --dump-section-data | FileCheck %s
+
+; Test correct relocation generation for thread-local storage using
+; the local dynamic model.
+
+target datalayout = "E-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-f128:128:128-v128:128:128-n32:64"
+target triple = "powerpc64-unknown-linux-gnu"
+
+@a = hidden thread_local global i32 0, align 4
+
+define signext i32 @main() nounwind {
+entry:
+ %retval = alloca i32, align 4
+ store i32 0, i32* %retval
+ %0 = load i32* @a, align 4
+ ret i32 %0
+}
+
+; Verify generation of R_PPC64_GOT_TLSGD16_HA, R_PPC64_GOT_TLSGD16_LO,
+; and R_PPC64_TLSGD for accessing external variable a, and R_PPC64_REL24
+; for the call to __tls_get_addr.
+;
+; CHECK: '.rela.text'
+; CHECK: Relocation 0
+; CHECK-NEXT: 'r_offset'
+; CHECK-NEXT: 'r_sym', 0x[[SYM1:[0-9a-f]+]]
+; CHECK-NEXT: 'r_type', 0x00000056
+; CHECK: Relocation 1
+; CHECK-NEXT: 'r_offset'
+; CHECK-NEXT: 'r_sym', 0x[[SYM1]]
+; CHECK-NEXT: 'r_type', 0x00000054
+; CHECK: Relocation 2
+; CHECK-NEXT: 'r_offset'
+; CHECK-NEXT: 'r_sym', 0x[[SYM1]]
+; CHECK-NEXT: 'r_type', 0x0000006c
+; CHECK: Relocation 3
+; CHECK-NEXT: 'r_offset'
+; CHECK-NEXT: 'r_sym', 0x{{[0-9a-f]+}}
+; CHECK-NEXT: 'r_type', 0x0000000a
+; CHECK: Relocation 4
+; CHECK-NEXT: 'r_offset'
+; CHECK-NEXT: 'r_sym', 0x[[SYM1]]
+; CHECK-NEXT: 'r_type', 0x0000004d
+; CHECK: Relocation 5
+; CHECK-NEXT: 'r_offset'
+; CHECK-NEXT: 'r_sym', 0x[[SYM1]]
+; CHECK-NEXT: 'r_type', 0x0000004b
+
--- /dev/null
+; RUN: llc -mcpu=pwr7 -O0 -relocation-model=pic < %s | FileCheck %s
+
+; Test correct assembly code generation for thread-local storage using
+; the local dynamic model.
+
+target datalayout = "E-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-f128:128:128-v128:128:128-n32:64"
+target triple = "powerpc64-unknown-linux-gnu"
+
+@a = hidden thread_local global i32 0, align 4
+
+define signext i32 @main() nounwind {
+entry:
+ %retval = alloca i32, align 4
+ store i32 0, i32* %retval
+ %0 = load i32* @a, align 4
+ ret i32 %0
+}
+
+; CHECK: addis [[REG:[0-9]+]], 2, a@got@tlsld@ha
+; CHECK-NEXT: addi 3, [[REG]], a@got@tlsld@l
+; CHECK-NEXT: bl __tls_get_addr(a@tlsld)
+; CHECK-NEXT: nop
+; CHECK-NEXT: addis [[REG2:[0-9]+]], 3, a@dtprel@ha
+; CHECK-NEXT: addi {{[0-9]+}}, [[REG2]], a@dtprel@l