[RISCV] Implement pseudo instructions for load/store from a symbol address.

Summary:
Those pseudo-instructions are making load/store instructions able to
load/store from/to a symbol, and its always using PC-relative addressing
to generating a symbol address.

Reviewers: asb, apazos, rogfer01, jrtc27

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

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@354430 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp b/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
index 2d797f3..239f7c4 100644
--- a/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
+++ b/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
@@ -91,6 +91,10 @@
   // Helper to emit pseudo instruction "la" used in GOT/PC-rel addressing.
   void emitLoadAddress(MCInst &Inst, SMLoc IDLoc, MCStreamer &Out);
 
+  // Helper to emit pseudo load/store instruction with a symbol.
+  void emitLoadStoreSymbol(MCInst &Inst, unsigned Opcode, SMLoc IDLoc,
+                           MCStreamer &Out, bool HasTmpReg);
+
   /// Helper for processing MC instructions that have been successfully matched
   /// by MatchAndEmitInstruction. Modifications to the emitted instructions,
   /// like the expansion of pseudo instructions (e.g., "li"), can be performed
@@ -1105,11 +1109,22 @@
     return MatchOperand_NoMatch;
 
   StringRef Identifier;
+  AsmToken Tok = getLexer().getTok();
+
   if (getParser().parseIdentifier(Identifier))
     return MatchOperand_ParseFail;
 
   MCSymbol *Sym = getContext().getOrCreateSymbol(Identifier);
-  Res = MCSymbolRefExpr::create(Sym, MCSymbolRefExpr::VK_None, getContext());
+
+  if (Sym->isVariable()) {
+    const MCExpr *V = Sym->getVariableValue(/*SetUsed=*/false);
+    if (!isa<MCSymbolRefExpr>(V)) {
+      getLexer().UnLex(Tok); // Put back if it's not a bare symbol.
+      return MatchOperand_NoMatch;
+    }
+    Res = V;
+  } else
+    Res = MCSymbolRefExpr::create(Sym, MCSymbolRefExpr::VK_None, getContext());
   Operands.push_back(RISCVOperand::createImm(Res, S, E, isRV64()));
   return MatchOperand_Success;
 }
@@ -1487,6 +1502,25 @@
   emitAuipcInstPair(DestReg, DestReg, Symbol, VKHi, SecondOpcode, IDLoc, Out);
 }
 
+void RISCVAsmParser::emitLoadStoreSymbol(MCInst &Inst, unsigned Opcode,
+                                         SMLoc IDLoc, MCStreamer &Out,
+                                         bool HasTmpReg) {
+  // The load/store pseudo-instruction does a pc-relative load with
+  // a symbol.
+  //
+  // The expansion looks like this
+  //
+  //   TmpLabel: AUIPC tmp, %pcrel_hi(symbol)
+  //             [S|L]X    rd, %pcrel_lo(TmpLabel)(tmp)
+  MCOperand DestReg = Inst.getOperand(0);
+  unsigned SymbolOpIdx = HasTmpReg ? 2 : 1;
+  unsigned TmpRegOpIdx = HasTmpReg ? 1 : 0;
+  MCOperand TmpReg = Inst.getOperand(TmpRegOpIdx);
+  const MCExpr *Symbol = Inst.getOperand(SymbolOpIdx).getExpr();
+  emitAuipcInstPair(DestReg, TmpReg, Symbol, RISCVMCExpr::VK_RISCV_PCREL_HI,
+                    Opcode, IDLoc, Out);
+}
+
 bool RISCVAsmParser::processInstruction(MCInst &Inst, SMLoc IDLoc,
                                         MCStreamer &Out) {
   Inst.setLoc(IDLoc);
@@ -1521,6 +1555,51 @@
   case RISCV::PseudoLA:
     emitLoadAddress(Inst, IDLoc, Out);
     return false;
+  case RISCV::PseudoLB:
+    emitLoadStoreSymbol(Inst, RISCV::LB, IDLoc, Out, /*HasTmpReg=*/false);
+    return false;
+  case RISCV::PseudoLBU:
+    emitLoadStoreSymbol(Inst, RISCV::LBU, IDLoc, Out, /*HasTmpReg=*/false);
+    return false;
+  case RISCV::PseudoLH:
+    emitLoadStoreSymbol(Inst, RISCV::LH, IDLoc, Out, /*HasTmpReg=*/false);
+    return false;
+  case RISCV::PseudoLHU:
+    emitLoadStoreSymbol(Inst, RISCV::LHU, IDLoc, Out, /*HasTmpReg=*/false);
+    return false;
+  case RISCV::PseudoLW:
+    emitLoadStoreSymbol(Inst, RISCV::LW, IDLoc, Out, /*HasTmpReg=*/false);
+    return false;
+  case RISCV::PseudoLWU:
+    emitLoadStoreSymbol(Inst, RISCV::LWU, IDLoc, Out, /*HasTmpReg=*/false);
+    return false;
+  case RISCV::PseudoLD:
+    emitLoadStoreSymbol(Inst, RISCV::LD, IDLoc, Out, /*HasTmpReg=*/false);
+    return false;
+  case RISCV::PseudoFLW:
+    emitLoadStoreSymbol(Inst, RISCV::FLW, IDLoc, Out, /*HasTmpReg=*/true);
+    return false;
+  case RISCV::PseudoFLD:
+    emitLoadStoreSymbol(Inst, RISCV::FLD, IDLoc, Out, /*HasTmpReg=*/true);
+    return false;
+  case RISCV::PseudoSB:
+    emitLoadStoreSymbol(Inst, RISCV::SB, IDLoc, Out, /*HasTmpReg=*/true);
+    return false;
+  case RISCV::PseudoSH:
+    emitLoadStoreSymbol(Inst, RISCV::SH, IDLoc, Out, /*HasTmpReg=*/true);
+    return false;
+  case RISCV::PseudoSW:
+    emitLoadStoreSymbol(Inst, RISCV::SW, IDLoc, Out, /*HasTmpReg=*/true);
+    return false;
+  case RISCV::PseudoSD:
+    emitLoadStoreSymbol(Inst, RISCV::SD, IDLoc, Out, /*HasTmpReg=*/true);
+    return false;
+  case RISCV::PseudoFSW:
+    emitLoadStoreSymbol(Inst, RISCV::FSW, IDLoc, Out, /*HasTmpReg=*/true);
+    return false;
+  case RISCV::PseudoFSD:
+    emitLoadStoreSymbol(Inst, RISCV::FSD, IDLoc, Out, /*HasTmpReg=*/true);
+    return false;
   }
 
   emitToStreamer(Out, Inst);
diff --git a/lib/Target/RISCV/RISCVInstrFormats.td b/lib/Target/RISCV/RISCVInstrFormats.td
index f2c6230..7229ebf 100644
--- a/lib/Target/RISCV/RISCVInstrFormats.td
+++ b/lib/Target/RISCV/RISCVInstrFormats.td
@@ -108,6 +108,35 @@
   let isCodeGenOnly = 1;
 }
 
+// Pseudo load instructions.
+class PseudoLoad<string opcodestr, RegisterClass rdty = GPR>
+    : Pseudo<(outs rdty:$rd), (ins bare_symbol:$addr), [], opcodestr, "$rd, $addr"> {
+  let hasSideEffects = 0;
+  let mayLoad = 1;
+  let mayStore = 0;
+  let isCodeGenOnly = 0;
+  let isAsmParserOnly = 1;
+}
+
+class PseudoFloatLoad<string opcodestr, RegisterClass rdty = GPR>
+    : Pseudo<(outs rdty:$rd, GPR:$tmp), (ins bare_symbol:$addr), [], opcodestr, "$rd, $addr, $tmp"> {
+  let hasSideEffects = 0;
+  let mayLoad = 1;
+  let mayStore = 0;
+  let isCodeGenOnly = 0;
+  let isAsmParserOnly = 1;
+}
+
+// Pseudo store instructions.
+class PseudoStore<string opcodestr, RegisterClass rsty = GPR>
+    : Pseudo<(outs rsty:$rs, GPR:$tmp), (ins bare_symbol:$addr), [], opcodestr, "$rs, $addr, $tmp"> {
+  let hasSideEffects = 0;
+  let mayLoad = 0;
+  let mayStore = 1;
+  let isCodeGenOnly = 0;
+  let isAsmParserOnly = 1;
+}
+
 // Instruction formats are listed in the order they appear in the RISC-V
 // instruction set manual (R, I, S, B, U, J) with sub-formats (e.g. RVInstR4,
 // RVInstRAtomic) sorted alphabetically.
diff --git a/lib/Target/RISCV/RISCVInstrInfo.td b/lib/Target/RISCV/RISCVInstrInfo.td
index de9ea19..c13bcf5 100644
--- a/lib/Target/RISCV/RISCVInstrInfo.td
+++ b/lib/Target/RISCV/RISCVInstrInfo.td
@@ -10,8 +10,6 @@
 //
 //===----------------------------------------------------------------------===//
 
-include "RISCVInstrFormats.td"
-
 //===----------------------------------------------------------------------===//
 // RISC-V specific DAG Nodes.
 //===----------------------------------------------------------------------===//
@@ -241,6 +239,12 @@
 }]>;
 
 //===----------------------------------------------------------------------===//
+// Instruction Formats
+//===----------------------------------------------------------------------===//
+
+include "RISCVInstrFormats.td"
+
+//===----------------------------------------------------------------------===//
 // Instruction Class Templates
 //===----------------------------------------------------------------------===//
 
@@ -497,11 +501,6 @@
 // Assembler Pseudo Instructions (User-Level ISA, Version 2.2, Chapter 20)
 //===----------------------------------------------------------------------===//
 
-// TODO lb lh lw
-// TODO RV64I: ld
-// TODO sb sh sw
-// TODO RV64I: sd
-
 def : InstAlias<"nop",           (ADDI      X0,      X0,       0)>;
 
 // Note that the size is 32 because up to 8 32-bit instructions are needed to
@@ -513,6 +512,22 @@
 def PseudoLI : Pseudo<(outs GPR:$rd), (ins ixlenimm_li:$imm), [],
                       "li", "$rd, $imm">;
 
+def PseudoLB  : PseudoLoad<"lb">;
+def PseudoLBU : PseudoLoad<"lbu">;
+def PseudoLH  : PseudoLoad<"lh">;
+def PseudoLHU : PseudoLoad<"lhu">;
+def PseudoLW  : PseudoLoad<"lw">;
+
+def PseudoSB  : PseudoStore<"sb">;
+def PseudoSH  : PseudoStore<"sh">;
+def PseudoSW  : PseudoStore<"sw">;
+
+let Predicates = [IsRV64] in {
+def PseudoLWU : PseudoLoad<"lwu">;
+def PseudoLD  : PseudoLoad<"ld">;
+def PseudoSD  : PseudoStore<"sd">;
+} // Predicates = [IsRV64]
+
 def : InstAlias<"mv $rd, $rs",   (ADDI GPR:$rd, GPR:$rs,       0)>;
 def : InstAlias<"not $rd, $rs",  (XORI GPR:$rd, GPR:$rs,      -1)>;
 def : InstAlias<"neg $rd, $rs",  (SUB  GPR:$rd,      X0, GPR:$rs)>;
diff --git a/lib/Target/RISCV/RISCVInstrInfoD.td b/lib/Target/RISCV/RISCVInstrInfoD.td
index 87efd36..1e48d24 100644
--- a/lib/Target/RISCV/RISCVInstrInfoD.td
+++ b/lib/Target/RISCV/RISCVInstrInfoD.td
@@ -178,9 +178,6 @@
 //===----------------------------------------------------------------------===//
 
 let Predicates = [HasStdExtD] in {
-// TODO fld
-// TODO fsd
-
 def : InstAlias<"fmv.d $rd, $rs",  (FSGNJ_D  FPR64:$rd, FPR64:$rs, FPR64:$rs)>;
 def : InstAlias<"fabs.d $rd, $rs", (FSGNJX_D FPR64:$rd, FPR64:$rs, FPR64:$rs)>;
 def : InstAlias<"fneg.d $rd, $rs", (FSGNJN_D FPR64:$rd, FPR64:$rs, FPR64:$rs)>;
@@ -191,6 +188,9 @@
                 (FLT_D GPR:$rd, FPR64:$rt, FPR64:$rs), 0>;
 def : InstAlias<"fge.d $rd, $rs, $rt",
                 (FLE_D GPR:$rd, FPR64:$rt, FPR64:$rs), 0>;
+
+def PseudoFLD  : PseudoFloatLoad<"fld", FPR64>;
+def PseudoFSD  : PseudoStore<"fsd", FPR64>;
 } // Predicates = [HasStdExtD]
 
 //===----------------------------------------------------------------------===//
diff --git a/lib/Target/RISCV/RISCVInstrInfoF.td b/lib/Target/RISCV/RISCVInstrInfoF.td
index 0fb20520..c7fa72d 100644
--- a/lib/Target/RISCV/RISCVInstrInfoF.td
+++ b/lib/Target/RISCV/RISCVInstrInfoF.td
@@ -206,9 +206,6 @@
 //===----------------------------------------------------------------------===//
 
 let Predicates = [HasStdExtF] in {
-// TODO flw
-// TODO fsw
-
 def : InstAlias<"fmv.s $rd, $rs",  (FSGNJ_S  FPR32:$rd, FPR32:$rs, FPR32:$rs)>;
 def : InstAlias<"fabs.s $rd, $rs", (FSGNJX_S FPR32:$rd, FPR32:$rs, FPR32:$rs)>;
 def : InstAlias<"fneg.s $rd, $rs", (FSGNJN_S FPR32:$rd, FPR32:$rs, FPR32:$rs)>;
@@ -244,6 +241,9 @@
 // spellings should be supported by standard tools.
 def : MnemonicAlias<"fmv.s.x", "fmv.w.x">;
 def : MnemonicAlias<"fmv.x.s", "fmv.x.w">;
+
+def PseudoFLW  : PseudoFloatLoad<"flw", FPR32>;
+def PseudoFSW  : PseudoStore<"fsw", FPR32>;
 } // Predicates = [HasStdExtF]
 
 //===----------------------------------------------------------------------===//
diff --git a/test/MC/RISCV/rv32d-invalid.s b/test/MC/RISCV/rv32d-invalid.s
index d6f5e72..f85a0ee 100644
--- a/test/MC/RISCV/rv32d-invalid.s
+++ b/test/MC/RISCV/rv32d-invalid.s
@@ -6,8 +6,8 @@
 fsd ft2, 2048(a1) # CHECK: :[[@LINE]]:10: error: operand must be a symbol with %lo/%pcrel_lo modifier or an integer in the range [-2048, 2047]
 
 # Memory operand not formatted correctly
-fld ft1, a0, -200 # CHECK: :[[@LINE]]:10: error: operand must be a symbol with %lo/%pcrel_lo modifier or an integer in the range [-2048, 2047]
-fsd ft2, a1, 100 # CHECK: :[[@LINE]]:10: error: operand must be a symbol with %lo/%pcrel_lo modifier or an integer in the range [-2048, 2047]
+fld ft1, a0, -200 # CHECK: :[[@LINE]]:14: error: invalid operand for instruction
+fsd ft2, a1, 100 # CHECK: :[[@LINE]]:14: error: invalid operand for instruction
 
 # Invalid register names
 fld ft15, 100(a0) # CHECK: :[[@LINE]]:5: error: invalid operand for instruction
diff --git a/test/MC/RISCV/rv32f-invalid.s b/test/MC/RISCV/rv32f-invalid.s
index 5b49244..470035d 100644
--- a/test/MC/RISCV/rv32f-invalid.s
+++ b/test/MC/RISCV/rv32f-invalid.s
@@ -6,8 +6,8 @@
 fsw ft2, 2048(a1) # CHECK: :[[@LINE]]:10: error: operand must be a symbol with %lo/%pcrel_lo modifier or an integer in the range [-2048, 2047]
 
 # Memory operand not formatted correctly
-flw ft1, a0, -200 # CHECK: :[[@LINE]]:10: error: operand must be a symbol with %lo/%pcrel_lo modifier or an integer in the range [-2048, 2047]
-fsw ft2, a1, 100 # CHECK: :[[@LINE]]:10: error: operand must be a symbol with %lo/%pcrel_lo modifier or an integer in the range [-2048, 2047]
+flw ft1, a0, -200 # CHECK: :[[@LINE]]:14: error: invalid operand for instruction
+fsw ft2, a1, 100 # CHECK: :[[@LINE]]:14: error: invalid operand for instruction
 
 # Invalid register names
 flw ft15, 100(a0) # CHECK: :[[@LINE]]:5: error: invalid operand for instruction
diff --git a/test/MC/RISCV/rv32i-invalid.s b/test/MC/RISCV/rv32i-invalid.s
index b5fabee..7cd795b 100644
--- a/test/MC/RISCV/rv32i-invalid.s
+++ b/test/MC/RISCV/rv32i-invalid.s
@@ -158,7 +158,7 @@
 sltiu s2, s3, 0x50, 0x60 # CHECK: :[[@LINE]]:21: error: invalid operand for instruction
 
 # Memory operand not formatted correctly
-lw a4, a5, 111 # CHECK: :[[@LINE]]:8: error: operand must be a symbol with %lo/%pcrel_lo modifier or an integer in the range [-2048, 2047]
+lw a4, a5, 111 # CHECK: :[[@LINE]]:12: error: invalid operand for instruction
 
 # Too few operands
 ori a0, a1 # CHECK: :[[@LINE]]:1: error: too few operands for instruction
diff --git a/test/MC/RISCV/rv64i-pseudos.s b/test/MC/RISCV/rv64i-pseudos.s
new file mode 100644
index 0000000..2814776
--- /dev/null
+++ b/test/MC/RISCV/rv64i-pseudos.s
@@ -0,0 +1,16 @@
+# RUN: llvm-mc %s -triple=riscv64 | FileCheck %s
+
+# CHECK: .Lpcrel_hi0:
+# CHECK: auipc a2, %pcrel_hi(a_symbol)
+# CHECK: lwu  a2, %pcrel_lo(.Lpcrel_hi0)(a2)
+lwu a2, a_symbol
+
+# CHECK: .Lpcrel_hi1:
+# CHECK: auipc a3, %pcrel_hi(a_symbol)
+# CHECK: ld  a3, %pcrel_lo(.Lpcrel_hi1)(a3)
+ld a3, a_symbol
+
+# CHECK: .Lpcrel_hi2:
+# CHECK: auipc a4, %pcrel_hi(a_symbol)
+# CHECK: sd  a3, %pcrel_lo(.Lpcrel_hi2)(a4)
+sd a3, a_symbol, a4
diff --git a/test/MC/RISCV/rvd-pseudos.s b/test/MC/RISCV/rvd-pseudos.s
new file mode 100644
index 0000000..5c146ca
--- /dev/null
+++ b/test/MC/RISCV/rvd-pseudos.s
@@ -0,0 +1,12 @@
+# RUN: llvm-mc %s -triple=riscv32 -mattr=+d | FileCheck %s
+# RUN: llvm-mc %s -triple=riscv64 -mattr=+d | FileCheck %s
+
+# CHECK: .Lpcrel_hi0:
+# CHECK: auipc a2, %pcrel_hi(a_symbol)
+# CHECK: fld  fa2, %pcrel_lo(.Lpcrel_hi0)(a2)
+fld fa2, a_symbol, a2
+
+# CHECK: .Lpcrel_hi1:
+# CHECK: auipc a3, %pcrel_hi(a_symbol)
+# CHECK: fsd  fa2, %pcrel_lo(.Lpcrel_hi1)(a3)
+fsd fa2, a_symbol, a3
diff --git a/test/MC/RISCV/rvf-pseudos.s b/test/MC/RISCV/rvf-pseudos.s
new file mode 100644
index 0000000..5907adf
--- /dev/null
+++ b/test/MC/RISCV/rvf-pseudos.s
@@ -0,0 +1,12 @@
+# RUN: llvm-mc %s -triple=riscv32 -mattr=+f | FileCheck %s
+# RUN: llvm-mc %s -triple=riscv64 -mattr=+f | FileCheck %s
+
+# CHECK: .Lpcrel_hi0:
+# CHECK: auipc a2, %pcrel_hi(a_symbol)
+# CHECK: flw  fa2, %pcrel_lo(.Lpcrel_hi0)(a2)
+flw fa2, a_symbol, a2
+
+# CHECK: .Lpcrel_hi1:
+# CHECK: auipc a3, %pcrel_hi(a_symbol)
+# CHECK: fsw  fa2, %pcrel_lo(.Lpcrel_hi1)(a3)
+fsw fa2, a_symbol, a3
diff --git a/test/MC/RISCV/rvi-pseudos-invalid.s b/test/MC/RISCV/rvi-pseudos-invalid.s
index 2ce1249..5d983c0 100644
--- a/test/MC/RISCV/rvi-pseudos-invalid.s
+++ b/test/MC/RISCV/rvi-pseudos-invalid.s
@@ -20,3 +20,10 @@
 la x1, %lo(1234) # CHECK: :[[@LINE]]:8: error: operand must be a bare symbol name
 la x1, %hi(foo) # CHECK: :[[@LINE]]:8: error: operand must be a bare symbol name
 la x1, %lo(foo) # CHECK: :[[@LINE]]:8: error: operand must be a bare symbol name
+
+sw a2, %hi(a_symbol), a3 # CHECK: :[[@LINE]]:8: error: operand must be a symbol with %lo/%pcrel_lo modifier or an integer in the range [-2048, 2047]
+sw a2, %lo(a_symbol), a3 # CHECK: :[[@LINE]]:23: error: invalid operand for instruction
+sw a2, %lo(a_symbol)(a4), a3 # CHECK: :[[@LINE]]:27: error: invalid operand for instruction
+
+# Too few operands must be rejected
+sw a2, a_symbol # CHECK: :[[@LINE]]:1: error: too few operands for instruction
diff --git a/test/MC/RISCV/rvi-pseudos.s b/test/MC/RISCV/rvi-pseudos.s
index 5b2ac27..41e6e9c 100644
--- a/test/MC/RISCV/rvi-pseudos.s
+++ b/test/MC/RISCV/rvi-pseudos.s
@@ -71,3 +71,49 @@
 # CHECK-PIC-RV32: lw    a4, %pcrel_lo(.Lpcrel_hi9)(a4)
 # CHECK-PIC-RV64: ld    a4, %pcrel_lo(.Lpcrel_hi9)(a4)
 la a4, f1
+
+# CHECK: .Lpcrel_hi10:
+# CHECK: auipc a0, %pcrel_hi(a_symbol)
+# CHECK: lb  a0, %pcrel_lo(.Lpcrel_hi10)(a0)
+lb a0, a_symbol
+
+# CHECK: .Lpcrel_hi11:
+# CHECK: auipc a1, %pcrel_hi(a_symbol)
+# CHECK: lh  a1, %pcrel_lo(.Lpcrel_hi11)(a1)
+lh a1, a_symbol
+
+# CHECK: .Lpcrel_hi12:
+# CHECK: auipc a2, %pcrel_hi(a_symbol)
+# CHECK: lhu  a2, %pcrel_lo(.Lpcrel_hi12)(a2)
+lhu a2, a_symbol
+
+# CHECK: .Lpcrel_hi13:
+# CHECK: auipc a3, %pcrel_hi(a_symbol)
+# CHECK: lw  a3, %pcrel_lo(.Lpcrel_hi13)(a3)
+lw a3, a_symbol
+
+# CHECK: .Lpcrel_hi14:
+# CHECK: auipc a4, %pcrel_hi(a_symbol)
+# CHECK: sb  a3, %pcrel_lo(.Lpcrel_hi14)(a4)
+sb a3, a_symbol, a4
+
+# CHECK: .Lpcrel_hi15:
+# CHECK: auipc a4, %pcrel_hi(a_symbol)
+# CHECK: sh  a3, %pcrel_lo(.Lpcrel_hi15)(a4)
+sh a3, a_symbol, a4
+
+# CHECK: .Lpcrel_hi16:
+# CHECK: auipc a4, %pcrel_hi(a_symbol)
+# CHECK: sw  a3, %pcrel_lo(.Lpcrel_hi16)(a4)
+sw a3, a_symbol, a4
+
+# Check that we can load the address of symbols that are spelled like a register
+# CHECK: .Lpcrel_hi17:
+# CHECK: auipc a2, %pcrel_hi(zero)
+# CHECK: lw  a2, %pcrel_lo(.Lpcrel_hi17)(a2)
+lw a2, zero
+
+# CHECK: .Lpcrel_hi18:
+# CHECK: auipc a4, %pcrel_hi(zero)
+# CHECK: sw  a3, %pcrel_lo(.Lpcrel_hi18)(a4)
+sw a3, zero, a4