[clang] [clang][Sema] support block pointers as non-type template parameters (PR #191694)

via cfe-commits cfe-commits at lists.llvm.org
Fri Apr 17 05:12:27 PDT 2026


https://github.com/Serosh-commits updated https://github.com/llvm/llvm-project/pull/191694

>From 7d5b73ca2a379c5240d13137d52ac6d98985b05f Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Sat, 11 Apr 2026 21:15:22 +0530
Subject: [PATCH 1/7] [Clang][Sema] Support block pointers as non-type template
 parameters

- Allow block pointers to be used as template parameters in C++20.
- Add mangling for block literals in template arguments.
- Let ODR hashing see block expressions so they don't cause a crash.
- Fix a crash by making sure block names decay to pointers.
- Allow matching block pointer types even if they have different const qualifiers.

Fixes #189247
---
 clang/lib/AST/ItaniumMangle.cpp          | 12 ++++--
 clang/lib/AST/ODRHash.cpp                |  9 +++-
 clang/lib/Sema/SemaTemplate.cpp          | 33 +++++++++------
 clang/lib/Sema/SemaTemplateDeduction.cpp |  1 +
 clang/test/SemaCXX/nttp-blockpointer.cpp | 53 ++++++++++++++++++++++++
 5 files changed, 89 insertions(+), 19 deletions(-)
 create mode 100644 clang/test/SemaCXX/nttp-blockpointer.cpp

diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index f58faa03bfa8c..af2b1b6ae0f20 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5009,7 +5009,11 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
   }
 
   // FIXME: invent manglings for all these.
-  case Expr::BlockExprClass:
+  case Expr::BlockExprClass: {
+    NotPrimaryExpr();
+    mangleUnqualifiedBlock(cast<BlockExpr>(E)->getBlockDecl());
+    break;
+  }
   case Expr::ChooseExprClass:
   case Expr::CompoundLiteralExprClass:
   case Expr::ExtVectorElementExprClass:
@@ -6775,7 +6779,7 @@ void CXXNameMangler::mangleValueInTemplateArg(QualType T, const APValue &V,
 
   case APValue::LValue: {
     // Proposed in https://github.com/itanium-cxx-abi/cxx-abi/issues/47.
-    assert((T->isPointerOrReferenceType()) &&
+    assert((T->isPointerOrReferenceType() || T->isBlockPointerType()) &&
            "unexpected type for LValue template arg");
 
     if (V.isNullPointer()) {
@@ -6844,7 +6848,7 @@ void CXXNameMangler::mangleValueInTemplateArg(QualType T, const APValue &V,
           Out << "cv";
           mangleType(T);
         }
-        if (T->isPointerType())
+        if (T->isPointerType() || T->isBlockPointerType())
           Out << "ad";
         Out << "so";
         mangleType(T->isVoidPointerType()
@@ -6859,7 +6863,7 @@ void CXXNameMangler::mangleValueInTemplateArg(QualType T, const APValue &V,
           Out << "cv";
           mangleType(T);
         }
-        if (T->isPointerType()) {
+        if (T->isPointerType() || T->isBlockPointerType()) {
           NotPrimaryExpr();
           Out << "ad";
         }
diff --git a/clang/lib/AST/ODRHash.cpp b/clang/lib/AST/ODRHash.cpp
index 46a4e256ea3e5..0f883b240f30c 100644
--- a/clang/lib/AST/ODRHash.cpp
+++ b/clang/lib/AST/ODRHash.cpp
@@ -1270,8 +1270,13 @@ void ODRHash::AddStructuralValue(const APValue &Value) {
       break;
     }
 
-    assert(Base.is<const ValueDecl *>());
-    AddDecl(Base.get<const ValueDecl *>());
+    if (auto *VD = Base.dyn_cast<const ValueDecl *>()) {
+      AddDecl(VD);
+    } else if (auto *E = Base.dyn_cast<const Expr *>()) {
+      AddStmt(E);
+    } else {
+      llvm_unreachable("unexpected LValue base kind in structural value");
+    }
     ID.AddInteger(Value.getLValueOffset().getQuantity());
 
     bool OnePastTheEnd = Value.isLValueOnePastTheEnd();
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index c436b7018a2bd..2851c68c62c7e 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -1459,11 +1459,6 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
     return QualType();
   }
 
-  if (T->isBlockPointerType()) {
-    Diag(Loc, diag::err_template_nontype_parm_bad_type) << T;
-    return QualType();
-  }
-
   // C++ [temp.param]p4:
   //
   // A non-type template-parameter shall have one of the following
@@ -1473,6 +1468,8 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
   if (T->isIntegralOrEnumerationType() ||
       //   -- pointer to object or pointer to function,
       T->isPointerType() ||
+      //   -- block pointer type,
+      (getLangOpts().CPlusPlus20 && T->isBlockPointerType()) ||
       //   -- lvalue reference to object or lvalue reference to function,
       T->isLValueReferenceType() ||
       //   -- pointer to member,
@@ -6670,8 +6667,8 @@ CheckTemplateArgumentIsCompatibleWithParameter(Sema &S, NamedDecl *Param,
                                                QualType ParamType, Expr *ArgIn,
                                                Expr *Arg, QualType ArgType) {
   bool ObjCLifetimeConversion;
-  if (ParamType->isPointerType() &&
-      !ParamType->castAs<PointerType>()->getPointeeType()->isFunctionType() &&
+  if ((ParamType->isPointerType() || ParamType->isBlockPointerType()) &&
+      !ParamType->getPointeeType()->isFunctionType() &&
       S.IsQualificationConversion(ArgType, ParamType, false,
                                   ObjCLifetimeConversion)) {
     // For pointer-to-object types, qualification conversions are
@@ -6821,7 +6818,8 @@ static bool CheckTemplateArgumentAddressOfObjectOrFunction(
     Entity = CUE->getGuidDecl();
 
   // If our parameter has pointer type, check for a null template value.
-  if (ParamType->isPointerType() || ParamType->isNullPtrType()) {
+  if (ParamType->isPointerType() || ParamType->isBlockPointerType() ||
+      ParamType->isNullPtrType()) {
     switch (isNullPointerValueTemplateArgument(S, Param, ParamType, ArgIn,
                                                Entity)) {
     case NPV_NullPointer:
@@ -7355,17 +7353,19 @@ ExprResult Sema::CheckTemplateArgument(NamedDecl *Param, QualType ParamType,
     if (Value.isLValue()) {
       APValue::LValueBase Base = Value.getLValueBase();
       auto *VD = const_cast<ValueDecl *>(Base.dyn_cast<const ValueDecl *>());
+      auto *E = const_cast<Expr *>(Base.dyn_cast<const Expr *>());
       //   For a non-type template-parameter of pointer or reference type,
       //   the value of the constant expression shall not refer to
       assert(ParamType->isPointerOrReferenceType() ||
-             ParamType->isNullPtrType());
+             ParamType->isBlockPointerType() || ParamType->isNullPtrType());
       // -- a temporary object
       // -- a string literal
       // -- the result of a typeid expression, or
       // -- a predefined __func__ variable
       if (Base &&
           (!VD ||
-           isa<LifetimeExtendedTemporaryDecl, UnnamedGlobalConstantDecl>(VD))) {
+           isa<LifetimeExtendedTemporaryDecl, UnnamedGlobalConstantDecl>(VD)) &&
+          (!E || !isa<BlockExpr>(E))) {
         Diag(Arg->getBeginLoc(), diag::err_template_arg_not_decl_ref)
             << Arg->getSourceRange();
         return ExprError();
@@ -7663,13 +7663,16 @@ ExprResult Sema::CheckTemplateArgument(NamedDecl *Param, QualType ParamType,
     return Arg;
   }
 
-  if (ParamType->isPointerType()) {
+  if (ParamType->isPointerType() || ParamType->isBlockPointerType()) {
     //   -- for a non-type template-parameter of type pointer to
     //      object, qualification conversions (4.4) and the
     //      array-to-pointer conversion (4.2) are applied.
     // C++0x also allows a value of std::nullptr_t.
-    assert(ParamType->getPointeeType()->isIncompleteOrObjectType() &&
-           "Only object pointers allowed here");
+    if (ParamType->isPointerType())
+      assert(ParamType->getPointeeType()->isIncompleteOrObjectType() &&
+             "Only object pointers allowed here");
+    else
+      assert(ParamType->isBlockPointerType() && "Expected block pointer");
 
     if (CheckTemplateArgumentAddressOfObjectOrFunction(
             *this, Param, ParamType, Arg, SugaredConverted, CanonicalConverted))
@@ -7976,6 +7979,10 @@ ExprResult Sema::BuildExpressionFromDeclTemplateArgument(
     RefExpr = DefaultFunctionArrayConversion(RefExpr.get());
     if (RefExpr.isInvalid())
       return ExprError();
+  } else if (ParamType->isBlockPointerType()) {
+    RefExpr = DefaultLvalueConversion(RefExpr.get());
+    if (RefExpr.isInvalid())
+      return ExprError();
   } else if (ParamType->isPointerType() || ParamType->isMemberPointerType()) {
     // For any other pointer, take the address (or form a pointer-to-member).
     RefExpr = CreateBuiltinUnaryOp(Loc, UO_AddrOf, RefExpr.get());
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index c71c40526ccdc..66bbc7b1353e1 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4986,6 +4986,7 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
   // both P and A are pointers or member pointers. In this case, we
   // just ignore cv-qualifiers completely).
   if ((P->isPointerType() && A->isPointerType()) ||
+      (P->isBlockPointerType() && A->isBlockPointerType()) ||
       (P->isMemberPointerType() && A->isMemberPointerType()))
     TDF |= TDF_IgnoreQualifiers;
 
diff --git a/clang/test/SemaCXX/nttp-blockpointer.cpp b/clang/test/SemaCXX/nttp-blockpointer.cpp
new file mode 100644
index 0000000000000..eb91f87e5bd5a
--- /dev/null
+++ b/clang/test/SemaCXX/nttp-blockpointer.cpp
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -std=c++20 -fblocks -triple x86_64-apple-darwin %s -verify
+
+template<void (^B)()>
+struct A {
+  void call() { B(); }
+};
+
+constexpr void (^global_block)() = ^{};
+void test_global() {
+  A<global_block> a;
+  a.call();
+}
+
+void test_literal() {
+  A<^{}> a;
+  a.call();
+}
+
+void test_null() {
+  A<nullptr> a;
+}
+
+void test_capturing(int x) {
+  A<^{ (void)x; }> a; // expected-error {{non-type template argument is not a constant expression}}
+}
+
+template<void (^B)()>
+void deduce(A<B> a) {}
+
+void test_deduce() {
+  A<global_block> a;
+  deduce(a);
+}
+
+constexpr void (^another_block)() = ^{};
+static_assert(!__is_same(A<global_block>, A<another_block>));
+
+template<int (^B)(int)>
+struct BFunc {
+  int call(int x) { return B(x); }
+};
+
+void test_params() {
+  BFunc<^(int x) { return x + 1; }> b;
+  (void)b.call(1);
+}
+
+template<auto B>
+struct AutoBlock {};
+
+void test_auto() {
+  AutoBlock<global_block> a;
+}

>From 1551be3f055cad4b56f5ab16d828fe99b67726b0 Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Tue, 14 Apr 2026 00:13:57 +0530
Subject: [PATCH 2/7] Address feedback and fixme update

---
 clang/lib/AST/ItaniumMangle.cpp          | 3 ++-
 clang/lib/Sema/SemaTemplate.cpp          | 2 +-
 clang/test/SemaCXX/blocks.cpp            | 2 +-
 clang/test/SemaCXX/nttp-blockpointer.cpp | 1 +
 4 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index af2b1b6ae0f20..93fc1f64481f3 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5008,12 +5008,13 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
     break;
   }
 
-  // FIXME: invent manglings for all these.
   case Expr::BlockExprClass: {
     NotPrimaryExpr();
     mangleUnqualifiedBlock(cast<BlockExpr>(E)->getBlockDecl());
     break;
   }
+
+  // FIXME: invent manglings for all these.
   case Expr::ChooseExprClass:
   case Expr::CompoundLiteralExprClass:
   case Expr::ExtVectorElementExprClass:
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 2851c68c62c7e..f584d6a135a81 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -1469,7 +1469,7 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
       //   -- pointer to object or pointer to function,
       T->isPointerType() ||
       //   -- block pointer type,
-      (getLangOpts().CPlusPlus20 && T->isBlockPointerType()) ||
+      T->isBlockPointerType() ||
       //   -- lvalue reference to object or lvalue reference to function,
       T->isLValueReferenceType() ||
       //   -- pointer to member,
diff --git a/clang/test/SemaCXX/blocks.cpp b/clang/test/SemaCXX/blocks.cpp
index 67ac7d42f52c9..557482c9a390e 100644
--- a/clang/test/SemaCXX/blocks.cpp
+++ b/clang/test/SemaCXX/blocks.cpp
@@ -165,5 +165,5 @@ void static_data_member() {
 }
 
 namespace gh189247 {
-  template<void (^)()> struct A; // expected-error {{a non-type template parameter cannot have type 'void (^)()'}}
+  template<void (^)()> struct A; 
 }
diff --git a/clang/test/SemaCXX/nttp-blockpointer.cpp b/clang/test/SemaCXX/nttp-blockpointer.cpp
index eb91f87e5bd5a..f5f604332d0ad 100644
--- a/clang/test/SemaCXX/nttp-blockpointer.cpp
+++ b/clang/test/SemaCXX/nttp-blockpointer.cpp
@@ -45,6 +45,7 @@ void test_params() {
   (void)b.call(1);
 }
 
+
 template<auto B>
 struct AutoBlock {};
 

>From 61bb53d7658eeafa340dc881a54a2fc22cb111cc Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Tue, 14 Apr 2026 00:53:08 +0530
Subject: [PATCH 3/7] add codegen tests

---
 clang/test/CodeGenCXX/nttp-blockpointer.cpp | 26 +++++++++++++++++++++
 1 file changed, 26 insertions(+)
 create mode 100644 clang/test/CodeGenCXX/nttp-blockpointer.cpp

diff --git a/clang/test/CodeGenCXX/nttp-blockpointer.cpp b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
new file mode 100644
index 0000000000000..546148623208e
--- /dev/null
+++ b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -std=c++20 -fblocks -triple x86_64-apple-darwin -emit-llvm -o - %s | FileCheck %s
+
+template<void (^B)()> void f() {}
+
+// CHECK: define{{.*}} void @_Z12test_literalv()
+void test_literal() {
+  // CHECK: call void @_Z1fIXcvU13block_pointerFvvEadUb_EEvv()
+  f<^{}>();
+}
+
+constexpr void (^global_block)() = ^{};
+// CHECK: define{{.*}} void @_Z11test_globalv()
+void test_global() {
+  // CHECK: call void @_Z1fIXcvU13block_pointerFvvEadUb0_EEvv()
+  f<global_block>();
+}
+
+template<int (^B)(int)> struct S {
+  static int call(int x) { return B(x); }
+};
+
+// CHECK: define{{.*}} i32 @_Z10test_parami(i32 noundef %x)
+int test_param(int x) {
+  // CHECK: call noundef i32 @_ZN1SIXadUb1_EE4callEi(i32 noundef %0)
+  return S<^(int x) { return x + 1; }>::call(x);
+}

>From 20d64884be83bc2cfe80d5146e535babba9f2e52 Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Wed, 15 Apr 2026 02:06:54 +0530
Subject: [PATCH 4/7] update test

---
 clang/test/CodeGenCXX/nttp-blockpointer.cpp | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/clang/test/CodeGenCXX/nttp-blockpointer.cpp b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
index 546148623208e..7f979bf7b274d 100644
--- a/clang/test/CodeGenCXX/nttp-blockpointer.cpp
+++ b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
@@ -2,14 +2,12 @@
 
 template<void (^B)()> void f() {}
 
-// CHECK: define{{.*}} void @_Z12test_literalv()
 void test_literal() {
   // CHECK: call void @_Z1fIXcvU13block_pointerFvvEadUb_EEvv()
   f<^{}>();
 }
 
 constexpr void (^global_block)() = ^{};
-// CHECK: define{{.*}} void @_Z11test_globalv()
 void test_global() {
   // CHECK: call void @_Z1fIXcvU13block_pointerFvvEadUb0_EEvv()
   f<global_block>();
@@ -19,7 +17,6 @@ template<int (^B)(int)> struct S {
   static int call(int x) { return B(x); }
 };
 
-// CHECK: define{{.*}} i32 @_Z10test_parami(i32 noundef %x)
 int test_param(int x) {
   // CHECK: call noundef i32 @_ZN1SIXadUb1_EE4callEi(i32 noundef %0)
   return S<^(int x) { return x + 1; }>::call(x);

>From 2f54b8d92ddc527f1115b95aa7c95d04e183902b Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Thu, 16 Apr 2026 01:22:36 +0530
Subject: [PATCH 5/7] update test

---
 clang/docs/ReleaseNotes.rst                 |  2 +
 clang/test/CodeGenCXX/nttp-blockpointer.cpp | 47 +++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index fd58d7847717c..4ee8d20e5e99b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -122,6 +122,8 @@ What's New in Clang |release|?
 C++ Language Changes
 --------------------
 
+- Clang now supports block pointers as non-type template parameters. (#GH189247)
+
 - ``__is_trivially_equality_comparable`` no longer returns false for all enum types. (#GH132672)
 
 C++2c Feature Support
diff --git a/clang/test/CodeGenCXX/nttp-blockpointer.cpp b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
index 7f979bf7b274d..ee6f752a227d8 100644
--- a/clang/test/CodeGenCXX/nttp-blockpointer.cpp
+++ b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
@@ -21,3 +21,50 @@ int test_param(int x) {
   // CHECK: call noundef i32 @_ZN1SIXadUb1_EE4callEi(i32 noundef %0)
   return S<^(int x) { return x + 1; }>::call(x);
 }
+
+void test_nullptr() {
+  // CHECK: call void @_Z1fILU13block_pointerFvvE0EEvv()
+  f<nullptr>();
+}
+
+namespace TestNamespace {
+  template<void (^B)()> struct S {
+    static void call() { B(); }
+  };
+  void test_namespace() {
+    // CHECK: call void @_ZN13TestNamespace1SIXadUb2_EE4callEv()
+    S<^{}>::call();
+  }
+}
+
+template<void (^B)() = ^{}>
+void f_default() { B(); }
+
+void test_default() {
+  // CHECK: call void @_Z9f_defaultIXcvU13block_pointerFvvEadUb_EEvv()
+  f_default();
+}
+
+struct Structural {
+  void (^b)();
+};
+
+template<Structural s>
+void f_struct() {
+  s.b();
+}
+
+void test_struct() {
+  // CHECK: call void @_Z8f_structIXtl10StructuraladUb3_EEEvv()
+  f_struct<Structural{^{}}>();
+}
+
+template<void (^...Blocks)()>
+void f_variadic() {
+  (Blocks(), ...);
+}
+
+void test_variadic() {
+  // CHECK: call void @_Z10f_variadicIJXcvU13block_pointerFvvEadUb4_EXcvS1_adUb5_EEEvv()
+  f_variadic<^{}, ^{}>();
+}

>From a076a625a83f7b8b9aaf2e0eb5826f9bac2910e5 Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Thu, 16 Apr 2026 02:58:07 +0530
Subject: [PATCH 6/7] address feedback

---
 clang/lib/Sema/SemaTemplate.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index f584d6a135a81..98910f8a7d254 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -1468,8 +1468,6 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
   if (T->isIntegralOrEnumerationType() ||
       //   -- pointer to object or pointer to function,
       T->isPointerType() ||
-      //   -- block pointer type,
-      T->isBlockPointerType() ||
       //   -- lvalue reference to object or lvalue reference to function,
       T->isLValueReferenceType() ||
       //   -- pointer to member,
@@ -1483,6 +1481,10 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
     return T.getUnqualifiedType();
   }
 
+  // Allow block pointer types as an extension.
+  if (T->isBlockPointerType())
+    return T.getUnqualifiedType();
+
   // C++ [temp.param]p8:
   //
   //   A non-type template-parameter of type "array of T" or

>From 1ca502924fccc077c1ce98c47e894c4005299563 Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Fri, 17 Apr 2026 17:40:20 +0530
Subject: [PATCH 7/7] address feedback

---
 clang/test/CodeGenCXX/nttp-blockpointer.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/clang/test/CodeGenCXX/nttp-blockpointer.cpp b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
index ee6f752a227d8..1a4028a239647 100644
--- a/clang/test/CodeGenCXX/nttp-blockpointer.cpp
+++ b/clang/test/CodeGenCXX/nttp-blockpointer.cpp
@@ -68,3 +68,5 @@ void test_variadic() {
   // CHECK: call void @_Z10f_variadicIJXcvU13block_pointerFvvEadUb4_EXcvS1_adUb5_EEEvv()
   f_variadic<^{}, ^{}>();
 }
+
+// CHECK: define internal void @_Z1fIXcvU13block_pointerFvvEadUb_EEvv()



More information about the cfe-commits mailing list