[Clang Interpreter] Initial patch for the constexpr interpreter

Summary:
This patch introduces the skeleton of the constexpr interpreter,
capable of evaluating a simple constexpr functions consisting of
if statements. The interpreter is described in more detail in the
RFC. Further patches will add more features.

Reviewers: Bigcheese, jfb, rsmith

Subscribers: bruno, uenoku, ldionne, Tyker, thegameg, tschuett, dexonsmith, mgorny, cfe-commits

Tags: #clang

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

llvm-svn: 371834
diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp
new file mode 100644
index 0000000..c9ace13
--- /dev/null
+++ b/clang/lib/AST/Interp/Interp.cpp
@@ -0,0 +1,417 @@
+//===--- InterpState.cpp - Interpreter for the constexpr VM -----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Interp.h"
+#include <limits>
+#include <vector>
+#include "Function.h"
+#include "InterpFrame.h"
+#include "InterpStack.h"
+#include "Opcode.h"
+#include "PrimType.h"
+#include "Program.h"
+#include "State.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTDiagnostic.h"
+#include "clang/AST/CXXInheritance.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "llvm/ADT/APSInt.h"
+
+using namespace clang;
+using namespace clang::interp;
+
+//===----------------------------------------------------------------------===//
+// Ret
+//===----------------------------------------------------------------------===//
+
+template <PrimType Name, class T = typename PrimConv<Name>::T>
+static bool Ret(InterpState &S, CodePtr &PC, APValue &Result) {
+  S.CallStackDepth--;
+  const T &Ret = S.Stk.pop<T>();
+
+  assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame");
+  if (!S.checkingPotentialConstantExpression())
+    S.Current->popArgs();
+
+  if (InterpFrame *Caller = S.Current->Caller) {
+    PC = S.Current->getRetPC();
+    delete S.Current;
+    S.Current = Caller;
+    S.Stk.push<T>(Ret);
+  } else {
+    delete S.Current;
+    S.Current = nullptr;
+    if (!ReturnValue<T>(Ret, Result))
+      return false;
+  }
+  return true;
+}
+
+static bool RetVoid(InterpState &S, CodePtr &PC, APValue &Result) {
+  S.CallStackDepth--;
+
+  assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame");
+  if (!S.checkingPotentialConstantExpression())
+    S.Current->popArgs();
+
+  if (InterpFrame *Caller = S.Current->Caller) {
+    PC = S.Current->getRetPC();
+    delete S.Current;
+    S.Current = Caller;
+  } else {
+    delete S.Current;
+    S.Current = nullptr;
+  }
+  return true;
+}
+
+static bool RetValue(InterpState &S, CodePtr &Pt, APValue &Result) {
+  llvm::report_fatal_error("Interpreter cannot return values");
+}
+
+//===----------------------------------------------------------------------===//
+// Jmp, Jt, Jf
+//===----------------------------------------------------------------------===//
+
+static bool Jmp(InterpState &S, CodePtr &PC, int32_t Offset) {
+  PC += Offset;
+  return true;
+}
+
+static bool Jt(InterpState &S, CodePtr &PC, int32_t Offset) {
+  if (S.Stk.pop<bool>()) {
+    PC += Offset;
+  }
+  return true;
+}
+
+static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) {
+  if (!S.Stk.pop<bool>()) {
+    PC += Offset;
+  }
+  return true;
+}
+
+static bool CheckInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                             AccessKinds AK) {
+  if (Ptr.isInitialized())
+    return true;
+  if (!S.checkingPotentialConstantExpression()) {
+    const SourceInfo &Loc = S.Current->getSource(OpPC);
+    S.FFDiag(Loc, diag::note_constexpr_access_uninit) << AK << false;
+  }
+  return false;
+}
+
+static bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                        AccessKinds AK) {
+  if (Ptr.isActive())
+    return true;
+
+  // Get the inactive field descriptor.
+  const FieldDecl *InactiveField = Ptr.getField();
+
+  // Walk up the pointer chain to find the union which is not active.
+  Pointer U = Ptr.getBase();
+  while (!U.isActive()) {
+    U = U.getBase();
+  }
+
+  // Find the active field of the union.
+  Record *R = U.getRecord();
+  assert(R && R->isUnion() && "Not a union");
+  const FieldDecl *ActiveField = nullptr;
+  for (unsigned I = 0, N = R->getNumFields(); I < N; ++I) {
+    const Pointer &Field = U.atField(R->getField(I)->Offset);
+    if (Field.isActive()) {
+      ActiveField = Field.getField();
+      break;
+    }
+  }
+
+  const SourceInfo &Loc = S.Current->getSource(OpPC);
+  S.FFDiag(Loc, diag::note_constexpr_access_inactive_union_member)
+      << AK << InactiveField << !ActiveField << ActiveField;
+  return false;
+}
+
+static bool CheckTemporary(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                           AccessKinds AK) {
+  if (auto ID = Ptr.getDeclID()) {
+    if (!Ptr.isStaticTemporary())
+      return true;
+
+    if (Ptr.getDeclDesc()->getType().isConstQualified())
+      return true;
+
+    if (S.P.getCurrentDecl() == ID)
+      return true;
+
+    const SourceInfo &E = S.Current->getSource(OpPC);
+    S.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK;
+    S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here);
+    return false;
+  }
+  return true;
+}
+
+static bool CheckGlobal(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  if (auto ID = Ptr.getDeclID()) {
+    if (!Ptr.isStatic())
+      return true;
+
+    if (S.P.getCurrentDecl() == ID)
+      return true;
+
+    S.FFDiag(S.Current->getLocation(OpPC), diag::note_constexpr_modify_global);
+    return false;
+  }
+  return true;
+}
+
+namespace clang {
+namespace interp {
+
+bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  if (!Ptr.isExtern())
+    return true;
+
+  if (!S.checkingPotentialConstantExpression()) {
+    auto *VD = Ptr.getDeclDesc()->asValueDecl();
+    const SourceInfo &Loc = S.Current->getSource(OpPC);
+    S.FFDiag(Loc, diag::note_constexpr_ltor_non_constexpr, 1) << VD;
+    S.Note(VD->getLocation(), diag::note_declared_at);
+  }
+  return false;
+}
+
+bool CheckArray(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  if (!Ptr.isUnknownSizeArray())
+    return true;
+  const SourceInfo &E = S.Current->getSource(OpPC);
+  S.FFDiag(E, diag::note_constexpr_unsized_array_indexed);
+  return false;
+}
+
+bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+               AccessKinds AK) {
+  const auto &Src = S.Current->getSource(OpPC);
+  if (Ptr.isZero()) {
+
+    if (Ptr.isField())
+      S.FFDiag(Src, diag::note_constexpr_null_subobject) << CSK_Field;
+    else
+      S.FFDiag(Src, diag::note_constexpr_access_null) << AK;
+
+    return false;
+  }
+
+  if (!Ptr.isLive()) {
+    bool IsTemp = Ptr.isTemporary();
+
+    S.FFDiag(Src, diag::note_constexpr_lifetime_ended, 1) << AK << !IsTemp;
+
+    if (IsTemp)
+      S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here);
+    else
+      S.Note(Ptr.getDeclLoc(), diag::note_declared_at);
+
+    return false;
+  }
+
+  return true;
+}
+
+bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+               CheckSubobjectKind CSK) {
+  if (!Ptr.isZero())
+    return true;
+  const SourceInfo &Loc = S.Current->getSource(OpPC);
+  S.FFDiag(Loc, diag::note_constexpr_null_subobject) << CSK;
+  return false;
+}
+
+bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                AccessKinds AK) {
+  if (!Ptr.isOnePastEnd())
+    return true;
+  const SourceInfo &Loc = S.Current->getSource(OpPC);
+  S.FFDiag(Loc, diag::note_constexpr_access_past_end) << AK;
+  return false;
+}
+
+bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                CheckSubobjectKind CSK) {
+  if (!Ptr.isElementPastEnd())
+    return true;
+  const SourceInfo &Loc = S.Current->getSource(OpPC);
+  S.FFDiag(Loc, diag::note_constexpr_past_end_subobject) << CSK;
+  return false;
+}
+
+bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  assert(Ptr.isLive() && "Pointer is not live");
+  if (!Ptr.isConst()) {
+    return true;
+  }
+
+  const QualType Ty = Ptr.getType();
+  const SourceInfo &Loc = S.Current->getSource(OpPC);
+  S.FFDiag(Loc, diag::note_constexpr_modify_const_type) << Ty;
+  return false;
+}
+
+bool CheckMutable(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  assert(Ptr.isLive() && "Pointer is not live");
+  if (!Ptr.isMutable()) {
+    return true;
+  }
+
+  const SourceInfo &Loc = S.Current->getSource(OpPC);
+  const FieldDecl *Field = Ptr.getField();
+  S.FFDiag(Loc, diag::note_constexpr_ltor_mutable, 1) << Field;
+  S.Note(Field->getLocation(), diag::note_declared_at);
+  return false;
+}
+
+bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  if (!CheckLive(S, OpPC, Ptr, AK_Read))
+    return false;
+  if (!CheckExtern(S, OpPC, Ptr))
+    return false;
+  if (!CheckRange(S, OpPC, Ptr, AK_Read))
+    return false;
+  if (!CheckInitialized(S, OpPC, Ptr, AK_Read))
+    return false;
+  if (!CheckActive(S, OpPC, Ptr, AK_Read))
+    return false;
+  if (!CheckTemporary(S, OpPC, Ptr, AK_Read))
+    return false;
+  if (!CheckMutable(S, OpPC, Ptr))
+    return false;
+  return true;
+}
+
+bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  if (!CheckLive(S, OpPC, Ptr, AK_Assign))
+    return false;
+  if (!CheckExtern(S, OpPC, Ptr))
+    return false;
+  if (!CheckRange(S, OpPC, Ptr, AK_Assign))
+    return false;
+  if (!CheckGlobal(S, OpPC, Ptr))
+    return false;
+  if (!CheckConst(S, OpPC, Ptr))
+    return false;
+  return true;
+}
+
+bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  if (!CheckLive(S, OpPC, Ptr, AK_MemberCall))
+    return false;
+  if (!CheckExtern(S, OpPC, Ptr))
+    return false;
+  if (!CheckRange(S, OpPC, Ptr, AK_MemberCall))
+    return false;
+  return true;
+}
+
+bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  if (!CheckLive(S, OpPC, Ptr, AK_Assign))
+    return false;
+  if (!CheckRange(S, OpPC, Ptr, AK_Assign))
+    return false;
+  return true;
+}
+
+bool CheckCallable(InterpState &S, CodePtr OpPC, Function *F) {
+  const SourceLocation &Loc = S.Current->getLocation(OpPC);
+
+  if (F->isVirtual()) {
+    if (!S.getLangOpts().CPlusPlus2a) {
+      S.CCEDiag(Loc, diag::note_constexpr_virtual_call);
+      return false;
+    }
+  }
+
+  if (!F->isConstexpr()) {
+    if (S.getLangOpts().CPlusPlus11) {
+      const FunctionDecl *DiagDecl = F->getDecl();
+
+      // If this function is not constexpr because it is an inherited
+      // non-constexpr constructor, diagnose that directly.
+      auto *CD = dyn_cast<CXXConstructorDecl>(DiagDecl);
+      if (CD && CD->isInheritingConstructor()) {
+        auto *Inherited = CD->getInheritedConstructor().getConstructor();
+        if (!Inherited->isConstexpr())
+          DiagDecl = CD = Inherited;
+      }
+
+      // FIXME: If DiagDecl is an implicitly-declared special member function
+      // or an inheriting constructor, we should be much more explicit about why
+      // it's not constexpr.
+      if (CD && CD->isInheritingConstructor())
+        S.FFDiag(Loc, diag::note_constexpr_invalid_inhctor, 1)
+          << CD->getInheritedConstructor().getConstructor()->getParent();
+      else
+        S.FFDiag(Loc, diag::note_constexpr_invalid_function, 1)
+          << DiagDecl->isConstexpr() << (bool)CD << DiagDecl;
+      S.Note(DiagDecl->getLocation(), diag::note_declared_at);
+    } else {
+      S.FFDiag(Loc, diag::note_invalid_subexpr_in_const_expr);
+    }
+    return false;
+  }
+
+  return true;
+}
+
+bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This) {
+  if (!This.isZero())
+    return true;
+
+  const SourceInfo &Loc = S.Current->getSource(OpPC);
+
+  bool IsImplicit = false;
+  if (auto *E = dyn_cast_or_null<CXXThisExpr>(Loc.asExpr()))
+    IsImplicit = E->isImplicit();
+
+  if (S.getLangOpts().CPlusPlus11)
+    S.FFDiag(Loc, diag::note_constexpr_this) << IsImplicit;
+  else
+    S.FFDiag(Loc);
+
+  return false;
+}
+
+bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD) {
+  if (!MD->isPure())
+    return true;
+  const SourceInfo &E = S.Current->getSource(OpPC);
+  S.FFDiag(E, diag::note_constexpr_pure_virtual_call, 1) << MD;
+  S.Note(MD->getLocation(), diag::note_declared_at);
+  return false;
+}
+bool Interpret(InterpState &S, APValue &Result) {
+  CodePtr PC = S.Current->getPC();
+
+  for (;;) {
+    auto Op = PC.read<Opcode>();
+    CodePtr OpPC = PC;
+
+    switch (Op) {
+#define GET_INTERP
+#include "Opcodes.inc"
+#undef GET_INTERP
+    }
+  }
+}
+
+} // namespace interp
+} // namespace clang