[llvm] 55587f7 - [CodeGen] Mark read_register of allocatable physreg as live-in (#200825)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 3 14:57:18 PDT 2026
Author: Adhemerval Zanella
Date: 2026-06-03T14:57:12-07:00
New Revision: 55587f7b757583b1e30c3027803ed092790471a3
URL: https://github.com/llvm/llvm-project/commit/55587f7b757583b1e30c3027803ed092790471a3
DIFF: https://github.com/llvm/llvm-project/commit/55587f7b757583b1e30c3027803ed092790471a3.diff
LOG: [CodeGen] Mark read_register of allocatable physreg as live-in (#200825)
llvm.read_register / llvm.read_volatile_register of an allocatable
register (e.g. the MSVC __getReg/__getRegFp intrinsics reading xN/dN)
was lowered to a plain COPY from the physical register. 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).
Reading such a register only makes sense as "whatever value it currently
holds", i.e. like an inline-asm read; it cannot be modelled as a normal
SSA use of a physical register, and a live-in only works in the entry
block.
Lower these reads in AArch64 like the existing MRS/MSR sysreg path:
select to a small pseudo (READ_REGISTER_GPR64 / READ_REGISTER_FPR64)
that carries the source register as an immediate operand rather than a
tracked physical-register use, and have the AsmPrinter materialize the
real "mov Xt, Xn" / "fmov Dt, Dn". The verifier never sees an undefined
physreg use, so the read is valid in any block and needs no live-in or
undef. Reads of reserved registers (e.g. sp) and system registers keep
their existing lowering.
Added:
llvm/test/CodeGen/AArch64/read-reg-non-entry.ll
Modified:
llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
llvm/lib/Target/AArch64/AArch64ISelLowering.h
llvm/lib/Target/AArch64/AArch64InstrInfo.td
llvm/test/CodeGen/AArch64/read-fp-reg.ll
llvm/test/CodeGen/AArch64/write-volatile-register.ll
Removed:
################################################################################
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 b619f525ae9a8..b83ff4aa550c3 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
@@ -4304,6 +4304,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;
}
}
@@ -4388,8 +4406,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 21515d3568e4c..ec52647135722 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -13135,18 +13135,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
diff erent 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 82d4fcedc5a5e..f23ae6501b285 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