[llvm-branch-commits] [llvm] [LLVM][CodeGen] Improve CTSELECT fallback lowering and target support modeling (PR #179395)

Akshay K via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Feb 10 02:43:40 PST 2026


https://github.com/kumarak updated https://github.com/llvm/llvm-project/pull/179395

>From 7b01e6e0a3d2764cc1a16b3e6edae0a1c42ff37c Mon Sep 17 00:00:00 2001
From: AkshayK <iit.akshay at gmail.com>
Date: Mon, 2 Feb 2026 23:26:31 -0500
Subject: [PATCH] [CodeGen] Improve CTSELECT fallback lowering and cleanup
 target modeling

Clarify CTSELECT semantics and refine the generic fallback lowering using
a canonical bitwise formulation for improved correctness. Simplify
TargetLowering support by introducing isCtSelectSupported() and removing
CTSELECT-specific SelectSupportKind entries. Minor DAGCombiner and
documentation cleanups.
---
 llvm/include/llvm/CodeGen/ISDOpcodes.h        |  4 +-
 llvm/include/llvm/CodeGen/SelectionDAGNodes.h |  2 -
 llvm/include/llvm/CodeGen/TargetLowering.h    | 11 ++--
 llvm/include/llvm/IR/Intrinsics.td            |  3 +-
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp |  8 +--
 .../SelectionDAG/SelectionDAGBuilder.cpp      | 61 ++++++-------------
 llvm/lib/Target/AArch64/AArch64ISelLowering.h |  2 +
 llvm/lib/Target/ARM/ARMISelLowering.h         |  2 +
 llvm/lib/Target/X86/X86ISelLowering.h         |  2 +
 9 files changed, 35 insertions(+), 60 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/ISDOpcodes.h b/llvm/include/llvm/CodeGen/ISDOpcodes.h
index e843a78549315..84f5a62cfeb08 100644
--- a/llvm/include/llvm/CodeGen/ISDOpcodes.h
+++ b/llvm/include/llvm/CodeGen/ISDOpcodes.h
@@ -783,8 +783,8 @@ enum NodeType {
   /// i1 then the high bits must conform to getBooleanContents.
   SELECT,
 
-  /// Constant-time Select, implemented with CMOV instruction. This is used to
-  /// implement constant-time select.
+  /// CTSELECT(Cond, TrueVal, FalseVal). Cond is i1 and the value operands must
+  /// have the same type. Used to lower the constant-time select intrinsic.
   CTSELECT,
 
   /// Select with a vector condition (op #0) and two vector operands (ops #1
diff --git a/llvm/include/llvm/CodeGen/SelectionDAGNodes.h b/llvm/include/llvm/CodeGen/SelectionDAGNodes.h
index 8a9e3335b2453..fdb76a93bc5bb 100644
--- a/llvm/include/llvm/CodeGen/SelectionDAGNodes.h
+++ b/llvm/include/llvm/CodeGen/SelectionDAGNodes.h
@@ -436,8 +436,6 @@ struct SDNodeFlags {
     FastMathFlags = NoNaNs | NoInfs | NoSignedZeros | AllowReciprocal |
                     AllowContract | ApproximateFuncs | AllowReassociation,
 
-    // Flag for disabling optimization
-    NoMerge = 1 << 15,
   };
 
   /// Default constructor turns off all optimization flags.
diff --git a/llvm/include/llvm/CodeGen/TargetLowering.h b/llvm/include/llvm/CodeGen/TargetLowering.h
index c1714c3cc073f..084e08e76bd2e 100644
--- a/llvm/include/llvm/CodeGen/TargetLowering.h
+++ b/llvm/include/llvm/CodeGen/TargetLowering.h
@@ -247,10 +247,6 @@ class LLVM_ABI TargetLoweringBase {
                          // and vector values (ex: cmov).
     VectorMaskSelect,    // The target supports vector selects with a vector
                          // mask (ex: x86 blends).
-    CtSelect,            // The target implements a custom constant-time select.
-    ScalarCondVectorValCtSelect, // The target supports selects with a scalar
-                                 // condition and vector values.
-    VectorMaskValCtSelect, // The target supports vector selects with a vector
   };
 
   /// Enum that specifies what an atomic load/AtomicRMWInst is expanded
@@ -481,9 +477,10 @@ class LLVM_ABI TargetLoweringBase {
   MachineMemOperand::Flags
   getVPIntrinsicMemOperandFlags(const VPIntrinsic &VPIntrin) const;
 
-  virtual bool isSelectSupported(SelectSupportKind kind) const {
-    return kind != CtSelect;
-  }
+  virtual bool isSelectSupported(SelectSupportKind kind) const { return true; }
+
+  /// Return true if the target has custom lowering for constant-time select.
+  virtual bool isCtSelectSupported(EVT VT) const { return false; }
 
   /// Return true if the @llvm.get.active.lane.mask intrinsic should be expanded
   /// using generic code in SelectionDAGBuilder.
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 4fe2736b78023..abfa5bc279918 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -1863,8 +1863,7 @@ def int_coro_subfn_addr : DefaultAttrsIntrinsic<
     [IntrReadMem, IntrArgMemOnly, ReadOnly<ArgIndex<0>>,
      NoCapture<ArgIndex<0>>]>;
 
-///===-------------------------- Constant Time Intrinsics
-///--------------------------===//
+///===---------------------- Constant Time Intrinsics ----------------------===//
 //
 // Intrinsic to support constant time select
 def int_ct_select
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 7cec739015756..646bc5e78c051 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -484,7 +484,8 @@ namespace {
     SDValue visitCTTZ_ZERO_UNDEF(SDNode *N);
     SDValue visitCTPOP(SDNode *N);
     SDValue visitSELECT(SDNode *N);
-    SDValue visitCTSELECT(SDNode *N);
+    // visit CTSELECT Node
+    SDValue visitConstantTimeSelect(SDNode *N);
     SDValue visitVSELECT(SDNode *N);
     SDValue visitVP_SELECT(SDNode *N);
     SDValue visitSELECT_CC(SDNode *N);
@@ -1905,7 +1906,6 @@ void DAGCombiner::Run(CombineLevel AtLevel) {
 }
 
 SDValue DAGCombiner::visit(SDNode *N) {
-
   // clang-format off
   switch (N->getOpcode()) {
   default: break;
@@ -1976,7 +1976,7 @@ SDValue DAGCombiner::visit(SDNode *N) {
   case ISD::CTTZ_ZERO_UNDEF:    return visitCTTZ_ZERO_UNDEF(N);
   case ISD::CTPOP:              return visitCTPOP(N);
   case ISD::SELECT:             return visitSELECT(N);
-  case ISD::CTSELECT:           return visitCTSELECT(N);
+  case ISD::CTSELECT:           return visitConstantTimeSelect(N);
   case ISD::VSELECT:            return visitVSELECT(N);
   case ISD::SELECT_CC:          return visitSELECT_CC(N);
   case ISD::SETCC:              return visitSETCC(N);
@@ -12583,7 +12583,7 @@ SDValue DAGCombiner::visitSELECT(SDNode *N) {
   return SDValue();
 }
 
-SDValue DAGCombiner::visitCTSELECT(SDNode *N) {
+SDValue DAGCombiner::visitConstantTimeSelect(SDNode *N) {
   SDValue N0 = N->getOperand(0);
   SDValue N1 = N->getOperand(1);
   SDValue N2 = N->getOperand(2);
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index eaecb87979648..49e38f56d30ad 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -6582,7 +6582,10 @@ void SelectionDAGBuilder::visitVectorExtractLastActive(const CallInst &I,
 
 /// Fallback implementation for constant-time select using DAG chaining.
 /// This implementation uses data dependencies through virtual registers to
-/// prevent optimizations from breaking the constant-time property.
+/// prevent optimizations from breaking the constant-time property. It is a
+/// best-effort safeguard; for stronger guarantees we prefer target-specific
+/// lowering pipelines that preserve the select pattern by construction.
+///
 /// It handles scalars, vectors (fixed and scalable), and floating-point types.
 SDValue SelectionDAGBuilder::createProtectedCtSelectFallback(
     SelectionDAG &DAG, const SDLoc &DL, SDValue Cond, SDValue T, SDValue F,
@@ -6599,28 +6602,12 @@ SDValue SelectionDAGBuilder::createProtectedCtSelectFallback(
   if (VT.isVector() && !Cond.getValueType().isVector()) {
     ElementCount NumElems = VT.getVectorElementCount();
     EVT CondVT = EVT::getVectorVT(*DAG.getContext(), MVT::i1, NumElems);
-
-    if (VT.isScalableVector()) {
-      Cond = DAG.getSplatVector(CondVT, DL, Cond);
-    } else {
-      Cond = DAG.getSplatBuildVector(CondVT, DL, Cond);
-    }
+    Cond = DAG.getSplat(CondVT, DL, Cond);
   }
 
   // Handle floating-point types: bitcast to integer for bitwise operations
   if (VT.isFloatingPoint()) {
-    if (VT.isVector()) {
-      // float vector -> int vector
-      EVT ElemVT = VT.getVectorElementType();
-      unsigned int ElemBitWidth = ElemVT.getScalarSizeInBits();
-      EVT IntElemVT = EVT::getIntegerVT(*DAG.getContext(), ElemBitWidth);
-
-      WorkingVT = EVT::getVectorVT(*DAG.getContext(), IntElemVT,
-                                   VT.getVectorElementCount());
-    } else {
-      WorkingVT = EVT::getIntegerVT(*DAG.getContext(), VT.getSizeInBits());
-    }
-
+    WorkingVT = VT.changeTypeToInteger();
     WorkingT = DAG.getBitcast(WorkingVT, T);
     WorkingF = DAG.getBitcast(WorkingVT, F);
   }
@@ -6628,24 +6615,10 @@ SDValue SelectionDAGBuilder::createProtectedCtSelectFallback(
   // Create mask: sign-extend condition to all bits
   SDValue Mask = DAG.getSExtOrTrunc(Cond, DL, WorkingVT);
 
-  // Create all-ones constant for inversion
-  SDValue AllOnes;
-  if (WorkingVT.isScalableVector()) {
-    unsigned BitWidth = WorkingVT.getScalarSizeInBits();
-    APInt AllOnesVal = APInt::getAllOnes(BitWidth);
-    SDValue ScalarAllOnes =
-        DAG.getConstant(AllOnesVal, DL, WorkingVT.getScalarType());
-    AllOnes = DAG.getSplatVector(WorkingVT, DL, ScalarAllOnes);
-  } else {
-    AllOnes = DAG.getAllOnesConstant(DL, WorkingVT);
-  }
-
-  // Invert mask for false value
-  SDValue Invert = DAG.getNode(ISD::XOR, DL, WorkingVT, Mask, AllOnes);
-
-  // Compute: (T & Mask) | (F & ~Mask)
+  // Compute: F ^ ((T ^ F) & Mask)
   // This is constant-time because both branches are always computed
-  SDValue TM = DAG.getNode(ISD::AND, DL, WorkingVT, Mask, WorkingT);
+  SDValue XorTF = DAG.getNode(ISD::XOR, DL, WorkingVT, WorkingT, WorkingF);
+  SDValue TM = DAG.getNode(ISD::AND, DL, WorkingVT, XorTF, Mask);
 
   // DAG chaining: create data dependency through virtual register
   // This prevents optimizations from reordering or eliminating operations
@@ -6653,10 +6626,15 @@ SDValue SelectionDAGBuilder::createProtectedCtSelectFallback(
   bool CanUseChaining = false;
 
   if (!WorkingVT.isScalableVector()) {
-    // For fixed-size vectors and scalars, check if type is legal
+    // For fixed-size vectors and scalars, chaining is a best-effort hardening
+    // step. The CT guarantee comes from the dataflow-only select
+    // pattern (both sides computed, no control-flow). Chaining only adds an
+    // extra dependency to discourage later combines.
     CanUseChaining = TLI.isTypeLegal(WorkingVT.getSimpleVT());
   } else {
-    // For scalable vectors, disable chaining (conservative approach)
+    // For scalable vectors, skip chaining because there is no stable register
+    // class to copy through. CT behavior still relies on the masking/select
+    // pattern above.
     CanUseChaining = false;
   }
 
@@ -6668,8 +6646,7 @@ SDValue SelectionDAGBuilder::createProtectedCtSelectFallback(
     TM = DAG.getCopyFromReg(Chain, DL, TMReg, WorkingVT);
   }
 
-  SDValue FM = DAG.getNode(ISD::AND, DL, WorkingVT, Invert, WorkingF);
-  SDValue Result = DAG.getNode(ISD::OR, DL, WorkingVT, TM, FM);
+  SDValue Result = DAG.getNode(ISD::XOR, DL, WorkingVT, WorkingF, TM);
 
   // Convert back to original type if needed
   if (WorkingVT != VT) {
@@ -6878,9 +6855,7 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I,
     assert(!CondVT.isVector() && "Vector type cond not supported yet");
 
     // Handle scalar types
-    if (TLI.isSelectSupported(
-            TargetLoweringBase::SelectSupportKind::CtSelect) &&
-        !CondVT.isVector()) {
+    if (TLI.isCtSelectSupported(VT) && !CondVT.isVector()) {
       SDValue Result = DAG.getNode(ISD::CTSELECT, DL, VT, Cond, A, B);
       setValue(&I, Result);
       return;
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.h b/llvm/lib/Target/AArch64/AArch64ISelLowering.h
index e8c026d989eb8..292caf16a97e2 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.h
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.h
@@ -157,6 +157,8 @@ class AArch64TargetLowering : public TargetLowering {
   EVT getSetCCResultType(const DataLayout &DL, LLVMContext &Context,
                          EVT VT) const override;
 
+  bool isCtSelectSupported(EVT VT) const override { return false; }
+
   SDValue ReconstructShuffle(SDValue Op, SelectionDAG &DAG) const;
 
   MachineBasicBlock *EmitF128CSEL(MachineInstr &MI,
diff --git a/llvm/lib/Target/ARM/ARMISelLowering.h b/llvm/lib/Target/ARM/ARMISelLowering.h
index d0fb58c764edd..faa12aba61ed3 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.h
+++ b/llvm/lib/Target/ARM/ARMISelLowering.h
@@ -119,6 +119,8 @@ class VectorType;
       return (Kind != ScalarCondVectorVal);
     }
 
+    bool isCtSelectSupported(EVT VT) const override { return false; }
+
     bool isReadOnly(const GlobalValue *GV) const;
 
     /// getSetCCResultType - Return the value type to use for ISD::SETCC.
diff --git a/llvm/lib/Target/X86/X86ISelLowering.h b/llvm/lib/Target/X86/X86ISelLowering.h
index 848fe4bf86d2c..4100ae9925781 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.h
+++ b/llvm/lib/Target/X86/X86ISelLowering.h
@@ -1078,6 +1078,8 @@ namespace llvm {
     unsigned getJumpTableEncoding() const override;
     bool useSoftFloat() const override;
 
+    bool isCtSelectSupported(EVT VT) const override { return false; }
+
     void markLibCallAttributes(MachineFunction *MF, unsigned CC,
                                ArgListTy &Args) const override;
 



More information about the llvm-branch-commits mailing list