[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
Tue Sep 8 05:10:43 PDT 2026


================
@@ -15989,6 +15989,155 @@ catch:
     ret void
 ```
 
+### Constant-Time Intrinsics
+
+These intrinsics are provided to support constant-time operations for
+security-sensitive code. Constant-time operations are designed to execute in
+time independent of secret data values, preventing timing side-channel leaks.
+
+(int_ct_select)=
+
+#### '`llvm.ct.select.*`' Intrinsic
+
+##### Syntax:
+
+This is an overloaded intrinsic. You can use `llvm.ct.select` on any integer,
+byte, floating-point, or pointer type, or on any vector of those types,
+including scalable vectors. The declarations below are a representative sample:
+
+```
+declare i8 @llvm.ct.select.i8(i1 <cond>, i8 <val1>, i8 <val2>)
+declare b8 @llvm.ct.select.b8(i1 <cond>, b8 <val1>, b8 <val2>)
+declare i32 @llvm.ct.select.i32(i1 <cond>, i32 <val1>, i32 <val2>)
+declare i64 @llvm.ct.select.i64(i1 <cond>, i64 <val1>, i64 <val2>)
+declare half @llvm.ct.select.f16(i1 <cond>, half <val1>, half <val2>)
+declare float @llvm.ct.select.f32(i1 <cond>, float <val1>, float <val2>)
+declare double @llvm.ct.select.f64(i1 <cond>, double <val1>, double <val2>)
+declare fp128 @llvm.ct.select.f128(i1 <cond>, fp128 <val1>, fp128 <val2>)
+declare ptr @llvm.ct.select.p0(i1 <cond>, ptr <val1>, ptr <val2>)
+declare <4 x i32> @llvm.ct.select.v4i32(i1 <cond>, <4 x i32> <val1>, <4 x i32> <val2>)
+declare <2 x double> @llvm.ct.select.v2f64(i1 <cond>, <2 x double> <val1>, <2 x double> <val2>)
+declare <2 x ptr> @llvm.ct.select.v2p0(i1 <cond>, <2 x ptr> <val1>, <2 x ptr> <val2>)
+declare <vscale x 4 x i32> @llvm.ct.select.nxv4i32(i1 <cond>, <vscale x 4 x i32> <val1>, <vscale x 4 x i32> <val2>)
+```
+
+##### Overview:
+
+The '`llvm.ct.select`' family of intrinsic functions selects one of two
+values based on a condition, like the standard {ref}`select <i_select>`
+instruction, but is lowered to branchless code whose execution time does not
+depend on the condition value. This keeps the condition from leaking through
+timing side channels; see the Semantics section for the exact guarantee and
+its platform requirements.
+
+##### Arguments:
+
+The '`llvm.ct.select`' intrinsic requires three arguments:
+
+1. The condition, which must be a scalar value of type 'i1'. Unlike
+   {ref}`select <i_select>` which accepts both scalar 'i1' and vector
+   '`<N x i1>`' conditions, `llvm.ct.select` only accepts a scalar 'i1'
+   condition. Vector conditions are not supported.
+2. The first value argument, which must be an integer, byte, floating-point,
+   or pointer type, or a vector of those types. Other
+   {ref}`first class <t_firstclass>` types, such as aggregates, are not
+   supported.
+3. The second value argument, which must have the same type as the first
+   value argument.
+
+##### Semantics:
+
+If the condition evaluates to true, the intrinsic returns the first value
+argument; otherwise, it returns the second value argument.
+
+Poison and undef propagate as for {ref}`select <i_select>`: a `poison`
+condition yields `poison`, an `undef` condition yields `undef`. Unlike
+`select`, both value arguments are always evaluated, so a `poison` value in
+either one yields `poison`.
+
+The key semantic difference from {ref}`select <i_select>` is the constant-time
+code generation guarantee: the intrinsic must be lowered to machine code that:
+
+- Does not introduce data-dependent control flow based on the condition value
+- Executes the same sequence of instructions regardless of the condition value
+- Uses both value arguments unconditionally; the not-selected argument is
+  never skipped or branched around
+
+**Platform Requirements:** The constant-time guarantee is conditional on
+hardware support for data-independent execution timing. This may be a
+processor mode that the platform enables, such as Arm DIT (Data Independent
+Timing) or x86 DOIT (Data Operand Independent Timing), or a static
+architectural guarantee, such as the RISC-V `Zkt` and `Zvkt` extensions,
+which certify data-independent latency for the scalar and vector instructions
+emitted by the lowering. Without such hardware support, the generated code
+will still be free from data-dependent control flow, but microarchitectural
+timing variations may still occur.
+
+The typical implementation uses bitwise operations to blend the two values
+based on a mask derived from the condition:
+
+```
+mask = sext(cond)  ; sign-extend condition to all 1s or all 0s
+result = val2 ^ ((val1 ^ val2) & mask)
+```
+
+Targets with native constant-time select support lower the intrinsic to
+suitable target instructions instead. Targets without native support use the
+generic bitwise expansion shown above. In either case the selection is kept
+as a single opaque operation until late in code generation, so optimization
+passes never see a pattern they could rewrite into a select or a conditional
+branch.
+
+Optimizations must preserve the constant-time code generation semantics.
+Transforms that would introduce data-dependent control flow are not permitted.
+This includes converting to conditional branches, using predicated instructions
+with data-dependent timing, or, except as described below, optimizing away
+either value argument before the selection completes.
+
+The call folds to one of its value arguments when the condition operand is a
+constant `i1`, or when both value arguments are the same value; the unused
----------------
wizardengineer wrote:

Fixed. Dropped the false "becomes ordinary dead code" clause, and unused calls are now actually removable. See the `Intrinsics.td` thread.

https://github.com/llvm/llvm-project/pull/166702


More information about the llvm-commits mailing list