[ConstExprPreter] Removed the flag forcing the use of the interpreter

Summary:
Removed the ```-fforce-experimental-new-constant-interpreter flag```, leaving
only the ```-fexperimental-new-constant-interpreter``` one. The interpreter
now always emits an error on an unsupported feature.

Allowing the interpreter to bail out would require a mapping from APValue to
interpreter memory, which will not be necessary in the final version. It is
more sensible to always emit an error if the interpreter fails.

Reviewers: jfb, Bigcheese, rsmith, dexonsmith

Subscribers: cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D70071
This commit is contained in:
Nandor Licker 2019-11-11 11:13:34 +00:00
parent 9283681e16
commit f584f04dab
9 changed files with 76 additions and 148 deletions

View File

@ -10,8 +10,7 @@ Introduction
The constexpr interpreter aims to replace the existing tree evaluator in clang, improving performance on constructs which are executed inefficiently by the evaluator. The interpreter is activated using the following flags: The constexpr interpreter aims to replace the existing tree evaluator in clang, improving performance on constructs which are executed inefficiently by the evaluator. The interpreter is activated using the following flags:
* ``-fexperimental-new-constant-interpreter`` enables the interpreter, falling back to the evaluator for unsupported features * ``-fexperimental-new-constant-interpreter`` enables the interpreter, emitting an error if an unsupported feature is encountered
* ``-fforce-experimental-new-constant-interpreter`` forces the use of the interpreter, bailing out if an unsupported feature is encountered
Bytecode Compilation Bytecode Compilation
==================== ====================

View File

@ -297,8 +297,6 @@ BENIGN_LANGOPT(ConstexprStepLimit, 32, 1048576,
"maximum constexpr evaluation steps") "maximum constexpr evaluation steps")
BENIGN_LANGOPT(EnableNewConstInterp, 1, 0, BENIGN_LANGOPT(EnableNewConstInterp, 1, 0,
"enable the experimental new constant interpreter") "enable the experimental new constant interpreter")
BENIGN_LANGOPT(ForceNewConstInterp, 1, 0,
"force the use of the experimental new constant interpreter")
BENIGN_LANGOPT(BracketDepth, 32, 256, BENIGN_LANGOPT(BracketDepth, 32, 256,
"maximum bracket nesting depth") "maximum bracket nesting depth")
BENIGN_LANGOPT(NumLargeByValueCopy, 32, 0, BENIGN_LANGOPT(NumLargeByValueCopy, 32, 0,

View File

@ -850,8 +850,6 @@ def fconstexpr_depth_EQ : Joined<["-"], "fconstexpr-depth=">, Group<f_Group>;
def fconstexpr_steps_EQ : Joined<["-"], "fconstexpr-steps=">, Group<f_Group>; def fconstexpr_steps_EQ : Joined<["-"], "fconstexpr-steps=">, Group<f_Group>;
def fexperimental_new_constant_interpreter : Flag<["-"], "fexperimental-new-constant-interpreter">, Group<f_Group>, def fexperimental_new_constant_interpreter : Flag<["-"], "fexperimental-new-constant-interpreter">, Group<f_Group>,
HelpText<"Enable the experimental new constant interpreter">, Flags<[CC1Option]>; HelpText<"Enable the experimental new constant interpreter">, Flags<[CC1Option]>;
def fforce_experimental_new_constant_interpreter : Flag<["-"], "fforce-experimental-new-constant-interpreter">, Group<f_Group>,
HelpText<"Force the use of the experimental new constant interpreter, failing on missing features">, Flags<[CC1Option]>;
def fconstexpr_backtrace_limit_EQ : Joined<["-"], "fconstexpr-backtrace-limit=">, def fconstexpr_backtrace_limit_EQ : Joined<["-"], "fconstexpr-backtrace-limit=">,
Group<f_Group>; Group<f_Group>;
def fno_crash_diagnostics : Flag<["-"], "fno-crash-diagnostics">, Group<f_clang_Group>, Flags<[NoArgumentUnused, CoreOption]>, def fno_crash_diagnostics : Flag<["-"], "fno-crash-diagnostics">, Group<f_clang_Group>, Flags<[NoArgumentUnused, CoreOption]>,

View File

@ -763,11 +763,8 @@ namespace {
/// we will evaluate. /// we will evaluate.
unsigned StepsLeft; unsigned StepsLeft;
/// Force the use of the experimental new constant interpreter, bailing out /// Enable the experimental new constant interpreter. If an expression is
/// with an error if a feature is not supported. /// not supported by the interpreter, an error is triggered.
bool ForceNewConstInterp;
/// Enable the experimental new constant interpreter.
bool EnableNewConstInterp; bool EnableNewConstInterp;
/// BottomFrame - The frame in which evaluation started. This must be /// BottomFrame - The frame in which evaluation started. This must be
@ -922,9 +919,7 @@ namespace {
: Ctx(const_cast<ASTContext &>(C)), EvalStatus(S), CurrentCall(nullptr), : Ctx(const_cast<ASTContext &>(C)), EvalStatus(S), CurrentCall(nullptr),
CallStackDepth(0), NextCallIndex(1), CallStackDepth(0), NextCallIndex(1),
StepsLeft(C.getLangOpts().ConstexprStepLimit), StepsLeft(C.getLangOpts().ConstexprStepLimit),
ForceNewConstInterp(C.getLangOpts().ForceNewConstInterp), EnableNewConstInterp(C.getLangOpts().EnableNewConstInterp),
EnableNewConstInterp(ForceNewConstInterp ||
C.getLangOpts().EnableNewConstInterp),
BottomFrame(*this, SourceLocation(), nullptr, nullptr, nullptr), BottomFrame(*this, SourceLocation(), nullptr, nullptr, nullptr),
EvaluatingDecl((const ValueDecl *)nullptr), EvaluatingDecl((const ValueDecl *)nullptr),
EvaluatingDeclValue(nullptr), HasActiveDiagnostic(false), EvaluatingDeclValue(nullptr), HasActiveDiagnostic(false),
@ -13400,34 +13395,27 @@ static bool EvaluateInPlace(APValue &Result, EvalInfo &Info, const LValue &This,
/// EvaluateAsRValue - Try to evaluate this expression, performing an implicit /// EvaluateAsRValue - Try to evaluate this expression, performing an implicit
/// lvalue-to-rvalue cast if it is an lvalue. /// lvalue-to-rvalue cast if it is an lvalue.
static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result) { static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result) {
if (Info.EnableNewConstInterp) { if (Info.EnableNewConstInterp) {
auto &InterpCtx = Info.Ctx.getInterpContext(); if (!Info.Ctx.getInterpContext().evaluateAsRValue(Info, E, Result))
switch (InterpCtx.evaluateAsRValue(Info, E, Result)) {
case interp::InterpResult::Success:
return true;
case interp::InterpResult::Fail:
return false; return false;
case interp::InterpResult::Bail: } else {
break; if (E->getType().isNull())
return false;
if (!CheckLiteralType(Info, E))
return false;
if (!::Evaluate(Result, Info, E))
return false;
if (E->isGLValue()) {
LValue LV;
LV.setFrom(Info.Ctx, Result);
if (!handleLValueToRValueConversion(Info, E, E->getType(), LV, Result))
return false;
} }
} }
if (E->getType().isNull())
return false;
if (!CheckLiteralType(Info, E))
return false;
if (!::Evaluate(Result, Info, E))
return false;
if (E->isGLValue()) {
LValue LV;
LV.setFrom(Info.Ctx, Result);
if (!handleLValueToRValueConversion(Info, E, E->getType(), LV, Result))
return false;
}
// Check this core constant expression is a constant expression. // Check this core constant expression is a constant expression.
return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result) && return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result) &&
CheckMemoryLeaks(Info); CheckMemoryLeaks(Info);
@ -13637,46 +13625,36 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
if (Info.EnableNewConstInterp) { if (Info.EnableNewConstInterp) {
auto &InterpCtx = const_cast<ASTContext &>(Ctx).getInterpContext(); auto &InterpCtx = const_cast<ASTContext &>(Ctx).getInterpContext();
switch (InterpCtx.evaluateAsInitializer(Info, VD, Value)) { if (!InterpCtx.evaluateAsInitializer(Info, VD, Value))
case interp::InterpResult::Fail:
// Bail out if an error was encountered.
return false; return false;
case interp::InterpResult::Success: } else {
// Evaluation succeeded and value was set. LValue LVal;
return CheckConstantExpression(Info, DeclLoc, DeclTy, Value); LVal.set(VD);
case interp::InterpResult::Bail:
// Evaluate the value again for the tree evaluator to use. // C++11 [basic.start.init]p2:
break; // Variables with static storage duration or thread storage duration shall
// be zero-initialized before any other initialization takes place.
// This behavior is not present in C.
if (Ctx.getLangOpts().CPlusPlus && !VD->hasLocalStorage() &&
!DeclTy->isReferenceType()) {
ImplicitValueInitExpr VIE(DeclTy);
if (!EvaluateInPlace(Value, Info, LVal, &VIE,
/*AllowNonLiteralTypes=*/true))
return false;
} }
}
LValue LVal; if (!EvaluateInPlace(Value, Info, LVal, this,
LVal.set(VD); /*AllowNonLiteralTypes=*/true) ||
EStatus.HasSideEffects)
// C++11 [basic.start.init]p2:
// Variables with static storage duration or thread storage duration shall be
// zero-initialized before any other initialization takes place.
// This behavior is not present in C.
if (Ctx.getLangOpts().CPlusPlus && !VD->hasLocalStorage() &&
!DeclTy->isReferenceType()) {
ImplicitValueInitExpr VIE(DeclTy);
if (!EvaluateInPlace(Value, Info, LVal, &VIE,
/*AllowNonLiteralTypes=*/true))
return false; return false;
// At this point, any lifetime-extended temporaries are completely
// initialized.
Info.performLifetimeExtension();
if (!Info.discardCleanups())
llvm_unreachable("Unhandled cleanup; missing full expression marker?");
} }
if (!EvaluateInPlace(Value, Info, LVal, this,
/*AllowNonLiteralTypes=*/true) ||
EStatus.HasSideEffects)
return false;
// At this point, any lifetime-extended temporaries are completely
// initialized.
Info.performLifetimeExtension();
if (!Info.discardCleanups())
llvm_unreachable("Unhandled cleanup; missing full expression marker?");
return CheckConstantExpression(Info, DeclLoc, DeclTy, Value) && return CheckConstantExpression(Info, DeclLoc, DeclTy, Value) &&
CheckMemoryLeaks(Info); CheckMemoryLeaks(Info);
} }
@ -14415,14 +14393,8 @@ bool Expr::isPotentialConstantExpr(const FunctionDecl *FD,
// The constexpr VM attempts to compile all methods to bytecode here. // The constexpr VM attempts to compile all methods to bytecode here.
if (Info.EnableNewConstInterp) { if (Info.EnableNewConstInterp) {
auto &InterpCtx = Info.Ctx.getInterpContext(); Info.Ctx.getInterpContext().isPotentialConstantExpr(Info, FD);
switch (InterpCtx.isPotentialConstantExpr(Info, FD)) { return Diags.empty();
case interp::InterpResult::Success:
case interp::InterpResult::Fail:
return Diags.empty();
case interp::InterpResult::Bail:
break;
}
} }
const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(FD); const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(FD);

View File

@ -21,44 +21,37 @@
using namespace clang; using namespace clang;
using namespace clang::interp; using namespace clang::interp;
Context::Context(ASTContext &Ctx) Context::Context(ASTContext &Ctx) : Ctx(Ctx), P(new Program(*this)) {}
: Ctx(Ctx), ForceInterp(getLangOpts().ForceNewConstInterp),
P(new Program(*this)) {}
Context::~Context() {} Context::~Context() {}
InterpResult Context::isPotentialConstantExpr(State &Parent, bool Context::isPotentialConstantExpr(State &Parent, const FunctionDecl *FD) {
const FunctionDecl *FD) {
Function *Func = P->getFunction(FD); Function *Func = P->getFunction(FD);
if (!Func) { if (!Func) {
if (auto R = ByteCodeStmtGen<ByteCodeEmitter>(*this, *P).compileFunc(FD)) { if (auto R = ByteCodeStmtGen<ByteCodeEmitter>(*this, *P).compileFunc(FD)) {
Func = *R; Func = *R;
} else if (ForceInterp) { } else {
handleAllErrors(R.takeError(), [&Parent](ByteCodeGenError &Err) { handleAllErrors(R.takeError(), [&Parent](ByteCodeGenError &Err) {
Parent.FFDiag(Err.getLoc(), diag::err_experimental_clang_interp_failed); Parent.FFDiag(Err.getLoc(), diag::err_experimental_clang_interp_failed);
}); });
return InterpResult::Fail; return false;
} else {
consumeError(R.takeError());
return InterpResult::Bail;
} }
} }
if (!Func->isConstexpr()) if (!Func->isConstexpr())
return InterpResult::Fail; return false;
APValue Dummy; APValue Dummy;
return Run(Parent, Func, Dummy); return Run(Parent, Func, Dummy);
} }
InterpResult Context::evaluateAsRValue(State &Parent, const Expr *E, bool Context::evaluateAsRValue(State &Parent, const Expr *E, APValue &Result) {
APValue &Result) {
ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk, Result); ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk, Result);
return Check(Parent, C.interpretExpr(E)); return Check(Parent, C.interpretExpr(E));
} }
InterpResult Context::evaluateAsInitializer(State &Parent, const VarDecl *VD, bool Context::evaluateAsInitializer(State &Parent, const VarDecl *VD,
APValue &Result) { APValue &Result) {
ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk, Result); ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk, Result);
return Check(Parent, C.interpretDecl(VD)); return Check(Parent, C.interpretDecl(VD));
} }
@ -116,33 +109,20 @@ unsigned Context::getCharBit() const {
return Ctx.getTargetInfo().getCharWidth(); return Ctx.getTargetInfo().getCharWidth();
} }
InterpResult Context::Run(State &Parent, Function *Func, APValue &Result) { bool Context::Run(State &Parent, Function *Func, APValue &Result) {
InterpResult Flag; InterpState State(Parent, *P, Stk, *this);
{ State.Current = new InterpFrame(State, Func, nullptr, {}, {});
InterpState State(Parent, *P, Stk, *this); if (Interpret(State, Result))
State.Current = new InterpFrame(State, Func, nullptr, {}, {}); return true;
if (Interpret(State, Result)) { Stk.clear();
Flag = InterpResult::Success; return false;
} else {
Flag = InterpResult::Fail;
}
}
if (Flag != InterpResult::Success)
Stk.clear();
return Flag;
} }
InterpResult Context::Check(State &Parent, llvm::Expected<bool> &&R) { bool Context::Check(State &Parent, llvm::Expected<bool> &&Flag) {
if (R) { if (Flag)
return *R ? InterpResult::Success : InterpResult::Fail; return *Flag;
} else if (ForceInterp) { handleAllErrors(Flag.takeError(), [&Parent](ByteCodeGenError &Err) {
handleAllErrors(R.takeError(), [&Parent](ByteCodeGenError &Err) { Parent.FFDiag(Err.getLoc(), diag::err_experimental_clang_interp_failed);
Parent.FFDiag(Err.getLoc(), diag::err_experimental_clang_interp_failed); });
}); return false;
return InterpResult::Fail;
} else {
consumeError(R.takeError());
return InterpResult::Bail;
}
} }

View File

@ -34,16 +34,6 @@ class Program;
class State; class State;
enum PrimType : unsigned; enum PrimType : unsigned;
/// Wrapper around interpreter termination results.
enum class InterpResult {
/// Interpreter successfully computed a value.
Success,
/// Interpreter encountered an error and quit.
Fail,
/// Interpreter encountered an unimplemented feature, AST fallback.
Bail,
};
/// Holds all information required to evaluate constexpr code in a module. /// Holds all information required to evaluate constexpr code in a module.
class Context { class Context {
public: public:
@ -54,15 +44,13 @@ public:
~Context(); ~Context();
/// Checks if a function is a potential constant expression. /// Checks if a function is a potential constant expression.
InterpResult isPotentialConstantExpr(State &Parent, bool isPotentialConstantExpr(State &Parent, const FunctionDecl *FnDecl);
const FunctionDecl *FnDecl);
/// Evaluates a toplevel expression as an rvalue. /// Evaluates a toplevel expression as an rvalue.
InterpResult evaluateAsRValue(State &Parent, const Expr *E, APValue &Result); bool evaluateAsRValue(State &Parent, const Expr *E, APValue &Result);
/// Evaluates a toplevel initializer. /// Evaluates a toplevel initializer.
InterpResult evaluateAsInitializer(State &Parent, const VarDecl *VD, bool evaluateAsInitializer(State &Parent, const VarDecl *VD, APValue &Result);
APValue &Result);
/// Returns the AST context. /// Returns the AST context.
ASTContext &getASTContext() const { return Ctx; } ASTContext &getASTContext() const { return Ctx; }
@ -78,16 +66,14 @@ public:
private: private:
/// Runs a function. /// Runs a function.
InterpResult Run(State &Parent, Function *Func, APValue &Result); bool Run(State &Parent, Function *Func, APValue &Result);
/// Checks a result fromt the interpreter. /// Checks a result fromt the interpreter.
InterpResult Check(State &Parent, llvm::Expected<bool> &&R); bool Check(State &Parent, llvm::Expected<bool> &&R);
private: private:
/// Current compilation context. /// Current compilation context.
ASTContext &Ctx; ASTContext &Ctx;
/// Flag to indicate if the use of the interpreter is mandatory.
bool ForceInterp;
/// Interpreter stack, shared across invocations. /// Interpreter stack, shared across invocations.
InterpStack Stk; InterpStack Stk;
/// Constexpr program. /// Constexpr program.

View File

@ -4503,9 +4503,6 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
if (Args.hasArg(options::OPT_fexperimental_new_constant_interpreter)) if (Args.hasArg(options::OPT_fexperimental_new_constant_interpreter))
CmdArgs.push_back("-fexperimental-new-constant-interpreter"); CmdArgs.push_back("-fexperimental-new-constant-interpreter");
if (Args.hasArg(options::OPT_fforce_experimental_new_constant_interpreter))
CmdArgs.push_back("-fforce-experimental-new-constant-interpreter");
if (Arg *A = Args.getLastArg(options::OPT_fbracket_depth_EQ)) { if (Arg *A = Args.getLastArg(options::OPT_fbracket_depth_EQ)) {
CmdArgs.push_back("-fbracket-depth"); CmdArgs.push_back("-fbracket-depth");
CmdArgs.push_back(A->getValue()); CmdArgs.push_back(A->getValue());

View File

@ -2854,8 +2854,6 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK,
getLastArgIntValue(Args, OPT_fconstexpr_steps, 1048576, Diags); getLastArgIntValue(Args, OPT_fconstexpr_steps, 1048576, Diags);
Opts.EnableNewConstInterp = Opts.EnableNewConstInterp =
Args.hasArg(OPT_fexperimental_new_constant_interpreter); Args.hasArg(OPT_fexperimental_new_constant_interpreter);
Opts.ForceNewConstInterp =
Args.hasArg(OPT_fforce_experimental_new_constant_interpreter);
Opts.BracketDepth = getLastArgIntValue(Args, OPT_fbracket_depth, 256, Diags); Opts.BracketDepth = getLastArgIntValue(Args, OPT_fbracket_depth, 256, Diags);
Opts.DelayedTemplateParsing = Args.hasArg(OPT_fdelayed_template_parsing); Opts.DelayedTemplateParsing = Args.hasArg(OPT_fdelayed_template_parsing);
Opts.NumLargeByValueCopy = Opts.NumLargeByValueCopy =

View File

@ -1,4 +1,4 @@
// RUN: %clang_cc1 -std=c++17 -fsyntax-only -fforce-experimental-new-constant-interpreter %s -verify // RUN: %clang_cc1 -std=c++17 -fsyntax-only -fexperimental-new-constant-interpreter %s -verify
// RUN: %clang_cc1 -std=c++17 -fsyntax-only %s -verify // RUN: %clang_cc1 -std=c++17 -fsyntax-only %s -verify
// expected-no-diagnostics // expected-no-diagnostics