https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/189410
>From f1726db90f3443760732e92206d8c0fcda5b5ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Tue, 10 Feb 2026 16:06:17 +0100 Subject: [PATCH] Exceptions --- clang/include/clang/Basic/Builtins.td | 11 + .../include/clang/Basic/DiagnosticASTKinds.td | 3 + clang/lib/AST/ByteCode/ByteCodeEmitter.cpp | 3 +- clang/lib/AST/ByteCode/ByteCodeEmitter.h | 10 + clang/lib/AST/ByteCode/Compiler.cpp | 182 +++- clang/lib/AST/ByteCode/Disasm.cpp | 2 + clang/lib/AST/ByteCode/EvalEmitter.cpp | 8 + clang/lib/AST/ByteCode/EvalEmitter.h | 11 + clang/lib/AST/ByteCode/Function.h | 25 +- clang/lib/AST/ByteCode/Interp.cpp | 334 ++++++- clang/lib/AST/ByteCode/Interp.h | 118 ++- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 32 + clang/lib/AST/ByteCode/InterpFrame.h | 1 + clang/lib/AST/ByteCode/InterpStack.cpp | 6 + clang/lib/AST/ByteCode/InterpStack.h | 2 + clang/lib/AST/ByteCode/InterpState.h | 23 + clang/lib/AST/ByteCode/Opcodes.td | 38 +- clang/lib/AST/ByteCode/PrimType.h | 9 + clang/lib/AST/ByteCode/Source.h | 2 + clang/test/AST/ByteCode/cxx20.cpp | 15 +- clang/test/AST/ByteCode/cxx23.cpp | 4 +- clang/test/AST/ByteCode/exceptions.cpp | 839 ++++++++++++++++++ clang/test/AST/ByteCode/invalid.cpp | 30 +- clang/utils/TableGen/ClangOpcodesEmitter.cpp | 12 +- 24 files changed, 1645 insertions(+), 75 deletions(-) create mode 100644 clang/test/AST/ByteCode/exceptions.cpp diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 506f47fb597af..dc23df692fbd7 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -5919,3 +5919,14 @@ def CountedByRef : Builtin { let Attributes = [NoThrow, CustomTypeChecking]; let Prototype = "int(...)"; } + +def BuiltinCurrentException: LangBuiltin<"CXX_LANG"> { + let Spellings = ["__builtin_current_exception"]; + let Attributes = [NoThrow, Constexpr]; + let Prototype = "void*()"; +} +def BuiltinUncaughtExceptions: LangBuiltin<"CXX_LANG"> { + let Spellings = ["__builtin_uncaught_exceptions"]; + let Attributes = [NoThrow, Constexpr]; + let Prototype = "int()"; +} diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index bde418695f647..bd3fefd8c54a3 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -406,6 +406,9 @@ def note_constexpr_infer_alloc_token_no_metadata : Note< "could not get token metadata for inferred type">; def note_constexpr_infer_alloc_token_stateful_mode : Note<"stateful alloc token mode not supported in constexpr">; +def note_constexpr_uncaught_exception : Note<"uncaught exception of type %0: '%1'">; + +def note_constexpr_exception_in_noexcept_func : Note<"uncaught exception in noexcept function">; def warn_attribute_needs_aggregate : Warning< "%0 attribute is ignored in non-aggregate type %1">, diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp b/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp index 393b8481fecd1..17cf076c52851 100644 --- a/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp +++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp @@ -85,7 +85,8 @@ void ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl, // Set the function's code. Func->setCode(FuncDecl, NextLocalOffset, std::move(Code), std::move(SrcMap), - std::move(Scopes), FuncDecl->hasBody(), IsValid); + std::move(Scopes), std::move(ExceptionTable), + FuncDecl->hasBody(), IsValid); Func->setIsFullyCompiled(true); } diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.h b/clang/lib/AST/ByteCode/ByteCodeEmitter.h index 34342e53837b9..dad8d5cc7e275 100644 --- a/clang/lib/AST/ByteCode/ByteCodeEmitter.h +++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.h @@ -82,6 +82,14 @@ class ByteCodeEmitter { llvm::SmallVector<SmallVector<Local, 8>, 2> Descriptors; std::optional<SourceInfo> LocOverride = std::nullopt; + unsigned currentCodeSize() const { return Code.size(); } + + void registerExceptionHandler(unsigned From, unsigned To, unsigned Target, + UnsignedOrNone DeclOffset, const Type *T) { + ExceptionTable.push_back( + ExceptionTableEntry{From, To, Target, DeclOffset, T}); + } + private: /// Current compilation context. Context &Ctx; @@ -97,9 +105,11 @@ class ByteCodeEmitter { llvm::DenseMap<LabelTy, llvm::SmallVector<unsigned, 5>> LabelRelocs; /// Program code. llvm::SmallVector<std::byte> Code; + llvm::SmallVector<ExceptionTableEntry> ExceptionTable; /// Opcode to expression mapping. SourceMap SrcMap; +public: /// Returns the offset for a jump or records a relocation. int32_t getOffset(LabelTy Label); diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 95f9a2f507335..bf02dfe756394 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -37,6 +37,17 @@ static std::optional<bool> getBoolValue(const Expr *E) { return std::nullopt; } +[[maybe_unused]] static bool blockEndsInReturn(const Stmt *S) { + if (isa<ReturnStmt>(S)) + return true; + + if (const auto *CS = dyn_cast<CompoundStmt>(S); CS && !CS->body_empty()) { + return isa<ReturnStmt>(CS->body_back()); + } + + return false; +} + /// Scope used to handle temporaries in toplevel variable declarations. template <class Emitter> class DeclScope final : public LocalScope<Emitter> { public: @@ -3471,10 +3482,138 @@ bool Compiler<Emitter>::VisitPredefinedExpr(const PredefinedExpr *E) { template <class Emitter> bool Compiler<Emitter>::VisitCXXThrowExpr(const CXXThrowExpr *E) { - if (E->getSubExpr() && !this->discard(E->getSubExpr())) + const Expr *SubExpr = E->getSubExpr(); + if (!Ctx.getLangOpts().CPlusPlus26 || !Ctx.getLangOpts().CXXExceptions) { + if (SubExpr && !this->discard(SubExpr)) + return false; + return this->emitInvalid(E); + } + + assert(E->getSubExpr()); // XXX + + QualType ExceptionType = SubExpr->getType(); + OptPrimType ExceptionT = classify(SubExpr); + + const Descriptor *Desc; + if (ExceptionT) + Desc = P.createDescriptor(SubExpr, *ExceptionT); + else + Desc = P.createDescriptor(SubExpr, ExceptionType.getTypePtr(), std::nullopt, + /*IsConst=*/false); + + if (!this->emitAllocException(Desc, E)) + return false; + + if (ExceptionT) { + if (!this->visit(SubExpr)) + return false; + if (!this->emitInit(*ExceptionT, E)) + return false; + } else { + if (!this->visitInitializer(SubExpr)) + return false; + } + + PrimType T = classify(E->getSubExpr()).value_or(PT_Ptr); + if (!this->emitSaveException(T, ExceptionType.getTypePtr(), E)) + return false; + + this->VarScope->destroyLocals(); + + return this->emitThrow(ExceptionType.getTypePtr(), E); +} + +template <class Emitter> +bool Compiler<Emitter>::visitCXXTryStmt(const CXXTryStmt *S) { + if (!Ctx.getLangOpts().CPlusPlus26 || !Ctx.getLangOpts().CXXExceptions) { + // Ignore all handlers. + return this->visitStmt(S->getTryBlock()); + } + + unsigned NumHandlers = S->getNumHandlers(); + + if (!this->emitThrowTrap(S)) + return false; + + unsigned s = this->currentCodeSize(); + + // Emit try block contents. + const auto *TryBlock = cast<CompoundStmt>(S->getTryBlock()); + if (!this->visitStmt(TryBlock)) return false; - return this->emitInvalid(E); + unsigned e = this->currentCodeSize(); + + // Jump after handlers if nothing was thrown. + LabelTy EndLabel = this->getLabel(); + this->jump(EndLabel, S); + + // Register and emit all handlers. + for (unsigned I = 0; I != NumHandlers; ++I) { + const CXXCatchStmt *Handler = S->getHandler(I); + const Stmt *HandlerBlock = Handler->getHandlerBlock(); + const VarDecl *ExceptionDecl = Handler->getExceptionDecl(); + QualType CatchType = Handler->getCaughtType(); + UnsignedOrNone ExceptionDeclOffset = std::nullopt; + + unsigned t = this->currentCodeSize(); + if (ExceptionDecl) { + if (OptPrimType T = classify(CatchType)) { + unsigned LocalOffset = allocateLocalPrimitive(ExceptionDecl, *T, + /*IsConst=*/true); + if (CatchType->isReferenceType()) { + if (!this->emitGetPtrExceptionValue(S)) + return false; + } else { + if (!this->emitGetExceptionValue(*T, S)) + return false; + } + if (!this->emitSetLocal(*T, LocalOffset, S)) + return false; + } else { + UnsignedOrNone LocalOffset = allocateLocal(ExceptionDecl, CatchType); + if (!LocalOffset) + return false; + + if (!this->emitGetPtrLocal(*LocalOffset, Handler)) + return false; + if (!this->emitGetPtrExceptionValue(Handler)) + return false; + // if (!this->emitGetExceptionValuePtr(Handler)) + // return false; + if (!this->emitMemcpy(Handler)) + return false; + if (!this->emitPopPtr(Handler)) + return false; + } + } else { + // This is a catch-all handler. + // if (!this->emitClearExceptionValue(S)) + // return false; + + // FIXME: MOve down + if (!this->emitCatch(S)) + return false; + } + const Type *CatchTypePtr = CatchType.getTypePtrOrNull(); + + this->registerExceptionHandler(s, e, t, ExceptionDeclOffset, CatchTypePtr); + if (!this->visitStmt(HandlerBlock)) + return false; + + if (!this->emitClearExceptionValue(S)) + return false; + + // FIXME: Re-enable this. + // if (blockEndsInReturn(TryBlock)) + // continue; + this->jump(EndLabel, S); + } + + this->fallthrough(EndLabel); + this->emitLabel(EndLabel); + + return true; } template <class Emitter> @@ -6877,12 +7016,6 @@ bool Compiler<Emitter>::visitAttributedStmt(const AttributedStmt *S) { return true; } -template <class Emitter> -bool Compiler<Emitter>::visitCXXTryStmt(const CXXTryStmt *S) { - // Ignore all handlers. - return this->visitStmt(S->getTryBlock()); -} - template <class Emitter> bool Compiler<Emitter>::emitLambdaStaticInvokerBody(const CXXMethodDecl *MD) { assert(MD->isLambdaStaticInvoker()); @@ -7153,8 +7286,17 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { return false; } - if (!visitStmt(Body)) - return false; + if (isa<CompoundStmt>(Body)) { + if (!visitStmt(Body)) + return false; + } else { + // direct try {} body. + LocalScope<Emitter> Scope(this); + if (!visitStmt(Body)) + return false; + if (!Scope.destroyLocals()) + return false; + } } return this->emitRetVoid(SourceInfo{}); @@ -7250,14 +7392,24 @@ bool Compiler<Emitter>::visitFunc(const FunctionDecl *F) { } // Regular functions. - if (const auto *Body = F->getBody()) - if (!visitStmt(Body)) - return false; + if (const auto *Body = F->getBody()) { + if (isa<CompoundStmt>(Body)) { + if (!visitStmt(Body)) + return false; + } else { + // direct try {} body. + LocalScope<Emitter> Scope(this); + if (!visitStmt(Body)) + return false; + if (!Scope.destroyLocals()) + return false; + } + } // Emit a guard return to protect against a code path missing one. if (F->getReturnType()->isVoidType()) - return this->emitRetVoid(SourceInfo{}); - return this->emitNoRet(SourceInfo{}); + return this->emitRetVoid(SourceInfo{}) && this->emitAfterRet(SourceInfo{}); + return this->emitNoRet(SourceInfo{}) && this->emitAfterRet(SourceInfo{}); } static uint32_t getBitWidth(const Expr *E) { diff --git a/clang/lib/AST/ByteCode/Disasm.cpp b/clang/lib/AST/ByteCode/Disasm.cpp index 4caf830a0a1b4..d94775767f6fb 100644 --- a/clang/lib/AST/ByteCode/Disasm.cpp +++ b/clang/lib/AST/ByteCode/Disasm.cpp @@ -188,6 +188,8 @@ LLVM_DUMP_METHOD void Function::dump(llvm::raw_ostream &OS, Text.Addr = Addr; Text.IsJump = isJumpOpcode(Op); Text.CurrentOp = (PC == OpPC); + // llvm::errs() << (void*)(*PC) << " / " << (void*)(*OpPC) << ((*OpPC - + // *PC)) << '\n'; switch (Op) { #define GET_DISASM #include "Opcodes.inc" diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp b/clang/lib/AST/ByteCode/EvalEmitter.cpp index dbec700c80d44..a458ee217ec9f 100644 --- a/clang/lib/AST/ByteCode/EvalEmitter.cpp +++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp @@ -238,6 +238,10 @@ template <PrimType OpType> bool EvalEmitter::emitRet(SourceInfo Info) { if (!isActive()) return true; + if (S.ThrownValue && !S.ThrownValue->Caught) { + return diagnoseUncaughtException(S, OpPC); + } + using T = typename PrimConv<OpType>::T; EvalResult.takeValue(S.Stk.pop<T>().toAPValue(Ctx.getASTContext())); return true; @@ -247,6 +251,10 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) { if (!isActive()) return true; + if (S.ThrownValue && !S.ThrownValue->Caught) { + llvm::errs() << "AAAAAHA!\n"; + } + const Pointer &Ptr = S.Stk.pop<Pointer>(); // If we're returning a raw pointer, call our callback. if (this->PtrCB) diff --git a/clang/lib/AST/ByteCode/EvalEmitter.h b/clang/lib/AST/ByteCode/EvalEmitter.h index f939ef4839a19..ad8c02e255afb 100644 --- a/clang/lib/AST/ByteCode/EvalEmitter.h +++ b/clang/lib/AST/ByteCode/EvalEmitter.h @@ -62,6 +62,17 @@ class EvalEmitter : public SourceMapper { virtual ~EvalEmitter(); + unsigned getOffset(LabelTy L) { return 0; } + unsigned currentCodeSize() const { + llvm_unreachable("Should never be called on EvalEmitter"); + return 0; + } + + void registerExceptionHandler(unsigned From, unsigned To, unsigned Target, + UnsignedOrNone, const Type *T) { + llvm_unreachable("Should never be called on EvalEmitter"); + } + /// Define a label. void emitLabel(LabelTy Label); /// Create a label. diff --git a/clang/lib/AST/ByteCode/Function.h b/clang/lib/AST/ByteCode/Function.h index 289bd64124004..beee1b44944e6 100644 --- a/clang/lib/AST/ByteCode/Function.h +++ b/clang/lib/AST/ByteCode/Function.h @@ -63,6 +63,15 @@ class Scope final { LocalVectorTy Descriptors; }; +struct ExceptionTableEntry { + unsigned CodeStart; + unsigned CodeEnd; + unsigned Target; + UnsignedOrNone DeclOffset; + /// If CatchType is nullptr, this is a catch-all handler. + const Type *CatchType; +}; + using FunctionDeclTy = llvm::PointerUnion<const FunctionDecl *, const BlockExpr *>; @@ -250,6 +259,16 @@ class Function final { return ArgSize - (align(primSize(PT_Ptr)) * (hasThisPointer() + hasRVO())); } + unsigned getParamOffset(unsigned ParamIndex) const { + return ParamDescriptors[ParamIndex].Offset; + } + + PrimType getParamType(unsigned ParamIndex) const { + return ParamDescriptors[ParamIndex].T; + } + + llvm::SmallVector<ExceptionTableEntry> ExceptionTable; + private: /// Construct a function representing an actual function. Function(Program &P, FunctionDeclTy Source, unsigned ArgSize, @@ -259,13 +278,15 @@ class Function final { /// Sets the code of a function. void setCode(FunctionDeclTy Source, unsigned NewFrameSize, llvm::SmallVector<std::byte> &&NewCode, SourceMap &&NewSrcMap, - llvm::SmallVector<Scope, 2> &&NewScopes, bool NewHasBody, - bool NewIsValid) { + llvm::SmallVector<Scope, 2> &&NewScopes, + llvm::SmallVector<ExceptionTableEntry> &&ExceptionTable, + bool NewHasBody, bool NewIsValid) { this->Source = Source; FrameSize = NewFrameSize; Code = std::move(NewCode); SrcMap = std::move(NewSrcMap); Scopes = std::move(NewScopes); + this->ExceptionTable = std::move(ExceptionTable); IsValid = NewIsValid; HasBody = NewHasBody; } diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 4815828adb613..272cfd9484ad0 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -24,6 +24,7 @@ #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/StringExtras.h" +#include <variant> using namespace clang; using namespace clang::interp; @@ -234,6 +235,20 @@ static bool CheckGlobal(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { namespace clang { namespace interp { + +bool diagnoseUncaughtException(InterpState &S, CodePtr OpPC) { + QualType UncaughtType = QualType(S.ThrownValue->Ty, 0); + std::string ValString; + + TYPE_SWITCH(S.ThrownValue->T, { + ValString = + S.ThrownValue->Ptr.deref<T>().toDiagnosticString(S.getASTContext()); + }); + S.FFDiag(S.ThrownValue->ThrowSite, diag::note_constexpr_uncaught_exception) + << UncaughtType << ValString; + return false; +} + PRESERVE_NONE static bool BCP(InterpState &S, CodePtr &RealPC, int32_t Offset, PrimType PT); @@ -852,9 +867,10 @@ bool CheckGlobalLoad(InterpState &S, CodePtr OpPC, const Block *B) { // Similarly, for local loads. bool CheckLocalLoad(InterpState &S, CodePtr OpPC, const Block *B) { assert(!B->isExtern()); - const auto &Desc = *reinterpret_cast<const InlineDescriptor *>(B->rawData()); + const auto &Desc = B->getBlockDesc<const InlineDescriptor>(); if (!CheckLifetime(S, OpPC, Desc.LifeState, B, AK_Read)) return false; + if (!Desc.IsInitialized) return DiagnoseUninitialized(S, OpPC, /*Extern=*/false, B, AK_Read); if (B->getDescriptor()->IsVolatile) { @@ -1353,7 +1369,7 @@ static bool runRecordDestructor(InterpState &S, CodePtr OpPC, return false; S.Stk.push<Pointer>(BasePtr); - return Call(S, OpPC, DtorFunc, 0); + return Call(S, OpPC, OpPC, DtorFunc, 0); } static bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B) { @@ -1863,16 +1879,16 @@ bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func, S.Current = FrameBefore; return false; } -bool Call(InterpState &S, CodePtr OpPC, const Function *Func, +bool Call(InterpState &S, CodePtr &PC, CodePtr OpPC, const Function *Func, uint32_t VarArgSize) { - + // CodePtr OpPC = PC - align(sizeof(uint32_t)) - align(sizeof(void*)); // C doesn't have constexpr functions. if (!S.getLangOpts().CPlusPlus) - return Invalid(S, OpPC); + return Invalid(S, PC); assert(Func); auto cleanup = [&]() -> bool { - cleanupAfterFunctionCall(S, OpPC, Func); + cleanupAfterFunctionCall(S, PC, Func); return false; }; @@ -1941,11 +1957,17 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func, auto Memory = new char[InterpFrame::allocSize(Func)]; auto NewFrame = new (Memory) InterpFrame(S, Func, OpPC, VarArgSize); + // Func->dump(); + + llvm::errs() << ":::::: CALLING " << Func->getName() << '\n'; + InterpFrame *FrameBefore = S.Current; S.Current = NewFrame; + bool NowThrownValueBefore = !S.ThrownValue; InterpStateCCOverride CCOverride(S, Func->isImmediate()); bool Success = Interpret(S); + // Remove initializing block again. if (InstancePtrTracked) S.InitializingPtrs.pop_back(); @@ -1958,7 +1980,145 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func, return false; } - assert(S.Current == FrameBefore); + if (S.ThrownValue && !S.ThrownValue->Caught && NowThrownValueBefore) { + // llvm::errs() << "WE HAVE A THROWN VALUE in CALL\n"; + // If we have a thrown value in the InterpState, it was thrown in the + // function we just called (or deeper down in the stack), but not caught. We + // now need to check if the current function can call it. + const Function *CurrFunction = S.Current->getFunction(); + if (!CurrFunction) { + // llvm::errs() << "BUT NO FUNCTION, SO DIAGNOSING\n"; + assert(S.Current->isBottomFrame()); + if (!S.checkingPotentialConstantExpression()) + return diagnoseUncaughtException(S, OpPC); + return false; + } + auto canCatch = [](const Type *CatchType, const Type *ThrowType) -> bool { + if (!CatchType || ASTContext::hasSameType(CatchType, ThrowType)) + return true; + + assert(CatchType); + + // nullptr_t can be caught by any pointer type. + if (ThrowType->isNullPtrType() && CatchType->isPointerType()) + return true; + + // void* can catch all thown pointer types. + if (ThrowType->isPointerType() && CatchType->isVoidPointerType()) + return true; + + if (CatchType->isPointerOrReferenceType()) + CatchType = CatchType->getPointeeType().getTypePtr(); + if (ThrowType->isPointerOrReferenceType()) + ThrowType = ThrowType->getPointeeType().getTypePtr(); + + if (CatchType->isRecordType() && ThrowType->isRecordType()) { + const CXXRecordDecl *CatchDecl = CatchType->getAsCXXRecordDecl(); + const CXXRecordDecl *ThrowDecl = ThrowType->getAsCXXRecordDecl(); + assert(CatchDecl); + assert(ThrowDecl); + + if (CatchDecl == ThrowDecl) + return true; + if (ThrowDecl->isDerivedFrom(CatchDecl)) + return true; + } + + return false; + }; + + bool Caught = false; + unsigned CodeOffset = PC - CurrFunction->getCodeBegin(); + for (const auto &E : CurrFunction->ExceptionTable) { + if (E.CodeStart <= CodeOffset && E.CodeEnd >= CodeOffset && + (canCatch(E.CatchType, S.ThrownValue->Ty))) { + + const Type *CaughtType = E.CatchType; + const Type *It = S.ThrownValue->Ty; + + llvm::errs() << "Resetting stack to " << S.ThrowTrapStackSize << '\n'; + while (S.Stk.size() != S.ThrowTrapStackSize) { + S.Stk.discardSlow(); + } + + bool NeedsCast = + CaughtType && + ((CaughtType->isRecordType() && It->isRecordType()) || + (CaughtType->isPointerOrReferenceType() && + CaughtType->getPointeeType()->isRecordType() && + It->isPointerOrReferenceType() && + It->getPointeeType()->isRecordType()) + + || (It->isRecordType() && CaughtType->isReferenceType() && + CaughtType->getPointeeType()->isRecordType())); + + llvm::errs() << "NeedsCast: " << NeedsCast << '\n'; + + if (NeedsCast) { + llvm::errs() << "CASTING AFTER CALL\n"; + Pointer ThrownValue = std::get<Pointer>(S.ThrownValue->Value); + Pointer CastedValue = + ThrownValue; // std::get<Pointer>(S.ThrownValue->Value); + llvm::errs() << "ThrownValue: " << ThrownValue << '\n'; + + if (CaughtType->isPointerOrReferenceType()) + CaughtType = CaughtType->getPointeeType().getTypePtr(); + + if (It->isPointerOrReferenceType()) + It = It->getPointeeType().getTypePtr(); + const Record *CaughtRecord = + S.getContext().getRecord(CaughtType->getAsCXXRecordDecl()); + const Record *ItRecord = + S.getContext().getRecord(It->getAsCXXRecordDecl()); + while (ItRecord != CaughtRecord) { + llvm::errs() << It << " / " << CaughtType << '\n'; + const Record *R = + S.getContext().getRecord(It->getAsCXXRecordDecl()); + assert(R); + llvm::errs() << "It record: " << R->getName() << '\n'; + for (const Record::Base &B : R->bases()) { + if (cast<CXXRecordDecl>(ItRecord->getDecl()) + ->isDerivedFrom(cast<CXXRecordDecl>(B.Decl))) { + llvm::errs() << "yay!\n"; + llvm::errs() << "Casted Value: " << CastedValue << '\n'; + CastedValue = CastedValue.atField(B.Offset); + llvm::errs() << "Casted Value: " << CastedValue << '\n'; + It = CastedValue.getType().getTypePtr(); + ItRecord = S.getContext().getRecord(It->getAsCXXRecordDecl()); + // return true; + break; + } + } + } + + S.ThrownValue->Value = CastedValue; + S.ThrownValue->Ptr = CastedValue; + } + + llvm::errs() << "AAAAAAAHA! IN " << CurrFunction->getName() << "\n"; + PC = S.Current->getFunction()->getCodeBegin() + E.Target; + llvm::errs() << "Offset now: " + << (PC - S.Current->getFunction()->getCodeBegin()) << '\n'; + Caught = true; + break; + } + } + + if (!Caught) { + bool IsNoExcept = S.Current->getFunction() + ->getDecl() + ->getType() + ->getAs<FunctionProtoType>() + ->hasNoexceptExceptionSpec(); + + if (IsNoExcept) + S.CCEDiag(S.Current->getSource(OpPC), + diag::note_constexpr_exception_in_noexcept_func); + PC = S.Current->getFunction()->getCodeEnd() - align(sizeof(Opcode)); + } + } + + // assert(S.Current == FrameBefore); return true; } @@ -2259,7 +2419,7 @@ bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func, } } - if (!Call(S, OpPC, Func, VarArgSize)) + if (!Call(S, OpPC, OpPC, Func, VarArgSize)) return false; // Covariant return types. The return type of Overrider is a pointer @@ -2301,7 +2461,7 @@ bool CallBI(InterpState &S, CodePtr OpPC, const CallExpr *CE, return InterpretBuiltin(S, OpPC, CE, BuiltinID); } -bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, +bool CallPtr(InterpState &S, CodePtr &PC, CodePtr OpPC, uint32_t ArgSize, const CallExpr *CE) { const Pointer &Ptr = S.Stk.pop<Pointer>(); @@ -2360,7 +2520,7 @@ bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, if (F->isVirtual()) return CallVirt(S, OpPC, F, VarArgSize); - return Call(S, OpPC, F, VarArgSize); + return Call(S, PC, OpPC, F, VarArgSize); } static void startLifetimeRecurse(PtrView Ptr) { @@ -3120,7 +3280,7 @@ constexpr bool OpReturns(Opcode Op) { Op == OP_RetSint64 || Op == OP_RetUint64 || Op == OP_RetIntAP || Op == OP_RetIntAPS || Op == OP_RetBool || Op == OP_RetFixedPoint || Op == OP_RetPtr || Op == OP_RetMemberPtr || Op == OP_RetFloat || - Op == OP_EndSpeculation; + Op == OP_EndSpeculation || Op == OP_AfterRet; } #if USE_TAILCALLS @@ -3263,5 +3423,157 @@ PRESERVE_NONE static bool BCP(InterpState &S, CodePtr &RealPC, int32_t Offset, return true; } +static bool canCatch(const Type *CatchType, const Type *ThrowType) { + if (!CatchType || ASTContext::hasSameType(CatchType, ThrowType)) + return true; + + assert(CatchType); + + // nullptr_t can be caught by any pointer type. + if (ThrowType->isNullPtrType() && CatchType->isPointerType()) + return true; + + // void* can catch all thown pointer types. + if (ThrowType->isPointerType() && CatchType->isVoidPointerType()) + return true; + + if (CatchType->isPointerType() && !ThrowType->isPointerType()) + return false; + + if (CatchType->isPointerOrReferenceType()) + CatchType = CatchType->getPointeeType().getTypePtr(); + if (ThrowType->isPointerOrReferenceType()) + ThrowType = ThrowType->getPointeeType().getTypePtr(); + + if (CatchType == ThrowType) + return true; + + if (CatchType->isRecordType() && ThrowType->isRecordType()) { + const CXXRecordDecl *CatchDecl = CatchType->getAsCXXRecordDecl(); + const CXXRecordDecl *ThrowDecl = ThrowType->getAsCXXRecordDecl(); + assert(CatchDecl); + assert(ThrowDecl); + + if (CatchDecl == ThrowDecl) + return true; + + if (ThrowDecl->isDerivedFrom(CatchDecl)) + return true; + } + + return false; +} + +bool catchException(InterpState &S, CodePtr OpPC, CodePtr &PC, const Type *Ty) { + assert(S.ThrownValue); + + // Will later be diagnosed in EvalEmitter. + if (S.Current->isBottomFrame()) + return true; + + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + const auto *Source = cast<CXXThrowExpr>(S.Current->getExpr(OpPC)); + const Pointer ThrownValue = S.ThrownValue->Ptr; + S.ThrownValue = std::nullopt; + + const Function *F = S.Current->getFunction(); + unsigned CodeOffset = PC - F->getCodeBegin(); + + bool Caught = false; + const Type *CaughtType = nullptr; + + for (const auto &E : F->ExceptionTable) { + if (E.CodeStart <= CodeOffset && E.CodeEnd >= CodeOffset && + canCatch(E.CatchType, Ty)) { + PC = S.Current->getFunction()->getCodeBegin() + E.Target; + Caught = true; + CaughtType = E.CatchType; + break; + } + } + + // llvm::errs() << S.Stk.size() <<" / " << S.ThrowTrapStackSize << '\n'; + // while (S.Stk.size() != S.ThrowTrapStackSize) { + // S.Stk.discardSlow(); + // } + + const Type *It = ThrownValue.getType().getTypePtr(); + bool IsComposite = !ThrownValue.getFieldDesc()->isPrimitive(); + bool NeedsCast = + CaughtType && + ((CaughtType->isRecordType() && It->isRecordType()) || + (CaughtType->isPointerOrReferenceType() && + CaughtType->getPointeeType()->isRecordType() && + It->isPointerOrReferenceType() && It->getPointeeType()->isRecordType()) + + || (It->isRecordType() && CaughtType->isReferenceType() && + CaughtType->getPointeeType()->isRecordType())); + + if (!NeedsCast) { + if (!IsComposite) { + + TYPE_SWITCH(ThrownValue.getFieldDesc()->getPrimType(), { + S.ThrownValue = + ThrowValue(Ty, Source, ThrownValue.deref<T>(), ThrownValue, + ThrownValue.getFieldDesc()->getPrimType(), false); + }); + } else { + S.ThrownValue = + ThrowValue{Ty, Source, ThrownValue, ThrownValue, PT_Ptr, false}; + } + if (!Caught) { + PC = S.Current->getFunction()->getCodeEnd() - align(sizeof(Opcode)); + } + return true; + } + + if (CaughtType && CaughtType->isPointerOrReferenceType()) + CaughtType = CaughtType->getPointeeType().getTypePtr(); + + // const Type *It = CaughtType; + assert(NeedsCast); + + Pointer CastedValue = ThrownValue; + // llvm::errs() << "Comp: " << IsComp << '\n'; + if (!IsComposite) { + // if (CaughtType && CaughtType->isRecordType() && It->isRecordType()) { + CastedValue = CastedValue.deref<Pointer>(); + // llvm::errs() << "WE ARE CASTING\n"; + It = It->getPointeeType().getTypePtr(); + } + + const Record *CaughtRecord = + S.getContext().getRecord(CaughtType->getAsCXXRecordDecl()); + const Record *ItRecord = S.getContext().getRecord(It->getAsCXXRecordDecl()); + while (ItRecord != CaughtRecord) { + // llvm::errs() << It << " / " << CaughtType << '\n'; + const Record *R = S.getContext().getRecord(It->getAsCXXRecordDecl()); + assert(R); + // llvm::errs() << "It record: " << R->getName() << '\n'; + for (const Record::Base &B : R->bases()) { + if (cast<CXXRecordDecl>(ItRecord->getDecl()) + ->isDerivedFrom(cast<CXXRecordDecl>(B.Decl))) { + // llvm::errs() << "yay!\n"; + // llvm::errs() << "Casted Value: " << CastedValue << '\n'; + CastedValue = CastedValue.atField(B.Offset); + // llvm::errs() << "Casted Value: " << CastedValue << '\n'; + It = CastedValue.getType().getTypePtr(); + ItRecord = S.getContext().getRecord(It->getAsCXXRecordDecl()); + // return true; + break; + } + } + } + S.ThrownValue = + ThrowValue{Ty, Source, CastedValue, CastedValue, PT_Ptr, false}; + if (!Caught) { + PC = S.Current->getFunction()->getCodeEnd() - align(sizeof(Opcode)); + } + + return true; + + return true; +} + } // namespace interp } // namespace clang diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 97d9e7cc14e32..580bc6fe2fef1 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -27,6 +27,7 @@ #include "InterpStack.h" #include "InterpState.h" #include "MemberPointer.h" +#include "Opcode.h" #include "PrimType.h" #include "Program.h" #include "State.h" @@ -85,6 +86,8 @@ bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr, bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern, const Block *B, AccessKinds AK); +bool diagnoseUncaughtException(InterpState &S, CodePtr OpPC); + /// Checks a direct load of a primitive value from a global or local variable. bool CheckGlobalLoad(InterpState &S, CodePtr OpPC, const Block *B); bool CheckLocalLoad(InterpState &S, CodePtr OpPC, const Block *B); @@ -118,13 +121,13 @@ bool SetThreeWayComparisonField(InterpState &S, CodePtr OpPC, bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func, uint32_t VarArgSize); -bool Call(InterpState &S, CodePtr OpPC, const Function *Func, +bool Call(InterpState &S, CodePtr &PC, CodePtr OpPC, const Function *Func, uint32_t VarArgSize); bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func, uint32_t VarArgSize); bool CallBI(InterpState &S, CodePtr OpPC, const CallExpr *CE, uint32_t BuiltinID); -bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, +bool CallPtr(InterpState &S, CodePtr &PC, CodePtr OpPC, uint32_t ArgSize, const CallExpr *CE); bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T); bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index); @@ -147,6 +150,8 @@ bool isConstexprUnknown(const Block *B); bool DynamicCast(InterpState &S, CodePtr OpPC, const Type *DestType, bool IsReferenceCast); +bool catchException(InterpState &S, CodePtr OpPC, CodePtr &PC, const Type *Ty); + enum class ShiftDir { Left, Right }; /// Checks if the shift operation is legal. @@ -305,6 +310,57 @@ PRESERVE_NONE inline bool RetVoid(InterpState &S, CodePtr &PC) { return true; } +inline bool Throw(InterpState &S, CodePtr &PC, const Type *Ty) { + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + CodePtr OpPC; + if (PC) + OpPC = PC - align(sizeof(Opcode)) - align(sizeof(bool)); // XXX + else + OpPC = PC; + + return catchException(S, OpPC, PC, Ty); +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +inline bool SaveException(InterpState &S, CodePtr OpPC, const Type *Ty) { + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + const auto *Source = cast<CXXThrowExpr>(S.Current->getExpr(OpPC)); + S.ThrownValue = ThrowValue(Ty, Source, Ptr, Ptr, Name); + return true; +} + +inline bool ThrowTrap(InterpState &S, CodePtr OpPC) { + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + S.ThrowTrapStackSize = S.Stk.size(); + llvm::errs() << "Trap now: " << S.ThrowTrapStackSize << '\n'; + + return true; +} + +inline bool Catch(InterpState &S, CodePtr OpPC) { + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + assert(S.ThrownValue); + S.ThrownValue->Caught = true; + + return true; +} + +/// Allocate memory to store an exception object. This uses the InterpState +/// allocator, so has the same lifetime as local variables (minus scoping). +inline bool AllocException(InterpState &S, CodePtr &PC, + const Descriptor *Desc) { + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + + assert(Desc); + char *Memory = (char *)S.allocate(sizeof(Block) + Desc->getAllocSize()); + Block *B = new (Memory) Block(~0u, Desc); + B->invokeCtor(); + S.Stk.push<Pointer>(B); + return true; +} + //===----------------------------------------------------------------------===// // Add, Sub, Mul //===----------------------------------------------------------------------===// @@ -2384,7 +2440,8 @@ bool Init(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek<Pointer>(); if (!CheckInit(S, OpPC, Ptr)) return false; - Ptr.initialize(); + if (Ptr.canBeInitialized()) + Ptr.initialize(); new (&Ptr.deref<T>()) T(Value); return true; } @@ -2395,7 +2452,8 @@ bool InitPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop<Pointer>(); if (!CheckInit(S, OpPC, Ptr)) return false; - Ptr.initialize(); + if (Ptr.canBeInitialized()) + Ptr.initialize(); new (&Ptr.deref<T>()) T(Value); return true; } @@ -3416,6 +3474,30 @@ PRESERVE_NONE inline bool NoRet(InterpState &S, CodePtr OpPC) { S.FFDiag(EndLoc, diag::note_constexpr_no_return); return false; } +PRESERVE_NONE inline bool AfterRet(InterpState &S, CodePtr &PC) { + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + + if (S.ThrownValue) { + while (S.Stk.size() != S.Current->getFrameOffset()) + S.Stk.discardSlow(); + } + + assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); + + if (!S.checkingPotentialConstantExpression() || S.Current->Caller) + cleanupAfterFunctionCall(S, PC, S.Current->getFunction()); + + if (InterpFrame *Caller = S.Current->Caller) { + PC = S.Current->getRetPC(); + InterpFrame::free(S.Current); + S.Current = Caller; + } else { + InterpFrame::free(S.Current); + S.Current = nullptr; + } + + return true; +} //===----------------------------------------------------------------------===// // NarrowPtr, ExpandPtr @@ -4100,6 +4182,34 @@ inline bool CheckDestruction(InterpState &S, CodePtr OpPC) { return CheckDestructor(S, OpPC, Ptr); } +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool GetExceptionValue(InterpState &S, CodePtr OpPC) { + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + assert(S.ThrownValue); + llvm::errs() << "-> " << std::get<T>(S.ThrownValue->Value) << '\n'; + S.Stk.push<T>(std::get<T>(S.ThrownValue->Value)); + S.ThrownValue = std::nullopt; + return true; +} + +inline bool GetPtrExceptionValue(InterpState &S, CodePtr OpPC) { + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + assert(S.ThrownValue); + llvm::errs() << "-> " << S.ThrownValue->Ptr << '\n'; + // llvm::errs() << " " << std::get<Pointer>(S.ThrownValue->Value) << '\n';; + S.Stk.push<Pointer>( + S.ThrownValue->Ptr); // std::get<T>(S.ThrownValue->Value)); + S.ThrownValue = std::nullopt; + return true; +} + +inline bool ClearExceptionValue(InterpState &S, CodePtr OpPC) { + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + // assert(S.ThrownValue); + S.ThrownValue = std::nullopt; + return true; +} + //===----------------------------------------------------------------------===// // Read opcode arguments //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 73952e032f1eb..93a8448311561 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -4551,6 +4551,33 @@ static bool interp__builtin_ia32_vpdp(InterpState &S, CodePtr OpPC, return true; } +static bool interp__builtin_current_exception(InterpState &S, CodePtr OpPC, + const CallExpr *Call) { + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + if (!S.ThrownValue) { + S.Stk.push<Pointer>(); + return true; + } + + S.Stk.push<Pointer>(S.ThrownValue->Ptr); + return true; +} + +static bool interp__builtin_uncaught_exceptions(InterpState &S, CodePtr OpPC, + const CallExpr *Call) { + // llvm::errs() << "CC: " << S.inConstantContext() << '\n'; + if (!S.inConstantContext()) + return false; + + llvm::errs() << __PRETTY_FUNCTION__ << '\n'; + llvm::errs() << "ThrownValue: " << (bool)S.ThrownValue << '\n'; + if (S.ThrownValue && !S.ThrownValue->Caught) + pushInteger(S, 1, Call->getType()); + else + pushInteger(S, 0, Call->getType()); + return true; +} + bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, uint32_t BuiltinID) { if (!S.getASTContext().BuiltinInfo.isConstantEvaluated(BuiltinID)) @@ -6633,6 +6660,11 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, case X86::BI__builtin_ia32_vpdpbusds256: case X86::BI__builtin_ia32_vpdpbusds512: return interp__builtin_ia32_vpdp(S, OpPC, Call, true); + + case Builtin::BI__builtin_current_exception: + return interp__builtin_current_exception(S, OpPC, Call); + case Builtin::BI__builtin_uncaught_exceptions: + return interp__builtin_uncaught_exceptions(S, OpPC, Call); default: S.FFDiag(S.Current->getLocation(OpPC), diag::note_invalid_subexpr_in_const_expr) diff --git a/clang/lib/AST/ByteCode/InterpFrame.h b/clang/lib/AST/ByteCode/InterpFrame.h index 731ffddc5a68d..2257ff660cba4 100644 --- a/clang/lib/AST/ByteCode/InterpFrame.h +++ b/clang/lib/AST/ByteCode/InterpFrame.h @@ -104,6 +104,7 @@ class InterpFrame final : public Frame { /// Mutates a local variable. template <typename T> void setLocal(unsigned Offset, const T &Value) { + // assert(localInlineDesc(Offset)->Desc->isPrimitive()); localRef<T>(Offset) = Value; localInlineDesc(Offset)->IsInitialized = true; localInlineDesc(Offset)->LifeState = Lifetime::Started; diff --git a/clang/lib/AST/ByteCode/InterpStack.cpp b/clang/lib/AST/ByteCode/InterpStack.cpp index 461bc35979247..55cf9f5cb7947 100644 --- a/clang/lib/AST/ByteCode/InterpStack.cpp +++ b/clang/lib/AST/ByteCode/InterpStack.cpp @@ -95,6 +95,12 @@ void InterpStack::shrink(size_t Size) { StackSize -= Size; } +void InterpStack::discardSlow() { + assert(!empty()); + + TYPE_SWITCH(ItemTypes.back(), { discard<T>(); }); +} + void InterpStack::dump() const { llvm::errs() << "Items: " << ItemTypes.size() << ". Size: " << size() << '\n'; if (ItemTypes.empty()) diff --git a/clang/lib/AST/ByteCode/InterpStack.h b/clang/lib/AST/ByteCode/InterpStack.h index 6d58f5a24bd77..a353505ad609b 100644 --- a/clang/lib/AST/ByteCode/InterpStack.h +++ b/clang/lib/AST/ByteCode/InterpStack.h @@ -58,6 +58,8 @@ class InterpStack final { shrink(aligned_size<T>()); } + void discardSlow(); + /// Returns a reference to the value on the top of the stack. template <typename T> T &peek() const { assert(!ItemTypes.empty()); diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h index 4e4a053d6bbed..d5b723dbf9e99 100644 --- a/clang/lib/AST/ByteCode/InterpState.h +++ b/clang/lib/AST/ByteCode/InterpState.h @@ -13,15 +13,22 @@ #ifndef LLVM_CLANG_AST_INTERP_INTERPSTATE_H #define LLVM_CLANG_AST_INTERP_INTERPSTATE_H +#include "Boolean.h" +#include "Char.h" #include "Context.h" #include "DynamicAllocator.h" #include "Floating.h" #include "Function.h" +#include "Integral.h" +#include "IntegralAP.h" #include "InterpFrame.h" #include "InterpStack.h" +#include "MemberPointer.h" +#include "Pointer.h" #include "State.h" namespace clang { +class CXXThrowExpr; namespace interp { class Context; class SourceMapper; @@ -39,6 +46,20 @@ enum class EvaluationKind : uint8_t { Dtor, /// We're checking for constant destruction of a global variable. }; +struct ThrowValue { + const Type *Ty; + const CXXThrowExpr *ThrowSite; + AnyPrimType Value; + Pointer Ptr; + PrimType T; + bool Caught; + explicit ThrowValue(const Type *Ty, const CXXThrowExpr *ThrowSite, + AnyPrimType Value, Pointer Ptr, PrimType T, + bool Caught = false) + : Ty(Ty), ThrowSite(ThrowSite), Value(Value), Ptr(Ptr), T(T), + Caught(Caught) {} +}; + /// Interpreter context. class InterpState final : public State, public SourceMapper { public: @@ -177,6 +198,7 @@ class InterpState final : public State, public SourceMapper { mutable std::optional<llvm::BumpPtrAllocator> Allocator; public: + size_t ThrowTrapStackSize = 0; /// Reference to the module containing all bytecode. Program &P; /// Temporary stack. @@ -200,6 +222,7 @@ class InterpState final : public State, public SourceMapper { const unsigned EvalID; EvaluationKind EvalKind = EvaluationKind::None; + std::optional<ThrowValue> ThrownValue = std::nullopt; /// Things needed to do speculative execution. SmallVectorImpl<PartialDiagnosticAt> *PrevDiags = nullptr; diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index b6190554178f3..1b2998f71af43 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -142,6 +142,7 @@ class Opcode { string Name = ""; bit CanReturn = 0; bit ChangesPC = 0; + bit BothPCs = 0; bit HasCustomLink = 0; bit HasCustomEval = 0; bit HasGroup = 0; @@ -227,10 +228,43 @@ def RetValue : Opcode { def NoRet : Opcode {} +/// Exceptions +def AfterRet : Opcode { + let CanReturn = 1; +} + +def Throw : Opcode { + let Args = [ArgTypePtr]; + let ChangesPC = 1; +} + +def SaveException : Opcode { + let Types = [AllTypeClass]; + let Args = [ArgTypePtr]; + let HasGroup = 1; +} + +def GetExceptionValue : Opcode { + let Types = [AllTypeClass]; + let HasGroup = 1; +} + +def GetPtrExceptionValue : Opcode; +def ClearExceptionValue : Opcode; + +def AllocException : Opcode { + let Args = [ArgDesc]; +} + +def ThrowTrap : Opcode; +def Catch : Opcode; + + def Call : Opcode { let Args = [ArgFunction, ArgUint32]; + let ChangesPC = 1; + let BothPCs = 1; } - def CallVirt : Opcode { let Args = [ArgFunction, ArgUint32]; } @@ -239,6 +273,8 @@ def CallBI : Opcode { let Args = [ArgCallExpr, ArgUint32]; } def CallPtr : Opcode { let Args = [ArgUint32, ArgCallExpr]; + let ChangesPC = 1; + let BothPCs = 1; } def CallVar : Opcode { diff --git a/clang/lib/AST/ByteCode/PrimType.h b/clang/lib/AST/ByteCode/PrimType.h index 8f725942fedb9..a3cd1ffd2d7f3 100644 --- a/clang/lib/AST/ByteCode/PrimType.h +++ b/clang/lib/AST/ByteCode/PrimType.h @@ -17,6 +17,7 @@ #include <climits> #include <cstddef> #include <cstdint> +#include <variant> namespace clang { namespace interp { @@ -49,6 +50,14 @@ enum PrimType : uint8_t { PT_MemberPtr = 14, }; +// Alias for using any one of our primitive types. +using AnyPrimType = + std::variant<Char<true>, Char<false>, Integral<16, true>, + Integral<16, false>, Integral<32, true>, Integral<32, false>, + Integral<64, true>, Integral<64, false>, IntegralAP<true>, + IntegralAP<false>, Boolean, FixedPoint, Floating, Pointer, + MemberPointer>; + constexpr bool isIntegerOrBoolType(PrimType T) { return T <= PT_Bool; } constexpr bool isIntegerType(PrimType T) { return T <= PT_IntAPS; } diff --git a/clang/lib/AST/ByteCode/Source.h b/clang/lib/AST/ByteCode/Source.h index 56ca197e66473..464c1c8bc9811 100644 --- a/clang/lib/AST/ByteCode/Source.h +++ b/clang/lib/AST/ByteCode/Source.h @@ -36,6 +36,8 @@ class CodePtr final { return *this; } + CodePtr operator+(int32_t Offset) { return CodePtr(Ptr + Offset); } + int32_t operator-(const CodePtr &RHS) const { assert(Ptr != nullptr && RHS.Ptr != nullptr && "Invalid code pointer"); return Ptr - RHS.Ptr; diff --git a/clang/test/AST/ByteCode/cxx20.cpp b/clang/test/AST/ByteCode/cxx20.cpp index b7ca154a0a987..8f9ec92e82cd6 100644 --- a/clang/test/AST/ByteCode/cxx20.cpp +++ b/clang/test/AST/ByteCode/cxx20.cpp @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,expected -fcxx-exceptions %s -DNEW_INTERP -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,ref -fcxx-exceptions %s +// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,expected %s -DNEW_INTERP -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,ref %s int x; @@ -673,7 +673,7 @@ namespace ConstexprArrayInitLoopExprDestructors struct Highlander { int *p = 0; constexpr Highlander() {} - constexpr void set(int *p) { this->p = p; ++*p; if (*p != 1) throw "there can be only one"; } + constexpr void set(int *p) { this->p = p; ++*p; if (*p != 1) __builtin_abort(); } constexpr ~Highlander() { --*p; } }; @@ -776,7 +776,7 @@ namespace FailingDestructor { constexpr ~D() { if (!can_destroy) - throw "oh no"; + __builtin_abort(); } }; template<D d> @@ -1062,7 +1062,7 @@ namespace OnePastEndDtor { namespace Virtual { struct NonZeroOffset { int padding = 123; }; - constexpr void assert(bool b) { if (!b) throw 0; } + constexpr void assert(bool b) { if (!b) __builtin_abort(); } // Ensure that we pick the right final overrider during construction. struct A { @@ -1191,9 +1191,8 @@ namespace DiscardedTrivialCXXConstructExpr { int x; }; - constexpr int foo(int x) { // ref-error {{never produces a constant expression}} - throw S(3); // both-note {{not valid in a constant expression}} \ - // ref-note {{not valid in a constant expression}} + constexpr int foo(int x) { // both-error {{never produces a constant expression}} + __builtin_abort(); // both-note 2{{not valid in a constant expression}} return 1; } diff --git a/clang/test/AST/ByteCode/cxx23.cpp b/clang/test/AST/ByteCode/cxx23.cpp index ba7db75a589a1..33bc905840e79 100644 --- a/clang/test/AST/ByteCode/cxx23.cpp +++ b/clang/test/AST/ByteCode/cxx23.cpp @@ -129,8 +129,8 @@ namespace StaticOperators { struct S1 { constexpr S1() { // all20-error {{never produces a constant expression}} - throw; // all-note {{not valid in a constant expression}} \ - // all20-note {{not valid in a constant expression}} + __builtin_abort(); // all-note {{not valid in a constant expression}} \ + // all20-note {{not valid in a constant expression}} } static constexpr int operator()() { return 3; } // ref20-warning {{C++23 extension}} \ // expected20-warning {{C++23 extension}} diff --git a/clang/test/AST/ByteCode/exceptions.cpp b/clang/test/AST/ByteCode/exceptions.cpp new file mode 100644 index 0000000000000..aff112384e005 --- /dev/null +++ b/clang/test/AST/ByteCode/exceptions.cpp @@ -0,0 +1,839 @@ +// RUN: %clang_cc1 -fcxx-exceptions -std=c++26 -fexperimental-new-constant-interpreter -verify %s + +namespace std { + class exception { + public: + constexpr exception() noexcept {}; + // constexpr exception(const exception&) noexcept; + // constexpr exception& operator=(const exception&) noexcept; + constexpr virtual ~exception() {}; + // constexpr virtual const char* what() const noexcept; + }; + + template <typename T> struct remove_reference { using type = T; }; + template <typename T> struct remove_reference<T &> { using type = T; }; + template <typename T> struct remove_reference<T &&> { using type = T; }; + template <typename T> + constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept { + return static_cast<typename std::remove_reference<T>::type &&>(t); + } +}; + + +class Bad : std::exception {}; + +namespace Simple { + constexpr int a() { + try { + } catch(int e){ + return 12; + } + return -2; + } + static_assert(a() == -2); + + constexpr int b() { + try { + throw 12; + } catch(int e){ + return 12; + } + return -2; + } + static_assert(b() == 12); + + constexpr int c() { + int m = 12; + try { + throw 12; + } catch(int e){ + m = 140; + } + return m; + } + static_assert(c() == 140); + + constexpr int d() { + int m = 12; + try { + throw 12; + } catch(int e){ + m = 140; + } catch (float f) { + m = 15; + } + return m; + } + static_assert(d() == 140); + + constexpr int e() { + int m = 12; + try { + throw 12; + } catch(int e){ + m = e + 2; + } + return m; + } + static_assert(e() == 14); + + constexpr int f() { + int m = 12; + try { + throw 12; + } catch(...){ + m = 100; + } + return m; + } + static_assert(f() == 100); + + constexpr int g() { + int m = 12; + try { + throw Bad(); + } catch(Bad &B){ + m = 100; + } + return m; + } + static_assert(g() == 100); + + constexpr int h() { + int m = 12; + try { + throw ++m; + } catch(...){ + } + return m; + } + static_assert(h() == 13); + + constexpr int i(bool b) { + try { + if (b) + throw 12; + else + throw 14.0f; + } catch (int) { + return 100; + } catch (float) { + return 200; + } + return 0; + } + static_assert(i(true) == 100); + static_assert(i(false) == 200); +} + +namespace Uncaught { + + constexpr int a() { + throw 12; // expected-note {{uncaught exception of type 'int': '12'}} + return 0; + } + static_assert(a() == 13); // expected-error {{not an integral constant expression}} +} + +namespace NoFrame { + static_assert((1, throw 2, 3) == 1); // expected-error {{not an integral constant expression}} \ + // expected-note {{uncaught exception of type 'int': '2'}} \ + // expected-warning {{left operand of comma operator has no effect}} + +} + +namespace CleanupAfterThrowingCall { + constexpr int a() { + throw 12; + return -12; + } + constexpr int test() { + try { + a(); + } catch (int i) { + return 26; + } + + return 120; + } + static_assert(test() == 26); + + constexpr int b2() { + throw 1.0; + return 1; + } + constexpr int a2() { + b2(); + throw 12; + return -12; + } + constexpr int test2() { + + try { + a2(); + } catch (int i) { + return 26; + } catch (double d ){ + return (int)(d * 2); + } + + return 120; + } + static_assert(test2() == 2); +} + +namespace Dtors { + class Inc { + public: + int &m; + constexpr Inc(int &m) : m(m) {} + constexpr ~Inc() { ++m; } + }; + + constexpr int test1() { + int m = 10; + Inc _(m); + + try { + throw 12; + } catch (int) { + return m; + } + } + static_assert(test1() == 10); + + constexpr int test2() { + int m = 10; + try { + Inc _(m); + throw 12; + } catch (int) { + } + return m; + } + static_assert(test2() == 11); + + struct checker { + int & counter; + constexpr ~checker() { + ++counter; + } + }; + + constexpr int test() { + int counter = 0; + { + try { + auto c1 = checker{counter}; + throw 42; + } catch (...) { + return counter * 7; + } + } + return counter * 3; + } + + constexpr int destruction_counter = test(); + static_assert(destruction_counter == 7); +} + +namespace CatchArray { + template <typename T> consteval T test(T head, auto... tail) { + const T array[] = {head, tail...}; + try { + throw array; + } catch (const T (&arr)[5]) { + return -2; + } catch (const T * ptr) { + return *ptr; + } catch (...) { + return -1; + } + } + + constexpr auto r0 = test(1,2,3,4,5,6); + static_assert(r0 == 1); + + constexpr auto r1 = test(1,2,3,4,5); + static_assert(r1 == 1); + + constexpr auto r2 = test(1,2,3,4); + static_assert(r2 == 1); + + constexpr auto r3 = test(7,1,2,3,4,5,6); + static_assert(r3 == 7); + + constexpr auto r4 = test(8,1,2,3,4,5); + static_assert(r4 == 8); + + constexpr auto r5 = test(9,1,2,3,4); + static_assert(r5 == 9); +} + +namespace CatchVoidPtr { + consteval int test() { + int p = 3; + try { + throw &p; + } catch (void * ptr) { + return *static_cast<int *>(ptr); + } + } + + static_assert(test() == 3); +} + +namespace Nullptr { + consteval int test_nullptr() { + try { + throw nullptr; + } catch (const int * ex) { + return true; + } catch (...) { + return false; + } + } + static_assert(test_nullptr()); + + consteval int test_zero() { + try { + throw 0; + } catch (const int * ex) { + return false; + } catch (...) { + return true; + } + } + static_assert(test_zero()); +} + +namespace CatchAll { + template <typename T, typename... Args> consteval int test(Args && ... args) { + try { + throw T{args...}; + } catch (unsigned v) { + return static_cast<int>(v) * 2; + } catch (int v) { + return v * 3; + } catch (bool v) { + return static_cast<int>(v) * 5; + } catch (...) { + return -1; + } + return 0; + } + + static_assert(test<unsigned>(42u) == 84); + static_assert(test<int>(13) == 39); + static_assert(test<bool>(true) == 5); + static_assert(test<long>(42) == -1); +} + +namespace Copy { + class Child {}; + constexpr int test() { + + try { + throw Child{}; + } catch (Child C) { + return 20; + } + return 30; + } + static_assert(test() == 20); + + constexpr int a(){ + throw Child{}; + } + constexpr int test2() { + + try { + a(); + } catch (Child C) { + return 20; + } + return 30; + } + static_assert(test2() == 20); +} + +namespace Inheritance { + class Parent2 { + public: + int F = 5; + constexpr int getFive() { return F; } + }; + class Parent : public Parent2{ + }; + class Child : public Parent {}; + + constexpr int foo() { + try { + throw Child{}; + } catch (Parent2 P) { + return P.getFive() + 9; + } + return 0; + } + static_assert(foo() == 14); + + constexpr int foo2() { + Child C{}; + try { + throw &C; + } catch (Parent2 *P) { + return P->getFive() + 12; + } + return 0; + } + static_assert(foo2() == 17); + + constexpr int a() { + throw Child{}; + }; + constexpr int foo3() { + try { + a(); + } catch (Parent2 P) { + return P.getFive(); + } + return 0; + } + static_assert(foo3() == 5); + + constexpr int b(Child *C) { + throw C; + }; + + constexpr int foo4() { + Child C{}; + try { + b(&C); + } catch (Parent *P) { + return P->getFive(); + } + return 0; + } + static_assert(foo4() == 5); +} + +namespace Pointer { + static constexpr auto via_const_catch = 2; + static constexpr auto via_childs_get_value = 3; + static constexpr auto via_special_child_catch = 5; + + struct parent { + int value; + explicit constexpr parent(int v) noexcept: value{v} { } + constexpr virtual int get_value() const noexcept { + return value; + } + constexpr virtual ~parent() = default; + }; + + struct modifying_child: parent { + explicit constexpr modifying_child(int v) noexcept: parent{v} { } + constexpr int get_value() const noexcept override { + return value * via_childs_get_value; + } + }; + + struct ordinary_child: parent { + explicit constexpr ordinary_child(int v) noexcept: parent{v} { } + }; + + struct special_child: parent { + explicit constexpr special_child(int v) noexcept: parent{v} { } + }; + + consteval int test(void (*fnc)()) { + int result = 0; + try { + fnc(); + } catch (special_child * sch) { + result = sch->get_value() * via_special_child_catch; + delete sch; + } catch (const special_child * sch) { + result = sch->get_value() * via_special_child_catch * via_const_catch; + delete sch; + } catch (parent * exc) { + result = exc->get_value(); + delete exc; + } catch (const parent * exc) { + result = exc->get_value() * via_const_catch; + delete exc; + } + return result; + } + + constexpr auto r1 = test([] { throw new parent{1}; }); + static_assert(r1 == 1); + + constexpr auto r2 = test([] { throw new modifying_child{3}; }); + static_assert(r2 == 3 * via_childs_get_value); + + constexpr auto r3 = test([] { throw new ordinary_child{5}; }); + static_assert(r3 == 5); + + constexpr auto r4 = test([] { throw new special_child{17}; }); + static_assert(r4 == 17 * via_special_child_catch); +} + +namespace References1 { + class Parent { + public: + int F = 10; + constexpr int getTen() { return F; } + }; + + class Child : public Parent { + public: + int F = 5; + constexpr int getFive() { return F; } + + }; + + constexpr int foo() { + try { + throw Child{}; + } catch (Child &C) { + return C.getFive(); + } + return 0; + } + static_assert(foo() == 5); + + constexpr int nested() { + throw Child{}; + return 1; + }; + constexpr int foo2() { + try { + nested(); + } catch (Child &C) { + return C.getFive(); + } + return 0; + } + static_assert(foo2() == 5); + + constexpr int foo3() { + try { + throw Child{}; + } catch (Parent &P) { + return P.getTen(); + } + return 0; + } + static_assert(foo3() == 10); + + constexpr int foo4() { + try { + nested(); + } catch (Parent &P) { + return P.getTen(); + } + return 0; + } + static_assert(foo4() == 10); + + constexpr int foo5() { + try { + throw 13; + } catch (const int &a) { + return 25; + } + return -1; + } + static_assert(foo5() == 25); + + consteval bool reference_test(const int & ref) { + try { + throw ref; + } catch (const int & exc_ref) { + if (exc_ref != ref) { + return 3; + } else if (&exc_ref != &ref) { + return 2; + } + return 1; + } + } + static_assert(reference_test(10) == 1); + + consteval bool copy_test(const int & ref) { + try { + throw ref; + } catch (const int exc_ref) { + if (exc_ref != ref) { + return 3; + } else if (&exc_ref == &ref) { + return 2; + } + return 1; + } + } + static_assert(copy_test(10) == 1); + + consteval bool conversion_test(const int & ref) { + try { + throw ref; + } catch (const long exc_ref) { + if (exc_ref != ref) { + return 3; + } else if (static_cast<const void *>(&exc_ref) == static_cast<const void *>(&ref)) { + return 2; + } + return 4; + } catch (int) { + return 1; + } + } + static_assert(conversion_test(10) == 1); + + + + +} + +namespace References2 { + static constexpr auto via_const_catch = 2; + static constexpr auto via_childs_get_value = 3; + static constexpr auto via_special_child_catch = 5; + + struct parent { + int value; + explicit constexpr parent(int v) noexcept: value{v} { } + constexpr virtual int get_value() const noexcept { + return value; + } + constexpr virtual ~parent() = default; + }; + + struct modifying_child: parent { + explicit constexpr modifying_child(int v) noexcept: parent{v} { } + constexpr int get_value() const noexcept override { + return value * via_childs_get_value; + } + }; + + struct ordinary_child: parent { + explicit constexpr ordinary_child(int v) noexcept: parent{v} { } + }; + + struct special_child: parent { + explicit constexpr special_child(int v) noexcept: parent{v} { } + }; + + consteval int test(void (*fnc)()) { + int result = 0; + try { + fnc(); + } catch (special_child & sch) { + result = sch.get_value() * via_special_child_catch; + } catch (const special_child & sch) { + result = sch.get_value() * via_special_child_catch * via_const_catch; + } catch (parent & exc) { + result = exc.get_value(); + } catch (const parent & exc) { + result = exc.get_value() * via_const_catch; + } + return result; + } + + constexpr auto r1 = test([] { throw parent{1}; }); + static_assert(r1 == 1); + + constexpr auto r2 = test([] { throw modifying_child{3}; }); + static_assert(r2 == 3 * via_childs_get_value); + + constexpr auto r3 = test([] { throw ordinary_child{5}; }); + static_assert(r3 == 5); + + constexpr auto r4 = test([] { throw special_child{17}; }); + static_assert(r4 == 17 * via_special_child_catch); +} + +namespace Comma { + constexpr int catch_it() { + try { + return (1, throw 2, 3); // expected-warning {{left operand of comma operator has no effect}} + } catch (int v) { + return v; + } + } + static_assert(catch_it() == 2); +} + +namespace Lifetime { + [[noreturn]] constexpr auto create(int v) -> int { + throw v; // expected-note {{uncaught exception of type 'int': '42'}} \ + // expected-note {{uncaught exception of type 'int': '56'}} \ + // expected-note {{uncaught exception of type 'int': '32'}} \ + // expected-note {{uncaught exception of type 'int': '4'}} \ + // expected-note {{uncaught exception of type 'int': '1'}} + } + + constexpr int convert(int x) { + return x; + } + + constexpr auto value1 = convert(create(42)); // expected-error {{must be initialized by a constant expression}} + + + struct wrapper { + int value; + }; + + constexpr auto value2 = wrapper{create(56)}; // expected-error {{must be initialized by a constant expression}} + constexpr auto value3 = wrapper(create(32)); // expected-error {{must be initialized by a constant expression}} + constexpr auto value4 = (1,2,3,create(4)); // expected-error {{must be initialized by a constant expression}} + + constexpr int fnc() { + const auto v = create(1); + return v; + } + + constexpr auto value5 = fnc(); // expected-error {{must be initialized by a constant expression}} +} + +namespace TheWorst { + constexpr int zomg() try { + throw 12; + } catch (int i) { + return 13; + } + + constexpr int c() { + return zomg(); + } + static_assert(c() == 13); + + + struct hanaxception { + int v; + }; + + struct checker { + int value; + constexpr checker(int v) try : value{v} { + if (v > 10) { + throw hanaxception{v}; + } + } catch (const hanaxception h) { + } + }; + + constexpr int test() { + auto c = checker{11}; + return 42; + } + constexpr int constructor_test = test(); + static_assert(constructor_test == 42); +} + +namespace Destructors { + struct F { + constexpr ~F() noexcept(false){ + throw 42; // expected-note {{uncaught exception of type 'int': '42'}} + } + }; + constexpr int test() { + try { + F f; + return 1337; + } catch (int i) { + return i; + } + + return 12; + } + static_assert(test() == 42); + + constexpr int test2() { + F f; + return 1337; + } + static_assert(test2() == 42); // expected-error {{not an integral constant expression}} + +} + +namespace Noexcept { + constexpr void throw_exception_here() noexcept(false) { + throw 42; + } + + constexpr int test() noexcept { + throw_exception_here(); // expected-note {{uncaught exception in noexcept function}} + return 42; + } + constexpr int value = test(); // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{in call to}} +} + +namespace Move { + struct foo { + int value; + constexpr foo(int v): value{v} {} + constexpr foo(foo && other): value{other.value + 1} {} + constexpr int get() const { + return value; + } + }; + + consteval int testMove() { + try { + throw std::move(foo{1}); + } catch (const foo & f) { + return f.get(); + } + return 8; + } + static_assert(testMove() == 2); +} + +namespace UncaughtExceptions { + constexpr int foo() { + try { + throw 42; + } catch (...) { + return __builtin_uncaught_exceptions(); + } + return -1; + } + static_assert(foo() == 0); + + struct F{ + constexpr ~F() { + if (__builtin_uncaught_exceptions() != 0) { + __builtin_abort(); // expected-note {{subexpression not valid in a constant expression}} + } + } + }; + constexpr int foo2() { + + try { + F f; // expected-note {{in call to}} + throw 42; + } catch (...) { + return 0; + } + + return -1; + } + static_assert(foo2() == 0); // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to}} + + constexpr int foo3() { + try { + throw 42; + } catch (int) { + return __builtin_uncaught_exceptions(); + } + return -1; + } + static_assert(foo3() == 0); + + + +} diff --git a/clang/test/AST/ByteCode/invalid.cpp b/clang/test/AST/ByteCode/invalid.cpp index e79b698a32719..f70f843569c03 100644 --- a/clang/test/AST/ByteCode/invalid.cpp +++ b/clang/test/AST/ByteCode/invalid.cpp @@ -1,31 +1,5 @@ -// RUN: %clang_cc1 -triple x86_64 -fcxx-exceptions -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s -// RUN: %clang_cc1 -triple x86_64 -fcxx-exceptions -std=c++20 -verify=ref,both %s - -namespace Throw { - - constexpr int ConditionalThrow(bool t) { - if (t) - throw 4; // both-note {{subexpression not valid in a constant expression}} - - return 0; - } - - static_assert(ConditionalThrow(false) == 0, ""); - static_assert(ConditionalThrow(true) == 0, ""); // both-error {{not an integral constant expression}} \ - // both-note {{in call to 'ConditionalThrow(true)'}} - - constexpr int Throw() { // both-error {{never produces a constant expression}} - throw 5; // both-note {{subexpression not valid in a constant expression}} - return 0; - } - - constexpr int NoSubExpr() { // both-error {{never produces a constant expression}} - throw; // both-note 2{{subexpression not valid}} - return 0; - } - static_assert(NoSubExpr() == 0, ""); // both-error {{not an integral constant expression}} \ - // both-note {{in call to}} -} +// RUN: %clang_cc1 -triple x86_64 -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s +// RUN: %clang_cc1 -triple x86_64 -std=c++20 -verify=ref,both %s namespace Asm { constexpr int ConditionalAsm(bool t) { diff --git a/clang/utils/TableGen/ClangOpcodesEmitter.cpp b/clang/utils/TableGen/ClangOpcodesEmitter.cpp index 154969cf49b04..c1c5ff067cf6d 100644 --- a/clang/utils/TableGen/ClangOpcodesEmitter.cpp +++ b/clang/utils/TableGen/ClangOpcodesEmitter.cpp @@ -128,6 +128,7 @@ void ClangOpcodesEmitter::EmitInterpFnDispatchers(raw_ostream &OS, StringRef N, const auto &Args = R->getValueAsListOfDefs("Args"); bool ChangesPC = R->getValueAsBit("ChangesPC"); bool CanFail = R->getValueAsBit("CanFail"); + bool BothPCs = R->getValueAsBit("BothPCs"); if (Args.empty()) { if (CanReturn) { @@ -162,7 +163,7 @@ void ClangOpcodesEmitter::EmitInterpFnDispatchers(raw_ostream &OS, StringRef N, OS << " {\n"; - if (!ChangesPC) + if (!ChangesPC || BothPCs) OS << " CodePtr OpPC = PC;\n"; // Emit calls to read arguments. @@ -185,9 +186,11 @@ void ClangOpcodesEmitter::EmitInterpFnDispatchers(raw_ostream &OS, StringRef N, OS << N; PrintTypes(OS, TS); OS << "(S"; - if (ChangesPC) + if (ChangesPC) { OS << ", PC"; - else + if (BothPCs) + OS << ", OpPC"; + } else OS << ", OpPC"; for (size_t I = 0, N = Args.size(); I < N; ++I) OS << ", V" << I; @@ -425,6 +428,7 @@ void ClangOpcodesEmitter::EmitEval(raw_ostream &OS, StringRef N, OS << (AsRef ? "const " : " ") << Name << " " << (AsRef ? "&" : "") << "A" << I << ", "; } + bool BothPCs = R->getValueAsBit("BothPCs"); OS << "SourceInfo L) {\n"; OS << " if (!isActive()) return true;\n"; OS << " CurrentSource = L;\n"; @@ -432,6 +436,8 @@ void ClangOpcodesEmitter::EmitEval(raw_ostream &OS, StringRef N, OS << " return " << N; PrintTypes(OS, TS); OS << "(S, OpPC"; + if (BothPCs) + OS << ", OpPC"; for (size_t I = 0, N = Args.size(); I < N; ++I) OS << ", A" << I; OS << ");\n"; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
