rnk created this revision.
rnk added a reviewer: rsmith.

When the C++ committee changed the wording around non-type template
arguments, they naively thought that every global declarations address
would be a constant expression, but this is not the case.  There is no
PE/COFF relocation that allows using the address of a dllimport global
in another global. Therefore, we do not consider them constexpr, because
users expect that constexpr globals do not require dynamic
initialization.

However, there's nothing that prevents us from doing template
instantiation with dllimported non-type template parameters, so we
should allow it. That's what this patch does.

Fixes PR35772.

At first I tried to implement this as another evaluation mode
'EM_NonTypeTemplateArgument', but I would need rvalue and lvalue
varaints in order to preserve existing behavior around side effects.


https://reviews.llvm.org/D43320

Files:
  clang/include/clang/AST/Expr.h
  clang/lib/AST/ExprConstant.cpp
  clang/lib/Sema/SemaOverload.cpp
  clang/test/SemaCXX/dllimport-constexpr.cpp
  clang/test/SemaCXX/dllimport-memptr.cpp

Index: clang/test/SemaCXX/dllimport-memptr.cpp
===================================================================
--- clang/test/SemaCXX/dllimport-memptr.cpp
+++ clang/test/SemaCXX/dllimport-memptr.cpp
@@ -1,4 +1,5 @@
 // RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-extensions -verify -std=c++11 %s
+// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-extensions -verify -std=c++17 %s
 
 // expected-no-diagnostics
 
Index: clang/test/SemaCXX/dllimport-constexpr.cpp
===================================================================
--- /dev/null
+++ clang/test/SemaCXX/dllimport-constexpr.cpp
@@ -0,0 +1,62 @@
+// RUN: %clang_cc1 -std=c++14 %s -verify -fms-extensions -triple x86_64-windows-msvc
+// RUN: %clang_cc1 -std=c++17 %s -verify -fms-extensions -triple x86_64-windows-msvc
+
+__declspec(dllimport) void imported_func();
+__declspec(dllimport) int imported_int;
+struct Foo {
+  void __declspec(dllimport) imported_method();
+};
+
+// Instantiation is OK.
+template <void (*FP)()> struct TemplateFnPtr {
+  static void getit() { FP(); }
+};
+template <void (&FP)()> struct TemplateFnRef {
+  static void getit() { FP(); }
+};
+void instantiate1() {
+  TemplateFnPtr<&imported_func>::getit();
+  TemplateFnRef<imported_func>::getit();
+}
+
+// Check variable template instantiation.
+template <int *GI> struct TemplateIntPtr {
+  static int getit() { return *GI; }
+};
+template <int &GI> struct TemplateIntRef {
+  static int getit() { return GI; }
+};
+int instantiate2() {
+  int r = 0;
+  r += TemplateIntPtr<&imported_int>::getit();
+  r += TemplateIntRef<imported_int>::getit();
+  return r;
+}
+
+// Member pointer instantiation.
+template <void (Foo::*MP)()> struct TemplateMemPtr { };
+TemplateMemPtr<&Foo::imported_method> instantiate_mp;
+
+// constexpr initialization doesn't work for dllimport things.
+// expected-error@+1{{must be initialized by a constant expression}}
+constexpr void (*constexpr_import_func)() = &imported_func;
+// expected-error@+1{{must be initialized by a constant expression}}
+constexpr int *constexpr_import_int = &imported_int;
+// expected-error@+1{{must be initialized by a constant expression}}
+constexpr void (Foo::*constexpr_memptr)() = &Foo::imported_method;
+
+// We make dynamic initializers for 'const' globals, but not constexpr ones.
+void (*const const_import_func)() = &imported_func;
+int *const const_import_int = &imported_int;
+void (Foo::*const const_memptr)() = &Foo::imported_method;
+
+// Check that using a non-type template parameter for constexpr global
+// initialization is correctly diagnosed during template instantiation.
+template <void (*FP)()> struct StaticConstexpr {
+  // expected-error@+1{{must be initialized by a constant expression}}
+  static constexpr void (*g_fp)() = FP;
+};
+void instantiate3() {
+  // expected-note@+1 {{requested here}}
+  StaticConstexpr<imported_func>::g_fp();
+}
Index: clang/lib/Sema/SemaOverload.cpp
===================================================================
--- clang/lib/Sema/SemaOverload.cpp
+++ clang/lib/Sema/SemaOverload.cpp
@@ -5398,9 +5398,7 @@
   Expr::EvalResult Eval;
   Eval.Diag = &Notes;
 
-  if ((T->isReferenceType()
-           ? !Result.get()->EvaluateAsLValue(Eval, S.Context)
-           : !Result.get()->EvaluateAsRValue(Eval, S.Context)) ||
+  if (!Result.get()->EvaluateAsNonTypeTemplateArgument(Eval, T, S.Context) ||
       (RequireInt && !Eval.Val.isInt())) {
     // The expression can't be folded, so we can't keep it at this position in
     // the AST.
Index: clang/lib/AST/ExprConstant.cpp
===================================================================
--- clang/lib/AST/ExprConstant.cpp
+++ clang/lib/AST/ExprConstant.cpp
@@ -582,6 +582,9 @@
     /// we will evaluate.
     unsigned StepsLeft;
 
+    /// True if we are evaluating for a non-type template argument.
+    bool IsNonTypeTemplateArgument = false;
+
     /// BottomFrame - The frame in which evaluation started. This must be
     /// initialized after CurrentCall and CallStackDepth.
     CallStackFrame BottomFrame;
@@ -738,6 +741,14 @@
       return false;
     }
 
+    /// Return true if this declaration is dllimport and we cannot treat the
+    /// address as a constant expression. Generally, we do not want to constant
+    /// fold dllimport declarations unless they are used in a non-type template
+    /// parameter.
+    bool isNonConstDllImportDecl(const Decl *D) {
+      return !IsNonTypeTemplateArgument && D->hasAttr<DLLImportAttr>();
+    }
+
     CallStackFrame *getCallFrame(unsigned CallIndex) {
       assert(CallIndex && "no call index in getCallFrame");
       // We will eventually hit BottomFrame, which has Index 1, so Frame can't
@@ -1686,7 +1697,7 @@
         return false;
 
       // A dllimport variable never acts like a constant.
-      if (Var->hasAttr<DLLImportAttr>())
+      if (Info.isNonConstDllImportDecl(Var))
         return false;
     }
     if (const auto *FD = dyn_cast<const FunctionDecl>(VD)) {
@@ -1700,7 +1711,7 @@
       // The C language has no notion of ODR; furthermore, it has no notion of
       // dynamic initialization.  This means that we are permitted to
       // perform initialization with the address of the thunk.
-      if (Info.getLangOpts().CPlusPlus && FD->hasAttr<DLLImportAttr>())
+      if (Info.getLangOpts().CPlusPlus && Info.isNonConstDllImportDecl(FD))
         return false;
     }
   }
@@ -1738,7 +1749,7 @@
   const auto *FD = dyn_cast_or_null<CXXMethodDecl>(Member);
   if (!FD)
     return true;
-  return FD->isVirtual() || !FD->hasAttr<DLLImportAttr>();
+  return FD->isVirtual() || !Info.isNonConstDllImportDecl(FD);
 }
 
 /// Check that this core constant expression is of literal type, and if not,
@@ -10147,6 +10158,32 @@
   return true;
 }
 
+bool Expr::EvaluateAsNonTypeTemplateArgument(EvalResult &Result,
+                                             QualType ParamType,
+                                             const ASTContext &Ctx) const {
+  // A reference must be an lvalue.
+  if (ParamType->isReferenceType()) {
+    EvalInfo Info(Ctx, Result, EvalInfo::EM_ConstantFold);
+    Info.IsNonTypeTemplateArgument = true;
+    LValue LV;
+    if (!EvaluateLValue(this, LV, Info) || Result.HasSideEffects ||
+        !CheckLValueConstantExpression(
+            Info, getExprLoc(), Ctx.getLValueReferenceType(getType()), LV))
+      return false;
+
+    LV.moveInto(Result.Val);
+    return true;
+  }
+
+  // Otherwise, evaluate it as an rvalue.
+  bool IsConst;
+  if (FastEvaluateAsRValue(this, Result, Ctx, IsConst))
+    return IsConst;
+  EvalInfo Info(Ctx, Result, EvalInfo::EM_IgnoreSideEffects);
+  Info.IsNonTypeTemplateArgument = true;
+  return ::EvaluateAsRValue(Info, this, Result.Val);
+}
+
 bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
                                  const VarDecl *VD,
                             SmallVectorImpl<PartialDiagnosticAt> &Notes) const {
Index: clang/include/clang/AST/Expr.h
===================================================================
--- clang/include/clang/AST/Expr.h
+++ clang/include/clang/AST/Expr.h
@@ -658,6 +658,10 @@
                                 ArrayRef<const Expr*> Args,
                                 const Expr *This = nullptr) const;
 
+  /// Evaluate an expression to be used as a non-type template argument.
+  bool EvaluateAsNonTypeTemplateArgument(EvalResult &Result, QualType ParamType,
+                                         const ASTContext &Ctx) const;
+
   /// \brief If the current Expr is a pointer, this will try to statically
   /// determine the number of bytes available where the pointer is pointing.
   /// Returns true if all of the above holds and we were able to figure out the
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D43320: A... Reid Kleckner via Phabricator via cfe-commits

Reply via email to