diff --git a/clang-tools-extra/clang-tidy/utils/Matchers.h b/clang-tools-extra/clang-tidy/utils/Matchers.h index fea3ef041df1c..d3727f3de45ea 100644 --- a/clang-tools-extra/clang-tidy/utils/Matchers.h +++ b/clang-tools-extra/clang-tidy/utils/Matchers.h @@ -58,7 +58,8 @@ AST_MATCHER(QualType, isSimpleChar) { } AST_MATCHER(Expr, hasUnevaluatedContext) { - if (isa(Node) || isa(Node)) + if (isa(Node) || isa(Node) || + isa(Node)) return true; if (const auto *UnaryExpr = dyn_cast(&Node)) { switch (UnaryExpr->getKind()) { diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md index bd9ae30c2607e..f9052cdd86574 100644 --- a/clang/docs/LanguageExtensions.md +++ b/clang/docs/LanguageExtensions.md @@ -514,6 +514,27 @@ These spellings result in the type of the operand, stripping all qualifiers. `char32_t` respectively, but are also available in C++ modes before C++11. They are only supported in C++. `__char8_t` is not available. +### declcall + +`declcall(call-expression)` yields a pointer, or pointer to member, to the +function that overload resolution would select for the *call-expression*, which +is an unevaluated operand: + +```c++ +int f(int); +int f(char); +constexpr auto p = declcall(f(0)); // &f(int) +``` + +For a member function it produces a pointer to member; a qualified call to a +virtual member is devirtualized. `declcall` is ill-formed if the operand is not +a call expression, names a constructor, destructor, or builtin function, or does +not select a function at compile time. + +`declcall` implements P2825, which is not yet standardized. It is enabled as a +native feature under `-fsycl`; otherwise it is a Clang extension diagnosed by +`-Wdeclcall-extension`. + % FIXME: This should list all the keyword extensions (langext-vectors)= diff --git a/clang/docs/LibASTMatchersReference.html b/clang/docs/LibASTMatchersReference.html index 7e0bbbcb18938..3071624d8a2f8 100644 --- a/clang/docs/LibASTMatchersReference.html +++ b/clang/docs/LibASTMatchersReference.html @@ -1667,6 +1667,11 @@

Node Matchers

+Matcher<Stmt>cxxDeclcallExprMatcher<CXXDeclcallExpr>... +
Matches declcall expressions.
+
+ + Matcher<Stmt>cxxDefaultArgExprMatcher<CXXDefaultArgExpr>...
Matches the value of a default argument at the call site.
 
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 9063e54b3e692..420080124b1cf 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -248,6 +248,7 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
 
 - `__is_trivially_equality_comparable` no longer returns false for all enum types. (#GH132672)
 - `auto` parameters are now available in all C++ language modes as an extension.
+- Clang now supports `declcall` ([P2825](https://wg21.link/p2825)) as an extension, yielding a pointer to the function that overload resolution would select for an unevaluated call expression.
 
 #### C++2d Feature Support
 
diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h
index acbd922ba5319..d4f3761cdf726 100644
--- a/clang/include/clang/AST/APValue.h
+++ b/clang/include/clang/AST/APValue.h
@@ -436,9 +436,9 @@ class APValue {
   /// \param IsDerivedMember Whether member is a derived one.
   /// \param Path The path of the member.
   APValue(const ValueDecl *Member, bool IsDerivedMember,
-          ArrayRef Path)
+          ArrayRef Path, bool DeVirtualized = false)
       : Kind(None), AllowConstexprUnknown(false) {
-    MakeMemberPointer(Member, IsDerivedMember, Path);
+    MakeMemberPointer(Member, IsDerivedMember, Path, DeVirtualized);
   }
   /// Creates a new address label diff APValue.
   /// \param LHSExpr The left-hand side of the difference.
@@ -691,6 +691,7 @@ class APValue {
   const ValueDecl *getMemberPointerDecl() const;
   bool isMemberPointerToDerivedMember() const;
   ArrayRef getMemberPointerPath() const;
+  bool isDeVirtualized() const;
 
   const AddrLabelExpr* getAddrLabelDiffLHS() const {
     assert(isAddrLabelDiff() && "Invalid accessor");
@@ -799,7 +800,8 @@ class APValue {
     Kind = Union;
   }
   void MakeMemberPointer(const ValueDecl *Member, bool IsDerivedMember,
-                         ArrayRef Path);
+                         ArrayRef Path,
+                         bool DeVirtualized);
   void MakeAddrLabelDiff() {
     assert(isAbsent() && "Bad state change");
     new ((void *)(char *)&Data) AddrLabelDiffData();
@@ -831,7 +833,7 @@ class APValue {
                   bool OnePastTheEnd, bool IsNullPtr);
   MutableArrayRef
   setMemberPointerUninit(const ValueDecl *Member, bool IsDerivedMember,
-                         unsigned Size);
+                         unsigned Size, bool DeVirtualized);
 };
 
 } // end namespace clang.
diff --git a/clang/include/clang/AST/ComputeDependence.h b/clang/include/clang/AST/ComputeDependence.h
index 5044f63fe0765..d88698d2331d5 100644
--- a/clang/include/clang/AST/ComputeDependence.h
+++ b/clang/include/clang/AST/ComputeDependence.h
@@ -64,6 +64,7 @@ class CXXDeleteExpr;
 class ArrayTypeTraitExpr;
 class ExpressionTraitExpr;
 class CXXNoexceptExpr;
+class CXXDeclcallExpr;
 class PackExpansionExpr;
 class PackIndexingExpr;
 class SubstNonTypeTemplateParmExpr;
@@ -161,6 +162,7 @@ ExprDependence computeDependence(CXXDeleteExpr *E);
 ExprDependence computeDependence(ArrayTypeTraitExpr *E);
 ExprDependence computeDependence(ExpressionTraitExpr *E);
 ExprDependence computeDependence(CXXNoexceptExpr *E, CanThrowResult CT);
+ExprDependence computeDependence(CXXDeclcallExpr *E);
 ExprDependence computeDependence(PackExpansionExpr *E);
 ExprDependence computeDependence(PackIndexingExpr *E);
 ExprDependence computeDependence(SubstNonTypeTemplateParmExpr *E);
diff --git a/clang/include/clang/AST/EvaluatedExprVisitor.h b/clang/include/clang/AST/EvaluatedExprVisitor.h
index 2991f2859ac4e..5f8417f6976a6 100644
--- a/clang/include/clang/AST/EvaluatedExprVisitor.h
+++ b/clang/include/clang/AST/EvaluatedExprVisitor.h
@@ -48,6 +48,7 @@ class EvaluatedExprVisitorBase : public StmtVisitorBase {
   void VisitBlockExpr(PTR(BlockExpr) E) { }
   void VisitCXXUuidofExpr(PTR(CXXUuidofExpr) E) { }
   void VisitCXXNoexceptExpr(PTR(CXXNoexceptExpr) E) { }
+  void VisitCXXDeclcallExpr(PTR(CXXDeclcallExpr) E) {}
 
   void VisitMemberExpr(PTR(MemberExpr) E) {
     // Only the base matters.
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index f1c8559cbff9b..ce45d5f0d0529 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -4343,6 +4343,55 @@ class CXXNoexceptExpr : public Expr {
   }
 };
 
+/// Represents a C++26 declcall expression (P2825).
+///
+/// An object which gives you member function pointer or function pointer
+class CXXDeclcallExpr : public Expr {
+  friend class ASTStmtReader;
+
+  Stmt *Operand;
+  /// The original call expression the declcall was written with, kept for
+  /// source fidelity (e.g. -ast-print). Evaluation and codegen use Operand,
+  /// which is the resolved callee (a pointer or pointer to member).
+  Stmt *SourceExpr;
+  SourceRange Range;
+
+  bool Devirtualize = false;
+
+public:
+  CXXDeclcallExpr(QualType Ty, Expr *Operand, Expr *SourceExpr,
+                  bool Devirtualize, SourceLocation Keyword,
+                  SourceLocation RParen)
+      : Expr(CXXDeclcallExprClass, Ty, VK_PRValue, OK_Ordinary),
+        Operand(Operand), SourceExpr(SourceExpr), Range(Keyword, RParen),
+        Devirtualize(Devirtualize) {
+    setDependence(computeDependence(this));
+  }
+
+  CXXDeclcallExpr(EmptyShell Empty) : Expr(CXXDeclcallExprClass, Empty) {}
+
+  Expr *getOperand() const { return static_cast(Operand); }
+  /// The original call expression, for printing / source fidelity.
+  Expr *getSourceExpr() const { return static_cast(SourceExpr); }
+
+  SourceLocation getBeginLoc() const { return Range.getBegin(); }
+  SourceLocation getEndLoc() const { return Range.getEnd(); }
+  SourceRange getSourceRange() const { return Range; }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CXXDeclcallExprClass;
+  }
+
+  bool isDevirtualized() const { return Devirtualize; }
+
+  // Iterators
+  child_range children() { return child_range(&Operand, &Operand + 1); }
+
+  const_child_range children() const {
+    return const_child_range(&Operand, &Operand + 1);
+  }
+};
+
 /// Represents a C++11 pack expansion that produces a sequence of
 /// expressions.
 ///
diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td
index fd3cce10be303..145fcd4ee3947 100644
--- a/clang/include/clang/AST/PropertiesBase.td
+++ b/clang/include/clang/AST/PropertiesBase.td
@@ -466,11 +466,14 @@ let Class = PropertyTypeCase in {
   def : Property<"memberPath", Array> {
     let Read = [{ node.getMemberPointerPath() }];
   }
+  def : Property<"isDeVirtualized", Bool> {
+    let Read = [{ node.isDeVirtualized() }];
+  }
   def : Creator<[{
     APValue result;
     unsigned pathSize = memberPath.size();
     const CXXRecordDecl **pathArray =
-        result.setMemberPointerUninit(member, isDerived, pathSize).data();
+        result.setMemberPointerUninit(member, isDerived, pathSize, isDeVirtualized).data();
     for (unsigned i = 0; i < pathSize; ++i)
       pathArray[i] = memberPath[i]->getCanonicalDecl();
     return result;
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 82c8185d3748a..52b705064fce1 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -3121,6 +3121,7 @@ DEF_TRAVERSE_STMT(UnaryOperator, {})
 DEF_TRAVERSE_STMT(BinaryOperator, {})
 DEF_TRAVERSE_STMT(CompoundAssignOperator, {})
 DEF_TRAVERSE_STMT(CXXNoexceptExpr, {})
+DEF_TRAVERSE_STMT(CXXDeclcallExpr, {})
 DEF_TRAVERSE_STMT(PackExpansionExpr, {})
 DEF_TRAVERSE_STMT(SizeOfPackExpr, {})
 DEF_TRAVERSE_STMT(PackIndexingExpr, {})
diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 640a4ea38d394..11f20b2ed6e6d 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -301,6 +301,7 @@ class TextNodeDumper
   void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *Node);
   void VisitCXXStaticCastExpr(const CXXStaticCastExpr *Node);
   void VisitCXXUnresolvedConstructExpr(const CXXUnresolvedConstructExpr *Node);
+  void VisitCXXNoexceptExpr(const CXXNoexceptExpr *Node);
   void VisitCXXConstructExpr(const CXXConstructExpr *Node);
   void VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *Node);
   void VisitCXXNewExpr(const CXXNewExpr *Node);
diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h
index 8108ee559acea..fc79e64248e74 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchers.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -2057,6 +2057,10 @@ extern const internal::VariadicDynCastAllOfMatcher
 extern const internal::VariadicDynCastAllOfMatcher
     cxxNoexceptExpr;
 
+/// Matches declcall expressions.
+extern const internal::VariadicDynCastAllOfMatcher
+    cxxDeclcallExpr;
+
 /// Matches a loop initializing the elements of an array in a number of contexts:
 ///  * in the implicit copy/move constructor for a class with an array member
 ///  * when a lambda-expression captures an array by value
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 937220f188585..edd03a4cc33e1 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -233,6 +233,7 @@ def UndefinedFuncTemplate : DiagGroup<"undefined-func-template">;
 def MissingNoEscape : DiagGroup<"missing-noescape">;
 
 def DefaultedFunctionDeleted : DiagGroup<"defaulted-function-deleted">;
+def DeclcallExtension : DiagGroup<"declcall-extension">;
 def DeleteIncomplete : DiagGroup<"delete-incomplete">;
 def DeleteNonAbstractNonVirtualDtor : DiagGroup<"delete-non-abstract-non-virtual-dtor">;
 def DeleteAbstractNonVirtualDtor : DiagGroup<"delete-abstract-non-virtual-dtor">;
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 79e74a846e3ea..06905c67de8cc 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -74,6 +74,8 @@ def warn_cxx11_keyword : Warning<"'%0' is a keyword in C++11">,
   InGroup, DefaultIgnore;
 def warn_cxx20_keyword : Warning<"'%0' is a keyword in C++20">,
   InGroup, DefaultIgnore;
+def warn_cxx26_keyword : Warning<"'%0' is a keyword in C++26">,
+  InGroup, DefaultIgnore;
 def warn_c99_keyword : Warning<"'%0' is a keyword in C99">,
   InGroup, DefaultIgnore;
 def warn_c23_keyword : Warning<"'%0' is a keyword in C23">,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 568a18e2c375c..7b190d463ce5c 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10161,6 +10161,15 @@ def note_inequality_comparison_to_or_assign : Note<
 def err_incomplete_type_used_in_type_trait_expr : Error<
   "incomplete type %0 used in type trait expression">, NoSFINAE;
 
+def ext_declcall : ExtWarn<
+  "'declcall' is a Clang extension">, InGroup;
+def err_declcall_must_contain_a_call : Error<
+  "declcall doesn't contain a call">;
+def err_declcall_not_implemented_for : Error<
+  "declcall doesn't support %0 yet">;
+def err_declcall_must_be_constant_evaluable : Error<
+  "declcall must not depend on runtime known value">;
+
 // C++20 constinit and require_constant_initialization attribute
 def warn_cxx20_compat_constinit
     : Warning<"'constinit' specifier is incompatible with C++ standards before "
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index f4245f8afea1f..9e2f2d77b6294 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -166,6 +166,7 @@ def OverloadExpr : StmtNode;
 def UnresolvedLookupExpr : StmtNode;
 def UnresolvedMemberExpr : StmtNode;
 def CXXNoexceptExpr : StmtNode;
+def CXXDeclcallExpr : StmtNode;
 def PackExpansionExpr : StmtNode;
 def SizeOfPackExpr : StmtNode;
 def PackIndexingExpr : StmtNode;
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index d24f4c9b3107d..a373fed05ee2a 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -28,6 +28,9 @@
 #ifndef CXX20_KEYWORD
 #define CXX20_KEYWORD(X,Y) KEYWORD(X,KEYCXX20|(Y))
 #endif
+#ifndef CXX26_KEYWORD
+#define CXX26_KEYWORD(X,Y) KEYWORD(X,KEYCXX26|(Y))
+#endif
 #ifndef C99_KEYWORD
 #define C99_KEYWORD(X,Y) KEYWORD(X,KEYC99|(Y))
 #endif
@@ -444,6 +447,9 @@ CXX20_KEYWORD(constinit             , 0)
 CXX20_KEYWORD(concept               , 0)
 CXX20_KEYWORD(requires              , 0)
 
+// C++26 keywords.
+CXX26_KEYWORD(declcall              , KEYSYCL)
+
 // Not a CXX20_KEYWORD because it is disabled by -fno-char8_t.
 KEYWORD(char8_t                     , CHAR8SUPPORT)
 
diff --git a/clang/include/clang/Basic/TokenKinds.h b/clang/include/clang/Basic/TokenKinds.h
index 89b2d43076511..393644ed47912 100644
--- a/clang/include/clang/Basic/TokenKinds.h
+++ b/clang/include/clang/Basic/TokenKinds.h
@@ -53,10 +53,11 @@ enum TokenKey : unsigned {
   KEYFIXEDPOINT = 0x8000000,
   KEYDEFERTS = 0x10000000,
   KEYNOHLSL = 0x20000000,
-  KEYMAX = KEYNOHLSL, // The maximum key
-  KEYALLCXX = KEYCXX | KEYCXX11 | KEYCXX20,
+  KEYCXX26 = 0x40000000,
+  KEYMAX = KEYCXX26, // The maximum key
+  KEYALLCXX = KEYCXX | KEYCXX11 | KEYCXX20 | KEYCXX26,
   KEYALL = (KEYMAX | (KEYMAX - 1)) & ~KEYNOMS18 & ~KEYNOOPENCL &
-           ~KEYNOHLSL // KEYNOMS18, KEYNOOPENCL, KEYNOHLSL excluded.
+      ~KEYNOHLSL // KEYNOMS18, KEYNOOPENCL, KEYNOHLSL excluded.
 };
 
 namespace tok {
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 1fe05f2299596..e345879296cd1 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -8730,6 +8730,11 @@ class Sema final : public SemaBase {
   ExprResult BuildCXXNoexceptExpr(SourceLocation KeyLoc, Expr *Operand,
                                   SourceLocation RParen);
 
+  ExprResult ActOnDeclcallExpr(SourceLocation KeyLoc, SourceLocation LParen,
+                               Expr *Operand, SourceLocation RParen);
+  ExprResult BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand,
+                                  SourceLocation RParen);
+
   ExprResult ActOnStartCXXMemberReference(Scope *S, Expr *Base,
                                           SourceLocation OpLoc,
                                           tok::TokenKind OpKind,
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 4815a7cb57018..baa133a523291 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1937,6 +1937,7 @@ enum StmtCode {
 
   EXPR_CXX_EXPRESSION_TRAIT, // ExpressionTraitExpr
   EXPR_CXX_NOEXCEPT,         // CXXNoexceptExpr
+  EXPR_CXX_DECLCALL,         // CXXDeclCallExpr
 
   EXPR_OPAQUE_VALUE,                // OpaqueValueExpr
   EXPR_BINARY_CONDITIONAL_OPERATOR, // BinaryConditionalOperator
diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp
index fd51584f564bb..5252dadc5e4e5 100644
--- a/clang/lib/AST/APValue.cpp
+++ b/clang/lib/AST/APValue.cpp
@@ -242,6 +242,7 @@ namespace {
   struct MemberPointerBase {
     llvm::PointerIntPair MemberAndIsDerivedMember;
     unsigned PathLength;
+    bool DeVirtualized;
   };
 }
 
@@ -254,7 +255,10 @@ struct APValue::MemberPointerData : MemberPointerBase {
     PathElem *PathPtr;
   };
 
-  MemberPointerData() { PathLength = 0; }
+  MemberPointerData() {
+    PathLength = 0;
+    DeVirtualized = false;
+  }
   ~MemberPointerData() { resizePath(0); }
 
   void resizePath(unsigned Length) {
@@ -362,7 +366,7 @@ APValue::APValue(const APValue &RHS)
   case MemberPointer:
     MakeMemberPointer(RHS.getMemberPointerDecl(),
                       RHS.isMemberPointerToDerivedMember(),
-                      RHS.getMemberPointerPath());
+                      RHS.getMemberPointerPath(), RHS.isDeVirtualized());
     break;
   case AddrLabelDiff:
     MakeAddrLabelDiff();
@@ -621,6 +625,7 @@ void APValue::Profile(llvm::FoldingSetNodeID &ID) const {
   case MemberPointer:
     ID.AddPointer(getMemberPointerDecl());
     ID.AddInteger(isMemberPointerToDerivedMember());
+    ID.AddInteger(isDeVirtualized());
     for (const CXXRecordDecl *D : getMemberPointerPath())
       ID.AddPointer(D);
     return;
@@ -1102,6 +1107,13 @@ ArrayRef APValue::getMemberPointerPath() const {
   return {MPD.getPath(), MPD.PathLength};
 }
 
+bool APValue::isDeVirtualized() const {
+  assert(isMemberPointer() && "Invalid accessor");
+  const MemberPointerData &MPD =
+      *((const MemberPointerData *)(const char *)&Data);
+  return MPD.DeVirtualized;
+}
+
 void APValue::MakeLValue() {
   assert(isAbsent() && "Bad state change");
   static_assert(sizeof(LV) <= DataSize, "LV too big");
@@ -1117,10 +1129,11 @@ void APValue::MakeArray(unsigned InitElts, unsigned Size) {
 
 MutableArrayRef
 APValue::setMemberPointerUninit(const ValueDecl *Member, bool IsDerivedMember,
-                                unsigned Size) {
+                                unsigned Size, bool DeVirtualized) {
   assert(isAbsent() && "Bad state change");
   MemberPointerData *MPD = new ((void *)(char *)&Data) MemberPointerData;
   Kind = MemberPointer;
+  MPD->DeVirtualized = DeVirtualized;
   MPD->MemberAndIsDerivedMember.setPointer(
       Member ? cast(Member->getCanonicalDecl()) : nullptr);
   MPD->MemberAndIsDerivedMember.setInt(IsDerivedMember);
@@ -1129,9 +1142,10 @@ APValue::setMemberPointerUninit(const ValueDecl *Member, bool IsDerivedMember,
 }
 
 void APValue::MakeMemberPointer(const ValueDecl *Member, bool IsDerivedMember,
-                                ArrayRef Path) {
-  MutableArrayRef InternalPath =
-      setMemberPointerUninit(Member, IsDerivedMember, Path.size());
+                                ArrayRef Path,
+                                bool DeVirtualized) {
+  MutableArrayRef InternalPath = setMemberPointerUninit(
+      Member, IsDerivedMember, Path.size(), DeVirtualized);
   for (unsigned I = 0; I != Path.size(); ++I)
     InternalPath[I] = Path[I]->getCanonicalDecl();
 }
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index b64db01d572b1..2351eca6fac87 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -666,6 +666,7 @@ namespace clang {
     ExpectedStmt VisitOffsetOfExpr(OffsetOfExpr *OE);
     ExpectedStmt VisitCXXThrowExpr(CXXThrowExpr *E);
     ExpectedStmt VisitCXXNoexceptExpr(CXXNoexceptExpr *E);
+    ExpectedStmt VisitCXXDeclcallExpr(CXXDeclcallExpr *E);
     ExpectedStmt VisitCXXDefaultArgExpr(CXXDefaultArgExpr *E);
     ExpectedStmt VisitCXXScalarValueInitExpr(CXXScalarValueInitExpr *E);
     ExpectedStmt VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *E);
@@ -8561,6 +8562,22 @@ ExpectedStmt ASTNodeImporter::VisitCXXNoexceptExpr(CXXNoexceptExpr *E) {
       ToType, ToOperand, ToCanThrow, ToBeginLoc, ToEndLoc);
 }
 
+ExpectedStmt ASTNodeImporter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) {
+  Error Err = Error::success();
+  auto ToType = importChecked(Err, E->getType());
+  auto ToOperand =
+      importChecked(Err, E->getOperand()); // FIXME: dependent values?!
+  auto ToSourceExpr = importChecked(Err, E->getSourceExpr());
+  auto ToBeginLoc = importChecked(Err, E->getBeginLoc());
+  auto ToEndLoc = importChecked(Err, E->getEndLoc());
+  if (Err)
+    return std::move(Err);
+
+  return new (Importer.getToContext())
+      CXXDeclcallExpr(ToType, ToOperand, ToSourceExpr, E->isDevirtualized(),
+                      ToBeginLoc, ToEndLoc);
+}
+
 ExpectedStmt ASTNodeImporter::VisitCXXThrowExpr(CXXThrowExpr *E) {
   Error Err = Error::success();
   auto ToSubExpr = importChecked(Err, E->getSubExpr());
@@ -10866,7 +10883,8 @@ ASTNodeImporter::ImportAPValue(const APValue &FromValue) {
         Result.setMemberPointerUninit(
             cast(ImpMemPtrDecl),
             FromValue.isMemberPointerToDerivedMember(),
-            FromValue.getMemberPointerPath().size());
+            FromValue.getMemberPointerPath().size(),
+            FromValue.isDeVirtualized());
     ArrayRef FromPath = Result.getMemberPointerPath();
     for (unsigned Idx = 0; Idx < FromValue.getMemberPointerPath().size();
          Idx++) {
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 113bf3c964421..f2fa4c1aaac6f 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -3820,6 +3820,22 @@ bool Compiler::VisitCXXNoexceptExpr(const CXXNoexceptExpr *E) {
   return this->emitConstBool(E->getValue(), E);
 }
 
+template 
+bool Compiler::VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) {
+  // declcall evaluates to its resolved operand: a pointer or member pointer
+  // to the function that overload resolution selected.
+  if (!this->delegate(E->getOperand()))
+    return false;
+
+  // A declcall of a qualified virtual member is devirtualized: mark the
+  // resulting member pointer so it lowers to a direct pointer rather than a
+  // vtable index. isDevirtualized() implies a member function pointer.
+  if (E->isDevirtualized() && !DiscardResult)
+    return this->emitDevirtualizeMemberPtr(E);
+
+  return true;
+}
+
 template 
 bool Compiler::VisitCXXConstructExpr(const CXXConstructExpr *E) {
   QualType T = E->getType();
@@ -5830,6 +5846,11 @@ bool Compiler::visitAPValue(const APValue &Val, PrimType ValType,
           return false;
       }
 
+      // Restore the devirtualized flag (declcall of a qualified virtual call)
+      // so a call through this pointer bypasses virtual dispatch.
+      if (Val.isDeVirtualized() && !this->emitDevirtualizeMemberPtr(Info))
+        return false;
+
       return true;
     }
     return this->emitNullMemberPtr(0, nullptr, Info);
@@ -6326,16 +6347,18 @@ bool Compiler::VisitCallExpr(const CallExpr *E) {
     // Get the callee, either from a member pointer or function pointer saved in
     // CalleeOffset.
     if (isa(E) && CalleeOffset) {
+      // Keep the member pointer on the stack so the call honors a
+      // devirtualized declcall pointer rather than always dispatching.
       if (!this->emitGetLocal(PT_MemberPtr, *CalleeOffset, E))
         return false;
-      if (!this->emitGetMemberPtrDecl(E))
+      if (!this->emitCallMemberPtr(ArgSize, E, E))
         return false;
     } else {
       if (!this->emitGetLocal(PT_Ptr, *CalleeOffset, E))
         return false;
+      if (!this->emitCallPtr(ArgSize, E, E))
+        return false;
     }
-    if (!this->emitCallPtr(ArgSize, E, E))
-      return false;
   }
 
   // Cleanup for discarded return values.
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 8af918f58c06d..b21a1683b2e76 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -201,6 +201,7 @@ class Compiler : public ConstStmtVisitor, bool>,
   bool VisitCXXReinterpretCastExpr(const CXXReinterpretCastExpr *E);
   bool VisitCXXDynamicCastExpr(const CXXDynamicCastExpr *E);
   bool VisitCXXNoexceptExpr(const CXXNoexceptExpr *E);
+  bool VisitCXXDeclcallExpr(const CXXDeclcallExpr *E);
   bool VisitCXXConstructExpr(const CXXConstructExpr *E);
   bool VisitSourceLocExpr(const SourceLocExpr *E);
   bool VisitOffsetOfExpr(const OffsetOfExpr *E);
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 58bbf4aaf5f89..b58d31ae21269 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2370,25 +2370,12 @@ bool CallBI(InterpState &S, CodePtr OpPC, const CallExpr *CE,
   return InterpretBuiltin(S, OpPC, CE, BuiltinID);
 }
 
-bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
-             const CallExpr *CE) {
-  const Pointer &Ptr = S.Stk.pop();
-
-  if (Ptr.isZero()) {
-    S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_null_callee)
-        << const_cast(CE->getCallee()) << CE->getSourceRange();
-    return false;
-  }
-
-  if (!Ptr.isFunctionPointer())
-    return Invalid(S, OpPC);
-
-  const Function *F = Ptr.asFunctionPointer().Func;
-  assert(F);
-  // Don't allow calling block pointers.
-  if (!F->getDecl())
-    return Invalid(S, OpPC);
-
+// Shared tail of CallPtr / CallMemberPtr: given an already-resolved callee
+// Function, perform the call. \p NoVirtual forces a direct (non-virtual) call,
+// used for a devirtualized declcall member pointer.
+static bool CallResolvedFunctionPointer(InterpState &S, CodePtr OpPC,
+                                        const Function *F, uint32_t ArgSize,
+                                        const CallExpr *CE, bool NoVirtual) {
   // This happens when the call expression has been cast to
   // something else, but we don't support that.
   if (S.Ctx.classify(F->getDecl()->getReturnType()) !=
@@ -2426,12 +2413,59 @@ bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
   if (F->hasExplicitThisPointer())
     VarArgSize -= align(primSize(PT_Ptr));
 
-  if (F->isVirtual())
+  if (!NoVirtual && F->isVirtual())
     return CallVirt(S, OpPC, F, VarArgSize);
 
   return Call(S, OpPC, F, VarArgSize);
 }
 
+bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
+             const CallExpr *CE) {
+  const Pointer &Ptr = S.Stk.pop();
+
+  if (Ptr.isZero()) {
+    S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_null_callee)
+        << const_cast(CE->getCallee()) << CE->getSourceRange();
+    return false;
+  }
+
+  if (!Ptr.isFunctionPointer())
+    return Invalid(S, OpPC);
+
+  const Function *F = Ptr.asFunctionPointer().Func;
+  assert(F);
+  // Don't allow calling block pointers.
+  if (!F->getDecl())
+    return Invalid(S, OpPC);
+
+  return CallResolvedFunctionPointer(S, OpPC, F, ArgSize, CE,
+                                     /*NoVirtual=*/false);
+}
+
+bool CallMemberPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
+                   const CallExpr *CE) {
+  const MemberPointer MP = S.Stk.pop();
+
+  const auto *FD = dyn_cast_if_present(MP.getDecl());
+  if (!FD || !isa(FD))
+    return false;
+
+  // The method must be accessible via the base of the member pointer.
+  const CXXRecordDecl *MethodParent = cast(FD)->getParent();
+  const Pointer &Base = MP.getBase();
+  if (!Base.getRecord() || Base.getRecord()->getDecl() != MethodParent)
+    return false;
+
+  const Function *F = S.getContext().getOrCreateFunction(FD);
+  if (!F)
+    return false;
+
+  // A declcall of a qualified virtual call yields a devirtualized member
+  // pointer, which bypasses virtual dispatch when called.
+  return CallResolvedFunctionPointer(S, OpPC, F, ArgSize, CE,
+                                     /*NoVirtual=*/MP.isDevirtualized());
+}
+
 static void startLifetimeRecurse(PtrView Ptr) {
   if (const Record *R = Ptr.getRecord()) {
     Ptr.startLifetime();
@@ -3142,6 +3176,13 @@ bool GetMemberPtrBase(InterpState &S) {
   return true;
 }
 
+bool DevirtualizeMemberPtr(InterpState &S) {
+  MemberPointer MP = S.Stk.pop();
+  MP.setDevirtualized();
+  S.Stk.push(MP);
+  return true;
+}
+
 bool GetMemberPtrDecl(InterpState &S) {
   const auto &MP = S.Stk.pop();
 
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 201679017c98b..d2d58e57c7a64 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -127,6 +127,8 @@ bool CallBI(InterpState &S, CodePtr OpPC, const CallExpr *CE,
             uint32_t BuiltinID);
 bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
              const CallExpr *CE);
+bool CallMemberPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
+                   const CallExpr *CE);
 bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T);
 bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index);
 bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits,
@@ -3607,6 +3609,7 @@ inline bool GetIntPtr(InterpState &S, CodePtr OpPC, const Type *Ty) {
 bool GetMemberPtr(InterpState &S, const ValueDecl *D);
 bool GetMemberPtrBase(InterpState &S);
 bool GetMemberPtrDecl(InterpState &S);
+bool DevirtualizeMemberPtr(InterpState &S);
 bool CopyMemberPtrPath(InterpState &S, const RecordDecl *Entry, bool IsDerived);
 
 /// Just emit a diagnostic. The expression that caused emission of this
diff --git a/clang/lib/AST/ByteCode/MemberPointer.cpp b/clang/lib/AST/ByteCode/MemberPointer.cpp
index bccadc4d4fc8f..6796a4f960970 100644
--- a/clang/lib/AST/ByteCode/MemberPointer.cpp
+++ b/clang/lib/AST/ByteCode/MemberPointer.cpp
@@ -84,7 +84,8 @@ APValue MemberPointer::toAPValue(const ASTContext &ASTCtx) const {
     return Base.toAPValue(ASTCtx);
 
   return APValue(getDecl(), /*IsDerivedMember=*/isDerivedMember(),
-                 /*Path=*/ArrayRef(Path, PathLength));
+                 /*Path=*/ArrayRef(Path, PathLength),
+                 /*DeVirtualized=*/DeVirtualized);
 }
 
 ComparisonCategoryResult
diff --git a/clang/lib/AST/ByteCode/MemberPointer.h b/clang/lib/AST/ByteCode/MemberPointer.h
index b23acf7befc67..9213d1891b437 100644
--- a/clang/lib/AST/ByteCode/MemberPointer.h
+++ b/clang/lib/AST/ByteCode/MemberPointer.h
@@ -33,12 +33,16 @@ class MemberPointer final {
   const CXXRecordDecl **Path = nullptr;
   int32_t PtrOffset = 0;
   uint8_t PathLength = 0;
+  /// Whether a declcall requested that virtual dispatch be skipped for this
+  /// member function pointer (see CXXDeclcallExpr).
+  bool DeVirtualized = false;
 
   MemberPointer(Pointer Base, const ValueDecl *Dcl, int32_t PtrOffset,
                 uint8_t PathLength = 0, const CXXRecordDecl **Path = nullptr,
-                bool IsDerived = false)
+                bool IsDerived = false, bool DeVirtualized = false)
       : Base(Base), DeclAndIsDerivedMember(Dcl, IsDerived), Path(Path),
-        PtrOffset(PtrOffset), PathLength(PathLength) {}
+        PtrOffset(PtrOffset), PathLength(PathLength),
+        DeVirtualized(DeVirtualized) {}
 
 public:
   MemberPointer() = default;
@@ -63,6 +67,9 @@ class MemberPointer final {
   /// Does this member pointer have a base declaration?
   bool hasDecl() const { return DeclAndIsDerivedMember.getPointer(); }
   bool isDerivedMember() const { return DeclAndIsDerivedMember.getInt(); }
+  /// Whether virtual dispatch should be skipped (declcall devirtualization).
+  bool isDevirtualized() const { return DeVirtualized; }
+  void setDevirtualized(bool V = true) { DeVirtualized = V; }
   /// Return the base declaration. Might be null.
   const ValueDecl *getDecl() const {
     return DeclAndIsDerivedMember.getPointer();
@@ -139,22 +146,23 @@ class MemberPointer final {
                                bool NewIsDerived = false) const {
     if (Base.isZero())
       return MemberPointer(Base, DeclAndIsDerivedMember.getPointer(), Offset,
-                           PathLength, Path, NewIsDerived);
+                           PathLength, Path, NewIsDerived, DeVirtualized);
     return MemberPointer(this->Base, DeclAndIsDerivedMember.getPointer(),
-                         Offset + PtrOffset, PathLength, Path, NewIsDerived);
+                         Offset + PtrOffset, PathLength, Path, NewIsDerived,
+                         DeVirtualized);
   }
 
   MemberPointer takeInstance(Pointer Instance) const {
     assert(this->Base.isZero());
     return MemberPointer(Instance, DeclAndIsDerivedMember.getPointer(),
                          this->PtrOffset, PathLength, Path,
-                         DeclAndIsDerivedMember.getInt());
+                         DeclAndIsDerivedMember.getInt(), DeVirtualized);
   }
 
   MemberPointer withPath(uint8_t PathLength, const CXXRecordDecl **Path,
                          bool IsDerived) const {
     return MemberPointer(this->Base, DeclAndIsDerivedMember.getPointer(),
-                         PtrOffset, PathLength, Path, IsDerived);
+                         PtrOffset, PathLength, Path, IsDerived, DeVirtualized);
   }
 
   APValue toAPValue(const ASTContext &) const;
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 1705ad9d9d24b..0331d0e90f035 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -238,6 +238,9 @@ def CallBI : Opcode { let Args = [ArgCallExpr, ArgUint32]; }
 def CallPtr : Opcode {
   let Args = [ArgUint32, ArgCallExpr];
 }
+def CallMemberPtr : Opcode {
+  let Args = [ArgUint32, ArgCallExpr];
+}
 
 def CallVar : Opcode {
   let Args = [ArgFunction, ArgUint32];
@@ -954,6 +957,9 @@ def GetMemberPtr : SuccessOpcode {
 def GetMemberPtrBase : Opcode {
   let NeedsOpPC = 0;
 }
+def DevirtualizeMemberPtr : Opcode {
+  let NeedsOpPC = 0;
+}
 def GetMemberPtrDecl : Opcode{
   let NeedsOpPC = 0;
 }
diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp
index 3394c3ea264d9..5bc81bdf5a8b8 100644
--- a/clang/lib/AST/ComputeDependence.cpp
+++ b/clang/lib/AST/ComputeDependence.cpp
@@ -378,6 +378,10 @@ ExprDependence clang::computeDependence(CXXNoexceptExpr *E, CanThrowResult CT) {
   return D;
 }
 
+ExprDependence clang::computeDependence(CXXDeclcallExpr *E) {
+  return E->getOperand()->getDependence();
+}
+
 ExprDependence clang::computeDependence(PackExpansionExpr *E) {
   return (E->getPattern()->getDependence() & ~ExprDependence::UnexpandedPack) |
          ExprDependence::TypeValueInstantiation;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 0e876a258061c..51e6156d622c1 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3803,6 +3803,7 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
   case ArrayTypeTraitExprClass:
   case ExpressionTraitExprClass:
   case CXXNoexceptExprClass:
+  case CXXDeclcallExprClass:
   case SizeOfPackExprClass:
   case ObjCStringLiteralClass:
   case ObjCEncodeExprClass:
diff --git a/clang/lib/AST/ExprClassification.cpp b/clang/lib/AST/ExprClassification.cpp
index 7e5299425f9e2..356bd75402f2c 100644
--- a/clang/lib/AST/ExprClassification.cpp
+++ b/clang/lib/AST/ExprClassification.cpp
@@ -191,6 +191,7 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) {
   case Expr::BlockExprClass:
   case Expr::FloatingLiteralClass:
   case Expr::CXXNoexceptExprClass:
+  case Expr::CXXDeclcallExprClass:
   case Expr::CXXScalarValueInitExprClass:
   case Expr::TypeTraitExprClass:
   case Expr::ArrayTypeTraitExprClass:
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 6dae4c0c89fc2..c180a113f7fa9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1627,7 +1627,7 @@ namespace {
     }
 
     void moveInto(APValue &V) const {
-      V = APValue(getDecl(), isDerivedMember(), Path);
+      V = APValue(getDecl(), isDerivedMember(), Path, DeVirtualized);
     }
     void setFrom(const APValue &V) {
       assert(V.isMemberPointer());
@@ -1635,6 +1635,7 @@ namespace {
       DeclAndIsDerivedMember.setInt(V.isMemberPointerToDerivedMember());
       Path.clear();
       llvm::append_range(Path, V.getMemberPointerPath());
+      DeVirtualized = V.isDeVirtualized();
     }
 
     /// DeclAndIsDerivedMember - The member declaration, and a flag indicating
@@ -1644,6 +1645,7 @@ namespace {
     /// Path - The path of base/derived classes from the member declaration's
     /// class (exclusive) to the class type of the member pointer (inclusive).
     SmallVector Path;
+    bool DeVirtualized = false;
 
     /// Perform a cast towards the class of the Decl (either up or down the
     /// hierarchy).
@@ -5288,21 +5290,25 @@ static bool EvaluateObjectArgument(EvalInfo &Info, const Expr *Object,
 ///        creating a bound member function.
 /// \return The field or method declaration to which the member pointer refers,
 ///         or 0 if evaluation fails.
-static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info,
-                                                  QualType LVType,
-                                                  LValue &LV,
-                                                  const Expr *RHS,
-                                                  bool IncludeMember = true) {
+
+struct MemberPointerAccessResult {
+  const ValueDecl *Decl;
+  bool DeVirtualized = false;
+};
+
+static const MemberPointerAccessResult
+HandleMemberPointerAccess(EvalInfo &Info, QualType LVType, LValue &LV,
+                          const Expr *RHS, bool IncludeMember = true) {
   MemberPtr MemPtr;
   if (!EvaluateMemberPointer(RHS, MemPtr, Info))
-    return nullptr;
+    return {nullptr};
 
   // C++11 [expr.mptr.oper]p6: If the second operand is the null pointer to
   // member value, the behavior is undefined.
   if (!MemPtr.getDecl()) {
     // FIXME: Specific diagnostic.
     Info.FFDiag(RHS);
-    return nullptr;
+    return {nullptr};
   }
 
   if (MemPtr.isDerivedMember()) {
@@ -5315,7 +5321,7 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info,
     if (LV.Designator.MostDerivedPathLength + MemPtr.Path.size() >
         LV.Designator.Entries.size()) {
       Info.FFDiag(RHS);
-      return nullptr;
+      return {nullptr};
     }
     unsigned PathLengthToMember =
         LV.Designator.Entries.size() - MemPtr.Path.size();
@@ -5325,7 +5331,7 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info,
       const CXXRecordDecl *MPDecl = MemPtr.Path[I];
       if (LVDecl->getCanonicalDecl() != MPDecl->getCanonicalDecl()) {
         Info.FFDiag(RHS);
-        return nullptr;
+        return {nullptr};
       }
     }
     // MemPtr.Path only contains the base classes of the class directly
@@ -5344,13 +5350,13 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info,
     const CXXRecordDecl *LastMPDecl = MemPtr.getContainingRecord();
     if (LastLVDecl->getCanonicalDecl() != LastMPDecl->getCanonicalDecl()) {
       Info.FFDiag(RHS);
-      return nullptr;
+      return {nullptr};
     }
 
     // Truncate the lvalue to the appropriate derived class.
     if (!CastToDerivedClass(Info, RHS, LV, MemPtr.getContainingRecord(),
                             PathLengthToMember))
-      return nullptr;
+      return {nullptr};
   } else if (!MemPtr.Path.empty()) {
     // Extend the LValue path with the member pointer's path.
     LV.Designator.Entries.reserve(LV.Designator.Entries.size() +
@@ -5365,36 +5371,35 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info,
     for (unsigned I = 1, N = MemPtr.Path.size(); I != N; ++I) {
       const CXXRecordDecl *Base = MemPtr.Path[N - I - 1];
       if (!HandleLValueDirectBase(Info, RHS, LV, RD, Base))
-        return nullptr;
+        return {nullptr};
       RD = Base;
     }
     // Finally cast to the class containing the member.
     if (!HandleLValueDirectBase(Info, RHS, LV, RD,
                                 MemPtr.getContainingRecord()))
-      return nullptr;
+      return {nullptr};
   }
 
   // Add the member. Note that we cannot build bound member functions here.
   if (IncludeMember) {
     if (const FieldDecl *FD = dyn_cast(MemPtr.getDecl())) {
       if (!HandleLValueMember(Info, RHS, LV, FD))
-        return nullptr;
+        return {nullptr};
     } else if (const IndirectFieldDecl *IFD =
                  dyn_cast(MemPtr.getDecl())) {
       if (!HandleLValueIndirectMember(Info, RHS, LV, IFD))
-        return nullptr;
+        return {nullptr};
     } else {
       llvm_unreachable("can't construct reference to bound member function");
     }
   }
 
-  return MemPtr.getDecl();
+  return {MemPtr.getDecl(), MemPtr.DeVirtualized};
 }
 
-static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info,
-                                                  const BinaryOperator *BO,
-                                                  LValue &LV,
-                                                  bool IncludeMember = true) {
+static const MemberPointerAccessResult
+HandleMemberPointerAccess(EvalInfo &Info, const BinaryOperator *BO, LValue &LV,
+                          bool IncludeMember = true) {
   assert(BO->getOpcode() == BO_PtrMemD || BO->getOpcode() == BO_PtrMemI);
 
   if (!EvaluateObjectArgument(Info, BO->getLHS(), LV)) {
@@ -5402,7 +5407,7 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info,
       MemberPtr MemPtr;
       EvaluateMemberPointer(BO->getRHS(), MemPtr, Info);
     }
-    return nullptr;
+    return {nullptr};
   }
 
   return HandleMemberPointerAccess(Info, BO->getLHS()->getType(), LV,
@@ -8612,7 +8617,7 @@ class ExprEvaluatorBase
     case BO_PtrMemD:
     case BO_PtrMemI: {
       LValue Obj;
-      if (!HandleMemberPointerAccess(Info, E, Obj))
+      if (!HandleMemberPointerAccess(Info, E, Obj).Decl)
         return false;
       APValue Result;
       if (!handleLValueToRValueConversion(Info, E, E->getType(), Obj, Result))
@@ -8748,14 +8753,15 @@ class ExprEvaluatorBase
         HasQualifier = ME->hasQualifier();
       } else if (const BinaryOperator *BE = dyn_cast(Callee)) {
         // Indirect bound member calls ('.*' or '->*').
-        const ValueDecl *D =
+        const auto &&[Decl, DeVirtualized] =
             HandleMemberPointerAccess(Info, BE, ObjectArg, false);
-        if (!D)
+        if (!Decl)
           return false;
-        Member = dyn_cast(D);
+        Member = dyn_cast(Decl);
         if (!Member)
           return Error(Callee);
         This = &ObjectArg;
+        HasQualifier = DeVirtualized;
       } else if (const auto *PDE = dyn_cast(Callee)) {
         if (!Info.getLangOpts().CPlusPlus20)
           Info.CCEDiag(PDE, diag::note_constexpr_pseudo_destructor);
@@ -9245,7 +9251,7 @@ class LValueExprEvaluatorBase
 
     case BO_PtrMemD:
     case BO_PtrMemI:
-      return HandleMemberPointerAccess(this->Info, E, Result);
+      return HandleMemberPointerAccess(this->Info, E, Result).Decl;
     }
   }
 
@@ -9595,7 +9601,8 @@ bool LValueExprEvaluator::VisitMaterializeTemporaryExpr(
 
     case SubobjectAdjustment::MemberPointerAdjustment:
       if (!HandleMemberPointerAccess(this->Info, Type, Result,
-                                     Adjustments[I].Ptr.RHS))
+                                     Adjustments[I].Ptr.RHS)
+               .Decl)
         return false;
       Type = Adjustments[I].Ptr.MPT->getPointeeType();
       break;
@@ -10054,6 +10061,10 @@ class PointerExprEvaluator
     return true;
   }
 
+  bool VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) {
+    return Visit(E->getOperand());
+  }
+
   bool VisitSYCLUniqueStableIdExpr(const SYCLUniqueStableIdExpr *E) {
     std::string ResultStr = E->ComputeName(Info.Ctx);
 
@@ -11121,6 +11132,13 @@ class MemberPointerExprEvaluator
 
   bool VisitCastExpr(const CastExpr *E);
   bool VisitUnaryAddrOf(const UnaryOperator *E);
+  bool VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) {
+    bool out = Visit(E->getOperand());
+    if (out && E->isDevirtualized()) {
+      Result.DeVirtualized = true;
+    }
+    return out;
+  }
 };
 } // end anonymous namespace
 
@@ -22349,6 +22367,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
   case Expr::SYCLBuiltinNumFieldsExprClass:
   case Expr::SYCLBuiltinNumBasesExprClass:
   case Expr::CXXReflectExprClass:
+  case Expr::CXXDeclcallExprClass:
     return NoDiag();
   case Expr::CallExprClass:
   case Expr::CXXOperatorCallExprClass: {
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 197a874eb180b..7ab1e551c15c1 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5409,6 +5409,24 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
     mangleExpression(cast(E)->getOperand());
     break;
 
+  case Expr::CXXDeclcallExprClass: {
+    // There is no standard Itanium mangling for declcall (P2825) yet. Mangle
+    // it as a vendor extended expression
+    //    ::= u  * E
+    // so that distinct operands mangle distinctly and the symbol stays
+    // demanglable. The operand is encoded as an  template-arg
+    // (X  E).
+    // FIXME: Replace with the ABI-assigned mangling once declcall is voted
+    // into the standard and the Itanium C++ ABI defines one.
+    NotPrimaryExpr();
+    Out << "u8declcall";
+    Out << 'X';
+    mangleExpression(cast(E)->getOperand());
+    Out << 'E';
+    Out << 'E';
+    break;
+  }
+
   case Expr::UnaryExprOrTypeTraitExprClass: {
     // Non-instantiation-dependent traits are an  integer literal.
     const UnaryExprOrTypeTraitExpr *SAE = cast(E);
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index ed830962a8949..bf3b0208a8801 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -2729,6 +2729,13 @@ void StmtPrinter::VisitCXXNoexceptExpr(CXXNoexceptExpr *E) {
   OS << ")";
 }
 
+void StmtPrinter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) {
+  OS << "declcall(";
+  // Print the original call expression, not the resolved callee operand.
+  PrintExpr(E->getSourceExpr() ? E->getSourceExpr() : E->getOperand());
+  OS << ")";
+}
+
 void StmtPrinter::VisitPackExpansionExpr(PackExpansionExpr *E) {
   PrintExpr(E->getPattern());
   OS << "...";
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index c7c2dfce5b1e7..064ca6ebc351a 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -2399,6 +2399,14 @@ void StmtProfiler::VisitCXXNoexceptExpr(const CXXNoexceptExpr *S) {
   VisitExpr(S);
 }
 
+void StmtProfiler::VisitCXXDeclcallExpr(const CXXDeclcallExpr *S) {
+  VisitExpr(S);
+  // The devirtualized flag is the only state distinguishing a declcall of a
+  // qualified virtual call from an unqualified one (both rebuild the same
+  // &Class::method operand), so it must participate in the profile / ODR hash.
+  ID.AddBoolean(S->isDevirtualized());
+}
+
 void StmtProfiler::VisitPackExpansionExpr(const PackExpansionExpr *S) {
   VisitExpr(S);
 }
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 050277fbf5a9b..f411eaeceb0d1 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -861,6 +861,8 @@ void TextNodeDumper::Visit(const APValue &Value, QualType Ty) {
       OS << MemDecl->getDeclName();
     else
       OS << "null";
+    if (Value.isDeVirtualized())
+      OS << " (devirtualized)";
     return;
   }
   case APValue::AddrLabelDiff:
@@ -1811,6 +1813,15 @@ void TextNodeDumper::VisitCXXUnresolvedConstructExpr(
     OS << " list";
 }
 
+void TextNodeDumper::VisitCXXNoexceptExpr(const CXXNoexceptExpr *Node) {
+  if (Node->isValueDependent())
+    OS << " noexcept()";
+  else if (Node->getValue())
+    OS << " noexcept(true)";
+  else
+    OS << " noexcept(false)";
+}
+
 void TextNodeDumper::VisitCXXConstructExpr(const CXXConstructExpr *Node) {
   CXXConstructorDecl *Ctor = Node->getConstructor();
   dumpType(Ctor->getType());
diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
index 5cbf134620e34..ff7e8a626c2ce 100644
--- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
@@ -921,6 +921,8 @@ const internal::VariadicDynCastAllOfMatcher cxxNewExpr;
 const internal::VariadicDynCastAllOfMatcher cxxDeleteExpr;
 const internal::VariadicDynCastAllOfMatcher
     cxxNoexceptExpr;
+const internal::VariadicDynCastAllOfMatcher
+    cxxDeclcallExpr;
 const internal::VariadicDynCastAllOfMatcher
     arraySubscriptExpr;
 const internal::VariadicDynCastAllOfMatcher
diff --git a/clang/lib/ASTMatchers/Dynamic/Registry.cpp b/clang/lib/ASTMatchers/Dynamic/Registry.cpp
index a04070971f0eb..0c04b6075dc46 100644
--- a/clang/lib/ASTMatchers/Dynamic/Registry.cpp
+++ b/clang/lib/ASTMatchers/Dynamic/Registry.cpp
@@ -204,6 +204,7 @@ RegistryMaps::RegistryMaps() {
   REGISTER_MATCHER(cxxMethodDecl);
   REGISTER_MATCHER(cxxNewExpr);
   REGISTER_MATCHER(cxxNoexceptExpr);
+  REGISTER_MATCHER(cxxDeclcallExpr);
   REGISTER_MATCHER(cxxNullPtrLiteralExpr);
   REGISTER_MATCHER(cxxOperatorCallExpr);
   REGISTER_MATCHER(cxxRecordDecl);
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index e80dfc82e60ba..ce738fb93ded7 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -209,6 +209,13 @@ class MatchDescendantVisitor : public DynamicRecursiveASTVisitor {
     return DynamicRecursiveASTVisitor::TraverseCXXNoexceptExpr(Node);
   }
 
+  bool TraverseCXXDeclcallExpr(CXXDeclcallExpr *Node) override {
+    // Unevaluated context.
+    if (IgnoreUnevaluatedContext)
+      return true;
+    return DynamicRecursiveASTVisitor::TraverseCXXDeclcallExpr(Node);
+  }
+
   bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) override {
     // Unevaluated context.
     if (IgnoreUnevaluatedContext)
diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp
index f11a72c7fa95c..bd82871fa209a 100644
--- a/clang/lib/Basic/IdentifierTable.cpp
+++ b/clang/lib/Basic/IdentifierTable.cpp
@@ -108,6 +108,10 @@ static KeywordStatus getKeywordStatusHelper(const LangOptions &LangOpts,
     if (LangOpts.CPlusPlus20)
       return KS_Enabled;
     return LangOpts.CPlusPlus ? KS_Future : KS_Unknown;
+  case KEYCXX26:
+    if (LangOpts.CPlusPlus26)
+      return KS_Enabled;
+    return LangOpts.CPlusPlus ? KS_Future : KS_Unknown;
   case KEYGNU:
     return LangOpts.GNUKeywords ? KS_Extension : KS_Unknown;
   case KEYMS:
@@ -848,6 +852,9 @@ IdentifierTable::getFutureCompatDiagKind(const IdentifierInfo &II,
     if (((Flags & KEYCXX20) == KEYCXX20) ||
         ((Flags & CHAR8SUPPORT) == CHAR8SUPPORT))
       return diag::warn_cxx20_keyword;
+
+    if ((Flags & KEYCXX26) == KEYCXX26)
+      return diag::warn_cxx26_keyword;
   } else {
     if ((Flags & KEYC99) == KEYC99)
       return diag::warn_c99_keyword;
diff --git a/clang/lib/CodeGen/CGCXXABI.cpp b/clang/lib/CodeGen/CGCXXABI.cpp
index 4a417dce88a1f..aa40541f0fa0d 100644
--- a/clang/lib/CodeGen/CGCXXABI.cpp
+++ b/clang/lib/CodeGen/CGCXXABI.cpp
@@ -103,7 +103,8 @@ CGCXXABI::EmitNullMemberPointer(const MemberPointerType *MPT) {
   return GetBogusMemberPointer(QualType(MPT, 0));
 }
 
-llvm::Constant *CGCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD) {
+llvm::Constant *CGCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD,
+                                                    bool AllowVirtual) {
   return GetBogusMemberPointer(CGM.getContext().getMemberPointerType(
       MD->getType(), /*Qualifier=*/std::nullopt, MD->getParent()));
 }
@@ -113,7 +114,8 @@ llvm::Constant *CGCXXABI::EmitMemberDataPointer(const MemberPointerType *MPT,
   return GetBogusMemberPointer(QualType(MPT, 0));
 }
 
-llvm::Constant *CGCXXABI::EmitMemberPointer(const APValue &MP, QualType MPT) {
+llvm::Constant *CGCXXABI::EmitMemberPointer(const APValue &MP, QualType MPT,
+                                            bool AllowVirtual) {
   return GetBogusMemberPointer(MPT);
 }
 
diff --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h
index 47090276c56b0..1ea4bd8e25922 100644
--- a/clang/lib/CodeGen/CGCXXABI.h
+++ b/clang/lib/CodeGen/CGCXXABI.h
@@ -218,14 +218,16 @@ class CGCXXABI {
   virtual llvm::Constant *EmitNullMemberPointer(const MemberPointerType *MPT);
 
   /// Create a member pointer for the given method.
-  virtual llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD);
+  virtual llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD,
+                                                    bool AllowVirtual = true);
 
   /// Create a member pointer for the given field.
   virtual llvm::Constant *EmitMemberDataPointer(const MemberPointerType *MPT,
                                                 CharUnits offset);
 
   /// Create a member pointer for the given member pointer constant.
-  virtual llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT);
+  virtual llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT,
+                                            bool AllowVirtual = false);
 
   /// Emit a comparison between two member pointers.  Returns an i1.
   virtual llvm::Value *
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp
index 27b7423a42f98..6848cc444863d 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -2725,7 +2725,8 @@ ConstantEmitter::tryEmitPrivate(const APValue &Value, QualType DestType,
                              Filler);
   }
   case APValue::MemberPointer:
-    return CGM.getCXXABI().EmitMemberPointer(Value, DestType);
+    return CGM.getCXXABI().EmitMemberPointer(Value, DestType,
+                                             !Value.isDeVirtualized());
   }
   llvm_unreachable("Unknown APValue kind");
 }
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index bbe1b48e06a94..acbef0688b0da 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -829,6 +829,21 @@ class ScalarExprEmitter
     return Builder.getInt1(E->getValue());
   }
 
+  Value *VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) {
+    // A declcall is required to be constant-evaluable (enforced in Sema), so
+    // emit it as a constant. This covers both function pointers and member
+    // function pointers; the devirtualized flag is carried by the evaluated
+    // APValue and honored by the member-pointer constant emission.
+    Expr::EvalResult Eval;
+    if (E->EvaluateAsConstantExpr(Eval, CGF.getContext()))
+      if (llvm::Constant *C =
+              ConstantEmitter(CGF).tryEmitAbstract(Eval.Val, E->getType()))
+        return C;
+
+    // Fall back to emitting the resolved operand directly.
+    return Visit(E->getOperand());
+  }
+
   // Binary Operators.
   Value *EmitMul(const BinOpInfo &Ops) {
     if (Ops.Ty->isSignedIntegerOrEnumerationType() ||
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index fd3ee14175612..adb332f09fa5a 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -149,12 +149,15 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
 
   llvm::Constant *EmitNullMemberPointer(const MemberPointerType *MPT) override;
 
-  llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD) override;
+  llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD,
+                                            bool AllowVirtual) override;
   llvm::Constant *EmitMemberDataPointer(const MemberPointerType *MPT,
                                         CharUnits offset) override;
-  llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT) override;
+  llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT,
+                                    bool AllowVirtual) override;
   llvm::Constant *BuildMemberPointer(const CXXMethodDecl *MD,
-                                     CharUnits ThisAdjustment);
+                                     CharUnits ThisAdjustment,
+                                     bool AllowVirtual = true);
 
   llvm::Value *EmitMemberPointerComparison(CodeGenFunction &CGF,
                                            llvm::Value *L, llvm::Value *R,
@@ -1156,19 +1159,21 @@ ItaniumCXXABI::EmitMemberDataPointer(const MemberPointerType *MPT,
 }
 
 llvm::Constant *
-ItaniumCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD) {
-  return BuildMemberPointer(MD, CharUnits::Zero());
+ItaniumCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD,
+                                         bool AllowVirtual) {
+  return BuildMemberPointer(MD, CharUnits::Zero(), AllowVirtual);
 }
 
 llvm::Constant *ItaniumCXXABI::BuildMemberPointer(const CXXMethodDecl *MD,
-                                                  CharUnits ThisAdjustment) {
+                                                  CharUnits ThisAdjustment,
+                                                  bool AllowVirtual) {
   assert(MD->isInstance() && "Member function must not be static!");
 
   CodeGenTypes &Types = CGM.getTypes();
 
   // Get the function pointer (or index if this is a virtual function).
   llvm::Constant *MemPtr[2];
-  if (MD->isVirtual()) {
+  if (MD->isVirtual() && AllowVirtual) {
     uint64_t Index = CGM.getItaniumVTableContext().getMethodVTableIndex(MD);
     uint64_t VTableOffset;
     if (CGM.getLangOpts().RelativeCXXABIVTables) {
@@ -1247,7 +1252,9 @@ llvm::Constant *ItaniumCXXABI::BuildMemberPointer(const CXXMethodDecl *MD,
 }
 
 llvm::Constant *ItaniumCXXABI::EmitMemberPointer(const APValue &MP,
-                                                 QualType MPType) {
+                                                 QualType MPType,
+                                                 bool AllowVirtual) {
+
   const MemberPointerType *MPT = MPType->castAs();
   const ValueDecl *MPD = MP.getMemberPointerDecl();
   if (!MPD)
@@ -1256,7 +1263,7 @@ llvm::Constant *ItaniumCXXABI::EmitMemberPointer(const APValue &MP,
   CharUnits ThisAdjustment = getContext().getMemberPointerPathAdjustment(MP);
 
   if (const CXXMethodDecl *MD = dyn_cast(MPD)) {
-    llvm::Constant *Src = BuildMemberPointer(MD, ThisAdjustment);
+    llvm::Constant *Src = BuildMemberPointer(MD, ThisAdjustment, AllowVirtual);
     QualType SrcType = getContext().getMemberPointerType(
         MD->getType(), /*Qualifier=*/std::nullopt, MD->getParent());
     return pointerAuthResignMemberFunctionPointer(Src, MPType, SrcType, CGM);
diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
index 78781da8fdfd4..681bf52f5796c 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -690,8 +690,10 @@ class MicrosoftCXXABI : public CGCXXABI {
 
   llvm::Constant *EmitMemberDataPointer(const MemberPointerType *MPT,
                                         CharUnits offset) override;
-  llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD) override;
-  llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT) override;
+  llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD,
+                                            bool AllowVirtual) override;
+  llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT,
+                                    bool AllowVirtual) override;
 
   llvm::Value *EmitMemberPointerComparison(CodeGenFunction &CGF,
                                            llvm::Value *L,
@@ -2957,7 +2959,8 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberDataPointer(const CXXRecordDecl *RD,
 }
 
 llvm::Constant *MicrosoftCXXABI::EmitMemberPointer(const APValue &MP,
-                                                   QualType MPType) {
+                                                   QualType MPType,
+                                                   bool AllowVirtual) {
   const MemberPointerType *DstTy = MPType->castAs();
   const ValueDecl *MPD = MP.getMemberPointerDecl();
   if (!MPD)
@@ -2968,7 +2971,7 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberPointer(const APValue &MP,
 
   llvm::Constant *C;
   if (const CXXMethodDecl *MD = dyn_cast(MPD)) {
-    C = EmitMemberFunctionPointer(MD);
+    C = EmitMemberFunctionPointer(MD, AllowVirtual);
   } else {
     // For a pointer to data member, start off with the offset of the field in
     // the class in which it was declared, and convert from there if necessary.
@@ -3021,7 +3024,8 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberPointer(const APValue &MP,
 }
 
 llvm::Constant *
-MicrosoftCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD) {
+MicrosoftCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD,
+                                           bool AllowVirtual) {
   assert(MD->isInstance() && "Member function must not be static!");
 
   CharUnits NonVirtualBaseAdjustment = CharUnits::Zero();
@@ -3031,7 +3035,9 @@ MicrosoftCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD) {
   unsigned VBTableIndex = 0;
   llvm::Constant *FirstField;
   const FunctionProtoType *FPT = MD->getType()->castAs();
-  if (!MD->isVirtual()) {
+  // A devirtualized pointer (declcall of a qualified virtual call, AllowVirtual
+  // == false) points directly at the function rather than at a vftable thunk.
+  if (!MD->isVirtual() || !AllowVirtual) {
     llvm::Type *Ty;
     // Check whether the function has a computable LLVM signature.
     if (Types.isFuncTypeConvertible(FPT)) {
diff --git a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp
index e8d4660fd36b2..c2ce880ab30c7 100644
--- a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp
+++ b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp
@@ -909,28 +909,17 @@ static bool subscriptOperatorNeedsParens(const Expr *FullExpr) {
 }
 static bool castOperatorNeedsParens(const Expr *FullExpr) {
   const Expr* Expr = FullExpr->IgnoreImpCasts();
-  if (isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(FullExpr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr))
+  if (isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(FullExpr) || isa(Expr) ||
+      isa(Expr) || isa(Expr))
     return false;
 
   return true;
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 56943087004d4..7430e07b5b55e 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -1501,6 +1501,33 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand,
     AllowSuffix = false;
     break;
 
+  case tok::kw_declcall: {
+    SourceLocation KeyLoc = ConsumeToken();
+    BalancedDelimiterTracker T(*this, tok::l_paren);
+
+    if (T.expectAndConsume(diag::err_expected_lparen_after, "declcall"))
+      return ExprError();
+
+    // The operand of declcall is an unevaluated operand: its argument
+    // subexpressions must not be odr-used or evaluated. This scope covers only
+    // parsing; ActOnDeclcallExpr then runs in the declcall's own evaluation
+    // context, so the selected function is odr-used (and instantiated) exactly
+    // when the declcall expression itself is.
+    {
+      EnterExpressionEvaluationContext Unevaluated(
+          Actions, Sema::ExpressionEvaluationContext::Unevaluated);
+      Res = ParseExpression();
+    }
+
+    T.consumeClose();
+
+    if (!Res.isInvalid())
+      Res = Actions.ActOnDeclcallExpr(KeyLoc, T.getOpenLocation(), Res.get(),
+                                      T.getCloseLocation());
+    AllowSuffix = false;
+    break;
+  }
+
   case tok::kw_noexcept: { // [C++0x] 'noexcept' '(' expression ')'
     if (NotPrimaryExpression)
       *NotPrimaryExpression = true;
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index 779d75a5bd4eb..024df669a9b64 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -1393,6 +1393,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
   case Expr::TypeTraitExprClass:
   case Expr::CXXBoolLiteralExprClass:
   case Expr::CXXNoexceptExprClass:
+  case Expr::CXXDeclcallExprClass:
   case Expr::CXXNullPtrLiteralExprClass:
   case Expr::CXXPseudoDestructorExprClass:
   case Expr::CXXReflectExprClass:
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 7c23e814d42ff..57c1ef7f96044 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -7492,6 +7492,144 @@ ExprResult Sema::ActOnNoexceptExpr(SourceLocation KeyLoc, SourceLocation,
   return BuildCXXNoexceptExpr(KeyLoc, Operand, RParen);
 }
 
+ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand,
+                                      SourceLocation RParen) {
+  // Remember the operand as written; the node keeps it for source fidelity
+  // (e.g. -ast-print) since Operand below is rewritten to the resolved callee.
+  Expr *SourceExpr = Operand;
+  // in template ... do it later
+  if (Operand->isInstantiationDependent()) {
+    return new (Context) CXXDeclcallExpr(Context.DependentTy, Operand,
+                                         SourceExpr, false, KeyLoc, RParen);
+  }
+
+  // unwrap parens
+  Operand = Operand->IgnoreParens();
+  CallExpr *CE = dyn_cast(Operand);
+
+  if (CE == nullptr) {
+    Diag(Operand->getExprLoc(), diag::err_declcall_must_contain_a_call);
+    return ExprError();
+  }
+
+  const auto mustBeEvaluableInCompileTime = [this](Expr *e) {
+    Expr::EvalResult Eval;
+    if (!e->isValueDependent() && !e->EvaluateAsConstantExpr(Eval, Context)) {
+      return false;
+    }
+    return true;
+  };
+
+  if (CE->getCallee() == nullptr) {
+    return ExprError();
+  }
+
+  // P2825: declcall is ill-formed when the selected callee is a destructor or
+  // a builtin function (constructors cannot form a call expression and are
+  // rejected above).
+  const Decl *CalleeDecl = CE->getCalleeDecl();
+  if (isa_and_nonnull(CalleeDecl)) {
+    Diag(CE->getExprLoc(), diag::err_declcall_not_implemented_for)
+        << "a destructor";
+    return ExprError();
+  }
+  if (const auto *FD = dyn_cast_or_null(CalleeDecl);
+      FD && FD->getBuiltinID() != 0) {
+    Diag(CE->getExprLoc(), diag::err_declcall_not_implemented_for)
+        << "a builtin function";
+    return ExprError();
+  }
+
+  // The operand's argument subexpressions were parsed unevaluated and are not
+  // odr-used. The selected function, however, is what declcall yields a pointer
+  // to, so odr-use it here: this runs in the declcall's own evaluation context,
+  // so it is instantiated/emitted exactly when the declcall expression is
+  // potentially-evaluated (and left alone inside decltype, sizeof, etc.).
+  if (auto *FD = dyn_cast_or_null(CE->getCalleeDecl()))
+    MarkFunctionReferenced(KeyLoc, FD);
+
+  // Check if we are calling a method (it can be static.)
+  CXXMethodDecl *method = dyn_cast_or_null(CE->getCalleeDecl());
+
+  bool MemberCallHasQualifierAndIsVirtual = false;
+
+  // transform method call
+  if (method && method->isInstance()) {
+    const auto memberCallHasQualifier = [](const CallExpr *e) {
+      const MemberExpr *ME =
+          dyn_cast(e->getCallee()->IgnoreParens());
+      if (!ME)
+        return false;
+
+      return ME->hasQualifier();
+    };
+
+    MemberCallHasQualifierAndIsVirtual =
+        method->isVirtual() && memberCallHasQualifier(CE);
+
+    // member function pointer
+    const auto T = QualType(method->getFunctionType(), 0);
+
+    // Build a qualified reference to the member (i.e. &Class::method). The
+    // nested-name-specifier makes this a well-formed pointer-to-member and
+    // lets CheckUseOfCXXMethodAsAddressOfOperand accept the address-of below.
+    NestedNameSpecifierLocBuilder NNSBuilder;
+    NNSBuilder.MakeTrivial(
+        Context,
+        NestedNameSpecifier(
+            Context.getCanonicalTagType(method->getParent())->getTypePtr()),
+        CE->getSourceRange());
+
+    // create reference to the function
+    auto *DRE = DeclRefExpr::Create(
+        Context, NNSBuilder.getWithLocInContext(Context), SourceLocation(),
+        method, /*RefersToEnclosingVariableOrCapture=*/false, CE->getExprLoc(),
+        T, CE->getValueKind(), nullptr, nullptr, NOUR_None);
+
+    // get its address
+    ExprResult AddrOf = CreateBuiltinUnaryOp(CE->getSourceRange().getBegin(),
+                                             UO_AddrOf, DRE, false);
+    if (AddrOf.isInvalid())
+      return ExprError();
+    Operand = AddrOf.get();
+  } else {
+    // A call through a pointer to member has no compile-time-known callee for
+    // declcall to resolve; P2825 handles such runtime cases via an explicit
+    // static_cast. Diagnose it rather than crashing on the failed conversion.
+    const Expr *Callee = CE->getCallee()->IgnoreParenImpCasts();
+    if (const auto *BO = dyn_cast(Callee);
+        BO && BO->isPtrMemOp()) {
+      Diag(CE->getExprLoc(), diag::err_declcall_not_implemented_for)
+          << "a call through a pointer to member";
+      return ExprError();
+    }
+
+    // Convert the callee (a function or function pointer) to a pointer.
+    ExprResult Conv = CallExprUnaryConversions(CE->getCallee()->IgnoreParens());
+    if (Conv.isInvalid())
+      return ExprError();
+    Operand = Conv.get();
+  }
+
+  if (!mustBeEvaluableInCompileTime(Operand)) {
+    Diag(CE->getExprLoc(), diag::err_declcall_must_be_constant_evaluable);
+    return ExprError();
+  }
+
+  return new (Context)
+      CXXDeclcallExpr(Operand->getType(), Operand, SourceExpr,
+                      MemberCallHasQualifierAndIsVirtual, KeyLoc, RParen);
+}
+
+ExprResult Sema::ActOnDeclcallExpr(SourceLocation KeyLoc, SourceLocation,
+                                   Expr *Operand, SourceLocation RParen) {
+  // declcall (P2825) is not standardized yet. It is a native feature in SYCL
+  // mode; elsewhere it is a Clang extension.
+  if (!getLangOpts().isSYCL())
+    Diag(KeyLoc, diag::ext_declcall);
+  return BuildCXXDeclcallExpr(KeyLoc, Operand, RParen);
+}
+
 static void MaybeDecrementCount(
     Expr *E, llvm::DenseMap &RefsMinusAssignments) {
   DeclRefExpr *LHS = nullptr;
diff --git a/clang/lib/Sema/SemaFixItUtils.cpp b/clang/lib/Sema/SemaFixItUtils.cpp
index 1dad46fd6b940..c4e11da7b50c2 100644
--- a/clang/lib/Sema/SemaFixItUtils.cpp
+++ b/clang/lib/Sema/SemaFixItUtils.cpp
@@ -65,26 +65,16 @@ bool ConversionFixItGenerator::tryToFixConversion(const Expr *FullExpr,
   const Expr* Expr = FullExpr->IgnoreImpCasts();
 
   bool NeedParen = true;
-  if (isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(Expr) ||
-      isa(FullExpr) ||
-      isa(Expr) ||
-      isa(Expr) ||
+  if (isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(Expr) ||
+      isa(Expr) || isa(FullExpr) ||
+      isa(Expr) || isa(Expr) ||
       isa(Expr))
     NeedParen = false;
 
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index f2f06ddfdedb4..72c3be1772bf0 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -3780,6 +3780,10 @@ class TreeTransform {
     return SemaRef.BuildCXXNoexceptExpr(Range.getBegin(), Arg, Range.getEnd());
   }
 
+  ExprResult RebuildCXXDeclcallExpr(SourceRange Range, Expr *Arg) {
+    return SemaRef.BuildCXXDeclcallExpr(Range.getBegin(), Arg, Range.getEnd());
+  }
+
   UnsignedOrNone
   ComputeSizeOfPackExprWithoutSubstitution(ArrayRef PackArgs);
 
@@ -16770,6 +16774,21 @@ TreeTransform::TransformCXXNoexceptExpr(CXXNoexceptExpr *E) {
   return getDerived().RebuildCXXNoexceptExpr(E->getSourceRange(),SubExpr.get());
 }
 
+template 
+ExprResult
+TreeTransform::TransformCXXDeclcallExpr(CXXDeclcallExpr *E) {
+  // we need to instantiate referenced functions, so it's evaluated scope
+  ExprResult SubExpr = getDerived().TransformExpr(E->getOperand());
+  if (SubExpr.isInvalid())
+    return ExprError();
+
+  if (!getDerived().AlwaysRebuild() && SubExpr.get() == E->getOperand())
+    return E;
+
+  return getDerived().RebuildCXXDeclcallExpr(E->getSourceRange(),
+                                             SubExpr.get());
+}
+
 template
 ExprResult
 TreeTransform::TransformPackExpansionExpr(PackExpansionExpr *E) {
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index db5d1f03e15f1..e02dac621b2ff 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -2309,6 +2309,14 @@ void ASTStmtReader::VisitCXXNoexceptExpr(CXXNoexceptExpr *E) {
   E->Operand = Record.readSubExpr();
 }
 
+void ASTStmtReader::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) {
+  VisitExpr(E);
+  E->Range = readSourceRange();
+  E->Operand = Record.readSubExpr();
+  E->SourceExpr = Record.readSubExpr();
+  E->Devirtualize = Record.readInt();
+}
+
 void ASTStmtReader::VisitPackExpansionExpr(PackExpansionExpr *E) {
   VisitExpr(E);
   E->EllipsisLoc = readSourceLocation();
@@ -4513,6 +4521,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
       S = new (Context) CXXNoexceptExpr(Empty);
       break;
 
+    case EXPR_CXX_DECLCALL:
+      S = new (Context) CXXDeclcallExpr(Empty);
+      break;
+
     case EXPR_PACK_EXPANSION:
       S = new (Context) PackExpansionExpr(Empty);
       break;
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 752d91e2a1678..bf57a2521b092 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -2328,6 +2328,15 @@ void ASTStmtWriter::VisitCXXNoexceptExpr(CXXNoexceptExpr *E) {
   Code = serialization::EXPR_CXX_NOEXCEPT;
 }
 
+void ASTStmtWriter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) {
+  VisitExpr(E);
+  Record.AddSourceRange(E->getSourceRange());
+  Record.AddStmt(E->getOperand());
+  Record.AddStmt(E->getSourceExpr());
+  Record.push_back(E->isDevirtualized());
+  Code = serialization::EXPR_CXX_DECLCALL;
+}
+
 void ASTStmtWriter::VisitPackExpansionExpr(PackExpansionExpr *E) {
   VisitExpr(E);
   Record.AddSourceLocation(E->getEllipsisLoc());
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 00118db13f19b..ec7e5cb60744f 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1703,6 +1703,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
     case Stmt::UnresolvedMemberExprClass:
     case Stmt::RecoveryExprClass:
     case Stmt::CXXNoexceptExprClass:
+    case Stmt::CXXDeclcallExprClass:
     case Stmt::PackExpansionExprClass:
     case Stmt::PackIndexingExprClass:
     case Stmt::SubstNonTypeTemplateParmPackExprClass:
diff --git a/clang/test/AST/ast-dump-declcall.cpp b/clang/test/AST/ast-dump-declcall.cpp
new file mode 100644
index 0000000000000..422009a0ffe7a
--- /dev/null
+++ b/clang/test/AST/ast-dump-declcall.cpp
@@ -0,0 +1,15 @@
+// RUN: %clang_cc1 -std=c++2c -ast-dump %s | FileCheck %s
+
+int f(int);
+int f(char);
+auto p = declcall(f(0));
+
+// CHECK: CXXDeclcallExpr {{.*}} 'int (*)(int)'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int (*)(int)' 
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int (int)' {{.*}} Function {{.*}} 'f' 'int (int)'
+
+// A devirtualized member-pointer value is dumped with a "(devirtualized)"
+// marker.
+struct B { virtual int g(int); };
+constexpr auto pm = declcall(((B *)0)->B::g(0));
+// CHECK: value: MemberPointer {{.*}}g (devirtualized)
diff --git a/clang/test/AST/ast-print-declcall.cpp b/clang/test/AST/ast-print-declcall.cpp
new file mode 100644
index 0000000000000..77c97e7ed8bd5
--- /dev/null
+++ b/clang/test/AST/ast-print-declcall.cpp
@@ -0,0 +1,15 @@
+// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -triple x86_64-unknown-linux-gnu -ast-print %s -o - | FileCheck %s
+// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -triple x86_64-unknown-linux-gnu -ast-print %s -o - | %clang_cc1 -std=c++2c -fsycl-is-host -triple x86_64-unknown-linux-gnu -fsyntax-only -x c++ -
+
+// declcall prints the original call expression it was written with (not the
+// resolved callee), so the pretty-printed output re-parses.
+
+int f(int);
+int f(char);
+struct B { virtual int g(int); };
+
+// CHECK: auto p = declcall(f(0));
+auto p = declcall(f(0));
+
+// CHECK: auto q = declcall(((B *)0)->B::g(0));
+auto q = declcall(((B *)0)->B::g(0));
diff --git a/clang/test/CodeGenCXX/declcall-microsoft.cpp b/clang/test/CodeGenCXX/declcall-microsoft.cpp
new file mode 100644
index 0000000000000..867e8fbbb5184
--- /dev/null
+++ b/clang/test/CodeGenCXX/declcall-microsoft.cpp
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -triple x86_64-pc-windows-msvc -emit-llvm %s -o - | FileCheck %s
+
+struct B { virtual int g(int); int h(int); };
+
+// A qualified call to a virtual member devirtualizes to a direct function
+// pointer, not a vftable (vcall) thunk.
+// CHECK: @"?pv@@{{[^"]*}}" = {{.*}}global ptr @"?g@B@@UEAAHH@Z"
+auto pv = declcall(((B *)0)->B::g(0));
+
+// An unqualified virtual call keeps virtual dispatch: a vcall thunk.
+// CHECK: @"?pu@@{{[^"]*}}" = {{.*}}global ptr @"??_9B@@$BA@AA"
+auto pu = declcall(((B *)0)->g(0));
+
+// A non-virtual member is a direct pointer.
+// CHECK: @"?ph@@{{[^"]*}}" = {{.*}}global ptr @"?h@B@@QEAAHH@Z"
+auto ph = declcall(((B *)0)->h(0));
diff --git a/clang/test/CodeGenCXX/declcall-modules.cpp b/clang/test/CodeGenCXX/declcall-modules.cpp
new file mode 100644
index 0000000000000..e477d7420e8b2
--- /dev/null
+++ b/clang/test/CodeGenCXX/declcall-modules.cpp
@@ -0,0 +1,18 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-module-interface %t/M.cppm -o %t/M.pcm
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -fmodule-file=M=%t/M.pcm %t/user.cpp -emit-llvm -o - | FileCheck %s
+
+// The devirtualized flag of a declcall must survive C++20 module
+// serialization, just as it does for a PCH.
+
+//--- M.cppm
+export module M;
+struct B { virtual int g(int); };
+export constexpr auto getptr() { return declcall(((B *)0)->B::g(0)); }
+
+//--- user.cpp
+import M;
+auto p = getptr();
+// A direct pointer to the member (devirtualized), not a vtable index.
+// CHECK: @p = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZNW1M1B1gEi to i64), i64 0 }
diff --git a/clang/test/CodeGenCXX/declcall.cpp b/clang/test/CodeGenCXX/declcall.cpp
new file mode 100644
index 0000000000000..ee10036be8855
--- /dev/null
+++ b/clang/test/CodeGenCXX/declcall.cpp
@@ -0,0 +1,57 @@
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -fexperimental-new-constant-interpreter -emit-llvm %s -o - | FileCheck %s
+
+int f(int);
+int f(char);
+using FP = int (*)(int);
+
+// A qualified call to a virtual member is devirtualized: the constant is a
+// direct pointer to the member, not a vtable index.
+// CHECK: @pv = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN1B1gEi to i64), i64 0 }
+// A non-virtual member is a direct pointer as well.
+// CHECK: @ph = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN1B1hEi to i64), i64 0 }
+// An *unqualified* virtual call is NOT devirtualized: the constant keeps
+// virtual dispatch, i.e. a vtable index ({ i64 1, i64 0 }).
+// CHECK: @pu = {{.*}}global { i64, i64 } { i64 1, i64 0 }
+// declcall on an operator call selects the operator function: a free operator
+// yields a function pointer, a member operator yields a member pointer.
+// CHECK: @pfree = {{.*}}global ptr @_Zpl2OpS_
+// CHECK: @pmem = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN2OmmiES_ to i64), i64 0 }
+// A static member function yields a plain function pointer.
+// CHECK: @ps = {{.*}}global ptr @_ZN2SM2sfEi
+struct B { virtual int g(int); int h(int); };
+auto pv = declcall(((B *)0)->B::g(0));
+auto ph = declcall(((B *)0)->h(0));
+auto pu = declcall(((B *)0)->g(0));
+
+// A free-function declcall lowers to the address of the selected overload.
+// CHECK-LABEL: define {{.*}} ptr @_Z3getv()
+// CHECK: ret ptr @_Z1fi
+FP get() { return declcall(f(0)); }
+
+// A declcall in a local (non-constant-initializer) context is emitted through
+// the scalar emitter; it must still produce a devirtualized direct pointer,
+// not a vtable index.
+// CHECK-LABEL: define {{.*}} @_Z3usePM1BFiiE
+// CHECK: store { i64, i64 } { i64 ptrtoint (ptr @_ZN1B1gEi to i64), i64 0 }
+void use(int (B::**out)(int)) {
+  auto pv = declcall(((B *)0)->B::g(0));
+  *out = pv;
+}
+
+// declcall selects and instantiates a function template specialization, and
+// that specialization is emitted.
+template  T tfn(T v) { return v; }
+// CHECK-LABEL: define {{.*}} ptr @_Z8get_tmplv()
+// CHECK: ret ptr @_Z3tfnIiET_S0_
+// CHECK: define {{.*}} @_Z3tfnIiET_S0_
+FP get_tmpl() { return declcall(tfn(0)); }
+
+// Operator-call declcall values (checked with the other globals above).
+struct Op {};
+int operator+(Op, Op);
+struct Om { int operator-(Om); };
+auto pfree = declcall(Op{} + Op{});
+auto pmem = declcall(((Om *)0)->operator-(Om{}));
+struct SM { static int sf(int); };
+auto ps = declcall(SM::sf(0));
diff --git a/clang/test/CodeGenCXX/mangle-declcall.cpp b/clang/test/CodeGenCXX/mangle-declcall.cpp
new file mode 100644
index 0000000000000..38bef13fcb313
--- /dev/null
+++ b/clang/test/CodeGenCXX/mangle-declcall.cpp
@@ -0,0 +1,18 @@
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-pc-windows-msvc -emit-llvm %s -o /dev/null
+
+int f(int);
+int g(int);
+
+template  void sink(T);
+template  void h(decltype(declcall(f(T{}))) p) { sink(p); }
+template  void k(decltype(declcall(g(T{}))) p) { sink(p); }
+
+// A dependent declcall reaches the Itanium mangler. It is mangled as a vendor
+// extended expression (u  * E), so h and k -- which
+// differ only in the declcall operand -- mangle distinctly, instead of both
+// collapsing to a malformed, non-demanglable empty decltype ("DTE").
+// CHECK-DAG: define {{.*}} @_Z1hIiEvDTu8declcallXcl1ftlT_EEEEE
+// CHECK-DAG: define {{.*}} @_Z1kIiEvDTu8declcallXcl1gtlT_EEEEE
+template void h(int (*)(int));
+template void k(int (*)(int));
diff --git a/clang/test/Modules/declcall-odr.cpp b/clang/test/Modules/declcall-odr.cpp
new file mode 100644
index 0000000000000..2d6c9a4bc163b
--- /dev/null
+++ b/clang/test/Modules/declcall-odr.cpp
@@ -0,0 +1,69 @@
+// Clear and create directories
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: mkdir %t/cache
+// RUN: mkdir %t/Inputs
+// Build a shared base header that defines the polymorphic class B in its own
+// module, so both FirstModule and SecondModule import (and merge) the *same* B.
+// RUN: echo "#ifndef BASE_H"                    >> %t/Inputs/base.h
+// RUN: echo "#define BASE_H"                    >> %t/Inputs/base.h
+// RUN: echo "struct B { virtual int g(int); };" >> %t/Inputs/base.h
+// RUN: echo "#endif"                            >> %t/Inputs/base.h
+// Build first header file
+// RUN: echo "#define FIRST" >> %t/Inputs/first.h
+// RUN: cat %s               >> %t/Inputs/first.h
+// Build second header file
+// RUN: echo "#define SECOND" >> %t/Inputs/second.h
+// RUN: cat %s                >> %t/Inputs/second.h
+// Build module map file
+// RUN: echo "module BaseModule {"       >> %t/Inputs/module.modulemap
+// RUN: echo "    header \"base.h\""      >> %t/Inputs/module.modulemap
+// RUN: echo "}"                          >> %t/Inputs/module.modulemap
+// RUN: echo "module FirstModule {"      >> %t/Inputs/module.modulemap
+// RUN: echo "    header \"first.h\""     >> %t/Inputs/module.modulemap
+// RUN: echo "}"                          >> %t/Inputs/module.modulemap
+// RUN: echo "module SecondModule {"     >> %t/Inputs/module.modulemap
+// RUN: echo "    header \"second.h\""    >> %t/Inputs/module.modulemap
+// RUN: echo "}"                          >> %t/Inputs/module.modulemap
+// Run test
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -x c++ -std=c++2c -Wno-declcall-extension \
+// RUN:   -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache \
+// RUN:   -I%t/Inputs -verify %s
+
+// The two definitions of S differ only in the declcall devirtualization flag:
+// FirstModule uses a qualified virtual call (B::g, which devirtualizes),
+// SecondModule uses an unqualified virtual call (g, which does not). Both
+// declcall operands rebuild the same &B::g operand AST, so only the
+// Devirtualize bit distinguishes them. That bit must participate in the ODR
+// hash so the mismatch is diagnosed rather than silently merged.
+
+#include "base.h"
+#if !defined(FIRST) && !defined(SECOND)
+#include "first.h"
+#include "second.h"
+#endif
+
+#if defined(FIRST)
+struct S {
+  int (B::*p)(int) = declcall(((B *)0)->B::g(0)); // devirtualized
+};
+#elif defined(SECOND)
+struct S {
+  int (B::*p)(int) = declcall(((B *)0)->g(0)); // virtual
+};
+#else
+S s;
+// expected-error@first.h:* {{'S' has different definitions in different modules; first difference is definition in module 'FirstModule' found field 'p' with an initializer}}
+// expected-note@second.h:* {{but in 'SecondModule' found field 'p' with a different initializer}}
+#endif
+
+// Keep the FIRST/SECOND macros contained to their own module so they do not
+// leak into the main translation unit (which would re-enter a branch above and
+// spuriously redefine 'S').
+#ifdef FIRST
+#undef FIRST
+#endif
+
+#ifdef SECOND
+#undef SECOND
+#endif
diff --git a/clang/test/PCH/declcall.cpp b/clang/test/PCH/declcall.cpp
new file mode 100644
index 0000000000000..6924d5c39a33f
--- /dev/null
+++ b/clang/test/PCH/declcall.cpp
@@ -0,0 +1,17 @@
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-pch -o %t %s
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -include-pch %t -emit-llvm %s -o - | FileCheck %s
+
+// Check that the devirtualized flag of a declcall expression survives
+// serialization. The declcall is written into the PCH and re-evaluated in the
+// second run.
+
+#ifndef HEADER
+#define HEADER
+struct B { virtual int g(int); };
+constexpr auto getptr() { return declcall(((B *)0)->B::g(0)); }
+#else
+auto p = getptr();
+// A direct pointer to the member (devirtualized), not a vtable index
+// ({ i64 1, i64 0 }).
+// CHECK: @p = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN1B1gEi to i64), i64 0 }
+#endif
diff --git a/clang/test/SemaCXX/declcall-addressof-member.cpp b/clang/test/SemaCXX/declcall-addressof-member.cpp
new file mode 100644
index 0000000000000..0c98c54deba7e
--- /dev/null
+++ b/clang/test/SemaCXX/declcall-addressof-member.cpp
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -fsyntax-only -verify %s
+
+// declcall builds a qualified reference to the selected member function to form
+// its pointer to member. That must not disable the normal diagnostics for
+// improperly taking the address of a member function.
+
+struct S {
+  void m();
+  ~S();
+  void f() {
+    auto p = &m; // expected-error {{must explicitly qualify name of member function when taking its address}}
+    (void)p;
+  }
+};
+
+auto pd = &S::~S; // expected-error {{taking the address of a destructor}}
+
+// declcall of a member function is still accepted.
+auto ok = declcall(((S *)0)->m());
diff --git a/clang/test/SemaCXX/declcall-constexpr-call.cpp b/clang/test/SemaCXX/declcall-constexpr-call.cpp
new file mode 100644
index 0000000000000..a3d46f466215b
--- /dev/null
+++ b/clang/test/SemaCXX/declcall-constexpr-call.cpp
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -fexperimental-new-constant-interpreter -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+// Calling through a declcall member pointer at compile time must honor
+// devirtualization consistently in both constant evaluators.
+
+struct Base {
+  constexpr virtual int foo() const { return 1; }
+};
+struct Derived : Base {
+  constexpr int foo() const override { return 2; }
+};
+
+constexpr Derived d;
+
+// A qualified virtual call devirtualizes: calling through the pointer bypasses
+// virtual dispatch and invokes Base::foo (1).
+constexpr auto devirt = declcall(((Base *)0)->Base::foo());
+static_assert((d.*devirt)() == 1);
+
+// An unqualified virtual call keeps virtual dispatch: Derived::foo (2).
+constexpr auto virt = declcall(((Base *)0)->foo());
+static_assert((d.*virt)() == 2);
diff --git a/clang/test/SemaCXX/declcall-extension.cpp b/clang/test/SemaCXX/declcall-extension.cpp
new file mode 100644
index 0000000000000..2272229d3535d
--- /dev/null
+++ b/clang/test/SemaCXX/declcall-extension.cpp
@@ -0,0 +1,17 @@
+// declcall (P2825) is not standardized. It is a native feature under -fsycl and
+// a Clang extension otherwise.
+
+// Extension warning by default in non-SYCL mode.
+// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify=ext %s
+// Promoted to an error under -pedantic-errors.
+// RUN: %clang_cc1 -std=c++2c -pedantic-errors -fsyntax-only -verify=pedantic %s
+// No diagnostic in SYCL mode, and available even before C++26.
+// RUN: %clang_cc1 -fsycl-is-host -std=c++17 -fsyntax-only -verify=sycl %s
+// RUN: %clang_cc1 -fsycl-is-device -std=c++17 -fsyntax-only -verify=sycl %s
+
+// sycl-no-diagnostics
+
+int f(int);
+
+auto p = declcall(f(0)); // ext-warning {{'declcall' is a Clang extension}} \
+                         // pedantic-error {{'declcall' is a Clang extension}}
diff --git a/clang/test/SemaCXX/declcall-sfinae.cpp b/clang/test/SemaCXX/declcall-sfinae.cpp
new file mode 100644
index 0000000000000..cba025be00637
--- /dev/null
+++ b/clang/test/SemaCXX/declcall-sfinae.cpp
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+// A declcall that is ill-formed in a SFINAE context is a substitution failure,
+// not a hard error.
+
+int f(int);
+
+template  auto sel(int) -> decltype(declcall(f(T{})), char{});
+template  auto sel(...) -> int;
+
+// declcall(f(int{})) is well-formed: the first overload is selected.
+static_assert(sizeof(decltype(sel(0))) == sizeof(char));
+
+// __builtin_abs(T{}) is a valid call, but declcall rejects builtins. The
+// failure must be a substitution failure, selecting the fallback overload,
+// rather than a hard error.
+template  auto selb(int) -> decltype(declcall(__builtin_abs(T{})), char{});
+template  auto selb(...) -> int;
+static_assert(sizeof(decltype(selb(0))) == sizeof(int));
diff --git a/clang/test/SemaCXX/declcall-unevaluated.cpp b/clang/test/SemaCXX/declcall-unevaluated.cpp
new file mode 100644
index 0000000000000..fc483da8030a1
--- /dev/null
+++ b/clang/test/SemaCXX/declcall-unevaluated.cpp
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -Wundefined-internal -verify %s
+// expected-no-diagnostics
+
+// The operand of declcall is an unevaluated operand: its argument
+// subexpressions are not odr-used. An internal-linkage function used only as an
+// argument of the call must therefore not be diagnosed as used-but-not-defined
+// (as it would be in a potentially-evaluated context).
+namespace {
+int undef_arg();
+}
+int f(int);
+auto p = declcall(f(undef_arg()));
diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp
new file mode 100644
index 0000000000000..bc456bfd6edc6
--- /dev/null
+++ b/clang/test/SemaCXX/declcall.cpp
@@ -0,0 +1,96 @@
+// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -fsyntax-only -verify %s
+
+// Baseline semantics for declcall (P2825): overload resolution, operand
+// requirements, and dependent-operand instantiation.
+
+int f(int);
+int f(char);
+using FPInt = int (*)(int);
+using FPChar = int (*)(char);
+
+// declcall(f(0)) resolves the overload as if calling f(0) and yields a
+// pointer to the selected function.
+constexpr auto p = declcall(f(0));
+static_assert(__is_same(decltype(p), int (*const)(int)));
+static_assert(p == static_cast(f));
+
+// A char argument selects the other overload.
+constexpr auto pc = declcall(f('a'));
+static_assert(__is_same(decltype(pc), int (*const)(char)));
+
+// declcall preserves the exact function type, including noexcept.
+int nef(int) noexcept;
+static_assert(__is_same(decltype(declcall(nef(0))), int (*)(int) noexcept));
+static_assert(!__is_same(decltype(declcall(nef(0))), int (*)(int)));
+struct NE { int m(int) noexcept; };
+static_assert(
+    __is_same(decltype(declcall(((NE *)0)->m(0))), int (NE::*)(int) noexcept));
+
+// declcall deduces and selects the right function template specialization.
+template  T ttmpl(T);
+static_assert(__is_same(decltype(declcall(ttmpl(0))), int (*)(int)));
+static_assert(__is_same(decltype(declcall(ttmpl('a'))), char (*)(char)));
+
+// declcall selects the right cv/ref-qualified member overload and preserves
+// the qualifier in the resulting member-pointer type.
+struct CV {
+  int m(int);
+  int m(int) const;
+  int r(int) &;
+  int r(int) &&;
+};
+static_assert(__is_same(decltype(declcall(((CV *)0)->m(0))), int (CV::*)(int)));
+static_assert(
+    __is_same(decltype(declcall(((const CV *)0)->m(0))), int (CV::*)(int) const));
+CV cv;
+static_assert(__is_same(decltype(declcall(cv.r(0))), int (CV::*)(int) &));
+static_assert(__is_same(decltype(declcall(CV{}.r(0))), int (CV::*)(int) &&));
+
+// declcall of a static member function yields a plain function pointer, not a
+// member pointer.
+struct SM {
+  static int sf(int);
+  static int sf(char);
+};
+static_assert(__is_same(decltype(declcall(SM::sf(0))), int (*)(int)));
+static_assert(__is_same(decltype(declcall(SM::sf('a'))), int (*)(char)));
+
+// declcall on an operator call selects the operator function.
+struct OpA {};
+int operator+(OpA, OpA);
+struct OpM { int operator-(OpM); };
+static_assert(__is_same(decltype(declcall(OpA{} + OpA{})), int (*)(OpA, OpA)));
+static_assert(
+    __is_same(decltype(declcall(((OpM *)0)->operator-(OpM{}))), int (OpM::*)(OpM)));
+
+// The operand must be a call expression.
+int x;
+auto bad = declcall(x); // expected-error {{declcall doesn't contain a call}}
+
+// A call through a runtime function pointer is not constant-evaluable. This
+// must produce exactly one diagnostic: the failed declcall must not leave a
+// live expression behind that cascades into further errors.
+int (*fp)(int) = &f;
+constexpr auto rt = declcall(fp(0)); // expected-error {{declcall must not depend on runtime known value}}
+
+// A call through a pointer to member has no compile-time-known callee. This
+// must be diagnosed, not crash the compiler.
+struct S { int m(int); };
+S s;
+int (S::*pmf)(int);
+auto pm = declcall((s.*pmf)(0)); // expected-error {{declcall doesn't support a call through a pointer to member yet}}
+
+// declcall is ill-formed for destructors and builtin functions.
+struct D { ~D(); };
+void ill_formed(D *d) {
+  auto pd = declcall(d->~D()); // expected-error {{declcall doesn't support a destructor yet}}
+  (void)pd;
+}
+auto pbuiltin = declcall(__builtin_abs(0)); // expected-error {{declcall doesn't support a builtin function yet}}
+
+// Dependent operands are resolved at instantiation time.
+template  auto call(T v) { return declcall(f(v)); }
+void use() {
+  static_assert(__is_same(decltype(call(0)), FPInt));
+  static_assert(__is_same(decltype(call('a')), FPChar));
+}
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index 267f8a10b14f6..9669d1ae00855 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -433,6 +433,7 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
 
   case Stmt::UnaryExprOrTypeTraitExprClass:
   case Stmt::CXXNoexceptExprClass:
+  case Stmt::CXXDeclcallExprClass:
     K = CXCursor_UnaryExpr;
     break;