[llvm] [IR][CodeGen] Add llvm.is.debugging.enabled intrinsic (PR #219175)

Alexander Hück via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 28 06:43:30 PDT 2026


https://github.com/ahueck updated https://github.com/llvm/llvm-project/pull/219175

>From f27cfacb675eff62c040cda83321d2df12bfd5c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexander=20H=C3=BCck?= <alexander.huck at amd.com>
Date: Mon, 17 Aug 2026 07:28:30 -0400
Subject: [PATCH 1/3] [AMDGPU][GlobalISel] Make control-flow intrinsic matching
 non-mutating

Refactor AMDGPULegalizer's control-flow intrinsic handling to separate
structural matching from MIR mutation. Replace verifyCFIntrinsic with an
explicit match-and-commit flow, and share branch fallthrough redirection
across the affected legalization paths.
---
 .../lib/Target/AMDGPU/AMDGPULegalizerInfo.cpp | 134 ++++++++++--------
 .../GlobalISel/legalize-amdgcn.if-invalid.mir |  27 +++-
 2 files changed, 97 insertions(+), 64 deletions(-)

diff --git a/llvm/lib/Target/AMDGPU/AMDGPULegalizerInfo.cpp b/llvm/lib/Target/AMDGPU/AMDGPULegalizerInfo.cpp
index 7c1a26f761c96..ed60350c7fc2d 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPULegalizerInfo.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPULegalizerInfo.cpp
@@ -4804,47 +4804,77 @@ static bool isNot(const MachineRegisterInfo &MRI, const MachineInstr &MI) {
   return ConstVal == -1;
 }
 
-// Return the use branch instruction, otherwise null if the usage is invalid.
-static MachineInstr *
-verifyCFIntrinsic(MachineInstr &MI, MachineRegisterInfo &MRI, MachineInstr *&Br,
-                  MachineBasicBlock *&UncondBrTarget, bool &Negated) {
+namespace {
+struct CFIntrinsicBranchMatch {
+  MachineInstr *Negation = nullptr;
+  MachineInstr *CondBr = nullptr;
+  MachineInstr *UncondBr = nullptr;
+
+  MachineBasicBlock *ConditionTrueTarget = nullptr;
+  MachineBasicBlock *ConditionFalseTarget = nullptr;
+
+  bool isNegated() const { return Negation != nullptr; }
+
+  // Retarget the explicit branch for the fallthrough edge, or materialize
+  // the branch when that edge was represented by layout fallthrough. The
+  // builder must be positioned at the replacement branch.
+  void redirectFallthroughEdge(MachineIRBuilder &B,
+                               MachineBasicBlock &Target) const {
+    if (UncondBr)
+      UncondBr->getOperand(0).setMBB(&Target);
+    else
+      B.buildBr(Target);
+  }
+
+  void eraseDeadNegation(MachineRegisterInfo &MRI) {
+    if (Negation)
+      eraseInstr(*Negation, MRI);
+  }
+};
+} // namespace
+
+static std::optional<CFIntrinsicBranchMatch>
+matchCFIntrinsicBranchUse(MachineInstr &MI, MachineRegisterInfo &MRI) {
   Register CondDef = MI.getOperand(0).getReg();
   if (!MRI.hasOneNonDBGUse(CondDef))
-    return nullptr;
+    return std::nullopt;
 
   MachineBasicBlock *Parent = MI.getParent();
   MachineInstr *UseMI = &*MRI.use_instr_nodbg_begin(CondDef);
+  MachineInstr *Negation = nullptr;
 
   if (isNot(MRI, *UseMI)) {
+    Negation = UseMI;
     Register NegatedCond = UseMI->getOperand(0).getReg();
     if (!MRI.hasOneNonDBGUse(NegatedCond))
-      return nullptr;
-
-    // We're deleting the def of this value, so we need to remove it.
-    eraseInstr(*UseMI, MRI);
+      return std::nullopt;
 
     UseMI = &*MRI.use_instr_nodbg_begin(NegatedCond);
-    Negated = true;
   }
 
   if (UseMI->getParent() != Parent || UseMI->getOpcode() != AMDGPU::G_BRCOND)
-    return nullptr;
+    return std::nullopt;
 
   // Make sure the cond br is followed by a G_BR, or is the last instruction.
+  MachineInstr *UncondBr = nullptr;
+  MachineBasicBlock *OtherTarget = nullptr;
   MachineBasicBlock::iterator Next = std::next(UseMI->getIterator());
   if (Next == Parent->end()) {
     MachineFunction::iterator NextMBB = std::next(Parent->getIterator());
     if (NextMBB == Parent->getParent()->end()) // Illegal intrinsic use.
-      return nullptr;
-    UncondBrTarget = &*NextMBB;
+      return std::nullopt;
+    OtherTarget = &*NextMBB;
   } else {
     if (Next->getOpcode() != AMDGPU::G_BR)
-      return nullptr;
-    Br = &*Next;
-    UncondBrTarget = Br->getOperand(0).getMBB();
+      return std::nullopt;
+    UncondBr = &*Next;
+    OtherTarget = UncondBr->getOperand(0).getMBB();
   }
 
-  return UseMI;
+  MachineBasicBlock *TakenTarget = UseMI->getOperand(1).getMBB();
+  return CFIntrinsicBranchMatch{Negation, UseMI, UncondBr,
+                                Negation ? OtherTarget : TakenTarget,
+                                Negation ? TakenTarget : OtherTarget};
 }
 
 void AMDGPULegalizerInfo::buildLoadInputValue(Register DstReg,
@@ -8230,80 +8260,58 @@ bool AMDGPULegalizerInfo::legalizeIntrinsic(LegalizerHelper &Helper,
     return true;
   case Intrinsic::amdgcn_if:
   case Intrinsic::amdgcn_else: {
-    MachineInstr *Br = nullptr;
-    MachineBasicBlock *UncondBrTarget = nullptr;
-    bool Negated = false;
-    if (MachineInstr *BrCond =
-            verifyCFIntrinsic(MI, MRI, Br, UncondBrTarget, Negated)) {
-      const SIRegisterInfo *TRI
-        = static_cast<const SIRegisterInfo *>(MRI.getTargetRegisterInfo());
+    if (auto Match = matchCFIntrinsicBranchUse(MI, MRI)) {
+      const SIRegisterInfo *TRI =
+          static_cast<const SIRegisterInfo *>(MRI.getTargetRegisterInfo());
 
       Register Def = MI.getOperand(1).getReg();
       Register Use = MI.getOperand(3).getReg();
 
-      MachineBasicBlock *CondBrTarget = BrCond->getOperand(1).getMBB();
-
-      if (Negated)
-        std::swap(CondBrTarget, UncondBrTarget);
-
-      B.setInsertPt(B.getMBB(), BrCond->getIterator());
+      B.setInsertPt(B.getMBB(), Match->CondBr->getIterator());
       if (IntrID == Intrinsic::amdgcn_if) {
         B.buildInstr(AMDGPU::SI_IF)
-          .addDef(Def)
-          .addUse(Use)
-          .addMBB(UncondBrTarget);
+            .addDef(Def)
+            .addUse(Use)
+            .addMBB(Match->ConditionFalseTarget);
       } else {
         B.buildInstr(AMDGPU::SI_ELSE)
             .addDef(Def)
             .addUse(Use)
-            .addMBB(UncondBrTarget);
+            .addMBB(Match->ConditionFalseTarget);
       }
 
-      if (Br) {
-        Br->getOperand(0).setMBB(CondBrTarget);
-      } else {
-        // The IRTranslator skips inserting the G_BR for fallthrough cases, but
-        // since we're swapping branch targets it needs to be reinserted.
-        // FIXME: IRTranslator should probably not do this
-        B.buildBr(*CondBrTarget);
-      }
+      // The IRTranslator skips inserting the G_BR for fallthrough cases, but
+      // since we're swapping branch targets it needs to be reinserted.
+      // FIXME: IRTranslator should probably not do this
+      Match->redirectFallthroughEdge(B, *Match->ConditionTrueTarget);
 
       MRI.setRegClass(Def, TRI->getWaveMaskRegClass());
       MRI.setRegClass(Use, TRI->getWaveMaskRegClass());
+      Match->eraseDeadNegation(MRI);
       MI.eraseFromParent();
-      BrCond->eraseFromParent();
+      Match->CondBr->eraseFromParent();
       return true;
     }
 
     return false;
   }
   case Intrinsic::amdgcn_loop: {
-    MachineInstr *Br = nullptr;
-    MachineBasicBlock *UncondBrTarget = nullptr;
-    bool Negated = false;
-    if (MachineInstr *BrCond =
-            verifyCFIntrinsic(MI, MRI, Br, UncondBrTarget, Negated)) {
-      const SIRegisterInfo *TRI
-        = static_cast<const SIRegisterInfo *>(MRI.getTargetRegisterInfo());
-
-      MachineBasicBlock *CondBrTarget = BrCond->getOperand(1).getMBB();
-      Register Reg = MI.getOperand(2).getReg();
+    if (auto Match = matchCFIntrinsicBranchUse(MI, MRI)) {
+      const SIRegisterInfo *TRI =
+          static_cast<const SIRegisterInfo *>(MRI.getTargetRegisterInfo());
 
-      if (Negated)
-        std::swap(CondBrTarget, UncondBrTarget);
+      Register Reg = MI.getOperand(2).getReg();
 
-      B.setInsertPt(B.getMBB(), BrCond->getIterator());
+      B.setInsertPt(B.getMBB(), Match->CondBr->getIterator());
       B.buildInstr(AMDGPU::SI_LOOP)
-        .addUse(Reg)
-        .addMBB(UncondBrTarget);
+          .addUse(Reg)
+          .addMBB(Match->ConditionFalseTarget);
 
-      if (Br)
-        Br->getOperand(0).setMBB(CondBrTarget);
-      else
-        B.buildBr(*CondBrTarget);
+      Match->redirectFallthroughEdge(B, *Match->ConditionTrueTarget);
 
+      Match->eraseDeadNegation(MRI);
       MI.eraseFromParent();
-      BrCond->eraseFromParent();
+      Match->CondBr->eraseFromParent();
       MRI.setRegClass(Reg, TRI->getWaveMaskRegClass());
       return true;
     }
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/legalize-amdgcn.if-invalid.mir b/llvm/test/CodeGen/AMDGPU/GlobalISel/legalize-amdgcn.if-invalid.mir
index d351eb6a57446..77040216daca8 100644
--- a/llvm/test/CodeGen/AMDGPU/GlobalISel/legalize-amdgcn.if-invalid.mir
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/legalize-amdgcn.if-invalid.mir
@@ -1,4 +1,5 @@
 # RUN: llc -mtriple=amdgpu8.03-mesa-mesa3d -O0 -run-pass=legalizer -global-isel-abort=2 -pass-remarks-missed='gisel*' -filetype=null %s 2>&1  | FileCheck -check-prefix=ERR %s
+# RUN: llc -mtriple=amdgpu8.03-mesa-mesa3d -O0 -run-pass=legalizer -global-isel-abort=0 %s -o - | FileCheck -check-prefix=MIR %s
 
 # Make sure incorrect usage of control flow intrinsics fails to select in case some transform separated the intrinsic from its branch.
 
@@ -8,7 +9,7 @@
 # ERR-NEXT: remark: <unknown>:0:0: unable to legalize instruction: %3:_(s1), %4:_(s64) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.amdgcn.if), %2:_(s1) (in function: brcond_si_if_xor_0)
 # ERR-NEXT: remark: <unknown>:0:0: unable to legalize instruction: %3:_(s1), %4:_(s64) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.amdgcn.if), %2:_(s1) (in function: brcond_si_if_or_neg1)
 # ERR-NEXT: remark: <unknown>:0:0: unable to legalize instruction: %3:_(s1), %4:_(s64) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.amdgcn.if), %2:_(s1) (in function: brcond_si_if_negated_multi_use)
-
+# ERR-NEXT: remark: <unknown>:0:0: unable to legalize instruction: %3:_(s1), %4:_(s64) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.amdgcn.if), %2:_(s1) (in function: si_if_negated_invalid_branch_layout)
 
 ---
 name: brcond_si_if_different_block
@@ -134,3 +135,27 @@ body:             |
   bb.3:
     S_NOP 2
 ...
+
+# A failed match after recognizing the negation must not modify the MIR.
+# MIR-LABEL: name: si_if_negated_invalid_branch_layout
+# MIR: [[NOT:%[0-9]+]]:_(s1) = G_XOR
+# MIR-NEXT: G_BRCOND [[NOT]](s1), %bb.1
+# MIR-NEXT: S_ENDPGM 0
+---
+name: si_if_negated_invalid_branch_layout
+body:             |
+  bb.0:
+    successors: %bb.1
+    liveins: $vgpr0, $vgpr1
+    %0:_(s32) = COPY $vgpr0
+    %1:_(s32) = COPY $vgpr1
+    %2:_(s1) = G_ICMP intpred(ne), %0, %1
+    %3:_(s1), %4:_(s64) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.amdgcn.if), %2
+    %5:_(s1) = G_CONSTANT i1 true
+    %6:_(s1) = G_XOR %3, %5
+    G_BRCOND %6, %bb.1
+    S_ENDPGM 0
+
+  bb.1:
+    S_ENDPGM 0
+...

>From 06d95fa53572e8da877a6c513a093a96a8017f84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexander=20H=C3=BCck?= <alexander.huck at amd.com>
Date: Mon, 17 Aug 2026 11:58:25 -0400
Subject: [PATCH 2/3] [AMDGPU][SelectionDAG] Refactor control-flow branch
 matching

Introduce BRCONDMatch to normalize wrapped and negated branch conditions
and record their true and false targets. Use the match in LowerBRCOND to
centralize fallthrough redirection and separate branch matching from
control-flow intrinsic lowering.
---
 llvm/lib/Target/AMDGPU/SIISelLowering.cpp | 124 ++++++++++++++--------
 1 file changed, 81 insertions(+), 43 deletions(-)

diff --git a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
index d9810e3d9fbd9..86cfd885c4f8f 100644
--- a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
+++ b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
@@ -8584,6 +8584,72 @@ static SDNode *findUser(SDValue Value, unsigned Opcode) {
   return nullptr;
 }
 
+namespace {
+struct BRCONDMatch {
+  SDValue CondBr;
+  SDValue Condition;
+  SDValue ConditionWrapper;
+  SDNode *UncondBr = nullptr;
+
+  SDValue ConditionTrueTarget;
+  SDValue ConditionFalseTarget;
+
+  void redirectFallthroughEdge(SelectionDAG &DAG, SDValue Target) const {
+    if (UncondBr->getOperand(1) == Target)
+      return;
+
+    SDValue NewBr = DAG.getNode(ISD::BR, SDLoc(CondBr), UncondBr->getVTList(),
+                                {UncondBr->getOperand(0), Target});
+    DAG.ReplaceAllUsesWith(UncondBr, NewBr.getNode());
+  }
+};
+} // namespace
+
+static std::optional<BRCONDMatch> matchBRCOND(SDValue CondBr) {
+  if (CondBr.getOpcode() != ISD::BRCOND)
+    return std::nullopt;
+
+  SDValue Condition = CondBr.getOperand(1);
+  SDValue ConditionWrapper;
+  bool IsNegated = false;
+
+  switch (Condition.getOpcode()) {
+  case ISD::SETCC: {
+    ISD::CondCode CC = cast<CondCodeSDNode>(Condition.getOperand(2))->get();
+    if (auto *C = dyn_cast<ConstantSDNode>(Condition.getOperand(1));
+        C && (CC == ISD::SETEQ || CC == ISD::SETNE)) {
+      IsNegated = (CC == ISD::SETEQ) == (C->getZExtValue() == 0);
+      ConditionWrapper = Condition;
+      Condition = Condition.getOperand(0);
+    }
+    break;
+  }
+  case ISD::XOR:
+    if (auto *C = dyn_cast<ConstantSDNode>(Condition.getOperand(1));
+        C && C->getZExtValue()) {
+      ConditionWrapper = Condition;
+      Condition = Condition.getOperand(0);
+      IsNegated = true;
+    }
+    break;
+  default:
+    break;
+  }
+
+  SDNode *UncondBr = findUser(CondBr, ISD::BR);
+  if (!UncondBr)
+    return std::nullopt;
+
+  SDValue TakenTarget = CondBr.getOperand(2);
+  SDValue OtherTarget = UncondBr->getOperand(1);
+  return BRCONDMatch{CondBr,
+                     Condition,
+                     ConditionWrapper,
+                     UncondBr,
+                     IsNegated ? OtherTarget : TakenTarget,
+                     IsNegated ? TakenTarget : OtherTarget};
+}
+
 unsigned SITargetLowering::isCFIntrinsic(const SDNode *Intr) const {
   if (Intr->getOpcode() == ISD::INTRINSIC_W_CHAIN) {
     switch (Intr->getConstantOperandVal(1)) {
@@ -8650,37 +8716,12 @@ bool SITargetLowering::shouldUseLDSConstAddress(const GlobalValue *GV) const {
 /// This transforms the control flow intrinsics to get the branch destination as
 /// last parameter, also switches branch target with BR if the need arise
 SDValue SITargetLowering::LowerBRCOND(SDValue BRCOND, SelectionDAG &DAG) const {
-  SDLoc DL(BRCOND);
+  auto Match = matchBRCOND(BRCOND);
+  if (!Match)
+    return BRCOND;
 
-  SDNode *Intr = BRCOND.getOperand(1).getNode();
-  SDValue Target = BRCOND.getOperand(2);
-  SDNode *BR = nullptr;
-  SDNode *SetCC = nullptr;
-
-  switch (Intr->getOpcode()) {
-  case ISD::SETCC: {
-    // As long as we negate the condition everything is fine
-    SetCC = Intr;
-    Intr = SetCC->getOperand(0).getNode();
-    break;
-  }
-  case ISD::XOR: {
-    // Similar to SETCC, if we have (xor c, -1), we will be fine.
-    SDValue LHS = Intr->getOperand(0);
-    SDValue RHS = Intr->getOperand(1);
-    if (auto *C = dyn_cast<ConstantSDNode>(RHS); C && C->getZExtValue()) {
-      Intr = LHS.getNode();
-      break;
-    }
-    [[fallthrough]];
-  }
-  default: {
-    // Get the target from BR if we don't negate the condition
-    BR = findUser(BRCOND, ISD::BR);
-    assert(BR && "brcond missing unconditional branch user");
-    Target = BR->getOperand(1);
-  }
-  }
+  SDLoc DL(Match->CondBr);
+  SDNode *Intr = Match->Condition.getNode();
 
   unsigned CFNode = isCFIntrinsic(Intr);
   if (CFNode == 0) {
@@ -8691,18 +8732,20 @@ SDValue SITargetLowering::LowerBRCOND(SDValue BRCOND, SelectionDAG &DAG) const {
   bool HaveChain = Intr->getOpcode() == ISD::INTRINSIC_VOID ||
                    Intr->getOpcode() == ISD::INTRINSIC_W_CHAIN;
 
-  assert(!SetCC ||
-         (SetCC->getConstantOperandVal(1) == 1 &&
-          cast<CondCodeSDNode>(SetCC->getOperand(2).getNode())->get() ==
-              ISD::SETNE));
+  assert((!Match->ConditionWrapper ||
+          Match->ConditionWrapper.getOpcode() != ISD::SETCC ||
+          (Match->ConditionWrapper.getConstantOperandVal(1) == 1 &&
+           cast<CondCodeSDNode>(Match->ConditionWrapper.getOperand(2).getNode())
+                   ->get() == ISD::SETNE)) &&
+         "unexpected control flow intrinsic condition wrapper");
 
   // operands of the new intrinsic call
   SmallVector<SDValue, 4> Ops;
   if (HaveChain)
-    Ops.push_back(BRCOND.getOperand(0));
+    Ops.push_back(Match->CondBr.getOperand(0));
 
   Ops.append(Intr->op_begin() + (HaveChain ? 2 : 1), Intr->op_end());
-  Ops.push_back(Target);
+  Ops.push_back(Match->ConditionFalseTarget);
 
   ArrayRef<EVT> Res(Intr->value_begin() + 1, Intr->value_end());
 
@@ -8710,17 +8753,12 @@ SDValue SITargetLowering::LowerBRCOND(SDValue BRCOND, SelectionDAG &DAG) const {
   SDNode *Result = DAG.getNode(CFNode, DL, DAG.getVTList(Res), Ops).getNode();
 
   if (!HaveChain) {
-    SDValue Ops[] = {SDValue(Result, 0), BRCOND.getOperand(0)};
+    SDValue Ops[] = {SDValue(Result, 0), Match->CondBr.getOperand(0)};
 
     Result = DAG.getMergeValues(Ops, DL).getNode();
   }
 
-  if (BR) {
-    // Give the branch instruction our target
-    SDValue Ops[] = {BR->getOperand(0), BRCOND.getOperand(2)};
-    SDValue NewBR = DAG.getNode(ISD::BR, DL, BR->getVTList(), Ops);
-    DAG.ReplaceAllUsesWith(BR, NewBR.getNode());
-  }
+  Match->redirectFallthroughEdge(DAG, Match->ConditionTrueTarget);
 
   SDValue Chain = SDValue(Result, Result->getNumValues() - 1);
 

>From 7029fca7ad337fbbe948a28b4dae5512b976f6b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexander=20H=C3=BCck?= <alexander.huck at amd.com>
Date: Mon, 24 Aug 2026 08:47:41 +0000
Subject: [PATCH 3/3] [IR][CodeGen] Add llvm.is.debugging.enabled

Add an intrinsic reporting whether debugging is enabled for the
target-defined execution context. Each call is a distinct observation, so
calls must not be removed, commoned, or reordered with one another.
This is modeled with inaccessiblemem readwrite and nomerge.

Add TargetMachine::canLowerIsDebuggingEnabled(), defaulting to false.
Pre-ISel intrinsic lowering replaces the intrinsic with false unless the
target opts in. A target that opts in is responsible for all of its
subtargets.
---
 llvm/docs/LangRef.md                          | 30 ++++++++++
 llvm/include/llvm/IR/Intrinsics.td            |  6 ++
 llvm/include/llvm/Target/TargetMachine.h      |  7 +++
 llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp |  8 +++
 llvm/test/Assembler/is-debugging-enabled.ll   | 11 ++++
 .../CodeGen/NVPTX/is-debugging-enabled.ll     | 11 ++++
 llvm/test/CodeGen/X86/is-debugging-enabled.ll | 12 ++++
 .../Transforms/DCE/is-debugging-enabled.ll    | 12 ++++
 .../EarlyCSE/is-debugging-enabled.ll          | 17 ++++++
 .../Transforms/LICM/is-debugging-enabled.ll   | 25 ++++++++
 .../LoopUnroll/is-debugging-enabled.ll        | 30 ++++++++++
 .../is-debugging-enabled.ll                   | 13 +++++
 .../SimplifyCFG/is-debugging-enabled.ll       | 58 +++++++++++++++++++
 llvm/test/Verifier/is-debugging-enabled.ll    | 15 +++++
 14 files changed, 255 insertions(+)
 create mode 100644 llvm/test/Assembler/is-debugging-enabled.ll
 create mode 100644 llvm/test/CodeGen/NVPTX/is-debugging-enabled.ll
 create mode 100644 llvm/test/CodeGen/X86/is-debugging-enabled.ll
 create mode 100644 llvm/test/Transforms/DCE/is-debugging-enabled.ll
 create mode 100644 llvm/test/Transforms/EarlyCSE/is-debugging-enabled.ll
 create mode 100644 llvm/test/Transforms/LICM/is-debugging-enabled.ll
 create mode 100644 llvm/test/Transforms/LoopUnroll/is-debugging-enabled.ll
 create mode 100644 llvm/test/Transforms/PreISelIntrinsicLowering/is-debugging-enabled.ll
 create mode 100644 llvm/test/Transforms/SimplifyCFG/is-debugging-enabled.ll
 create mode 100644 llvm/test/Verifier/is-debugging-enabled.ll

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index ee1600b96f1dd..9f2ab993f3a80 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -26263,6 +26263,36 @@ This intrinsic is lowered to code which is intended to cause an
 execution trap with the intention of requesting the attention of a
 debugger.
 
+(llvm.is.debugging.enabled)=
+
+#### '`llvm.is.debugging.enabled`' Intrinsic
+
+##### Syntax:
+
+```llvm
+declare noundef i1 @llvm.is.debugging.enabled() nomerge memory(inaccessiblemem: readwrite)
+```
+
+##### Overview:
+
+The '`llvm.is.debugging.enabled`' intrinsic returns whether debugging is enabled
+for the target-defined execution context of the current invocation.
+
+##### Arguments:
+
+None.
+
+##### Semantics:
+
+Each call observes the current debugging-enabled state. Calls are distinct
+observations: LLVM must not assume separate calls return the same value, and a
+call may not be removed when its result is unused, commoned with another call,
+or reordered with respect to one.
+
+On targets that do not support querying debugging-enabled state, the intrinsic
+returns `false` and performs no observation.
+
+
 (llvm.ubsantrap)=
 
 #### '`llvm.ubsantrap`' Intrinsic
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index c20ea64c7eef4..b14375e277e7a 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -2059,6 +2059,12 @@ def int_trap : Intrinsic<[], [],
                ClangBuiltin<"__builtin_trap">;
 def int_debugtrap : Intrinsic<[]>,
                     ClangBuiltin<"__builtin_debugtrap">;
+// Query whether debugging is enabled for the current execution context.
+def int_is_debugging_enabled :
+  DefaultAttrsIntrinsic<[llvm_i1_ty], [],
+                        [IntrInaccessibleMemOnly,
+                         IntrNoMerge,
+                         NoUndef<RetIndex>]>;
 def int_ubsantrap : Intrinsic<[], [llvm_i8_ty],
                               [IntrNoReturn, IntrCold, ImmArg<ArgIndex<0>>,
                                IntrInaccessibleMemOnly, IntrWriteMem]>;
diff --git a/llvm/include/llvm/Target/TargetMachine.h b/llvm/include/llvm/Target/TargetMachine.h
index a6b73d636dc27..4e8125d34524d 100644
--- a/llvm/include/llvm/Target/TargetMachine.h
+++ b/llvm/include/llvm/Target/TargetMachine.h
@@ -567,6 +567,13 @@ class LLVM_ABI TargetMachine {
   /// this function returns false, the intrinsic will be supported generically
   /// but without loop detection support.
   virtual bool canLowerCondLoop() const { return false; }
+
+  /// Returns whether this target takes responsibility for lowering
+  /// llvm.is.debugging.enabled. If false, generic lowering replaces the
+  /// intrinsic with false. If true, the target must handle every supported
+  /// subtarget, including replacing the intrinsic with false on subtargets
+  /// without native lowering.
+  virtual bool canLowerIsDebuggingEnabled() const { return false; }
 };
 
 } // end namespace llvm
diff --git a/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp b/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp
index 24788148b9f15..4ec866d7b973f 100644
--- a/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp
+++ b/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp
@@ -815,6 +815,14 @@ bool PreISelIntrinsicLowering::lowerIntrinsics(Module &M) const {
     case Intrinsic::protected_field_ptr:
       Changed |= expandProtectedFieldPtr(F);
       break;
+    case Intrinsic::is_debugging_enabled:
+      if (!TM || !TM->canLowerIsDebuggingEnabled())
+        Changed |= forEachCall(F, [](CallInst *CI) {
+          CI->replaceAllUsesWith(ConstantInt::getFalse(CI->getContext()));
+          CI->eraseFromParent();
+          return true;
+        });
+      break;
     case Intrinsic::cond_loop:
       if (!TM->canLowerCondLoop())
         Changed |= expandCondLoop(F);
diff --git a/llvm/test/Assembler/is-debugging-enabled.ll b/llvm/test/Assembler/is-debugging-enabled.ll
new file mode 100644
index 0000000000000..a48d002373e38
--- /dev/null
+++ b/llvm/test/Assembler/is-debugging-enabled.ll
@@ -0,0 +1,11 @@
+; RUN: llvm-as < %s | llvm-dis | FileCheck %s
+
+define i1 @query_debugging_enabled() {
+  %enabled = call i1 @llvm.is.debugging.enabled()
+  ret i1 %enabled
+}
+
+; CHECK: declare noundef i1 @llvm.is.debugging.enabled() #[[ATTRS:[0-9]+]]
+; CHECK: attributes #[[ATTRS]] = { nocallback nofree nomerge nosync nounwind willreturn memory(inaccessiblemem: readwrite) }
+
+declare i1 @llvm.is.debugging.enabled()
diff --git a/llvm/test/CodeGen/NVPTX/is-debugging-enabled.ll b/llvm/test/CodeGen/NVPTX/is-debugging-enabled.ll
new file mode 100644
index 0000000000000..d2794285b4466
--- /dev/null
+++ b/llvm/test/CodeGen/NVPTX/is-debugging-enabled.ll
@@ -0,0 +1,11 @@
+; RUN: llc -mtriple=nvptx64-nvidia-cuda < %s | FileCheck %s
+
+define i1 @query() {
+; CHECK-LABEL: query(
+; CHECK:       st.param.b32 [func_retval0], 0;
+; CHECK-NEXT:  ret;
+  %enabled = call i1 @llvm.is.debugging.enabled()
+  ret i1 %enabled
+}
+
+declare i1 @llvm.is.debugging.enabled()
diff --git a/llvm/test/CodeGen/X86/is-debugging-enabled.ll b/llvm/test/CodeGen/X86/is-debugging-enabled.ll
new file mode 100644
index 0000000000000..a2f8d98ce2081
--- /dev/null
+++ b/llvm/test/CodeGen/X86/is-debugging-enabled.ll
@@ -0,0 +1,12 @@
+; RUN: llc -mtriple=x86_64 < %s | FileCheck %s
+
+define i1 @query() {
+; CHECK-LABEL: query:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    xorl %eax, %eax
+; CHECK-NEXT:    retq
+  %enabled = call i1 @llvm.is.debugging.enabled()
+  ret i1 %enabled
+}
+
+declare i1 @llvm.is.debugging.enabled()
diff --git a/llvm/test/Transforms/DCE/is-debugging-enabled.ll b/llvm/test/Transforms/DCE/is-debugging-enabled.ll
new file mode 100644
index 0000000000000..ddf9d80648e78
--- /dev/null
+++ b/llvm/test/Transforms/DCE/is-debugging-enabled.ll
@@ -0,0 +1,12 @@
+; RUN: opt -passes=dce -S < %s | FileCheck %s
+
+define void @unused_query_is_observable() {
+; CHECK-LABEL: define void @unused_query_is_observable() {
+; CHECK-NEXT:    [[ENABLED:%.*]] = call i1 @llvm.is.debugging.enabled()
+; CHECK-NEXT:    ret void
+;
+  %enabled = call i1 @llvm.is.debugging.enabled()
+  ret void
+}
+
+declare i1 @llvm.is.debugging.enabled()
diff --git a/llvm/test/Transforms/EarlyCSE/is-debugging-enabled.ll b/llvm/test/Transforms/EarlyCSE/is-debugging-enabled.ll
new file mode 100644
index 0000000000000..e9a4ba6d137e5
--- /dev/null
+++ b/llvm/test/Transforms/EarlyCSE/is-debugging-enabled.ll
@@ -0,0 +1,17 @@
+; RUN: opt -passes=early-cse -S < %s | FileCheck %s
+; RUN: opt -passes=gvn -S < %s | FileCheck %s
+
+define i1 @distinct_queries_are_not_commoned() {
+; CHECK-LABEL: define i1 @distinct_queries_are_not_commoned() {
+; CHECK-NEXT:    [[FIRST:%.*]] = call i1 @llvm.is.debugging.enabled()
+; CHECK-NEXT:    [[SECOND:%.*]] = call i1 @llvm.is.debugging.enabled()
+; CHECK-NEXT:    [[DIFFER:%.*]] = xor i1 [[FIRST]], [[SECOND]]
+; CHECK-NEXT:    ret i1 [[DIFFER]]
+;
+  %first = call i1 @llvm.is.debugging.enabled()
+  %second = call i1 @llvm.is.debugging.enabled()
+  %differ = xor i1 %first, %second
+  ret i1 %differ
+}
+
+declare i1 @llvm.is.debugging.enabled()
diff --git a/llvm/test/Transforms/LICM/is-debugging-enabled.ll b/llvm/test/Transforms/LICM/is-debugging-enabled.ll
new file mode 100644
index 0000000000000..e7725f716fe5a
--- /dev/null
+++ b/llvm/test/Transforms/LICM/is-debugging-enabled.ll
@@ -0,0 +1,25 @@
+; RUN: opt -passes=licm -S < %s | FileCheck %s
+
+define void @query_stays_in_loop(i32 %count) {
+; CHECK-LABEL: define void @query_stays_in_loop(
+; CHECK:       loop:
+; CHECK:         call i1 @llvm.is.debugging.enabled()
+; CHECK:         br i1 {{.*}}, label %loop, label %exit
+; CHECK:       exit:
+;
+entry:
+  %nonzero = icmp ne i32 %count, 0
+  br i1 %nonzero, label %loop, label %exit
+
+loop:
+  %index = phi i32 [ 0, %entry ], [ %next, %loop ]
+  %enabled = call i1 @llvm.is.debugging.enabled()
+  %next = add nuw i32 %index, 1
+  %more = icmp ult i32 %next, %count
+  br i1 %more, label %loop, label %exit
+
+exit:
+  ret void
+}
+
+declare i1 @llvm.is.debugging.enabled()
diff --git a/llvm/test/Transforms/LoopUnroll/is-debugging-enabled.ll b/llvm/test/Transforms/LoopUnroll/is-debugging-enabled.ll
new file mode 100644
index 0000000000000..de1d71edd6d9f
--- /dev/null
+++ b/llvm/test/Transforms/LoopUnroll/is-debugging-enabled.ll
@@ -0,0 +1,30 @@
+; RUN: opt -passes=loop-unroll -S < %s | FileCheck %s
+
+; Full unrolling may duplicate the static query site because it preserves the
+; three dynamically executed observations from the original loop.
+define void @query_allows_full_unrolling() {
+; CHECK-LABEL: define void @query_allows_full_unrolling() {
+; CHECK:       loop:
+; CHECK-NEXT:    call i1 @llvm.is.debugging.enabled()
+; CHECK-NEXT:    call i1 @llvm.is.debugging.enabled()
+; CHECK-NEXT:    call i1 @llvm.is.debugging.enabled()
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+
+loop:
+  %index = phi i32 [ 0, %entry ], [ %next, %loop ]
+  %enabled = call i1 @llvm.is.debugging.enabled()
+  %next = add nuw nsw i32 %index, 1
+  %done = icmp eq i32 %next, 3
+  br i1 %done, label %exit, label %loop, !llvm.loop !0
+
+exit:
+  ret void
+}
+
+declare i1 @llvm.is.debugging.enabled()
+
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.unroll.full"}
diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/is-debugging-enabled.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/is-debugging-enabled.ll
new file mode 100644
index 0000000000000..2ce321558677b
--- /dev/null
+++ b/llvm/test/Transforms/PreISelIntrinsicLowering/is-debugging-enabled.ll
@@ -0,0 +1,13 @@
+; RUN: %if x86-registered-target %{ opt -mtriple=x86_64 -passes=pre-isel-intrinsic-lowering -S < %s | FileCheck %s --check-prefix=UNSUPPORTED %}
+; RUN: %if nvptx-registered-target %{ opt -mtriple=nvptx64-nvidia-cuda -passes=pre-isel-intrinsic-lowering -S < %s | FileCheck %s --check-prefix=UNSUPPORTED %}
+; RUN: %if amdgpu-registered-target %{ opt -mtriple=r600 -passes=pre-isel-intrinsic-lowering -S < %s | FileCheck %s --check-prefix=UNSUPPORTED %}
+; RUN: %if amdgpu-registered-target %{ opt -mtriple=amdgcn-amd-amdhsa -passes=pre-isel-intrinsic-lowering -S < %s | FileCheck %s --check-prefix=UNSUPPORTED %}
+
+define i1 @query() {
+; UNSUPPORTED-LABEL: define i1 @query() {
+; UNSUPPORTED-NEXT:    ret i1 false
+  %enabled = call i1 @llvm.is.debugging.enabled()
+  ret i1 %enabled
+}
+
+declare i1 @llvm.is.debugging.enabled()
diff --git a/llvm/test/Transforms/SimplifyCFG/is-debugging-enabled.ll b/llvm/test/Transforms/SimplifyCFG/is-debugging-enabled.ll
new file mode 100644
index 0000000000000..299a97cdbb487
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/is-debugging-enabled.ll
@@ -0,0 +1,58 @@
+; RUN: opt -passes='default<O1>' -S < %s | FileCheck %s
+
+; The three query sites must not be merged by CFG simplification.
+define void @preserve_query_sites(i32 %selector) {
+; CHECK-LABEL: define void @preserve_query_sites(
+; CHECK:       first:
+; CHECK-NEXT:    call i1 @llvm.is.debugging.enabled()
+; CHECK:       second:
+; CHECK-NEXT:    call i1 @llvm.is.debugging.enabled()
+; CHECK:       join:
+; CHECK-NEXT:    call i1 @llvm.is.debugging.enabled()
+;
+entry:
+  switch i32 %selector, label %join [
+    i32 5, label %first
+    i32 7, label %second
+  ]
+
+first:
+  %first.query = call i1 @llvm.is.debugging.enabled()
+  br label %join
+
+second:
+  %second.query = call i1 @llvm.is.debugging.enabled()
+  br label %join
+
+join:
+  %join.query = call i1 @llvm.is.debugging.enabled()
+  ret void
+}
+
+; The equivalent unannotated calls establish that the pipeline exercises the
+; merge opportunity used above.
+define void @mergeable_control(i32 %selector) {
+; CHECK-LABEL: define void @mergeable_control(
+; CHECK-COUNT-1: call i1 @ordinary.query()
+;
+entry:
+  switch i32 %selector, label %join [
+    i32 5, label %first
+    i32 7, label %second
+  ]
+
+first:
+  %first.query = call i1 @ordinary.query()
+  br label %join
+
+second:
+  %second.query = call i1 @ordinary.query()
+  br label %join
+
+join:
+  %join.query = call i1 @ordinary.query()
+  ret void
+}
+
+declare i1 @llvm.is.debugging.enabled()
+declare i1 @ordinary.query()
diff --git a/llvm/test/Verifier/is-debugging-enabled.ll b/llvm/test/Verifier/is-debugging-enabled.ll
new file mode 100644
index 0000000000000..640318f9f082e
--- /dev/null
+++ b/llvm/test/Verifier/is-debugging-enabled.ll
@@ -0,0 +1,15 @@
+; RUN: split-file %s %t
+; RUN: not opt -passes=verify -disable-output %t/wrong-return.ll 2>&1 | FileCheck %s --check-prefix=RETURN
+; RUN: not opt -passes=verify -disable-output %t/wrong-arity.ll 2>&1 | FileCheck %s --check-prefix=ARITY
+
+;--- wrong-return.ll
+
+; RETURN: intrinsic return type expected i1, but got i32
+; RETURN-NEXT: declare i32 @llvm.is.debugging.enabled()
+declare i32 @llvm.is.debugging.enabled()
+
+;--- wrong-arity.ll
+
+; ARITY: intrinsic has incorrect number of args. Expected 0, but got 1
+; ARITY-NEXT: declare i1 @llvm.is.debugging.enabled(i1)
+declare i1 @llvm.is.debugging.enabled(i1)



More information about the llvm-commits mailing list