[llvm-branch-commits] [clang] [ConstantTime][Clang] Add __builtin_ct_select for constant-time selection (PR #166703)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Nov 6 12:37:18 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-codegen
Author: Julius Alexandre (wizardengineer)
<details>
<summary>Changes</summary>
---
Patch is 49.36 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/166703.diff
5 Files Affected:
- (modified) clang/include/clang/Basic/Builtins.td (+8)
- (modified) clang/lib/CodeGen/CGBuiltin.cpp (+36-1)
- (modified) clang/lib/Sema/SemaChecking.cpp (+89)
- (added) clang/test/Sema/builtin-ct-select-edge-cases.c (+384)
- (added) clang/test/Sema/builtin-ct-select.c (+683)
``````````diff
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 2b400b012d6ed..13e2a9849bfca 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -5278,3 +5278,11 @@ def CountedByRef : Builtin {
let Attributes = [NoThrow, CustomTypeChecking];
let Prototype = "int(...)";
}
+
+// Constant-time select builtin
+def CtSelect : Builtin {
+ let Spellings = ["__builtin_ct_select"];
+ let Attributes = [NoThrow, Const, UnevaluatedArguments,
+ ConstIgnoringExceptions, CustomTypeChecking];
+ let Prototype = "void(...)";
+}
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index b81e0d02da2c9..3703c9a4ffa79 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -26,8 +26,9 @@
#include "TargetInfo.h"
#include "clang/AST/OSLog.h"
#include "clang/AST/StmtVisitor.h"
+#include "clang/Basic/DiagnosticFrontend.h"
+#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/TargetInfo.h"
-#include "clang/Frontend/FrontendDiagnostic.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Intrinsics.h"
@@ -6450,6 +6451,40 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
auto Str = CGM.GetAddrOfConstantCString(Name, "");
return RValue::get(Str.getPointer());
}
+ case Builtin::BI__builtin_ct_select: {
+ if (E->getNumArgs() != 3) {
+ CGM.getDiags().Report(E->getBeginLoc(),
+ E->getNumArgs() > 3
+ ? diag::err_typecheck_call_too_many_args
+ : diag::err_typecheck_call_too_few_args);
+ return GetUndefRValue(E->getType());
+ }
+
+ auto *Cond = EmitScalarExpr(E->getArg(0));
+ auto *A = EmitScalarExpr(E->getArg(1));
+ auto *B = EmitScalarExpr(E->getArg(2));
+
+ // Verify types match
+ if (A->getType() != B->getType()) {
+ CGM.getDiags().Report(E->getBeginLoc(),
+ diag::err_typecheck_convert_incompatible);
+ return GetUndefRValue(E->getType());
+ }
+
+ // Verify condition is integer type
+ if (!Cond->getType()->isIntegerTy()) {
+ CGM.getDiags().Report(E->getBeginLoc(), diag::err_typecheck_expect_int);
+ return GetUndefRValue(E->getType());
+ }
+
+ if (Cond->getType()->getIntegerBitWidth() != 1)
+ Cond = Builder.CreateICmpNE(
+ Cond, llvm::ConstantInt::get(Cond->getType(), 0), "cond.bool");
+
+ llvm::Function *Fn =
+ CGM.getIntrinsic(llvm::Intrinsic::ct_select, {A->getType()});
+ return RValue::get(Builder.CreateCall(Fn, {Cond, A, B}));
+ }
}
// If this is an alias for a lib function (e.g. __builtin_sin), emit
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index ad2c2e4a97bb9..026912c859c73 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -3494,6 +3494,95 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
if (BuiltinCountedByRef(TheCall))
return ExprError();
break;
+
+ case Builtin::BI__builtin_ct_select: {
+ if (TheCall->getNumArgs() != 3) {
+ // Simple argument count check without complex diagnostics
+ if (TheCall->getNumArgs() < 3) {
+ return Diag(TheCall->getEndLoc(),
+ diag::err_typecheck_call_too_few_args_at_least)
+ << 0 << 3 << TheCall->getNumArgs() << 0
+ << TheCall->getCallee()->getSourceRange();
+ } else {
+ return Diag(TheCall->getEndLoc(),
+ diag::err_typecheck_call_too_many_args)
+ << 0 << 3 << TheCall->getNumArgs() << 0
+ << TheCall->getCallee()->getSourceRange();
+ }
+ }
+ auto *Cond = TheCall->getArg(0);
+ auto *A = TheCall->getArg(1);
+ auto *B = TheCall->getArg(2);
+
+ QualType CondTy = Cond->getType();
+ if (!CondTy->isIntegerType()) {
+ return Diag(Cond->getBeginLoc(), diag::err_typecheck_cond_expect_scalar)
+ << CondTy << Cond->getSourceRange();
+ }
+
+ QualType ATy = A->getType();
+ QualType BTy = B->getType();
+
+ // check for scalar or vector scalar type
+ if ((!ATy->isScalarType() && !ATy->isVectorType()) ||
+ (!BTy->isScalarType() && !BTy->isVectorType())) {
+ return Diag(A->getBeginLoc(),
+ diag::err_typecheck_cond_incompatible_operands)
+ << ATy << BTy << A->getSourceRange() << B->getSourceRange();
+ }
+
+ // Check if both operands have the same type or can be implicitly converted
+ QualType ResultTy;
+ if (Context.hasSameType(ATy, BTy)) {
+ ResultTy = ATy;
+ } else {
+ // Try to find a common type using the same logic as conditional
+ // expressions
+ ExprResult ARes = ExprResult(A);
+ ExprResult BRes = ExprResult(B);
+
+ // For arithmetic types, allow promotions within the same category only
+ if (ATy->isArithmeticType() && BTy->isArithmeticType()) {
+ // Check if both are integer types or both are floating types
+ bool AIsInteger = ATy->isIntegerType();
+ bool BIsInteger = BTy->isIntegerType();
+ bool AIsFloating = ATy->isFloatingType();
+ bool BIsFloating = BTy->isFloatingType();
+
+ if ((AIsInteger && BIsInteger) || (AIsFloating && BIsFloating)) {
+ // Both are in the same category, allow usual arithmetic conversions
+ ResultTy = UsualArithmeticConversions(
+ ARes, BRes, TheCall->getBeginLoc(), ArithConvKind::Conditional);
+ if (ARes.isInvalid() || BRes.isInvalid() || ResultTy.isNull()) {
+ return Diag(A->getBeginLoc(),
+ diag::err_typecheck_cond_incompatible_operands)
+ << ATy << BTy << A->getSourceRange() << B->getSourceRange();
+ }
+ // Update the arguments with any necessary implicit casts
+ TheCall->setArg(1, ARes.get());
+ TheCall->setArg(2, BRes.get());
+ } else {
+ // Different categories (int vs float), not allowed
+ return Diag(A->getBeginLoc(),
+ diag::err_typecheck_cond_incompatible_operands)
+ << ATy << BTy << A->getSourceRange() << B->getSourceRange();
+ }
+ } else {
+ // For non-arithmetic types, they must be exactly the same
+ return Diag(A->getBeginLoc(),
+ diag::err_typecheck_cond_incompatible_operands)
+ << ATy << BTy << A->getSourceRange() << B->getSourceRange();
+ }
+ }
+
+ ExprResult CondRes = PerformContextuallyConvertToBool(Cond);
+ if (CondRes.isInvalid())
+ return ExprError();
+
+ TheCall->setArg(0, CondRes.get());
+ TheCall->setType(ResultTy);
+ return TheCall;
+ } break;
}
if (getLangOpts().HLSL && HLSL().CheckBuiltinFunctionCall(BuiltinID, TheCall))
diff --git a/clang/test/Sema/builtin-ct-select-edge-cases.c b/clang/test/Sema/builtin-ct-select-edge-cases.c
new file mode 100644
index 0000000000000..3998e9d68748d
--- /dev/null
+++ b/clang/test/Sema/builtin-ct-select-edge-cases.c
@@ -0,0 +1,384 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify %s -fexperimental-new-constant-interpreter
+
+// Test with various condition expressions
+int test_conditional_expressions(int x, int y, int a, int b) {
+ // Logical expressions
+ int result1 = __builtin_ct_select(x && y, a, b);
+ int result2 = __builtin_ct_select(x || y, a, b);
+ int result3 = __builtin_ct_select(!x, a, b);
+
+ // Comparison expressions
+ int result4 = __builtin_ct_select(x == y, a, b);
+ int result5 = __builtin_ct_select(x != y, a, b);
+ int result6 = __builtin_ct_select(x < y, a, b);
+ int result7 = __builtin_ct_select(x > y, a, b);
+ int result8 = __builtin_ct_select(x <= y, a, b);
+ int result9 = __builtin_ct_select(x >= y, a, b);
+
+ // Bitwise expressions
+ int result10 = __builtin_ct_select(x & y, a, b);
+ int result11 = __builtin_ct_select(x | y, a, b);
+ int result12 = __builtin_ct_select(x ^ y, a, b);
+ int result13 = __builtin_ct_select(~x, a, b);
+
+ // Arithmetic expressions
+ int result14 = __builtin_ct_select(x + y, a, b);
+ int result15 = __builtin_ct_select(x - y, a, b);
+ int result16 = __builtin_ct_select(x * y, a, b);
+ int result17 = __builtin_ct_select(x / y, a, b);
+ int result18 = __builtin_ct_select(x % y, a, b);
+
+ return result1 + result2 + result3 + result4 + result5 + result6 + result7 + result8 + result9 + result10 + result11 + result12 + result13 + result14 + result15 + result16 + result17 + result18;
+}
+
+// Test with extreme values
+int test_extreme_values(int cond) {
+ // Maximum and minimum values
+ int max_int = __builtin_ct_select(cond, __INT_MAX__, -__INT_MAX__ - 1);
+
+ // Very large numbers
+ long long max_ll = __builtin_ct_select(cond, __LONG_LONG_MAX__, -__LONG_LONG_MAX__ - 1);
+
+ // Floating point extremes
+ float max_float = __builtin_ct_select(cond, __FLT_MAX__, -__FLT_MAX__);
+ double max_double = __builtin_ct_select(cond, __DBL_MAX__, -__DBL_MAX__);
+
+ return max_int;
+}
+
+// Test with zero and negative zero
+int test_zero_values(int cond) {
+ // Integer zeros
+ int zero_int = __builtin_ct_select(cond, 0, -0);
+
+ // Floating point zeros
+ float zero_float = __builtin_ct_select(cond, 0.0f, -0.0f);
+ double zero_double = __builtin_ct_select(cond, 0.0, -0.0);
+
+ return zero_int;
+}
+
+// Test with infinity and NaN
+int test_special_float_values(int cond) {
+ // Infinity
+ float inf_float = __builtin_ct_select(cond, __builtin_inff(), -__builtin_inff());
+ double inf_double = __builtin_ct_select(cond, __builtin_inf(), -__builtin_inf());
+
+ // NaN
+ float nan_float = __builtin_ct_select(cond, __builtin_nanf(""), __builtin_nanf(""));
+ double nan_double = __builtin_ct_select(cond, __builtin_nan(""), __builtin_nan(""));
+
+ return 0;
+}
+
+// Test with complex pointer scenarios
+int test_pointer_edge_cases(int cond) {
+ int arr[10];
+ int *ptr1 = arr;
+ int *ptr2 = arr + 5;
+
+ // Array pointers
+ int *result1 = __builtin_ct_select(cond, ptr1, ptr2);
+
+ // Pointer arithmetic
+ int *result2 = __builtin_ct_select(cond, arr + 1, arr + 2);
+
+ // NULL vs non-NULL
+ int *result3 = __builtin_ct_select(cond, ptr1, (int*)0);
+
+ // Different pointer types (should fail)
+ float *fptr = (float*)0;
+ int *result4 = __builtin_ct_select(cond, ptr1, fptr); // expected-error {{incompatible operand types ('int *' and 'float *')}}
+
+ return *result1;
+}
+
+// Test with function pointers
+int func1(int x) { return x; }
+int func2(int x) { return x * 2; }
+float func3(float x) { return x; }
+
+int test_function_pointers(int cond, int x) {
+ // Same signature function pointer
+ int (*fptr)(int) = __builtin_ct_select(cond, &func1, &func2);
+
+ // Different signature function pointers (should fail)
+ int (*bad_fptr)(int) = __builtin_ct_select(cond, &func1, &func3); // expected-error {{incompatible operand types ('int (*)(int)' and 'float (*)(float)')}}
+
+ return fptr(x);
+}
+
+// Test with void pointers
+void *test_void_pointers(int cond, void *a, void *b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test with const/volatile qualifiers
+int test_qualifiers(int cond) {
+ const int ca = 10;
+ const int cb = 20;
+ volatile int va = 30;
+ volatile int vb = 40;
+ const volatile int cva = 50;
+ const volatile int cvb = 60;
+
+ // const to const
+ const int result1 = __builtin_ct_select(cond, ca, cb);
+
+ // volatile to volatile
+ volatile int result2 = __builtin_ct_select(cond, va, vb);
+
+ // const volatile to const volatile
+ const volatile int result3 = __builtin_ct_select(cond, cva, cvb);
+
+ return result1 + result2 + result3;
+}
+
+// Test with arrays (should fail as they're not arithmetic or pointer)
+int test_arrays(int cond) {
+ int arr1[5] = {1, 2, 3, 4, 5};
+ int arr2[5] = {6, 7, 8, 9, 10};
+
+ // This should fail??
+ int *result = __builtin_ct_select(cond, arr1, arr2); // expected-error {{incompatible operand types ('int[5]' and 'int[5]')}}
+
+ return result[0];
+}
+
+// Test with structures (should fail)
+struct Point {
+ int x, y;
+};
+
+struct Point test_structs(int cond) {
+ struct Point p1 = {1, 2};
+ struct Point p2 = {3, 4};
+
+ return __builtin_ct_select(cond, p1, p2); // expected-error {{incompatible operand types ('struct Point' and 'struct Point')}}
+}
+
+// Test with unions (should fail)
+union Data {
+ int i;
+ float f;
+};
+
+union Data test_unions(int cond) {
+ union Data d1 = {.i = 10};
+ union Data d2 = {.i = 20};
+
+ return __builtin_ct_select(cond, d1, d2); // expected-error {{incompatible operand types ('union Data' and 'union Data')}}
+}
+
+// Test with bit fields (should work as they're integers)
+struct BitField {
+ int a : 4;
+ int b : 4;
+};
+
+int test_bit_fields(int cond) {
+ struct BitField bf1 = {1, 2};
+ struct BitField bf2 = {3, 4};
+
+ // Individual bit fields should work
+ int result1 = __builtin_ct_select(cond, bf1.a, bf2.a);
+ int result2 = __builtin_ct_select(cond, bf1.b, bf2.b);
+
+ return result1 + result2;
+}
+
+// Test with designated initializers
+int test_designated_init(int cond) {
+ int arr1[3] = {[0] = 1, [1] = 2, [2] = 3};
+ int arr2[3] = {[0] = 4, [1] = 5, [2] = 6};
+
+ // Access specific elements
+ int result1 = __builtin_ct_select(cond, arr1[0], arr2[0]);
+ int result2 = __builtin_ct_select(cond, arr1[1], arr2[1]);
+
+ return result1 + result2;
+}
+
+// Test with complex expressions in arguments
+int complex_expr(int x) { return x * x; }
+
+int test_complex_arguments(int cond, int x, int y) {
+ // Function calls as arguments
+ int result1 = __builtin_ct_select(cond, complex_expr(x), complex_expr(y));
+
+ // Ternary operator as arguments
+ int result2 = __builtin_ct_select(cond, x > 0 ? x : -x, y > 0 ? y : -y);
+
+ // Compound literals
+ int result3 = __builtin_ct_select(cond, (int){x}, (int){y});
+
+ return result1 + result2 + result3;
+}
+
+// Test with preprocessor macros
+#define MACRO_A 42
+#define MACRO_B 24
+#define MACRO_COND(x) (x > 0)
+
+int test_macros(int x) {
+ int result1 = __builtin_ct_select(MACRO_COND(x), MACRO_A, MACRO_B);
+
+ // Nested macros
+ #define NESTED_SELECT(c, a, b) __builtin_ct_select(c, a, b)
+ int result2 = NESTED_SELECT(x, 10, 20);
+
+ return result1 + result2;
+}
+
+// Test with string literals (should fail)
+const char *test_strings(int cond) {
+ return __builtin_ct_select(cond, "hello", "world"); // expected-error {{incompatible operand types ('char[6]' and 'char[6]')}}
+}
+
+// Test with variable length arrays (VLA)
+int test_vla(int cond, int n) {
+ int vla1[n];
+ int vla2[n];
+
+ // Individual elements should work
+ vla1[0] = 1;
+ vla2[0] = 2;
+ int result = __builtin_ct_select(cond, vla1[0], vla2[0]);
+
+ return result;
+}
+
+// Test with typedef
+typedef int MyInt;
+typedef float MyFloat;
+
+MyInt test_typedef(int cond, MyInt a, MyInt b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test with different typedef types (should fail)
+MyInt test_different_typedef(int cond, MyInt a, MyFloat b) {
+ return __builtin_ct_select(cond, a, b); // expected-error {{incompatible operand types ('MyInt' (aka 'int') and 'MyFloat' (aka 'float'))}}
+}
+
+// Test with side effects (should be evaluated)
+int side_effect_counter = 0;
+int side_effect_func(int x) {
+ side_effect_counter++;
+ return x;
+}
+
+int test_side_effects(int cond) {
+ // Both arguments should be evaluated
+ int result = __builtin_ct_select(cond, side_effect_func(10), side_effect_func(20));
+ return result;
+}
+
+// Test with goto labels (context where expressions are used)
+int test_goto_context(int cond, int a, int b) {
+ int result = __builtin_ct_select(cond, a, b);
+
+ if (result > 0) {
+ goto positive;
+ } else {
+ goto negative;
+ }
+
+positive:
+ return result;
+
+negative:
+ return -result;
+}
+
+// Test with switch statements
+int test_switch_context(int cond, int a, int b) {
+ int result = __builtin_ct_select(cond, a, b);
+
+ switch (result) {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ default:
+ return -1;
+ }
+}
+
+// Test with loops
+int test_loop_context(int cond, int a, int b) {
+ int result = __builtin_ct_select(cond, a, b);
+ int sum = 0;
+
+ for (int i = 0; i < result; i++) {
+ sum += i;
+ }
+
+ return sum;
+}
+
+// Test with recursive functions
+int factorial(int n) {
+ if (n <= 1) return 1;
+ return n * factorial(n - 1);
+}
+
+int test_recursive(int cond, int n) {
+ int result = __builtin_ct_select(cond, n, n + 1);
+ return factorial(result);
+}
+
+// Test with inline functions
+static inline int inline_func(int x) {
+ return x * 2;
+}
+
+int test_inline(int cond, int a, int b) {
+ return __builtin_ct_select(cond, inline_func(a), inline_func(b));
+}
+
+// Test with static variables
+int test_static_vars(int cond) {
+ static int static_a = 10;
+ static int static_b = 20;
+
+ return __builtin_ct_select(cond, static_a, static_b);
+}
+
+// Test with extern variables
+extern int extern_a;
+extern int extern_b;
+
+int test_extern_vars(int cond) {
+ return __builtin_ct_select(cond, extern_a, extern_b);
+}
+
+// Test with register variables
+int test_register_vars(int cond) {
+ register int reg_a = 30;
+ register int reg_b = 40;
+
+ return __builtin_ct_select(cond, reg_a, reg_b);
+}
+
+// Test with thread-local variables (C11)
+#if __STDC_VERSION__ >= 201112L
+_Thread_local int tls_a = 50;
+_Thread_local int tls_b = 60;
+
+int test_tls_vars(int cond) {
+ return __builtin_ct_select(cond, tls_a, tls_b);
+}
+#endif
+
+// Test with atomic variables (C11)
+#if __STDC_VERSION__ >= 201112L
+#include <stdatomic.h>
+atomic_int atomic_a = 70;
+atomic_int atomic_b = 80;
+
+int test_atomic_vars(int cond) {
+ return __builtin_ct_select(cond, atomic_a, atomic_b); // expected-error {{incompatible operand types ('atomic_int' (aka '_Atomic(int)') and 'atomic_int')}}
+}
+#endif
diff --git a/clang/test/Sema/builtin-ct-select.c b/clang/test/Sema/builtin-ct-select.c
new file mode 100644
index 0000000000000..7749eb52eecb3
--- /dev/null
+++ b/clang/test/Sema/builtin-ct-select.c
@@ -0,0 +1,683 @@
+// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s
+
+// Test integer types
+int test_int(int cond, int a, int b) {
+ // CHECK-LABEL: define {{.*}} @test_int
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+long test_long(int cond, long a, long b) {
+ // CHECK-LABEL: define {{.*}} @test_long
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call i64 @llvm.ct.select.i64(i1 [[COND]], i64 %{{.*}}, i64 %{{.*}})
+ // CHECK: ret i64 [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+short test_short(int cond, short a, short b) {
+ // CHECK-LABEL: define {{.*}} @test_short
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call i16 @llvm.ct.select.i16(i1 [[COND]], i16 %{{.*}}, i16 %{{.*}})
+ // CHECK: ret i16 [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+unsigned char test_uchar(int cond, unsigned char a, unsigned char b) {
+ // CHECK-LABEL: define {{.*}} @test_uchar
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call i8 @llvm.ct.select.i8(i1 [[COND]], i8 %{{.*}}, i8 %{{.*}})
+ // CHECK: ret i8 [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+long long test_longlong(int cond, long long a, long long b) {
+ // CHECK-LABEL: define {{.*}} @test_longlong
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call i64 @llvm.ct.select.i64(i1 [[COND]], i64 %{{.*}}, i64 %{{.*}})
+ // CHECK: ret i64 [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test floating point types
+float test_float(int cond, float a, float b) {
+ // CHECK-LABEL: define {{.*}} @test_float
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call float @llvm.ct.select.f32(i1 [[COND]], float %{{.*}}, float %{{.*}})
+ // CHECK: ret float [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+double test_double(...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/166703
More information about the llvm-branch-commits
mailing list