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