[llvm] [CodeGen] Mark read_register of allocatable physreg as live-in (PR #200825)

Adhemerval Zanella via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 3 07:16:34 PDT 2026


https://github.com/zatrazz updated https://github.com/llvm/llvm-project/pull/200825

>From 644e05e00e39751788adbdf5459cdc4bfb60833a Mon Sep 17 00:00:00 2001
From: Adhemerval Zanella <zatrazz at gmail.com>
Date: Tue, 2 Jun 2026 13:19:54 -0300
Subject: [PATCH] [AArch64] Access named allocatable registers via
 register-immediate pseudos

llvm.read_register / llvm.write_register of an allocatable register (e.g. the
MSVC __getReg/__getRegFp/__setReg intrinsics naming xN/dN) was lowered through
the generic SelectionDAG path to a COPY to/from the physical register. For a
read, that COPY uses a physical register that is never defined, which the
machine verifier rejects as "Using an undefined physical register" (seen on
read-fp-reg.ll under LLVM_ENABLE_EXPENSIVE_CHECKS).

Such an access should act as an inline asm ("whatever value the register currently
holds"); it cannot be modelled as a normal SSA use of a physical register, and
a live-in is only valid in the entry block.

Handle these accesses in AArch64 like the existing MRS/MSR sysreg path, with
the register carried as an immediate rather than a tracked physical-register
operand.

getRegisterByName now rejects allocatable registers, so the generic
read/write_register fallback emits a clear diagnostic instead of invalid code.
Reserved registers (e.g. sp) and system registers keep their existing
lowering.
---
 llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp | 16 +++++++
 .../Target/AArch64/AArch64ISelDAGToDAG.cpp    | 40 ++++++++++++++++-
 .../Target/AArch64/AArch64ISelLowering.cpp    | 20 +++++----
 llvm/lib/Target/AArch64/AArch64ISelLowering.h |  6 +++
 llvm/lib/Target/AArch64/AArch64InstrInfo.td   |  9 ++++
 llvm/test/CodeGen/AArch64/read-fp-reg.ll      |  2 +-
 .../CodeGen/AArch64/read-reg-non-entry.ll     | 45 +++++++++++++++++++
 .../AArch64/write-volatile-register.ll        |  2 +-
 8 files changed, 129 insertions(+), 11 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/read-reg-non-entry.ll

diff --git a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
index 4eb475ef606de..b16c0460adf38 100644
--- a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
+++ b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
@@ -3178,6 +3178,22 @@ void AArch64AsmPrinter::emitInstruction(const MachineInstr *MI) {
     assert(!AArch64InstrInfo::isTailCallReturnInst(*MI) &&
            "Unhandled tail call instruction");
     break;
+  case AArch64::READ_REGISTER_GPR64:
+    // Read of a named GPR: emit "mov Xt, Xn" (ORR Xt, XZR, Xn). The source
+    // register is encoded as an immediate operand so that earlier passes do not
+    // see a use of an undefined physical register.
+    EmitToStreamer(*OutStreamer, MCInstBuilder(AArch64::ORRXrs)
+                                     .addReg(MI->getOperand(0).getReg())
+                                     .addReg(AArch64::XZR)
+                                     .addReg(MI->getOperand(1).getImm())
+                                     .addImm(0));
+    return;
+  case AArch64::READ_REGISTER_FPR64:
+    // Read of a named FP/SIMD d-register: emit "fmov Dt, Dn".
+    EmitToStreamer(*OutStreamer, MCInstBuilder(AArch64::FMOVDr)
+                                     .addReg(MI->getOperand(0).getReg())
+                                     .addReg(MI->getOperand(1).getImm()));
+    return;
   case AArch64::HINT: {
     // CurrentPatchableFunctionEntrySym can be CurrentFnBegin only for
     // -fpatchable-function-entry=N,0. The entry MBB is guaranteed to be
diff --git a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
index f25f45a058aa8..591eaad420df3 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
@@ -4309,6 +4309,24 @@ bool AArch64DAGToDAGISel::tryReadRegister(SDNode *N) {
         Opcode64Bit = AArch64::ADR;
         Imm = 0;
       } else {
+        // Not a system register. It may name an allocatable 64-bit GPR/FPR read
+        // by the MSVC __getReg/__getRegFp intrinsics. Emit a pseudo that
+        // carries the source register as an immediate so the read does not
+        // reference an undefined physical register (which the machine verifier
+        // rejects); the AsmPrinter materializes the real mov/fmov.
+        Register PReg = Subtarget->getTargetLowering()->matchRegisterName(
+            RegString->getString());
+        unsigned PseudoOp = 0;
+        if (AArch64::GPR64RegClass.contains(PReg))
+          PseudoOp = AArch64::READ_REGISTER_GPR64;
+        else if (AArch64::FPR64RegClass.contains(PReg))
+          PseudoOp = AArch64::READ_REGISTER_FPR64;
+        if (!ReadIs128Bit && PseudoOp && N->getValueType(0) == MVT::i64) {
+          CurDAG->SelectNodeTo(N, PseudoOp, MVT::i64, MVT::Other,
+                               {CurDAG->getTargetConstant(PReg, DL, MVT::i32),
+                                N->getOperand(0)});
+          return true;
+        }
         return false;
       }
     }
@@ -4393,8 +4411,28 @@ bool AArch64DAGToDAGISel::tryWriteRegister(SDNode *N) {
     else
       Imm = AArch64SysReg::parseGenericRegister(RegString->getString());
 
-    if (Imm == -1)
+    if (Imm == -1) {
+      // Used by the MSVC __setReg/__setRegFp intrinsics. Copy the value into
+      // the physical register and keep it live with a FAKE_USE so the write is
+      // not dead-eliminated. (getRegisterByName rejects allocatable registers,
+      // so the generic write path cannot handle these.)
+      Register PReg = Subtarget->getTargetLowering()->matchRegisterName(
+          RegString->getString());
+      bool IsGPR = AArch64::GPR64RegClass.contains(PReg);
+      bool IsFPR = AArch64::FPR64RegClass.contains(PReg);
+      if (!WriteIs128Bit && (IsGPR || IsFPR) &&
+          N->getOperand(2).getValueType() == MVT::i64) {
+        SDValue Copy =
+            CurDAG->getCopyToReg(N->getOperand(0), DL, PReg, N->getOperand(2));
+        SDValue RegOp = CurDAG->getRegister(PReg, MVT::i64);
+        SDNode *FakeUse = CurDAG->getMachineNode(TargetOpcode::FAKE_USE, DL,
+                                                 MVT::Other, {RegOp, Copy});
+        ReplaceUses(SDValue(N, 0), SDValue(FakeUse, 0));
+        CurDAG->RemoveDeadNode(N);
+        return true;
+      }
       return false;
+    }
   }
 
   SDValue InChain = N->getOperand(0);
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 89466adf7d4f3..46f3abec568f3 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -13136,18 +13136,22 @@ SDValue AArch64TargetLowering::LowerSPONENTRY(SDValue Op,
 #define GET_REGISTER_MATCHER
 #include "AArch64GenAsmMatcher.inc"
 
+Register AArch64TargetLowering::matchRegisterName(StringRef RegName) const {
+  return MatchRegisterName(RegName);
+}
+
 // FIXME? Maybe this could be a TableGen attribute on some registers and
 // this table could be generated automatically from RegInfo.
 Register AArch64TargetLowering::
 getRegisterByName(const char* RegName, LLT VT, const MachineFunction &MF) const {
-  Register Reg = MatchRegisterName(RegName);
-  if (AArch64::X1 <= Reg && Reg <= AArch64::X28) {
-    const AArch64RegisterInfo *MRI = Subtarget->getRegisterInfo();
-    unsigned DwarfRegNum = MRI->getDwarfRegNum(Reg, false);
-    if (!Subtarget->isXRegisterReserved(DwarfRegNum) &&
-        !MRI->isReservedReg(MF, Reg))
-      Reg = Register();
-  }
+  Register Reg = matchRegisterName(RegName);
+  // llvm.read_register / llvm.write_register instrisinc handled through the
+  // generic SelectionDAG path can only access a register that is not
+  // allocatable: reading one would use an undefined physical register, and its
+  // contents are not meaningful. Reject anything allocatable here so the
+  // generic path diagnoses rather than emitting invalid code.
+  if (Reg && !Subtarget->getRegisterInfo()->isReservedReg(MF, Reg))
+    Reg = Register();
   return Reg;
 }
 
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.h b/llvm/lib/Target/AArch64/AArch64ISelLowering.h
index 3a93d9a3c0d1e..7e4c4e1ba25ff 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.h
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.h
@@ -596,6 +596,11 @@ class AArch64TargetLowering : public TargetLowering {
   /// semantics to be preserved for instruction selection.
   bool shouldPreservePtrArith(const Function &F, EVT PtrVT) const override;
 
+  // Match a register name (e.g. "x5", "d5", "sp") to its register number, with
+  // no validity filtering. This is the single entry point for the generated
+  // register-name matcher, shared with getRegisterByName.
+  Register matchRegisterName(StringRef RegName) const;
+
 private:
   /// Keep a pointer to the AArch64Subtarget around so that we can
   /// make the right decision when generating code for different targets.
@@ -841,6 +846,7 @@ class AArch64TargetLowering : public TargetLowering {
   Register getRegisterByName(const char* RegName, LLT VT,
                              const MachineFunction &MF) const override;
 
+private:
   /// Examine constraint string and operand type and determine a weight value.
   /// The operand object must already have been set up with the operand type.
   ConstraintWeight
diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.td b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
index 79dea283a1d23..a1261c80b537a 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -1402,6 +1402,15 @@ def PROBED_STACKALLOC_DYN : Pseudo<(outs),
 } // Defs = [SP, NZCV], Uses = [SP] in
 } // hasSideEffects = 1, isCodeGenOnly = 1
 
+// Read of an allocatable register by name (e.g. the MSVC __getReg/__getRegFp
+// intrinsics, lowered from llvm.read[_volatile]_register).
+let hasSideEffects = 1, mayLoad = 1, Size = 4, isCodeGenOnly = 1 in {
+def READ_REGISTER_GPR64 : Pseudo<(outs GPR64:$Rt), (ins i32imm:$reg), []>,
+                          Sched<[]>;
+def READ_REGISTER_FPR64 : Pseudo<(outs FPR64:$Rt), (ins i32imm:$reg), []>,
+                          Sched<[]>;
+}
+
 let isReMaterializable = 1, isCodeGenOnly = 1 in {
 // FIXME: The following pseudo instructions are only needed because remat
 // cannot handle multiple instructions.  When that changes, they can be
diff --git a/llvm/test/CodeGen/AArch64/read-fp-reg.ll b/llvm/test/CodeGen/AArch64/read-fp-reg.ll
index 853f1fc051716..02390cc30b5bb 100644
--- a/llvm/test/CodeGen/AArch64/read-fp-reg.ll
+++ b/llvm/test/CodeGen/AArch64/read-fp-reg.ll
@@ -1,5 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
-; RUN: llc -mtriple=aarch64 -fast-isel=0 -global-isel=false < %s | FileCheck %s
+; RUN: llc -mtriple=aarch64 -fast-isel=0 -global-isel=false -verify-machineinstrs < %s | FileCheck %s
 
 define double @test_getRegFp_d5() {
 ; CHECK-LABEL: test_getRegFp_d5:
diff --git a/llvm/test/CodeGen/AArch64/read-reg-non-entry.ll b/llvm/test/CodeGen/AArch64/read-reg-non-entry.ll
new file mode 100644
index 0000000000000..b87e86ed21dc2
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/read-reg-non-entry.ll
@@ -0,0 +1,45 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=aarch64 -fast-isel=0 -global-isel=false -verify-machineinstrs < %s | FileCheck %s
+
+define double @getRegFp_d5_branch(i1 %c) {
+; CHECK-LABEL: getRegFp_d5_branch:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    movi d0, #0000000000000000
+; CHECK-NEXT:    tbz w0, #0, .LBB0_2
+; CHECK-NEXT:  // %bb.1: // %then
+; CHECK-NEXT:    fmov d0, d5
+; CHECK-NEXT:  .LBB0_2: // %common.ret
+; CHECK-NEXT:    ret
+entry:
+  br i1 %c, label %then, label %else
+then:
+  %0 = call i64 @llvm.read_volatile_register.i64(metadata !0)
+  %1 = bitcast i64 %0 to double
+  ret double %1
+else:
+  ret double 0.0
+}
+
+define i64 @getReg_x5_branch(i1 %c) {
+; CHECK-LABEL: getReg_x5_branch:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    tbz w0, #0, .LBB1_2
+; CHECK-NEXT:  // %bb.1: // %then
+; CHECK-NEXT:    mov x0, x5
+; CHECK-NEXT:    ret
+; CHECK-NEXT:  .LBB1_2:
+; CHECK-NEXT:    mov x0, xzr
+; CHECK-NEXT:    ret
+entry:
+  br i1 %c, label %then, label %else
+then:
+  %0 = call i64 @llvm.read_volatile_register.i64(metadata !1)
+  ret i64 %0
+else:
+  ret i64 0
+}
+
+declare i64 @llvm.read_volatile_register.i64(metadata)
+
+!0 = !{!"d5"}
+!1 = !{!"x5"}
diff --git a/llvm/test/CodeGen/AArch64/write-volatile-register.ll b/llvm/test/CodeGen/AArch64/write-volatile-register.ll
index ad8292905583a..2720e90233986 100644
--- a/llvm/test/CodeGen/AArch64/write-volatile-register.ll
+++ b/llvm/test/CodeGen/AArch64/write-volatile-register.ll
@@ -1,5 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
-; RUN: llc -mtriple=aarch64 -fast-isel=0 -global-isel=false < %s | FileCheck %s
+; RUN: llc -mtriple=aarch64 -fast-isel=0 -global-isel=false -verify-machineinstrs < %s | FileCheck %s
 
 ; Tests for llvm.write_volatile_register on AArch64.
 ;



More information about the llvm-commits mailing list