[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