[LoongArch] Add codegen support of GlobalTLSAddress lowering
authorwanglei <wanglei@loongson.cn>
Tue, 11 Oct 2022 09:43:59 +0000 (17:43 +0800)
committerWeining Lu <luweining@loongson.cn>
Tue, 11 Oct 2022 10:10:13 +0000 (18:10 +0800)
There are static and dynamic TLS address lowering in DAG stage according
to different TLS models.

TLS address will be lowered to pseudo instruction and then expanded by
the `LoongArch Pre-RA pseudo instruction expansion` pass.

Differential Revision: https://reviews.llvm.org/D134713

llvm/lib/Target/LoongArch/LoongArchExpandPseudoInsts.cpp
llvm/lib/Target/LoongArch/LoongArchISelLowering.cpp
llvm/lib/Target/LoongArch/LoongArchISelLowering.h
llvm/lib/Target/LoongArch/LoongArchInstrInfo.td
llvm/lib/Target/LoongArch/LoongArchMCInstLower.cpp
llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchBaseInfo.h
llvm/test/CodeGen/LoongArch/tls-models.ll [new file with mode: 0644]

index 08b3a0f..1961444 100644 (file)
@@ -62,6 +62,18 @@ private:
   bool expandLoadAddressGot(MachineBasicBlock &MBB,
                             MachineBasicBlock::iterator MBBI,
                             MachineBasicBlock::iterator &NextMBBI);
+  bool expandLoadAddressTLSLE(MachineBasicBlock &MBB,
+                              MachineBasicBlock::iterator MBBI,
+                              MachineBasicBlock::iterator &NextMBBI);
+  bool expandLoadAddressTLSIE(MachineBasicBlock &MBB,
+                              MachineBasicBlock::iterator MBBI,
+                              MachineBasicBlock::iterator &NextMBBI);
+  bool expandLoadAddressTLSLD(MachineBasicBlock &MBB,
+                              MachineBasicBlock::iterator MBBI,
+                              MachineBasicBlock::iterator &NextMBBI);
+  bool expandLoadAddressTLSGD(MachineBasicBlock &MBB,
+                              MachineBasicBlock::iterator MBBI,
+                              MachineBasicBlock::iterator &NextMBBI);
 };
 
 char LoongArchPreRAExpandPseudo::ID = 0;
@@ -96,6 +108,14 @@ bool LoongArchPreRAExpandPseudo::expandMI(
     return expandLoadAddressPcrel(MBB, MBBI, NextMBBI);
   case LoongArch::PseudoLA_GOT:
     return expandLoadAddressGot(MBB, MBBI, NextMBBI);
+  case LoongArch::PseudoLA_TLS_LE:
+    return expandLoadAddressTLSLE(MBB, MBBI, NextMBBI);
+  case LoongArch::PseudoLA_TLS_IE:
+    return expandLoadAddressTLSIE(MBB, MBBI, NextMBBI);
+  case LoongArch::PseudoLA_TLS_LD:
+    return expandLoadAddressTLSLD(MBB, MBBI, NextMBBI);
+  case LoongArch::PseudoLA_TLS_GD:
+    return expandLoadAddressTLSGD(MBB, MBBI, NextMBBI);
   }
   return false;
 }
@@ -154,6 +174,71 @@ bool LoongArchPreRAExpandPseudo::expandLoadAddressGot(
                                  SecondOpcode, LoongArchII::MO_GOT_PC_LO);
 }
 
+bool LoongArchPreRAExpandPseudo::expandLoadAddressTLSLE(
+    MachineBasicBlock &MBB, MachineBasicBlock::iterator MBBI,
+    MachineBasicBlock::iterator &NextMBBI) {
+  // Code Sequence:
+  // lu12i.w $rd, %le_hi20(sym)
+  // ori $rd, $rd, %le_lo12(sym)
+  MachineFunction *MF = MBB.getParent();
+  MachineInstr &MI = *MBBI;
+  DebugLoc DL = MI.getDebugLoc();
+
+  Register DestReg = MI.getOperand(0).getReg();
+  Register ScratchReg =
+      MF->getRegInfo().createVirtualRegister(&LoongArch::GPRRegClass);
+  MachineOperand &Symbol = MI.getOperand(1);
+
+  BuildMI(MBB, MBBI, DL, TII->get(LoongArch::LU12I_W), ScratchReg)
+      .addDisp(Symbol, 0, LoongArchII::MO_LE_HI);
+
+  BuildMI(MBB, MBBI, DL, TII->get(LoongArch::ORI), DestReg)
+      .addReg(ScratchReg)
+      .addDisp(Symbol, 0, LoongArchII::MO_LE_LO);
+
+  MI.eraseFromParent();
+  return true;
+}
+
+bool LoongArchPreRAExpandPseudo::expandLoadAddressTLSIE(
+    MachineBasicBlock &MBB, MachineBasicBlock::iterator MBBI,
+    MachineBasicBlock::iterator &NextMBBI) {
+  // Code Sequence:
+  // pcalau12i $rd, %ie_pc_hi20(sym)
+  // ld.w/d $rd, $rd, %ie_pc_lo12(sym)
+  MachineFunction *MF = MBB.getParent();
+  const auto &STI = MF->getSubtarget<LoongArchSubtarget>();
+  unsigned SecondOpcode = STI.is64Bit() ? LoongArch::LD_D : LoongArch::LD_W;
+  return expandPcalau12iInstPair(MBB, MBBI, NextMBBI, LoongArchII::MO_IE_PC_HI,
+                                 SecondOpcode, LoongArchII::MO_IE_PC_LO);
+}
+
+bool LoongArchPreRAExpandPseudo::expandLoadAddressTLSLD(
+    MachineBasicBlock &MBB, MachineBasicBlock::iterator MBBI,
+    MachineBasicBlock::iterator &NextMBBI) {
+  // Code Sequence:
+  // pcalau12i $rd, %ld_pc_hi20(sym)
+  // addi.w/d $rd, $rd, %got_pc_lo12(sym)
+  MachineFunction *MF = MBB.getParent();
+  const auto &STI = MF->getSubtarget<LoongArchSubtarget>();
+  unsigned SecondOpcode = STI.is64Bit() ? LoongArch::ADDI_D : LoongArch::ADDI_W;
+  return expandPcalau12iInstPair(MBB, MBBI, NextMBBI, LoongArchII::MO_LD_PC_HI,
+                                 SecondOpcode, LoongArchII::MO_GOT_PC_LO);
+}
+
+bool LoongArchPreRAExpandPseudo::expandLoadAddressTLSGD(
+    MachineBasicBlock &MBB, MachineBasicBlock::iterator MBBI,
+    MachineBasicBlock::iterator &NextMBBI) {
+  // Code Sequence:
+  // pcalau12i $rd, %gd_pc_hi20(sym)
+  // addi.w/d $rd, $rd, %got_pc_lo12(sym)
+  MachineFunction *MF = MBB.getParent();
+  const auto &STI = MF->getSubtarget<LoongArchSubtarget>();
+  unsigned SecondOpcode = STI.is64Bit() ? LoongArch::ADDI_D : LoongArch::ADDI_W;
+  return expandPcalau12iInstPair(MBB, MBBI, NextMBBI, LoongArchII::MO_GD_PC_HI,
+                                 SecondOpcode, LoongArchII::MO_GOT_PC_LO);
+}
+
 } // end namespace
 
 INITIALIZE_PASS(LoongArchPreRAExpandPseudo, "LoongArch-prera-expand-pseudo",
index 003a4ac..2b00bb4 100644 (file)
@@ -62,6 +62,8 @@ LoongArchTargetLowering::LoongArchTargetLowering(const TargetMachine &TM,
                       ISD::JumpTable},
                      GRLenVT, Custom);
 
+  setOperationAction(ISD::GlobalTLSAddress, GRLenVT, Custom);
+
   setOperationAction(ISD::INTRINSIC_WO_CHAIN, MVT::Other, Custom);
 
   setOperationAction(ISD::EH_DWARF_CFA, MVT::i32, Custom);
@@ -193,6 +195,8 @@ SDValue LoongArchTargetLowering::LowerOperation(SDValue Op,
     return lowerEH_DWARF_CFA(Op, DAG);
   case ISD::GlobalAddress:
     return lowerGlobalAddress(Op, DAG);
+  case ISD::GlobalTLSAddress:
+    return lowerGlobalTLSAddress(Op, DAG);
   case ISD::INTRINSIC_WO_CHAIN:
     return lowerINTRINSIC_WO_CHAIN(Op, DAG);
   case ISD::BlockAddress:
@@ -353,6 +357,85 @@ SDValue LoongArchTargetLowering::lowerGlobalAddress(SDValue Op,
   return getAddr(N, DAG, N->getGlobal()->isDSOLocal());
 }
 
+SDValue LoongArchTargetLowering::getStaticTLSAddr(GlobalAddressSDNode *N,
+                                                  SelectionDAG &DAG,
+                                                  unsigned Opc) const {
+  SDLoc DL(N);
+  EVT Ty = getPointerTy(DAG.getDataLayout());
+  MVT GRLenVT = Subtarget.getGRLenVT();
+
+  SDValue Addr = DAG.getTargetGlobalAddress(N->getGlobal(), DL, Ty, 0, 0);
+  SDValue Offset = SDValue(DAG.getMachineNode(Opc, DL, Ty, Addr), 0);
+
+  // Add the thread pointer.
+  return DAG.getNode(ISD::ADD, DL, Ty, Offset,
+                     DAG.getRegister(LoongArch::R2, GRLenVT));
+}
+
+SDValue LoongArchTargetLowering::getDynamicTLSAddr(GlobalAddressSDNode *N,
+                                                   SelectionDAG &DAG,
+                                                   unsigned Opc) const {
+  SDLoc DL(N);
+  EVT Ty = getPointerTy(DAG.getDataLayout());
+  IntegerType *CallTy = Type::getIntNTy(*DAG.getContext(), Ty.getSizeInBits());
+
+  // Use a PC-relative addressing mode to access the dynamic GOT address.
+  SDValue Addr = DAG.getTargetGlobalAddress(N->getGlobal(), DL, Ty, 0, 0);
+  SDValue Load = SDValue(DAG.getMachineNode(Opc, DL, Ty, Addr), 0);
+
+  // Prepare argument list to generate call.
+  ArgListTy Args;
+  ArgListEntry Entry;
+  Entry.Node = Load;
+  Entry.Ty = CallTy;
+  Args.push_back(Entry);
+
+  // Setup call to __tls_get_addr.
+  TargetLowering::CallLoweringInfo CLI(DAG);
+  CLI.setDebugLoc(DL)
+      .setChain(DAG.getEntryNode())
+      .setLibCallee(CallingConv::C, CallTy,
+                    DAG.getExternalSymbol("__tls_get_addr", Ty),
+                    std::move(Args));
+
+  return LowerCallTo(CLI).first;
+}
+
+SDValue
+LoongArchTargetLowering::lowerGlobalTLSAddress(SDValue Op,
+                                               SelectionDAG &DAG) const {
+  GlobalAddressSDNode *N = cast<GlobalAddressSDNode>(Op);
+  assert(N->getOffset() == 0 && "unexpected offset in global node");
+
+  SDValue Addr;
+  TLSModel::Model Model = getTargetMachine().getTLSModel(N->getGlobal());
+
+  switch (Model) {
+  case TLSModel::GeneralDynamic:
+    // In this model, application code calls the dynamic linker function
+    // __tls_get_addr to locate TLS offsets into the dynamic thread vector at
+    // runtime.
+    Addr = getDynamicTLSAddr(N, DAG, LoongArch::PseudoLA_TLS_GD);
+    break;
+  case TLSModel::LocalDynamic:
+    // Same as GeneralDynamic, except for assembly modifiers and relocation
+    // records.
+    Addr = getDynamicTLSAddr(N, DAG, LoongArch::PseudoLA_TLS_LD);
+    break;
+  case TLSModel::InitialExec:
+    // This model uses the GOT to resolve TLS offsets.
+    Addr = getStaticTLSAddr(N, DAG, LoongArch::PseudoLA_TLS_IE);
+    break;
+  case TLSModel::LocalExec:
+    // This model is used when static linking as the TLS offsets are resolved
+    // during program linking.
+    Addr = getStaticTLSAddr(N, DAG, LoongArch::PseudoLA_TLS_LE);
+    break;
+  }
+
+  return Addr;
+}
+
 SDValue LoongArchTargetLowering::lowerINTRINSIC_WO_CHAIN(SDValue Op,
                                                          SelectionDAG &DAG) const {
   unsigned IntNo = Op.getConstantOperandVal(0);
index 084b269..a0b9a8d 100644 (file)
@@ -138,9 +138,14 @@ private:
 
   template <class NodeTy>
   SDValue getAddr(NodeTy *N, SelectionDAG &DAG, bool IsLocal = true) const;
+  SDValue getStaticTLSAddr(GlobalAddressSDNode *N, SelectionDAG &DAG,
+                           unsigned Opc) const;
+  SDValue getDynamicTLSAddr(GlobalAddressSDNode *N, SelectionDAG &DAG,
+                            unsigned Opc) const;
   SDValue lowerGlobalAddress(SDValue Op, SelectionDAG &DAG) const;
   SDValue lowerBlockAddress(SDValue Op, SelectionDAG &DAG) const;
   SDValue lowerJumpTable(SDValue Op, SelectionDAG &DAG) const;
+  SDValue lowerGlobalTLSAddress(SDValue Op, SelectionDAG &DAG) const;
   SDValue lowerShiftLeftParts(SDValue Op, SelectionDAG &DAG) const;
   SDValue lowerShiftRightParts(SDValue Op, SelectionDAG &DAG, bool IsSRA) const;
 
index 4a0f6ed..657c494 100644 (file)
@@ -926,6 +926,18 @@ def PseudoLA_PCREL : Pseudo<(outs GPR:$dst), (ins grlenimm:$src), []>;
 let hasSideEffects = 0, mayLoad = 1, mayStore = 0 in
 def PseudoLA_GOT : Pseudo<(outs GPR:$dst), (ins grlenimm:$src), []>;
 
+let hasSideEffects = 0, mayLoad = 0, mayStore = 0 in
+def PseudoLA_TLS_LE : Pseudo<(outs GPR:$dst), (ins grlenimm:$src), []>;
+
+let hasSideEffects = 0, mayLoad = 1, mayStore = 0 in
+def PseudoLA_TLS_IE : Pseudo<(outs GPR:$dst), (ins grlenimm:$src), []>;
+
+let hasSideEffects = 0, mayLoad = 1, mayStore = 0 in
+def PseudoLA_TLS_LD : Pseudo<(outs GPR:$dst), (ins grlenimm:$src), []>;
+
+let hasSideEffects = 0, mayLoad = 1, mayStore = 0 in
+def PseudoLA_TLS_GD : Pseudo<(outs GPR:$dst), (ins grlenimm:$src), []>;
+
 /// BSTRINS and BSTRPICK
 
 let Predicates = [IsLA32] in {
index af17ffc..40c9814 100644 (file)
@@ -53,6 +53,24 @@ static MCOperand lowerSymbolOperand(const MachineOperand &MO, MCSymbol *Sym,
   case LoongArchII::MO_GOT_PC_LO:
     Kind = LoongArchMCExpr::VK_LoongArch_GOT_LO12;
     break;
+  case LoongArchII::MO_LE_HI:
+    Kind = LoongArchMCExpr::VK_LoongArch_TLS_LE_HI20;
+    break;
+  case LoongArchII::MO_LE_LO:
+    Kind = LoongArchMCExpr::VK_LoongArch_TLS_LE_LO12;
+    break;
+  case LoongArchII::MO_IE_PC_HI:
+    Kind = LoongArchMCExpr::VK_LoongArch_TLS_IE_PC_HI20;
+    break;
+  case LoongArchII::MO_IE_PC_LO:
+    Kind = LoongArchMCExpr::VK_LoongArch_TLS_IE_PC_LO12;
+    break;
+  case LoongArchII::MO_LD_PC_HI:
+    Kind = LoongArchMCExpr::VK_LoongArch_TLS_LD_PC_HI20;
+    break;
+  case LoongArchII::MO_GD_PC_HI:
+    Kind = LoongArchMCExpr::VK_LoongArch_TLS_GD_PC_HI20;
+    break;
     // TODO: Handle more target-flags.
   }
 
index 77938b9..c5f0726 100644 (file)
@@ -33,6 +33,12 @@ enum {
   MO_PCREL_LO,
   MO_GOT_PC_HI,
   MO_GOT_PC_LO,
+  MO_LE_HI,
+  MO_LE_LO,
+  MO_IE_PC_HI,
+  MO_IE_PC_LO,
+  MO_LD_PC_HI,
+  MO_GD_PC_HI,
   // TODO: Add more flags.
 };
 } // end namespace LoongArchII
diff --git a/llvm/test/CodeGen/LoongArch/tls-models.ll b/llvm/test/CodeGen/LoongArch/tls-models.ll
new file mode 100644 (file)
index 0000000..1128532
--- /dev/null
@@ -0,0 +1,166 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc --mtriple=loongarch32 --relocation-model=pic < %s | FileCheck %s --check-prefix=LA32PIC
+; RUN: llc --mtriple=loongarch64 --relocation-model=pic < %s | FileCheck %s --check-prefix=LA64PIC
+; RUN: llc --mtriple=loongarch32 < %s | FileCheck %s --check-prefix=LA32NOPIC
+; RUN: llc --mtriple=loongarch64 < %s | FileCheck %s --check-prefix=LA64NOPIC
+
+;; Check that TLS symbols are lowered correctly based on the specified
+;; model. Make sure they're external to avoid them all being optimised to Local
+;; Exec for the executable.
+
+@unspecified = external thread_local global i32
+@ld = external thread_local(localdynamic) global i32
+@ie = external thread_local(initialexec) global i32
+@le = external thread_local(localexec) global i32
+
+;; No model specified (global dynamic)
+
+define ptr @f1() nounwind {
+; LA32PIC-LABEL: f1:
+; LA32PIC:       # %bb.0: # %entry
+; LA32PIC-NEXT:    addi.w $sp, $sp, -16
+; LA32PIC-NEXT:    st.w $ra, $sp, 12 # 4-byte Folded Spill
+; LA32PIC-NEXT:    pcalau12i $a0, %gd_pc_hi20(unspecified)
+; LA32PIC-NEXT:    addi.w $a0, $a0, %got_lo12(unspecified)
+; LA32PIC-NEXT:    bl %plt(__tls_get_addr)
+; LA32PIC-NEXT:    ld.w $ra, $sp, 12 # 4-byte Folded Reload
+; LA32PIC-NEXT:    addi.w $sp, $sp, 16
+; LA32PIC-NEXT:    ret
+;
+; LA64PIC-LABEL: f1:
+; LA64PIC:       # %bb.0: # %entry
+; LA64PIC-NEXT:    addi.d $sp, $sp, -16
+; LA64PIC-NEXT:    st.d $ra, $sp, 8 # 8-byte Folded Spill
+; LA64PIC-NEXT:    pcalau12i $a0, %gd_pc_hi20(unspecified)
+; LA64PIC-NEXT:    addi.d $a0, $a0, %got_lo12(unspecified)
+; LA64PIC-NEXT:    bl %plt(__tls_get_addr)
+; LA64PIC-NEXT:    ld.d $ra, $sp, 8 # 8-byte Folded Reload
+; LA64PIC-NEXT:    addi.d $sp, $sp, 16
+; LA64PIC-NEXT:    ret
+;
+; LA32NOPIC-LABEL: f1:
+; LA32NOPIC:       # %bb.0: # %entry
+; LA32NOPIC-NEXT:    pcalau12i $a0, %ie_pc_hi20(unspecified)
+; LA32NOPIC-NEXT:    ld.w $a0, $a0, %ie_pc_lo12(unspecified)
+; LA32NOPIC-NEXT:    add.w $a0, $a0, $tp
+; LA32NOPIC-NEXT:    ret
+;
+; LA64NOPIC-LABEL: f1:
+; LA64NOPIC:       # %bb.0: # %entry
+; LA64NOPIC-NEXT:    pcalau12i $a0, %ie_pc_hi20(unspecified)
+; LA64NOPIC-NEXT:    ld.d $a0, $a0, %ie_pc_lo12(unspecified)
+; LA64NOPIC-NEXT:    add.d $a0, $a0, $tp
+; LA64NOPIC-NEXT:    ret
+entry:
+  ret ptr @unspecified
+}
+
+;; localdynamic specified
+
+define ptr @f2() nounwind {
+; LA32PIC-LABEL: f2:
+; LA32PIC:       # %bb.0: # %entry
+; LA32PIC-NEXT:    addi.w $sp, $sp, -16
+; LA32PIC-NEXT:    st.w $ra, $sp, 12 # 4-byte Folded Spill
+; LA32PIC-NEXT:    pcalau12i $a0, %ld_pc_hi20(ld)
+; LA32PIC-NEXT:    addi.w $a0, $a0, %got_lo12(ld)
+; LA32PIC-NEXT:    bl %plt(__tls_get_addr)
+; LA32PIC-NEXT:    ld.w $ra, $sp, 12 # 4-byte Folded Reload
+; LA32PIC-NEXT:    addi.w $sp, $sp, 16
+; LA32PIC-NEXT:    ret
+;
+; LA64PIC-LABEL: f2:
+; LA64PIC:       # %bb.0: # %entry
+; LA64PIC-NEXT:    addi.d $sp, $sp, -16
+; LA64PIC-NEXT:    st.d $ra, $sp, 8 # 8-byte Folded Spill
+; LA64PIC-NEXT:    pcalau12i $a0, %ld_pc_hi20(ld)
+; LA64PIC-NEXT:    addi.d $a0, $a0, %got_lo12(ld)
+; LA64PIC-NEXT:    bl %plt(__tls_get_addr)
+; LA64PIC-NEXT:    ld.d $ra, $sp, 8 # 8-byte Folded Reload
+; LA64PIC-NEXT:    addi.d $sp, $sp, 16
+; LA64PIC-NEXT:    ret
+;
+; LA32NOPIC-LABEL: f2:
+; LA32NOPIC:       # %bb.0: # %entry
+; LA32NOPIC-NEXT:    pcalau12i $a0, %ie_pc_hi20(ld)
+; LA32NOPIC-NEXT:    ld.w $a0, $a0, %ie_pc_lo12(ld)
+; LA32NOPIC-NEXT:    add.w $a0, $a0, $tp
+; LA32NOPIC-NEXT:    ret
+;
+; LA64NOPIC-LABEL: f2:
+; LA64NOPIC:       # %bb.0: # %entry
+; LA64NOPIC-NEXT:    pcalau12i $a0, %ie_pc_hi20(ld)
+; LA64NOPIC-NEXT:    ld.d $a0, $a0, %ie_pc_lo12(ld)
+; LA64NOPIC-NEXT:    add.d $a0, $a0, $tp
+; LA64NOPIC-NEXT:    ret
+entry:
+  ret ptr @ld
+}
+
+;; initialexec specified
+
+define ptr @f3() nounwind {
+; LA32PIC-LABEL: f3:
+; LA32PIC:       # %bb.0: # %entry
+; LA32PIC-NEXT:    pcalau12i $a0, %ie_pc_hi20(ie)
+; LA32PIC-NEXT:    ld.w $a0, $a0, %ie_pc_lo12(ie)
+; LA32PIC-NEXT:    add.w $a0, $a0, $tp
+; LA32PIC-NEXT:    ret
+;
+; LA64PIC-LABEL: f3:
+; LA64PIC:       # %bb.0: # %entry
+; LA64PIC-NEXT:    pcalau12i $a0, %ie_pc_hi20(ie)
+; LA64PIC-NEXT:    ld.d $a0, $a0, %ie_pc_lo12(ie)
+; LA64PIC-NEXT:    add.d $a0, $a0, $tp
+; LA64PIC-NEXT:    ret
+;
+; LA32NOPIC-LABEL: f3:
+; LA32NOPIC:       # %bb.0: # %entry
+; LA32NOPIC-NEXT:    pcalau12i $a0, %ie_pc_hi20(ie)
+; LA32NOPIC-NEXT:    ld.w $a0, $a0, %ie_pc_lo12(ie)
+; LA32NOPIC-NEXT:    add.w $a0, $a0, $tp
+; LA32NOPIC-NEXT:    ret
+;
+; LA64NOPIC-LABEL: f3:
+; LA64NOPIC:       # %bb.0: # %entry
+; LA64NOPIC-NEXT:    pcalau12i $a0, %ie_pc_hi20(ie)
+; LA64NOPIC-NEXT:    ld.d $a0, $a0, %ie_pc_lo12(ie)
+; LA64NOPIC-NEXT:    add.d $a0, $a0, $tp
+; LA64NOPIC-NEXT:    ret
+entry:
+  ret ptr @ie
+}
+
+;; localexec specified
+
+define ptr @f4() nounwind {
+; LA32PIC-LABEL: f4:
+; LA32PIC:       # %bb.0: # %entry
+; LA32PIC-NEXT:    lu12i.w $a0, %le_hi20(le)
+; LA32PIC-NEXT:    ori $a0, $a0, %le_lo12(le)
+; LA32PIC-NEXT:    add.w $a0, $a0, $tp
+; LA32PIC-NEXT:    ret
+;
+; LA64PIC-LABEL: f4:
+; LA64PIC:       # %bb.0: # %entry
+; LA64PIC-NEXT:    lu12i.w $a0, %le_hi20(le)
+; LA64PIC-NEXT:    ori $a0, $a0, %le_lo12(le)
+; LA64PIC-NEXT:    add.d $a0, $a0, $tp
+; LA64PIC-NEXT:    ret
+;
+; LA32NOPIC-LABEL: f4:
+; LA32NOPIC:       # %bb.0: # %entry
+; LA32NOPIC-NEXT:    lu12i.w $a0, %le_hi20(le)
+; LA32NOPIC-NEXT:    ori $a0, $a0, %le_lo12(le)
+; LA32NOPIC-NEXT:    add.w $a0, $a0, $tp
+; LA32NOPIC-NEXT:    ret
+;
+; LA64NOPIC-LABEL: f4:
+; LA64NOPIC:       # %bb.0: # %entry
+; LA64NOPIC-NEXT:    lu12i.w $a0, %le_hi20(le)
+; LA64NOPIC-NEXT:    ori $a0, $a0, %le_lo12(le)
+; LA64NOPIC-NEXT:    add.d $a0, $a0, $tp
+; LA64NOPIC-NEXT:    ret
+entry:
+  ret ptr @le
+}