[llvm] [ConstantTime][LLVM] Add llvm.ct.select intrinsic with generic SelectionDAG lowering (PR #166702)
Julius Alexandre via llvm-commits
llvm-commits at lists.llvm.org
Sun Jul 19 20:13:18 PDT 2026
================
@@ -4329,6 +4330,159 @@ bool SelectionDAGLegalize::ExpandNode(SDNode *Node) {
}
Results.push_back(Tmp1);
break;
+ case ISD::CT_SELECT: {
+ // Constant-time select: F ^ ((T ^ F) & Mask), Mask = 0 - (cond & 1).
+ // Bitwise-only — no select/cmov SDNode is constructed, so the CT property
+ // holds against any combiner that targets those opcodes. FP types operate
+ // on the same-size integer; vectors build the mask as a scalar then splat
+ // (avoids illegal vNi1).
+ //
+ // The masked-diff is routed through a virtual register (CopyToReg /
+ // CopyFromReg) below as a forward-looking DAGCombine barrier. This is
+ // *not* required for correctness against any combiner in tree today —
+ // DAGCombiner has no rewrite that recognizes XOR/AND/XOR-with-sext-mask
+ // and reconstructs a SELECT. The chain edge is defense-in-depth against
+ // a hypothetical future fold of that form: the dependency partitions the
+ // bitwise sequence into a region the combiner can't see through. Cost is
+ // at most a coalesce-able MOV per call. Swap for ARITH_FENCE when its
+ // int/vector extension lands.
+ Tmp1 = Node->getOperand(0); // cond
+ Tmp2 = Node->getOperand(1); // T
+ Tmp3 = Node->getOperand(2); // F
+ EVT VT = Tmp2.getValueType();
+
+ // Memory-blend for FP scalars whose same-size integer isn't legal (f64 on
+ // i386 no-SSE, x86_fp80, fp128). The bitcast-to-int expansion below can't
+ // run since LegalizeDAG is the last legalization stage. Spill T/F, blend
+ // chunk-by-chunk at a legal int width, reload as the FP type. Fixed and
+ // scalable FP vectors fall through to the unified path below.
+ if (VT.isFloatingPoint() && !VT.isVector() &&
+ !TLI.isTypeLegal(VT.changeTypeToInteger())) {
+ const DataLayout &DL = DAG.getDataLayout();
+ Type *VTTy = VT.getTypeForEVT(*DAG.getContext());
+ unsigned StorageBytes = DL.getTypeStoreSize(VTTy);
+ assert(StorageBytes > 0 && "FP type with zero storage size");
+
+ // Pick the largest legal scalar integer chunk that divides StorageBytes
+ // evenly. i8 is the universal fallback (legal on every target).
+ MVT ChunkVT = MVT::i8;
+ for (MVT MV : {MVT::i64, MVT::i32, MVT::i16}) {
+ unsigned MVBytes = MV.getSizeInBits() / 8;
+ if (TLI.isTypeLegal(MV) && MVBytes <= StorageBytes &&
+ StorageBytes % MVBytes == 0) {
+ ChunkVT = MV;
+ break;
+ }
+ }
+ unsigned ChunkBytes = ChunkVT.getSizeInBits() / 8;
+ unsigned NumChunks = StorageBytes / ChunkBytes;
+
+ MachineFunction &MF = DAG.getMachineFunction();
+ SDValue StackT = DAG.CreateStackTemporary(VT);
+ SDValue StackF = DAG.CreateStackTemporary(VT);
+ SDValue StackR = DAG.CreateStackTemporary(VT);
+ int FIT = cast<FrameIndexSDNode>(StackT.getNode())->getIndex();
+ int FIF = cast<FrameIndexSDNode>(StackF.getNode())->getIndex();
+ int FIR = cast<FrameIndexSDNode>(StackR.getNode())->getIndex();
+ MachinePointerInfo PIT = MachinePointerInfo::getFixedStack(MF, FIT);
+ MachinePointerInfo PIF = MachinePointerInfo::getFixedStack(MF, FIF);
+ MachinePointerInfo PIR = MachinePointerInfo::getFixedStack(MF, FIR);
+
+ SDValue Chain = DAG.getEntryNode();
+ Chain = DAG.getStore(Chain, dl, Tmp2, StackT, PIT);
+ Chain = DAG.getStore(Chain, dl, Tmp3, StackF, PIF);
+
+ for (unsigned i = 0; i < NumChunks; ++i) {
+ TypeSize Off = TypeSize::getFixed(i * ChunkBytes);
+ SDValue TPtr = DAG.getMemBasePlusOffset(StackT, Off, dl);
+ SDValue FPtr = DAG.getMemBasePlusOffset(StackF, Off, dl);
+ SDValue RPtr = DAG.getMemBasePlusOffset(StackR, Off, dl);
+
+ SDValue Ti = DAG.getLoad(ChunkVT, dl, Chain, TPtr,
----------------
wizardengineer wrote:
Done in 64613ed31ef6. The narrow tail chunks use an ext-load and trunc-store now.
https://github.com/llvm/llvm-project/pull/166702
More information about the llvm-commits
mailing list