[clang] c4f95ef - Reimplement `__builtin_dump_struct` in Sema.

Richard Smith via cfe-commits cfe-commits at lists.llvm.org
Thu May 5 14:55:56 PDT 2022


Author: Richard Smith
Date: 2022-05-05T14:55:47-07:00
New Revision: c4f95ef86a224fe730d2219aab90e88a0e7b03d2

URL: https://github.com/llvm/llvm-project/commit/c4f95ef86a224fe730d2219aab90e88a0e7b03d2
DIFF: https://github.com/llvm/llvm-project/commit/c4f95ef86a224fe730d2219aab90e88a0e7b03d2.diff

LOG: Reimplement `__builtin_dump_struct` in Sema.

Compared to the old implementation:

* In C++, we only recurse into aggregate classes.
* Unnamed bit-fields are not printed.
* Constant evaluation is supported.
* Proper conversion is done when passing arguments through `...`.
* Additional arguments are supported and are injected prior to the
  format string; this directly supports use with `fprintf`, for example.
* An arbitrary callable can be passed rather than only a function
  pointer. In particular, in C++, a function template or overload set is
  acceptable.
* All text generated by Clang is printed via `%s` rather than directly;
  this avoids issues where Clang's pretty-printing output might itself
  contain a `%` character.
* Fields of types that we don't know how to print are printed with a
  `"*%p"` format and passed by address to the print function.
* No return value is produced.

Reviewed By: aaron.ballman, erichkeane, yihanaa

Differential Revision: https://reviews.llvm.org/D124221

Added: 
    clang/test/CodeGen/builtin-dump-struct.c
    clang/test/CodeGenCXX/builtin-dump-struct.cpp
    clang/test/SemaCXX/builtin-dump-struct.cpp

Modified: 
    clang/docs/LanguageExtensions.rst
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Basic/Builtins.def
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Sema/Sema.h
    clang/lib/CodeGen/CGBuiltin.cpp
    clang/lib/Frontend/FrontendActions.cpp
    clang/lib/Sema/SemaChecking.cpp
    clang/lib/Sema/SemaTemplateInstantiate.cpp
    clang/test/Sema/builtin-dump-struct.c

Removed: 
    clang/test/CodeGen/dump-struct-builtin.c


################################################################################
diff  --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 426ca4aa24dc..bc90f9cf7480 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -2373,44 +2373,82 @@ controlled state.
 
 .. code-block:: c++
 
-     __builtin_dump_struct(&some_struct, &some_printf_func);
+    __builtin_dump_struct(&some_struct, some_printf_func, args...);
 
 **Examples**:
 
 .. code-block:: c++
 
-     struct S {
-       int x, y;
-       float f;
-       struct T {
-         int i;
-       } t;
-     };
+    struct S {
+      int x, y;
+      float f;
+      struct T {
+        int i;
+      } t;
+    };
 
-     void func(struct S *s) {
-       __builtin_dump_struct(s, &printf);
-     }
+    void func(struct S *s) {
+      __builtin_dump_struct(s, printf);
+    }
 
 Example output:
 
 .. code-block:: none
 
-     struct S {
-     int i : 100
-     int j : 42
-     float f : 3.14159
-     struct T t : struct T {
-         int i : 1997
-         }
-     }
+    struct S {
+      int x = 100
+      int y = 42
+      float f = 3.141593
+      struct T t = {
+        int i = 1997
+      }
+    }
+
+.. code-block:: c++
+
+    #include <string>
+    struct T { int a, b; };
+    constexpr void constexpr_sprintf(std::string &out, const char *format,
+                                     auto ...args) {
+      // ...
+    }
+    constexpr std::string dump_struct(auto &x) {
+      std::string s;
+      __builtin_dump_struct(&x, constexpr_sprintf, s);
+      return s;
+    }
+    static_assert(dump_struct(T{1, 2}) == R"(struct T {
+      int a = 1
+      int b = 2
+    }
+    )");
 
 **Description**:
 
-The '``__builtin_dump_struct``' function is used to print the fields of a simple
-structure and their values for debugging purposes. The builtin accepts a pointer
-to a structure to dump the fields of, and a pointer to a formatted output
-function whose signature must be: ``int (*)(const char *, ...)`` and must
-support the format specifiers used by ``printf()``.
+The ``__builtin_dump_struct`` function is used to print the fields of a simple
+structure and their values for debugging purposes. The first argument of the
+builtin should be a pointer to the struct to dump. 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 a ``printf``-compatible format string and the corresponding
+arguments. ``f`` may be called more than once, and ``f`` and ``args`` will be
+evaluated once per call. In C++, ``f`` may be a template or overload set and
+resolve to 
diff erent functions for each call.
+
+In the format string, a suitable format specifier will be used for builtin
+types that Clang knows how to format. This includes standard builtin types, as
+well as aggregate structures, ``void*`` (printed with ``%p``), and ``const
+char*`` (printed with ``%s``). A ``*%p`` specifier will be used for a field
+that Clang doesn't know how to format, and the corresopnding argument will be a
+pointer to the field. This allows a C++ templated formatting function to detect
+this case and implement custom formatting. A ``*`` will otherwise not precede a
+format specifier.
+
+This builtin does not return a value.
+
+This builtin can be used in constant expressions.
+
+Query for this feature with ``__has_builtin(__builtin_dump_struct)``
 
 .. _langext-__builtin_shufflevector:
 

diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 090b4c6b1b6c..47abde0747d6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -211,8 +211,15 @@ Non-comprehensive list of changes in this release
 - Improve __builtin_dump_struct:
   - Support bitfields in struct and union.
   - Improve the dump format, dump both bitwidth(if its a bitfield) and field value.
-  - Remove anonymous tag locations.
-  - Beautify dump format, add indent for nested struct and struct members.
+  - Remove anonymous tag locations and flatten anonymous struct members.
+  - Beautify dump format, add indent for struct members.
+  - Support passing additional arguments to the formatting function, allowing
+    use with ``fprintf`` and similar formatting functions.
+  - Support use within constant evaluation in C++, if a ``constexpr``
+    formatting function is provided.
+  - Support formatting of base classes in C++.
+  - Support calling a formatting function template in C++, which can provide
+    custom formatting for non-aggregate types.
 - Previously disabled sanitizer options now enabled by default:
   - ASAN_OPTIONS=detect_stack_use_after_return=1 (only on Linux).
   - MSAN_OPTIONS=poison_in_dtor=1.

diff  --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def
index e86475b0e260..ad55fdbc7c62 100644
--- a/clang/include/clang/Basic/Builtins.def
+++ b/clang/include/clang/Basic/Builtins.def
@@ -1603,7 +1603,7 @@ BUILTIN(__builtin_function_start, "v*v&", "nct")
 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, "v.", "t")
 BUILTIN(__builtin_preserve_access_index, "v.", "t")
 
 // Alignment builtins (uses custom parsing to support pointers and integers)

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 614bacc34af5..2e1688d798f2 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8471,6 +8471,12 @@ def err_overflow_builtin_must_be_ptr_int : Error<
 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_expected_callable_argument : Error<
+  "expected a callable expression as %ordinal0 argument to %1, found %2">;
+def note_building_builtin_dump_struct_call : Note<
+  "in call to printing function with arguments '(%0)' while dumping struct">;
 
 def err_atomic_load_store_uses_lib : Error<
   "atomic %select{load|store}0 requires runtime support that is not "

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 7d33b5047a67..44fbffdda390 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -8897,6 +8897,10 @@ class Sema final {
       /// We are marking a class as __dllexport.
       MarkingClassDllexported,
 
+      /// We are building an implied call from __builtin_dump_struct. The
+      /// arguments are in CallArgs.
+      BuildingBuiltinDumpStructCall,
+
       /// Added for Template instantiation observation.
       /// Memoization means we are _not_ instantiating a template because
       /// it is already instantiated (but we entered a context where we
@@ -8918,9 +8922,14 @@ class Sema final {
     /// arguments.
     NamedDecl *Template;
 
-    /// The list of template arguments we are substituting, if they
-    /// are not part of the entity.
-    const TemplateArgument *TemplateArgs;
+    union {
+      /// The list of template arguments we are substituting, if they
+      /// are not part of the entity.
+      const TemplateArgument *TemplateArgs;
+
+      /// The list of argument expressions in a synthesized call.
+      const Expr *const *CallArgs;
+    };
 
     // FIXME: Wrap this union around more members, or perhaps store the
     // kind-specific members in the RAII object owning the context.
@@ -8928,6 +8937,9 @@ class Sema final {
       /// The number of template arguments in TemplateArgs.
       unsigned NumTemplateArgs;
 
+      /// The number of expressions in CallArgs.
+      unsigned NumCallArgs;
+
       /// The special member being declared or defined.
       CXXSpecialMember SpecialMember;
     };

diff  --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index caea5d16b265..a76677ecc2b2 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -24,7 +24,6 @@
 #include "clang/AST/Attr.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/OSLog.h"
-#include "clang/AST/FormatString.h"
 #include "clang/Basic/TargetBuiltins.h"
 #include "clang/Basic/TargetInfo.h"
 #include "clang/CodeGen/CGFunctionInfo.h"
@@ -2044,117 +2043,6 @@ EmitCheckedMixedSignMultiply(CodeGenFunction &CGF, const clang::Expr *Op1,
   return RValue::get(Overflow);
 }
 
-static std::string getPrintfSpecifier(CodeGenFunction &CGF, QualType QT) {
-  analyze_printf::PrintfSpecifier spec;
-  if (!spec.fixType(QT, CGF.getLangOpts(), CGF.getContext(), false)) {
-    // If this type is a boolean type, we should use '%d' to dump its value.
-    if (QT->isBooleanType())
-      return "%d";
-
-    // Otherwise, in order to keep the same behavior as before, use '%p' for
-    // unknown types
-    return "%p";
-  }
-  std::string str;
-  llvm::raw_string_ostream ss(str);
-  spec.toString(ss);
-  return str;
-}
-
-static llvm::Value *dumpValue(CodeGenFunction &CGF, QualType RType,
-                              LValue RecordLV, CharUnits Align,
-                              llvm::FunctionCallee Func, PrintingPolicy Policy,
-                              int Lvl) {
-  RecordDecl *RD = RType->castAs<RecordType>()->getDecl()->getDefinition();
-  std::string Pad = std::string(Lvl * 4, ' ');
-  std::string ElementPad = std::string((Lvl + 1) * 4, ' ');
-
-  Value *GString = CGF.Builder.CreateGlobalStringPtr("{\n");
-  Value *Res = CGF.Builder.CreateCall(Func, {GString});
-
-  for (const auto *FD : RD->fields()) {
-    Value *TmpRes = nullptr;
-
-    std::string Format = llvm::Twine(ElementPad)
-                             .concat(FD->getType().getAsString(Policy))
-                             .concat(llvm::Twine(' '))
-                             .concat(FD->getNameAsString())
-                             .str();
-
-    if (FD->isBitField()) {
-      unsigned BitfieldWidth = FD->getBitWidthValue(CGF.getContext());
-
-      // If current field is a unnamed bitfield, we should dump only one ' '
-      // between type-name and ':'
-      if (!FD->getDeclName().isEmpty())
-        Format += ' ';
-      Format += llvm::Twine(": ").concat(llvm::Twine(BitfieldWidth)).str();
-
-      // If current field is a zero-width bitfield, we just dump a string like
-      // 'type-name : 0'
-      if (FD->isZeroSize(CGF.getContext())) {
-        Format += "\n";
-        GString = CGF.Builder.CreateGlobalStringPtr(Format);
-        TmpRes = CGF.Builder.CreateCall(Func, {GString});
-        Res = CGF.Builder.CreateAdd(Res, TmpRes);
-        continue;
-      }
-    }
-
-    GString = CGF.Builder.CreateGlobalStringPtr(
-        llvm::Twine(Format).concat(" = ").str());
-    TmpRes = CGF.Builder.CreateCall(Func, {GString});
-    Res = CGF.Builder.CreateAdd(TmpRes, Res);
-
-    LValue FieldLV = CGF.EmitLValueForField(RecordLV, FD);
-    QualType CanonicalType =
-        FD->getType().getUnqualifiedType().getCanonicalType();
-
-    // We check whether we are in a recursive type
-    if (CanonicalType->isRecordType()) {
-
-      // If current field is a record type, we should not dump the type name in
-      // recursive dumpRecord call, and we only dump the things between {...}
-      TmpRes =
-          dumpValue(CGF, CanonicalType, FieldLV, Align, Func, Policy, Lvl + 1);
-      Res = CGF.Builder.CreateAdd(TmpRes, Res);
-      continue;
-    }
-
-    // We try to determine the best format to print the current field
-    std::string PrintFormatSpec = getPrintfSpecifier(CGF, FD->getType());
-    GString = CGF.Builder.CreateGlobalStringPtr(
-        llvm::Twine(PrintFormatSpec).concat(llvm::Twine('\n')).str());
-
-    RValue RV = FD->isBitField()
-                    ? CGF.EmitLoadOfBitfieldLValue(FieldLV, FD->getLocation())
-                    : CGF.EmitLoadOfLValue(FieldLV, FD->getLocation());
-
-    /// FIXME: This place needs type promotion.
-    TmpRes = CGF.Builder.CreateCall(Func, {GString, RV.getScalarVal()});
-    Res = CGF.Builder.CreateAdd(Res, TmpRes);
-  }
-
-  GString = CGF.Builder.CreateGlobalStringPtr(Pad + "}\n");
-  Value *TmpRes = CGF.Builder.CreateCall(Func, {GString});
-  Res = CGF.Builder.CreateAdd(Res, TmpRes);
-  return Res;
-}
-
-static llvm::Value *dumpRecord(CodeGenFunction &CGF, QualType RType,
-                               LValue RecordLV, CharUnits Align,
-                               llvm::FunctionCallee Func) {
-  ASTContext &Context = CGF.getContext();
-  PrintingPolicy Policy(Context.getLangOpts());
-  Policy.AnonymousTagLocations = false;
-  std::string Name = llvm::Twine(RType.getAsString(Policy)).concat(" ").str();
-  Value *GString = CGF.Builder.CreateGlobalStringPtr(Name);
-  Value *Res = CGF.Builder.CreateCall(Func, {GString});
-  Value *TmpRes = dumpValue(CGF, RType, RecordLV, Align, Func, Policy, 0);
-  Res = CGF.Builder.CreateAdd(Res, TmpRes);
-  return Res;
-}
-
 static bool
 TypeRequiresBuiltinLaunderImp(const ASTContext &Ctx, QualType Ty,
                               llvm::SmallPtrSetImpl<const Decl *> &Seen) {
@@ -2681,24 +2569,6 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
     return RValue::get(ComplexVal.first);
   }
 
-  case Builtin::BI__builtin_dump_struct: {
-    llvm::Type *LLVMIntTy = getTypes().ConvertType(getContext().IntTy);
-    llvm::FunctionType *LLVMFuncType = llvm::FunctionType::get(
-        LLVMIntTy, {llvm::Type::getInt8PtrTy(getLLVMContext())}, true);
-
-    Value *Func = EmitScalarExpr(E->getArg(1)->IgnoreImpCasts());
-    CharUnits Arg0Align = EmitPointerWithAlignment(E->getArg(0)).getAlignment();
-
-    const Expr *Arg0 = E->getArg(0)->IgnoreImpCasts();
-    QualType Arg0Type = Arg0->getType()->getPointeeType();
-
-    Value *RecordPtr = EmitScalarExpr(Arg0);
-    LValue RecordLV = MakeAddrLValue(RecordPtr, Arg0Type, Arg0Align);
-    Value *Res = dumpRecord(*this, Arg0Type, RecordLV, Arg0Align,
-                            {LLVMFuncType, Func});
-    return RValue::get(Res);
-  }
-
   case Builtin::BI__builtin_preserve_access_index: {
     // Only enabled preserved access index region when debuginfo
     // is available as debuginfo is needed to preserve user-level

diff  --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp
index e219be35cfea..10d6e1f8417b 100644
--- a/clang/lib/Frontend/FrontendActions.cpp
+++ b/clang/lib/Frontend/FrontendActions.cpp
@@ -480,6 +480,8 @@ class DefaultTemplateInstCallback : public TemplateInstantiationCallback {
       return "InitializingStructuredBinding";
     case CodeSynthesisContext::MarkingClassDllexported:
       return "MarkingClassDllexported";
+    case CodeSynthesisContext::BuildingBuiltinDumpStructCall:
+      return "BuildingBuiltinDumpStructCall";
     }
     return "";
   }

diff  --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 18387c241185..1ef8a6ae833a 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -109,24 +109,38 @@ SourceLocation Sema::getLocationOfStringLiteralByte(const StringLiteral *SL,
                                Context.getTargetInfo());
 }
 
+/// Checks that a call expression's argument count is at least the desired
+/// number. This is useful when doing custom type-checking on a variadic
+/// function. Returns true on error.
+static bool checkArgCountAtLeast(Sema &S, CallExpr *Call,
+                                 unsigned MinArgCount) {
+  unsigned ArgCount = Call->getNumArgs();
+  if (ArgCount >= MinArgCount)
+    return false;
+
+  return S.Diag(Call->getEndLoc(), diag::err_typecheck_call_too_few_args)
+         << 0 /*function call*/ << MinArgCount << ArgCount
+         << Call->getSourceRange();
+}
+
 /// Checks that a call expression's argument count is the desired number.
 /// This is useful when doing custom type-checking.  Returns true on error.
-static bool checkArgCount(Sema &S, CallExpr *call, unsigned desiredArgCount) {
-  unsigned argCount = call->getNumArgs();
-  if (argCount == desiredArgCount) return false;
+static bool checkArgCount(Sema &S, CallExpr *Call, unsigned DesiredArgCount) {
+  unsigned ArgCount = Call->getNumArgs();
+  if (ArgCount == DesiredArgCount)
+    return false;
 
-  if (argCount < desiredArgCount)
-    return S.Diag(call->getEndLoc(), diag::err_typecheck_call_too_few_args)
-           << 0 /*function call*/ << desiredArgCount << argCount
-           << call->getSourceRange();
+  if (checkArgCountAtLeast(S, Call, DesiredArgCount))
+    return true;
+  assert(ArgCount > DesiredArgCount && "should have diagnosed this");
 
   // Highlight all the excess arguments.
-  SourceRange range(call->getArg(desiredArgCount)->getBeginLoc(),
-                    call->getArg(argCount - 1)->getEndLoc());
+  SourceRange Range(Call->getArg(DesiredArgCount)->getBeginLoc(),
+                    Call->getArg(ArgCount - 1)->getEndLoc());
 
-  return S.Diag(range.getBegin(), diag::err_typecheck_call_too_many_args)
-    << 0 /*function call*/ << desiredArgCount << argCount
-    << call->getArg(1)->getSourceRange();
+  return S.Diag(Range.getBegin(), diag::err_typecheck_call_too_many_args)
+         << 0 /*function call*/ << DesiredArgCount << ArgCount
+         << Call->getArg(1)->getSourceRange();
 }
 
 /// Check that the first argument to __builtin_annotation is an integer
@@ -366,6 +380,311 @@ static bool SemaBuiltinOverflow(Sema &S, CallExpr *TheCall,
   return false;
 }
 
+namespace {
+struct BuiltinDumpStructGenerator {
+  Sema &S;
+  CallExpr *TheCall;
+  SourceLocation Loc = TheCall->getBeginLoc();
+  SmallVector<Expr *, 32> Actions;
+  DiagnosticErrorTrap ErrorTracker;
+  PrintingPolicy Policy;
+
+  BuiltinDumpStructGenerator(Sema &S, CallExpr *TheCall)
+      : S(S), TheCall(TheCall), ErrorTracker(S.getDiagnostics()),
+        Policy(S.Context.getPrintingPolicy()) {
+    Policy.AnonymousTagLocations = false;
+  }
+
+  Expr *makeOpaqueValueExpr(Expr *Inner) {
+    auto *OVE = new (S.Context)
+        OpaqueValueExpr(Loc, Inner->getType(), Inner->getValueKind(),
+                        Inner->getObjectKind(), Inner);
+    Actions.push_back(OVE);
+    return OVE;
+  }
+
+  Expr *getStringLiteral(llvm::StringRef Str) {
+    Expr *Lit = S.Context.getPredefinedStringLiteralFromCache(Str);
+    // Wrap the literal in parentheses to attach a source location.
+    return new (S.Context) ParenExpr(Loc, Loc, Lit);
+  }
+
+  bool callPrintFunction(llvm::StringRef Format,
+                         llvm::ArrayRef<Expr *> Exprs = {}) {
+    SmallVector<Expr *, 8> Args;
+    assert(TheCall->getNumArgs() >= 2);
+    Args.reserve((TheCall->getNumArgs() - 2) + /*Format*/ 1 + Exprs.size());
+    Args.assign(TheCall->arg_begin() + 2, TheCall->arg_end());
+    Args.push_back(getStringLiteral(Format));
+    Args.insert(Args.end(), Exprs.begin(), Exprs.end());
+
+    // Register a note to explain why we're performing the call.
+    Sema::CodeSynthesisContext Ctx;
+    Ctx.Kind = Sema::CodeSynthesisContext::BuildingBuiltinDumpStructCall;
+    Ctx.PointOfInstantiation = Loc;
+    Ctx.CallArgs = Args.data();
+    Ctx.NumCallArgs = Args.size();
+    S.pushCodeSynthesisContext(Ctx);
+
+    ExprResult RealCall =
+        S.BuildCallExpr(/*Scope=*/nullptr, TheCall->getArg(1),
+                        TheCall->getBeginLoc(), Args, TheCall->getRParenLoc());
+
+    S.popCodeSynthesisContext();
+    if (!RealCall.isInvalid())
+      Actions.push_back(RealCall.get());
+    // Bail out if we've hit any errors, even if we managed to build the
+    // call. We don't want to produce more than one error.
+    return RealCall.isInvalid() || ErrorTracker.hasErrorOccurred();
+  }
+
+  Expr *getIndentString(unsigned Depth) {
+    if (!Depth)
+      return nullptr;
+
+    llvm::SmallString<32> Indent;
+    Indent.resize(Depth * Policy.Indentation, ' ');
+    return getStringLiteral(Indent);
+  }
+
+  Expr *getTypeString(QualType T) {
+    return getStringLiteral(T.getAsString(Policy));
+  }
+
+  bool appendFormatSpecifier(QualType T, llvm::SmallVectorImpl<char> &Str) {
+    llvm::raw_svector_ostream OS(Str);
+
+    // Format 'bool', 'char', 'signed char', 'unsigned char' as numbers, rather
+    // than trying to print a single character.
+    if (auto *BT = T->getAs<BuiltinType>()) {
+      switch (BT->getKind()) {
+      case BuiltinType::Bool:
+        OS << "%d";
+        return true;
+      case BuiltinType::Char_U:
+      case BuiltinType::UChar:
+        OS << "%hhu";
+        return true;
+      case BuiltinType::Char_S:
+      case BuiltinType::SChar:
+        OS << "%hhd";
+        return true;
+      default:
+        break;
+      }
+    }
+
+    analyze_printf::PrintfSpecifier Specifier;
+    if (Specifier.fixType(T, S.getLangOpts(), S.Context, /*IsObjCLiteral=*/false)) {
+      // We were able to guess how to format this.
+      if (Specifier.getConversionSpecifier().getKind() ==
+          analyze_printf::PrintfConversionSpecifier::sArg) {
+        // Wrap double-quotes around a '%s' specifier and limit its maximum
+        // length. Ideally we'd also somehow escape special characters in the
+        // contents but printf doesn't support that.
+        // FIXME: '%s' formatting is not safe in general.
+        OS << '"';
+        Specifier.setPrecision(analyze_printf::OptionalAmount(32u));
+        Specifier.toString(OS);
+        OS << '"';
+        // FIXME: It would be nice to include a '...' if the string doesn't fit
+        // in the length limit.
+      } else {
+        Specifier.toString(OS);
+      }
+      return true;
+    }
+
+    if (T->isPointerType()) {
+      // Format all pointers with '%p'.
+      OS << "%p";
+      return true;
+    }
+
+    return false;
+  }
+
+  bool dumpUnnamedRecord(const RecordDecl *RD, Expr *E, unsigned Depth) {
+    Expr *IndentLit = getIndentString(Depth);
+    Expr *TypeLit = getTypeString(S.Context.getRecordType(RD));
+    if (IndentLit ? callPrintFunction("%s%s", {IndentLit, TypeLit})
+                  : callPrintFunction("%s", {TypeLit}))
+      return true;
+
+    return dumpRecordValue(RD, E, IndentLit, Depth);
+  }
+
+  // Dump a record value. E should be a pointer or lvalue referring to an RD.
+  bool dumpRecordValue(const RecordDecl *RD, Expr *E, Expr *RecordIndent,
+                       unsigned Depth) {
+    // FIXME: Decide what to do if RD is a union. At least we should probably
+    // turn off printing `const char*` members with `%s`, because that is very
+    // likely to crash if that's not the active member. Whatever we decide, we
+    // should document it.
+
+    // Build an OpaqueValueExpr so we can refer to E more than once without
+    // triggering re-evaluation.
+    Expr *RecordArg = makeOpaqueValueExpr(E);
+    bool RecordArgIsPtr = RecordArg->getType()->isPointerType();
+
+    if (callPrintFunction(" {\n"))
+      return true;
+
+    // Dump each base class, regardless of whether they're aggregates.
+    if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
+      for (const auto &Base : CXXRD->bases()) {
+        QualType BaseType =
+            RecordArgIsPtr ? S.Context.getPointerType(Base.getType())
+                           : S.Context.getLValueReferenceType(Base.getType());
+        ExprResult BasePtr = S.BuildCStyleCastExpr(
+            Loc, S.Context.getTrivialTypeSourceInfo(BaseType, Loc), Loc,
+            RecordArg);
+        if (BasePtr.isInvalid() ||
+            dumpUnnamedRecord(Base.getType()->getAsRecordDecl(), BasePtr.get(),
+                              Depth + 1))
+          return true;
+      }
+    }
+
+    Expr *FieldIndentArg = getIndentString(Depth + 1);
+
+    // Dump each 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;
+
+      llvm::SmallString<20> Format = llvm::StringRef("%s%s %s ");
+      llvm::SmallVector<Expr *, 5> Args = {FieldIndentArg,
+                                           getTypeString(FD->getType()),
+                                           getStringLiteral(FD->getName())};
+
+      if (FD->isBitField()) {
+        Format += ": %zu ";
+        QualType SizeT = S.Context.getSizeType();
+        llvm::APInt BitWidth(S.Context.getIntWidth(SizeT),
+                             FD->getBitWidthValue(S.Context));
+        Args.push_back(IntegerLiteral::Create(S.Context, BitWidth, SizeT, Loc));
+      }
+
+      Format += "=";
+
+      ExprResult Field =
+          IFD ? S.BuildAnonymousStructUnionMemberReference(
+                    CXXScopeSpec(), Loc, IFD,
+                    DeclAccessPair::make(IFD, AS_public), RecordArg, Loc)
+              : S.BuildFieldReferenceExpr(
+                    RecordArg, RecordArgIsPtr, Loc, CXXScopeSpec(), FD,
+                    DeclAccessPair::make(FD, AS_public),
+                    DeclarationNameInfo(FD->getDeclName(), Loc));
+      if (Field.isInvalid())
+        return true;
+
+      auto *InnerRD = FD->getType()->getAsRecordDecl();
+      auto *InnerCXXRD = dyn_cast_or_null<CXXRecordDecl>(InnerRD);
+      if (InnerRD && (!InnerCXXRD || InnerCXXRD->isAggregate())) {
+        // Recursively print the values of members of aggregate record type.
+        if (callPrintFunction(Format, Args) ||
+            dumpRecordValue(InnerRD, Field.get(), FieldIndentArg, Depth + 1))
+          return true;
+      } else {
+        Format += " ";
+        if (appendFormatSpecifier(FD->getType(), Format)) {
+          // We know how to print this field.
+          Args.push_back(Field.get());
+        } else {
+          // We don't know how to print this field. Print out its address
+          // with a format specifier that a smart tool will be able to
+          // recognize and treat specially.
+          Format += "*%p";
+          ExprResult FieldAddr =
+              S.BuildUnaryOp(nullptr, Loc, UO_AddrOf, Field.get());
+          if (FieldAddr.isInvalid())
+            return true;
+          Args.push_back(FieldAddr.get());
+        }
+        Format += "\n";
+        if (callPrintFunction(Format, Args))
+          return true;
+      }
+    }
+
+    return RecordIndent ? callPrintFunction("%s}\n", RecordIndent)
+                        : callPrintFunction("}\n");
+  }
+
+  Expr *buildWrapper() {
+    auto *Wrapper = PseudoObjectExpr::Create(S.Context, TheCall, Actions,
+                                             PseudoObjectExpr::NoResult);
+    TheCall->setType(Wrapper->getType());
+    TheCall->setValueKind(Wrapper->getValueKind());
+    return Wrapper;
+  }
+};
+} // namespace
+
+static ExprResult SemaBuiltinDumpStruct(Sema &S, CallExpr *TheCall) {
+  if (checkArgCountAtLeast(S, TheCall, 2))
+    return ExprError();
+
+  ExprResult PtrArgResult = S.DefaultLvalueConversion(TheCall->getArg(0));
+  if (PtrArgResult.isInvalid())
+    return ExprError();
+  TheCall->setArg(0, PtrArgResult.get());
+
+  // First argument should be a pointer to a struct.
+  QualType PtrArgType = PtrArgResult.get()->getType();
+  if (!PtrArgType->isPointerType() ||
+      !PtrArgType->getPointeeType()->isRecordType()) {
+    S.Diag(PtrArgResult.get()->getBeginLoc(),
+           diag::err_expected_struct_pointer_argument)
+        << 1 << TheCall->getDirectCallee() << PtrArgType;
+    return ExprError();
+  }
+  const RecordDecl *RD = PtrArgType->getPointeeType()->getAsRecordDecl();
+
+  // Second argument is a callable, but we can't fully validate it until we try
+  // calling it.
+  QualType FnArgType = TheCall->getArg(1)->getType();
+  if (!FnArgType->isFunctionType() && !FnArgType->isFunctionPointerType() &&
+      !FnArgType->isBlockPointerType() &&
+      !(S.getLangOpts().CPlusPlus && FnArgType->isRecordType())) {
+    auto *BT = FnArgType->getAs<BuiltinType>();
+    switch (BT ? BT->getKind() : BuiltinType::Void) {
+    case BuiltinType::Dependent:
+    case BuiltinType::Overload:
+    case BuiltinType::BoundMember:
+    case BuiltinType::PseudoObject:
+    case BuiltinType::UnknownAny:
+    case BuiltinType::BuiltinFn:
+      // This might be a callable.
+      break;
+
+    default:
+      S.Diag(TheCall->getArg(1)->getBeginLoc(),
+             diag::err_expected_callable_argument)
+          << 2 << TheCall->getDirectCallee() << FnArgType;
+      return ExprError();
+    }
+  }
+
+  BuiltinDumpStructGenerator Generator(S, TheCall);
+
+  // Wrap parentheses around the given pointer. This is not necessary for
+  // correct code generation, but it means that when we pretty-print the call
+  // arguments in our diagnostics we will produce '(&s)->n' instead of the
+  // incorrect '&s->n'.
+  Expr *PtrArg = PtrArgResult.get();
+  PtrArg = new (S.Context)
+      ParenExpr(PtrArg->getBeginLoc(),
+                S.getLocForEndOfToken(PtrArg->getEndLoc()), PtrArg);
+  if (Generator.dumpUnnamedRecord(RD, PtrArg, 0))
+    return ExprError();
+
+  return Generator.buildWrapper();
+}
+
 static bool SemaBuiltinCallWithStaticChain(Sema &S, CallExpr *BuiltinCall) {
   if (checkArgCount(S, BuiltinCall, 2))
     return true;
@@ -2013,62 +2332,8 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
       CorrectDelayedTyposInExpr(TheCallResult.get());
     return Res;
   }
-  case Builtin::BI__builtin_dump_struct: {
-    // We first want to ensure we are called with 2 arguments
-    if (checkArgCount(*this, TheCall, 2))
-      return ExprError();
-    // Ensure that the first argument is of type 'struct XX *'
-    const Expr *PtrArg = TheCall->getArg(0)->IgnoreParenImpCasts();
-    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";
-      return ExprError();
-    }
-
-    // Ensure that the second argument is of type 'FunctionType'
-    const Expr *FnPtrArg = TheCall->getArg(1)->IgnoreImpCasts();
-    const QualType FnPtrArgType = FnPtrArg->getType();
-    if (!FnPtrArgType->isPointerType()) {
-      Diag(FnPtrArg->getBeginLoc(), diag::err_typecheck_convert_incompatible)
-          << FnPtrArgType << "'int (*)(const char *, ...)'" << 1 << 0 << 3 << 2
-          << FnPtrArgType << "'int (*)(const char *, ...)'";
-      return ExprError();
-    }
-
-    const auto *FuncType =
-        FnPtrArgType->getPointeeType()->getAs<FunctionType>();
-
-    if (!FuncType) {
-      Diag(FnPtrArg->getBeginLoc(), diag::err_typecheck_convert_incompatible)
-          << FnPtrArgType << "'int (*)(const char *, ...)'" << 1 << 0 << 3 << 2
-          << FnPtrArgType << "'int (*)(const char *, ...)'";
-      return ExprError();
-    }
-
-    if (const auto *FT = dyn_cast<FunctionProtoType>(FuncType)) {
-      if (!FT->getNumParams()) {
-        Diag(FnPtrArg->getBeginLoc(), diag::err_typecheck_convert_incompatible)
-            << FnPtrArgType << "'int (*)(const char *, ...)'" << 1 << 0 << 3
-            << 2 << FnPtrArgType << "'int (*)(const char *, ...)'";
-        return ExprError();
-      }
-      QualType PT = FT->getParamType(0);
-      if (!FT->isVariadic() || FT->getReturnType() != Context.IntTy ||
-          !PT->isPointerType() || !PT->getPointeeType()->isCharType() ||
-          !PT->getPointeeType().isConstQualified()) {
-        Diag(FnPtrArg->getBeginLoc(), diag::err_typecheck_convert_incompatible)
-            << FnPtrArgType << "'int (*)(const char *, ...)'" << 1 << 0 << 3
-            << 2 << FnPtrArgType << "'int (*)(const char *, ...)'";
-        return ExprError();
-      }
-    }
-
-    TheCall->setType(Context.IntTy);
-    break;
-  }
+  case Builtin::BI__builtin_dump_struct:
+    return SemaBuiltinDumpStruct(*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/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 1143023bb5ba..4fd9d003e66a 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -213,6 +213,7 @@ bool Sema::CodeSynthesisContext::isInstantiationRecord() const {
   case RewritingOperatorAsSpaceship:
   case InitializingStructuredBinding:
   case MarkingClassDllexported:
+  case BuildingBuiltinDumpStructCall:
     return false;
 
   // This function should never be called when Kind's value is Memoization.
@@ -482,6 +483,19 @@ void Sema::InstantiatingTemplate::Clear() {
   }
 }
 
+static std::string convertCallArgsToString(Sema &S,
+                                           llvm::ArrayRef<const Expr *> Args) {
+  std::string Result;
+  llvm::raw_string_ostream OS(Result);
+  llvm::ListSeparator Comma;
+  for (const Expr *Arg : Args) {
+    OS << Comma;
+    Arg->IgnoreParens()->printPretty(OS, nullptr,
+                                     S.Context.getPrintingPolicy());
+  }
+  return Result;
+}
+
 bool Sema::InstantiatingTemplate::CheckInstantiationDepth(
                                         SourceLocation PointOfInstantiation,
                                            SourceRange InstantiationRange) {
@@ -770,6 +784,14 @@ void Sema::PrintInstantiationStack() {
           << cast<CXXRecordDecl>(Active->Entity) << !getLangOpts().CPlusPlus11;
       break;
 
+    case CodeSynthesisContext::BuildingBuiltinDumpStructCall:
+      Diags.Report(Active->PointOfInstantiation,
+                   diag::note_building_builtin_dump_struct_call)
+          << convertCallArgsToString(
+                 *this,
+                 llvm::makeArrayRef(Active->CallArgs, Active->NumCallArgs));
+      break;
+
     case CodeSynthesisContext::Memoization:
       break;
 
@@ -874,6 +896,7 @@ Optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const {
     case CodeSynthesisContext::DefiningSynthesizedFunction:
     case CodeSynthesisContext::InitializingStructuredBinding:
     case CodeSynthesisContext::MarkingClassDllexported:
+    case CodeSynthesisContext::BuildingBuiltinDumpStructCall:
       // This happens in a context unrelated to template instantiation, so
       // there is no SFINAE.
       return None;

diff  --git a/clang/test/CodeGen/builtin-dump-struct.c b/clang/test/CodeGen/builtin-dump-struct.c
new file mode 100644
index 000000000000..af0a42c5a040
--- /dev/null
+++ b/clang/test/CodeGen/builtin-dump-struct.c
@@ -0,0 +1,271 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm %s -o - | FileCheck %s
+
+#include "Inputs/stdio.h"
+#include <stdint.h>
+
+// CHECK-DAG: @[[STR_0:.*]] = private unnamed_addr constant [3 x i8] c"%s\00",
+// CHECK-DAG: @[[STR_1:.*]] = private unnamed_addr constant [9 x i8] c"struct A\00",
+// CHECK-DAG: @[[STR_2:.*]] = private unnamed_addr constant [4 x i8] c" {\0A\00",
+// CHECK-DAG: @[[STR_4:.*]] = private unnamed_addr constant [3 x i8] c"  \00",
+// CHECK-DAG: @[[STR_5:.*]] = private unnamed_addr constant [5 x i8] c"char\00",
+// CHECK-DAG: @[[STR_6:.*]] = private unnamed_addr constant [3 x i8] c"i1\00",
+// CHECK-DAG: @[[STR_7:.*]] = private unnamed_addr constant [16 x i8] c"%s%s %s = %hhd\0A\00",
+// CHECK-DAG: @[[STR_8:.*]] = private unnamed_addr constant [12 x i8] c"signed char\00",
+// CHECK-DAG: @[[STR_9:.*]] = private unnamed_addr constant [3 x i8] c"i2\00",
+// CHECK-DAG: @[[STR_10:.*]] = private unnamed_addr constant [16 x i8] c"%s%s %s = %hhu\0A\00",
+// CHECK-DAG: @[[STR_11:.*]] = private unnamed_addr constant [14 x i8] c"unsigned char\00",
+// CHECK-DAG: @[[STR_12:.*]] = private unnamed_addr constant [3 x i8] c"i3\00",
+// CHECK-DAG: @[[STR_13:.*]] = private unnamed_addr constant [15 x i8] c"%s%s %s = %hd\0A\00",
+// CHECK-DAG: @[[STR_14:.*]] = private unnamed_addr constant [6 x i8] c"short\00",
+// CHECK-DAG: @[[STR_15:.*]] = private unnamed_addr constant [3 x i8] c"i4\00",
+// CHECK-DAG: @[[STR_16:.*]] = private unnamed_addr constant [15 x i8] c"%s%s %s = %hu\0A\00",
+// CHECK-DAG: @[[STR_17:.*]] = private unnamed_addr constant [15 x i8] c"unsigned short\00",
+// CHECK-DAG: @[[STR_18:.*]] = private unnamed_addr constant [3 x i8] c"i5\00",
+// CHECK-DAG: @[[STR_19:.*]] = private unnamed_addr constant [14 x i8] c"%s%s %s = %d\0A\00",
+// CHECK-DAG: @[[STR_20:.*]] = private unnamed_addr constant [4 x i8] c"int\00",
+// CHECK-DAG: @[[STR_21:.*]] = private unnamed_addr constant [3 x i8] c"i6\00",
+// CHECK-DAG: @[[STR_22:.*]] = private unnamed_addr constant [14 x i8] c"%s%s %s = %u\0A\00",
+// CHECK-DAG: @[[STR_23:.*]] = private unnamed_addr constant [13 x i8] c"unsigned int\00",
+// CHECK-DAG: @[[STR_24:.*]] = private unnamed_addr constant [3 x i8] c"i7\00",
+// CHECK-DAG: @[[STR_25:.*]] = private unnamed_addr constant [15 x i8] c"%s%s %s = %ld\0A\00",
+// CHECK-DAG: @[[STR_26:.*]] = private unnamed_addr constant [5 x i8] c"long\00",
+// CHECK-DAG: @[[STR_27:.*]] = private unnamed_addr constant [3 x i8] c"i8\00",
+// CHECK-DAG: @[[STR_28:.*]] = private unnamed_addr constant [15 x i8] c"%s%s %s = %lu\0A\00",
+// CHECK-DAG: @[[STR_29:.*]] = private unnamed_addr constant [14 x i8] c"unsigned long\00",
+// CHECK-DAG: @[[STR_30:.*]] = private unnamed_addr constant [3 x i8] c"i9\00",
+// CHECK-DAG: @[[STR_31:.*]] = private unnamed_addr constant [16 x i8] c"%s%s %s = %lld\0A\00",
+// CHECK-DAG: @[[STR_32:.*]] = private unnamed_addr constant [10 x i8] c"long long\00",
+// CHECK-DAG: @[[STR_33:.*]] = private unnamed_addr constant [4 x i8] c"i10\00",
+// CHECK-DAG: @[[STR_34:.*]] = private unnamed_addr constant [16 x i8] c"%s%s %s = %llu\0A\00",
+// CHECK-DAG: @[[STR_35:.*]] = private unnamed_addr constant [19 x i8] c"unsigned long long\00",
+// CHECK-DAG: @[[STR_36:.*]] = private unnamed_addr constant [4 x i8] c"i11\00",
+// CHECK-DAG: @[[STR_37:.*]] = private unnamed_addr constant [14 x i8] c"%s%s %s = %f\0A\00",
+// CHECK-DAG: @[[STR_38:.*]] = private unnamed_addr constant [6 x i8] c"float\00",
+// CHECK-DAG: @[[STR_39:.*]] = private unnamed_addr constant [3 x i8] c"f1\00",
+// CHECK-DAG: @[[STR_40:.*]] = private unnamed_addr constant [7 x i8] c"double\00",
+// CHECK-DAG: @[[STR_41:.*]] = private unnamed_addr constant [3 x i8] c"f2\00",
+// CHECK-DAG: @[[STR_42:.*]] = private unnamed_addr constant [15 x i8] c"%s%s %s = %Lf\0A\00",
+// CHECK-DAG: @[[STR_43:.*]] = private unnamed_addr constant [12 x i8] c"long double\00",
+// CHECK-DAG: @[[STR_44:.*]] = private unnamed_addr constant [3 x i8] c"f3\00",
+// CHECK-DAG: @[[STR_45:.*]] = private unnamed_addr constant [14 x i8] c"%s%s %s = %p\0A\00",
+// CHECK-DAG: @[[STR_46:.*]] = private unnamed_addr constant [7 x i8] c"void *\00",
+// CHECK-DAG: @[[STR_47:.*]] = private unnamed_addr constant [3 x i8] c"p1\00",
+// CHECK-DAG: @[[STR_48:.*]] = private unnamed_addr constant [19 x i8] c"%s%s %s = \22%.32s\22\0A\00",
+// CHECK-DAG: @[[STR_49:.*]] = private unnamed_addr constant [7 x i8] c"char *\00",
+// CHECK-DAG: @[[STR_50:.*]] = private unnamed_addr constant [3 x i8] c"s1\00",
+// CHECK-DAG: @[[STR_51:.*]] = private unnamed_addr constant [13 x i8] c"const char *\00",
+// CHECK-DAG: @[[STR_52:.*]] = private unnamed_addr constant [3 x i8] c"s2\00",
+// CHECK-DAG: @[[STR_53:.*]] = private unnamed_addr constant [15 x i8] c"%s%s %s = *%p\0A\00",
+// CHECK-DAG: @[[STR_54:.*]] = private unnamed_addr constant [9 x i8] c"char[10]\00",
+// CHECK-DAG: @[[STR_55:.*]] = private unnamed_addr constant [3 x i8] c"s3\00",
+// CHECK-DAG: @[[STR_56:.*]] = private unnamed_addr constant [10 x i8] c"%s%s %s =\00",
+// CHECK-DAG: @[[STR_57:.*]] = private unnamed_addr constant [9 x i8] c"struct X\00",
+// CHECK-DAG: @[[STR_58:.*]] = private unnamed_addr constant [3 x i8] c"x1\00",
+// CHECK-DAG: @[[STR_59:.*]] = private unnamed_addr constant [5 x i8] c"    \00",
+// CHECK-DAG: @[[STR_60:.*]] = private unnamed_addr constant [2 x i8] c"n\00",
+// CHECK-DAG: @[[STR_61:.*]] = private unnamed_addr constant [5 x i8] c"%s}\0A\00",
+// CHECK-DAG: @[[STR_62:.*]] = private unnamed_addr constant [3 x i8] c"n1\00",
+// CHECK-DAG: @[[STR_63:.*]] = private unnamed_addr constant [3 x i8] c"n2\00",
+// CHECK-DAG: @[[STR_64:.*]] = private unnamed_addr constant [3 x i8] c"u1\00",
+// CHECK-DAG: @[[STR_65:.*]] = private unnamed_addr constant [3 x i8] c"u2\00",
+// CHECK-DAG: @[[STR_66:.*]] = private unnamed_addr constant [20 x i8] c"%s%s %s : %zu = %d\0A\00",
+// CHECK-DAG: @[[STR_67:.*]] = private unnamed_addr constant [3 x i8] c"b1\00",
+// CHECK-DAG: @[[STR_68:.*]] = private unnamed_addr constant [3 x i8] c"b2\00",
+// CHECK-DAG: @[[STR_69:.*]] = private unnamed_addr constant [13 x i8] c"_Complex int\00",
+// CHECK-DAG: @[[STR_70:.*]] = private unnamed_addr constant [4 x i8] c"ci1\00",
+// CHECK-DAG: @[[STR_71:.*]] = private unnamed_addr constant [16 x i8] c"_Complex double\00",
+// CHECK-DAG: @[[STR_72:.*]] = private unnamed_addr constant [4 x i8] c"cd1\00",
+// CHECK-DAG: @[[STR_73:.*]] = private unnamed_addr constant [3 x i8] c"}\0A\00",
+
+struct X {
+  int n;
+};
+
+struct A {
+  char i1;
+  signed char i2;
+  unsigned char i3;
+  short i4;
+  unsigned short i5;
+  int i6;
+  unsigned int i7;
+  long i8;
+  unsigned long i9;
+  long long i10;
+  unsigned long long i11;
+
+  float f1;
+  double f2;
+  long double f3;
+
+  void *p1;
+  char *s1;
+  const char *s2;
+  char s3[10];
+
+  struct X x1;
+
+  struct {
+    int n1;
+    struct X n2;
+  };
+  union {
+    int u1;
+    int u2;
+  };
+
+  int b1 : 5;
+  int : 0;
+  int b2 : 3;
+  int : 5;
+
+  _Complex int ci1;
+  _Complex double cd1;
+};
+
+int printf(const char *fmt, ...);
+
+// CHECK-LABEL: define {{.*}} @test(
+void test(struct A *a) {
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_0]], ptr noundef @[[STR_1]])
+
+  // CHECK: %[[VAL_0:.*]] = load ptr, ptr %[[VAL_a_addr:.*]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_2]])
+
+  // CHECK: %[[VAL_i1:.*]] = getelementptr inbounds %[[VAL_struct_A:.*]], ptr %[[VAL_0]], i32 0, i32 0
+  // CHECK: %[[VAL_1:.*]] = load i8, ptr %[[VAL_i1]],
+  // CHECK: %[[VAL_conv:.*]] = sext i8 %[[VAL_1]] to i32
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_7]], ptr noundef @[[STR_4]], ptr noundef @[[STR_5]], ptr noundef @[[STR_6]], i32 noundef %[[VAL_conv]])
+
+  // CHECK: %[[VAL_i2:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 1
+  // CHECK: %[[VAL_2:.*]] = load i8, ptr %[[VAL_i2]],
+  // CHECK: %[[VAL_conv3:.*]] = sext i8 %[[VAL_2]] to i32
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_7]], ptr noundef @[[STR_4]], ptr noundef @[[STR_8]], ptr noundef @[[STR_9]], i32 noundef %[[VAL_conv3]])
+
+  // CHECK: %[[VAL_i3:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 2
+  // CHECK: %[[VAL_3:.*]] = load i8, ptr %[[VAL_i3]],
+  // CHECK: %[[VAL_conv5:.*]] = zext i8 %[[VAL_3]] to i32
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_10]], ptr noundef @[[STR_4]], ptr noundef @[[STR_11]], ptr noundef @[[STR_12]], i32 noundef %[[VAL_conv5]])
+
+  // CHECK: %[[VAL_i4:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 3
+  // CHECK: %[[VAL_4:.*]] = load i16, ptr %[[VAL_i4]],
+  // CHECK: %[[VAL_conv7:.*]] = sext i16 %[[VAL_4]] to i32
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_13]], ptr noundef @[[STR_4]], ptr noundef @[[STR_14]], ptr noundef @[[STR_15]], i32 noundef %[[VAL_conv7]])
+
+  // CHECK: %[[VAL_i5:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 4
+  // CHECK: %[[VAL_5:.*]] = load i16, ptr %[[VAL_i5]],
+  // CHECK: %[[VAL_conv9:.*]] = zext i16 %[[VAL_5]] to i32
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_16]], ptr noundef @[[STR_4]], ptr noundef @[[STR_17]], ptr noundef @[[STR_18]], i32 noundef %[[VAL_conv9]])
+
+  // CHECK: %[[VAL_i6:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 5
+  // CHECK: %[[VAL_6:.*]] = load i32, ptr %[[VAL_i6]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_19]], ptr noundef @[[STR_4]], ptr noundef @[[STR_20]], ptr noundef @[[STR_21]], i32 noundef %[[VAL_6]])
+
+  // CHECK: %[[VAL_i7:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 6
+  // CHECK: %[[VAL_7:.*]] = load i32, ptr %[[VAL_i7]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_22]], ptr noundef @[[STR_4]], ptr noundef @[[STR_23]], ptr noundef @[[STR_24]], i32 noundef %[[VAL_7]])
+
+  // CHECK: %[[VAL_i8:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 7
+  // CHECK: %[[VAL_8:.*]] = load i64, ptr %[[VAL_i8]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_25]], ptr noundef @[[STR_4]], ptr noundef @[[STR_26]], ptr noundef @[[STR_27]], i64 noundef %[[VAL_8]])
+
+  // CHECK: %[[VAL_i9:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 8
+  // CHECK: %[[VAL_9:.*]] = load i64, ptr %[[VAL_i9]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_28]], ptr noundef @[[STR_4]], ptr noundef @[[STR_29]], ptr noundef @[[STR_30]], i64 noundef %[[VAL_9]])
+
+  // CHECK: %[[VAL_i10:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 9
+  // CHECK: %[[VAL_10:.*]] = load i64, ptr %[[VAL_i10]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_31]], ptr noundef @[[STR_4]], ptr noundef @[[STR_32]], ptr noundef @[[STR_33]], i64 noundef %[[VAL_10]])
+
+  // CHECK: %[[VAL_i11:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 10
+  // CHECK: %[[VAL_11:.*]] = load i64, ptr %[[VAL_i11]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_34]], ptr noundef @[[STR_4]], ptr noundef @[[STR_35]], ptr noundef @[[STR_36]], i64 noundef %[[VAL_11]])
+
+  // CHECK: %[[VAL_f1:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 11
+  // CHECK: %[[VAL_12:.*]] = load float, ptr %[[VAL_f1]],
+  // CHECK: %[[VAL_conv17:.*]] = fpext float %[[VAL_12]] to double
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_37]], ptr noundef @[[STR_4]], ptr noundef @[[STR_38]], ptr noundef @[[STR_39]], double noundef %[[VAL_conv17]])
+
+  // CHECK: %[[VAL_f2:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 12
+  // CHECK: %[[VAL_13:.*]] = load double, ptr %[[VAL_f2]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_37]], ptr noundef @[[STR_4]], ptr noundef @[[STR_40]], ptr noundef @[[STR_41]], double noundef %[[VAL_13]])
+
+  // CHECK: %[[VAL_f3:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 13
+  // CHECK: %[[VAL_14:.*]] = load x86_fp80, ptr %[[VAL_f3]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_42]], ptr noundef @[[STR_4]], ptr noundef @[[STR_43]], ptr noundef @[[STR_44]], x86_fp80 noundef %[[VAL_14]])
+
+  // CHECK: %[[VAL_p1:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 14
+  // CHECK: %[[VAL_15:.*]] = load ptr, ptr %[[VAL_p1]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_45]], ptr noundef @[[STR_4]], ptr noundef @[[STR_46]], ptr noundef @[[STR_47]], ptr noundef %[[VAL_15]])
+
+  // CHECK: %[[VAL_s1:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 15
+  // CHECK: %[[VAL_16:.*]] = load ptr, ptr %[[VAL_s1]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_48]], ptr noundef @[[STR_4]], ptr noundef @[[STR_49]], ptr noundef @[[STR_50]], ptr noundef %[[VAL_16]])
+
+  // CHECK: %[[VAL_s2:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 16
+  // CHECK: %[[VAL_17:.*]] = load ptr, ptr %[[VAL_s2]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_48]], ptr noundef @[[STR_4]], ptr noundef @[[STR_51]], ptr noundef @[[STR_52]], ptr noundef %[[VAL_17]])
+
+  // CHECK: %[[VAL_s3:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 17
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_53]], ptr noundef @[[STR_4]], ptr noundef @[[STR_54]], ptr noundef @[[STR_55]], ptr noundef %[[VAL_s3]])
+
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_56]], ptr noundef @[[STR_4]], ptr noundef @[[STR_57]], ptr noundef @[[STR_58]])
+
+  // CHECK: %[[VAL_x1:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 18
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_2]])
+
+  // CHECK: %[[VAL_n:.*]] = getelementptr inbounds %[[VAL_struct_X:.*]], ptr %[[VAL_x1]], i32 0, i32 0
+  // CHECK: %[[VAL_18:.*]] = load i32, ptr %[[VAL_n]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_19]], ptr noundef @[[STR_59]], ptr noundef @[[STR_20]], ptr noundef @[[STR_60]], i32 noundef %[[VAL_18]])
+
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_61]], ptr noundef @[[STR_4]])
+
+  // CHECK: %[[VAL_19:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 19
+  // CHECK: %[[VAL_n1:.*]] = getelementptr inbounds %[[VAL_struct_anon:.*]], ptr %[[VAL_19]], i32 0, i32 0
+  // CHECK: %[[VAL_20:.*]] = load i32, ptr %[[VAL_n1]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_19]], ptr noundef @[[STR_4]], ptr noundef @[[STR_20]], ptr noundef @[[STR_62]], i32 noundef %[[VAL_20]])
+
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_56]], ptr noundef @[[STR_4]], ptr noundef @[[STR_57]], ptr noundef @[[STR_63]])
+
+  // CHECK: %[[VAL_21:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 19
+  // CHECK: %[[VAL_n2:.*]] = getelementptr inbounds %[[VAL_struct_anon]], ptr %[[VAL_21]], i32 0, i32 1
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_2]])
+
+  // CHECK: %[[VAL_n32:.*]] = getelementptr inbounds %[[VAL_struct_X]], ptr %[[VAL_n2]], i32 0, i32 0
+  // CHECK: %[[VAL_22:.*]] = load i32, ptr %[[VAL_n32]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_19]], ptr noundef @[[STR_59]], ptr noundef @[[STR_20]], ptr noundef @[[STR_60]], i32 noundef %[[VAL_22]])
+
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_61]], ptr noundef @[[STR_4]])
+
+  // CHECK: %[[VAL_23:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 20
+  // CHECK: %[[VAL_24:.*]] = load i32, ptr %[[VAL_23]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_19]], ptr noundef @[[STR_4]], ptr noundef @[[STR_20]], ptr noundef @[[STR_64]], i32 noundef %[[VAL_24]])
+
+  // CHECK: %[[VAL_25:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 20
+  // CHECK: %[[VAL_26:.*]] = load i32, ptr %[[VAL_25]],
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_19]], ptr noundef @[[STR_4]], ptr noundef @[[STR_20]], ptr noundef @[[STR_65]], i32 noundef %[[VAL_26]])
+
+  // CHECK: %[[VAL_b1:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 21
+  // CHECK: %[[VAL_bf_load:.*]] = load i8, ptr %[[VAL_b1]],
+  // CHECK: %[[VAL_bf_shl:.*]] = shl i8 %[[VAL_bf_load]], 3
+  // CHECK: %[[VAL_bf_ashr:.*]] = ashr i8 %[[VAL_bf_shl]], 3
+  // CHECK: %[[VAL_bf_cast:.*]] = sext i8 %[[VAL_bf_ashr]] to i32
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_66]], ptr noundef @[[STR_4]], ptr noundef @[[STR_20]], ptr noundef @[[STR_67]], i64 noundef 5, i32 noundef %[[VAL_bf_cast]])
+
+  // CHECK: %[[VAL_b2:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 23
+  // CHECK: %[[VAL_bf_load38:.*]] = load i8, ptr %[[VAL_b2]],
+  // CHECK: %[[VAL_bf_shl39:.*]] = shl i8 %[[VAL_bf_load38]], 5
+  // CHECK: %[[VAL_bf_ashr40:.*]] = ashr i8 %[[VAL_bf_shl39]], 5
+  // CHECK: %[[VAL_bf_cast41:.*]] = sext i8 %[[VAL_bf_ashr40]] to i32
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_66]], ptr noundef @[[STR_4]], ptr noundef @[[STR_20]], ptr noundef @[[STR_68]], i64 noundef 3, i32 noundef %[[VAL_bf_cast41]])
+
+  // CHECK: %[[VAL_ci1:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 24
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_53]], ptr noundef @[[STR_4]], ptr noundef @[[STR_69]], ptr noundef @[[STR_70]], ptr noundef %[[VAL_ci1]])
+
+  // CHECK: %[[VAL_cd1:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_0]], i32 0, i32 25
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_53]], ptr noundef @[[STR_4]], ptr noundef @[[STR_71]], ptr noundef @[[STR_72]], ptr noundef %[[VAL_cd1]])
+
+  // CHECK: call {{.*}} @printf(ptr noundef @[[STR_73]])
+  __builtin_dump_struct(a, printf);
+
+}

diff  --git a/clang/test/CodeGen/dump-struct-builtin.c b/clang/test/CodeGen/dump-struct-builtin.c
deleted file mode 100644
index 730119c897dd..000000000000
--- a/clang/test/CodeGen/dump-struct-builtin.c
+++ /dev/null
@@ -1,809 +0,0 @@
-// RUN: %clang_cc1 -no-opaque-pointers -triple x86_64-unknown-unknown -emit-llvm %s -o - | FileCheck %s
-
-#include "Inputs/stdio.h"
-#include <stdint.h>
-
-// CHECK: @__const.unit1.a = private unnamed_addr constant %struct.U1A { i16 12 }, align 2
-// CHECK-NEXT: [[STRUCT_STR_U1:@[0-9]+]] = private unnamed_addr constant [12 x i8] c"struct U1A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U1:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U1:@[0-9]+]] = private unnamed_addr constant [15 x i8] c"    short a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U1:@[0-9]+]] = private unnamed_addr constant [5 x i8] c"%hd\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U1:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit2.a = private unnamed_addr constant %struct.U2A { i16 12 }, align 2
-// CHECK-NEXT: [[STRUCT_STR_U2:@[0-9]+]] = private unnamed_addr constant [12 x i8] c"struct U2A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U2:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U2:@[0-9]+]] = private unnamed_addr constant [24 x i8] c"    unsigned short a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U2:@[0-9]+]] = private unnamed_addr constant [5 x i8] c"%hu\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U2:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit3.a = private unnamed_addr constant %struct.U3A { i32 12 }, align 4
-// CHECK-NEXT: [[STRUCT_STR_U3:@[0-9]+]] = private unnamed_addr constant [12 x i8] c"struct U3A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U3:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U3:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"    int a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U3:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U3:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit4.a = private unnamed_addr constant %struct.U4A { i32 12 }, align 4
-// CHECK-NEXT: [[STRUCT_STR_U4:@[0-9]+]] = private unnamed_addr constant [12 x i8] c"struct U4A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U4:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U4:@[0-9]+]] = private unnamed_addr constant [22 x i8] c"    unsigned int a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U4:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%u\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U4:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit5.a = private unnamed_addr constant %struct.U5A { i64 12 }, align 8
-// CHECK-NEXT: [[STRUCT_STR_U5:@[0-9]+]] = private unnamed_addr constant [12 x i8] c"struct U5A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U5:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U5:@[0-9]+]] = private unnamed_addr constant [14 x i8] c"    long a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U5:@[0-9]+]] = private unnamed_addr constant [5 x i8] c"%ld\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U5:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit6.a = private unnamed_addr constant %struct.U6A { i64 12 }, align 8
-// CHECK-NEXT: [[STRUCT_STR_U6:@[0-9]+]] = private unnamed_addr constant [12 x i8] c"struct U6A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U6:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U6:@[0-9]+]] = private unnamed_addr constant [23 x i8] c"    unsigned long a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U6:@[0-9]+]] = private unnamed_addr constant [5 x i8] c"%lu\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U6:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit7.a = private unnamed_addr constant %struct.U7A { i64 12 }, align 8
-// CHECK-NEXT: [[STRUCT_STR_U7:@[0-9]+]] = private unnamed_addr constant [12 x i8] c"struct U7A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U7:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U7:@[0-9]+]] = private unnamed_addr constant [19 x i8] c"    long long a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U7:@[0-9]+]] = private unnamed_addr constant [6 x i8] c"%lld\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U7:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit8.a = private unnamed_addr constant %struct.U8A { i64 12 }, align 8
-// CHECK-NEXT: [[STRUCT_STR_U8:@[0-9]+]] = private unnamed_addr constant [12 x i8] c"struct U8A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U8:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U8:@[0-9]+]] = private unnamed_addr constant [28 x i8] c"    unsigned long long a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U8:@[0-9]+]] = private unnamed_addr constant [6 x i8] c"%llu\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U8:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit9.a = private unnamed_addr constant %struct.U9A { i8 97 }, align 1
-// CHECK-NEXT: [[STRUCT_STR_U9:@[0-9]+]] = private unnamed_addr constant [12 x i8] c"struct U9A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U9:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U9:@[0-9]+]] = private unnamed_addr constant [14 x i8] c"    char a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U9:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%c\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U9:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @.str = private unnamed_addr constant [4 x i8] c"LSE\00", align 1
-// CHECK: @__const.unit10.a = private unnamed_addr constant %struct.U10A { i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0) }, align 8
-// CHECK-NEXT: [[STRUCT_STR_U10:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U10A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U10:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U10:@[0-9]+]] = private unnamed_addr constant [16 x i8] c"    char * a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U10:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U10:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit11.a = private unnamed_addr constant %struct.U11A { i8* inttoptr (i64 305419896 to i8*) }, align 8
-// CHECK-NEXT: [[STRUCT_STR_U11:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U11A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U11:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U11:@[0-9]+]] = private unnamed_addr constant [16 x i8] c"    void * a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U11:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%p\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U11:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit12.a = private unnamed_addr constant %struct.U12A { i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0) }, align 8
-// CHECK-NEXT: [[STRUCT_STR_U12:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U12A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U12:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U12:@[0-9]+]] = private unnamed_addr constant [22 x i8] c"    const char * a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U12:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U12:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit13.a = private unnamed_addr constant %struct.U13A { i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0) }, align 8
-// CHECK-NEXT: [[STRUCT_STR_U13:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U13A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U13:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U13:@[0-9]+]] = private unnamed_addr constant [24 x i8] c"    const charstar a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U13:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U13:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit14.a = private unnamed_addr constant %struct.U14A { double 0x3FF1F9ACFFA7EB6C }, align 8
-// CHECK-NEXT: [[STRUCT_STR_U14:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U14A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U14:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U14:@[0-9]+]] = private unnamed_addr constant [16 x i8] c"    double a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U14:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%f\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U14:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit15.a = private unnamed_addr constant %struct.U15A { [3 x i32] [i32 1, i32 2, i32 3] }, align 4
-// CHECK-NEXT: [[STRUCT_STR_U15:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U15A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U15:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U15:@[0-9]+]] = private unnamed_addr constant [16 x i8] c"    int[3] a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U15:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%p\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U15:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit16.a = private unnamed_addr constant %struct.U16A { i8 12 }, align 1
-// CHECK-NEXT: [[STRUCT_STR_U16:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U16A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U16:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U16:@[0-9]+]] = private unnamed_addr constant [17 x i8] c"    uint8_t a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U16:@[0-9]+]] = private unnamed_addr constant [6 x i8] c"%hhu\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U16:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit17.a = private unnamed_addr constant %struct.U17A { i8 12 }, align 1
-// CHECK-NEXT: [[STRUCT_STR_U17:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U17A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U17:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U17:@[0-9]+]] = private unnamed_addr constant [16 x i8] c"    int8_t a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U17:@[0-9]+]] = private unnamed_addr constant [6 x i8] c"%hhd\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U17:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit18.a = private unnamed_addr constant %struct.U18A { x86_fp80 0xK3FFF8FCD67FD3F5B6000 }, align 16
-// CHECK-NEXT: [[STRUCT_STR_U18:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U18A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U18:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U18:@[0-9]+]] = private unnamed_addr constant [21 x i8] c"    long double a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U18:@[0-9]+]] = private unnamed_addr constant [5 x i8] c"%Lf\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U18:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit19.a = private unnamed_addr constant %struct.U19B { %struct.U19A { i32 2022 } }, align 4
-// CHECK-NEXT: [[STRUCT_STR_U19:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U19B \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U19:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U19:@[0-9]+]] = private unnamed_addr constant [21 x i8] c"    struct U19A a = \00", align 1
-// CHECK-NEXT: [[NESTED_STRUCT_L_BRACE_U19:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[NESTED_FIELD_U19:@[0-9]+]] = private unnamed_addr constant [17 x i8] c"        int a = \00", align 1
-// CHECK-NEXT: [[NESTED_FORMAT_U19:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
-// CHECK-NEXT: [[NESTED_STRUCT_R_BRACE_U19:@[0-9]+]] = private unnamed_addr constant [7 x i8] c"    }\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U19:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-// CHECK: @__const.unit20.a = private unnamed_addr constant %struct.U20A { i8 1 }, align 1
-// CHECK-NEXT: [[STRUCT_STR_U20:@[0-9]+]] = private unnamed_addr constant [13 x i8] c"struct U20A \00", align 1
-// CHECK-NEXT: [[STRUCT_L_BRACE_U20:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"{\0A\00", align 1
-// CHECK-NEXT: [[FIELD_U20:@[0-9]+]] = private unnamed_addr constant [15 x i8] c"    _Bool a = \00", align 1
-// CHECK-NEXT: [[FORMAT_U20:@[0-9]+]] = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
-// CHECK-NEXT: [[STRUCT_R_BRACE_U20:@[0-9]+]] = private unnamed_addr constant [3 x i8] c"}\0A\00", align 1
-
-int printf(const char *fmt, ...) {
-    return 0;
-}
-
-void unit1(void) {
-  struct U1A {
-    short a;
-  };
-
-  struct U1A a = {
-      .a = 12,
-  };
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* [[STRUCT_STR_U1]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U1]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([15 x i8], [15 x i8]* [[FIELD_U1]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U1A, %struct.U1A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i16, i16* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([5 x i8], [5 x i8]* [[FORMAT_U1]], i32 0, i32 0), i16 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U1]], i32 0, i32 0))
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit2(void) {
-  struct U2A {
-    unsigned short a;
-  };
-
-  struct U2A a = {
-      .a = 12,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* [[STRUCT_STR_U2]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U2]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([24 x i8], [24 x i8]* [[FIELD_U2]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U2A, %struct.U2A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i16, i16* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([5 x i8], [5 x i8]* [[FORMAT_U2]], i32 0, i32 0), i16 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U2]], i32 0, i32 0))
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit3(void) {
-  struct U3A {
-    int a;
-  };
-
-  struct U3A a = {
-      .a = 12,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* [[STRUCT_STR_U3]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U3]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[FIELD_U3]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U3A, %struct.U3A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i32, i32* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[FORMAT_U3]], i32 0, i32 0), i32 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U3]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit4(void) {
-  struct U4A {
-    unsigned int a;
-  };
-
-  struct U4A a = {
-      .a = 12,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* [[STRUCT_STR_U4]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U4]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([22 x i8], [22 x i8]* [[FIELD_U4]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U4A, %struct.U4A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i32, i32* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[FORMAT_U4]], i32 0, i32 0), i32 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U4]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit5(void) {
-  struct U5A {
-    long a;
-  };
-
-  struct U5A a = {
-      .a = 12,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* [[STRUCT_STR_U5]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U5]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* [[FIELD_U5]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U5A, %struct.U5A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i64, i64* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([5 x i8], [5 x i8]* [[FORMAT_U5]], i32 0, i32 0), i64 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U5]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit6(void) {
-  struct U6A {
-    unsigned long a;
-  };
-
-  struct U6A a = {
-      .a = 12,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* [[STRUCT_STR_U6]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U6]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([23 x i8], [23 x i8]* [[FIELD_U6]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U6A, %struct.U6A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i64, i64* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([5 x i8], [5 x i8]* [[FORMAT_U6]], i32 0, i32 0), i64 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U6]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit7(void) {
-  struct U7A {
-    long long a;
-  };
-
-  struct U7A a = {
-      .a = 12,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* [[STRUCT_STR_U7]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U7]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([19 x i8], [19 x i8]* [[FIELD_U7]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U7A, %struct.U7A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i64, i64* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* [[FORMAT_U7]], i32 0, i32 0), i64 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U7]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit8(void) {
-  struct U8A {
-    unsigned long long a;
-  };
-
-  struct U8A a = {
-      .a = 12,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* [[STRUCT_STR_U8]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U8]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([28 x i8], [28 x i8]* [[FIELD_U8]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U8A, %struct.U8A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i64, i64* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* [[FORMAT_U8]], i32 0, i32 0), i64 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U8]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit9(void) {
-  struct U9A {
-    char a;
-  };
-
-  struct U9A a = {
-      .a = 'a',
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* [[STRUCT_STR_U9]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U9]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* [[FIELD_U9]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U9A, %struct.U9A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i8, i8* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[FORMAT_U9]], i32 0, i32 0), i8 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U9]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit10(void) {
-  struct U10A {
-    char *a;
-  };
-
-  struct U10A a = {
-      .a = "LSE",
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U10]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U10]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([16 x i8], [16 x i8]* [[FIELD_U10]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U10A, %struct.U10A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i8*, i8** [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[FORMAT_U10]], i32 0, i32 0), i8* [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U10]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit11(void) {
-  struct U11A {
-    void *a;
-  };
-
-  struct U11A a = {
-      .a = (void *)0x12345678,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U11]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U11]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([16 x i8], [16 x i8]* [[FIELD_U11]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U11A, %struct.U11A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i8*, i8** [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[FORMAT_U11]], i32 0, i32 0), i8* [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U11]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit12(void) {
-  struct U12A {
-    const char *a;
-  };
-
-  struct U12A a = {
-      .a = "LSE",
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U12]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U12]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([22 x i8], [22 x i8]* [[FIELD_U12]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U12A, %struct.U12A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i8*, i8** [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[FORMAT_U12]], i32 0, i32 0), i8* [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U12]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit13(void) {
-  typedef char *charstar;
-  struct U13A {
-    const charstar a;
-  };
-
-  struct U13A a = {
-      .a = "LSE",
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U13]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U13]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([24 x i8], [24 x i8]* [[FIELD_U13]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U13A, %struct.U13A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i8*, i8** [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[FORMAT_U13]], i32 0, i32 0), i8* [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U13]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit14(void) {
-  struct U14A {
-    double a;
-  };
-
-  struct U14A a = {
-      .a = 1.123456,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U14]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U14]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([16 x i8], [16 x i8]* [[FIELD_U14]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U14A, %struct.U14A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load double, double* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[FORMAT_U14]], i32 0, i32 0), double [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U14]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit15(void) {
-  struct U15A {
-    int a[3];
-  };
-
-  struct U15A a = {
-      .a = {1, 2, 3},
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U15]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U15]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([16 x i8], [16 x i8]* [[FIELD_U15]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U15A, %struct.U15A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load [3 x i32], [3 x i32]* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[FORMAT_U15]], i32 0, i32 0), [3 x i32] [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U15]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit16(void) {
-  struct U16A {
-    uint8_t a;
-  };
-
-  struct U16A a = {
-      .a = 12,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U16]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U16]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([17 x i8], [17 x i8]* [[FIELD_U16]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U16A, %struct.U16A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i8, i8* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* [[FORMAT_U16]], i32 0, i32 0), i8 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U16]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit17(void) {
-  struct U17A {
-    int8_t a;
-  };
-
-  struct U17A a = {
-      .a = 12,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U17]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U17]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([16 x i8], [16 x i8]* [[FIELD_U17]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U17A, %struct.U17A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i8, i8* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* [[FORMAT_U17]], i32 0, i32 0), i8 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U17]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit18(void) {
-  struct U18A {
-    long double a;
-  };
-
-  struct U18A a = {
-      .a = 1.123456,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U18]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U18]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([21 x i8], [21 x i8]* [[FIELD_U18]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U18A, %struct.U18A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load x86_fp80, x86_fp80* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([5 x i8], [5 x i8]* [[FORMAT_U18]], i32 0, i32 0), x86_fp80 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U18]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit19(void) {
-
-  struct U19A {
-    int a;
-  };
-
-  struct U19B {
-    struct U19A a;
-  };
-
-  struct U19B a = {
-    .a.a = 2022
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U19]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U19]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([21 x i8], [21 x i8]* [[FIELD_U19]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U19B, %struct.U19B* %a, i32 0, i32 0
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[NESTED_STRUCT_L_BRACE_U19]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([17 x i8], [17 x i8]* [[NESTED_FIELD_U19]], i32 0, i32 0))
-  // CHECK: [[RES2:%.*]] = getelementptr inbounds %struct.U19A, %struct.U19A* [[RES1]], i32 0, i32 0
-  // CHECK: [[LOAD2:%.*]] = load i32, i32* [[RES2]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[NESTED_FORMAT_U19]], i32 0, i32 0), i32 [[LOAD2]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([7 x i8], [7 x i8]* [[NESTED_STRUCT_R_BRACE_U19]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U19]], i32 0, i32 0))
-  __builtin_dump_struct(&a, &printf);
-}
-
-void unit20(void) {
-  struct U20A {
-    _Bool a;
-  };
-
-  struct U20A a = {
-    .a = 1,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* [[STRUCT_STR_U20]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_L_BRACE_U20]], i32 0, i32 0))
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([15 x i8], [15 x i8]* [[FIELD_U20]], i32 0, i32 0))
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.U20A, %struct.U20A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i8, i8* [[RES1]],
-  // CHECK: [[TOBOOL:%.*]] = trunc i8 [[LOAD1]] to i1
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[FORMAT_U20]], i32 0, i32 0), i1 [[TOBOOL]])
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* [[STRUCT_R_BRACE_U20]], i32 0, i32 0)
-  __builtin_dump_struct(&a, &printf);
-}
-
-void test1(void) {
-  struct T1A {
-    int a;
-    char *b;
-  };
-
-  struct T1A a = {
-      .a = 12,
-      .b = "LSE",
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.T1A, %struct.T1A* %a, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i32, i32* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf({{.*}}, i32 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES2:%.*]] = getelementptr inbounds %struct.T1A, %struct.T1A* %a, i32 0, i32 1
-  // CHECK: [[LOAD2:%.*]] = load i8*, i8** [[RES2]],
-  // CHECK: call i32 (i8*, ...) @printf({{.*}}, i8* [[LOAD2]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  __builtin_dump_struct(&a, &printf);
-}
-
-void test2(void) {
-  struct T2A {
-    int a;
-  };
-
-  struct T2B {
-    int b;
-    struct T2A a;
-  };
-
-  struct T2B b = {
-      .b = 24,
-      .a = {
-          .a = 12,
-      }
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.T2B, %struct.T2B* %b, i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i32, i32* [[RES1]],
-  // CHECK: call i32 (i8*, ...) @printf({{.*}}, i32 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[NESTED_STRUCT:%.*]] = getelementptr inbounds %struct.T2B, %struct.T2B* %b, i32 0, i32 1
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES2:%.*]] = getelementptr inbounds %struct.T2A, %struct.T2A* [[NESTED_STRUCT]], i32 0, i32 0
-  // CHECK: [[LOAD2:%.*]] = load i32, i32* [[RES2]],
-  // CHECK: call i32 (i8*, ...) @printf({{.*}}, i32 [[LOAD2]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  __builtin_dump_struct(&b, &printf);
-}
-
-void test3(void) {
-  struct T3A {
-    union {
-      int a;
-      char b[4];
-    };
-  };
-
-  struct T3A a = {
-      .a = 42,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.T3A, %struct.T3A* %a, i32 0, i32 0
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC1:%.*]] = bitcast %union.anon* [[RES1]] to i32*
-  // CHECK: [[LOAD1:%.*]] = load i32, i32* [[BC1]],
-  // CHECK: call i32 (i8*, ...) @printf({{.*}}, i32 [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC2:%.*]] = bitcast %union.anon* [[RES1]] to [4 x i8]*
-  // CHECK: [[LOAD2:%.*]] = load [4 x i8], [4 x i8]* [[BC2]],
-  // CHECK: call i32 (i8*, ...) @printf({{.*}}, [4 x i8] [[LOAD2]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  __builtin_dump_struct(&a, &printf);
-}
-
-void test4(void) {
-  struct T4A {
-    union {
-      struct {
-        void *a;
-      };
-      struct {
-        unsigned long b;
-      };
-    };
-  };
-
-  struct T4A a = {
-      .a = (void *)0x12345678,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.T4A, %struct.T4A* %a, i32 0, i32 0
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC1:%.*]] = bitcast %union.anon.0* [[RES1]] to %struct.anon*
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES2:%.*]] = getelementptr inbounds %struct.anon, %struct.anon* [[BC1]], i32 0, i32 0
-  // CHECK: [[LOAD1:%.*]] = load i8*, i8** [[RES2]],
-  // CHECK: call i32 (i8*, ...) @printf({{.*}}, i8* [[LOAD1]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC2:%.*]] = bitcast %union.anon.0* [[RES1]] to %struct.anon.1*
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES3:%.*]] = getelementptr inbounds %struct.anon.1, %struct.anon.1* [[BC2]], i32 0, i32 0
-  // CHECK: [[LOAD2:%.*]] = load i64, i64* [[RES3]],
-  // CHECK: call i32 (i8*, ...) @printf({{.*}}, i64 [[LOAD2]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  __builtin_dump_struct(&a, &printf);
-}
-
-void test5(void) {
-  struct T5A {
-    unsigned a : 1;
-  };
-
-  struct T5A a = {
-    .a = 0,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC1:%.*]] = bitcast %struct.T5A* %a to i8*
-  // CHECK: [[LOAD1:%.*]] = load i8, i8* [[BC1]],
-  // CHECK: [[CLEAR1:%.*]] = and i8 [[LOAD1]], 1
-  // CHECK: [[CAST1:%.*]] = zext i8 [[CLEAR1]] to i32
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ({{.*}}, i32 [[CAST1]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  __builtin_dump_struct(&a, &printf);
-}
-
-void test6(void) {
-  struct T6A {
-    unsigned a : 1;
-    unsigned b : 1;
-    unsigned c : 1;
-  };
-
-  struct T6A a = {
-    .a = 1,
-    .b = 0,
-    .c = 1,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC1:%.*]] = bitcast %struct.T6A* %a to i8*
-  // CHECK: [[LOAD1:%.*]] = load i8, i8* [[BC1]],
-  // CHECK: [[CLEAR1:%.*]] = and i8 [[LOAD1]], 1
-  // CHECK: [[CAST1:%.*]] = zext i8 [[CLEAR1]] to i32
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ({{.*}}, i32 [[CAST1]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC2:%.*]] = bitcast %struct.T6A* %a to i8*
-  // CHECK: [[LOAD2:%.*]] = load i8, i8* [[BC2]], align 4
-  // CHECK: [[LSHR2:%.*]] = lshr i8 [[LOAD2]], 1
-  // CHECK: [[CLEAR2:%.*]] = and i8 [[LSHR2]], 1
-  // CHECK: [[CAST2:%.*]] = zext i8 [[CLEAR2]] to i32
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ({{.*}}, i32 [[CAST2]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC3:%.*]] = bitcast %struct.T6A* %a to i8*
-  // CHECK: [[LOAD3:%.*]] = load i8, i8* [[BC3]], align 4
-  // CHECK: [[LSHR3:%.*]] = lshr i8 [[LOAD3]], 2
-  // CHECK: [[CLEAR3:%.*]] = and i8 [[LSHR3]], 1
-  // CHECK: [[CAST3:%.*]] = zext i8 [[CLEAR3]] to i32
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ({{.*}}, i32 [[CAST3]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  __builtin_dump_struct(&a, &printf);
-}
-
-void test7(void) {
-
-  struct T7A {
-    unsigned a : 1;
-  };
-
-  struct T7B {
-    struct T7A a;
-    unsigned b : 1;
-  };
-
-  struct T7B a = {
-    .a = {.a = 0},
-    .b = 1,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES1:%.*]] = getelementptr inbounds %struct.T7B, %struct.T7B* %a, i32 0, i32 0
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC1:%.*]] = bitcast %struct.T7A* [[RES1]] to i8*
-  // CHECK: [[LOAD1:%.*]] = load i8, i8* [[BC1]],
-  // CHECK: [[CLEAR1:%.*]] = and i8 [[LOAD1]], 1
-  // CHECK: [[CAST1:%.*]] = zext i8 [[CLEAR1]] to i32
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ({{.*}}, i32 [[CAST1]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES2:%.*]] = getelementptr inbounds %struct.T7B, %struct.T7B* %a, i32 0, i32 1
-  // CHECK: [[LOAD2:%.*]] = load i8, i8* [[RES2]], align 4
-  // CHECK: [[CLEAR2:%.*]] = and i8 [[LOAD2]], 1
-  // CHECK: [[CAST2:%.*]] = zext i8 [[CLEAR2]] to i32
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ({{.*}}, i32 [[CAST2]])
-  // CHECK: call i32 (i8*, ...) @printf(
-   __builtin_dump_struct(&a, &printf);
-}
-
-void test8(void) {
-  struct T8A {
-    unsigned c : 1;
-    unsigned : 3;
-    unsigned : 0;
-    unsigned b;
-  };
-
-  struct T8A a = {
-    .b = 2022,
-  };
-
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC1:%.*]] = bitcast %struct.T8A* %a to i8*
-  // CHECK: [[LOAD1:%.*]] = load i8, i8* [[BC1]],
-  // CHECK: [[CLEAR1:%.*]] = and i8 [[LOAD1]], 1
-  // CHECK: [[CAST1:%.*]] = zext i8 [[CLEAR1]] to i32
-  // CHECK: call i32 (i8*, ...) @printf({{.*}}, i32 [[CAST1]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[BC2:%.*]] = bitcast %struct.T8A* %a to i8*
-  // CHECK: [[LOAD2:%.*]] = load i8, i8* [[BC2]],
-  // CHECK: [[LSHR2:%.*]] = lshr i8 [[LOAD2]], 1
-  // CHECK: [[CLEAR2:%.*]] = and i8 [[LSHR2]], 7
-  // CHECK: [[CAST2:%.*]] = zext i8 [[CLEAR2]] to i32
-  // CHECK: call i32 (i8*, ...) @printf({{.*}}, i32 0, i32 0), i32 [[CAST2]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: call i32 (i8*, ...) @printf(
-  // CHECK: [[RES3:%.*]] = getelementptr inbounds %struct.T8A, %struct.T8A* %a, i32 0, i32 1
-  // CHECK: [[LOAD3:%.*]] = load i32, i32* [[RES3]],
-  // CHECK: call i32 (i8*, ...) @printf(i8* getelementptr inbounds ({{.*}}, i32 [[LOAD3]])
-  // CHECK: call i32 (i8*, ...) @printf(
-  __builtin_dump_struct(&a, &printf);
-}
-

diff  --git a/clang/test/CodeGenCXX/builtin-dump-struct.cpp b/clang/test/CodeGenCXX/builtin-dump-struct.cpp
new file mode 100644
index 000000000000..4f5f5b924c6b
--- /dev/null
+++ b/clang/test/CodeGenCXX/builtin-dump-struct.cpp
@@ -0,0 +1,125 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-linux-gnu %s -emit-llvm -o - | FileCheck %s
+
+// CHECK-DAG: @[[STR_0:.*]] = {{.*}} [3 x i8] c"%s\00",
+// CHECK-DAG: @[[STR_1:.*]] = {{.*}} [2 x i8] c"C\00",
+// CHECK-DAG: @[[STR_2:.*]] = {{.*}} [4 x i8] c" {\0A\00",
+// CHECK-DAG: @[[STR_3:.*]] = {{.*}} [5 x i8] c"%s%s\00",
+// CHECK-DAG: @[[STR_4:.*]] = {{.*}} [3 x i8] c"  \00",
+// CHECK-DAG: @[[STR_5:.*]] = {{.*}} [2 x i8] c"A\00",
+// CHECK-DAG: @[[STR_6:.*]] = {{.*}} [14 x i8] c"%s%s %s = %d\0A\00",
+// CHECK-DAG: @[[STR_7:.*]] = {{.*}} [5 x i8] c"    \00",
+// CHECK-DAG: @[[STR_8:.*]] = {{.*}} [4 x i8] c"int\00",
+// CHECK-DAG: @[[STR_9:.*]] = {{.*}} [2 x i8] c"n\00",
+// CHECK-DAG: @[[STR_10:.*]] = {{.*}} [5 x i8] c"%s}\0A\00",
+// CHECK-DAG: @[[STR_11:.*]] = {{.*}} [2 x i8] c"B\00",
+// CHECK-DAG: @[[STR_12:.*]] = {{.*}} [10 x i8] c"%s%s %s =\00",
+// CHECK-DAG: @[[STR_13:.*]] = {{.*}} [2 x i8] c"a\00",
+// CHECK-DAG: @[[STR_14:.*]] = {{.*}} [15 x i8] c"%s%s %s = *%p\0A\00",
+// CHECK-DAG: @[[STR_15:.*]] = {{.*}} [2 x i8] c"X\00",
+// CHECK-DAG: @[[STR_16:.*]] = {{.*}} [2 x i8] c"x\00",
+// CHECK-DAG: @[[STR_17:.*]] = {{.*}} [2 x i8] c"f\00",
+// CHECK-DAG: @[[STR_18:.*]] = {{.*}} [2 x i8] c"g\00",
+// CHECK-DAG: @[[STR_19:.*]] = {{.*}} [3 x i8] c"}\0A\00",
+
+struct A { int n; };
+struct B { int n; };
+class X { private: int n; };
+struct C : A, B { A a; X x; int f, g; };
+
+template<typename ...T> int format(int a, const char *str, T ...);
+
+int f();
+
+// CHECK-LABEL: define {{.*}} @_Z1gR1C(
+void g(C &c) {
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJPKcEEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_0]], ptr noundef @[[STR_1]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJEEiiPKcDpT_(i32 {{.*}}, ptr noundef @[[STR_2]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJPKcS1_EEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_3]], ptr noundef @[[STR_4]], ptr noundef @[[STR_5]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJEEiiPKcDpT_(i32 {{.*}}, ptr noundef @[[STR_2]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: %[[VAL_n:.*]] = getelementptr inbounds %[[VAL_struct_A:.*]], ptr %[[VAL_0:.*]], i32 0, i32 0
+  // CHECK: %[[VAL_1:.*]] = load i32, ptr %[[VAL_n]],
+  // CHECK: call {{.*}} @_Z6formatIJPKcS1_S1_iEEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_6]], ptr noundef @[[STR_7]], ptr noundef @[[STR_8]], ptr noundef @[[STR_9]], i32 noundef %[[VAL_1]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJPKcEEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_10]], ptr noundef @[[STR_4]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJPKcS1_EEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_3]], ptr noundef @[[STR_4]], ptr noundef @[[STR_11]])
+
+  // CHECK: %[[VAL_2:.*]] = icmp eq ptr %[[VAL_0]], null
+  // CHECK: br i1 %[[VAL_2]],
+
+  // CHECK: %[[VAL_add_ptr:.*]] = getelementptr inbounds i8, ptr %[[VAL_0]], i64 4
+  // CHECK: br label
+
+  // CHECK: %[[VAL_cast_result:.*]] = phi
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJEEiiPKcDpT_(i32 {{.*}}, ptr noundef @[[STR_2]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: %[[VAL_n17:.*]] = getelementptr inbounds %[[VAL_struct_B:.*]], ptr %[[VAL_cast_result]], i32 0, i32 0
+  // CHECK: %[[VAL_3:.*]] = load i32, ptr %[[VAL_n17]],
+  // CHECK: call {{.*}} @_Z6formatIJPKcS1_S1_iEEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_6]], ptr noundef @[[STR_7]], ptr noundef @[[STR_8]], ptr noundef @[[STR_9]], i32 noundef %[[VAL_3]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJPKcEEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_10]], ptr noundef @[[STR_4]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJPKcS1_S1_EEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_12]], ptr noundef @[[STR_4]], ptr noundef @[[STR_5]], ptr noundef @[[STR_13]])
+
+  // CHECK: %[[VAL_a:.*]] = getelementptr inbounds %[[VAL_struct_C:.*]], ptr %[[VAL_0]], i32 0, i32 2
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJEEiiPKcDpT_(i32 {{.*}}, ptr noundef @[[STR_2]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: %[[VAL_n26:.*]] = getelementptr inbounds %[[VAL_struct_A]], ptr %[[VAL_a]], i32 0, i32 0
+  // CHECK: %[[VAL_4:.*]] = load i32, ptr %[[VAL_n26]],
+  // CHECK: call {{.*}} @_Z6formatIJPKcS1_S1_iEEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_6]], ptr noundef @[[STR_7]], ptr noundef @[[STR_8]], ptr noundef @[[STR_9]], i32 noundef %[[VAL_4]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJPKcEEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_10]], ptr noundef @[[STR_4]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: %[[VAL_x:.*]] = getelementptr inbounds %[[VAL_struct_C]], ptr %[[VAL_0]], i32 0, i32 3
+  // CHECK: call {{.*}} @_Z6formatIJPKcS1_S1_P1XEEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_14]], ptr noundef @[[STR_4]], ptr noundef @[[STR_15]], ptr noundef @[[STR_16]], ptr noundef %[[VAL_x]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: %[[VAL_f:.*]] = getelementptr inbounds %[[VAL_struct_C]], ptr %[[VAL_0]], i32 0, i32 4
+  // CHECK: %[[VAL_5:.*]] = load i32, ptr %[[VAL_f]],
+  // CHECK: call {{.*}} @_Z6formatIJPKcS1_S1_iEEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_6]], ptr noundef @[[STR_4]], ptr noundef @[[STR_8]], ptr noundef @[[STR_17]], i32 noundef %[[VAL_5]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: %[[VAL_g:.*]] = getelementptr inbounds %[[VAL_struct_C]], ptr %[[VAL_0]], i32 0, i32 5
+  // CHECK: %[[VAL_6:.*]] = load i32, ptr %[[VAL_g]],
+  // CHECK: call {{.*}} @_Z6formatIJPKcS1_S1_iEEiiS1_DpT_(i32 {{.*}}, ptr noundef @[[STR_6]], ptr noundef @[[STR_4]], ptr noundef @[[STR_8]], ptr noundef @[[STR_18]], i32 noundef %[[VAL_6]])
+
+  // CHECK: call {{.*}} @_Z1fv()
+  // CHECK: call {{.*}} @_Z6formatIJEEiiPKcDpT_(i32 {{.*}}, ptr noundef @[[STR_19]])
+  __builtin_dump_struct(&c, format, f());
+}
+
+// CHECK-LABEL: define {{.*}} @_Z1hR1X(
+void h(X &x) {
+  // CHECK: %[[VAL_x_addr:.*]] = alloca ptr,
+  // CHECK: store ptr %[[VAL_x]], ptr %[[VAL_x_addr]],
+  // CHECK: call {{.*}} @_Z6formatIJPKcEEiiS1_DpT_(i32 noundef 0, ptr noundef @[[STR_0]], ptr noundef @[[STR_15]])
+
+  // CHECK: %[[VAL_0:.*]] = load ptr, ptr %[[VAL_x_addr]],
+  // CHECK: call {{.*}} @_Z6formatIJEEiiPKcDpT_(i32 noundef 0, ptr noundef @[[STR_2]])
+
+  // CHECK: %[[VAL_n:.*]] = getelementptr inbounds %[[VAL_class_X:.*]], ptr %[[VAL_0]], i32 0, i32 0
+  // CHECK: %[[VAL_1:.*]] = load i32, ptr %[[VAL_n]],
+  // CHECK: call {{.*}} @_Z6formatIJPKcS1_S1_iEEiiS1_DpT_(i32 noundef 0, ptr noundef @[[STR_6]], ptr noundef @[[STR_4]], ptr noundef @[[STR_8]], ptr noundef @[[STR_9]], i32 noundef %[[VAL_1]])
+
+  // CHECK: call {{.*}} @_Z6formatIJEEiiPKcDpT_(i32 noundef 0, ptr noundef @[[STR_19]])
+  __builtin_dump_struct(&x, format, 0);
+}

diff  --git a/clang/test/Sema/builtin-dump-struct.c b/clang/test/Sema/builtin-dump-struct.c
index d898bac4103b..d0415a22732f 100644
--- a/clang/test/Sema/builtin-dump-struct.c
+++ b/clang/test/Sema/builtin-dump-struct.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple i386-unknown-unknown -fsyntax-only -fno-spell-checking -Wno-strict-prototypes -verify %s
+// RUN: %clang_cc1 -triple i386-unknown-unknown -fsyntax-only -fno-spell-checking -Wno-strict-prototypes -verify %s -fblocks
 
 void invalid_uses(void) {
   struct A {
@@ -8,23 +8,22 @@ void invalid_uses(void) {
   int (*goodfunc)(const char *, ...);
   int (*badfunc1)(const char *);
   int (*badfunc2)(int, ...);
-  void (*badfunc3)(const char *, ...);
-  int (*badfunc4)(char *, ...);
-  int (*badfunc5)(void);
+  int (*badfunc3)(void);
 
   __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(&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(&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(1, 2);         // expected-error {{expected pointer to struct as 1st argument to '__builtin_dump_struct', found 'int'}}
+  __builtin_dump_struct(&a, 2);        // expected-error {{expected a callable expression as 2nd argument to '__builtin_dump_struct', found 'int'}}
+  __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 {{too many arguments to function call, expected 1, have 2}} expected-note  {{in call to printing function with arguments '("%s", "struct A")'}}
+  __builtin_dump_struct(&a, badfunc2); // expected-warning-re 1+{{incompatible pointer to integer conversion passing 'char[{{.*}}]' to parameter of type 'int'}}
+                                       // expected-note at -1 1+{{in call to printing function with arguments '("}}
+  __builtin_dump_struct(&a, badfunc3); // expected-error {{too many arguments to function call, expected 0, have 2}} expected-note {{in call to printing function with arguments '("%s", "struct A")'}}
+  __builtin_dump_struct(a, goodfunc);  // expected-error {{expected pointer to struct as 1st argument to '__builtin_dump_struct', found 'struct A'}}
 }
 
+int goodglobalfunc(const char*, ...);
+
 void valid_uses(void) {
   struct A {
   };
@@ -33,10 +32,18 @@ void valid_uses(void) {
 
   int (*goodfunc)(const char *, ...);
   int (*goodfunc2)();
+  void (*goodfunc3)(const char *, ...);
+  int (*goodfunc4)(char *, ...);
+  int (^goodblock)(const char*, ...);
   struct A a;
   union B b;
 
+  __builtin_dump_struct(&a, goodglobalfunc);
+  __builtin_dump_struct(&a, &goodglobalfunc);
   __builtin_dump_struct(&a, goodfunc);
   __builtin_dump_struct(&b, goodfunc);
   __builtin_dump_struct(&a, goodfunc2);
+  __builtin_dump_struct(&a, goodfunc3);
+  __builtin_dump_struct(&a, goodfunc4);
+  __builtin_dump_struct(&a, goodblock);
 }

diff  --git a/clang/test/SemaCXX/builtin-dump-struct.cpp b/clang/test/SemaCXX/builtin-dump-struct.cpp
new file mode 100644
index 000000000000..e057eac02946
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-dump-struct.cpp
@@ -0,0 +1,161 @@
+// RUN: %clang_cc1 -std=c++20 -verify %s
+
+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() : ConstexprString("") {}
+  constexpr ConstexprString(const char *p, std::size_t size) : data(new char[size+1]) {
+    __builtin_memcpy(data, p, size);
+    data[size] = '\0';
+  }
+  constexpr ConstexprString(const char *p) : ConstexprString(p, __builtin_strlen(p)) {}
+  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(const ConstexprString &o) : ConstexprString(o.data) {}
+  constexpr ConstexprString(ConstexprString &&o) : data(o.data) { o.data = nullptr; }
+  constexpr ConstexprString &operator=(const ConstexprString &o) {
+    return *this = ConstexprString(o);
+  }
+  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 ConstexprString &operator+=(ConstexprString &a, const ConstexprString &b) {
+    return a = a + b;
+  }
+  friend constexpr bool operator==(const ConstexprString &a, const ConstexprString &b) {
+    return __builtin_strcmp(a.data, b.data) == 0;
+  }
+};
+
+template<typename... T> constexpr void Format(ConstexprString &out, const char *fmt, T... args);
+
+struct Arg {
+  template<typename T, int (*)[__is_integral(T) ? 1 : -1] = nullptr>
+  constexpr Arg(T value) {
+    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;
+  }
+  template<typename T, int (*)[__is_class(T) ? 1 : -1] = nullptr>
+  constexpr Arg(const T &value) {
+    __builtin_dump_struct(&value, Format, s);
+  }
+  constexpr Arg(const char *s) : s(s) {}
+  constexpr Arg(const ConstexprString *s) : s("\"" + *s + "\"") {}
+  template<typename T, int (*)[__is_integral(T) ? 1 : -1] = nullptr>
+  constexpr Arg(const T *p) : s("reference to " + Arg(*p).s) {}
+  ConstexprString s;
+};
+
+template<typename... T> constexpr void Format(ConstexprString &out, const char *fmt, T... args) { // #Format
+  Arg formatted_args[] = {args...};
+  int i = 0;
+  while (const char *percent = __builtin_strchr(fmt, '%')) {
+    if (percent[1] == '%') continue;
+    if (percent != fmt && percent[-1] == '*') --percent;
+    out += ConstexprString(fmt, percent - fmt);
+    out += formatted_args[i++].s;
+
+    // Skip past format specifier until we hit a conversion specifier.
+    fmt = percent;
+    while (!__builtin_strchr("diouxXeEfFgGcsp", *fmt)) ++fmt;
+    // Skip the conversion specifier too. TODO: Check it's the right one.
+    ++fmt;
+  }
+  out += ConstexprString(fmt);
+}
+
+template<typename T> constexpr ConstexprString ToString(const T &t) { return Arg(t).s; }
+
+struct A {
+  int x, y, z : 3;
+  int : 4;
+  ConstexprString s;
+};
+struct B : A {
+  int p, q;
+  struct {
+    int anon1, anon2;
+  };
+  union {
+    int anon3;
+  };
+  struct {
+    int m;
+  } c;
+  int &&r;
+};
+
+#if PRINT_OUTPUT
+#include <stdio.h>
+int main() {
+  puts(ToString(B{1, 2, 3, "hello", 4, 5, 6, 7, 8, 9, 10}).data);
+}
+#else
+static_assert(ToString(B{1, 2, 3, "hello", 4, 5, 6, 7, 8, 9, 10}) == &R"(
+B {
+  A {
+    int x = 1
+    int y = 2
+    int z : 3 = 3
+    ConstexprString s = "hello"
+  }
+  int p = 4
+  int q = 5
+  int anon1 = 6
+  int anon2 = 7
+  int anon3 = 8
+  struct (unnamed) c = {
+    int m = 9
+  }
+  int && r = reference to 10
+}
+)"[1]);
+
+void errors(B b) {
+  __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 {{expected pointer to struct as 1st argument to '__builtin_dump_struct', found 'int'}}
+  __builtin_dump_struct(&b, 2); // expected-error {{expected a callable expression as 2nd argument to '__builtin_dump_struct', found 'int'}}
+  __builtin_dump_struct(&b, Format, 0); // expected-error {{no matching function for call to 'Format'}}
+                                        // expected-note at -1 {{in call to printing function with arguments '(0, "%s", "B")' while dumping struct}}
+                                        // expected-note@#Format {{no known conversion from 'int' to 'ConstexprString &' for 1st argument}}
+}
+#endif


        


More information about the cfe-commits mailing list