Skip to content

Commit

Permalink
Rework __builtin_classify_type support to better match GCC and to not…
Browse files Browse the repository at this point in the history
… assert on

unusual types.

Following the observed behavior of GCC, we now return -1 for vector types
(along with all of our extensions that GCC doesn't support), and for atomic
types we classify the underlying type.

GCC appears to have changed its classification for function and array arguments
between version 5 and version 6. Previously it would classify them as pointers
in C and as functions or arrays in C++, but from version 6 onwards, it
classifies them as pointers. We now follow the more recent GCC behavior rather
than emulating what I can only assume to be a historical bug in their C++
support for this builtin.

Finally, no version of GCC that I can find has ever used the "method"
classification for C++ pointers to member functions. Instead, GCC classifies
them as record types, presumably reflecting an internal implementation detail,
but whatever the reason we now produce compatible results.

llvm-svn: 333126
  • Loading branch information
zygoloid committed May 23, 2018
1 parent 13229af commit 08b682b
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 86 deletions.
176 changes: 96 additions & 80 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7277,30 +7277,43 @@ bool IntExprEvaluator::CheckReferencedDecl(const Expr* E, const Decl* D) {
return false;
}

/// Values returned by __builtin_classify_type, chosen to match the values
/// produced by GCC's builtin.
enum class GCCTypeClass {
None = -1,
Void = 0,
Integer = 1,
// GCC reserves 2 for character types, but instead classifies them as
// integers.
Enum = 3,
Bool = 4,
Pointer = 5,
// GCC reserves 6 for references, but appears to never use it (because
// expressions never have reference type, presumably).
PointerToDataMember = 7,
RealFloat = 8,
Complex = 9,
// GCC reserves 10 for functions, but does not use it since GCC version 6 due
// to decay to pointer. (Prior to version 6 it was only used in C++ mode).
// GCC claims to reserve 11 for pointers to member functions, but *actually*
// uses 12 for that purpose, same as for a class or struct. Maybe it
// internally implements a pointer to member as a struct? Who knows.
PointerToMemberFunction = 12, // Not a bug, see above.
ClassOrStruct = 12,
Union = 13,
// GCC reserves 14 for arrays, but does not use it since GCC version 6 due to
// decay to pointer. (Prior to version 6 it was only used in C++ mode).
// GCC reserves 15 for strings, but actually uses 5 (pointer) for string
// literals.
};

/// EvaluateBuiltinClassifyType - Evaluate __builtin_classify_type the same way
/// as GCC.
static int EvaluateBuiltinClassifyType(const CallExpr *E,
const LangOptions &LangOpts) {
// The following enum mimics the values returned by GCC.
// FIXME: Does GCC differ between lvalue and rvalue references here?
enum gcc_type_class {
no_type_class = -1,
void_type_class, integer_type_class, char_type_class,
enumeral_type_class, boolean_type_class,
pointer_type_class, reference_type_class, offset_type_class,
real_type_class, complex_type_class,
function_type_class, method_type_class,
record_type_class, union_type_class,
array_type_class, string_type_class,
lang_type_class
};
static GCCTypeClass
EvaluateBuiltinClassifyType(QualType T, const LangOptions &LangOpts) {
assert(!T->isDependentType() && "unexpected dependent type");

// If no argument was supplied, default to "no_type_class". This isn't
// ideal, however it is what gcc does.
if (E->getNumArgs() == 0)
return no_type_class;

QualType CanTy = E->getArg(0)->getType().getCanonicalType();
QualType CanTy = T.getCanonicalType();
const BuiltinType *BT = dyn_cast<BuiltinType>(CanTy);

switch (CanTy->getTypeClass()) {
Expand All @@ -7309,37 +7322,41 @@ static int EvaluateBuiltinClassifyType(const CallExpr *E,
#define NON_CANONICAL_TYPE(ID, BASE) case Type::ID:
#define NON_CANONICAL_UNLESS_DEPENDENT_TYPE(ID, BASE) case Type::ID:
#include "clang/AST/TypeNodes.def"
llvm_unreachable("CallExpr::isBuiltinClassifyType(): unimplemented type");
case Type::Auto:
case Type::DeducedTemplateSpecialization:
llvm_unreachable("unexpected non-canonical or dependent type");

case Type::Builtin:
switch (BT->getKind()) {
#define BUILTIN_TYPE(ID, SINGLETON_ID)
#define SIGNED_TYPE(ID, SINGLETON_ID) case BuiltinType::ID: return integer_type_class;
#define FLOATING_TYPE(ID, SINGLETON_ID) case BuiltinType::ID: return real_type_class;
#define PLACEHOLDER_TYPE(ID, SINGLETON_ID) case BuiltinType::ID: break;
#define SIGNED_TYPE(ID, SINGLETON_ID) \
case BuiltinType::ID: return GCCTypeClass::Integer;
#define FLOATING_TYPE(ID, SINGLETON_ID) \
case BuiltinType::ID: return GCCTypeClass::RealFloat;
#define PLACEHOLDER_TYPE(ID, SINGLETON_ID) \
case BuiltinType::ID: break;
#include "clang/AST/BuiltinTypes.def"
case BuiltinType::Void:
return void_type_class;
return GCCTypeClass::Void;

case BuiltinType::Bool:
return boolean_type_class;
return GCCTypeClass::Bool;

case BuiltinType::Char_U: // gcc doesn't appear to use char_type_class
case BuiltinType::Char_U:
case BuiltinType::UChar:
case BuiltinType::WChar_U:
case BuiltinType::Char8:
case BuiltinType::Char16:
case BuiltinType::Char32:
case BuiltinType::UShort:
case BuiltinType::UInt:
case BuiltinType::ULong:
case BuiltinType::ULongLong:
case BuiltinType::UInt128:
return integer_type_class;
return GCCTypeClass::Integer;

case BuiltinType::NullPtr:
return pointer_type_class;

case BuiltinType::WChar_U:
case BuiltinType::Char8:
case BuiltinType::Char16:
case BuiltinType::Char32:
case BuiltinType::ObjCId:
case BuiltinType::ObjCClass:
case BuiltinType::ObjCSel:
Expand All @@ -7351,74 +7368,73 @@ static int EvaluateBuiltinClassifyType(const CallExpr *E,
case BuiltinType::OCLClkEvent:
case BuiltinType::OCLQueue:
case BuiltinType::OCLReserveID:
return GCCTypeClass::None;

case BuiltinType::Dependent:
llvm_unreachable("CallExpr::isBuiltinClassifyType(): unimplemented type");
llvm_unreachable("unexpected dependent type");
};
break;
llvm_unreachable("unexpected placeholder type");

case Type::Enum:
return LangOpts.CPlusPlus ? enumeral_type_class : integer_type_class;
break;
return LangOpts.CPlusPlus ? GCCTypeClass::Enum : GCCTypeClass::Integer;

case Type::Pointer:
return pointer_type_class;
break;
case Type::ConstantArray:
case Type::VariableArray:
case Type::IncompleteArray:
case Type::FunctionNoProto:
case Type::FunctionProto:
return GCCTypeClass::Pointer;

case Type::MemberPointer:
if (CanTy->isMemberDataPointerType())
return offset_type_class;
else {
// We expect member pointers to be either data or function pointers,
// nothing else.
assert(CanTy->isMemberFunctionPointerType());
return method_type_class;
}
return CanTy->isMemberDataPointerType()
? GCCTypeClass::PointerToDataMember
: GCCTypeClass::PointerToMemberFunction;

case Type::Complex:
return complex_type_class;

case Type::FunctionNoProto:
case Type::FunctionProto:
return LangOpts.CPlusPlus ? function_type_class : pointer_type_class;
return GCCTypeClass::Complex;

case Type::Record:
if (const RecordType *RT = CanTy->getAs<RecordType>()) {
switch (RT->getDecl()->getTagKind()) {
case TagTypeKind::TTK_Struct:
case TagTypeKind::TTK_Class:
case TagTypeKind::TTK_Interface:
return record_type_class;

case TagTypeKind::TTK_Enum:
return LangOpts.CPlusPlus ? enumeral_type_class : integer_type_class;

case TagTypeKind::TTK_Union:
return union_type_class;
}
}
llvm_unreachable("CallExpr::isBuiltinClassifyType(): unimplemented type");
return CanTy->isUnionType() ? GCCTypeClass::Union
: GCCTypeClass::ClassOrStruct;

case Type::ConstantArray:
case Type::VariableArray:
case Type::IncompleteArray:
return LangOpts.CPlusPlus ? array_type_class : pointer_type_class;
case Type::Atomic:
// GCC classifies _Atomic T the same as T.
return EvaluateBuiltinClassifyType(
CanTy->castAs<AtomicType>()->getValueType(), LangOpts);

case Type::BlockPointer:
case Type::LValueReference:
case Type::RValueReference:
case Type::Vector:
case Type::ExtVector:
case Type::Auto:
case Type::DeducedTemplateSpecialization:
case Type::ObjCObject:
case Type::ObjCInterface:
case Type::ObjCObjectPointer:
case Type::Pipe:
case Type::Atomic:
llvm_unreachable("CallExpr::isBuiltinClassifyType(): unimplemented type");
// GCC classifies vectors as None. We follow its lead and classify all
// other types that don't fit into the regular classification the same way.
return GCCTypeClass::None;

case Type::LValueReference:
case Type::RValueReference:
llvm_unreachable("invalid type for expression");
}

llvm_unreachable("CallExpr::isBuiltinClassifyType(): unimplemented type");
llvm_unreachable("unexpected type class");
}

/// EvaluateBuiltinClassifyType - Evaluate __builtin_classify_type the same way
/// as GCC.
static GCCTypeClass
EvaluateBuiltinClassifyType(const CallExpr *E, const LangOptions &LangOpts) {
// If no argument was supplied, default to None. This isn't
// ideal, however it is what gcc does.
if (E->getNumArgs() == 0)
return GCCTypeClass::None;

// FIXME: Bizarrely, GCC treats a call with more than one argument as not
// being an ICE, but still folds it to a constant using the type of the first
// argument.
return EvaluateBuiltinClassifyType(E->getArg(0)->getType(), LangOpts);
}

/// EvaluateBuiltinConstantPForLValue - Determine the result of
Expand Down Expand Up @@ -7842,7 +7858,7 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
}

case Builtin::BI__builtin_classify_type:
return Success(EvaluateBuiltinClassifyType(E, Info.getLangOpts()), E);
return Success((int)EvaluateBuiltinClassifyType(E, Info.getLangOpts()), E);

// FIXME: BI__builtin_clrsb
// FIXME: BI__builtin_clrsbl
Expand Down
19 changes: 18 additions & 1 deletion clang/test/Sema/builtin-classify-type.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
// RUN: %clang_cc1 -fsyntax-only -verify %s -fblocks

// expected-no-diagnostics

Expand All @@ -25,6 +25,14 @@ void foo() {
struct { int a; float b; } s_obj;
union { int a; float b; } u_obj;
int arr[10];
int (^block)();
__attribute__((vector_size(16))) int vec;
typedef __attribute__((ext_vector_type(4))) int evec_t;
evec_t evec;
_Atomic int atomic_i;
_Atomic double atomic_d;
_Complex int complex_i;
_Complex double complex_d;

int a1[__builtin_classify_type(f()) == void_type_class ? 1 : -1];
int a2[__builtin_classify_type(i) == integer_type_class ? 1 : -1];
Expand All @@ -38,5 +46,14 @@ void foo() {
int a10[__builtin_classify_type(u_obj) == union_type_class ? 1 : -1];
int a11[__builtin_classify_type(arr) == pointer_type_class ? 1 : -1];
int a12[__builtin_classify_type("abc") == pointer_type_class ? 1 : -1];
int a13[__builtin_classify_type(block) == no_type_class ? 1 : -1];
int a14[__builtin_classify_type(vec) == no_type_class ? 1 : -1];
int a15[__builtin_classify_type(evec) == no_type_class ? 1 : -1];
int a16[__builtin_classify_type(atomic_i) == integer_type_class ? 1 : -1];
int a17[__builtin_classify_type(atomic_d) == real_type_class ? 1 : -1];
int a18[__builtin_classify_type(complex_i) == complex_type_class ? 1 : -1];
int a19[__builtin_classify_type(complex_d) == complex_type_class ? 1 : -1];
}

extern int (^p)();
int n = __builtin_classify_type(p);
25 changes: 20 additions & 5 deletions clang/test/SemaCXX/builtin-classify-type.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
// RUN: %clang_cc1 -fsyntax-only -verify -fblocks %s

// expected-no-diagnostics

Expand Down Expand Up @@ -34,6 +34,14 @@ void foo() {
cl cl_obj;
union { int a; float b; } u_obj;
int arr[10];
int (^block)();
__attribute__((vector_size(16))) int vec;
typedef __attribute__((ext_vector_type(4))) int evec_t;
evec_t evec;
_Atomic int atomic_i;
_Atomic double atomic_d;
_Complex int complex_i;
_Complex double complex_d;

int a1[__builtin_classify_type(f()) == void_type_class ? 1 : -1];
int a2[__builtin_classify_type(i) == integer_type_class ? 1 : -1];
Expand All @@ -44,11 +52,18 @@ void foo() {
int a7[__builtin_classify_type(r) == integer_type_class ? 1 : -1];
int a8[__builtin_classify_type(&cl::baz) == offset_type_class ? 1 : -1];
int a9[__builtin_classify_type(d) == real_type_class ? 1 : -1];
int a10[__builtin_classify_type(f) == function_type_class ? 1 : -1];
int a11[__builtin_classify_type(&cl::bar) == method_type_class ? 1 : -1];
int a10[__builtin_classify_type(f) == pointer_type_class ? 1 : -1];
int a11[__builtin_classify_type(&cl::bar) == record_type_class ? 1 : -1];
int a12[__builtin_classify_type(cl_obj) == record_type_class ? 1 : -1];
int a13[__builtin_classify_type(u_obj) == union_type_class ? 1 : -1];
int a14[__builtin_classify_type(arr) == array_type_class ? 1 : -1];
int a15[__builtin_classify_type("abc") == array_type_class ? 1 : -1];
int a14[__builtin_classify_type(arr) == pointer_type_class ? 1 : -1];
int a15[__builtin_classify_type("abc") == pointer_type_class ? 1 : -1];
int a16[__builtin_classify_type(block) == no_type_class ? 1 : -1];
int a17[__builtin_classify_type(vec) == no_type_class ? 1 : -1];
int a18[__builtin_classify_type(evec) == no_type_class ? 1 : -1];
int a19[__builtin_classify_type(atomic_i) == integer_type_class ? 1 : -1];
int a20[__builtin_classify_type(atomic_d) == real_type_class ? 1 : -1];
int a21[__builtin_classify_type(complex_i) == complex_type_class ? 1 : -1];
int a22[__builtin_classify_type(complex_d) == complex_type_class ? 1 : -1];
}

0 comments on commit 08b682b

Please sign in to comment.