diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -139,6 +139,12 @@ } // namespace comments +namespace vm { + +class Context; + +} // namespace vm + struct TypeInfo { uint64_t Width = 0; unsigned Align = 0; @@ -564,6 +570,7 @@ const TargetInfo *Target = nullptr; const TargetInfo *AuxTarget = nullptr; clang::PrintingPolicy PrintingPolicy; + std::unique_ptr ConstexprCtx; public: IdentifierTable &Idents; @@ -573,6 +580,9 @@ IntrusiveRefCntPtr ExternalSource; ASTMutationListener *Listener = nullptr; + /// Returns the constexpr VM context. + vm::Context &getConstexprCtx(); + /// Container for either a single DynTypedNode or for an ArrayRef to /// DynTypedNode. For use with ParentMap. class DynTypedNodeList { diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -228,6 +228,10 @@ def note_constexpr_bit_cast_indet_dest : Note< "indeterminate value can only initialize an object of type 'unsigned char'" "%select{, 'char',|}1 or 'std::byte'; %0 is invalid">; +def err_constexpr_compilation_failed : Error< + "constexpr cannot be compiled by the experimental interpreter">; +def err_constexpr_interp_failed : Error< + "constexpr cannot be evaluated by the experimental interpreter">; def warn_integer_constant_overflow : Warning< "overflow in expression; result is %0 with type %1">, diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -286,6 +286,10 @@ "maximum constexpr call depth") BENIGN_LANGOPT(ConstexprStepLimit, 32, 1048576, "maximum constexpr evaluation steps") +BENIGN_LANGOPT(ConstexprInterpreter, 1, 0, + "enable the experimental constexpr interpreter") +BENIGN_LANGOPT(ConstexprForceInterpreter, 1, 0, + "force the use of the experimental constexpr interpreter") BENIGN_LANGOPT(BracketDepth, 32, 256, "maximum bracket nesting depth") BENIGN_LANGOPT(NumLargeByValueCopy, 32, 0, diff --git a/clang/include/clang/Basic/OptionalDiagnostic.h b/clang/include/clang/Basic/OptionalDiagnostic.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Basic/OptionalDiagnostic.h @@ -0,0 +1,78 @@ +//===- OptionalDiagnostic.h - An optional diagnostic ------------*- 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 +// +//===----------------------------------------------------------------------===// +// +/// \file +/// Implements a partial diagnostic which may not be emitted. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_BASIC_OPTIONALDIAGNOSTIC_H +#define LLVM_CLANG_BASIC_OPTIONALDIAGNOSTIC_H + +#include "clang/AST/APValue.h" +#include "clang/Basic/PartialDiagnostic.h" +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { + +/// A partial diagnostic which we might know in advance that we are not going +/// to emit. +class OptionalDiagnostic { + PartialDiagnostic *Diag; + +public: + explicit OptionalDiagnostic(PartialDiagnostic *Diag = nullptr) : Diag(Diag) {} + + template OptionalDiagnostic &operator<<(const T &v) { + if (Diag) + *Diag << v; + return *this; + } + + OptionalDiagnostic &operator<<(const llvm::APSInt &I) { + if (Diag) { + SmallVector Buffer; + I.toString(Buffer); + *Diag << StringRef(Buffer.data(), Buffer.size()); + } + return *this; + } + + OptionalDiagnostic &operator<<(const llvm::APFloat &F) { + if (Diag) { + // FIXME: Force the precision of the source value down so we don't + // print digits which are usually useless (we don't really care here if + // we truncate a digit by accident in edge cases). Ideally, + // APFloat::toString would automatically print the shortest + // representation which rounds to the correct value, but it's a bit + // tricky to implement. Could use std::to_chars. + unsigned precision = llvm::APFloat::semanticsPrecision(F.getSemantics()); + precision = (precision * 59 + 195) / 196; + SmallVector Buffer; + F.toString(Buffer, precision); + *Diag << StringRef(Buffer.data(), Buffer.size()); + } + return *this; + } + + OptionalDiagnostic &operator<<(const APFixedPoint &FX) { + if (Diag) { + SmallVector Buffer; + FX.toString(Buffer); + *Diag << StringRef(Buffer.data(), Buffer.size()); + } + return *this; + } +}; + +} // namespace clang + +#endif diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -833,6 +833,10 @@ def fconstant_string_class_EQ : Joined<["-"], "fconstant-string-class=">, Group; def fconstexpr_depth_EQ : Joined<["-"], "fconstexpr-depth=">, Group; def fconstexpr_steps_EQ : Joined<["-"], "fconstexpr-steps=">, Group; +def fexperimental_constexpr_interpreter : Flag<["-"], "fexperimental-constexpr-interpreter">, Group, + HelpText<"Enable the experimental constexpr interpreter">, Flags<[CC1Option]>; +def fexperimental_constexpr_force_interpreter : Flag<["-"], "fexperimental-constexpr-force-interpreter">, Group, + HelpText<"Force the use of the experimental constexpr interpreter, failing on missing features">, Flags<[CC1Option]>; def fconstexpr_backtrace_limit_EQ : Joined<["-"], "fconstexpr-backtrace-limit=">, Group; def fno_crash_diagnostics : Flag<["-"], "fno-crash-diagnostics">, Group, Flags<[NoArgumentUnused, CoreOption]>, diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -12,6 +12,7 @@ #include "clang/AST/ASTContext.h" #include "CXXABI.h" +#include "ExprVM/Context.h" #include "clang/AST/APValue.h" #include "clang/AST/ASTMutationListener.h" #include "clang/AST/ASTTypeTraits.h" @@ -737,6 +738,13 @@ llvm_unreachable("Invalid CXXABI type!"); } +vm::Context &ASTContext::getConstexprCtx() { + if (!ConstexprCtx) { + ConstexprCtx.reset(new vm::Context(*this)); + } + return *ConstexprCtx.get(); +} + static const LangASMap *getAddressSpaceMap(const TargetInfo &T, const LangOptions &LOpts) { if (LOpts.FakeAddressSpaceMap) { diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -4,6 +4,8 @@ Support ) +add_subdirectory(ExprVM) + add_clang_library(clangAST APValue.cpp ASTConsumer.cpp @@ -80,6 +82,7 @@ VTTBuilder.cpp LINK_LIBS + clangExpr clangBasic clangLex ) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -32,13 +32,17 @@ // //===----------------------------------------------------------------------===// +#include "ExprVM/Context.h" +#include "ExprVM/Compiler.h" +#include "ExprVM/Frame.h" +#include "ExprVM/State.h" #include "clang/AST/APValue.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" #include "clang/AST/ASTLambda.h" +#include "clang/AST/CXXInheritance.h" #include "clang/AST/CharUnits.h" #include "clang/AST/CurrentSourceLocExprScope.h" -#include "clang/AST/CXXInheritance.h" #include "clang/AST/Expr.h" #include "clang/AST/OSLog.h" #include "clang/AST/RecordLayout.h" @@ -49,6 +53,8 @@ #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallBitVector.h" +#include "clang/Basic/OptionalDiagnostic.h" +#include "clang/Basic/TargetInfo.h" #include "llvm/Support/SaveAndRestore.h" #include "llvm/Support/raw_ostream.h" #include @@ -66,8 +72,8 @@ namespace { struct LValue; - struct CallStackFrame; - struct EvalInfo; + class CallStackFrame; + class EvalInfo; using SourceLocExprScopeGuard = CurrentSourceLocExprScope::SourceLocExprScopeGuard; @@ -480,7 +486,8 @@ }; /// A stack frame in the constexpr call stack. - struct CallStackFrame { + class CallStackFrame : public vm::Frame { + public: EvalInfo &Info; /// Parent - The caller of this stack frame. @@ -574,6 +581,12 @@ } APValue &createTemporary(const void *Key, bool IsLifetimeExtended); + + void describe(llvm::raw_ostream &OS) override; + + Frame *getCaller() const override { return Caller; } + SourceLocation getLoc() const override { return CallLoc; } + const FunctionDecl *getCallee() const override { return Callee; } }; /// Temporarily override 'this'. @@ -592,59 +605,6 @@ const LValue *OldThis; }; - /// A partial diagnostic which we might know in advance that we are not going - /// to emit. - class OptionalDiagnostic { - PartialDiagnostic *Diag; - - public: - explicit OptionalDiagnostic(PartialDiagnostic *Diag = nullptr) - : Diag(Diag) {} - - template - OptionalDiagnostic &operator<<(const T &v) { - if (Diag) - *Diag << v; - return *this; - } - - OptionalDiagnostic &operator<<(const APSInt &I) { - if (Diag) { - SmallVector Buffer; - I.toString(Buffer); - *Diag << StringRef(Buffer.data(), Buffer.size()); - } - return *this; - } - - OptionalDiagnostic &operator<<(const APFloat &F) { - if (Diag) { - // FIXME: Force the precision of the source value down so we don't - // print digits which are usually useless (we don't really care here if - // we truncate a digit by accident in edge cases). Ideally, - // APFloat::toString would automatically print the shortest - // representation which rounds to the correct value, but it's a bit - // tricky to implement. - unsigned precision = - llvm::APFloat::semanticsPrecision(F.getSemantics()); - precision = (precision * 59 + 195) / 196; - SmallVector Buffer; - F.toString(Buffer, precision); - *Diag << StringRef(Buffer.data(), Buffer.size()); - } - return *this; - } - - OptionalDiagnostic &operator<<(const APFixedPoint &FX) { - if (Diag) { - SmallVector Buffer; - FX.toString(Buffer); - *Diag << StringRef(Buffer.data(), Buffer.size()); - } - return *this; - } - }; - /// A cleanup, and a flag indicating whether it is lifetime-extended. class Cleanup { llvm::PointerIntPair Value; @@ -707,7 +667,8 @@ /// rules. For example, the RHS of (0 && foo()) is not evaluated. We can /// evaluate the expression regardless of what the RHS is, but C only allows /// certain things in certain situations. - struct EvalInfo { + class EvalInfo : public vm::State { + public: ASTContext &Ctx; /// EvalStatus - Contains information about the evaluation. @@ -727,6 +688,13 @@ /// we will evaluate. unsigned StepsLeft; + /// Force the use of the constexpr interpreter, bailing out with an error + /// if a feature is unsupported. + bool ForceInterp; + + /// Enable the constexpr interpreter. + bool EnableInterp; + /// BottomFrame - The frame in which evaluation started. This must be /// initialized after CurrentCall and CallStackDepth. CallStackFrame BottomFrame; @@ -837,7 +805,7 @@ /// Are we checking whether the expression is a potential constant /// expression? - bool checkingPotentialConstantExpression() const { + bool checkingPotentialConstantExpression() const override { return EvalMode == EM_PotentialConstantExpression || EvalMode == EM_PotentialConstantExpressionUnevaluated; } @@ -845,12 +813,16 @@ /// Are we checking an expression for overflow? // FIXME: We should check for any kind of undefined or suspicious behavior // in such constructs, not just overflow. - bool checkingForOverflow() { return EvalMode == EM_EvaluateForOverflow; } + bool checkingForOverflow() const override { + return EvalMode == EM_EvaluateForOverflow; + } EvalInfo(const ASTContext &C, Expr::EvalStatus &S, EvaluationMode Mode) : Ctx(const_cast(C)), EvalStatus(S), CurrentCall(nullptr), CallStackDepth(0), NextCallIndex(1), StepsLeft(getLangOpts().ConstexprStepLimit), + ForceInterp(getLangOpts().ConstexprForceInterpreter), + EnableInterp(ForceInterp || getLangOpts().ConstexprInterpreter), BottomFrame(*this, SourceLocation(), nullptr, nullptr, nullptr), EvaluatingDecl((const ValueDecl *)nullptr), EvaluatingDeclValue(nullptr), HasActiveDiagnostic(false), @@ -862,8 +834,6 @@ EvaluatingDeclValue = &Value; } - const LangOptions &getLangOpts() const { return Ctx.getLangOpts(); } - bool CheckCallLimit(SourceLocation Loc) { // Don't perform any constexpr calls (other than the call we're checking) // when checking a potential constant expression. @@ -907,118 +877,52 @@ } private: - /// Add a diagnostic to the diagnostics list. - PartialDiagnostic &addDiag(SourceLocation Loc, diag::kind DiagId) { - PartialDiagnostic PD(DiagId, Ctx.getDiagAllocator()); - EvalStatus.Diag->push_back(std::make_pair(Loc, PD)); - return EvalStatus.Diag->back().second; - } - - /// Add notes containing a call stack to the current point of evaluation. - void addCallStack(unsigned Limit); - - private: - OptionalDiagnostic Diag(SourceLocation Loc, diag::kind DiagId, - unsigned ExtraNotes, bool IsCCEDiag) { - - if (EvalStatus.Diag) { - // If we have a prior diagnostic, it will be noting that the expression - // isn't a constant expression. This diagnostic is more important, - // unless we require this evaluation to produce a constant expression. - // - // FIXME: We might want to show both diagnostics to the user in - // EM_ConstantFold mode. - if (!EvalStatus.Diag->empty()) { - switch (EvalMode) { - case EM_ConstantFold: - case EM_IgnoreSideEffects: - case EM_EvaluateForOverflow: - if (!HasFoldFailureDiagnostic) - break; - // We've already failed to fold something. Keep that diagnostic. - LLVM_FALLTHROUGH; - case EM_ConstantExpression: - case EM_PotentialConstantExpression: - case EM_ConstantExpressionUnevaluated: - case EM_PotentialConstantExpressionUnevaluated: - HasActiveDiagnostic = false; - return OptionalDiagnostic(); - } + vm::Frame *getCurrentFrame() override { return CurrentCall; } + const vm::Frame *getBottomFrame() const override { return &BottomFrame; } + + bool hasActiveDiagnostic() override { return HasActiveDiagnostic; } + void setActiveDiagnostic(bool Flag) override { HasActiveDiagnostic = Flag; } + + void setFoldFailureDiagnostic(bool Flag) override { + HasFoldFailureDiagnostic = Flag; + } + + Expr::EvalStatus &getEvalStatus() const override { return EvalStatus; } + + ASTContext &getCtx() const override { return Ctx; } + + // If we have a prior diagnostic, it will be noting that the expression + // isn't a constant expression. This diagnostic is more important, + // unless we require this evaluation to produce a constant expression. + // + // FIXME: We might want to show both diagnostics to the user in + // EM_ConstantFold mode. + bool hasPriorDiagnostic() override { + if (!EvalStatus.Diag->empty()) { + switch (EvalMode) { + case EM_ConstantFold: + case EM_IgnoreSideEffects: + case EM_EvaluateForOverflow: + if (!HasFoldFailureDiagnostic) + break; + // We've already failed to fold something. Keep that diagnostic. + LLVM_FALLTHROUGH; + case EM_ConstantExpression: + case EM_PotentialConstantExpression: + case EM_ConstantExpressionUnevaluated: + case EM_PotentialConstantExpressionUnevaluated: + setActiveDiagnostic(false); + return true; } - - unsigned CallStackNotes = CallStackDepth - 1; - unsigned Limit = Ctx.getDiagnostics().getConstexprBacktraceLimit(); - if (Limit) - CallStackNotes = std::min(CallStackNotes, Limit + 1); - if (checkingPotentialConstantExpression()) - CallStackNotes = 0; - - HasActiveDiagnostic = true; - HasFoldFailureDiagnostic = !IsCCEDiag; - EvalStatus.Diag->clear(); - EvalStatus.Diag->reserve(1 + ExtraNotes + CallStackNotes); - addDiag(Loc, DiagId); - if (!checkingPotentialConstantExpression()) - addCallStack(Limit); - return OptionalDiagnostic(&(*EvalStatus.Diag)[0].second); - } - HasActiveDiagnostic = false; - return OptionalDiagnostic(); - } - public: - // Diagnose that the evaluation could not be folded (FF => FoldFailure) - OptionalDiagnostic - FFDiag(SourceLocation Loc, - diag::kind DiagId = diag::note_invalid_subexpr_in_const_expr, - unsigned ExtraNotes = 0) { - return Diag(Loc, DiagId, ExtraNotes, false); - } - - OptionalDiagnostic FFDiag(const Expr *E, diag::kind DiagId - = diag::note_invalid_subexpr_in_const_expr, - unsigned ExtraNotes = 0) { - if (EvalStatus.Diag) - return Diag(E->getExprLoc(), DiagId, ExtraNotes, /*IsCCEDiag*/false); - HasActiveDiagnostic = false; - return OptionalDiagnostic(); - } - - /// Diagnose that the evaluation does not produce a C++11 core constant - /// expression. - /// - /// FIXME: Stop evaluating if we're in EM_ConstantExpression or - /// EM_PotentialConstantExpression mode and we produce one of these. - OptionalDiagnostic CCEDiag(SourceLocation Loc, diag::kind DiagId - = diag::note_invalid_subexpr_in_const_expr, - unsigned ExtraNotes = 0) { - // Don't override a previous diagnostic. Don't bother collecting - // diagnostics if we're evaluating for overflow. - if (!EvalStatus.Diag || !EvalStatus.Diag->empty()) { - HasActiveDiagnostic = false; - return OptionalDiagnostic(); } - return Diag(Loc, DiagId, ExtraNotes, true); - } - OptionalDiagnostic CCEDiag(const Expr *E, diag::kind DiagId - = diag::note_invalid_subexpr_in_const_expr, - unsigned ExtraNotes = 0) { - return CCEDiag(E->getExprLoc(), DiagId, ExtraNotes); - } - /// Add a note to a prior diagnostic. - OptionalDiagnostic Note(SourceLocation Loc, diag::kind DiagId) { - if (!HasActiveDiagnostic) - return OptionalDiagnostic(); - return OptionalDiagnostic(&addDiag(Loc, DiagId)); + return false; } - /// Add a stack of notes to a prior diagnostic. - void addNotes(ArrayRef Diags) { - if (HasActiveDiagnostic) { - EvalStatus.Diag->insert(EvalStatus.Diag->end(), - Diags.begin(), Diags.end()); - } + unsigned getCallStackDepth() override { + return CallStackDepth; } + public: /// Should we continue evaluation after encountering a side-effect that we /// couldn't model? bool keepEvaluatingAfterSideEffect() { @@ -1064,7 +968,7 @@ /// Note that we hit something that was technically undefined behavior, but /// that we can evaluate past it (such as signed overflow or floating-point /// division by zero.) - bool noteUndefinedBehavior() { + bool noteUndefinedBehavior() override { EvalStatus.HasUndefinedBehavior = true; return keepEvaluatingAfterUndefinedBehavior(); } @@ -1321,62 +1225,6 @@ return Result; } -static void describeCall(CallStackFrame *Frame, raw_ostream &Out); - -void EvalInfo::addCallStack(unsigned Limit) { - // Determine which calls to skip, if any. - unsigned ActiveCalls = CallStackDepth - 1; - unsigned SkipStart = ActiveCalls, SkipEnd = SkipStart; - if (Limit && Limit < ActiveCalls) { - SkipStart = Limit / 2 + Limit % 2; - SkipEnd = ActiveCalls - Limit / 2; - } - - // Walk the call stack and add the diagnostics. - unsigned CallIdx = 0; - for (CallStackFrame *Frame = CurrentCall; Frame != &BottomFrame; - Frame = Frame->Caller, ++CallIdx) { - // Skip this call? - if (CallIdx >= SkipStart && CallIdx < SkipEnd) { - if (CallIdx == SkipStart) { - // Note that we're skipping calls. - addDiag(Frame->CallLoc, diag::note_constexpr_calls_suppressed) - << unsigned(ActiveCalls - Limit); - } - continue; - } - - // Use a different note for an inheriting constructor, because from the - // user's perspective it's not really a function at all. - if (auto *CD = dyn_cast_or_null(Frame->Callee)) { - if (CD->isInheritingConstructor()) { - addDiag(Frame->CallLoc, diag::note_constexpr_inherited_ctor_call_here) - << CD->getParent(); - continue; - } - } - - SmallVector Buffer; - llvm::raw_svector_ostream Out(Buffer); - describeCall(Frame, Out); - addDiag(Frame->CallLoc, diag::note_constexpr_call_here) << Out.str(); - } -} - -/// Kinds of access we can perform on an object, for diagnostics. Note that -/// we consider a member function call to be a kind of access, even though -/// it is not formally an access of the object, because it has (largely) the -/// same set of semantic restrictions. -enum AccessKinds { - AK_Read, - AK_Assign, - AK_Increment, - AK_Decrement, - AK_MemberCall, - AK_DynamicCast, - AK_TypeId, -}; - static bool isModification(AccessKinds AK) { switch (AK) { case AK_Read: @@ -1744,36 +1592,36 @@ } /// Produce a string describing the given constexpr call. -static void describeCall(CallStackFrame *Frame, raw_ostream &Out) { +void CallStackFrame::describe(raw_ostream &Out) { unsigned ArgIndex = 0; - bool IsMemberCall = isa(Frame->Callee) && - !isa(Frame->Callee) && - cast(Frame->Callee)->isInstance(); + bool IsMemberCall = isa(Callee) && + !isa(Callee) && + cast(Callee)->isInstance(); if (!IsMemberCall) - Out << *Frame->Callee << '('; + Out << *Callee << '('; - if (Frame->This && IsMemberCall) { + if (This && IsMemberCall) { APValue Val; - Frame->This->moveInto(Val); - Val.printPretty(Out, Frame->Info.Ctx, - Frame->This->Designator.MostDerivedType); + This->moveInto(Val); + Val.printPretty(Out, Info.Ctx, + This->Designator.MostDerivedType); // FIXME: Add parens around Val if needed. - Out << "->" << *Frame->Callee << '('; + Out << "->" << *Callee << '('; IsMemberCall = false; } - for (FunctionDecl::param_const_iterator I = Frame->Callee->param_begin(), - E = Frame->Callee->param_end(); I != E; ++I, ++ArgIndex) { + for (FunctionDecl::param_const_iterator I = Callee->param_begin(), + E = Callee->param_end(); I != E; ++I, ++ArgIndex) { if (ArgIndex > (unsigned)IsMemberCall) Out << ", "; const ParmVarDecl *Param = *I; - const APValue &Arg = Frame->Arguments[ArgIndex]; - Arg.printPretty(Out, Frame->Info.Ctx, Param->getType()); + const APValue &Arg = Arguments[ArgIndex]; + Arg.printPretty(Out, Info.Ctx, Param->getType()); if (ArgIndex == 0 && IsMemberCall) - Out << "->" << *Frame->Callee << '('; + Out << "->" << *Callee << '('; } Out << ')'; @@ -5185,6 +5033,42 @@ Frame.LambdaThisCaptureField); } + // Try to evaluate the function call in the bytecode VM. + if (Info.EnableInterp) { + auto &Ctx = Info.Ctx.getConstexprCtx(); + if (auto Func = Ctx.compileFunction(Callee)) { + switch (Ctx.executeFunction(Info, *Func, ArgValues, Result)) { + case vm::InterpResult::SUCCESS: { + // Interpreter computed a value and converted it to the format + // used by the AST walker - continue interpretation here. + return true; + } + case vm::InterpResult::FAIL: { + // Diagnostics already emitted through the VM. + return false; + } + case vm::InterpResult::BAIL: { + // Interpreter bailed out due to unsupported features. + // Evaluation continues with the AST walked, unless force is set. + if (Info.ForceInterp) { + Info.FFDiag(Callee->getBeginLoc(), diag::err_constexpr_interp_failed); + return false; + } + break; + } + } + } else { + // Code cannot be compiled to bytecode - unless force flag is set, + // evaluation fall back to the AST walker. + if (Info.ForceInterp) { + handleAllErrors(Func.takeError(), [&Info](vm::CompilerError &Err) { + Info.FFDiag(Err.getLoc(), diag::err_constexpr_compilation_failed); + }); + return false; + } + } + } + StmtResult Ret = {Result, ResultSlot}; EvalStmtResult ESR = EvaluateStmt(Ret, Info, Body); if (ESR == ESR_Succeeded) { diff --git a/clang/lib/AST/ExprVM/CMakeLists.txt b/clang/lib/AST/ExprVM/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS + ) + +add_clang_library(clangExpr + Compiler.cpp + Context.cpp + Disasm.cpp + Frame.cpp + Function.cpp + Interp.cpp + InterpFrame.cpp + InterpStack.cpp + Program.cpp + Pointer.cpp + State.cpp + Type.cpp + ) diff --git a/clang/lib/AST/ExprVM/Compiler.h b/clang/lib/AST/ExprVM/Compiler.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Compiler.h @@ -0,0 +1,178 @@ +//===--- Compiler.h - Code Generator 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the constexpr bytecode compiler. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_EXPRVM_COMPILER_H +#define LLVM_CLANG_AST_EXPRVM_COMPILER_H + +#include "Pointer.h" +#include "Type.h" +#include "clang/AST/Decl.h" +#include "clang/AST/StmtVisitor.h" +#include "llvm/ADT/Optional.h" + +namespace clang { +class QualType; + +namespace vm { +class Compiler; +class Context; +class Function; +class LocalScope; +class Program; +class SourceInfo; +enum Opcode : uint32_t; + +/// Error thrown by the compiler. +struct CompilerError : public llvm::ErrorInfo { +public: + CompilerError(SourceLocation Loc) : Loc(Loc) {} + + void log(raw_ostream &OS) const override { OS << "unimplemented feature"; } + + const SourceLocation &getLoc() const { return Loc; } + + static char ID; + +private: + // Start of the item where the error occurred. + SourceLocation Loc; + + // Users are not expected to use error_code. + std::error_code convertToErrorCode() const override { + return llvm::inconvertibleErrorCode(); + } +}; + +/// Compilation context for a constexpr function. +class Compiler : public ConstStmtVisitor { +private: + using LabelTy = int; + using EmitFn = void (Compiler::*)(PrimType, const SourceInfo &); + + /// Current compilation context. + Context &Ctx; + /// Program to link to. + Program &P; + /// Function declaration being compiled. + const FunctionDecl *F; + +public: + /// Creates a compiler for a funciton. + Compiler(Context &Ctx, Program &P, const FunctionDecl *F); + + /// Compiles the function into the module. + llvm::Expected compile(); + +public: + // Statement visitors. + llvm::Error compile(const Stmt *S); + llvm::Error compileCompoundStmt(const CompoundStmt *S); + llvm::Error compileDeclStmt(const DeclStmt *DS); + llvm::Error compileForStmt(const ForStmt *FS); + llvm::Error compileWhileStmt(const WhileStmt *DS); + llvm::Error compileReturnStmt(const ReturnStmt *RS); + llvm::Error compileIfStmt(const IfStmt *IS); + + // Expression visitors - result returned on stack. + llvm::Error VisitCastExpr(const CastExpr *CE); + llvm::Error VisitIntegerLiteral(const IntegerLiteral *E); + llvm::Error VisitParenExpr(const ParenExpr *PE); + llvm::Error VisitBinaryOperator(const BinaryOperator *BO); + + // If an unsupported construct is encountered, no code is generated. + llvm::Error VisitExpr(const Expr *E) { return bail(E); } + llvm::Error VisitStmt(const Stmt *S) { llvm_unreachable("not compiled"); } + +private: + /// Evaluates an expr for side effects and discards the result. + llvm::Error discard(const Expr *E); + llvm::Error discardBinaryOperator(const BinaryOperator *BO); + + /// Bails out if a given node cannot be compiled. + llvm::Error bail(const Stmt *S) { + return llvm::make_error(S->getBeginLoc()); + } + llvm::Error bail(const Decl *D) { + return llvm::make_error(D->getBeginLoc()); + } + +private: + friend class LocalScope; + + /// Parameter indices. + llvm::DenseMap Params; + + /// Parameter descriptors. + llvm::DenseMap> ParamDescriptors; + /// Perform an action if a parameter is found. + llvm::Error withParam(const ParmVarDecl *P, + std::function &&f); + + /// Information about a local variable's storage. + struct Local { + /// Offset of the local in frame. + unsigned Location; + /// Size of the local, in bytes. + unsigned Size; + }; + /// Variable to storage mapping. + llvm::DenseMap Locals; + /// Local descriptors. + llvm::SmallVector, 2> Descriptors; + /// Next offset for a local. + unsigned NextLocalOffset = 0; + /// Creates a local. + const Local &createLocal(const VarDecl *V, unsigned Size, unsigned ElemSize, + PrimType ElemTy); + /// Current scope. + LocalScope *Top = nullptr; + + /// Perform an action if a local var is found. + llvm::Error withLocal(const VarDecl *V, + std::function &&f); + + /// Index of the next available label. + LabelTy NextLabel = 0; + + /// Create a label. + LabelTy getLabel() { return ++NextLabel; } + + /// Define a label. + void emitLabel(LabelTy Label); + + /// Helpers to emit instructions. + void emitJT(LabelTy Label); + void emitJF(LabelTy Label); + void emitJMP(LabelTy Label); + +#define GET_EMITTER_PROTO +#include "Opcodes.inc" +#undef GET_EMITTER_PROTO + + /// Emits an opcode. + template + void emitOp(Opcode Op, const typename PrimConv::T &... Args, + const SourceInfo &L); + + /// Label information for linker. + llvm::DenseMap LabelOffsets; + /// Location of label relocations. + llvm::DenseMap> LabelRelocs; + + /// Emits a label or a lazy relocation for a forward pointer. + void emitReloc(LabelTy Label); +}; + +} // namespace vm +} // namespace clang + +#endif diff --git a/clang/lib/AST/ExprVM/Compiler.cpp b/clang/lib/AST/ExprVM/Compiler.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Compiler.cpp @@ -0,0 +1,653 @@ +//===--- Compiler.cpp - Code Generator 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 "Compiler.h" +#include "Context.h" +#include "Function.h" +#include "Opcode.h" +#include "Program.h" +#include "Type.h" + +using namespace clang; +using namespace clang::vm; + +using Error = llvm::Error; + +char CompilerError::ID; + +namespace clang { +namespace vm { + +class LocalScope { +public: + LocalScope(Compiler *Ctx) : Ctx(Ctx), Parent(Ctx->Top) { Ctx->Top = this; } + + virtual ~LocalScope() { + emitDestructor(); + Ctx->Top = Parent; + } + + virtual void addDescriptor(Descriptor &&Desc) { + if (!Idx.hasValue()) { + Idx = Ctx->Descriptors.size(); + Ctx->Descriptors.emplace_back(); + } + + Ctx->Descriptors[*Idx].emplace_back(std::move(Desc)); + } + + LocalScope *getParent() { return Parent; } + + void emitDestructor() { + // TODO: emit an opcode to destroy the frame. + } + +private: + /// Compiler instance. + Compiler *Ctx; + /// Link to the parent scope. + LocalScope *Parent; + /// Index of the scope in the chain. + llvm::Optional Idx; +}; + +class BlockScope final : public LocalScope { +public: + BlockScope(Compiler *Ctx) : LocalScope(Ctx) {} +}; + +class ExprScope final : public LocalScope { +public: + ExprScope(Compiler *Ctx) : LocalScope(Ctx) {} +}; + +} // namespace vm +} // namespace clang + +Compiler::Compiler(Context &Ctx, Program &P, const FunctionDecl *F) + : Ctx(Ctx), P(P), F(F) {} + +Expected Compiler::compile() { + // Set up argument indices. + unsigned ParamOffset = 0; + SmallVector Args; + for (auto *ParamDecl : F->parameters()) { + if (auto T = Ctx.classify(ParamDecl->getType())) { + auto Size = primSize(*T); + auto Desc = llvm::make_unique(ParamDecl, Size, Size, *T, + /* isMutable */ true, + /* isArray */ false, + /* isGlobal */ false); + ParamDescriptors.insert({ParamOffset, std::move(Desc)}); + Params.insert({ParamDecl, ParamOffset}); + ParamOffset += align(Size); + Args.push_back(*T); + } else { + return bail(F->getBody()); + } + } + + // Compile the function body. + unsigned Start, End; + { + Start = P.getOffset(); + if (auto E = compile(F->getBody())) + return std::move(E); + + // Emit a guard return to protect against a code path missing one. + if (F->getReturnType()->isVoidType()) + emitOp(EO_RET_VOID, {}); + else + emitOp(EO_NORET_VOID, {}); + + End = P.getOffset(); + } + + // Create scopes from descriptors. + llvm::SmallVector Scopes; + for (auto &DS : Descriptors) { + Scopes.emplace_back(std::move(DS)); + } + + // Create a handle over the emitted code. + return P.createFunction(F, Start, End, NextLocalOffset, ParamOffset, + std::move(Scopes), std::move(Args), + std::move(ParamDescriptors)); +} + +Error Compiler::compile(const Stmt *S) { + switch (S->getStmtClass()) { + case Stmt::CompoundStmtClass: + return compileCompoundStmt(static_cast(S)); + case Stmt::DeclStmtClass: + return compileDeclStmt(static_cast(S)); + case Stmt::ForStmtClass: + return compileForStmt(static_cast(S)); + case Stmt::WhileStmtClass: + return compileWhileStmt(static_cast(S)); + case Stmt::ReturnStmtClass: + return compileReturnStmt(static_cast(S)); + case Stmt::IfStmtClass: + return compileIfStmt(static_cast(S)); + case Stmt::NullStmtClass: + return Error::success(); + default: { + if (auto *E = dyn_cast(S)) { + if (E->getType()->isVoidType()) { + return Visit(E); + } else { + return discard(static_cast(S)); + } + } else { + return bail(S); + } + } + } +} + +Error Compiler::compileCompoundStmt(const CompoundStmt *S) { + // Visitor for a block of code - generates bytecode. + BlockScope Scope(this); + for (auto *Stmt : S->body()) + if (auto E = compile(Stmt)) + return E; + return Error::success(); +} + +Error Compiler::compileDeclStmt(const DeclStmt *DS) { + for (auto *D : DS->decls()) { + // Variable declarator. + if (auto *VD = dyn_cast(D)) { + auto DT = VD->getType(); + if (!VD->hasLocalStorage()) { + // TODO: implement non-local variables. + return bail(DS); + } + + // Integers, pointers, primitives. + if (DT->isFundamentalType() || DT->hasPointerRepresentation()) { + if (auto T = Ctx.classify(VD->getType())) { + const auto Size = primSize(*T); + const auto &L = createLocal(VD, Size, Size, *T); + // Compile the initialiser in its own scope. + { + ExprScope Scope(this); + if (auto E = Visit(VD->getInit())) + return E; + } + emitSET_LOCAL(*T, L.Location, VD); + continue; + } + } + + return bail(DS); + } + + // TODO: implement other declarations. + return bail(DS); + } + + return Error::success(); +} + +Error Compiler::compileForStmt(const ForStmt *FS) { + // Compile the initialisation statement in an outer scope. + BlockScope OuterScope(this); + if (auto *Init = FS->getInit()) + if (auto E = compile(Init)) + return E; + + auto LabelStart = getLabel(); + auto LabelEnd = getLabel(); + + // Compile the condition, body and increment in the loop scope. + emitLabel(LabelStart); + { + BlockScope LoopScope(this); + + if (auto *Cond = FS->getCond()) { + if (auto *CondDecl = FS->getConditionVariableDeclStmt()) + if (auto E = compileDeclStmt(CondDecl)) + return E; + + if (auto E = Visit(Cond)) + return E; + + emitJF(LabelEnd); + } + + if (auto *Body = FS->getBody()) + if (auto E = compile(Body)) + return E; + + if (auto *Inc = FS->getInc()) { + ExprScope IncScope(this); + if (auto E = discard(Inc)) + return E; + } + } + emitJMP(LabelStart); + emitLabel(LabelEnd); + return Error::success(); +} + +Error Compiler::compileWhileStmt(const WhileStmt *WS) { + auto LabelStart = getLabel(); + auto LabelEnd = getLabel(); + emitLabel(LabelStart); + { + BlockScope LoopScope(this); + if (auto *CondDecl = WS->getConditionVariableDeclStmt()) + if (auto E = compileDeclStmt(CondDecl)) + return E; + + if (auto E = Visit(WS->getCond())) + return E; + emitJF(LabelEnd); + + if (auto E = compile(WS->getBody())) + return E; + } + emitJMP(LabelStart); + emitLabel(LabelEnd); + return Error::success(); +} + +Error Compiler::compileReturnStmt(const ReturnStmt *RS) { + if (auto *RetExpr = RS->getRetValue()) { + if (auto T = Ctx.classify(RetExpr->getType())) { + ExprScope RetScope(this); + if (auto E = Visit(RetExpr)) + return E; + for (auto *C = Top; C; C = C->getParent()) + C->emitDestructor(); + emitRET(*T, RS); + return Error::success(); + } + return bail(RS); + } else { + for (auto *C = Top; C; C = C->getParent()) + C->emitDestructor(); + emitOp(EO_RET_VOID, RS); + return Error::success(); + } +} + +Error Compiler::compileIfStmt(const IfStmt *IS) { + BlockScope IfScope(this); + if (auto *CondInit = IS->getInit()) + if (auto E = compile(IS->getInit())) + return E; + + if (auto *CondDecl = IS->getConditionVariableDeclStmt()) + if (auto E = compileDeclStmt(CondDecl)) + return E; + + if (auto E = Visit(IS->getCond())) + return E; + + if (auto *Else = IS->getElse()) { + auto LabelElse = getLabel(); + auto LabelEnd = getLabel(); + emitJF(LabelElse); + if (auto E = compile(IS->getThen())) + return E; + emitJMP(LabelEnd); + emitLabel(LabelElse); + if (auto E = compile(Else)) { + return E; + } + emitLabel(LabelEnd); + } else { + auto LabelEnd = getLabel(); + emitJF(LabelEnd); + if (auto E = compile(IS->getThen())) + return E; + emitLabel(LabelEnd); + } + + return Error::success(); +} + +Error Compiler::VisitCastExpr(const CastExpr *CE) { + auto *SubExpr = CE->getSubExpr(); + switch (CE->getCastKind()) { + case CK_LValueToRValue: { + if (auto T = Ctx.classify(CE->getType())) { + if (auto *DE = dyn_cast(SubExpr)) { + bool IsReference = DE->getDecl()->getType()->isReferenceType(); + if (!IsReference) { + if (auto *PD = dyn_cast(DE->getDecl())) { + return withParam(PD, [this, T, DE](unsigned Idx) -> Error { + emitGET_PARAM(*T, Idx, DE); + return Error::success(); + }); + } + + if (auto *VD = dyn_cast(DE->getDecl())) { + return withLocal(VD, [this, T, DE](const Local &L) -> Error { + emitGET_LOCAL(*T, L.Location, DE); + return Error::success(); + }); + } + } + } + + return bail(CE); + } else { + // TODO: implement a fallback. + return bail(CE); + } + break; + } + case CK_IntegralToBoolean: { + if (auto T = Ctx.classify(SubExpr->getType())) { + if (auto E = Visit(SubExpr)) { + return E; + } + + emitTEST(*T, CE); + return Error::success(); + } else { + return bail(CE); + } + break; + } + case CK_NoOp: { + return Visit(SubExpr); + } + default: { + // TODO: implement other casts. + return bail(CE); + } + } +} + +Error Compiler::VisitIntegerLiteral(const IntegerLiteral *E) { + if (auto T = Ctx.classify(E->getType())) { + switch (*T) { + case ET_SINT_8: { + emitOp(EO_CONST_SINT_8, E->getValue().getSExtValue(), E); + return Error::success(); + } + case ET_UINT_8: { + emitOp(EO_CONST_UINT_8, E->getValue().getZExtValue(), E); + return Error::success(); + } + case ET_SINT_16: { + emitOp(EO_CONST_SINT_16, E->getValue().getSExtValue(), E); + return Error::success(); + } + case ET_UINT_16: { + emitOp(EO_CONST_UINT_16, E->getValue().getZExtValue(), E); + return Error::success(); + } + case ET_SINT_32: { + emitOp(EO_CONST_SINT_32, E->getValue().getSExtValue(), E); + return Error::success(); + } + case ET_UINT_32: { + emitOp(EO_CONST_UINT_32, E->getValue().getZExtValue(), E); + return Error::success(); + } + case ET_SINT_64: { + emitOp(EO_CONST_SINT_64, E->getValue().getSExtValue(), E); + return Error::success(); + } + case ET_UINT_64: { + emitOp(EO_CONST_UINT_64, E->getValue().getZExtValue(), E); + return Error::success(); + } + case ET_BOOL: + return bail(E); + } + } else { + // TODO: implement other literal types. + return bail(E); + } +} + +Error Compiler::VisitParenExpr(const ParenExpr *PE) { + return Visit(PE->getSubExpr()); +} + +Error Compiler::VisitBinaryOperator(const BinaryOperator *BO) { + // Typecheck the args. + auto *LHS = BO->getLHS(); + auto TypeLHS = Ctx.classify(LHS->getType()); + if (!TypeLHS) { + return bail(BO); + } + auto *RHS = BO->getRHS(); + auto TypeRHS = Ctx.classify(RHS->getType()); + if (!TypeRHS) { + return bail(BO); + } + + if (auto T = Ctx.classify(BO->getType())) { + // If the LHS is a local variable, set it directly. + if (BO->getOpcode() == BO_Assign) { + if (auto *DE = dyn_cast(BO->getLHS())) { + bool IsReference = DE->getDecl()->getType()->isReferenceType(); + if (*TypeLHS == *T && *TypeRHS == *T && !IsReference) { + if (auto *PD = dyn_cast(DE->getDecl())) { + return withParam(PD, [this, RHS, BO](unsigned Idx) -> Error { + if (auto E = Visit(RHS)) + return E; + return bail(BO); + }); + } + + if (auto *VD = dyn_cast(DE->getDecl())) { + return withLocal(VD, [this, T, RHS, DE](const Local &L) -> Error { + if (auto E = Visit(RHS)) + return E; + emitSET_LOCAL(*T, L.Location, DE); + // TODO: return pointer + return Error::success(); + }); + } + } + } + } + + if (*TypeLHS != *TypeRHS) + return bail(BO); + if (auto E = Visit(LHS)) + return E; + if (auto E = Visit(RHS)) + return E; + + switch (BO->getOpcode()) { + case BO_EQ: emitEQ(*TypeLHS, BO); break; + case BO_NE: emitNE(*TypeLHS, BO); break; + case BO_LT: emitLT(*TypeLHS, BO); break; + case BO_LE: emitLE(*TypeLHS, BO); break; + case BO_GT: emitGT(*TypeLHS, BO); break; + case BO_GE: emitGE(*TypeLHS, BO); break; + case BO_Rem: emitREM(*T, BO); break; + case BO_Div: emitDIV(*T, BO); break; + case BO_Sub: emitSUB(*T, BO); break; + case BO_Add: emitADD(*T, BO); break; + case BO_Mul: emitMUL(*T, BO); break; + default: + return bail(BO); + } + + return Error::success(); + } + + return bail(BO); +} + +Error Compiler::discard(const Expr *DE) { + switch (DE->getStmtClass()) { + case Stmt::BinaryOperatorClass: + return discardBinaryOperator(static_cast(DE)); + default: + if (auto E = Visit(DE)) + return E; + if (auto T = Ctx.classify(DE)) { + emitPOP(*T, DE); + return Error::success(); + } + break; + } + + return bail(DE); +} + +Error Compiler::discardBinaryOperator(const BinaryOperator *BO) { + // Typecheck the args. + auto *LHS = BO->getLHS(); + auto TypeLHS = Ctx.classify(LHS->getType()); + if (!TypeLHS) { + return bail(BO); + } + auto *RHS = BO->getRHS(); + auto TypeRHS = Ctx.classify(RHS->getType()); + if (!TypeRHS) { + return bail(BO); + } + + // Peephole optimisations. + if (auto T = Ctx.classify(BO->getType())) { + switch (BO->getOpcode()) { + case BO_Assign: + if (auto *DE = dyn_cast(BO->getLHS())) { + bool IsReference = DE->getDecl()->getType()->isReferenceType(); + if (*TypeLHS == *T && *TypeRHS == *T && !IsReference) { + if (auto *PD = dyn_cast(DE->getDecl())) { + return withParam(PD, [this, RHS, BO](unsigned Idx) -> Error { + if (auto E = Visit(RHS)) + return E; + return bail(BO); + }); + } + + if (auto *VD = dyn_cast(DE->getDecl())) { + return withLocal(VD, [this, T, RHS, DE](const Local &L) -> Error { + if (auto E = Visit(RHS)) + return E; + emitSET_LOCAL(*T, L.Location, DE); + return Error::success(); + }); + } + } + } + break; + case BO_Comma: { + if (auto E = discard(LHS)) + return E; + if (auto E = discard(RHS)) + return E; + return Error::success(); + } + default: + break; + } + } + + if (auto E = Visit(BO)) + return E; + + emitPOP(*Ctx.classify(BO), BO); + return Error::success(); +} + +Error Compiler::withParam(const ParmVarDecl *P, + std::function &&f) { + auto It = Params.find(P); + if (It == Params.end()) { + return bail(P); + } + return f(It->second); +} + +const Compiler::Local &Compiler::createLocal(const VarDecl *V, unsigned Size, + unsigned ElemSize, + PrimType ElemTy) { + auto Offset = NextLocalOffset; + // TODO: reserve space for block metadata + auto Location = NextLocalOffset; + NextLocalOffset += align(Size); + Local L{Location, Size}; + bool IsMutable = !V->getType().isConstQualified(); + bool IsArray = V->getType()->isArrayType(); + Top->addDescriptor({V, Offset, Size, ElemSize, ElemTy, IsMutable, IsArray}); + return Locals.insert({V, L}).first->second; +} + +Error Compiler::withLocal(const VarDecl *V, + std::function &&f) { + auto It = Locals.find(V); + if (It == Locals.end()) { + return bail(V); + } + return f(It->second); +} + +void Compiler::emitLabel(LabelTy Label) { + size_t Offset = P.getOffset(); + LabelOffsets.insert({Label, Offset}); + auto It = LabelRelocs.find(Label); + if (It != LabelRelocs.end()) { + for (unsigned Reloc : It->second) { + P.write(Reloc, Offset - static_cast(Reloc)); + } + LabelRelocs.erase(It); + } +} + +void Compiler::emitJT(LabelTy Label) { + emitOp(EO_JT, {}); + emitReloc(Label); +} + +void Compiler::emitJF(LabelTy Label) { + emitOp(EO_JF, {}); + emitReloc(Label); +} + +void Compiler::emitJMP(LabelTy Label) { + emitOp(EO_JMP, {}); + emitReloc(Label); +} + +template +void Compiler::emitOp(Opcode Op, const typename PrimConv::T &... Args, + const SourceInfo &L) { + if (L) + P.emitSource(L); + P.emitBytes(reinterpret_cast(&Op), sizeof(Op)); + (void)std::initializer_list{ + (P.emitBytes(reinterpret_cast(&Args), sizeof(Args)), 0)...}; +} + +void Compiler::emitReloc(LabelTy Label) { + uint32_t Offset; + + auto Position = P.getOffset(); + auto It = LabelOffsets.find(Label); + if (It != LabelOffsets.end()) { + Offset = It->second - static_cast(Position); + } else { + Offset = 0; + LabelRelocs[Label].push_back(Position); + } + + P.emitBytes(reinterpret_cast(&Offset), sizeof(Offset)); +} + +//===----------------------------------------------------------------------===// +// Opcode emitters +//===----------------------------------------------------------------------===// + +#define GET_EMITTER_IMPL +#include "Opcodes.inc" +#undef GET_EMITTER_IMPL diff --git a/clang/lib/AST/ExprVM/Context.h b/clang/lib/AST/ExprVM/Context.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Context.h @@ -0,0 +1,78 @@ +//===--- Context.h - Context 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the constexpr execution context. +// +// The execution context manages cached bytecode and the global context. +// It invokes the compiler and interpreter, propagating errors. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_EXPRVM_CONTEXT_H +#define LLVM_CLANG_AST_EXPRVM_CONTEXT_H + +#include "Context.h" +#include "clang/AST/APValue.h" +#include "llvm/Support/Error.h" + +namespace clang { +class ASTContext; +class Stmt; +class FunctionDecl; + +namespace vm { +class Function; +class Program; +class State; +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. +class Context { +public: + /// Initialises the constexpr VM. + Context(ASTContext &Ctx); + + /// Cleans up the constexpr VM. + ~Context(); + + /// Compiles a function and links it to the constexpr program. + llvm::Expected compileFunction(const FunctionDecl *F); + + /// Evaluats a function call. + InterpResult executeFunction(State &Parent, Function *Func, + llvm::ArrayRef Args, APValue &Result); + + /// Returns the AST context. + ASTContext &getASTContext() const { return Ctx; } + + /// Classifies an expression. + llvm::Optional classify(QualType T); + /// Classifies a type. + llvm::Optional classify(const Expr *E); + +private: + /// Current compilation context. + ASTContext &Ctx; + /// Constexpr program. + std::unique_ptr P; +}; + +} // namespace vm +} // namespace clang + +#endif diff --git a/clang/lib/AST/ExprVM/Context.cpp b/clang/lib/AST/ExprVM/Context.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Context.cpp @@ -0,0 +1,124 @@ +//===--- Context.cpp - Context 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 "Context.h" +#include "Compiler.h" +#include "Interp.h" +#include "InterpFrame.h" +#include "InterpStack.h" +#include "Program.h" +#include "Type.h" +#include "clang/AST/Expr.h" + +using namespace clang; +using namespace clang::vm; + +Context::Context(ASTContext &Ctx) : Ctx(Ctx), P(new Program()) {} + +Context::~Context() {} + +Expected Context::compileFunction(const FunctionDecl *F) { + if (auto *Func = P->getFunction(F)) + return Func; + return Compiler(*this, *P, F).compile(); +} + +InterpResult Context::executeFunction(State &Parent, Function *Func, + llvm::ArrayRef Args, + APValue &Result) { + + InterpState State(Parent, *P, *this); + + // Convert the arguments and store them on the stack. + if (!Parent.checkingPotentialConstantExpression()) { + auto *F = Func->getDecl(); + auto &Stk = State.Stk; + + assert(Args.size() == F->getNumParams() && "Missing arguments"); + + for (unsigned I = 0, N = F->getNumParams(); I < N; ++I) { + auto *ParamDecl = F->getParamDecl(I); + auto &Value = Args[I]; + + switch (*classify(ParamDecl->getType())) { + case ET_SINT_8: + Stk.push::T>(Value.getInt().getSExtValue()); + break; + case ET_UINT_8: + Stk.push::T>(Value.getInt().getZExtValue()); + break; + case ET_SINT_16: + Stk.push::T>(Value.getInt().getSExtValue()); + break; + case ET_UINT_16: + Stk.push::T>(Value.getInt().getZExtValue()); + break; + case ET_SINT_32: + Stk.push::T>(Value.getInt().getSExtValue()); + break; + case ET_UINT_32: + Stk.push::T>(Value.getInt().getZExtValue()); + break; + case ET_SINT_64: + Stk.push::T>(Value.getInt().getSExtValue()); + break; + case ET_UINT_64: + Stk.push::T>(Value.getInt().getZExtValue()); + break; + case ET_BOOL: + Stk.push::T>(Value.getInt().getBoolValue()); + break; + } + } + } + + State.Current = new InterpFrame(State, Func, nullptr); + return Interpret(State, Result) ? InterpResult::SUCCESS : InterpResult::FAIL; +} + +llvm::Optional Context::classify(QualType T) { + if (T->isBooleanType()) { + return ET_BOOL; + } + + if (T->isSignedIntegerType()) { + switch (Ctx.getIntWidth(T)) { + case 64: + return ET_SINT_64; + case 32: + return ET_SINT_32; + case 16: + return ET_SINT_16; + case 8: + return ET_SINT_8; + default: + return {}; + } + } + + if (T->isUnsignedIntegerType()) { + switch (Ctx.getIntWidth(T)) { + case 64: + return ET_UINT_64; + case 32: + return ET_UINT_32; + case 16: + return ET_SINT_16; + case 8: + return ET_SINT_8; + default: + return {}; + } + } + + return {}; +} + +llvm::Optional Context::classify(const Expr *E) { + return classify(E->getType()); +} diff --git a/clang/lib/AST/ExprVM/Disasm.cpp b/clang/lib/AST/ExprVM/Disasm.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Disasm.cpp @@ -0,0 +1,58 @@ +//===--- Disasm.cpp - Disassembler for bytecode functions -------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Dump method for Function which disassembles the bytecode. +// +//===----------------------------------------------------------------------===// + +#include "Function.h" +#include "Opcode.h" +#include "Program.h" +#include "Type.h" +#include "llvm/Support/Compiler.h" + +using namespace clang; +using namespace vm; + +namespace { + +template T Get(const char *&PC) { + auto Value = + llvm::support::endian::read(PC); + PC += sizeof(T); + return Value; +} + +} // namespace + +LLVM_DUMP_METHOD void Function::dump() const { dump(llvm::errs()); } + +LLVM_DUMP_METHOD void Function::dump(llvm::raw_ostream &OS) const { + OS << F->getName() << ":\n"; + + const char *PC = P.getCodeBegin() + Start; + + auto PrintName = [&OS](const char *Name) { + OS << Name; + for (long I = 0, N = strlen(Name); I < 30 - N; ++I) { + OS << ' '; + } + }; + + while (PC != P.getCodeBegin() + End) { + size_t Addr = PC - P.getCodeBegin(); + Opcode Op = *reinterpret_cast(PC); + PC += sizeof(Opcode); + OS << llvm::format("%8d", Addr) << " "; + switch (Op) { +#define GET_DISASM +#include "Opcodes.inc" +#undef GET_DISASM + } + } +} diff --git a/clang/lib/AST/ExprVM/Frame.h b/clang/lib/AST/ExprVM/Frame.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Frame.h @@ -0,0 +1,41 @@ +//===--- Frame.h - Call frame for the VM and AST Walker ---------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the base class of interpreter and evaluator stack frames. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_EXPRVM_FRAME_H +#define LLVM_CLANG_AST_EXPRVM_FRAME_H + +#include "clang/Basic/SourceLocation.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +class FunctionDecl; + +namespace vm { + +/// Base class for stack frames, shared between VM and walker. +class Frame { +public: + virtual ~Frame(); + + virtual void describe(llvm::raw_ostream &OS) = 0; + + virtual Frame *getCaller() const = 0; + + virtual SourceLocation getLoc() const = 0; + + virtual const FunctionDecl *getCallee() const = 0; +}; + +} // namespace vm +} // namespace clang + +#endif diff --git a/clang/lib/AST/ExprVM/Frame.cpp b/clang/lib/AST/ExprVM/Frame.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Frame.cpp @@ -0,0 +1,14 @@ +//===--- Frame.cpp - Call frame for the VM and AST Walker -------*- 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 "Frame.h" + +using namespace clang; +using namespace clang::vm; + +Frame::~Frame() {} diff --git a/clang/lib/AST/ExprVM/Function.h b/clang/lib/AST/ExprVM/Function.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Function.h @@ -0,0 +1,126 @@ +//===--- Function.h - Bytecode function for the 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the Function class which holds all bytecode function-specific data. +// +// The scope class which describes local variables is also defined here. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_EXPRVM_FUNCTION_H +#define LLVM_CLANG_AST_EXPRVM_FUNCTION_H + +#include "Pointer.h" +#include "clang/AST/Decl.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace vm { +class Program; +enum PrimType : uint32_t; + +using CodePtr = const char *; + +/// Describes a scope block. +/// +/// The block gathers all the descriptors of the locals defined in this block. +class Scope { +public: + using DescVector = llvm::SmallVector; + + Scope(DescVector &&Descriptors) : Descriptors(std::move(Descriptors)) {} + + llvm::iterator_range descriptors() { + return llvm::make_range(Descriptors.begin(), Descriptors.end()); + } + +private: + /// Object descriptors in this block. + DescVector Descriptors; +}; + +/// Bytecode function. +/// +/// Contains links to the bytecode of the function, as well as metadata +/// describing all arguments and stack-local variables. +class Function { +public: + /// Returns the size of the function's local stack. + unsigned getFrameSize() const { return FrameSize; } + /// Returns the size of the argument stack. + unsigned getArgSize() const { return ArgSize; } + + /// Returns a pointer to the start of the code. + CodePtr getCodeBegin() const; + + /// Returns the address of the function. + uint32_t getAddress() const { return Start; } + + /// Returns the function's name. + StringRef getName() const { return F->getName(); } + + /// Returns the original FunctionDecl. + const FunctionDecl *getDecl() const { return F; } + + /// Returns a parameter descriptor. + Descriptor *getParamDescriptor(unsigned Offset) const; + + /// Range over the scope blocks. + llvm::iterator_range::iterator> scopes() { + return llvm::make_range(Scopes.begin(), Scopes.end()); + } + + /// Range over argument types. + using arg_reverse_iterator = SmallVectorImpl::reverse_iterator; + llvm::iterator_range args_reverse() { + return llvm::make_range(Args.rbegin(), Args.rend()); + } + + /// Returns a specific scope. + Scope &getScope(unsigned Idx) { return Scopes[Idx]; } + +private: + /// Only Program is allowed to construct this. + Function(Program &P, const FunctionDecl *F, unsigned Start, unsigned End, + unsigned FrameSize, unsigned ArgSize, + llvm::SmallVector &&Scopes, + llvm::SmallVector &&Args, + llvm::DenseMap> &&Params); + +private: + friend class Program; + + /// Program reference. + Program &P; + /// Declaration this function was compiled from. + const FunctionDecl *F; + /// Function start index. + unsigned Start; + /// Function end index. + unsigned End; + /// Local area size: storage + metadata. + unsigned FrameSize; + /// Size of the argument stack. + unsigned ArgSize; + /// List of block descriptors. + llvm::SmallVector Scopes; + /// List of argument types. + llvm::SmallVector Args; + /// Map from byte offset to parameter descriptor. + llvm::DenseMap> Params; + +public: + /// Dumps the disassembled bytecode to \c llvm::errs(). + void dump() const; + void dump(llvm::raw_ostream &OS) const; +}; + +} // namespace vm +} // namespace clang + +#endif diff --git a/clang/lib/AST/ExprVM/Function.cpp b/clang/lib/AST/ExprVM/Function.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Function.cpp @@ -0,0 +1,30 @@ +//===--- Function.h - Bytecode function for the 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 "Function.h" +#include "Program.h" +#include "clang/AST/Decl.h" + +using namespace clang; +using namespace clang::vm; + +Function::Function( + Program &P, const FunctionDecl *F, unsigned Start, unsigned End, + unsigned FrameSize, unsigned ArgSize, llvm::SmallVector &&Scopes, + llvm::SmallVector &&Args, + llvm::DenseMap> &&Params) + : P(P), F(F), Start(Start), End(End), FrameSize(FrameSize), + ArgSize(ArgSize), Scopes(std::move(Scopes)), Args(std::move(Args)), + Params(std::move(Params)) {} + +CodePtr Function::getCodeBegin() const { return P.getCodeBegin() + Start; } + +Descriptor *Function::getParamDescriptor(unsigned Offset) const { + auto It = Params.find(Offset); + return It == Params.end() ? nullptr : It->second.get(); +} diff --git a/clang/lib/AST/ExprVM/Interp.h b/clang/lib/AST/ExprVM/Interp.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Interp.h @@ -0,0 +1,101 @@ +//===--- Interp.h - 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 +// +//===----------------------------------------------------------------------===// +// +// Definition of the interpreter state and entry point. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_EXPRVM_INTERP_H +#define LLVM_CLANG_AST_EXPRVM_INTERP_H + +#include "Context.h" +#include "Function.h" +#include "InterpStack.h" +#include "State.h" +#include "clang/AST/APValue.h" +#include "clang/AST/ASTDiagnostic.h" +#include "clang/AST/Expr.h" +#include "clang/Basic/OptionalDiagnostic.h" + +namespace clang { +namespace vm { +class Context; +class Function; +class InterpStack; +class InterpFrame; + +/// Interpreter context. +class InterpState final : public State { +public: + InterpState(State &Parent, Program &P, Context &Ctx); + + ~InterpState(); + + // Stack frame accessors. + Frame *getSplitFrame() { return Parent.getCurrentFrame(); } + Frame *getCurrentFrame() override; + unsigned getCallStackDepth() override { return CallStackDepth; } + const Frame *getBottomFrame() const override { + return Parent.getBottomFrame(); + } + + // Acces objects from the walker context. + Expr::EvalStatus &getEvalStatus() const override { + return Parent.getEvalStatus(); + } + ASTContext &getCtx() const override { return Parent.getCtx(); } + + // Forward status checks and updates to the walker. + bool checkingForOverflow() const override { + return Parent.checkingForOverflow(); + } + bool checkingPotentialConstantExpression() const override { + return Parent.checkingPotentialConstantExpression(); + } + bool noteUndefinedBehavior() override { + return Parent.noteUndefinedBehavior(); + } + bool hasActiveDiagnostic() override { return Parent.hasActiveDiagnostic(); } + void setActiveDiagnostic(bool Flag) override { + Parent.setActiveDiagnostic(Flag); + } + void setFoldFailureDiagnostic(bool Flag) override { + Parent.setFoldFailureDiagnostic(Flag); + } + bool hasPriorDiagnostic() override { return Parent.hasPriorDiagnostic(); } + + /// Reports overflow and return true if evaluation should continue. + bool reportOverflow(const Expr *E, const llvm::APSInt &Value); + + /// Notes the location of a descriptor. + void noteLocation(const Descriptor *Desc); + +private: + /// AST Walker state. + State &Parent; + +public: + /// Reference to the module containing all bytecode. + Program &P; + /// Interpreter Context. + Context &Ctx; + /// Temporary stack. + InterpStack Stk; + /// The current frame. + InterpFrame *Current = nullptr; + /// Call stack depth. + unsigned CallStackDepth; +}; + +/// Interpreter entry point. +bool Interpret(InterpState &S, APValue &Result); + +} // namespace vm +} // namespace clang + +#endif diff --git a/clang/lib/AST/ExprVM/Interp.cpp b/clang/lib/AST/ExprVM/Interp.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/AST/ExprVM/Interp.cpp @@ -0,0 +1,400 @@ +//===--- Interp.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 "Function.h" +#include "InterpFrame.h" +#include "InterpStack.h" +#include "Opcode.h" +#include "Program.h" +#include "State.h" +#include "Type.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTDiagnostic.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/Expr.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/Support/Endian.h" +#include +#include + +using namespace clang; +using namespace clang::vm; + +using APSInt = llvm::APSInt; + +InterpState::InterpState(State &Parent, Program &P, Context &Ctx) + : Parent(Parent), P(P), Ctx(Ctx), Current(nullptr), + CallStackDepth(Parent.getCallStackDepth() + 1) {} + +InterpState::~InterpState() { + while (Current) { + InterpFrame *Next = Current->Caller; + delete Current; + Current = Next; + } +} + +Frame *InterpState::getCurrentFrame() { + return Current->Caller ? Current : Parent.getCurrentFrame(); +} + +bool InterpState::reportOverflow(const Expr *E, const llvm::APSInt &Value) { + QualType Type = E->getType(); + CCEDiag(E, diag::note_constexpr_overflow) << Value << Type; + return noteUndefinedBehavior(); +} + +void InterpState::noteLocation(const Descriptor *Desc) { + if (auto *D = Desc->Source.dyn_cast()) + Note(D->getLocation(), diag::note_declared_at); + if (auto *E = Desc->Source.dyn_cast()) + Note(E->getExprLoc(), diag::note_constexpr_temporary_here); +} + +namespace { + +/// Reads a primitive and advances the program counter. +template T read(CodePtr &PC) { + T Value = + llvm::support::endian::read(PC); + PC += sizeof(T); + return Value; +} + +/// Converts an integer into an APSInt. +template +APSInt MakeAPSInt(T Value, unsigned Digits = sizeof(T) * 8) { + llvm::APInt Boxed(Digits, Value, std::is_signed::value); + return APSInt(Boxed, std::is_unsigned::value); +} + +/// Convers a value to an APValue. +template APValue MakeValue(const T &Value) { + return APValue(MakeAPSInt(Value)); +} + +template <> APValue MakeValue(const bool &V) { + return {APValue(APSInt(llvm::APInt(1, V, false), true))}; +} + +//===----------------------------------------------------------------------===// +// ADD, SUB, MUL +//===----------------------------------------------------------------------===// + +template class OpAP> +bool AddSubMul(InterpState &S, CodePtr OpPC) { + const T RHS = S.Stk.pop(); + const T LHS = S.Stk.pop(); + + // Unsigned overflows are not UB - defined as modular arithmetic. + // Signed path must handle overflows as they are UB. + if (std::is_unsigned::value) { + S.Stk.push(OpAP()(LHS, RHS)); + return true; + } else { + // Fast path - add the numbers with fixed width. + T Result; + if (!OpFW(LHS, RHS, &Result)) { + S.Stk.push(Result); + return true; + } + + // If for some reason evaluation continues, use the truncated results. + S.Stk.push(Result); + + // Slow path - compute the result using another bit of precision. + APSInt APLHS = MakeAPSInt(LHS, BitWidth); + APSInt APRHS = MakeAPSInt(RHS, BitWidth); + APSInt Value = OpAP()(APLHS, APRHS); + + // Report undefined behaviour, stopping if required. + const Expr *E = S.P.getExpr(OpPC); + QualType Type = E->getType(); + if (S.checkingForOverflow()) { + auto Trunc = Value.trunc(APLHS.getBitWidth()).toString(10); + auto Loc = E->getExprLoc(); + S.report(Loc, diag::warn_integer_constant_overflow) << Trunc << Type; + return true; + } else { + S.CCEDiag(E, diag::note_constexpr_overflow) << Value << Type; + return S.noteUndefinedBehavior(); + } + } +}; + +template bool AddFn(T a, T b, T *r) { + return __builtin_add_overflow(a, b, r); +} + +template bool SubFn(T a, T b, T *r) { + return __builtin_sub_overflow(a, b, r); +} + +template bool MulFn(T a, T b, T *r) { + return __builtin_mul_overflow(a, b, r); +} + +template bool ADD(InterpState &S, CodePtr OpPC) { + constexpr auto BitWidth = sizeof(T) * CHAR_BIT + 1; + return AddSubMul, std::plus>(S, OpPC); +} + +template bool SUB(InterpState &S, CodePtr OpPC) { + constexpr auto BitWidth = sizeof(T) * CHAR_BIT + 1; + return AddSubMul, std::minus>(S, OpPC); +} + +template bool MUL(InterpState &S, CodePtr OpPC) { + constexpr auto BitWidth = sizeof(T) * CHAR_BIT * 2; + return AddSubMul, std::multiplies>(S, OpPC); +} + +//===----------------------------------------------------------------------===// +// DIV, REM +//===----------------------------------------------------------------------===// + +template +bool DivRem(InterpState &S, CodePtr OpPC) { + const T RHS = S.Stk.pop(); + const T LHS = S.Stk.pop(); + + // Bail on division by zero. + if (RHS == 0) { + S.FFDiag(S.P.getSource(OpPC), diag::note_expr_divide_by_zero); + return false; + } + + if (std::is_signed::value) { + // (-MAX - 1) / -1 = MAX + 1 overflows. + if (LHS == std::numeric_limits::min() && RHS == static_cast(-1)) { + // Push the truncated value in case the interpreter continues. + S.Stk.push(std::numeric_limits::min()); + + // Compute the actual value for the diagnostic. + constexpr size_t BitWidth = sizeof(T) * CHAR_BIT + 1; + APSInt Value = + OpAP()(MakeAPSInt(LHS, BitWidth), MakeAPSInt(RHS, BitWidth)); + return S.reportOverflow(S.P.getExpr(OpPC), Value); + } + } + + // Safe to execute division here. + S.Stk.push(OpFW(LHS, RHS)); + return true; +} + +template T DivFn(T a, T b) { return a / b; } + +template T RemFn(T a, T b) { return a % b; } + +template bool DIV(InterpState &S, CodePtr OpPC) { + return DivRem, std::divides>(S, OpPC); +} + +template bool REM(InterpState &S, CodePtr OpPC) { + return DivRem, std::modulus>(S, OpPC); +} + +//===----------------------------------------------------------------------===// +// EQ, NE, GT, GE, LT, LE +//===----------------------------------------------------------------------===// + +template