[llvm-branch-commits] [clang] [ConstantTime][Clang] Add __builtin_ct_select for constant-time selection (PR #166703)
Julius Alexandre via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri May 22 18:09:53 PDT 2026
https://github.com/wizardengineer updated https://github.com/llvm/llvm-project/pull/166703
>From 4a82a13a2532ce2e71787c666dbdfd11585e20ed Mon Sep 17 00:00:00 2001
From: wizardengineer <juliuswoosebert at gmail.com>
Date: Wed, 5 Nov 2025 10:56:34 -0500
Subject: [PATCH 1/2] [ConstantTime][Clang] Add __builtin_ct_select for
constant-time selection
---
clang/docs/LanguageExtensions.rst | 44 ++
clang/include/clang/Basic/Builtins.td | 8 +
clang/lib/CodeGen/CGBuiltin.cpp | 13 +
clang/lib/Sema/SemaChecking.cpp | 64 ++
.../test/Sema/builtin-ct-select-edge-cases.c | 373 ++++++++++
clang/test/Sema/builtin-ct-select.c | 683 ++++++++++++++++++
6 files changed, 1185 insertions(+)
create mode 100644 clang/test/Sema/builtin-ct-select-edge-cases.c
create mode 100644 clang/test/Sema/builtin-ct-select.c
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 03cb02deb5e7f..6f5cd5f95cdb0 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -7332,3 +7332,47 @@ Clang fails to reject some code that should be rejected. e.g.,
// own initializer rather than rejecting the code with an undeclared identifier
// diagnostic.
auto x = x;
+
+.. _langext-__builtin_ct_select:
+
+``__builtin_ct_select``
+-----------------------
+
+``__builtin_ct_select`` performs a constant-time conditional selection between
+two values. Unlike the ternary operator ``?:``, this builtin is designed to
+execute in constant time regardless of the condition value, making it suitable
+for cryptographic and security-sensitive code where timing side-channels must
+be avoided.
+
+**Syntax**:
+
+.. code-block:: c++
+
+ __builtin_ct_select(condition, true_value, false_value)
+
+**Examples**:
+
+.. code-block:: c++
+
+ // Select between two integers
+ int result = __builtin_ct_select(secret_bit, value_a, value_b);
+
+ // Select between two pointers
+ int *ptr = __builtin_ct_select(condition, ptr_a, ptr_b);
+
+ // Select between two floating-point values
+ double d = __builtin_ct_select(flag, 1.0, 2.0);
+
+**Description**:
+
+The first argument is an integer condition that is converted to a boolean
+(non-zero is true, zero is false). The second and third arguments must have
+the same scalar or vector type. The builtin returns the second argument if
+the condition is true, otherwise the third argument.
+
+The operation is guaranteed to be lowered to constant-time machine code that
+does not branch on the condition value, preventing timing-based side-channel
+attacks.
+
+Query for this feature with ``__has_builtin(__builtin_ct_select)``.
+
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 40ec94ab75046..389754a37f7e3 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -5810,3 +5810,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 cac1628e68721..f69390b4ace57 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -6668,6 +6668,19 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
auto Str = CGM.GetAddrOfConstantCString(Name, "");
return RValue::get(Str.getPointer());
}
+ case Builtin::BI__builtin_ct_select: {
+ auto *Cond = EmitScalarExpr(E->getArg(0));
+ auto *A = EmitScalarExpr(E->getArg(1));
+ auto *B = EmitScalarExpr(E->getArg(2));
+
+ 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 cc834bbee23c4..e5a15c84de8d3 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -3928,6 +3928,70 @@ 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();
+ }
+
+ ExprResult ARes = DefaultFunctionArrayLvalueConversion(A);
+ ExprResult BRes = DefaultFunctionArrayLvalueConversion(B);
+ if (ARes.isInvalid() || BRes.isInvalid())
+ return ExprError();
+
+ A = ARes.get();
+ B = BRes.get();
+ TheCall->setArg(1, A);
+ TheCall->setArg(2, B);
+
+ 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
+ if (!Context.hasSameType(ATy, BTy)) {
+ // 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();
+ }
+
+ QualType ResultTy = ATy;
+ ExprResult CondRes = PerformContextuallyConvertToBool(Cond);
+ if (CondRes.isInvalid())
+ return ExprError();
+
+ TheCall->setArg(0, CondRes.get());
+ TheCall->setType(ResultTy);
+ return TheCall;
+ }
}
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..167b19bf20663
--- /dev/null
+++ b/clang/test/Sema/builtin-ct-select-edge-cases.c
@@ -0,0 +1,373 @@
+// 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);
+
+ 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");
+}
+
+// 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
diff --git a/clang/test/Sema/builtin-ct-select.c b/clang/test/Sema/builtin-ct-select.c
new file mode 100644
index 0000000000000..7f2d9291299d6
--- /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 long test_long(int cond, long long a, long 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(int cond, double a, double b) {
+ // CHECK-LABEL: define {{.*}} @test_double
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call double @llvm.ct.select.f64(i1 [[COND]], double %{{.*}}, double %{{.*}})
+ // CHECK: ret double [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test pointer types
+int *test_pointer(int cond, int *a, int *b) {
+ // CHECK-LABEL: define {{.*}} @test_pointer
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call ptr @llvm.ct.select.p0(i1 [[COND]], ptr %{{.*}}, ptr %{{.*}})
+ // CHECK: ret ptr [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test with different condition types
+int test_char_cond(char cond, int a, int b) {
+ // CHECK-LABEL: define {{.*}} @test_char_cond
+ // CHECK: [[COND:%.*]] = icmp ne i8 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+int test_long_cond(long long cond, int a, int b) {
+ // CHECK-LABEL: define {{.*}} @test_long_cond
+ // CHECK: [[COND:%.*]] = icmp ne i64 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test with boolean condition
+int test_bool_cond(_Bool cond, int a, int b) {
+ // CHECK-LABEL: define {{.*}} @test_bool_cond
+ // CHECK: [[COND:%.*]] = icmp ne i8 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test with constants
+int test_constant_cond(void) {
+ // CHECK-LABEL: define {{.*}} @test_constant_cond
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 true, i32 42, i32 24)
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(1, 42, 24);
+}
+
+int test_zero_cond(void) {
+ // CHECK-LABEL: define {{.*}} @test_zero_cond
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 false, i32 42, i32 24)
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(0, 42, 24);
+}
+
+// Test type promotion
+int test_promotion(int cond, short a, short b) {
+ // CHECK-LABEL: define {{.*}} @test_promotion
+ // CHECK-DAG: [[A_EXT:%.*]] = sext i16 %{{.*}} to i32
+ // CHECK-DAG: [[B_EXT:%.*]] = sext i16 %{{.*}} to i32
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 [[A_EXT]], i32 [[B_EXT]])
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(cond, (int)a, (int)b);
+}
+
+// Test mixed signedness
+unsigned int test_mixed_signedness(int cond, int a, unsigned int b) {
+ // CHECK-LABEL: define {{.*}} @test_mixed_signedness
+ // CHECK-DAG: [[A_EXT:%.*]] = sext i32 %{{.*}} to i64
+ // CHECK-DAG: [[B_EXT:%.*]] = zext i32 %{{.*}} to i64
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call i64 @llvm.ct.select.i64(i1 [[COND]], i64 [[A_EXT]], i64 [[B_EXT]])
+ // CHECK: [[RESULT_TRUNC:%.*]] = trunc i64 [[RESULT]] to i32
+ // CHECK: ret i32 [[RESULT_TRUNC]]
+ return __builtin_ct_select(cond, (long long)a, (long long)b);
+}
+
+// Test complex expression
+int test_complex_expr_alt(int x, int y) {
+ // CHECK-LABEL: define {{.*}} @test_complex_expr_alt
+ // CHECK-DAG: [[CMP:%.*]] = icmp sgt i32 %{{.*}}, 0
+ // CHECK-DAG: [[ADD:%.*]] = add nsw i32 %{{.*}}, %{{.*}}
+ // CHECK-DAG: [[SUB:%.*]] = sub nsw i32 %{{.*}}, %{{.*}}
+ // Separate the final sequence to ensure proper ordering
+ // CHECK-NEXT: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP]], i32 [[ADD]], i32 [[SUB]])
+ // CHECK-NEXT: ret i32 [[RESULT]]
+ return __builtin_ct_select(x > 0, x + y, x - y);
+}
+
+// Test nested calls
+int test_nested_structured(int cond1, int cond2, int a, int b, int c) {
+ // CHECK-LABEL: define {{.*}} @test_nested_structured
+ // Phase 1: Conditions (order doesn't matter)
+ // CHECK-DAG: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
+
+ // Phase 2: Inner select (must happen before outer)
+ // CHECK: [[INNER:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND2]], i32 %{{.*}}, i32 %{{.*}})
+
+ // Phase 3: Outer select (must use inner result)
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND1]], i32 [[INNER]], i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(cond1, __builtin_ct_select(cond2, a, b), c);
+}
+
+// Test with function calls
+int helper(int x) { return x * 2; }
+int test_function_calls(int cond, int x, int y) {
+ // CHECK-LABEL: define {{.*}} @test_function_calls
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[CALL1:%.*]] = call i32 @helper(i32 noundef %{{.*}})
+ // CHECK-DAG: [[CALL2:%.*]] = call i32 @helper(i32 noundef %{{.*}})
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 [[CALL1]], i32 [[CALL2]])
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(cond, helper(x), helper(y));
+}
+
+// Test using ct_select as condition for another ct_select
+int test_intrinsic_condition(int cond1, int cond2, int a, int b, int c, int d) {
+ // CHECK-LABEL: define {{.*}} @test_intrinsic_condition
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[INNER_COND:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK-DAG: [[FINAL_COND:%.*]] = icmp ne i32 [[INNER_COND]], 0
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(__builtin_ct_select(cond1, cond2, a), b, c);
+}
+
+// Test using comparison result of ct_select as condition
+int test_comparison_condition(int cond, int a, int b, int c, int d) {
+ // CHECK-LABEL: define {{.*}} @test_comparison_condition
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[FIRST_SELECT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: [[CMP:%.*]] = icmp sgt i32 [[FIRST_SELECT]], %{{.*}}
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(__builtin_ct_select(cond, a, b) > c, d, a);
+}
+
+// Test using ct_select result in arithmetic as condition
+int test_arithmetic_condition(int cond, int a, int b, int c, int d) {
+ // CHECK-LABEL: define {{.*}} @test_arithmetic_condition
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[FIRST_SELECT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: [[ADD:%.*]] = add nsw i32 [[FIRST_SELECT]], %{{.*}}
+ // CHECK: [[FINAL_COND:%.*]] = icmp ne i32 [[ADD]], 0
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(__builtin_ct_select(cond, a, b) + c, d, a);
+}
+
+// Test chained ct_select as conditions
+int test_chained_conditions(int cond1, int cond2, int cond3, int a, int b, int c, int d, int e) {
+ // CHECK-LABEL: define {{.*}} @test_chained_conditions
+ // CHECK: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[FIRST:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND1]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[SECOND:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND2]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK-DAG: [[FINAL_COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ int first_select = __builtin_ct_select(cond1, a, b);
+ int second_select = __builtin_ct_select(cond2, first_select, c);
+ return __builtin_ct_select(second_select, d, e);
+}
+
+// Test using ct_select with pointer condition
+//int test_pointer_condition(int *ptr1, int *ptr2, int a, int b, int c) {
+ // NO-CHECK-LABEL: define {{.*}} @test_pointer_condition
+ // NO-CHECK: [[PTR_COND:%.*]] = icmp ne ptr %{{.*}}, null
+ // NO-CHECK: [[PTR_SELECT:%.*]] = call ptr @llvm.ct.select.p0(i1 [[PTR_COND]], ptr %{{.*}}, ptr %{{.*}})
+ // NO-CHECK: [[FINAL_COND:%.*]] = icmp ne ptr [[PTR_SELECT]], null
+ // NO-CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
+ // NO-CHECK: ret i32 [[RESULT]]
+// return __builtin_ct_select(__builtin_ct_select(ptr1, ptr1, ptr2), a, b);
+//}
+
+
+// Test using ct_select result in logical operations as condition
+int test_logical_condition(int cond1, int cond2, int a, int b, int c, int d) {
+ // CHECK-LABEL: define {{.*}} @test_logical_condition
+ // CHECK-DAG: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[FIRST_SELECT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND1]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK-DAG: [[SELECT_BOOL:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 %{{.*}}, i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(__builtin_ct_select(cond1, a, b) && cond2, c, d);
+}
+
+// Test multiple levels of ct_select as conditions
+int test_deep_condition_nesting(int cond1, int cond2, int cond3, int a, int b, int c, int d, int e, int f) {
+ // CHECK-LABEL: define {{.*}} @test_deep_condition_nesting
+ // CHECK-DAG: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[INNER1:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND2]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK-DAG: [[INNER1_COND:%.*]] = icmp ne i32 [[INNER1]], 0
+ // CHECK-DAG: [[INNER2:%.*]] = call i32 @llvm.ct.select.i32(i1 [[INNER1_COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK-DAG: [[OUTER:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND1]], i32 [[INNER2]], i32 %{{.*}})
+ // CHECK-DAG: [[FINAL_COND:%.*]] = icmp ne i32 [[OUTER]], 0
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(__builtin_ct_select(cond1, __builtin_ct_select(__builtin_ct_select(cond2, a, b), c, d), e), f, a);
+}
+
+// Test ct_select with complex condition expressions
+int test_complex_condition_expr(int x, int y, int z, int a, int b) {
+ // CHECK-LABEL: define {{.*}} @test_complex_condition_expr
+ // CHECK: [[CMP1:%.*]] = icmp sgt i32 %{{.*}}, %{{.*}}
+ // CHECK: [[SELECT1:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP1]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: [[CMP2:%.*]] = icmp slt i32 [[SELECT1]], %{{.*}}
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP2]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ return __builtin_ct_select(__builtin_ct_select(x > y, x, y) < z, a, b);
+}
+
+// Test vector types - 128-bit vectors
+typedef int __attribute__((vector_size(16))) int4;
+typedef float __attribute__((vector_size(16))) float4;
+typedef short __attribute__((vector_size(16))) short8;
+typedef char __attribute__((vector_size(16))) char16;
+
+int4 test_vector_int4(int cond, int4 a, int4 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_int4
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[COND]], <4 x i32> %{{.*}}, <4 x i32> %{{.*}})
+ // CHECK: ret <4 x i32> [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+float4 test_vector_float4(int cond, float4 a, float4 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_float4
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
+ // CHECK: ret <4 x float> [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+short8 test_vector_short8(int cond, short8 a, short8 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_short8
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <8 x i16> @llvm.ct.select.v8i16(i1 [[COND]], <8 x i16> %{{.*}}, <8 x i16> %{{.*}})
+ // CHECK: ret <8 x i16> [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+char16 test_vector_char16(int cond, char16 a, char16 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_char16
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <16 x i8> @llvm.ct.select.v16i8(i1 [[COND]], <16 x i8> %{{.*}}, <16 x i8> %{{.*}})
+ // CHECK: ret <16 x i8> [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test 256-bit vectors
+typedef int __attribute__((vector_size(32))) int8;
+typedef float __attribute__((vector_size(32))) float8;
+typedef double __attribute__((vector_size(32))) double4;
+
+int8 test_vector_int8(int cond, int8 a, int8 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_int8
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call <8 x i32> @llvm.ct.select.v8i32(i1 [[COND]], <8 x i32> %{{.*}}, <8 x i32> %{{.*}})
+ return __builtin_ct_select(cond, a, b);
+}
+
+float8 test_vector_float8(int cond, float8 a, float8 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_float8
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call <8 x float> @llvm.ct.select.v8f32(i1 [[COND]], <8 x float> %{{.*}}, <8 x float> %{{.*}})
+ return __builtin_ct_select(cond, a, b);
+}
+
+double4 test_vector_double4(int cond, double4 a, double4 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_double4
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call <4 x double> @llvm.ct.select.v4f64(i1 [[COND]], <4 x double> %{{.*}}, <4 x double> %{{.*}})
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test 512-bit vectors
+typedef int __attribute__((vector_size(64))) int16;
+typedef float __attribute__((vector_size(64))) float16;
+
+int16 test_vector_int16(int cond, int16 a, int16 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_int16
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <16 x i32> @llvm.ct.select.v16i32(i1 [[COND]], <16 x i32> %{{.*}}, <16 x i32> %{{.*}})
+ return __builtin_ct_select(cond, a, b);
+}
+
+float16 test_vector_float16(int cond, float16 a, float16 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_float16
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <16 x float> @llvm.ct.select.v16f32(i1 [[COND]], <16 x float> %{{.*}}, <16 x float> %{{.*}})
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test vector operations with different condition types
+int4 test_vector_char_cond(char cond, int4 a, int4 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_char_cond
+ // CHECK: [[COND:%.*]] = icmp ne i8 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[COND]], <4 x i32> %{{.*}}, <4 x i32> %{{.*}})
+ // CHECK: ret <4 x i32> [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+float4 test_vector_long_cond(long long cond, float4 a, float4 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_long_cond
+ // CHECK: [[COND:%.*]] = icmp ne i64 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
+ // CHECK: ret <4 x float> [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test vector constants
+int4 test_vector_constant_cond(void) {
+ // CHECK-LABEL: define {{.*}} @test_vector_constant_cond
+ // CHECK: [[RESULT:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 true, <4 x i32> %{{.*}}, <4 x i32> %{{.*}})
+ // CHECK: ret <4 x i32> [[RESULT]]
+ int4 a = {1, 2, 3, 4};
+ int4 b = {5, 6, 7, 8};
+ return __builtin_ct_select(1, a, b);
+}
+
+float4 test_vector_zero_cond(void) {
+ // CHECK-LABEL: define {{.*}} @test_vector_zero_cond
+ // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 false, <4 x float> %{{.*}}, <4 x float> %{{.*}})
+ // CHECK: ret <4 x float> [[RESULT]]
+ float4 a = {1.0f, 2.0f, 3.0f, 4.0f};
+ float4 b = {5.0f, 6.0f, 7.0f, 8.0f};
+ return __builtin_ct_select(0, a, b);
+}
+
+// Test nested vector selections
+int4 test_vector_nested(int cond1, int cond2, int4 a, int4 b, int4 c) {
+ // CHECK-LABEL: define {{.*}} @test_vector_nested
+ // CHECK-DAG: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[INNER:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[COND2]], <4 x i32> %{{.*}}, <4 x i32> %{{.*}})
+ // CHECK: [[RESULT:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[COND1]], <4 x i32> [[INNER]], <4 x i32> %{{.*}})
+ // CHECK: ret <4 x i32> [[RESULT]]
+ return __builtin_ct_select(cond1, __builtin_ct_select(cond2, a, b), c);
+}
+
+// Test vector selection with complex expressions
+float4 test_vector_complex_expr(int x, int y, float4 a, float4 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_complex_expr
+ // CHECK: [[CMP:%.*]] = icmp sgt i32 %{{.*}}, %{{.*}}
+ // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[CMP]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
+ // CHECK: ret <4 x float> [[RESULT]]
+ return __builtin_ct_select(x > y, a, b);
+}
+
+// Test vector with different element sizes
+typedef long long __attribute__((vector_size(16))) long2;
+typedef double __attribute__((vector_size(16))) double2;
+
+long2 test_vector_long2(int cond, long2 a, long2 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_long2
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <2 x i64> @llvm.ct.select.v2i64(i1 [[COND]], <2 x i64> %{{.*}}, <2 x i64> %{{.*}})
+ // CHECK: ret <2 x i64> [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+double2 test_vector_double2(int cond, double2 a, double2 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_double2
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <2 x double> @llvm.ct.select.v2f64(i1 [[COND]], <2 x double> %{{.*}}, <2 x double> %{{.*}})
+ // CHECK: ret <2 x double> [[RESULT]]
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test mixed vector operations
+int4 test_vector_from_scalar_condition(int4 vec_cond, int4 a, int4 b) {
+ // CHECK-LABEL: define {{.*}} @test_vector_from_scalar_condition
+ // Extract first element and use as condition
+ int scalar_cond = vec_cond[0];
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[COND]], <4 x i32> %{{.*}}, <4 x i32> %{{.*}})
+ // CHECK: ret <4 x i32> [[RESULT]]
+ return __builtin_ct_select(scalar_cond, a, b);
+}
+
+// Test vector chaining
+float4 test_vector_chaining(int cond1, int cond2, int cond3, float4 a, float4 b, float4 c, float4 d) {
+ // CHECK-LABEL: define {{.*}} @test_vector_chaining
+ // CHECK-DAG: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[COND3:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[FIRST:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND1]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
+ // CHECK-DAG: [[SECOND:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND2]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
+ // CHECK-DAG: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND3]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
+ // CHECK: ret <4 x float> [[RESULT]]
+ float4 first = __builtin_ct_select(cond1, a, b);
+ float4 second = __builtin_ct_select(cond2, first, c);
+ return __builtin_ct_select(cond3, second, d);
+}
+
+// Test special floating point values - NaN
+float test_nan_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_nan_operands
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call float @llvm.ct.select.f32(i1 [[COND]], float %{{.*}}, float 1.000000e+00)
+ // CHECK: ret float [[RESULT]]
+ float nan_val = __builtin_nanf("");
+ return __builtin_ct_select(cond, nan_val, 1.0f);
+}
+
+double test_nan_double_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_nan_double_operands
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call double @llvm.ct.select.f64(i1 [[COND]], double %{{.*}}, double 2.000000e+00)
+ // CHECK: ret double [[RESULT]]
+ double nan_val = __builtin_nan("");
+ return __builtin_ct_select(cond, nan_val, 2.0);
+}
+
+// Test infinity values
+float test_infinity_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_infinity_operands
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call float @llvm.ct.select.f32(i1 [[COND]], float %{{.*}}, float %{{.*}})
+ // CHECK: ret float [[RESULT]]
+ float pos_inf = __builtin_inff();
+ float neg_inf = -__builtin_inff();
+ return __builtin_ct_select(cond, pos_inf, neg_inf);
+}
+
+double test_infinity_double_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_infinity_double_operands
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call double @llvm.ct.select.f64(i1 [[COND]], double %{{.*}}, double %{{.*}})
+ // CHECK: ret double [[RESULT]]
+ double pos_inf = __builtin_inf();
+ double neg_inf = -__builtin_inf();
+ return __builtin_ct_select(cond, pos_inf, neg_inf);
+}
+
+// Test subnormal/denormal values
+float test_subnormal_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_subnormal_operands
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call float @llvm.ct.select.f32(i1 [[COND]], float %{{.*}}, float %{{.*}})
+ // CHECK: ret float [[RESULT]]
+ // Very small subnormal values
+ float subnormal1 = 1e-40f;
+ float subnormal2 = 1e-45f;
+ return __builtin_ct_select(cond, subnormal1, subnormal2);
+}
+
+// Test integer overflow boundaries
+int test_integer_overflow_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_integer_overflow_operands
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ int max_int = __INT_MAX__;
+ int min_int = (-__INT_MAX__ - 1);
+ return __builtin_ct_select(cond, max_int, min_int);
+}
+
+long long test_longlong_overflow_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_longlong_overflow_operands
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call i64 @llvm.ct.select.i64(i1 [[COND]], i64 %{{.*}}, i64 %{{.*}})
+ // CHECK: ret i64 [[RESULT]]
+ long long max_ll = __LONG_LONG_MAX__;
+ long long min_ll = (-__LONG_LONG_MAX__ - 1);
+ return __builtin_ct_select(cond, max_ll, min_ll);
+}
+
+// Test unsigned overflow boundaries
+unsigned int test_unsigned_overflow_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_unsigned_overflow_operands
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ unsigned int max_uint = 4294967295;
+ unsigned int min_uint = 0;
+ return __builtin_ct_select(cond, max_uint, min_uint);
+}
+
+// Test null pointer dereference avoidance
+int* test_null_pointer_operands(int cond, int* valid_ptr) {
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call ptr @llvm.ct.select.p0(i1 [[COND]], ptr %{{.*}}, ptr %{{.*}})
+ // CHECK: ret ptr [[RESULT]]
+ int* null_ptr = (int*)0;
+ return __builtin_ct_select(cond, null_ptr, valid_ptr);
+}
+
+// Test volatile operations
+volatile int global_volatile = 42;
+int test_volatile_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_volatile_operands
+ // CHECK-DAG: [[VOLATILE_LOAD:%.*]] = load volatile i32, ptr {{.*}}
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 100)
+ // CHECK: ret i32 [[RESULT]]
+ volatile int vol_val = global_volatile;
+ return __builtin_ct_select(cond, vol_val, 100);
+}
+
+// Test uninitialized variable behavior (should still work with ct_select)
+int test_uninitialized_operands(int cond, int initialized) {
+ // CHECK-LABEL: define {{.*}} @test_uninitialized_operands
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ int uninitialized; // Intentionally uninitialized
+ return __builtin_ct_select(cond, uninitialized, initialized);
+}
+
+// Test zero division avoidance patterns
+int test_division_by_zero_avoidance(int cond, int dividend, int divisor) {
+ // CHECK-LABEL: define {{.*}} @test_division_by_zero_avoidance
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[DIV_RESULT:%.*]] = sdiv i32 %{{.*}}, %{{.*}}
+ // CHECK-DAG: [[SAFE_DIVISOR:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 1)
+ // First get a safe divisor (never zero)
+ int safe_divisor = __builtin_ct_select(divisor != 0, divisor, 1);
+ // Then perform division with guaranteed non-zero divisor
+ return dividend / safe_divisor;
+}
+
+// Test array bounds checking patterns
+int test_array_bounds_protection(int cond, int index, int* array) {
+ // CHECK-LABEL: define {{.*}} @test_array_bounds_protection
+ // CHECK-DAG: [[SAFE_INDEX:%.*]] = call i32 @llvm.ct.select.i32(i1 {{.*}}, i32 %{{.*}}, i32 0)
+ // Use ct_select to ensure safe array indexing
+ int safe_index = __builtin_ct_select(index >= 0 && index < 10, index, 0);
+ return array[safe_index];
+}
+
+// Test bit manipulation edge cases
+unsigned int test_bit_manipulation_edge_cases(int cond, unsigned int value) {
+ // CHECK-LABEL: define {{.*}} @test_bit_manipulation_edge_cases
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[SHIFT_LEFT:%.*]] = shl i32 %{{.*}}, 31
+ // CHECK-DAG: [[SHIFT_RIGHT:%.*]] = lshr i32 %{{.*}}, 31
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ // Test extreme bit shifts that could cause undefined behavior
+ unsigned int left_shift = value << 31; // Could overflow
+ unsigned int right_shift = value >> 31; // Extract sign bit
+ return __builtin_ct_select(cond, left_shift, right_shift);
+}
+
+// Test signed integer wraparound
+int test_signed_wraparound(int cond, int a, int b) {
+ // CHECK-LABEL: define {{.*}} @test_signed_wraparound
+ // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK-DAG: [[ADD:%.*]] = add nsw i32 %{{.*}}, %{{.*}}
+ // CHECK-DAG: [[SUB:%.*]] = sub nsw i32 %{{.*}}, %{{.*}}
+ // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
+ // CHECK: ret i32 [[RESULT]]
+ int sum = a + b; // Could overflow
+ int diff = a - b; // Could underflow
+ return __builtin_ct_select(cond, sum, diff);
+}
+
+// Test vector NaN handling
+float4 test_vector_nan_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_vector_nan_operands
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
+ // CHECK: ret <4 x float> [[RESULT]]
+ float nan_val = __builtin_nanf("");
+ float4 nan_vec = {nan_val, nan_val, nan_val, nan_val};
+ float4 normal_vec = {1.0f, 2.0f, 3.0f, 4.0f};
+ return __builtin_ct_select(cond, nan_vec, normal_vec);
+}
+
+// Test vector infinity handling
+float4 test_vector_infinity_operands(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_vector_infinity_operands
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
+ // CHECK: ret <4 x float> [[RESULT]]
+ float pos_inf = __builtin_inff();
+ float neg_inf = -__builtin_inff();
+ float4 inf_vec = {pos_inf, neg_inf, pos_inf, neg_inf};
+ float4 zero_vec = {0.0f, 0.0f, 0.0f, 0.0f};
+ return __builtin_ct_select(cond, inf_vec, zero_vec);
+}
+
+// Test mixed special values
+double test_mixed_special_values(int cond) {
+ // CHECK-LABEL: define {{.*}} @test_mixed_special_values
+ // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
+ // CHECK: [[RESULT:%.*]] = call double @llvm.ct.select.f64(i1 [[COND]], double %{{.*}}, double %{{.*}})
+ // CHECK: ret double [[RESULT]]
+ double nan_val = __builtin_nan("");
+ double inf_val = __builtin_inf();
+ return __builtin_ct_select(cond, nan_val, inf_val);
+}
+
+// Test constant-time memory access pattern
+int test_constant_time_memory_access(int secret_index, int* data_array) {
+ // CHECK-LABEL: define {{.*}} @test_constant_time_memory_access
+ // This pattern ensures constant-time memory access regardless of secret_index value
+ int result = 0;
+ // Use ct_select to accumulate values without revealing the secret index
+ for (int i = 0; i < 8; i++) {
+ int is_target = (i == secret_index);
+ int current_value = data_array[i];
+ int selected_value = __builtin_ct_select(is_target, current_value, 0);
+ result += selected_value;
+ }
+ return result;
+}
+
+// Test timing-attack resistant comparison
+int test_timing_resistant_comparison(const char* secret, const char* guess) {
+ // CHECK-LABEL: define {{.*}} @test_timing_resistant_comparison
+ // Constant-time string comparison using ct_select
+ int match = 1;
+ for (int i = 0; i < 32; i++) {
+ int chars_equal = (secret[i] == guess[i]);
+ int both_null = (secret[i] == 0) && (guess[i] == 0);
+ int still_matching = __builtin_ct_select(chars_equal || both_null, match, 0);
+ match = __builtin_ct_select(both_null, match, still_matching);
+ }
+ return match;
+}
>From ee350427d29647d798b13bc74517ffe3c210acd0 Mon Sep 17 00:00:00 2001
From: AkshayK <iit.akshay at gmail.com>
Date: Fri, 22 May 2026 18:27:24 -0400
Subject: [PATCH 2/2] [ConstantTime][Clang] Split __builtin_ct_select tests;
expand coverage; misc fixes
- Move codegen test to clang/test/CodeGen/, regen with update_cc_test_checks
- Add ext_vector_type, half/bfloat, and array/function-decay coverage
- New clang/test/Sema/builtin-ct-select.c: -verify diagnostic tests for
too-few/too-many args, non-integer cond, non-scalar/mismatched operands
- Sema: drop else-after-return in arg-count check
---
clang/lib/Sema/SemaChecking.cpp | 13 +-
clang/test/CodeGen/builtin-ct-select.c | 1938 ++++++++++++++++++++++++
clang/test/Sema/builtin-ct-select.c | 692 +--------
3 files changed, 1966 insertions(+), 677 deletions(-)
create mode 100644 clang/test/CodeGen/builtin-ct-select.c
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index e5a15c84de8d3..319d9227343a9 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -3931,18 +3931,15 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
case Builtin::BI__builtin_ct_select: {
if (TheCall->getNumArgs() != 3) {
- // Simple argument count check without complex diagnostics
- if (TheCall->getNumArgs() < 3) {
+ 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();
- }
+ 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);
diff --git a/clang/test/CodeGen/builtin-ct-select.c b/clang/test/CodeGen/builtin-ct-select.c
new file mode 100644
index 0000000000000..da39ded5ad300
--- /dev/null
+++ b/clang/test/CodeGen/builtin-ct-select.c
@@ -0,0 +1,1938 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 6
+// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s
+
+// Test integer types
+// CHECK-LABEL: define i32 @test_int(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+int test_int(int cond, int a, int b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define i64 @test_long(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i64 noundef [[A:%.*]], i64 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i64 [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT: store i64 [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i64, ptr [[A_ADDR]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load i64, ptr [[B_ADDR]], align 8
+// CHECK-NEXT: [[TMP3:%.*]] = call i64 @llvm.ct.select.i64(i1 [[TOBOOL]], i64 [[TMP1]], i64 [[TMP2]])
+// CHECK-NEXT: ret i64 [[TMP3]]
+//
+long long test_long(int cond, long long a, long long b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define i16 @test_short(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i16 noundef [[A:%.*]], i16 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i16, align 2
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i16, align 2
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i16 [[A]], ptr [[A_ADDR]], align 2
+// CHECK-NEXT: store i16 [[B]], ptr [[B_ADDR]], align 2
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i16, ptr [[A_ADDR]], align 2
+// CHECK-NEXT: [[TMP2:%.*]] = load i16, ptr [[B_ADDR]], align 2
+// CHECK-NEXT: [[TMP3:%.*]] = call i16 @llvm.ct.select.i16(i1 [[TOBOOL]], i16 [[TMP1]], i16 [[TMP2]])
+// CHECK-NEXT: ret i16 [[TMP3]]
+//
+short test_short(int cond, short a, short b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define i8 @test_uchar(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i8 noundef [[A:%.*]], i8 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i8, align 1
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i8, align 1
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i8 [[A]], ptr [[A_ADDR]], align 1
+// CHECK-NEXT: store i8 [[B]], ptr [[B_ADDR]], align 1
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[A_ADDR]], align 1
+// CHECK-NEXT: [[TMP2:%.*]] = load i8, ptr [[B_ADDR]], align 1
+// CHECK-NEXT: [[TMP3:%.*]] = call i8 @llvm.ct.select.i8(i1 [[TOBOOL]], i8 [[TMP1]], i8 [[TMP2]])
+// CHECK-NEXT: ret i8 [[TMP3]]
+//
+unsigned char test_uchar(int cond, unsigned char a, unsigned char b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define i64 @test_longlong(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i64 noundef [[A:%.*]], i64 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i64 [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT: store i64 [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i64, ptr [[A_ADDR]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load i64, ptr [[B_ADDR]], align 8
+// CHECK-NEXT: [[TMP3:%.*]] = call i64 @llvm.ct.select.i64(i1 [[TOBOOL]], i64 [[TMP1]], i64 [[TMP2]])
+// CHECK-NEXT: ret i64 [[TMP3]]
+//
+long long test_longlong(int cond, long long a, long long b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test floating point types
+// CHECK-LABEL: define float @test_float(
+// CHECK-SAME: i32 noundef [[COND:%.*]], float noundef [[A:%.*]], float noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca float, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca float, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store float [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store float [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load float, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load float, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call float @llvm.ct.select.f32(i1 [[TOBOOL]], float [[TMP1]], float [[TMP2]])
+// CHECK-NEXT: ret float [[TMP3]]
+//
+float test_float(int cond, float a, float b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define double @test_double(
+// CHECK-SAME: i32 noundef [[COND:%.*]], double noundef [[A:%.*]], double noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca double, align 8
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store double [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT: store double [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load double, ptr [[A_ADDR]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load double, ptr [[B_ADDR]], align 8
+// CHECK-NEXT: [[TMP3:%.*]] = call double @llvm.ct.select.f64(i1 [[TOBOOL]], double [[TMP1]], double [[TMP2]])
+// CHECK-NEXT: ret double [[TMP3]]
+//
+double test_double(int cond, double a, double b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test pointer types
+// CHECK-LABEL: define ptr @test_pointer(
+// CHECK-SAME: i32 noundef [[COND:%.*]], ptr noundef [[A:%.*]], ptr noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store ptr [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT: store ptr [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// CHECK-NEXT: [[TMP3:%.*]] = call ptr @llvm.ct.select.p0(i1 [[TOBOOL]], ptr [[TMP1]], ptr [[TMP2]])
+// CHECK-NEXT: ret ptr [[TMP3]]
+//
+int *test_pointer(int cond, int *a, int *b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test with different condition types
+// CHECK-LABEL: define i32 @test_char_cond(
+// CHECK-SAME: i8 noundef [[COND:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i8, align 1
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i8 [[COND]], ptr [[COND_ADDR]], align 1
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[COND_ADDR]], align 1
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i8 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+int test_char_cond(char cond, int a, int b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define i32 @test_long_cond(
+// CHECK-SAME: i64 noundef [[COND:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i64 [[COND]], ptr [[COND_ADDR]], align 8
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i64, ptr [[COND_ADDR]], align 8
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i64 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+int test_long_cond(long long cond, int a, int b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test with boolean condition
+// CHECK-LABEL: define i32 @test_bool_cond(
+// CHECK-SAME: i1 noundef [[COND:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i8, align 1
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[STOREDV:%.*]] = zext i1 [[COND]] to i8
+// CHECK-NEXT: store i8 [[STOREDV]], ptr [[COND_ADDR]], align 1
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[COND_ADDR]], align 1
+// CHECK-NEXT: [[LOADEDV:%.*]] = icmp ne i8 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[LOADEDV]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+int test_bool_cond(_Bool cond, int a, int b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test with constants
+// CHECK-LABEL: define i32 @test_constant_cond(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call i32 @llvm.ct.select.i32(i1 true, i32 42, i32 24)
+// CHECK-NEXT: ret i32 [[TMP0]]
+//
+int test_constant_cond(void) {
+ return __builtin_ct_select(1, 42, 24);
+}
+
+// CHECK-LABEL: define i32 @test_zero_cond(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call i32 @llvm.ct.select.i32(i1 false, i32 42, i32 24)
+// CHECK-NEXT: ret i32 [[TMP0]]
+//
+int test_zero_cond(void) {
+ return __builtin_ct_select(0, 42, 24);
+}
+
+// Test type promotion
+// CHECK-LABEL: define i32 @test_promotion(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i16 noundef [[A:%.*]], i16 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i16, align 2
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i16, align 2
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i16 [[A]], ptr [[A_ADDR]], align 2
+// CHECK-NEXT: store i16 [[B]], ptr [[B_ADDR]], align 2
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i16, ptr [[A_ADDR]], align 2
+// CHECK-NEXT: [[CONV:%.*]] = sext i16 [[TMP1]] to i32
+// CHECK-NEXT: [[TMP2:%.*]] = load i16, ptr [[B_ADDR]], align 2
+// CHECK-NEXT: [[CONV1:%.*]] = sext i16 [[TMP2]] to i32
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[CONV]], i32 [[CONV1]])
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+int test_promotion(int cond, short a, short b) {
+ return __builtin_ct_select(cond, (int)a, (int)b);
+}
+
+// Test mixed signedness
+// CHECK-LABEL: define i32 @test_mixed_signedness(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[CONV:%.*]] = sext i32 [[TMP1]] to i64
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[CONV1:%.*]] = zext i32 [[TMP2]] to i64
+// CHECK-NEXT: [[TMP3:%.*]] = call i64 @llvm.ct.select.i64(i1 [[TOBOOL]], i64 [[CONV]], i64 [[CONV1]])
+// CHECK-NEXT: [[CONV2:%.*]] = trunc i64 [[TMP3]] to i32
+// CHECK-NEXT: ret i32 [[CONV2]]
+//
+unsigned int test_mixed_signedness(int cond, int a, unsigned int b) {
+ return __builtin_ct_select(cond, (long long)a, (long long)b);
+}
+
+// Test complex expression
+// CHECK-LABEL: define i32 @test_complex_expr_alt(
+// CHECK-SAME: i32 noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[Y_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[X]], ptr [[X_ADDR]], align 4
+// CHECK-NEXT: store i32 [[Y]], ptr [[Y_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[Y_ADDR]], align 4
+// CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP1]], [[TMP2]]
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[Y_ADDR]], align 4
+// CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 [[TMP3]], [[TMP4]]
+// CHECK-NEXT: [[TMP5:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP]], i32 [[ADD]], i32 [[SUB]])
+// CHECK-NEXT: ret i32 [[TMP5]]
+//
+int test_complex_expr_alt(int x, int y) {
+ // Separate the final sequence to ensure proper ordering
+ return __builtin_ct_select(x > 0, x + y, x - y);
+}
+
+// Test nested calls
+// CHECK-LABEL: define i32 @test_nested_structured(
+// CHECK-SAME: i32 noundef [[COND1:%.*]], i32 noundef [[COND2:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]], i32 noundef [[C:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND1_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COND2_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND1]], ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: store i32 [[COND2]], ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL1:%.*]] = icmp ne i32 [[TMP1]], 0
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL1]], i32 [[TMP2]], i32 [[TMP3]])
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// CHECK-NEXT: [[TMP6:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP4]], i32 [[TMP5]])
+// CHECK-NEXT: ret i32 [[TMP6]]
+//
+int test_nested_structured(int cond1, int cond2, int a, int b, int c) {
+ // Phase 1: Conditions (order doesn't matter)
+
+ // Phase 2: Inner select (must happen before outer)
+
+ // Phase 3: Outer select (must use inner result)
+ return __builtin_ct_select(cond1, __builtin_ct_select(cond2, a, b), c);
+}
+
+// Test with function calls
+// CHECK-LABEL: define i32 @helper(
+// CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[X]], ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[MUL:%.*]] = mul nsw i32 [[TMP0]], 2
+// CHECK-NEXT: ret i32 [[MUL]]
+//
+int helper(int x) { return x * 2; }
+// CHECK-LABEL: define i32 @test_function_calls(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i32 noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[Y_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[X]], ptr [[X_ADDR]], align 4
+// CHECK-NEXT: store i32 [[Y]], ptr [[Y_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[CALL:%.*]] = call i32 @helper(i32 noundef [[TMP1]])
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[Y_ADDR]], align 4
+// CHECK-NEXT: [[CALL1:%.*]] = call i32 @helper(i32 noundef [[TMP2]])
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[CALL]], i32 [[CALL1]])
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+int test_function_calls(int cond, int x, int y) {
+ return __builtin_ct_select(cond, helper(x), helper(y));
+}
+
+// Test using ct_select as condition for another ct_select
+// CHECK-LABEL: define i32 @test_intrinsic_condition(
+// CHECK-SAME: i32 noundef [[COND1:%.*]], i32 noundef [[COND2:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]], i32 noundef [[C:%.*]], i32 noundef [[D:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND1_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COND2_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[D_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND1]], ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: store i32 [[COND2]], ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// CHECK-NEXT: store i32 [[D]], ptr [[D_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: [[TOBOOL1:%.*]] = icmp ne i32 [[TMP3]], 0
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// CHECK-NEXT: [[TMP6:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL1]], i32 [[TMP4]], i32 [[TMP5]])
+// CHECK-NEXT: ret i32 [[TMP6]]
+//
+int test_intrinsic_condition(int cond1, int cond2, int a, int b, int c, int d) {
+ return __builtin_ct_select(__builtin_ct_select(cond1, cond2, a), b, c);
+}
+
+// Test using comparison result of ct_select as condition
+// CHECK-LABEL: define i32 @test_comparison_condition(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]], i32 noundef [[C:%.*]], i32 noundef [[D:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[D_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// CHECK-NEXT: store i32 [[D]], ptr [[D_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP3]], [[TMP4]]
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[D_ADDR]], align 4
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP7:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP]], i32 [[TMP5]], i32 [[TMP6]])
+// CHECK-NEXT: ret i32 [[TMP7]]
+//
+int test_comparison_condition(int cond, int a, int b, int c, int d) {
+ return __builtin_ct_select(__builtin_ct_select(cond, a, b) > c, d, a);
+}
+
+// Test using ct_select result in arithmetic as condition
+// CHECK-LABEL: define i32 @test_arithmetic_condition(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]], i32 noundef [[C:%.*]], i32 noundef [[D:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[D_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// CHECK-NEXT: store i32 [[D]], ptr [[D_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP3]], [[TMP4]]
+// CHECK-NEXT: [[TOBOOL1:%.*]] = icmp ne i32 [[ADD]], 0
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[D_ADDR]], align 4
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP7:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL1]], i32 [[TMP5]], i32 [[TMP6]])
+// CHECK-NEXT: ret i32 [[TMP7]]
+//
+int test_arithmetic_condition(int cond, int a, int b, int c, int d) {
+ return __builtin_ct_select(__builtin_ct_select(cond, a, b) + c, d, a);
+}
+
+// Test chained ct_select as conditions
+// CHECK-LABEL: define i32 @test_chained_conditions(
+// CHECK-SAME: i32 noundef [[COND1:%.*]], i32 noundef [[COND2:%.*]], i32 noundef [[COND3:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]], i32 noundef [[C:%.*]], i32 noundef [[D:%.*]], i32 noundef [[E:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND1_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COND2_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COND3_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[D_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[E_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[FIRST_SELECT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[SECOND_SELECT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND1]], ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: store i32 [[COND2]], ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: store i32 [[COND3]], ptr [[COND3_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// CHECK-NEXT: store i32 [[D]], ptr [[D_ADDR]], align 4
+// CHECK-NEXT: store i32 [[E]], ptr [[E_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: store i32 [[TMP3]], ptr [[FIRST_SELECT]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL1:%.*]] = icmp ne i32 [[TMP4]], 0
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[FIRST_SELECT]], align 4
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// CHECK-NEXT: [[TMP7:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL1]], i32 [[TMP5]], i32 [[TMP6]])
+// CHECK-NEXT: store i32 [[TMP7]], ptr [[SECOND_SELECT]], align 4
+// CHECK-NEXT: [[TMP8:%.*]] = load i32, ptr [[SECOND_SELECT]], align 4
+// CHECK-NEXT: [[TOBOOL2:%.*]] = icmp ne i32 [[TMP8]], 0
+// CHECK-NEXT: [[TMP9:%.*]] = load i32, ptr [[D_ADDR]], align 4
+// CHECK-NEXT: [[TMP10:%.*]] = load i32, ptr [[E_ADDR]], align 4
+// CHECK-NEXT: [[TMP11:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL2]], i32 [[TMP9]], i32 [[TMP10]])
+// CHECK-NEXT: ret i32 [[TMP11]]
+//
+int test_chained_conditions(int cond1, int cond2, int cond3, int a, int b, int c, int d, int e) {
+ int first_select = __builtin_ct_select(cond1, a, b);
+ int second_select = __builtin_ct_select(cond2, first_select, c);
+ return __builtin_ct_select(second_select, d, e);
+}
+
+// Test using ct_select with pointer condition
+//int test_pointer_condition(int *ptr1, int *ptr2, int a, int b, int c) {
+ // NO-CHECK-LABEL: define {{.*}} @test_pointer_condition
+ // NO-CHECK: [[PTR_COND:%.*]] = icmp ne ptr %{{.*}}, null
+ // NO-CHECK: [[PTR_SELECT:%.*]] = call ptr @llvm.ct.select.p0(i1 [[PTR_COND]], ptr %{{.*}}, ptr %{{.*}})
+ // NO-CHECK: [[FINAL_COND:%.*]] = icmp ne ptr [[PTR_SELECT]], null
+ // NO-CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
+ // NO-CHECK: ret i32 [[RESULT]]
+// return __builtin_ct_select(__builtin_ct_select(ptr1, ptr1, ptr2), a, b);
+//}
+
+
+// Test using ct_select result in logical operations as condition
+// CHECK-LABEL: define i32 @test_logical_condition(
+// CHECK-SAME: i32 noundef [[COND1:%.*]], i32 noundef [[COND2:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]], i32 noundef [[C:%.*]], i32 noundef [[D:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*]]:
+// CHECK-NEXT: [[COND1_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COND2_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[D_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND1]], ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: store i32 [[COND2]], ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// CHECK-NEXT: store i32 [[D]], ptr [[D_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: [[TOBOOL1:%.*]] = icmp ne i32 [[TMP3]], 0
+// CHECK-NEXT: br i1 [[TOBOOL1]], label %[[LAND_RHS:.*]], label %[[LAND_END:.*]]
+// CHECK: [[LAND_RHS]]:
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL2:%.*]] = icmp ne i32 [[TMP4]], 0
+// CHECK-NEXT: br label %[[LAND_END]]
+// CHECK: [[LAND_END]]:
+// CHECK-NEXT: [[TMP5:%.*]] = phi i1 [ false, %[[ENTRY]] ], [ [[TOBOOL2]], %[[LAND_RHS]] ]
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[D_ADDR]], align 4
+// CHECK-NEXT: [[TMP8:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TMP5]], i32 [[TMP6]], i32 [[TMP7]])
+// CHECK-NEXT: ret i32 [[TMP8]]
+//
+int test_logical_condition(int cond1, int cond2, int a, int b, int c, int d) {
+ return __builtin_ct_select(__builtin_ct_select(cond1, a, b) && cond2, c, d);
+}
+
+// Test multiple levels of ct_select as conditions
+// CHECK-LABEL: define i32 @test_deep_condition_nesting(
+// CHECK-SAME: i32 noundef [[COND1:%.*]], i32 noundef [[COND2:%.*]], i32 noundef [[COND3:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]], i32 noundef [[C:%.*]], i32 noundef [[D:%.*]], i32 noundef [[E:%.*]], i32 noundef [[F:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND1_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COND2_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COND3_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[D_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[E_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[F_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND1]], ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: store i32 [[COND2]], ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: store i32 [[COND3]], ptr [[COND3_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// CHECK-NEXT: store i32 [[D]], ptr [[D_ADDR]], align 4
+// CHECK-NEXT: store i32 [[E]], ptr [[E_ADDR]], align 4
+// CHECK-NEXT: store i32 [[F]], ptr [[F_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL1:%.*]] = icmp ne i32 [[TMP1]], 0
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL1]], i32 [[TMP2]], i32 [[TMP3]])
+// CHECK-NEXT: [[TOBOOL2:%.*]] = icmp ne i32 [[TMP4]], 0
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[D_ADDR]], align 4
+// CHECK-NEXT: [[TMP7:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL2]], i32 [[TMP5]], i32 [[TMP6]])
+// CHECK-NEXT: [[TMP8:%.*]] = load i32, ptr [[E_ADDR]], align 4
+// CHECK-NEXT: [[TMP9:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP7]], i32 [[TMP8]])
+// CHECK-NEXT: [[TOBOOL3:%.*]] = icmp ne i32 [[TMP9]], 0
+// CHECK-NEXT: [[TMP10:%.*]] = load i32, ptr [[F_ADDR]], align 4
+// CHECK-NEXT: [[TMP11:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP12:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL3]], i32 [[TMP10]], i32 [[TMP11]])
+// CHECK-NEXT: ret i32 [[TMP12]]
+//
+int test_deep_condition_nesting(int cond1, int cond2, int cond3, int a, int b, int c, int d, int e, int f) {
+ return __builtin_ct_select(__builtin_ct_select(cond1, __builtin_ct_select(__builtin_ct_select(cond2, a, b), c, d), e), f, a);
+}
+
+// Test ct_select with complex condition expressions
+// CHECK-LABEL: define i32 @test_complex_condition_expr(
+// CHECK-SAME: i32 noundef [[X:%.*]], i32 noundef [[Y:%.*]], i32 noundef [[Z:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[Y_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[X]], ptr [[X_ADDR]], align 4
+// CHECK-NEXT: store i32 [[Y]], ptr [[Y_ADDR]], align 4
+// CHECK-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[Y_ADDR]], align 4
+// CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP0]], [[TMP1]]
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[Y_ADDR]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP]], i32 [[TMP2]], i32 [[TMP3]])
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[Z_ADDR]], align 4
+// CHECK-NEXT: [[CMP1:%.*]] = icmp slt i32 [[TMP4]], [[TMP5]]
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP8:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP1]], i32 [[TMP6]], i32 [[TMP7]])
+// CHECK-NEXT: ret i32 [[TMP8]]
+//
+int test_complex_condition_expr(int x, int y, int z, int a, int b) {
+ return __builtin_ct_select(__builtin_ct_select(x > y, x, y) < z, a, b);
+}
+
+// Test vector types - 128-bit vectors
+typedef int __attribute__((vector_size(16))) int4;
+typedef float __attribute__((vector_size(16))) float4;
+typedef short __attribute__((vector_size(16))) short8;
+typedef char __attribute__((vector_size(16))) char16;
+
+// CHECK-LABEL: define <4 x i32> @test_vector_int4(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <4 x i32> noundef [[A:%.*]], <4 x i32> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <4 x i32> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x i32> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <4 x i32>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <4 x i32>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[TOBOOL]], <4 x i32> [[TMP1]], <4 x i32> [[TMP2]])
+// CHECK-NEXT: ret <4 x i32> [[TMP3]]
+//
+int4 test_vector_int4(int cond, int4 a, int4 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define <4 x float> @test_vector_float4(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <4 x float> noundef [[A:%.*]], <4 x float> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <4 x float> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x float> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <4 x float>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <4 x float>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[TOBOOL]], <4 x float> [[TMP1]], <4 x float> [[TMP2]])
+// CHECK-NEXT: ret <4 x float> [[TMP3]]
+//
+float4 test_vector_float4(int cond, float4 a, float4 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define <8 x i16> @test_vector_short8(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <8 x i16> noundef [[A:%.*]], <8 x i16> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <8 x i16>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <8 x i16>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <8 x i16> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <8 x i16> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <8 x i16>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <8 x i16>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <8 x i16> @llvm.ct.select.v8i16(i1 [[TOBOOL]], <8 x i16> [[TMP1]], <8 x i16> [[TMP2]])
+// CHECK-NEXT: ret <8 x i16> [[TMP3]]
+//
+short8 test_vector_short8(int cond, short8 a, short8 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define <16 x i8> @test_vector_char16(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <16 x i8> noundef [[A:%.*]], <16 x i8> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <16 x i8>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <16 x i8>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <16 x i8> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <16 x i8> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <16 x i8>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <16 x i8>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <16 x i8> @llvm.ct.select.v16i8(i1 [[TOBOOL]], <16 x i8> [[TMP1]], <16 x i8> [[TMP2]])
+// CHECK-NEXT: ret <16 x i8> [[TMP3]]
+//
+char16 test_vector_char16(int cond, char16 a, char16 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test 256-bit vectors
+typedef int __attribute__((vector_size(32))) int8;
+typedef float __attribute__((vector_size(32))) float8;
+typedef double __attribute__((vector_size(32))) double4;
+
+// CHECK-LABEL: define void @test_vector_int8(
+// CHECK-SAME: ptr dead_on_unwind noalias writable sret(<8 x i32>) align 16 [[AGG_RESULT:%.*]], i32 noundef [[COND:%.*]], ptr noundef dead_on_return [[TMP0:%.*]], ptr noundef dead_on_return [[TMP1:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <8 x i32>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <8 x i32>, align 16
+// CHECK-NEXT: [[A:%.*]] = load <8 x i32>, ptr [[TMP0]], align 16
+// CHECK-NEXT: [[B:%.*]] = load <8 x i32>, ptr [[TMP1]], align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <8 x i32> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <8 x i32> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP2]], 0
+// CHECK-NEXT: [[TMP3:%.*]] = load <8 x i32>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = load <8 x i32>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP5:%.*]] = call <8 x i32> @llvm.ct.select.v8i32(i1 [[TOBOOL]], <8 x i32> [[TMP3]], <8 x i32> [[TMP4]])
+// CHECK-NEXT: store <8 x i32> [[TMP5]], ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: [[TMP6:%.*]] = load <8 x i32>, ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: store <8 x i32> [[TMP6]], ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: ret void
+//
+int8 test_vector_int8(int cond, int8 a, int8 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define void @test_vector_float8(
+// CHECK-SAME: ptr dead_on_unwind noalias writable sret(<8 x float>) align 16 [[AGG_RESULT:%.*]], i32 noundef [[COND:%.*]], ptr noundef dead_on_return [[TMP0:%.*]], ptr noundef dead_on_return [[TMP1:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <8 x float>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <8 x float>, align 16
+// CHECK-NEXT: [[A:%.*]] = load <8 x float>, ptr [[TMP0]], align 16
+// CHECK-NEXT: [[B:%.*]] = load <8 x float>, ptr [[TMP1]], align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <8 x float> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <8 x float> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP2]], 0
+// CHECK-NEXT: [[TMP3:%.*]] = load <8 x float>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = load <8 x float>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP5:%.*]] = call <8 x float> @llvm.ct.select.v8f32(i1 [[TOBOOL]], <8 x float> [[TMP3]], <8 x float> [[TMP4]])
+// CHECK-NEXT: store <8 x float> [[TMP5]], ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: [[TMP6:%.*]] = load <8 x float>, ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: store <8 x float> [[TMP6]], ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: ret void
+//
+float8 test_vector_float8(int cond, float8 a, float8 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define void @test_vector_double4(
+// CHECK-SAME: ptr dead_on_unwind noalias writable sret(<4 x double>) align 16 [[AGG_RESULT:%.*]], i32 noundef [[COND:%.*]], ptr noundef dead_on_return [[TMP0:%.*]], ptr noundef dead_on_return [[TMP1:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x double>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x double>, align 16
+// CHECK-NEXT: [[A:%.*]] = load <4 x double>, ptr [[TMP0]], align 16
+// CHECK-NEXT: [[B:%.*]] = load <4 x double>, ptr [[TMP1]], align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <4 x double> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x double> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP2]], 0
+// CHECK-NEXT: [[TMP3:%.*]] = load <4 x double>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = load <4 x double>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP5:%.*]] = call <4 x double> @llvm.ct.select.v4f64(i1 [[TOBOOL]], <4 x double> [[TMP3]], <4 x double> [[TMP4]])
+// CHECK-NEXT: store <4 x double> [[TMP5]], ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: [[TMP6:%.*]] = load <4 x double>, ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: store <4 x double> [[TMP6]], ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: ret void
+//
+double4 test_vector_double4(int cond, double4 a, double4 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test 512-bit vectors
+typedef int __attribute__((vector_size(64))) int16;
+typedef float __attribute__((vector_size(64))) float16;
+
+// CHECK-LABEL: define void @test_vector_int16(
+// CHECK-SAME: ptr dead_on_unwind noalias writable sret(<16 x i32>) align 16 [[AGG_RESULT:%.*]], i32 noundef [[COND:%.*]], ptr noundef dead_on_return [[TMP0:%.*]], ptr noundef dead_on_return [[TMP1:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <16 x i32>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <16 x i32>, align 16
+// CHECK-NEXT: [[A:%.*]] = load <16 x i32>, ptr [[TMP0]], align 16
+// CHECK-NEXT: [[B:%.*]] = load <16 x i32>, ptr [[TMP1]], align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <16 x i32> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <16 x i32> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP2]], 0
+// CHECK-NEXT: [[TMP3:%.*]] = load <16 x i32>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = load <16 x i32>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP5:%.*]] = call <16 x i32> @llvm.ct.select.v16i32(i1 [[TOBOOL]], <16 x i32> [[TMP3]], <16 x i32> [[TMP4]])
+// CHECK-NEXT: store <16 x i32> [[TMP5]], ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: [[TMP6:%.*]] = load <16 x i32>, ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: store <16 x i32> [[TMP6]], ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: ret void
+//
+int16 test_vector_int16(int cond, int16 a, int16 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define void @test_vector_float16(
+// CHECK-SAME: ptr dead_on_unwind noalias writable sret(<16 x float>) align 16 [[AGG_RESULT:%.*]], i32 noundef [[COND:%.*]], ptr noundef dead_on_return [[TMP0:%.*]], ptr noundef dead_on_return [[TMP1:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <16 x float>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <16 x float>, align 16
+// CHECK-NEXT: [[A:%.*]] = load <16 x float>, ptr [[TMP0]], align 16
+// CHECK-NEXT: [[B:%.*]] = load <16 x float>, ptr [[TMP1]], align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <16 x float> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <16 x float> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP2]], 0
+// CHECK-NEXT: [[TMP3:%.*]] = load <16 x float>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = load <16 x float>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP5:%.*]] = call <16 x float> @llvm.ct.select.v16f32(i1 [[TOBOOL]], <16 x float> [[TMP3]], <16 x float> [[TMP4]])
+// CHECK-NEXT: store <16 x float> [[TMP5]], ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: [[TMP6:%.*]] = load <16 x float>, ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: store <16 x float> [[TMP6]], ptr [[AGG_RESULT]], align 16
+// CHECK-NEXT: ret void
+//
+float16 test_vector_float16(int cond, float16 a, float16 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test vector operations with different condition types
+// CHECK-LABEL: define <4 x i32> @test_vector_char_cond(
+// CHECK-SAME: i8 noundef [[COND:%.*]], <4 x i32> noundef [[A:%.*]], <4 x i32> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i8, align 1
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: store i8 [[COND]], ptr [[COND_ADDR]], align 1
+// CHECK-NEXT: store <4 x i32> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x i32> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[COND_ADDR]], align 1
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i8 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <4 x i32>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <4 x i32>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[TOBOOL]], <4 x i32> [[TMP1]], <4 x i32> [[TMP2]])
+// CHECK-NEXT: ret <4 x i32> [[TMP3]]
+//
+int4 test_vector_char_cond(char cond, int4 a, int4 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define <4 x float> @test_vector_long_cond(
+// CHECK-SAME: i64 noundef [[COND:%.*]], <4 x float> noundef [[A:%.*]], <4 x float> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: store i64 [[COND]], ptr [[COND_ADDR]], align 8
+// CHECK-NEXT: store <4 x float> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x float> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i64, ptr [[COND_ADDR]], align 8
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i64 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <4 x float>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <4 x float>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[TOBOOL]], <4 x float> [[TMP1]], <4 x float> [[TMP2]])
+// CHECK-NEXT: ret <4 x float> [[TMP3]]
+//
+float4 test_vector_long_cond(long long cond, float4 a, float4 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test vector constants
+// CHECK-LABEL: define <4 x i32> @test_vector_constant_cond(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[A:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: [[B:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: store <4 x i32> <i32 1, i32 2, i32 3, i32 4>, ptr [[A]], align 16
+// CHECK-NEXT: store <4 x i32> <i32 5, i32 6, i32 7, i32 8>, ptr [[B]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load <4 x i32>, ptr [[A]], align 16
+// CHECK-NEXT: [[TMP1:%.*]] = load <4 x i32>, ptr [[B]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 true, <4 x i32> [[TMP0]], <4 x i32> [[TMP1]])
+// CHECK-NEXT: ret <4 x i32> [[TMP2]]
+//
+int4 test_vector_constant_cond(void) {
+ int4 a = {1, 2, 3, 4};
+ int4 b = {5, 6, 7, 8};
+ return __builtin_ct_select(1, a, b);
+}
+
+// CHECK-LABEL: define <4 x float> @test_vector_zero_cond(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[A:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[B:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: store <4 x float> <float 1.000000e+00, float 2.000000e+00, float 3.000000e+00, float 4.000000e+00>, ptr [[A]], align 16
+// CHECK-NEXT: store <4 x float> <float 5.000000e+00, float 6.000000e+00, float 7.000000e+00, float 8.000000e+00>, ptr [[B]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load <4 x float>, ptr [[A]], align 16
+// CHECK-NEXT: [[TMP1:%.*]] = load <4 x float>, ptr [[B]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 false, <4 x float> [[TMP0]], <4 x float> [[TMP1]])
+// CHECK-NEXT: ret <4 x float> [[TMP2]]
+//
+float4 test_vector_zero_cond(void) {
+ float4 a = {1.0f, 2.0f, 3.0f, 4.0f};
+ float4 b = {5.0f, 6.0f, 7.0f, 8.0f};
+ return __builtin_ct_select(0, a, b);
+}
+
+// Test nested vector selections
+// CHECK-LABEL: define <4 x i32> @test_vector_nested(
+// CHECK-SAME: i32 noundef [[COND1:%.*]], i32 noundef [[COND2:%.*]], <4 x i32> noundef [[A:%.*]], <4 x i32> noundef [[B:%.*]], <4 x i32> noundef [[C:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND1_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COND2_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: [[C_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: store i32 [[COND1]], ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: store i32 [[COND2]], ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: store <4 x i32> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x i32> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: store <4 x i32> [[C]], ptr [[C_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL1:%.*]] = icmp ne i32 [[TMP1]], 0
+// CHECK-NEXT: [[TMP2:%.*]] = load <4 x i32>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = load <4 x i32>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[TOBOOL1]], <4 x i32> [[TMP2]], <4 x i32> [[TMP3]])
+// CHECK-NEXT: [[TMP5:%.*]] = load <4 x i32>, ptr [[C_ADDR]], align 16
+// CHECK-NEXT: [[TMP6:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[TOBOOL]], <4 x i32> [[TMP4]], <4 x i32> [[TMP5]])
+// CHECK-NEXT: ret <4 x i32> [[TMP6]]
+//
+int4 test_vector_nested(int cond1, int cond2, int4 a, int4 b, int4 c) {
+ return __builtin_ct_select(cond1, __builtin_ct_select(cond2, a, b), c);
+}
+
+// Test vector selection with complex expressions
+// CHECK-LABEL: define <4 x float> @test_vector_complex_expr(
+// CHECK-SAME: i32 noundef [[X:%.*]], i32 noundef [[Y:%.*]], <4 x float> noundef [[A:%.*]], <4 x float> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[Y_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: store i32 [[X]], ptr [[X_ADDR]], align 4
+// CHECK-NEXT: store i32 [[Y]], ptr [[Y_ADDR]], align 4
+// CHECK-NEXT: store <4 x float> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x float> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[Y_ADDR]], align 4
+// CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP0]], [[TMP1]]
+// CHECK-NEXT: [[TMP2:%.*]] = load <4 x float>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = load <4 x float>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[CMP]], <4 x float> [[TMP2]], <4 x float> [[TMP3]])
+// CHECK-NEXT: ret <4 x float> [[TMP4]]
+//
+float4 test_vector_complex_expr(int x, int y, float4 a, float4 b) {
+ return __builtin_ct_select(x > y, a, b);
+}
+
+// Test vector with different element sizes
+typedef long long __attribute__((vector_size(16))) long2;
+typedef double __attribute__((vector_size(16))) double2;
+
+// CHECK-LABEL: define <2 x i64> @test_vector_long2(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <2 x i64> noundef [[A:%.*]], <2 x i64> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <2 x i64>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <2 x i64>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <2 x i64> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <2 x i64> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <2 x i64>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <2 x i64>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <2 x i64> @llvm.ct.select.v2i64(i1 [[TOBOOL]], <2 x i64> [[TMP1]], <2 x i64> [[TMP2]])
+// CHECK-NEXT: ret <2 x i64> [[TMP3]]
+//
+long2 test_vector_long2(int cond, long2 a, long2 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define <2 x double> @test_vector_double2(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <2 x double> noundef [[A:%.*]], <2 x double> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <2 x double>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <2 x double>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <2 x double> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <2 x double> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <2 x double>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <2 x double>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <2 x double> @llvm.ct.select.v2f64(i1 [[TOBOOL]], <2 x double> [[TMP1]], <2 x double> [[TMP2]])
+// CHECK-NEXT: ret <2 x double> [[TMP3]]
+//
+double2 test_vector_double2(int cond, double2 a, double2 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test mixed vector operations
+// CHECK-LABEL: define <4 x i32> @test_vector_from_scalar_condition(
+// CHECK-SAME: <4 x i32> noundef [[VEC_COND:%.*]], <4 x i32> noundef [[A:%.*]], <4 x i32> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[VEC_COND_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: [[SCALAR_COND:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store <4 x i32> [[VEC_COND]], ptr [[VEC_COND_ADDR]], align 16
+// CHECK-NEXT: store <4 x i32> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x i32> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load <4 x i32>, ptr [[VEC_COND_ADDR]], align 16
+// CHECK-NEXT: [[VECEXT:%.*]] = extractelement <4 x i32> [[TMP0]], i32 0
+// CHECK-NEXT: store i32 [[VECEXT]], ptr [[SCALAR_COND]], align 4
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[SCALAR_COND]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP1]], 0
+// CHECK-NEXT: [[TMP2:%.*]] = load <4 x i32>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = load <4 x i32>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[TOBOOL]], <4 x i32> [[TMP2]], <4 x i32> [[TMP3]])
+// CHECK-NEXT: ret <4 x i32> [[TMP4]]
+//
+int4 test_vector_from_scalar_condition(int4 vec_cond, int4 a, int4 b) {
+ // Extract first element and use as condition
+ int scalar_cond = vec_cond[0];
+ return __builtin_ct_select(scalar_cond, a, b);
+}
+
+// Test vector chaining
+// CHECK-LABEL: define <4 x float> @test_vector_chaining(
+// CHECK-SAME: i32 noundef [[COND1:%.*]], i32 noundef [[COND2:%.*]], i32 noundef [[COND3:%.*]], <4 x float> noundef [[A:%.*]], <4 x float> noundef [[B:%.*]], <4 x float> noundef [[C:%.*]], <4 x float> noundef [[D:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND1_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COND2_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COND3_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[C_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[D_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[FIRST:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[SECOND:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: store i32 [[COND1]], ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: store i32 [[COND2]], ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: store i32 [[COND3]], ptr [[COND3_ADDR]], align 4
+// CHECK-NEXT: store <4 x float> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x float> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: store <4 x float> [[C]], ptr [[C_ADDR]], align 16
+// CHECK-NEXT: store <4 x float> [[D]], ptr [[D_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND1_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <4 x float>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <4 x float>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[TOBOOL]], <4 x float> [[TMP1]], <4 x float> [[TMP2]])
+// CHECK-NEXT: store <4 x float> [[TMP3]], ptr [[FIRST]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[COND2_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL1:%.*]] = icmp ne i32 [[TMP4]], 0
+// CHECK-NEXT: [[TMP5:%.*]] = load <4 x float>, ptr [[FIRST]], align 16
+// CHECK-NEXT: [[TMP6:%.*]] = load <4 x float>, ptr [[C_ADDR]], align 16
+// CHECK-NEXT: [[TMP7:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[TOBOOL1]], <4 x float> [[TMP5]], <4 x float> [[TMP6]])
+// CHECK-NEXT: store <4 x float> [[TMP7]], ptr [[SECOND]], align 16
+// CHECK-NEXT: [[TMP8:%.*]] = load i32, ptr [[COND3_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL2:%.*]] = icmp ne i32 [[TMP8]], 0
+// CHECK-NEXT: [[TMP9:%.*]] = load <4 x float>, ptr [[SECOND]], align 16
+// CHECK-NEXT: [[TMP10:%.*]] = load <4 x float>, ptr [[D_ADDR]], align 16
+// CHECK-NEXT: [[TMP11:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[TOBOOL2]], <4 x float> [[TMP9]], <4 x float> [[TMP10]])
+// CHECK-NEXT: ret <4 x float> [[TMP11]]
+//
+float4 test_vector_chaining(int cond1, int cond2, int cond3, float4 a, float4 b, float4 c, float4 d) {
+ float4 first = __builtin_ct_select(cond1, a, b);
+ float4 second = __builtin_ct_select(cond2, first, c);
+ return __builtin_ct_select(cond3, second, d);
+}
+
+// Test special floating point values - NaN
+// CHECK-LABEL: define float @test_nan_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[NAN_VAL:%.*]] = alloca float, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store float +qnan, ptr [[NAN_VAL]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load float, ptr [[NAN_VAL]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = call float @llvm.ct.select.f32(i1 [[TOBOOL]], float [[TMP1]], float 1.000000e+00)
+// CHECK-NEXT: ret float [[TMP2]]
+//
+float test_nan_operands(int cond) {
+ float nan_val = __builtin_nanf("");
+ return __builtin_ct_select(cond, nan_val, 1.0f);
+}
+
+// CHECK-LABEL: define double @test_nan_double_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[NAN_VAL:%.*]] = alloca double, align 8
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store double +qnan, ptr [[NAN_VAL]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load double, ptr [[NAN_VAL]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = call double @llvm.ct.select.f64(i1 [[TOBOOL]], double [[TMP1]], double 2.000000e+00)
+// CHECK-NEXT: ret double [[TMP2]]
+//
+double test_nan_double_operands(int cond) {
+ double nan_val = __builtin_nan("");
+ return __builtin_ct_select(cond, nan_val, 2.0);
+}
+
+// Test infinity values
+// CHECK-LABEL: define float @test_infinity_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[POS_INF:%.*]] = alloca float, align 4
+// CHECK-NEXT: [[NEG_INF:%.*]] = alloca float, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store float +inf, ptr [[POS_INF]], align 4
+// CHECK-NEXT: store float -inf, ptr [[NEG_INF]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load float, ptr [[POS_INF]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load float, ptr [[NEG_INF]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call float @llvm.ct.select.f32(i1 [[TOBOOL]], float [[TMP1]], float [[TMP2]])
+// CHECK-NEXT: ret float [[TMP3]]
+//
+float test_infinity_operands(int cond) {
+ float pos_inf = __builtin_inff();
+ float neg_inf = -__builtin_inff();
+ return __builtin_ct_select(cond, pos_inf, neg_inf);
+}
+
+// CHECK-LABEL: define double @test_infinity_double_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[POS_INF:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[NEG_INF:%.*]] = alloca double, align 8
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store double +inf, ptr [[POS_INF]], align 8
+// CHECK-NEXT: store double -inf, ptr [[NEG_INF]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load double, ptr [[POS_INF]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load double, ptr [[NEG_INF]], align 8
+// CHECK-NEXT: [[TMP3:%.*]] = call double @llvm.ct.select.f64(i1 [[TOBOOL]], double [[TMP1]], double [[TMP2]])
+// CHECK-NEXT: ret double [[TMP3]]
+//
+double test_infinity_double_operands(int cond) {
+ double pos_inf = __builtin_inf();
+ double neg_inf = -__builtin_inf();
+ return __builtin_ct_select(cond, pos_inf, neg_inf);
+}
+
+// Test subnormal/denormal values
+// CHECK-LABEL: define float @test_subnormal_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[SUBNORMAL1:%.*]] = alloca float, align 4
+// CHECK-NEXT: [[SUBNORMAL2:%.*]] = alloca float, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store float 9.999940e-41, ptr [[SUBNORMAL1]], align 4
+// CHECK-NEXT: store float 1.401300e-45, ptr [[SUBNORMAL2]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load float, ptr [[SUBNORMAL1]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load float, ptr [[SUBNORMAL2]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call float @llvm.ct.select.f32(i1 [[TOBOOL]], float [[TMP1]], float [[TMP2]])
+// CHECK-NEXT: ret float [[TMP3]]
+//
+float test_subnormal_operands(int cond) {
+ // Very small subnormal values
+ float subnormal1 = 1e-40f;
+ float subnormal2 = 1e-45f;
+ return __builtin_ct_select(cond, subnormal1, subnormal2);
+}
+
+// Test integer overflow boundaries
+// CHECK-LABEL: define i32 @test_integer_overflow_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[MAX_INT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[MIN_INT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 2147483647, ptr [[MAX_INT]], align 4
+// CHECK-NEXT: store i32 -2147483648, ptr [[MIN_INT]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[MAX_INT]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[MIN_INT]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+int test_integer_overflow_operands(int cond) {
+ int max_int = __INT_MAX__;
+ int min_int = (-__INT_MAX__ - 1);
+ return __builtin_ct_select(cond, max_int, min_int);
+}
+
+// CHECK-LABEL: define i64 @test_longlong_overflow_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[MAX_LL:%.*]] = alloca i64, align 8
+// CHECK-NEXT: [[MIN_LL:%.*]] = alloca i64, align 8
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i64 9223372036854775807, ptr [[MAX_LL]], align 8
+// CHECK-NEXT: store i64 -9223372036854775808, ptr [[MIN_LL]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i64, ptr [[MAX_LL]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load i64, ptr [[MIN_LL]], align 8
+// CHECK-NEXT: [[TMP3:%.*]] = call i64 @llvm.ct.select.i64(i1 [[TOBOOL]], i64 [[TMP1]], i64 [[TMP2]])
+// CHECK-NEXT: ret i64 [[TMP3]]
+//
+long long test_longlong_overflow_operands(int cond) {
+ long long max_ll = __LONG_LONG_MAX__;
+ long long min_ll = (-__LONG_LONG_MAX__ - 1);
+ return __builtin_ct_select(cond, max_ll, min_ll);
+}
+
+// Test unsigned overflow boundaries
+// CHECK-LABEL: define i32 @test_unsigned_overflow_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[MAX_UINT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[MIN_UINT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 -1, ptr [[MAX_UINT]], align 4
+// CHECK-NEXT: store i32 0, ptr [[MIN_UINT]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[MAX_UINT]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[MIN_UINT]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+unsigned int test_unsigned_overflow_operands(int cond) {
+ unsigned int max_uint = 4294967295;
+ unsigned int min_uint = 0;
+ return __builtin_ct_select(cond, max_uint, min_uint);
+}
+
+// Test null pointer dereference avoidance
+// CHECK-LABEL: define ptr @test_null_pointer_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]], ptr noundef [[VALID_PTR:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[VALID_PTR_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[NULL_PTR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store ptr [[VALID_PTR]], ptr [[VALID_PTR_ADDR]], align 8
+// CHECK-NEXT: store ptr null, ptr [[NULL_PTR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[NULL_PTR]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load ptr, ptr [[VALID_PTR_ADDR]], align 8
+// CHECK-NEXT: [[TMP3:%.*]] = call ptr @llvm.ct.select.p0(i1 [[TOBOOL]], ptr [[TMP1]], ptr [[TMP2]])
+// CHECK-NEXT: ret ptr [[TMP3]]
+//
+int* test_null_pointer_operands(int cond, int* valid_ptr) {
+ int* null_ptr = (int*)0;
+ return __builtin_ct_select(cond, null_ptr, valid_ptr);
+}
+
+// Test volatile operations
+volatile int global_volatile = 42;
+// CHECK-LABEL: define i32 @test_volatile_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[VOL_VAL:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load volatile i32, ptr @global_volatile, align 4
+// CHECK-NEXT: store volatile i32 [[TMP0]], ptr [[VOL_VAL]], align 4
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP1]], 0
+// CHECK-NEXT: [[TMP2:%.*]] = load volatile i32, ptr [[VOL_VAL]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP2]], i32 100)
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+int test_volatile_operands(int cond) {
+ volatile int vol_val = global_volatile;
+ return __builtin_ct_select(cond, vol_val, 100);
+}
+
+// Test uninitialized variable behavior (should still work with ct_select)
+// CHECK-LABEL: define i32 @test_uninitialized_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i32 noundef [[INITIALIZED:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[INITIALIZED_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[UNINITIALIZED:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[INITIALIZED]], ptr [[INITIALIZED_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[UNINITIALIZED]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[INITIALIZED_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP1]], i32 [[TMP2]])
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+int test_uninitialized_operands(int cond, int initialized) {
+ int uninitialized; // Intentionally uninitialized
+ return __builtin_ct_select(cond, uninitialized, initialized);
+}
+
+// Test zero division avoidance patterns
+// CHECK-LABEL: define i32 @test_division_by_zero_avoidance(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i32 noundef [[DIVIDEND:%.*]], i32 noundef [[DIVISOR:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[DIVIDEND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[DIVISOR_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[SAFE_DIVISOR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[DIVIDEND]], ptr [[DIVIDEND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[DIVISOR]], ptr [[DIVISOR_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[DIVISOR_ADDR]], align 4
+// CHECK-NEXT: [[CMP:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[DIVISOR_ADDR]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP]], i32 [[TMP1]], i32 1)
+// CHECK-NEXT: store i32 [[TMP2]], ptr [[SAFE_DIVISOR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[DIVIDEND_ADDR]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[SAFE_DIVISOR]], align 4
+// CHECK-NEXT: [[DIV:%.*]] = sdiv i32 [[TMP3]], [[TMP4]]
+// CHECK-NEXT: ret i32 [[DIV]]
+//
+int test_division_by_zero_avoidance(int cond, int dividend, int divisor) {
+ // First get a safe divisor (never zero)
+ int safe_divisor = __builtin_ct_select(divisor != 0, divisor, 1);
+ // Then perform division with guaranteed non-zero divisor
+ return dividend / safe_divisor;
+}
+
+// Test array bounds checking patterns
+// CHECK-LABEL: define i32 @test_array_bounds_protection(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i32 noundef [[INDEX:%.*]], ptr noundef [[ARRAY:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*]]:
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[INDEX_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[ARRAY_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[SAFE_INDEX:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[INDEX]], ptr [[INDEX_ADDR]], align 4
+// CHECK-NEXT: store ptr [[ARRAY]], ptr [[ARRAY_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[INDEX_ADDR]], align 4
+// CHECK-NEXT: [[CMP:%.*]] = icmp sge i32 [[TMP0]], 0
+// CHECK-NEXT: br i1 [[CMP]], label %[[LAND_RHS:.*]], label %[[LAND_END:.*]]
+// CHECK: [[LAND_RHS]]:
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[INDEX_ADDR]], align 4
+// CHECK-NEXT: [[CMP1:%.*]] = icmp slt i32 [[TMP1]], 10
+// CHECK-NEXT: br label %[[LAND_END]]
+// CHECK: [[LAND_END]]:
+// CHECK-NEXT: [[TMP2:%.*]] = phi i1 [ false, %[[ENTRY]] ], [ [[CMP1]], %[[LAND_RHS]] ]
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[INDEX_ADDR]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TMP2]], i32 [[TMP3]], i32 0)
+// CHECK-NEXT: store i32 [[TMP4]], ptr [[SAFE_INDEX]], align 4
+// CHECK-NEXT: [[TMP5:%.*]] = load ptr, ptr [[ARRAY_ADDR]], align 8
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[SAFE_INDEX]], align 4
+// CHECK-NEXT: [[IDXPROM:%.*]] = sext i32 [[TMP6]] to i64
+// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds i32, ptr [[TMP5]], i64 [[IDXPROM]]
+// CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK-NEXT: ret i32 [[TMP7]]
+//
+int test_array_bounds_protection(int cond, int index, int* array) {
+ // Use ct_select to ensure safe array indexing
+ int safe_index = __builtin_ct_select(index >= 0 && index < 10, index, 0);
+ return array[safe_index];
+}
+
+// Test bit manipulation edge cases
+// CHECK-LABEL: define i32 @test_bit_manipulation_edge_cases(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i32 noundef [[VALUE:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[VALUE_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[LEFT_SHIFT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[RIGHT_SHIFT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[VALUE]], ptr [[VALUE_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[VALUE_ADDR]], align 4
+// CHECK-NEXT: [[SHL:%.*]] = shl i32 [[TMP0]], 31
+// CHECK-NEXT: store i32 [[SHL]], ptr [[LEFT_SHIFT]], align 4
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[VALUE_ADDR]], align 4
+// CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[TMP1]], 31
+// CHECK-NEXT: store i32 [[SHR]], ptr [[RIGHT_SHIFT]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP2]], 0
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[LEFT_SHIFT]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[RIGHT_SHIFT]], align 4
+// CHECK-NEXT: [[TMP5:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP3]], i32 [[TMP4]])
+// CHECK-NEXT: ret i32 [[TMP5]]
+//
+unsigned int test_bit_manipulation_edge_cases(int cond, unsigned int value) {
+ // Test extreme bit shifts that could cause undefined behavior
+ unsigned int left_shift = value << 31; // Could overflow
+ unsigned int right_shift = value >> 31; // Extract sign bit
+ return __builtin_ct_select(cond, left_shift, right_shift);
+}
+
+// Test signed integer wraparound
+// CHECK-LABEL: define i32 @test_signed_wraparound(
+// CHECK-SAME: i32 noundef [[COND:%.*]], i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[SUM:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[DIFF:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP0]], [[TMP1]]
+// CHECK-NEXT: store i32 [[ADD]], ptr [[SUM]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 [[TMP2]], [[TMP3]]
+// CHECK-NEXT: store i32 [[SUB]], ptr [[DIFF]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP4]], 0
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[SUM]], align 4
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[DIFF]], align 4
+// CHECK-NEXT: [[TMP7:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP5]], i32 [[TMP6]])
+// CHECK-NEXT: ret i32 [[TMP7]]
+//
+int test_signed_wraparound(int cond, int a, int b) {
+ int sum = a + b; // Could overflow
+ int diff = a - b; // Could underflow
+ return __builtin_ct_select(cond, sum, diff);
+}
+
+// Test vector NaN handling
+// CHECK-LABEL: define <4 x float> @test_vector_nan_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[NAN_VAL:%.*]] = alloca float, align 4
+// CHECK-NEXT: [[NAN_VEC:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[NORMAL_VEC:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store float +qnan, ptr [[NAN_VAL]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load float, ptr [[NAN_VAL]], align 4
+// CHECK-NEXT: [[VECINIT:%.*]] = insertelement <4 x float> poison, float [[TMP0]], i32 0
+// CHECK-NEXT: [[TMP1:%.*]] = load float, ptr [[NAN_VAL]], align 4
+// CHECK-NEXT: [[VECINIT1:%.*]] = insertelement <4 x float> [[VECINIT]], float [[TMP1]], i32 1
+// CHECK-NEXT: [[TMP2:%.*]] = load float, ptr [[NAN_VAL]], align 4
+// CHECK-NEXT: [[VECINIT2:%.*]] = insertelement <4 x float> [[VECINIT1]], float [[TMP2]], i32 2
+// CHECK-NEXT: [[TMP3:%.*]] = load float, ptr [[NAN_VAL]], align 4
+// CHECK-NEXT: [[VECINIT3:%.*]] = insertelement <4 x float> [[VECINIT2]], float [[TMP3]], i32 3
+// CHECK-NEXT: store <4 x float> [[VECINIT3]], ptr [[NAN_VEC]], align 16
+// CHECK-NEXT: store <4 x float> <float 1.000000e+00, float 2.000000e+00, float 3.000000e+00, float 4.000000e+00>, ptr [[NORMAL_VEC]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP4]], 0
+// CHECK-NEXT: [[TMP5:%.*]] = load <4 x float>, ptr [[NAN_VEC]], align 16
+// CHECK-NEXT: [[TMP6:%.*]] = load <4 x float>, ptr [[NORMAL_VEC]], align 16
+// CHECK-NEXT: [[TMP7:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[TOBOOL]], <4 x float> [[TMP5]], <4 x float> [[TMP6]])
+// CHECK-NEXT: ret <4 x float> [[TMP7]]
+//
+float4 test_vector_nan_operands(int cond) {
+ float nan_val = __builtin_nanf("");
+ float4 nan_vec = {nan_val, nan_val, nan_val, nan_val};
+ float4 normal_vec = {1.0f, 2.0f, 3.0f, 4.0f};
+ return __builtin_ct_select(cond, nan_vec, normal_vec);
+}
+
+// Test vector infinity handling
+// CHECK-LABEL: define <4 x float> @test_vector_infinity_operands(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[POS_INF:%.*]] = alloca float, align 4
+// CHECK-NEXT: [[NEG_INF:%.*]] = alloca float, align 4
+// CHECK-NEXT: [[INF_VEC:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[ZERO_VEC:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store float +inf, ptr [[POS_INF]], align 4
+// CHECK-NEXT: store float -inf, ptr [[NEG_INF]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load float, ptr [[POS_INF]], align 4
+// CHECK-NEXT: [[VECINIT:%.*]] = insertelement <4 x float> poison, float [[TMP0]], i32 0
+// CHECK-NEXT: [[TMP1:%.*]] = load float, ptr [[NEG_INF]], align 4
+// CHECK-NEXT: [[VECINIT1:%.*]] = insertelement <4 x float> [[VECINIT]], float [[TMP1]], i32 1
+// CHECK-NEXT: [[TMP2:%.*]] = load float, ptr [[POS_INF]], align 4
+// CHECK-NEXT: [[VECINIT2:%.*]] = insertelement <4 x float> [[VECINIT1]], float [[TMP2]], i32 2
+// CHECK-NEXT: [[TMP3:%.*]] = load float, ptr [[NEG_INF]], align 4
+// CHECK-NEXT: [[VECINIT3:%.*]] = insertelement <4 x float> [[VECINIT2]], float [[TMP3]], i32 3
+// CHECK-NEXT: store <4 x float> [[VECINIT3]], ptr [[INF_VEC]], align 16
+// CHECK-NEXT: store <4 x float> zeroinitializer, ptr [[ZERO_VEC]], align 16
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP4]], 0
+// CHECK-NEXT: [[TMP5:%.*]] = load <4 x float>, ptr [[INF_VEC]], align 16
+// CHECK-NEXT: [[TMP6:%.*]] = load <4 x float>, ptr [[ZERO_VEC]], align 16
+// CHECK-NEXT: [[TMP7:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[TOBOOL]], <4 x float> [[TMP5]], <4 x float> [[TMP6]])
+// CHECK-NEXT: ret <4 x float> [[TMP7]]
+//
+float4 test_vector_infinity_operands(int cond) {
+ float pos_inf = __builtin_inff();
+ float neg_inf = -__builtin_inff();
+ float4 inf_vec = {pos_inf, neg_inf, pos_inf, neg_inf};
+ float4 zero_vec = {0.0f, 0.0f, 0.0f, 0.0f};
+ return __builtin_ct_select(cond, inf_vec, zero_vec);
+}
+
+// Test mixed special values
+// CHECK-LABEL: define double @test_mixed_special_values(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[NAN_VAL:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[INF_VAL:%.*]] = alloca double, align 8
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store double +qnan, ptr [[NAN_VAL]], align 8
+// CHECK-NEXT: store double +inf, ptr [[INF_VAL]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load double, ptr [[NAN_VAL]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load double, ptr [[INF_VAL]], align 8
+// CHECK-NEXT: [[TMP3:%.*]] = call double @llvm.ct.select.f64(i1 [[TOBOOL]], double [[TMP1]], double [[TMP2]])
+// CHECK-NEXT: ret double [[TMP3]]
+//
+double test_mixed_special_values(int cond) {
+ double nan_val = __builtin_nan("");
+ double inf_val = __builtin_inf();
+ return __builtin_ct_select(cond, nan_val, inf_val);
+}
+
+// Test constant-time memory access pattern
+// CHECK-LABEL: define i32 @test_constant_time_memory_access(
+// CHECK-SAME: i32 noundef [[SECRET_INDEX:%.*]], ptr noundef [[DATA_ARRAY:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[SECRET_INDEX_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[DATA_ARRAY_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[RESULT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[I:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[IS_TARGET:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[CURRENT_VALUE:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[SELECTED_VALUE:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[SECRET_INDEX]], ptr [[SECRET_INDEX_ADDR]], align 4
+// CHECK-NEXT: store ptr [[DATA_ARRAY]], ptr [[DATA_ARRAY_ADDR]], align 8
+// CHECK-NEXT: store i32 0, ptr [[RESULT]], align 4
+// CHECK-NEXT: store i32 0, ptr [[I]], align 4
+// CHECK-NEXT: br label %[[FOR_COND:.*]]
+// CHECK: [[FOR_COND]]:
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP0]], 8
+// CHECK-NEXT: br i1 [[CMP]], label %[[FOR_BODY:.*]], label %[[FOR_END:.*]]
+// CHECK: [[FOR_BODY]]:
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[SECRET_INDEX_ADDR]], align 4
+// CHECK-NEXT: [[CMP1:%.*]] = icmp eq i32 [[TMP1]], [[TMP2]]
+// CHECK-NEXT: [[CONV:%.*]] = zext i1 [[CMP1]] to i32
+// CHECK-NEXT: store i32 [[CONV]], ptr [[IS_TARGET]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = load ptr, ptr [[DATA_ARRAY_ADDR]], align 8
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[IDXPROM:%.*]] = sext i32 [[TMP4]] to i64
+// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds i32, ptr [[TMP3]], i64 [[IDXPROM]]
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK-NEXT: store i32 [[TMP5]], ptr [[CURRENT_VALUE]], align 4
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[IS_TARGET]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP6]], 0
+// CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[CURRENT_VALUE]], align 4
+// CHECK-NEXT: [[TMP8:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL]], i32 [[TMP7]], i32 0)
+// CHECK-NEXT: store i32 [[TMP8]], ptr [[SELECTED_VALUE]], align 4
+// CHECK-NEXT: [[TMP9:%.*]] = load i32, ptr [[SELECTED_VALUE]], align 4
+// CHECK-NEXT: [[TMP10:%.*]] = load i32, ptr [[RESULT]], align 4
+// CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP10]], [[TMP9]]
+// CHECK-NEXT: store i32 [[ADD]], ptr [[RESULT]], align 4
+// CHECK-NEXT: br label %[[FOR_INC:.*]]
+// CHECK: [[FOR_INC]]:
+// CHECK-NEXT: [[TMP11:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[INC:%.*]] = add nsw i32 [[TMP11]], 1
+// CHECK-NEXT: store i32 [[INC]], ptr [[I]], align 4
+// CHECK-NEXT: br label %[[FOR_COND]], !llvm.loop [[LOOP1:![0-9]+]]
+// CHECK: [[FOR_END]]:
+// CHECK-NEXT: [[TMP12:%.*]] = load i32, ptr [[RESULT]], align 4
+// CHECK-NEXT: ret i32 [[TMP12]]
+//
+int test_constant_time_memory_access(int secret_index, int* data_array) {
+ // This pattern ensures constant-time memory access regardless of secret_index value
+ int result = 0;
+ // Use ct_select to accumulate values without revealing the secret index
+ for (int i = 0; i < 8; i++) {
+ int is_target = (i == secret_index);
+ int current_value = data_array[i];
+ int selected_value = __builtin_ct_select(is_target, current_value, 0);
+ result += selected_value;
+ }
+ return result;
+}
+
+// Test timing-attack resistant comparison
+// CHECK-LABEL: define i32 @test_timing_resistant_comparison(
+// CHECK-SAME: ptr noundef [[SECRET:%.*]], ptr noundef [[GUESS:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[SECRET_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[GUESS_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[MATCH:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[I:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[CHARS_EQUAL:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[BOTH_NULL:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[STILL_MATCHING:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store ptr [[SECRET]], ptr [[SECRET_ADDR]], align 8
+// CHECK-NEXT: store ptr [[GUESS]], ptr [[GUESS_ADDR]], align 8
+// CHECK-NEXT: store i32 1, ptr [[MATCH]], align 4
+// CHECK-NEXT: store i32 0, ptr [[I]], align 4
+// CHECK-NEXT: br label %[[FOR_COND:.*]]
+// CHECK: [[FOR_COND]]:
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP0]], 32
+// CHECK-NEXT: br i1 [[CMP]], label %[[FOR_BODY:.*]], label %[[FOR_END:.*]]
+// CHECK: [[FOR_BODY]]:
+// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[SECRET_ADDR]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[IDXPROM:%.*]] = sext i32 [[TMP2]] to i64
+// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds i8, ptr [[TMP1]], i64 [[IDXPROM]]
+// CHECK-NEXT: [[TMP3:%.*]] = load i8, ptr [[ARRAYIDX]], align 1
+// CHECK-NEXT: [[CONV:%.*]] = sext i8 [[TMP3]] to i32
+// CHECK-NEXT: [[TMP4:%.*]] = load ptr, ptr [[GUESS_ADDR]], align 8
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[IDXPROM1:%.*]] = sext i32 [[TMP5]] to i64
+// CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds i8, ptr [[TMP4]], i64 [[IDXPROM1]]
+// CHECK-NEXT: [[TMP6:%.*]] = load i8, ptr [[ARRAYIDX2]], align 1
+// CHECK-NEXT: [[CONV3:%.*]] = sext i8 [[TMP6]] to i32
+// CHECK-NEXT: [[CMP4:%.*]] = icmp eq i32 [[CONV]], [[CONV3]]
+// CHECK-NEXT: [[CONV5:%.*]] = zext i1 [[CMP4]] to i32
+// CHECK-NEXT: store i32 [[CONV5]], ptr [[CHARS_EQUAL]], align 4
+// CHECK-NEXT: [[TMP7:%.*]] = load ptr, ptr [[SECRET_ADDR]], align 8
+// CHECK-NEXT: [[TMP8:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[IDXPROM6:%.*]] = sext i32 [[TMP8]] to i64
+// CHECK-NEXT: [[ARRAYIDX7:%.*]] = getelementptr inbounds i8, ptr [[TMP7]], i64 [[IDXPROM6]]
+// CHECK-NEXT: [[TMP9:%.*]] = load i8, ptr [[ARRAYIDX7]], align 1
+// CHECK-NEXT: [[CONV8:%.*]] = sext i8 [[TMP9]] to i32
+// CHECK-NEXT: [[CMP9:%.*]] = icmp eq i32 [[CONV8]], 0
+// CHECK-NEXT: br i1 [[CMP9]], label %[[LAND_RHS:.*]], label %[[LAND_END:.*]]
+// CHECK: [[LAND_RHS]]:
+// CHECK-NEXT: [[TMP10:%.*]] = load ptr, ptr [[GUESS_ADDR]], align 8
+// CHECK-NEXT: [[TMP11:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[IDXPROM11:%.*]] = sext i32 [[TMP11]] to i64
+// CHECK-NEXT: [[ARRAYIDX12:%.*]] = getelementptr inbounds i8, ptr [[TMP10]], i64 [[IDXPROM11]]
+// CHECK-NEXT: [[TMP12:%.*]] = load i8, ptr [[ARRAYIDX12]], align 1
+// CHECK-NEXT: [[CONV13:%.*]] = sext i8 [[TMP12]] to i32
+// CHECK-NEXT: [[CMP14:%.*]] = icmp eq i32 [[CONV13]], 0
+// CHECK-NEXT: br label %[[LAND_END]]
+// CHECK: [[LAND_END]]:
+// CHECK-NEXT: [[TMP13:%.*]] = phi i1 [ false, %[[FOR_BODY]] ], [ [[CMP14]], %[[LAND_RHS]] ]
+// CHECK-NEXT: [[LAND_EXT:%.*]] = zext i1 [[TMP13]] to i32
+// CHECK-NEXT: store i32 [[LAND_EXT]], ptr [[BOTH_NULL]], align 4
+// CHECK-NEXT: [[TMP14:%.*]] = load i32, ptr [[CHARS_EQUAL]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP14]], 0
+// CHECK-NEXT: br i1 [[TOBOOL]], label %[[LOR_END:.*]], label %[[LOR_RHS:.*]]
+// CHECK: [[LOR_RHS]]:
+// CHECK-NEXT: [[TMP15:%.*]] = load i32, ptr [[BOTH_NULL]], align 4
+// CHECK-NEXT: [[TOBOOL16:%.*]] = icmp ne i32 [[TMP15]], 0
+// CHECK-NEXT: br label %[[LOR_END]]
+// CHECK: [[LOR_END]]:
+// CHECK-NEXT: [[TMP16:%.*]] = phi i1 [ true, %[[LAND_END]] ], [ [[TOBOOL16]], %[[LOR_RHS]] ]
+// CHECK-NEXT: [[TMP17:%.*]] = load i32, ptr [[MATCH]], align 4
+// CHECK-NEXT: [[TMP18:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TMP16]], i32 [[TMP17]], i32 0)
+// CHECK-NEXT: store i32 [[TMP18]], ptr [[STILL_MATCHING]], align 4
+// CHECK-NEXT: [[TMP19:%.*]] = load i32, ptr [[BOTH_NULL]], align 4
+// CHECK-NEXT: [[TOBOOL17:%.*]] = icmp ne i32 [[TMP19]], 0
+// CHECK-NEXT: [[TMP20:%.*]] = load i32, ptr [[MATCH]], align 4
+// CHECK-NEXT: [[TMP21:%.*]] = load i32, ptr [[STILL_MATCHING]], align 4
+// CHECK-NEXT: [[TMP22:%.*]] = call i32 @llvm.ct.select.i32(i1 [[TOBOOL17]], i32 [[TMP20]], i32 [[TMP21]])
+// CHECK-NEXT: store i32 [[TMP22]], ptr [[MATCH]], align 4
+// CHECK-NEXT: br label %[[FOR_INC:.*]]
+// CHECK: [[FOR_INC]]:
+// CHECK-NEXT: [[TMP23:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[INC:%.*]] = add nsw i32 [[TMP23]], 1
+// CHECK-NEXT: store i32 [[INC]], ptr [[I]], align 4
+// CHECK-NEXT: br label %[[FOR_COND]], !llvm.loop [[LOOP3:![0-9]+]]
+// CHECK: [[FOR_END]]:
+// CHECK-NEXT: [[TMP24:%.*]] = load i32, ptr [[MATCH]], align 4
+// CHECK-NEXT: ret i32 [[TMP24]]
+//
+int test_timing_resistant_comparison(const char* secret, const char* guess) {
+ // Constant-time string comparison using ct_select
+ int match = 1;
+ for (int i = 0; i < 32; i++) {
+ int chars_equal = (secret[i] == guess[i]);
+ int both_null = (secret[i] == 0) && (guess[i] == 0);
+ int still_matching = __builtin_ct_select(chars_equal || both_null, match, 0);
+ match = __builtin_ct_select(both_null, match, still_matching);
+ }
+ return match;
+}
+
+// Test ext_vector_type vectors -- should lower identically to vector_size.
+typedef int __attribute__((ext_vector_type(4))) int4_ext;
+typedef float __attribute__((ext_vector_type(4))) float4_ext;
+typedef char __attribute__((ext_vector_type(16))) char16_ext;
+
+// CHECK-LABEL: define <4 x i32> @test_ext_vector_int4(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <4 x i32> noundef [[A:%.*]], <4 x i32> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x i32>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <4 x i32> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x i32> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <4 x i32>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <4 x i32>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[TOBOOL]], <4 x i32> [[TMP1]], <4 x i32> [[TMP2]])
+// CHECK-NEXT: ret <4 x i32> [[TMP3]]
+//
+int4_ext test_ext_vector_int4(int cond, int4_ext a, int4_ext b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define <4 x float> @test_ext_vector_float4(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <4 x float> noundef [[A:%.*]], <4 x float> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <4 x float> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <4 x float> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <4 x float>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <4 x float>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[TOBOOL]], <4 x float> [[TMP1]], <4 x float> [[TMP2]])
+// CHECK-NEXT: ret <4 x float> [[TMP3]]
+//
+float4_ext test_ext_vector_float4(int cond, float4_ext a, float4_ext b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define <16 x i8> @test_ext_vector_char16(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <16 x i8> noundef [[A:%.*]], <16 x i8> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <16 x i8>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <16 x i8>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <16 x i8> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <16 x i8> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <16 x i8>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <16 x i8>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <16 x i8> @llvm.ct.select.v16i8(i1 [[TOBOOL]], <16 x i8> [[TMP1]], <16 x i8> [[TMP2]])
+// CHECK-NEXT: ret <16 x i8> [[TMP3]]
+//
+char16_ext test_ext_vector_char16(int cond, char16_ext a, char16_ext b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test half and bfloat scalar types.
+// CHECK-LABEL: define half @test_half(
+// CHECK-SAME: i32 noundef [[COND:%.*]], half noundef [[A:%.*]], half noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca half, align 2
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca half, align 2
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store half [[A]], ptr [[A_ADDR]], align 2
+// CHECK-NEXT: store half [[B]], ptr [[B_ADDR]], align 2
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load half, ptr [[A_ADDR]], align 2
+// CHECK-NEXT: [[TMP2:%.*]] = load half, ptr [[B_ADDR]], align 2
+// CHECK-NEXT: [[TMP3:%.*]] = call half @llvm.ct.select.f16(i1 [[TOBOOL]], half [[TMP1]], half [[TMP2]])
+// CHECK-NEXT: ret half [[TMP3]]
+//
+_Float16 test_half(int cond, _Float16 a, _Float16 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define bfloat @test_bfloat(
+// CHECK-SAME: i32 noundef [[COND:%.*]], bfloat noundef [[A:%.*]], bfloat noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca bfloat, align 2
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca bfloat, align 2
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store bfloat [[A]], ptr [[A_ADDR]], align 2
+// CHECK-NEXT: store bfloat [[B]], ptr [[B_ADDR]], align 2
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load bfloat, ptr [[A_ADDR]], align 2
+// CHECK-NEXT: [[TMP2:%.*]] = load bfloat, ptr [[B_ADDR]], align 2
+// CHECK-NEXT: [[TMP3:%.*]] = call bfloat @llvm.ct.select.bf16(i1 [[TOBOOL]], bfloat [[TMP1]], bfloat [[TMP2]])
+// CHECK-NEXT: ret bfloat [[TMP3]]
+//
+__bf16 test_bfloat(int cond, __bf16 a, __bf16 b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Test half and bfloat ext_vector_type vectors.
+typedef _Float16 __attribute__((ext_vector_type(8))) half8_ext;
+typedef __bf16 __attribute__((ext_vector_type(8))) bfloat8_ext;
+
+// CHECK-LABEL: define <8 x half> @test_ext_vector_half8(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <8 x half> noundef [[A:%.*]], <8 x half> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <8 x half>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <8 x half>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <8 x half> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <8 x half> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <8 x half>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <8 x half>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <8 x half> @llvm.ct.select.v8f16(i1 [[TOBOOL]], <8 x half> [[TMP1]], <8 x half> [[TMP2]])
+// CHECK-NEXT: ret <8 x half> [[TMP3]]
+//
+half8_ext test_ext_vector_half8(int cond, half8_ext a, half8_ext b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// CHECK-LABEL: define <8 x bfloat> @test_ext_vector_bfloat8(
+// CHECK-SAME: i32 noundef [[COND:%.*]], <8 x bfloat> noundef [[A:%.*]], <8 x bfloat> noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca <8 x bfloat>, align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca <8 x bfloat>, align 16
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: store <8 x bfloat> [[A]], ptr [[A_ADDR]], align 16
+// CHECK-NEXT: store <8 x bfloat> [[B]], ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = load <8 x bfloat>, ptr [[A_ADDR]], align 16
+// CHECK-NEXT: [[TMP2:%.*]] = load <8 x bfloat>, ptr [[B_ADDR]], align 16
+// CHECK-NEXT: [[TMP3:%.*]] = call <8 x bfloat> @llvm.ct.select.v8bf16(i1 [[TOBOOL]], <8 x bfloat> [[TMP1]], <8 x bfloat> [[TMP2]])
+// CHECK-NEXT: ret <8 x bfloat> [[TMP3]]
+//
+bfloat8_ext test_ext_vector_bfloat8(int cond, bfloat8_ext a, bfloat8_ext b) {
+ return __builtin_ct_select(cond, a, b);
+}
+
+// Array arguments decay to pointers before the operand type check.
+// CHECK-LABEL: define i32 @test_array_decay(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[ARR1:%.*]] = alloca [4 x i32], align 4
+// CHECK-NEXT: [[ARR2:%.*]] = alloca [4 x i32], align 4
+// CHECK-NEXT: [[R:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [4 x i32], ptr [[ARR1]], i64 0, i64 0
+// CHECK-NEXT: [[ARRAYDECAY1:%.*]] = getelementptr inbounds [4 x i32], ptr [[ARR2]], i64 0, i64 0
+// CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.ct.select.p0(i1 [[TOBOOL]], ptr [[ARRAYDECAY]], ptr [[ARRAYDECAY1]])
+// CHECK-NEXT: store ptr [[TMP1]], ptr [[R]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load ptr, ptr [[R]], align 8
+// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds i32, ptr [[TMP2]], i64 0
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK-NEXT: ret i32 [[TMP3]]
+//
+int test_array_decay(int cond) {
+ int arr1[4], arr2[4];
+ int *r = __builtin_ct_select(cond, arr1, arr2);
+ return r[0];
+}
+
+// Function arguments decay to function pointers before the operand type check.
+typedef void (*fnptr)(void);
+void fn_a(void);
+void fn_b(void);
+// CHECK-LABEL: define ptr @test_func_decay(
+// CHECK-SAME: i32 noundef [[COND:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[COND_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[COND]], ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[COND_ADDR]], align 4
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.ct.select.p0(i1 [[TOBOOL]], ptr @fn_a, ptr @fn_b)
+// CHECK-NEXT: ret ptr [[TMP1]]
+//
+fnptr test_func_decay(int cond) {
+ return __builtin_ct_select(cond, fn_a, fn_b);
+}
+//.
+// CHECK: [[LOOP1]] = distinct !{[[LOOP1]], [[META2:![0-9]+]]}
+// CHECK: [[META2]] = !{!"llvm.loop.mustprogress"}
+// CHECK: [[LOOP3]] = distinct !{[[LOOP3]], [[META2]]}
+//.
diff --git a/clang/test/Sema/builtin-ct-select.c b/clang/test/Sema/builtin-ct-select.c
index 7f2d9291299d6..1f2d61eaff78d 100644
--- a/clang/test/Sema/builtin-ct-select.c
+++ b/clang/test/Sema/builtin-ct-select.c
@@ -1,683 +1,37 @@
-// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -fsyntax-only -verify %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 long test_long(int cond, long long a, long 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(int cond, double a, double b) {
- // CHECK-LABEL: define {{.*}} @test_double
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call double @llvm.ct.select.f64(i1 [[COND]], double %{{.*}}, double %{{.*}})
- // CHECK: ret double [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-// Test pointer types
-int *test_pointer(int cond, int *a, int *b) {
- // CHECK-LABEL: define {{.*}} @test_pointer
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call ptr @llvm.ct.select.p0(i1 [[COND]], ptr %{{.*}}, ptr %{{.*}})
- // CHECK: ret ptr [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-// Test with different condition types
-int test_char_cond(char cond, int a, int b) {
- // CHECK-LABEL: define {{.*}} @test_char_cond
- // CHECK: [[COND:%.*]] = icmp ne i8 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-int test_long_cond(long long cond, int a, int b) {
- // CHECK-LABEL: define {{.*}} @test_long_cond
- // CHECK: [[COND:%.*]] = icmp ne i64 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-// Test with boolean condition
-int test_bool_cond(_Bool cond, int a, int b) {
- // CHECK-LABEL: define {{.*}} @test_bool_cond
- // CHECK: [[COND:%.*]] = icmp ne i8 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-// Test with constants
-int test_constant_cond(void) {
- // CHECK-LABEL: define {{.*}} @test_constant_cond
- // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 true, i32 42, i32 24)
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(1, 42, 24);
-}
-
-int test_zero_cond(void) {
- // CHECK-LABEL: define {{.*}} @test_zero_cond
- // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 false, i32 42, i32 24)
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(0, 42, 24);
-}
-
-// Test type promotion
-int test_promotion(int cond, short a, short b) {
- // CHECK-LABEL: define {{.*}} @test_promotion
- // CHECK-DAG: [[A_EXT:%.*]] = sext i16 %{{.*}} to i32
- // CHECK-DAG: [[B_EXT:%.*]] = sext i16 %{{.*}} to i32
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 [[A_EXT]], i32 [[B_EXT]])
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(cond, (int)a, (int)b);
-}
-
-// Test mixed signedness
-unsigned int test_mixed_signedness(int cond, int a, unsigned int b) {
- // CHECK-LABEL: define {{.*}} @test_mixed_signedness
- // CHECK-DAG: [[A_EXT:%.*]] = sext i32 %{{.*}} to i64
- // CHECK-DAG: [[B_EXT:%.*]] = zext i32 %{{.*}} to i64
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call i64 @llvm.ct.select.i64(i1 [[COND]], i64 [[A_EXT]], i64 [[B_EXT]])
- // CHECK: [[RESULT_TRUNC:%.*]] = trunc i64 [[RESULT]] to i32
- // CHECK: ret i32 [[RESULT_TRUNC]]
- return __builtin_ct_select(cond, (long long)a, (long long)b);
-}
-
-// Test complex expression
-int test_complex_expr_alt(int x, int y) {
- // CHECK-LABEL: define {{.*}} @test_complex_expr_alt
- // CHECK-DAG: [[CMP:%.*]] = icmp sgt i32 %{{.*}}, 0
- // CHECK-DAG: [[ADD:%.*]] = add nsw i32 %{{.*}}, %{{.*}}
- // CHECK-DAG: [[SUB:%.*]] = sub nsw i32 %{{.*}}, %{{.*}}
- // Separate the final sequence to ensure proper ordering
- // CHECK-NEXT: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP]], i32 [[ADD]], i32 [[SUB]])
- // CHECK-NEXT: ret i32 [[RESULT]]
- return __builtin_ct_select(x > 0, x + y, x - y);
-}
-
-// Test nested calls
-int test_nested_structured(int cond1, int cond2, int a, int b, int c) {
- // CHECK-LABEL: define {{.*}} @test_nested_structured
- // Phase 1: Conditions (order doesn't matter)
- // CHECK-DAG: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
-
- // Phase 2: Inner select (must happen before outer)
- // CHECK: [[INNER:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND2]], i32 %{{.*}}, i32 %{{.*}})
-
- // Phase 3: Outer select (must use inner result)
- // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND1]], i32 [[INNER]], i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(cond1, __builtin_ct_select(cond2, a, b), c);
-}
-
-// Test with function calls
-int helper(int x) { return x * 2; }
-int test_function_calls(int cond, int x, int y) {
- // CHECK-LABEL: define {{.*}} @test_function_calls
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[CALL1:%.*]] = call i32 @helper(i32 noundef %{{.*}})
- // CHECK-DAG: [[CALL2:%.*]] = call i32 @helper(i32 noundef %{{.*}})
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 [[CALL1]], i32 [[CALL2]])
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(cond, helper(x), helper(y));
-}
-
-// Test using ct_select as condition for another ct_select
-int test_intrinsic_condition(int cond1, int cond2, int a, int b, int c, int d) {
- // CHECK-LABEL: define {{.*}} @test_intrinsic_condition
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[INNER_COND:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK-DAG: [[FINAL_COND:%.*]] = icmp ne i32 [[INNER_COND]], 0
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(__builtin_ct_select(cond1, cond2, a), b, c);
-}
-
-// Test using comparison result of ct_select as condition
-int test_comparison_condition(int cond, int a, int b, int c, int d) {
- // CHECK-LABEL: define {{.*}} @test_comparison_condition
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[FIRST_SELECT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: [[CMP:%.*]] = icmp sgt i32 [[FIRST_SELECT]], %{{.*}}
- // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(__builtin_ct_select(cond, a, b) > c, d, a);
-}
-
-// Test using ct_select result in arithmetic as condition
-int test_arithmetic_condition(int cond, int a, int b, int c, int d) {
- // CHECK-LABEL: define {{.*}} @test_arithmetic_condition
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[FIRST_SELECT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: [[ADD:%.*]] = add nsw i32 [[FIRST_SELECT]], %{{.*}}
- // CHECK: [[FINAL_COND:%.*]] = icmp ne i32 [[ADD]], 0
- // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(__builtin_ct_select(cond, a, b) + c, d, a);
-}
-
-// Test chained ct_select as conditions
-int test_chained_conditions(int cond1, int cond2, int cond3, int a, int b, int c, int d, int e) {
- // CHECK-LABEL: define {{.*}} @test_chained_conditions
- // CHECK: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[FIRST:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND1]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[SECOND:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND2]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK-DAG: [[FINAL_COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- int first_select = __builtin_ct_select(cond1, a, b);
- int second_select = __builtin_ct_select(cond2, first_select, c);
- return __builtin_ct_select(second_select, d, e);
-}
-
-// Test using ct_select with pointer condition
-//int test_pointer_condition(int *ptr1, int *ptr2, int a, int b, int c) {
- // NO-CHECK-LABEL: define {{.*}} @test_pointer_condition
- // NO-CHECK: [[PTR_COND:%.*]] = icmp ne ptr %{{.*}}, null
- // NO-CHECK: [[PTR_SELECT:%.*]] = call ptr @llvm.ct.select.p0(i1 [[PTR_COND]], ptr %{{.*}}, ptr %{{.*}})
- // NO-CHECK: [[FINAL_COND:%.*]] = icmp ne ptr [[PTR_SELECT]], null
- // NO-CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
- // NO-CHECK: ret i32 [[RESULT]]
-// return __builtin_ct_select(__builtin_ct_select(ptr1, ptr1, ptr2), a, b);
-//}
-
-
-// Test using ct_select result in logical operations as condition
-int test_logical_condition(int cond1, int cond2, int a, int b, int c, int d) {
- // CHECK-LABEL: define {{.*}} @test_logical_condition
- // CHECK-DAG: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[FIRST_SELECT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND1]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK-DAG: [[SELECT_BOOL:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 %{{.*}}, i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(__builtin_ct_select(cond1, a, b) && cond2, c, d);
-}
-
-// Test multiple levels of ct_select as conditions
-int test_deep_condition_nesting(int cond1, int cond2, int cond3, int a, int b, int c, int d, int e, int f) {
- // CHECK-LABEL: define {{.*}} @test_deep_condition_nesting
- // CHECK-DAG: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[INNER1:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND2]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK-DAG: [[INNER1_COND:%.*]] = icmp ne i32 [[INNER1]], 0
- // CHECK-DAG: [[INNER2:%.*]] = call i32 @llvm.ct.select.i32(i1 [[INNER1_COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK-DAG: [[OUTER:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND1]], i32 [[INNER2]], i32 %{{.*}})
- // CHECK-DAG: [[FINAL_COND:%.*]] = icmp ne i32 [[OUTER]], 0
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[FINAL_COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(__builtin_ct_select(cond1, __builtin_ct_select(__builtin_ct_select(cond2, a, b), c, d), e), f, a);
-}
-
-// Test ct_select with complex condition expressions
-int test_complex_condition_expr(int x, int y, int z, int a, int b) {
- // CHECK-LABEL: define {{.*}} @test_complex_condition_expr
- // CHECK: [[CMP1:%.*]] = icmp sgt i32 %{{.*}}, %{{.*}}
- // CHECK: [[SELECT1:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP1]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: [[CMP2:%.*]] = icmp slt i32 [[SELECT1]], %{{.*}}
- // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[CMP2]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- return __builtin_ct_select(__builtin_ct_select(x > y, x, y) < z, a, b);
-}
-
-// Test vector types - 128-bit vectors
-typedef int __attribute__((vector_size(16))) int4;
-typedef float __attribute__((vector_size(16))) float4;
-typedef short __attribute__((vector_size(16))) short8;
-typedef char __attribute__((vector_size(16))) char16;
-
-int4 test_vector_int4(int cond, int4 a, int4 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_int4
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[COND]], <4 x i32> %{{.*}}, <4 x i32> %{{.*}})
- // CHECK: ret <4 x i32> [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-float4 test_vector_float4(int cond, float4 a, float4 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_float4
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
- // CHECK: ret <4 x float> [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-short8 test_vector_short8(int cond, short8 a, short8 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_short8
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <8 x i16> @llvm.ct.select.v8i16(i1 [[COND]], <8 x i16> %{{.*}}, <8 x i16> %{{.*}})
- // CHECK: ret <8 x i16> [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-char16 test_vector_char16(int cond, char16 a, char16 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_char16
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <16 x i8> @llvm.ct.select.v16i8(i1 [[COND]], <16 x i8> %{{.*}}, <16 x i8> %{{.*}})
- // CHECK: ret <16 x i8> [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
+// Diagnostic coverage for the __builtin_ct_select Sema checks. Codegen
+// behavior is tested separately in clang/test/CodeGen/builtin-ct-select.c.
-// Test 256-bit vectors
-typedef int __attribute__((vector_size(32))) int8;
-typedef float __attribute__((vector_size(32))) float8;
-typedef double __attribute__((vector_size(32))) double4;
-
-int8 test_vector_int8(int cond, int8 a, int8 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_int8
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call <8 x i32> @llvm.ct.select.v8i32(i1 [[COND]], <8 x i32> %{{.*}}, <8 x i32> %{{.*}})
- return __builtin_ct_select(cond, a, b);
-}
-
-float8 test_vector_float8(int cond, float8 a, float8 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_float8
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call <8 x float> @llvm.ct.select.v8f32(i1 [[COND]], <8 x float> %{{.*}}, <8 x float> %{{.*}})
- return __builtin_ct_select(cond, a, b);
-}
-
-double4 test_vector_double4(int cond, double4 a, double4 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_double4
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call <4 x double> @llvm.ct.select.v4f64(i1 [[COND]], <4 x double> %{{.*}}, <4 x double> %{{.*}})
- return __builtin_ct_select(cond, a, b);
-}
-
-// Test 512-bit vectors
-typedef int __attribute__((vector_size(64))) int16;
-typedef float __attribute__((vector_size(64))) float16;
-
-int16 test_vector_int16(int cond, int16 a, int16 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_int16
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <16 x i32> @llvm.ct.select.v16i32(i1 [[COND]], <16 x i32> %{{.*}}, <16 x i32> %{{.*}})
- return __builtin_ct_select(cond, a, b);
-}
-
-float16 test_vector_float16(int cond, float16 a, float16 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_float16
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <16 x float> @llvm.ct.select.v16f32(i1 [[COND]], <16 x float> %{{.*}}, <16 x float> %{{.*}})
- return __builtin_ct_select(cond, a, b);
-}
-
-// Test vector operations with different condition types
-int4 test_vector_char_cond(char cond, int4 a, int4 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_char_cond
- // CHECK: [[COND:%.*]] = icmp ne i8 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[COND]], <4 x i32> %{{.*}}, <4 x i32> %{{.*}})
- // CHECK: ret <4 x i32> [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-float4 test_vector_long_cond(long long cond, float4 a, float4 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_long_cond
- // CHECK: [[COND:%.*]] = icmp ne i64 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
- // CHECK: ret <4 x float> [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-// Test vector constants
-int4 test_vector_constant_cond(void) {
- // CHECK-LABEL: define {{.*}} @test_vector_constant_cond
- // CHECK: [[RESULT:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 true, <4 x i32> %{{.*}}, <4 x i32> %{{.*}})
- // CHECK: ret <4 x i32> [[RESULT]]
- int4 a = {1, 2, 3, 4};
- int4 b = {5, 6, 7, 8};
- return __builtin_ct_select(1, a, b);
-}
-
-float4 test_vector_zero_cond(void) {
- // CHECK-LABEL: define {{.*}} @test_vector_zero_cond
- // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 false, <4 x float> %{{.*}}, <4 x float> %{{.*}})
- // CHECK: ret <4 x float> [[RESULT]]
- float4 a = {1.0f, 2.0f, 3.0f, 4.0f};
- float4 b = {5.0f, 6.0f, 7.0f, 8.0f};
- return __builtin_ct_select(0, a, b);
-}
-
-// Test nested vector selections
-int4 test_vector_nested(int cond1, int cond2, int4 a, int4 b, int4 c) {
- // CHECK-LABEL: define {{.*}} @test_vector_nested
- // CHECK-DAG: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[INNER:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[COND2]], <4 x i32> %{{.*}}, <4 x i32> %{{.*}})
- // CHECK: [[RESULT:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[COND1]], <4 x i32> [[INNER]], <4 x i32> %{{.*}})
- // CHECK: ret <4 x i32> [[RESULT]]
- return __builtin_ct_select(cond1, __builtin_ct_select(cond2, a, b), c);
-}
-
-// Test vector selection with complex expressions
-float4 test_vector_complex_expr(int x, int y, float4 a, float4 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_complex_expr
- // CHECK: [[CMP:%.*]] = icmp sgt i32 %{{.*}}, %{{.*}}
- // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[CMP]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
- // CHECK: ret <4 x float> [[RESULT]]
- return __builtin_ct_select(x > y, a, b);
-}
-
-// Test vector with different element sizes
-typedef long long __attribute__((vector_size(16))) long2;
-typedef double __attribute__((vector_size(16))) double2;
-
-long2 test_vector_long2(int cond, long2 a, long2 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_long2
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <2 x i64> @llvm.ct.select.v2i64(i1 [[COND]], <2 x i64> %{{.*}}, <2 x i64> %{{.*}})
- // CHECK: ret <2 x i64> [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-double2 test_vector_double2(int cond, double2 a, double2 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_double2
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <2 x double> @llvm.ct.select.v2f64(i1 [[COND]], <2 x double> %{{.*}}, <2 x double> %{{.*}})
- // CHECK: ret <2 x double> [[RESULT]]
- return __builtin_ct_select(cond, a, b);
-}
-
-// Test mixed vector operations
-int4 test_vector_from_scalar_condition(int4 vec_cond, int4 a, int4 b) {
- // CHECK-LABEL: define {{.*}} @test_vector_from_scalar_condition
- // Extract first element and use as condition
- int scalar_cond = vec_cond[0];
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <4 x i32> @llvm.ct.select.v4i32(i1 [[COND]], <4 x i32> %{{.*}}, <4 x i32> %{{.*}})
- // CHECK: ret <4 x i32> [[RESULT]]
- return __builtin_ct_select(scalar_cond, a, b);
-}
-
-// Test vector chaining
-float4 test_vector_chaining(int cond1, int cond2, int cond3, float4 a, float4 b, float4 c, float4 d) {
- // CHECK-LABEL: define {{.*}} @test_vector_chaining
- // CHECK-DAG: [[COND1:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[COND2:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[COND3:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[FIRST:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND1]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
- // CHECK-DAG: [[SECOND:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND2]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
- // CHECK-DAG: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND3]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
- // CHECK: ret <4 x float> [[RESULT]]
- float4 first = __builtin_ct_select(cond1, a, b);
- float4 second = __builtin_ct_select(cond2, first, c);
- return __builtin_ct_select(cond3, second, d);
-}
-
-// Test special floating point values - NaN
-float test_nan_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_nan_operands
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call float @llvm.ct.select.f32(i1 [[COND]], float %{{.*}}, float 1.000000e+00)
- // CHECK: ret float [[RESULT]]
- float nan_val = __builtin_nanf("");
- return __builtin_ct_select(cond, nan_val, 1.0f);
-}
-
-double test_nan_double_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_nan_double_operands
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call double @llvm.ct.select.f64(i1 [[COND]], double %{{.*}}, double 2.000000e+00)
- // CHECK: ret double [[RESULT]]
- double nan_val = __builtin_nan("");
- return __builtin_ct_select(cond, nan_val, 2.0);
-}
-
-// Test infinity values
-float test_infinity_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_infinity_operands
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call float @llvm.ct.select.f32(i1 [[COND]], float %{{.*}}, float %{{.*}})
- // CHECK: ret float [[RESULT]]
- float pos_inf = __builtin_inff();
- float neg_inf = -__builtin_inff();
- return __builtin_ct_select(cond, pos_inf, neg_inf);
-}
-
-double test_infinity_double_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_infinity_double_operands
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call double @llvm.ct.select.f64(i1 [[COND]], double %{{.*}}, double %{{.*}})
- // CHECK: ret double [[RESULT]]
- double pos_inf = __builtin_inf();
- double neg_inf = -__builtin_inf();
- return __builtin_ct_select(cond, pos_inf, neg_inf);
-}
-
-// Test subnormal/denormal values
-float test_subnormal_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_subnormal_operands
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call float @llvm.ct.select.f32(i1 [[COND]], float %{{.*}}, float %{{.*}})
- // CHECK: ret float [[RESULT]]
- // Very small subnormal values
- float subnormal1 = 1e-40f;
- float subnormal2 = 1e-45f;
- return __builtin_ct_select(cond, subnormal1, subnormal2);
-}
-
-// Test integer overflow boundaries
-int test_integer_overflow_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_integer_overflow_operands
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- int max_int = __INT_MAX__;
- int min_int = (-__INT_MAX__ - 1);
- return __builtin_ct_select(cond, max_int, min_int);
-}
-
-long long test_longlong_overflow_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_longlong_overflow_operands
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call i64 @llvm.ct.select.i64(i1 [[COND]], i64 %{{.*}}, i64 %{{.*}})
- // CHECK: ret i64 [[RESULT]]
- long long max_ll = __LONG_LONG_MAX__;
- long long min_ll = (-__LONG_LONG_MAX__ - 1);
- return __builtin_ct_select(cond, max_ll, min_ll);
-}
-
-// Test unsigned overflow boundaries
-unsigned int test_unsigned_overflow_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_unsigned_overflow_operands
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- unsigned int max_uint = 4294967295;
- unsigned int min_uint = 0;
- return __builtin_ct_select(cond, max_uint, min_uint);
-}
-
-// Test null pointer dereference avoidance
-int* test_null_pointer_operands(int cond, int* valid_ptr) {
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call ptr @llvm.ct.select.p0(i1 [[COND]], ptr %{{.*}}, ptr %{{.*}})
- // CHECK: ret ptr [[RESULT]]
- int* null_ptr = (int*)0;
- return __builtin_ct_select(cond, null_ptr, valid_ptr);
-}
-
-// Test volatile operations
-volatile int global_volatile = 42;
-int test_volatile_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_volatile_operands
- // CHECK-DAG: [[VOLATILE_LOAD:%.*]] = load volatile i32, ptr {{.*}}
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 100)
- // CHECK: ret i32 [[RESULT]]
- volatile int vol_val = global_volatile;
- return __builtin_ct_select(cond, vol_val, 100);
-}
-
-// Test uninitialized variable behavior (should still work with ct_select)
-int test_uninitialized_operands(int cond, int initialized) {
- // CHECK-LABEL: define {{.*}} @test_uninitialized_operands
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- int uninitialized; // Intentionally uninitialized
- return __builtin_ct_select(cond, uninitialized, initialized);
-}
-
-// Test zero division avoidance patterns
-int test_division_by_zero_avoidance(int cond, int dividend, int divisor) {
- // CHECK-LABEL: define {{.*}} @test_division_by_zero_avoidance
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[DIV_RESULT:%.*]] = sdiv i32 %{{.*}}, %{{.*}}
- // CHECK-DAG: [[SAFE_DIVISOR:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 1)
- // First get a safe divisor (never zero)
- int safe_divisor = __builtin_ct_select(divisor != 0, divisor, 1);
- // Then perform division with guaranteed non-zero divisor
- return dividend / safe_divisor;
-}
-
-// Test array bounds checking patterns
-int test_array_bounds_protection(int cond, int index, int* array) {
- // CHECK-LABEL: define {{.*}} @test_array_bounds_protection
- // CHECK-DAG: [[SAFE_INDEX:%.*]] = call i32 @llvm.ct.select.i32(i1 {{.*}}, i32 %{{.*}}, i32 0)
- // Use ct_select to ensure safe array indexing
- int safe_index = __builtin_ct_select(index >= 0 && index < 10, index, 0);
- return array[safe_index];
-}
-
-// Test bit manipulation edge cases
-unsigned int test_bit_manipulation_edge_cases(int cond, unsigned int value) {
- // CHECK-LABEL: define {{.*}} @test_bit_manipulation_edge_cases
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[SHIFT_LEFT:%.*]] = shl i32 %{{.*}}, 31
- // CHECK-DAG: [[SHIFT_RIGHT:%.*]] = lshr i32 %{{.*}}, 31
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- // Test extreme bit shifts that could cause undefined behavior
- unsigned int left_shift = value << 31; // Could overflow
- unsigned int right_shift = value >> 31; // Extract sign bit
- return __builtin_ct_select(cond, left_shift, right_shift);
-}
+struct S {
+ int x;
+};
-// Test signed integer wraparound
-int test_signed_wraparound(int cond, int a, int b) {
- // CHECK-LABEL: define {{.*}} @test_signed_wraparound
- // CHECK-DAG: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK-DAG: [[ADD:%.*]] = add nsw i32 %{{.*}}, %{{.*}}
- // CHECK-DAG: [[SUB:%.*]] = sub nsw i32 %{{.*}}, %{{.*}}
- // CHECK-DAG: [[RESULT:%.*]] = call i32 @llvm.ct.select.i32(i1 [[COND]], i32 %{{.*}}, i32 %{{.*}})
- // CHECK: ret i32 [[RESULT]]
- int sum = a + b; // Could overflow
- int diff = a - b; // Could underflow
- return __builtin_ct_select(cond, sum, diff);
+// A well-formed call must not diagnose.
+int test_valid(int c, int a, int b) {
+ return __builtin_ct_select(c, a, b);
}
-// Test vector NaN handling
-float4 test_vector_nan_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_vector_nan_operands
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
- // CHECK: ret <4 x float> [[RESULT]]
- float nan_val = __builtin_nanf("");
- float4 nan_vec = {nan_val, nan_val, nan_val, nan_val};
- float4 normal_vec = {1.0f, 2.0f, 3.0f, 4.0f};
- return __builtin_ct_select(cond, nan_vec, normal_vec);
+// The builtin requires exactly three arguments.
+void test_too_few(int c, int a) {
+ __builtin_ct_select(c, a); // expected-error {{too few arguments to function call, expected at least 3, have 2}}
}
-// Test vector infinity handling
-float4 test_vector_infinity_operands(int cond) {
- // CHECK-LABEL: define {{.*}} @test_vector_infinity_operands
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call <4 x float> @llvm.ct.select.v4f32(i1 [[COND]], <4 x float> %{{.*}}, <4 x float> %{{.*}})
- // CHECK: ret <4 x float> [[RESULT]]
- float pos_inf = __builtin_inff();
- float neg_inf = -__builtin_inff();
- float4 inf_vec = {pos_inf, neg_inf, pos_inf, neg_inf};
- float4 zero_vec = {0.0f, 0.0f, 0.0f, 0.0f};
- return __builtin_ct_select(cond, inf_vec, zero_vec);
+void test_too_many(int c, int a, int b, int d) {
+ __builtin_ct_select(c, a, b, d); // expected-error {{too many arguments to function call, expected 3, have 4}}
}
-// Test mixed special values
-double test_mixed_special_values(int cond) {
- // CHECK-LABEL: define {{.*}} @test_mixed_special_values
- // CHECK: [[COND:%.*]] = icmp ne i32 %{{.*}}, 0
- // CHECK: [[RESULT:%.*]] = call double @llvm.ct.select.f64(i1 [[COND]], double %{{.*}}, double %{{.*}})
- // CHECK: ret double [[RESULT]]
- double nan_val = __builtin_nan("");
- double inf_val = __builtin_inf();
- return __builtin_ct_select(cond, nan_val, inf_val);
+// The condition must be an integer type.
+void test_noninteger_cond(struct S s, int a, int b) {
+ __builtin_ct_select(s, a, b); // expected-error {{used type 'struct S' where arithmetic or pointer type is required}}
}
-// Test constant-time memory access pattern
-int test_constant_time_memory_access(int secret_index, int* data_array) {
- // CHECK-LABEL: define {{.*}} @test_constant_time_memory_access
- // This pattern ensures constant-time memory access regardless of secret_index value
- int result = 0;
- // Use ct_select to accumulate values without revealing the secret index
- for (int i = 0; i < 8; i++) {
- int is_target = (i == secret_index);
- int current_value = data_array[i];
- int selected_value = __builtin_ct_select(is_target, current_value, 0);
- result += selected_value;
- }
- return result;
+// The value operands must be scalar or vector types.
+void test_nonscalar_operands(int c, struct S s) {
+ __builtin_ct_select(c, s, s); // expected-error {{incompatible operand types ('struct S' and 'struct S')}}
}
-// Test timing-attack resistant comparison
-int test_timing_resistant_comparison(const char* secret, const char* guess) {
- // CHECK-LABEL: define {{.*}} @test_timing_resistant_comparison
- // Constant-time string comparison using ct_select
- int match = 1;
- for (int i = 0; i < 32; i++) {
- int chars_equal = (secret[i] == guess[i]);
- int both_null = (secret[i] == 0) && (guess[i] == 0);
- int still_matching = __builtin_ct_select(chars_equal || both_null, match, 0);
- match = __builtin_ct_select(both_null, match, still_matching);
- }
- return match;
+// The two value operands must have the same type.
+void test_mismatched_operands(int c, int a, int *p) {
+ __builtin_ct_select(c, a, p); // expected-error {{incompatible operand types ('int' and 'int *')}}
}
More information about the llvm-branch-commits
mailing list