rsmith created this revision.
rsmith added reviewers: aaron.ballman, erichkeane, yihanaa.
Herald added a project: All.
rsmith requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

This builtin, like __builtin_dump_struct, allows for limited
introspection into a struct value. Unlike __builtin_dump_struct, it
makes no assumption that you want to pass the value to printf, and
instead provides a general interface that can be used for any kind of
introspection.

This builtin is supported during constant evaluation. It's also
permitted in C, but not particularly usable there.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D124221

Files:
  clang/docs/LanguageExtensions.rst
  clang/docs/ReleaseNotes.rst
  clang/include/clang/Basic/Builtins.def
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/lib/AST/Expr.cpp
  clang/lib/Sema/SemaChecking.cpp
  clang/lib/Sema/SemaStmt.cpp
  clang/test/CodeGenCXX/builtin-reflect-struct.cpp
  clang/test/SemaCXX/builtin-reflect-struct.cpp

Index: clang/test/SemaCXX/builtin-reflect-struct.cpp
===================================================================
--- /dev/null
+++ clang/test/SemaCXX/builtin-reflect-struct.cpp
@@ -0,0 +1,104 @@
+// RUN: %clang_cc1 -std=c++20 -verify %s
+// expected-no-diagnostics
+
+namespace std {
+  typedef decltype(sizeof(int)) size_t;
+
+  template <class E> 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<typename T> constexpr ConstexprString ToString(const T &t);
+
+struct FieldOrBase {
+  template <typename Base>
+  constexpr FieldOrBase(const Base *base) : repr(ToString(*base)) {}
+  template <typename T>
+  constexpr FieldOrBase(const char *name, const T &field)
+      : repr(name + ConstexprString(" = ") + ToString(field)) {}
+  template <typename T>
+  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<FieldOrBase> elements) {
+  ConstexprString result = "{";
+  bool first = true;
+  for (const auto &element : elements) {
+    if (!first)
+      result = result + ", ";
+    result = result + element.repr;
+    first = false;
+  }
+  return result + "}";
+}
+
+template<typename T> 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}");
Index: clang/test/CodeGenCXX/builtin-reflect-struct.cpp
===================================================================
--- /dev/null
+++ clang/test/CodeGenCXX/builtin-reflect-struct.cpp
@@ -0,0 +1,36 @@
+// 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);
Index: clang/lib/Sema/SemaStmt.cpp
===================================================================
--- clang/lib/Sema/SemaStmt.cpp
+++ clang/lib/Sema/SemaStmt.cpp
@@ -341,7 +341,7 @@
       return DiagnoseUnusedExprResult(POE->getSemanticExpr(0), DiagID);
     if (isa<ObjCSubscriptRefExpr>(Source))
       DiagID = diag::warn_unused_container_subscript_expr;
-    else
+    else if (isa<ObjCPropertyRefExpr>(Source))
       DiagID = diag::warn_unused_property_expr;
   } else if (const CXXFunctionalCastExpr *FC
                                        = dyn_cast<CXXFunctionalCastExpr>(E)) {
Index: clang/lib/Sema/SemaChecking.cpp
===================================================================
--- clang/lib/Sema/SemaChecking.cpp
+++ 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<Expr *, 8> Args(TheCall->arg_begin() + 2, TheCall->arg_end());
+
+  SmallVector<Expr *, 32> Elts;
+
+  // Add a 'static_cast<Base*>(arg)' argument for each base class.
+  if (const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(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<IndirectFieldDecl>(D);
+    auto *FD = IFD ? IFD->getAnonField() : dyn_cast<FieldDecl>(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))
Index: clang/lib/AST/Expr.cpp
===================================================================
--- clang/lib/AST/Expr.cpp
+++ clang/lib/AST/Expr.cpp
@@ -2708,24 +2708,19 @@
   }
 
   case ObjCPropertyRefExprClass:
+  case ObjCSubscriptRefExprClass:
     WarnE = this;
     Loc = getExprLoc();
     R1 = getSourceRange();
     return true;
 
-  case PseudoObjectExprClass: {
-    const PseudoObjectExpr *PO = cast<PseudoObjectExpr>(this);
-
-    // Only complain about things that have the form of a getter.
-    if (isa<UnaryOperator>(PO->getSyntacticForm()) ||
-        isa<BinaryOperator>(PO->getSyntacticForm()))
-      return false;
-
-    WarnE = this;
-    Loc = getExprLoc();
-    R1 = getSourceRange();
-    return true;
-  }
+  case PseudoObjectExprClass:
+    // Ask the syntactic form whether to warn.
+    // FIXME: Consider looking at the result expression in the semantic form
+    // instead.
+    return cast<PseudoObjectExpr>(this)
+        ->getSyntacticForm()
+        ->isUnusedResultAWarning(WarnE, Loc, R1, R2, Ctx);
 
   case StmtExprClass: {
     // Statement exprs don't logically have side effects themselves, but are
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ 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 "
Index: clang/include/clang/Basic/Builtins.def
===================================================================
--- clang/include/clang/Basic/Builtins.def
+++ 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")
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ 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``
Index: clang/docs/LanguageExtensions.rst
===================================================================
--- clang/docs/LanguageExtensions.rst
+++ 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<Field> 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``
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D124221: ... Richard Smith - zygoloid via Phabricator via cfe-commits

Reply via email to