[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
2019-09-13 17:46:16 +08:00
|
|
|
//===--- 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();
|
2019-09-29 13:08:46 +08:00
|
|
|
S.FFDiag(Loc, diag::note_constexpr_access_mutable, 1) << AK_Read << Field;
|
[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
2019-09-13 17:46:16 +08:00
|
|
|
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()) {
|
2020-04-22 03:37:19 +08:00
|
|
|
if (!S.getLangOpts().CPlusPlus20) {
|
[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
2019-09-13 17:46:16 +08:00
|
|
|
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
|