diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -2407,6 +2407,81 @@ function whose signature must be: ``int (*)(const char *, ...)`` and must support the format specifiers used by ``printf()``. +``__builtin_reflect_struct`` +---------------------------- + +**Syntax**: + +.. code-block:: c++ + + __builtin_reflect_struct(&some_struct, some_func, args...); + +**Examples**: + +.. code-block:: c++ + + struct S { + int x, y; + int z : 4; + }; + + struct Field { + const char *name; + int value; + int bitwidth; + }; + + bool print(int arg1, int arg2, std::initializer_list fields) { + for (auto f : fields) { + std::cout << f.name; + if (f.bitwidth) + std::cout << " : " << f.bitwidth; + std::cout << " = " << f.value << std::endl; + } + return true; + } + + bool func(int arg1, int arg2) { + S s = {1, 2, 3}; + return __builtin_reflect_struct(&s, print, arg1, arg2); + } + +Example output: + +.. code-block:: none + + x = 1 + y = 2 + z : 4 = 3 + +**Description**: + +The ``__builtin_reflect_struct`` function provides simple reflection for a +class, struct, or union. The first argument of the builtin should be a pointer +to the type to reflect. The second argument ``f`` should be some callable +expression, and can be a function object or an overload set. The builtin calls +``f``, passing any further arguments ``args...`` followed by an initializer +list describing the struct value. + +.. code-block:: none + + // '__builtin_reflect_struct(&s, print, arg1, arg2)' calls: + print(arg1, arg2, {{"x", 1}, {"y", 2}, {"z", 3, 4}}) + +The initializer list contains the following components: + +* For each direct base class, the address of the base class, in declaration + order. +* For each field, ``{"field_name", p->field_name}`` for a regular field, and + ``{"field_name", p->field_name, bit_width}`` for a bit-field, in declaration + order. + +The result of the builtin is the result of the call to function ``f``. + +This builtin can be used in constant expressions. + +Query for this feature with ``__has_builtin(__builtin_reflect_struct)`` + .. _langext-__builtin_shufflevector: ``__builtin_shufflevector`` diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -68,6 +68,10 @@ Randomizing structure layout is a C-only feature. +- Clang now supports a ``__builtin_reflect_struct`` builtin. This builtin + provides a simple and flexible way to reflect on the fields and base classes + of an object of struct or class type. + Bug Fixes ------------------ - ``CXXNewExpr::getArraySize()`` previously returned a ``llvm::Optional`` diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def --- a/clang/include/clang/Basic/Builtins.def +++ b/clang/include/clang/Basic/Builtins.def @@ -1602,8 +1602,9 @@ BUILTIN(__builtin_operator_new, "v*z", "tc") BUILTIN(__builtin_operator_delete, "vv*", "tn") BUILTIN(__builtin_char_memchr, "c*cC*iz", "n") -BUILTIN(__builtin_dump_struct, "ivC*v*", "tn") +BUILTIN(__builtin_dump_struct, "ivC*v*", "t") BUILTIN(__builtin_preserve_access_index, "v.", "t") +BUILTIN(__builtin_reflect_struct, "v.", "t") // Alignment builtins (uses custom parsing to support pointers and integers) BUILTIN(__builtin_is_aligned, "bvC*z", "nct") diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8467,6 +8467,8 @@ def err_overflow_builtin_bit_int_max_size : Error< "__builtin_mul_overflow does not support 'signed _BitInt' operands of more " "than %0 bits">; +def err_expected_struct_pointer_argument : Error< + "expected pointer to struct as %ordinal0 argument to %1, found %2">; def err_atomic_load_store_uses_lib : Error< "atomic %select{load|store}0 requires runtime support that is not " diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -2457,8 +2457,12 @@ } // Objective-C++ extensions to the rule. - if (isa(E) || isa(E)) + if (isa(E)) return true; + if (const auto *POE = dyn_cast(E)) { + if (isa(POE->getSyntacticForm())) + return true; + } return false; } @@ -2708,23 +2712,35 @@ } case ObjCPropertyRefExprClass: + case ObjCSubscriptRefExprClass: WarnE = this; Loc = getExprLoc(); R1 = getSourceRange(); return true; case PseudoObjectExprClass: { - const PseudoObjectExpr *PO = cast(this); + const auto *POE = cast(this); - // Only complain about things that have the form of a getter. - if (isa(PO->getSyntacticForm()) || - isa(PO->getSyntacticForm())) - return false; + // For some syntactic forms, we should always warn. + if (isa( + POE->getSyntacticForm())) { + WarnE = this; + Loc = getExprLoc(); + R1 = getSourceRange(); + return true; + } - WarnE = this; - Loc = getExprLoc(); - R1 = getSourceRange(); - return true; + // For others, we should never warn. + if (auto *BO = dyn_cast(POE->getSyntacticForm())) + if (BO->isAssignmentOp()) + return false; + if (auto *UO = dyn_cast(POE->getSyntacticForm())) + if (UO->isIncrementDecrementOp()) + return false; + + // Otherwise, warn if the result expression would warn. + const Expr *Result = POE->getResultExpr(); + return Result && Result->isUnusedResultAWarning(WarnE, Loc, R1, R2, Ctx); } case StmtExprClass: { diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -366,6 +366,114 @@ return false; } +static ExprResult SemaBuiltinReflectStruct(Sema &S, CallExpr *TheCall) { + if (TheCall->getNumArgs() < 2 && checkArgCount(S, TheCall, 2)) + return ExprError(); + + SourceLocation Loc = TheCall->getBeginLoc(); + + // First argument should be a pointer to a struct. + ExprResult PtrArgResult = S.DefaultLvalueConversion(TheCall->getArg(0)); + if (PtrArgResult.isInvalid()) + return ExprError(); + Expr *PtrArg = PtrArgResult.get(); + QualType PtrArgType = PtrArg->getType(); + if (!PtrArgType->isPointerType() || + !PtrArgType->getPointeeType()->isRecordType()) { + S.Diag(PtrArg->getBeginLoc(), diag::err_expected_struct_pointer_argument) + << 1 << TheCall->getDirectCallee() << PtrArgType; + return ExprError(); + } + const RecordDecl *RD = PtrArgType->getPointeeType()->getAsRecordDecl(); + + // Build an OpaqueValueExpr so we can refer to the argument more than once. + auto *ArgOVE = new (S.Context) + OpaqueValueExpr(Loc, PtrArg->getType(), PtrArg->getValueKind(), + PtrArg->getObjectKind(), PtrArg); + + // Start with all the provided arguments after the callee. + SmallVector Args(TheCall->arg_begin() + 2, TheCall->arg_end()); + + SmallVector Elts; + + // Add a 'static_cast(arg)' argument for each base class. + if (const CXXRecordDecl *CXXRD = dyn_cast(RD)) { + for (const auto &Base : CXXRD->bases()) { + QualType BaseType = S.Context.getPointerType(S.Context.getQualifiedType( + Base.getType(), PtrArgType->getPointeeType().getQualifiers())); + ExprResult BasePtr = S.BuildCXXNamedCast( + Loc, tok::kw_static_cast, + S.Context.getTrivialTypeSourceInfo(BaseType, Loc), ArgOVE, + SourceRange(Loc, Loc), SourceRange(Loc, Loc)); + if (BasePtr.isInvalid()) + return ExprError(); + Elts.push_back(BasePtr.get()); + } + } + + // Add a '{"field_name", arg->field_name}' argument for each field. + // Add a '{"field_name", arg->field_name, bitwidth}' argument for each + // bit-field. + for (auto *D : RD->decls()) { + auto *IFD = dyn_cast(D); + auto *FD = IFD ? IFD->getAnonField() : dyn_cast(D); + if (!FD || (FD->isUnnamedBitfield() || FD->isAnonymousStructOrUnion())) + continue; + + Expr *Name = S.Context.getPredefinedStringLiteralFromCache(FD->getName()); + + // FIXME: Access check? + ExprResult Field = + IFD ? S.BuildAnonymousStructUnionMemberReference( + CXXScopeSpec(), Loc, IFD, + DeclAccessPair::make(IFD, IFD->getAccess()), ArgOVE, Loc) + : S.BuildFieldReferenceExpr( + ArgOVE, /*IsArrow=*/true, Loc, CXXScopeSpec(), FD, + DeclAccessPair::make(FD, FD->getAccess()), + DeclarationNameInfo(FD->getDeclName(), Loc)); + if (Field.isInvalid()) + return ExprError(); + + Expr *Width = nullptr; + if (FD->isBitField()) { + QualType SizeT = S.Context.getSizeType(); + llvm::APInt BitWidth(S.Context.getIntWidth(SizeT), + FD->getBitWidthValue(S.Context)); + Width = IntegerLiteral::Create(S.Context, BitWidth, SizeT, Loc); + } + + Expr *Parts[] = {Name, Field.get(), Width}; + MultiExprArg PartList = Parts; + if (!Width) + PartList = PartList.drop_back(1); + + ExprResult InitList = S.BuildInitList(Loc, PartList, Loc); + if (InitList.isInvalid()) + return ExprError(); + + Elts.push_back(InitList.get()); + } + + // Wrap all the reflection data in '{'...'}'. + ExprResult ReflectionData = S.BuildInitList(Loc, Elts, Loc); + Args.push_back(ReflectionData.get()); + + // Call the second argument with the assembled argument list. + ExprResult RealCall = + S.BuildCallExpr(/*Scope=*/nullptr, TheCall->getArg(1), + TheCall->getBeginLoc(), Args, TheCall->getRParenLoc()); + if (RealCall.isInvalid()) + return ExprError(); + + // The result of the real call is the result of the builtin call. + TheCall->setType(RealCall.get()->getType()); + TheCall->setValueKind(RealCall.get()->getValueKind()); + + Expr *SemanticForm[] = {ArgOVE, RealCall.get()}; + return PseudoObjectExpr::Create(S.Context, TheCall, SemanticForm, + /*ResultIdx*/ 1); +} + static bool SemaBuiltinCallWithStaticChain(Sema &S, CallExpr *BuiltinCall) { if (checkArgCount(S, BuiltinCall, 2)) return true; @@ -2022,9 +2130,8 @@ const QualType PtrArgType = PtrArg->getType(); if (!PtrArgType->isPointerType() || !PtrArgType->getPointeeType()->isRecordType()) { - Diag(PtrArg->getBeginLoc(), diag::err_typecheck_convert_incompatible) - << PtrArgType << "structure pointer" << 1 << 0 << 3 << 1 << PtrArgType - << "structure pointer"; + Diag(PtrArg->getBeginLoc(), diag::err_expected_struct_pointer_argument) + << 1 << FDecl << TheCall->getArg(0)->getType(); return ExprError(); } @@ -2069,6 +2176,8 @@ TheCall->setType(Context.IntTy); break; } + case Builtin::BI__builtin_reflect_struct: + return SemaBuiltinReflectStruct(*this, TheCall); case Builtin::BI__builtin_expect_with_probability: { // We first want to ensure we are called with 3 arguments if (checkArgCount(*this, TheCall, 3)) diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -341,7 +341,7 @@ return DiagnoseUnusedExprResult(POE->getSemanticExpr(0), DiagID); if (isa(Source)) DiagID = diag::warn_unused_container_subscript_expr; - else + else if (isa(Source)) DiagID = diag::warn_unused_property_expr; } else if (const CXXFunctionalCastExpr *FC = dyn_cast(E)) { diff --git a/clang/test/CodeGenCXX/builtin-reflect-struct.cpp b/clang/test/CodeGenCXX/builtin-reflect-struct.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/builtin-reflect-struct.cpp @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-linux-gnu %s -emit-llvm -o - | FileCheck %s + +// CHECK: @[[F_STR:.*]] = {{.*}} constant [2 x i8] c"f\00", +// CHECK: @[[G_STR:.*]] = {{.*}} constant [2 x i8] c"g\00", + +struct A { int n; }; +struct B { int n; }; +struct C : A, B { int f, g; }; + +struct Base { + Base(void*); + void *p; +}; +struct Field { + Field(const char*, int&); + int n; +}; + +struct Expected { + Base a, b; + Field f, g; +}; + +int consume(int, Expected); + +C c; + +// CHECK: %[[VAL:.*]] = alloca %[[STRUCT_EXPECTED:.*]], +// CHECK: call void @_ZN4BaseC1EPv(ptr {{.*}}, ptr {{.*}} @c) +// CHECK: %[[BASE_B:.*]] = {{.*}} getelementptr inbounds (i8, ptr @c, i64 4) +// CHECK: call void @_ZN4BaseC1EPv(ptr {{.*}}, ptr {{.*}} %[[BASE_B]]) +// CHECK: call void @_ZN5FieldC1EPKcRi(ptr {{.*}}, ptr {{.*}} @[[F_STR]], ptr {{.*}} getelementptr inbounds (%struct.C, ptr @c, i32 0, i32 2)) +// CHECK: call void @_ZN5FieldC1EPKcRi(ptr {{.*}}, ptr {{.*}} @[[G_STR]], ptr {{.*}} getelementptr inbounds (%struct.C, ptr @c, i32 0, i32 3)) +// CHECK: %[[RET:.*]] = call {{.*}} i32 @_Z7consumei8Expected(i32 {{.*}} 42, ptr noundef byval(%[[STRUCT_EXPECTED]]) {{.*}} %[[VAL]]) +// CHECK: store i32 %[[RET]], ptr @k +int k = __builtin_reflect_struct(&c, consume, 42); + +// A discarded volatile lvalue produced by the builtin should not be loaded +// from. +struct Empty {} empty; +volatile int &volatile_return(int); +// CHECK-LABEL: define {{.*}}check_volatile +void check_volatile() { + // CHECK: call {{.*}}volatile_return + // CHECK-NOT: load volatile + // CHECK: } + __builtin_reflect_struct(&empty, volatile_return); +} diff --git a/clang/test/Sema/builtin-dump-struct.c b/clang/test/Sema/builtin-dump-struct.c --- a/clang/test/Sema/builtin-dump-struct.c +++ b/clang/test/Sema/builtin-dump-struct.c @@ -14,15 +14,15 @@ __builtin_dump_struct(); // expected-error {{too few arguments to function call, expected 2, have 0}} __builtin_dump_struct(1); // expected-error {{too few arguments to function call, expected 2, have 1}} - __builtin_dump_struct(1, 2); // expected-error {{passing 'int' to parameter of incompatible type structure pointer: type mismatch at 1st parameter ('int' vs structure pointer)}} + __builtin_dump_struct(1, 2); // expected-error {{expected pointer to struct as 1st argument to '__builtin_dump_struct', found 'int'}} __builtin_dump_struct(&a, 2); // expected-error {{passing 'int' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('int' vs 'int (*)(const char *, ...)')}} - __builtin_dump_struct(b, goodfunc); // expected-error {{passing 'void *' to parameter of incompatible type structure pointer: type mismatch at 1st parameter ('void *' vs structure pointer)}} + __builtin_dump_struct(b, goodfunc); // expected-error {{expected pointer to struct as 1st argument to '__builtin_dump_struct', found 'void *'}} __builtin_dump_struct(&a, badfunc1); // expected-error {{passing 'int (*)(const char *)' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('int (*)(const char *)' vs 'int (*)(const char *, ...)')}} __builtin_dump_struct(&a, badfunc2); // expected-error {{passing 'int (*)(int, ...)' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('int (*)(int, ...)' vs 'int (*)(const char *, ...)')}} __builtin_dump_struct(&a, badfunc3); // expected-error {{passing 'void (*)(const char *, ...)' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('void (*)(const char *, ...)' vs 'int (*)(const char *, ...)')}} __builtin_dump_struct(&a, badfunc4); // expected-error {{passing 'int (*)(char *, ...)' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('int (*)(char *, ...)' vs 'int (*)(const char *, ...)')}} __builtin_dump_struct(&a, badfunc5); // expected-error {{passing 'int (*)(void)' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('int (*)(void)' vs 'int (*)(const char *, ...)')}} - __builtin_dump_struct(a, goodfunc); // expected-error {{passing 'struct A' to parameter of incompatible type structure pointer: type mismatch at 1st parameter ('struct A' vs structure pointer)}} + __builtin_dump_struct(a, goodfunc); // expected-error {{expected pointer to struct as 1st argument to '__builtin_dump_struct', found 'struct A'}} } void valid_uses(void) { diff --git a/clang/test/SemaCXX/builtin-reflect-struct.cpp b/clang/test/SemaCXX/builtin-reflect-struct.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/builtin-reflect-struct.cpp @@ -0,0 +1,119 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s + +namespace std { + typedef decltype(sizeof(int)) size_t; + + template struct initializer_list { + const E *data; + size_t size; + + constexpr initializer_list(const E *data, size_t size) + : data(data), size(size) {} + constexpr initializer_list() : data(), size() {} + + constexpr const E *begin() const { return data; } + constexpr const E *end() const { return data + size; } + }; +} + +struct ConstexprString { + constexpr ConstexprString(const char *p) : data(nullptr) { + auto size = __builtin_strlen(p) + 1; + data = new char[size]; + __builtin_memcpy(data, p, size); + } + constexpr explicit ConstexprString(const char *p, const char *q) : data(nullptr) { + auto p_size = __builtin_strlen(p); + auto q_size = __builtin_strlen(q); + data = new char[p_size + q_size + 1]; + __builtin_memcpy(data, p, p_size); + __builtin_memcpy(data + p_size, q, q_size + 1); + } + constexpr ConstexprString(ConstexprString &&o) : data(o.data) { o.data = nullptr; } + constexpr ConstexprString &operator=(ConstexprString &&o) { + delete[] data; + data = o.data; + o.data = nullptr; + return *this; + } + constexpr ~ConstexprString() { delete[] data; } + char *data; + + friend constexpr ConstexprString operator+(const ConstexprString &a, const ConstexprString &b) { + return ConstexprString(a.data, b.data); + } + friend constexpr bool operator==(const ConstexprString &a, const ConstexprString &b) { + return __builtin_strcmp(a.data, b.data) == 0; + } +}; + +template constexpr ConstexprString ToString(const T &t); + +struct FieldOrBase { + template + constexpr FieldOrBase(const Base *base) : repr(ToString(*base)) {} + template + constexpr FieldOrBase(const char *name, const T &field) + : repr(name + ConstexprString(" = ") + ToString(field)) {} + template + constexpr FieldOrBase(const char *name, T field, int bitwidth) + : repr(name + ConstexprString(" : ") + ToString(bitwidth) + + ConstexprString(" = ") + ToString(field)) {} + ConstexprString repr; +}; + +constexpr ConstexprString FieldsToString(std::initializer_list elements) { // expected-note {{here}} + ConstexprString result = "{"; + bool first = true; + for (const auto &element : elements) { + if (!first) + result = result + ", "; + result = result + element.repr; + first = false; + } + return result + "}"; +} + +template constexpr ConstexprString ToString(const T &t) { + if constexpr (__is_class(T)) { + return __builtin_reflect_struct(&t, FieldsToString); + } else { + // Assume it's an integer. + T value = t; + ConstexprString s = ""; + bool negative = false; + if (value < 0) { + value = -value; + negative = true; + } + while (value > 0) { + char str[2] = {char('0' + value % 10), '\0'}; + s = ConstexprString(str) + s; + value /= 10; + } + if (negative) + s = "-" + s; + return s; + } +} + +struct A { int x, y, z : 3; int : 4; }; +struct B : A { int p, q; struct { int anon1, anon2; }; union { int anon3; }; }; + +static_assert(ToString(B{1, 2, 3, 4, 5, 6, 7, 8}) == "{{x = 1, y = 2, z : 3 = 3}, p = 4, q = 5, anon1 = 6, anon2 = 7, anon3 = 8}"); + +void errors(B b) { + __builtin_reflect_struct(); // expected-error {{too few arguments to function call, expected 2, have 0}} + __builtin_reflect_struct(1); // expected-error {{too few arguments to function call, expected 2, have 1}} + __builtin_reflect_struct(1, 2); // expected-error {{expected pointer to struct as 1st argument to '__builtin_reflect_struct', found 'int'}} + __builtin_reflect_struct(&b, 2); // expected-error {{called object type 'int' is not a function or function pointer}} + __builtin_reflect_struct(&b, FieldsToString, 0); // expected-error {{too many arguments to function call, expected single argument 'elements', have 2 arguments}} +} + +[[nodiscard]] int nodiscard(int); +int yesdiscard(int); +void test_discard() { + struct Empty {} empty; + __builtin_reflect_struct(&empty, nodiscard); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + __builtin_reflect_struct(&empty, yesdiscard); +}