[clang] fc8d376 - [ExtVectorType] Support conditional select operator for C++.
Florian Hahn via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 9 05:09:09 PST 2021
Author: Florian Hahn
Date: 2021-03-09T13:08:52Z
New Revision: fc8d3766d721ebc075e16814e48adf48034ea858
URL: https://github.com/llvm/llvm-project/commit/fc8d3766d721ebc075e16814e48adf48034ea858
DIFF: https://github.com/llvm/llvm-project/commit/fc8d3766d721ebc075e16814e48adf48034ea858.diff
LOG: [ExtVectorType] Support conditional select operator for C++.
This patch implements the conditional select operator for
ext_vector_types in C++. It does so by using the same semantics as for
C.
D71463 added support for the conditional select operator for VectorType
in C++. Unfortunately the semantics between ext_vector_type in C are
different to VectorType in C++. Select for ext_vector_type is based on
the MSB of the condition vector, whereas for VectorType it is `!= 0`.
This unfortunately means that the behavior is inconsistent between
ExtVectorType and VectorType, but I think using the C semantics for
ExtVectorType in C++ as well should be less surprising for users.
Reviewed By: erichkeane, aaron.ballman
Differential Revision: https://reviews.llvm.org/D98055
Added:
clang/test/CodeGenCXX/ext-vector-type-conditional.cpp
clang/test/CodeGenCXX/vector-size-conditional.cpp
clang/test/SemaCXX/ext-vector-type-conditional.cpp
clang/test/SemaCXX/vector-size-conditional.cpp
Modified:
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Sema/Sema.h
clang/lib/Sema/SemaExprCXX.cpp
Removed:
clang/test/CodeGenCXX/vector-conditional.cpp
clang/test/SemaCXX/vector-conditional.cpp
################################################################################
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 79eeed869ba6..7076274dcb6b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7423,9 +7423,10 @@ def err_conditional_vector_element_size : Error<
def err_conditional_vector_has_void : Error<
"GNU vector conditional operand cannot be %select{void|a throw expression}0">;
def err_conditional_vector_operand_type
- : Error<"%select{enumeration|extended vector}0 type %1 is not allowed in a "
- "vector conditional">;
-def err_conditional_vector_mismatched_vectors
+ : Error<"enumeration type %0 is not allowed in a vector conditional">;
+def err_conditional_vector_cond_result_mismatch
+ : Error<"cannot mix vectors and extended vectors in a vector conditional">;
+def err_conditional_vector_mismatched
: Error<"vector operands to the vector conditional must be the same type "
"%
diff {($ and $)|}0,1}">;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e561aaaca7bb..a919740aa662 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -11557,9 +11557,9 @@ class Sema final {
QualType CXXCheckConditionalOperands( // C++ 5.16
ExprResult &cond, ExprResult &lhs, ExprResult &rhs,
ExprValueKind &VK, ExprObjectKind &OK, SourceLocation questionLoc);
- QualType CheckGNUVectorConditionalTypes(ExprResult &Cond, ExprResult &LHS,
- ExprResult &RHS,
- SourceLocation QuestionLoc);
+ QualType CheckVectorConditionalTypes(ExprResult &Cond, ExprResult &LHS,
+ ExprResult &RHS,
+ SourceLocation QuestionLoc);
QualType FindCompositePointerType(SourceLocation Loc, Expr *&E1, Expr *&E2,
bool ConvertArgs = true);
QualType FindCompositePointerType(SourceLocation Loc,
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 8da913035c8f..6a79d621f5de 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5970,19 +5970,18 @@ static bool ConvertForConditional(Sema &Self, ExprResult &E, QualType T) {
// extension.
static bool isValidVectorForConditionalCondition(ASTContext &Ctx,
QualType CondTy) {
- if (!CondTy->isVectorType() || CondTy->isExtVectorType())
+ if (!CondTy->isVectorType() && !CondTy->isExtVectorType())
return false;
const QualType EltTy =
cast<VectorType>(CondTy.getCanonicalType())->getElementType();
-
assert(!EltTy->isBooleanType() && !EltTy->isEnumeralType() &&
"Vectors cant be boolean or enum types");
return EltTy->isIntegralType(Ctx);
}
-QualType Sema::CheckGNUVectorConditionalTypes(ExprResult &Cond, ExprResult &LHS,
- ExprResult &RHS,
- SourceLocation QuestionLoc) {
+QualType Sema::CheckVectorConditionalTypes(ExprResult &Cond, ExprResult &LHS,
+ ExprResult &RHS,
+ SourceLocation QuestionLoc) {
LHS = DefaultFunctionArrayLvalueConversion(LHS.get());
RHS = DefaultFunctionArrayLvalueConversion(RHS.get());
@@ -5997,24 +5996,17 @@ QualType Sema::CheckGNUVectorConditionalTypes(ExprResult &Cond, ExprResult &LHS,
QualType ResultType;
- // FIXME: In the future we should define what the Extvector conditional
- // operator looks like.
- if (LHSVT && isa<ExtVectorType>(LHSVT)) {
- Diag(QuestionLoc, diag::err_conditional_vector_operand_type)
- << /*isExtVector*/ true << LHSType;
- return {};
- }
-
- if (RHSVT && isa<ExtVectorType>(RHSVT)) {
- Diag(QuestionLoc, diag::err_conditional_vector_operand_type)
- << /*isExtVector*/ true << RHSType;
- return {};
- }
if (LHSVT && RHSVT) {
+ if (isa<ExtVectorType>(CondVT) != isa<ExtVectorType>(LHSVT)) {
+ Diag(QuestionLoc, diag::err_conditional_vector_cond_result_mismatch)
+ << /*isExtVector*/ isa<ExtVectorType>(CondVT);
+ return {};
+ }
+
// If both are vector types, they must be the same type.
if (!Context.hasSameType(LHSType, RHSType)) {
- Diag(QuestionLoc, diag::err_conditional_vector_mismatched_vectors)
+ Diag(QuestionLoc, diag::err_conditional_vector_mismatched)
<< LHSType << RHSType;
return {};
}
@@ -6039,18 +6031,22 @@ QualType Sema::CheckGNUVectorConditionalTypes(ExprResult &Cond, ExprResult &LHS,
if (ResultElementTy->isEnumeralType()) {
Diag(QuestionLoc, diag::err_conditional_vector_operand_type)
- << /*isExtVector*/ false << ResultElementTy;
+ << ResultElementTy;
return {};
}
- ResultType = Context.getVectorType(
- ResultElementTy, CondType->castAs<VectorType>()->getNumElements(),
- VectorType::GenericVector);
+ if (CondType->isExtVectorType())
+ ResultType =
+ Context.getExtVectorType(ResultElementTy, CondVT->getNumElements());
+ else
+ ResultType = Context.getVectorType(
+ ResultElementTy, CondVT->getNumElements(), VectorType::GenericVector);
LHS = ImpCastExprToType(LHS.get(), ResultType, CK_VectorSplat);
RHS = ImpCastExprToType(RHS.get(), ResultType, CK_VectorSplat);
}
assert(!ResultType.isNull() && ResultType->isVectorType() &&
+ (!CondType->isExtVectorType() || ResultType->isExtVectorType()) &&
"Result should have been a vector type");
auto *ResultVectorTy = ResultType->castAs<VectorType>();
QualType ResultElementTy = ResultVectorTy->getElementType();
@@ -6077,15 +6073,21 @@ QualType Sema::CheckGNUVectorConditionalTypes(ExprResult &Cond, ExprResult &LHS,
/// See C++ [expr.cond]. Note that LHS is never null, even for the GNU x ?: y
/// extension. In this case, LHS == Cond. (But they're not aliases.)
///
-/// This function also implements GCC's vector extension for conditionals.
-/// GCC's vector extension permits the use of a?b:c where the type of
-/// a is that of a integer vector with the same number of elements and
-/// size as the vectors of b and c. If one of either b or c is a scalar
-/// it is implicitly converted to match the type of the vector.
-/// Otherwise the expression is ill-formed. If both b and c are scalars,
-/// then b and c are checked and converted to the type of a if possible.
-/// Unlike the OpenCL ?: operator, the expression is evaluated as
-/// (a[0] != 0 ? b[0] : c[0], .. , a[n] != 0 ? b[n] : c[n]).
+/// This function also implements GCC's vector extension and the
+/// OpenCL/ext_vector_type extension for conditionals. The vector extensions
+/// permit the use of a?b:c where the type of a is that of a integer vector with
+/// the same number of elements and size as the vectors of b and c. If one of
+/// either b or c is a scalar it is implicitly converted to match the type of
+/// the vector. Otherwise the expression is ill-formed. If both b and c are
+/// scalars, then b and c are checked and converted to the type of a if
+/// possible.
+///
+/// The expressions are evaluated
diff erently for GCC's and OpenCL's extensions.
+/// For the GCC extension, the ?: operator is evaluated as
+/// (a[0] != 0 ? b[0] : c[0], .. , a[n] != 0 ? b[n] : c[n]).
+/// For the OpenCL extensions, the ?: operator is evaluated as
+/// (most-significant-bit-set(a[0]) ? b[0] : c[0], .. ,
+/// most-significant-bit-set(a[n]) ? b[n] : c[n]).
QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS,
ExprResult &RHS, ExprValueKind &VK,
ExprObjectKind &OK,
@@ -6169,7 +6171,7 @@ QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS,
// Neither is void.
if (IsVectorConditional)
- return CheckGNUVectorConditionalTypes(Cond, LHS, RHS, QuestionLoc);
+ return CheckVectorConditionalTypes(Cond, LHS, RHS, QuestionLoc);
// C++11 [expr.cond]p3
// Otherwise, if the second and third operand have
diff erent types, and
diff --git a/clang/test/CodeGenCXX/ext-vector-type-conditional.cpp b/clang/test/CodeGenCXX/ext-vector-type-conditional.cpp
new file mode 100644
index 000000000000..573b8ea2e74a
--- /dev/null
+++ b/clang/test/CodeGenCXX/ext-vector-type-conditional.cpp
@@ -0,0 +1,267 @@
+// RUN: %clang_cc1 %s -triple x86_64-linux-gnu -Wno-unused -std=c++11 -emit-llvm -o - | FileCheck %s
+
+using FourShorts = short __attribute__((ext_vector_type(4)));
+using TwoInts = int __attribute__((ext_vector_type(2)));
+using TwoUInts = unsigned __attribute__((ext_vector_type(2)));
+using FourInts = int __attribute__((ext_vector_type(4)));
+using FourUInts = unsigned __attribute__((ext_vector_type(4)));
+using TwoLongLong = long long __attribute__((ext_vector_type(2)));
+using FourLongLong = long long __attribute__((ext_vector_type(4)));
+using TwoFloats = float __attribute__((ext_vector_type(2)));
+using FourFloats = float __attribute__((ext_vector_type(4)));
+using TwoDoubles = double __attribute__((ext_vector_type(2)));
+using FourDoubles = double __attribute__((ext_vector_type(4)));
+
+FourShorts four_shorts;
+TwoInts two_ints;
+TwoUInts two_uints;
+FourInts four_ints;
+FourUInts four_uints;
+TwoLongLong two_ll;
+FourLongLong four_ll;
+TwoFloats two_floats;
+FourFloats four_floats;
+TwoDoubles two_doubles;
+FourDoubles four_doubles;
+
+short some_short;
+unsigned short some_ushort;
+int some_int;
+float some_float;
+unsigned int some_uint;
+long long some_ll;
+unsigned long long some_ull;
+double some_double;
+
+// CHECK: TwoVectorOps
+void TwoVectorOps() {
+ two_ints ? two_ints : two_ints;
+ // CHECK: [[COND:%.+]] = load <2 x i32>
+ // CHECK: [[LHS:%.+]] = load <2 x i32>
+ // CHECK: [[RHS:%.+]] = load <2 x i32>
+ // CHECK: [[NEG:%.+]] = icmp slt <2 x i32> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <2 x i1> [[NEG]] to <2 x i32>
+ // CHECK: [[XOR:%.+]] = xor <2 x i32> [[SEXT]], <i32 -1, i32 -1>
+ // CHECK: [[RHS_AND:%.+]] = and <2 x i32> [[RHS]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <2 x i32> [[LHS]], [[SEXT]]
+ // CHECK: = or <2 x i32> [[RHS_AND]], [[LHS_AND]]
+
+ two_ints ? two_floats : two_floats;
+ // CHECK: [[COND:%.+]] = load <2 x i32>
+ // CHECK: [[LHS:%.+]] = load <2 x float>
+ // CHECK: [[RHS:%.+]] = load <2 x float>
+ // CHECK: [[NEG:%.+]] = icmp slt <2 x i32> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <2 x i1> [[NEG]] to <2 x i32>
+ // CHECK: [[XOR:%.+]] = xor <2 x i32> [[SEXT]], <i32 -1, i32 -1>
+ // CHECK: [[RHS_EXT:%.+]] = bitcast <2 x float> [[RHS]] to <2 x i32>
+ // CHECK: [[LHS_EXT:%.+]] = bitcast <2 x float> [[LHS]] to <2 x i32>
+ // CHECK: [[RHS_AND:%.+]] = and <2 x i32> [[RHS_EXT]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <2 x i32> [[LHS_EXT]], [[SEXT]]
+ // CHECK: [[OR:%.+]] = or <2 x i32> [[RHS_AND]], [[LHS_AND]]
+ // CHECK: = bitcast <2 x i32> [[OR]] to <2 x float>
+
+ two_ll ? two_doubles : two_doubles;
+ // CHECK: [[COND:%.+]] = load <2 x i64>
+ // CHECK: [[LHS:%.+]] = load <2 x double>
+ // CHECK: [[RHS:%.+]] = load <2 x double>
+ // CHECK: [[NEG:%.+]] = icmp slt <2 x i64> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <2 x i1> [[NEG]] to <2 x i64>
+ // CHECK: [[XOR:%.+]] = xor <2 x i64> [[SEXT]], <i64 -1, i64 -1>
+ // CHECK: [[RHS_EXT:%.+]] = bitcast <2 x double> [[RHS]] to <2 x i64>
+ // CHECK: [[LHS_EXT:%.+]] = bitcast <2 x double> [[LHS]] to <2 x i64>
+ // CHECK: [[RHS_AND:%.+]] = and <2 x i64> [[RHS_EXT]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <2 x i64> [[LHS_EXT]], [[SEXT]]
+ // CHECK: [[OR:%.+]] = or <2 x i64> [[RHS_AND]], [[LHS_AND]]
+ // CHECK: = bitcast <2 x i64> [[OR]] to <2 x double>
+}
+
+// CHECK: TwoScalarOps
+void TwoScalarOps() {
+ four_shorts ? some_short : some_short;
+ // CHECK: [[COND:%.+]] = load <4 x i16>
+ // CHECK: [[LHS:%.+]] = load i16
+ // CHECK: [[LHS_SPLAT_INSERT:%.+]] = insertelement <4 x i16> poison, i16 [[LHS]], i32 0
+ // CHECK: [[LHS_SPLAT:%.+]] = shufflevector <4 x i16> [[LHS_SPLAT_INSERT]], <4 x i16> poison, <4 x i32> zeroinitializer
+ // CHECK: [[RHS:%.+]] = load i16
+ // CHECK: [[RHS_SPLAT_INSERT:%.+]] = insertelement <4 x i16> poison, i16 [[RHS]], i32 0
+ // CHECK: [[RHS_SPLAT:%.+]] = shufflevector <4 x i16> [[RHS_SPLAT_INSERT]], <4 x i16> poison, <4 x i32> zeroinitializer
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i16> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i16>
+ // CHECK: [[XOR:%.+]] = xor <4 x i16> [[SEXT]], <i16 -1, i16 -1, i16 -1, i16 -1>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i16> [[RHS_SPLAT]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i16> [[LHS_SPLAT]], [[SEXT]]
+ // CHECK: = or <4 x i16> [[RHS_AND]], [[LHS_AND]]
+
+ four_shorts ? some_ushort : some_ushort;
+ // CHECK: [[COND:%.+]] = load <4 x i16>
+ // CHECK: [[LHS:%.+]] = load i16
+ // CHECK: [[LHS_SPLAT_INSERT:%.+]] = insertelement <4 x i16> poison, i16 [[LHS]], i32 0
+ // CHECK: [[LHS_SPLAT:%.+]] = shufflevector <4 x i16> [[LHS_SPLAT_INSERT]], <4 x i16> poison, <4 x i32> zeroinitializer
+ // CHECK: [[RHS:%.+]] = load i16
+ // CHECK: [[RHS_SPLAT_INSERT:%.+]] = insertelement <4 x i16> poison, i16 [[RHS]], i32 0
+ // CHECK: [[RHS_SPLAT:%.+]] = shufflevector <4 x i16> [[RHS_SPLAT_INSERT]], <4 x i16> poison, <4 x i32> zeroinitializer
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i16> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i16>
+ // CHECK: [[XOR:%.+]] = xor <4 x i16> [[SEXT]], <i16 -1, i16 -1, i16 -1, i16 -1>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i16> [[RHS_SPLAT]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i16> [[LHS_SPLAT]], [[SEXT]]
+ // CHECK: = or <4 x i16> [[RHS_AND]], [[LHS_AND]]
+
+ four_ints ? some_ushort : some_short;
+ // CHECK: [[COND:%.+]] = load <4 x i32>
+ // CHECK: [[LHS:%.+]] = load i16
+ // CHECK: [[LHS_ZEXT:%.+]] = zext i16 [[LHS]] to i32
+ // CHECK: [[LHS_SPLAT_INSERT:%.+]] = insertelement <4 x i32> poison, i32 [[LHS_ZEXT]], i32 0
+ // CHECK: [[LHS_SPLAT:%.+]] = shufflevector <4 x i32> [[LHS_SPLAT_INSERT]], <4 x i32> poison, <4 x i32> zeroinitializer
+ // CHECK: [[RHS:%.+]] = load i16
+ // CHECK: [[RHS_SEXT:%.+]] = sext i16 [[RHS]] to i32
+ // CHECK: [[RHS_SPLAT_INSERT:%.+]] = insertelement <4 x i32> poison, i32 [[RHS_SEXT]], i32 0
+ // CHECK: [[RHS_SPLAT:%.+]] = shufflevector <4 x i32> [[RHS_SPLAT_INSERT]], <4 x i32> poison, <4 x i32> zeroinitializer
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i32> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i32>
+ // CHECK: [[XOR:%.+]] = xor <4 x i32> [[SEXT]], <i32 -1, i32 -1, i32 -1, i32 -1>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i32> [[RHS_SPLAT]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i32> [[LHS_SPLAT]], [[SEXT]]
+ // CHECK: = or <4 x i32> [[RHS_AND]], [[LHS_AND]]
+
+ four_ints ? some_int : some_float;
+ // CHECK: [[COND:%.+]] = load <4 x i32>
+ // CHECK: [[LHS:%.+]] = load i32
+ // CHECK: [[LHS_CONV:%.+]] = sitofp i32 [[LHS]] to float
+ // CHECK: [[LHS_SPLAT_INSERT:%.+]] = insertelement <4 x float> poison, float [[LHS_CONV]], i32 0
+ // CHECK: [[LHS_SPLAT:%.+]] = shufflevector <4 x float> [[LHS_SPLAT_INSERT]], <4 x float> poison, <4 x i32> zeroinitializer
+ // CHECK: [[RHS:%.+]] = load float
+ // CHECK: [[RHS_SPLAT_INSERT:%.+]] = insertelement <4 x float> poison, float [[RHS]], i32 0
+ // CHECK: [[RHS_SPLAT:%.+]] = shufflevector <4 x float> [[RHS_SPLAT_INSERT]], <4 x float> poison, <4 x i32> zeroinitializer
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i32> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i32>
+ // CHECK: [[XOR:%.+]] = xor <4 x i32> [[SEXT]], <i32 -1, i32 -1, i32 -1, i32 -1>
+ // CHECK: [[RHS_CAST:%.+]] = bitcast <4 x float> [[RHS_SPLAT]] to <4 x i32>
+ // CHECK: [[LHS_CAST:%.+]] = bitcast <4 x float> [[LHS_SPLAT]] to <4 x i32>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i32> [[RHS_CAST]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i32> [[LHS_CAST]], [[SEXT]]
+ // CHECK: = or <4 x i32> [[RHS_AND]], [[LHS_AND]]
+
+ four_ll ? some_double : some_ll;
+ // CHECK: [[COND:%.+]] = load <4 x i64>
+ // CHECK: [[LHS:%.+]] = load double
+ // CHECK: [[LHS_SPLAT_INSERT:%.+]] = insertelement <4 x double> poison, double [[LHS]], i32 0
+ // CHECK: [[LHS_SPLAT:%.+]] = shufflevector <4 x double> [[LHS_SPLAT_INSERT]], <4 x double> poison, <4 x i32> zeroinitializer
+ // CHECK: [[RHS:%.+]] = load i64
+ // CHECK: [[RHS_CONV:%.+]] = sitofp i64 [[RHS]] to double
+ // CHECK: [[RHS_SPLAT_INSERT:%.+]] = insertelement <4 x double> poison, double [[RHS_CONV]], i32 0
+ // CHECK: [[RHS_SPLAT:%.+]] = shufflevector <4 x double> [[RHS_SPLAT_INSERT]], <4 x double> poison, <4 x i32> zeroinitializer
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i64> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i64>
+ // CHECK: [[XOR:%.+]] = xor <4 x i64> [[SEXT]], <i64 -1, i64 -1, i64 -1, i64 -1>
+ // CHECK: [[RHS_CAST:%.+]] = bitcast <4 x double> [[RHS_SPLAT]] to <4 x i64>
+ // CHECK: [[LHS_CAST:%.+]] = bitcast <4 x double> [[LHS_SPLAT]] to <4 x i64>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i64> [[RHS_CAST]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i64> [[LHS_CAST]], [[SEXT]]
+ // CHECK: = or <4 x i64> [[RHS_AND]], [[LHS_AND]]
+
+ four_ints ? some_int : some_short;
+ // CHECK: [[COND:%.+]] = load <4 x i32>
+ // CHECK: [[LHS:%.+]] = load i32
+ // CHECK: [[LHS_SPLAT_INSERT:%.+]] = insertelement <4 x i32> poison, i32 [[LHS]], i32 0
+ // CHECK: [[LHS_SPLAT:%.+]] = shufflevector <4 x i32> [[LHS_SPLAT_INSERT]], <4 x i32> poison, <4 x i32> zeroinitializer
+ // CHECK: [[RHS:%.+]] = load i16
+ // CHECK: [[RHS_SEXT:%.+]] = sext i16 [[RHS]] to i32
+ // CHECK: [[RHS_SPLAT_INSERT:%.+]] = insertelement <4 x i32> poison, i32 [[RHS_SEXT]], i32 0
+ // CHECK: [[RHS_SPLAT:%.+]] = shufflevector <4 x i32> [[RHS_SPLAT_INSERT]], <4 x i32> poison, <4 x i32> zeroinitializer
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i32> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i32>
+ // CHECK: [[XOR:%.+]] = xor <4 x i32> [[SEXT]], <i32 -1, i32 -1, i32 -1, i32 -1>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i32> [[RHS_SPLAT]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i32> [[LHS_SPLAT]], [[SEXT]]
+ // CHECK: = or <4 x i32> [[RHS_AND]], [[LHS_AND]]
+}
+
+// CHECK: OneScalarOp
+void OneScalarOp() {
+ four_ints ? four_ints : some_int;
+ // CHECK: [[COND:%.+]] = load <4 x i32>
+ // CHECK: [[LHS:%.+]] = load <4 x i32>
+ // CHECK: [[RHS:%.+]] = load i32
+ // CHECK: [[RHS_SPLAT_INSERT:%.+]] = insertelement <4 x i32> poison, i32 [[RHS]], i32 0
+ // CHECK: [[RHS_SPLAT:%.+]] = shufflevector <4 x i32> [[RHS_SPLAT_INSERT]], <4 x i32> poison, <4 x i32> zeroinitializer
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i32> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i32>
+ // CHECK: [[XOR:%.+]] = xor <4 x i32> [[SEXT]], <i32 -1, i32 -1, i32 -1, i32 -1>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i32> [[RHS_SPLAT]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i32> [[LHS]], [[SEXT]]
+ // CHECK: = or <4 x i32> [[RHS_AND]], [[LHS_AND]]
+
+ four_ints ? four_ints : 5;
+ // CHECK: [[COND:%.+]] = load <4 x i32>
+ // CHECK: [[LHS:%.+]] = load <4 x i32>
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i32> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i32>
+ // CHECK: [[XOR:%.+]] = xor <4 x i32> [[SEXT]], <i32 -1, i32 -1, i32 -1, i32 -1>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i32> <i32 5, i32 5, i32 5, i32 5>, [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i32> [[LHS]], [[SEXT]]
+ // CHECK: = or <4 x i32> [[RHS_AND]], [[LHS_AND]]
+
+ four_ints ? four_floats : some_float;
+ // CHECK: [[COND:%.+]] = load <4 x i32>
+ // CHECK: [[LHS:%.+]] = load <4 x float>
+ // CHECK: [[RHS:%.+]] = load float
+ // CHECK: [[RHS_SPLAT_INSERT:%.+]] = insertelement <4 x float> poison, float [[RHS]], i32 0
+ // CHECK: [[RHS_SPLAT:%.+]] = shufflevector <4 x float> [[RHS_SPLAT_INSERT]], <4 x float> poison, <4 x i32> zeroinitializer
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i32> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i32>
+ // CHECK: [[XOR:%.+]] = xor <4 x i32> [[SEXT]], <i32 -1, i32 -1, i32 -1, i32 -1>
+ // CHECK: [[RHS_CAST:%.+]] = bitcast <4 x float> [[RHS_SPLAT]] to <4 x i32>
+ // CHECK: [[LHS_CAST:%.+]] = bitcast <4 x float> [[LHS]] to <4 x i32>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i32> [[RHS_CAST]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i32> [[LHS_CAST]], [[SEXT]]
+ // CHECK: = or <4 x i32> [[RHS_AND]], [[LHS_AND]]
+
+ four_ll ? four_doubles : 6.0;
+ // CHECK: [[COND:%.+]] = load <4 x i64>
+ // CHECK: [[LHS:%.+]] = load <4 x double>
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i64> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i64>
+ // CHECK: [[XOR:%.+]] = xor <4 x i64> [[SEXT]], <i64 -1, i64 -1, i64 -1, i64 -1>
+ // CHECK: [[LHS_CAST:%.+]] = bitcast <4 x double> [[LHS]] to <4 x i64>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i64> <i64 4618441417868443648, i64 4618441417868443648, i64 4618441417868443648, i64 4618441417868443648>, [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i64> [[LHS_CAST]], [[SEXT]]
+ // CHECK: = or <4 x i64> [[RHS_AND]], [[LHS_AND]]
+
+ four_ll ? four_ll : 6;
+ // CHECK: [[COND:%.+]] = load <4 x i64>
+ // CHECK: [[LHS:%.+]] = load <4 x i64>
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i64> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i64>
+ // CHECK: [[XOR:%.+]] = xor <4 x i64> [[SEXT]], <i64 -1, i64 -1, i64 -1, i64 -1>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i64> <i64 6, i64 6, i64 6, i64 6>, [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i64> [[LHS]], [[SEXT]]
+ // CHECK: [[OR:%.+]] = or <4 x i64> [[RHS_AND]], [[LHS_AND]]
+
+ four_ll ? four_ll : some_int;
+ // CHECK: [[COND:%.+]] = load <4 x i64>
+ // CHECK: [[LHS:%.+]] = load <4 x i64>
+ // CHECK: [[RHS:%.+]] = load i32
+ // CHECK: [[RHS_CONV:%.+]] = sext i32 [[RHS]] to i64
+ // CHECK: [[RHS_SPLAT_INSERT:%.+]] = insertelement <4 x i64> poison, i64 [[RHS_CONV]], i32 0
+ // CHECK: [[RHS_SPLAT:%.+]] = shufflevector <4 x i64> [[RHS_SPLAT_INSERT]], <4 x i64> poison, <4 x i32> zeroinitializer
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i64> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i64>
+ // CHECK: [[XOR:%.+]] = xor <4 x i64> [[SEXT]], <i64 -1, i64 -1, i64 -1, i64 -1>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i64> [[RHS_SPLAT]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i64> [[LHS]], [[SEXT]]
+ // CHECK: [[OR:%.+]] = or <4 x i64> [[RHS_AND]], [[LHS_AND]]
+
+ four_ll ? four_ll : some_ll;
+ // CHECK: [[COND:%.+]] = load <4 x i64>
+ // CHECK: [[LHS:%.+]] = load <4 x i64>
+ // CHECK: [[RHS:%.+]] = load i64
+ // CHECK: [[RHS_SPLAT_INSERT:%.+]] = insertelement <4 x i64> poison, i64 [[RHS]], i32 0
+ // CHECK: [[RHS_SPLAT:%.+]] = shufflevector <4 x i64> [[RHS_SPLAT_INSERT]], <4 x i64> poison, <4 x i32> zeroinitializer
+ // CHECK: [[NEG:%.+]] = icmp slt <4 x i64> [[COND]], zeroinitializer
+ // CHECK: [[SEXT:%.+]] = sext <4 x i1> [[NEG]] to <4 x i64>
+ // CHECK: [[XOR:%.+]] = xor <4 x i64> [[SEXT]], <i64 -1, i64 -1, i64 -1, i64 -1>
+ // CHECK: [[RHS_AND:%.+]] = and <4 x i64> [[RHS_SPLAT]], [[XOR]]
+ // CHECK: [[LHS_AND:%.+]] = and <4 x i64> [[LHS]], [[SEXT]]
+ // CHECK: [[OR:%.+]] = or <4 x i64> [[RHS_AND]], [[LHS_AND]]
+}
diff --git a/clang/test/CodeGenCXX/vector-conditional.cpp b/clang/test/CodeGenCXX/vector-size-conditional.cpp
similarity index 100%
rename from clang/test/CodeGenCXX/vector-conditional.cpp
rename to clang/test/CodeGenCXX/vector-size-conditional.cpp
diff --git a/clang/test/SemaCXX/ext-vector-type-conditional.cpp b/clang/test/SemaCXX/ext-vector-type-conditional.cpp
new file mode 100644
index 000000000000..b2e125b350cc
--- /dev/null
+++ b/clang/test/SemaCXX/ext-vector-type-conditional.cpp
@@ -0,0 +1,194 @@
+// RUN: %clang_cc1 -triple x86_64-linux-pc -fsyntax-only -verify -fexceptions -fcxx-exceptions %s -std=c++17
+// Note that this test depends on the size of long-long to be
diff erent from
+// int, so it specifies a triple.
+
+using FourShorts = short __attribute__((ext_vector_type(4)));
+using TwoInts = int __attribute__((ext_vector_type(2)));
+using TwoUInts = unsigned __attribute__((ext_vector_type(2)));
+using FourInts = int __attribute__((ext_vector_type(4)));
+using FourUInts = unsigned __attribute__((ext_vector_type(4)));
+using TwoLongLong = long long __attribute__((ext_vector_type(2)));
+using FourLongLong = long long __attribute__((ext_vector_type(4)));
+using TwoFloats = float __attribute__((ext_vector_type(2)));
+using FourFloats = float __attribute__((ext_vector_type(4)));
+using TwoDoubles = double __attribute__((ext_vector_type(2)));
+using FourDoubles = double __attribute__((ext_vector_type(4)));
+
+FourShorts four_shorts;
+TwoInts two_ints;
+TwoUInts two_uints;
+FourInts four_ints;
+FourUInts four_uints;
+TwoLongLong two_ll;
+FourLongLong four_ll;
+TwoFloats two_floats;
+FourFloats four_floats;
+TwoDoubles two_doubles;
+FourDoubles four_doubles;
+
+enum E {};
+enum class SE {};
+E e;
+SE se;
+
+// Check the rules of the condition of the conditional operator.
+void Condition() {
+ // Only int types are allowed here, the rest should fail to convert to bool.
+ (void)(four_floats ? 1 : 1); // expected-error {{is not contextually convertible to 'bool'}}}
+ (void)(two_doubles ? 1 : 1); // expected-error {{is not contextually convertible to 'bool'}}}
+}
+
+// Check the rules of the LHS/RHS of the conditional operator.
+void Operands() {
+ (void)(four_ints ? four_ints : throw 1); // expected-error {{GNU vector conditional operand cannot be a throw expression}}
+ (void)(four_ints ? throw 1 : four_ints); // expected-error {{GNU vector conditional operand cannot be a throw expression}}
+ (void)(four_ints ?: throw 1); // expected-error {{GNU vector conditional operand cannot be a throw expression}}
+ (void)(four_ints ? (void)1 : four_ints); // expected-error {{GNU vector conditional operand cannot be void}}
+ (void)(four_ints ?: (void)1); // expected-error {{GNU vector conditional operand cannot be void}}
+
+ // Vector types must be the same element size as the condition.
+ (void)(four_ints ? two_ll : two_ll); // expected-error {{vector condition type 'FourInts' (vector of 4 'int' values) and result type 'TwoLongLong' (vector of 2 'long long' values) do not have the same number of elements}}
+ (void)(four_ints ? four_ll : four_ll); // expected-error {{vector condition type 'FourInts' (vector of 4 'int' values) and result type 'FourLongLong' (vector of 4 'long long' values) do not have elements of the same size}}
+ (void)(four_ints ? two_doubles : two_doubles); // expected-error {{vector condition type 'FourInts' (vector of 4 'int' values) and result type 'TwoDoubles' (vector of 2 'double' values) do not have the same number of elements}}
+ (void)(four_ints ? four_doubles : four_doubles); // expected-error {{vector condition type 'FourInts' (vector of 4 'int' values) and result type 'FourDoubles' (vector of 4 'double' values) do not have elements of the same size}}
+ (void)(four_ints ?: two_ints); // expected-error {{vector operands to the vector conditional must be the same type ('FourInts' (vector of 4 'int' values) and 'TwoInts' (vector of 2 'int' values)}}
+ (void)(four_ints ?: four_doubles); // expected-error {{vector operands to the vector conditional must be the same type ('FourInts' (vector of 4 'int' values) and 'FourDoubles' (vector of 4 'double' values)}}
+
+ // Scalars are promoted, but must be the same element size.
+ (void)(four_ints ? 3.0f : 3.0); // expected-error {{vector condition type 'FourInts' (vector of 4 'int' values) and result type 'double __attribute__((ext_vector_type(4)))' (vector of 4 'double' values) do not have elements of the same size}}
+ (void)(four_ints ? 5ll : 5); // expected-error {{vector condition type 'FourInts' (vector of 4 'int' values) and result type 'long long __attribute__((ext_vector_type(4)))' (vector of 4 'long long' values) do not have elements of the same size}}
+ (void)(four_ints ?: 3.0); // expected-error {{annot convert between vector values of
diff erent size ('FourInts' (vector of 4 'int' values) and 'double')}}
+ (void)(four_ints ?: 5ll); // We allow this despite GCc not allowing this since we support integral->vector-integral conversions despite integer rank.
+
+ // This one would be allowed in GCC, but we don't allow vectors of enum. Also,
+ // the error message isn't perfect, since it is only going to be a problem
+ // when both sides are an enum, otherwise it'll be promoted to whatever type
+ // the other side causes.
+ (void)(four_ints ? e : e); // expected-error {{enumeration type 'E' is not allowed in a vector conditional}}
+ (void)(four_ints ? se : se); // expected-error {{enumeration type 'SE' is not allowed in a vector conditional}}
+ (void)(four_shorts ? (short)5 : (unsigned short)5); // expected-error {{vector condition type 'FourShorts' (vector of 4 'short' values) and result type 'int __attribute__((ext_vector_type(4)))' (vector of 4 'int' values) do not have elements of the same size}}
+
+ // They must also be convertible.
+ (void)(four_ints ? 3.0f : 5u);
+ (void)(four_ints ? 3.0f : 5);
+ unsigned us = 5u;
+ int sint = 5;
+ short shrt = 5;
+ unsigned short uss = 5u;
+ // The following 2 error in GCC for truncation errors, but it seems
+ // unimportant and inconsistent to enforce that rule.
+ (void)(four_ints ? 3.0f : us);
+ (void)(four_ints ? 3.0f : sint);
+
+ // Test promotion:
+ (void)(four_shorts ? uss : shrt); // expected-error {{vector condition type 'FourShorts' (vector of 4 'short' values) and result type 'int __attribute__((ext_vector_type(4)))' (vector of 4 'int' values) do not have elements of the same size}}
+ (void)(four_shorts ? shrt : shrt); // should be fine.
+ (void)(four_ints ? uss : shrt); // should be fine, since they get promoted to int.
+ (void)(four_ints ? shrt : shrt); // expected-error {{vector condition type 'FourInts' (vector of 4 'int' values) and result type 'short __attribute__((ext_vector_type(4)))' (vector of 4 'short' values) do not have elements of the same size}}
+
+ // Vectors must be the same type as eachother.
+ (void)(four_ints ? four_uints : four_floats); // expected-error {{vector operands to the vector conditional must be the same type ('FourUInts' (vector of 4 'unsigned int' values) and 'FourFloats' (vector of 4 'float' values))}}
+ (void)(four_ints ? four_uints : four_ints); // expected-error {{vector operands to the vector conditional must be the same type ('FourUInts' (vector of 4 'unsigned int' values) and 'FourInts' (vector of 4 'int' values))}}
+ (void)(four_ints ? four_ints : four_uints); // expected-error {{vector operands to the vector conditional must be the same type ('FourInts' (vector of 4 'int' values) and 'FourUInts' (vector of 4 'unsigned int' values))}}
+
+ (void)(four_ints ? four_uints : 3.0f); // expected-error {{cannot convert between vector values of
diff erent size ('FourUInts' (vector of 4 'unsigned int' values) and 'float')}}
+ (void)(four_ints ? four_ints : 3.0f); // expected-error {{cannot convert between vector values of
diff erent size ('FourInts' (vector of 4 'int' values) and 'float')}}
+
+ // When there is a vector and a scalar, conversions must be legal.
+ (void)(four_ints ? four_floats : 3); // should work, ints can convert to floats.
+ (void)(four_ints ? four_uints : e); // expected-error {{cannot convert between vector values of
diff erent size ('FourUInts' (vector of 4 'unsigned int' values) and 'E')}}
+ (void)(four_ints ? four_uints : se); // expected-error {{cannot convert between vector and non-scalar values ('FourUInts' (vector of 4 'unsigned int' values) and 'SE'}}
+
+ (void)(two_ints ? two_ints : us);
+ (void)(four_shorts ? four_shorts : uss);
+ (void)(four_ints ? four_floats : us);
+ (void)(four_ints ? four_floats : sint);
+}
+
+template <typename T1, typename T2>
+struct is_same {
+ static constexpr bool value = false;
+};
+template <typename T>
+struct is_same<T, T> {
+ static constexpr bool value = true;
+};
+template <typename T1, typename T2>
+constexpr bool is_same_v = is_same<T1, T2>::value;
+template <typename T>
+T &&declval();
+
+// Check the result types when given two vector types.
+void ResultTypes() {
+ // Vectors must be the same, but result is the type of the LHS/RHS.
+ static_assert(is_same_v<TwoInts, decltype(declval<TwoInts>() ? declval<TwoInts>() : declval<TwoInts>())>);
+ static_assert(is_same_v<TwoFloats, decltype(declval<TwoInts>() ? declval<TwoFloats>() : declval<TwoFloats>())>);
+
+ // When both are scalars, converts to vectors of common type.
+ static_assert(is_same_v<TwoUInts, decltype(declval<TwoInts>() ? declval<int>() : declval<unsigned int>())>);
+
+ // Constant is allowed since it doesn't truncate, and should promote to float.
+ static_assert(is_same_v<TwoFloats, decltype(declval<TwoInts>() ? declval<float>() : 5u)>);
+ static_assert(is_same_v<TwoFloats, decltype(declval<TwoInts>() ? 5 : declval<float>())>);
+
+ // when only 1 is a scalar, it should convert to a compatible type.
+ static_assert(is_same_v<TwoFloats, decltype(declval<TwoInts>() ? declval<TwoFloats>() : declval<float>())>);
+ static_assert(is_same_v<TwoInts, decltype(declval<TwoInts>() ? declval<TwoInts>() : declval<int>())>);
+ static_assert(is_same_v<TwoFloats, decltype(declval<TwoInts>() ? declval<TwoFloats>() : 5)>);
+
+ // For the Binary conditional operator, the result type is either the vector on the RHS (that fits the rules on size/count), or the scalar extended to the correct count.
+ static_assert(is_same_v<TwoInts, decltype(declval<TwoInts>() ?: declval<TwoInts>())>);
+ static_assert(is_same_v<TwoInts, decltype(declval<TwoInts>() ?: declval<int>())>);
+}
+
+template <typename Cond>
+void dependent_cond(Cond C) {
+ (void)(C ? 1 : 2);
+}
+
+template <typename Operand>
+void dependent_operand(Operand C) {
+ (void)(two_ints ? 1 : C);
+ (void)(two_ints ? C : 1);
+ (void)(two_ints ? C : C);
+}
+
+template <typename Cond, typename LHS, typename RHS>
+void all_dependent(Cond C, LHS L, RHS R) {
+ (void)(C ? L : R);
+}
+
+// Check dependent cases.
+void Templates() {
+ dependent_cond(two_ints);
+ dependent_operand(two_floats);
+ // expected-error at 158 {{vector operands to the vector conditional must be the same type ('unsigned int __attribute__((ext_vector_type(4)))' (vector of 4 'unsigned int' values) and 'double __attribute__((ext_vector_type(4)))' (vector of 4 'double' values))}}}
+ all_dependent(four_ints, four_uints, four_doubles); // expected-note {{in instantiation of}}
+
+ // expected-error at 158 {{vector operands to the vector conditional must be the same type ('unsigned int __attribute__((ext_vector_type(4)))' (vector of 4 'unsigned int' values) and 'unsigned int __attribute__((ext_vector_type(2)))' (vector of 2 'unsigned int' values))}}}
+ all_dependent(four_ints, four_uints, two_uints); // expected-note {{in instantiation of}}
+ all_dependent(four_ints, four_uints, four_uints);
+}
+
+using FourShortsVS = short __attribute__((__vector_size__(8)));
+
+void mix_vector_types() {
+ FourShortsVS vs;
+ (vs == 1 ? four_shorts : four_shorts);
+ // expected-error at -1 {{cannot mix vectors and extended vectors in a vector conditional}}
+
+ (four_shorts == 1 ? vs : vs);
+ // expected-error at -1 {{cannot mix vectors and extended vectors in a vector conditional}}
+
+ (four_shorts == 1 ? four_shorts : vs);
+ // expected-error at -1 {{vector operands to the vector conditional must be the same type ('FourShorts' (vector of 4 'short' values) and 'FourShortsVS' (vector of 4 'short' values))}}
+
+ (four_shorts == 1 ? vs : four_shorts);
+ // expected-error at -1 {{cannot mix vectors and extended vectors in a vector conditional}}
+
+ (vs == 1 ? vs : four_shorts);
+ // expected-error at -1 {{vector operands to the vector conditional must be the same type ('FourShortsVS' (vector of 4 'short' values) and 'FourShorts' (vector of 4 'short' values))}}
+
+ (vs == 1 ? four_shorts : vs);
+ // expected-error at -1 {{cannot mix vectors and extended vectors in a vector conditional}}
+}
diff --git a/clang/test/SemaCXX/vector-conditional.cpp b/clang/test/SemaCXX/vector-size-conditional.cpp
similarity index 100%
rename from clang/test/SemaCXX/vector-conditional.cpp
rename to clang/test/SemaCXX/vector-size-conditional.cpp
More information about the cfe-commits
mailing list