diff --git a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp index 4f33670a8500a..6618341296aaf 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp @@ -237,6 +237,12 @@ static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1, return false; return true; } + case Stmt::DeferStmtClass: { + const auto *DefStmt1 = cast(Stmt1); + const auto *DefStmt2 = cast(Stmt2); + return isIdenticalStmt(Ctx, DefStmt1->getBody(), DefStmt2->getBody(), + IgnoreSideEffects); + } case Stmt::CompoundStmtClass: { const auto *CompStmt1 = cast(Stmt1); const auto *CompStmt2 = cast(Stmt2); diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 1cd465e25947a..51f61097ceb47 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -208,6 +208,11 @@ Resolutions to C++ Defect Reports C Language Changes ------------------ +- Implemented the ``defer`` draft Technical Specification + (`WG14 N3734 `_); it is enabled in C mode by + passing ``-fdefer-ts``. Note, the details of this feature are subject to change given that the Technical + Specification is not yet ratified. + C2y Feature Support ^^^^^^^^^^^^^^^^^^^ - No longer triggering ``-Wstatic-in-inline`` in C2y mode; use of a static diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index 8f427427d71ed..c3ac310bf5402 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2561,6 +2561,7 @@ DEF_TRAVERSE_STMT(DefaultStmt, {}) DEF_TRAVERSE_STMT(DoStmt, {}) DEF_TRAVERSE_STMT(ForStmt, {}) DEF_TRAVERSE_STMT(GotoStmt, {}) +DEF_TRAVERSE_STMT(DeferStmt, {}) DEF_TRAVERSE_STMT(IfStmt, {}) DEF_TRAVERSE_STMT(IndirectGotoStmt, {}) DEF_TRAVERSE_STMT(LabelStmt, {}) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index e1cca34d2212c..d56de08eaf279 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -317,6 +317,16 @@ class alignas(void *) Stmt { SourceLocation KeywordLoc; }; + class DeferStmtBitfields { + friend class DeferStmt; + + LLVM_PREFERRED_TYPE(StmtBitfields) + unsigned : NumStmtBits; + + /// The location of the "defer". + SourceLocation DeferLoc; + }; + //===--- Expression bitfields classes ---===// class ExprBitfields { @@ -1318,6 +1328,7 @@ class alignas(void *) Stmt { LoopControlStmtBitfields LoopControlStmtBits; ReturnStmtBitfields ReturnStmtBits; SwitchCaseBitfields SwitchCaseBits; + DeferStmtBitfields DeferStmtBits; // Expressions ExprBitfields ExprBits; @@ -3211,6 +3222,47 @@ class ReturnStmt final } }; +/// DeferStmt - This represents a deferred statement. +class DeferStmt : public Stmt { + friend class ASTStmtReader; + + /// The deferred statement. + Stmt *Body; + + DeferStmt(EmptyShell Empty); + DeferStmt(SourceLocation DeferLoc, Stmt *Body); + +public: + static DeferStmt *CreateEmpty(ASTContext &Context, EmptyShell Empty); + static DeferStmt *Create(ASTContext &Context, SourceLocation DeferLoc, + Stmt *Body); + + SourceLocation getDeferLoc() const { return DeferStmtBits.DeferLoc; } + void setDeferLoc(SourceLocation DeferLoc) { + DeferStmtBits.DeferLoc = DeferLoc; + } + + Stmt *getBody() { return Body; } + const Stmt *getBody() const { return Body; } + void setBody(Stmt *S) { + assert(S && "defer body must not be null"); + Body = S; + } + + SourceLocation getBeginLoc() const { return getDeferLoc(); } + SourceLocation getEndLoc() const { return Body->getEndLoc(); } + + child_range children() { return child_range(&Body, &Body + 1); } + + const_child_range children() const { + return const_child_range(&Body, &Body + 1); + } + + static bool classof(const Stmt *S) { + return S->getStmtClass() == DeferStmtClass; + } +}; + /// AsmStmt is the base class for GCCAsmStmt and MSAsmStmt. class AsmStmt : public Stmt { protected: diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 9401377002223..442a90ec2472d 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -350,6 +350,8 @@ def err_address_of_label_outside_fn : Error< "use of address-of-label extension outside of a function body">; def err_asm_operand_wide_string_literal : Error< "cannot use %select{unicode|wide}0 string literal in 'asm'">; +def err_defer_ts_labeled_stmt : Error< + "substatement of defer must not be a label">; def err_asm_expected_string : Error< "expected string literal %select{or parenthesized constant expression |}0in 'asm'">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 9147b6a041c70..0abad8d1cfb86 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6848,6 +6848,7 @@ def note_protected_by_objc_weak_init : Note< "jump bypasses initialization of __weak variable">; def note_protected_by_non_trivial_c_struct_init : Note< "jump bypasses initialization of variable of non-trivial C struct type">; +def note_protected_by_defer_stmt : Note<"jump bypasses defer statement">; def note_enters_block_captures_cxx_obj : Note< "jump enters lifetime of block which captures a destructible C++ object">; def note_enters_block_captures_strong : Note< @@ -6861,6 +6862,7 @@ def note_enters_compound_literal_scope : Note< "jump enters lifetime of a compound literal that is non-trivial to destruct">; def note_enters_statement_expression : Note< "jump enters a statement expression">; +def note_enters_defer_stmt : Note<"jump enters a defer statement">; def note_exits_cleanup : Note< "jump exits scope of variable with __attribute__((cleanup))">; @@ -6906,6 +6908,16 @@ def note_exits_block_captures_non_trivial_c_struct : Note< "to destroy">; def note_exits_compound_literal_scope : Note< "jump exits lifetime of a compound literal that is non-trivial to destruct">; +def note_exits_defer_stmt : Note<"jump exits a defer statement">; +def err_jump_out_of_defer_stmt : Error< + "cannot %enum_select{" + "%Break{break out of a}|" + "%Continue{continue loop outside of enclosing}|" + "%Return{return from a}|" + "%SEHLeave{__leave a}" + "}0 defer statement">; +def err_defer_invalid_sjlj : Error< + "cannot use %0 inside a defer statement">; def err_func_returning_qualified_void : ExtWarn< "function cannot return qualified void type %0">, @@ -11020,6 +11032,8 @@ def err_switch_explicit_conversion : Error< def err_switch_incomplete_class_type : Error< "switch condition has incomplete class type %0">; +// TODO: It ought to be possible to refactor these to be a single warning that +// uses %enum_select. def warn_empty_if_body : Warning< "if statement has empty body">, InGroup; def warn_empty_for_body : Warning< @@ -11030,6 +11044,8 @@ def warn_empty_while_body : Warning< "while loop has empty body">, InGroup; def warn_empty_switch_body : Warning< "switch statement has empty body">, InGroup; +def warn_empty_defer_body : Warning< + "defer statement has empty body">, InGroup; def note_empty_body_on_separate_line : Note< "put the semicolon on a separate line to silence this warning">; diff --git a/clang/include/clang/Basic/IdentifierTable.h b/clang/include/clang/Basic/IdentifierTable.h index b27492d19a65b..043c184323876 100644 --- a/clang/include/clang/Basic/IdentifierTable.h +++ b/clang/include/clang/Basic/IdentifierTable.h @@ -77,7 +77,8 @@ enum TokenKey : unsigned { KEYNOZOS = 0x4000000, KEYHLSL = 0x8000000, KEYFIXEDPOINT = 0x10000000, - KEYMAX = KEYFIXEDPOINT, // The maximum key + KEYDEFERTS = 0x20000000, + KEYMAX = KEYDEFERTS, // The maximum key KEYALLCXX = KEYCXX | KEYCXX11 | KEYCXX20, KEYALL = (KEYMAX | (KEYMAX - 1)) & ~KEYNOMS18 & ~KEYNOOPENCL & ~KEYNOZOS // KEYNOMS18, KEYNOOPENCL, KEYNOZOS are excluded. diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 40fc66ea12e34..670266ead6253 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -194,6 +194,7 @@ LANGOPT(NoSignedZero , 1, 0, Benign, "Permit Floating Point optimization wi LANGOPT(AllowRecip , 1, 0, Benign, "Permit Floating Point reciprocal") LANGOPT(ApproxFunc , 1, 0, Benign, "Permit Floating Point approximation") LANGOPT(NamedLoops , 1, 0, Benign, "Permit named break/continue") +LANGOPT(DeferTS , 1, 0, Benign, "C '_Defer' Technical Specification") ENUM_LANGOPT(ComplexRange, ComplexRangeKind, 3, CX_None, NotCompatible, "Enable use of range reduction for complex arithmetics.") diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td index bf3686bb372d5..2d740425a3cb0 100644 --- a/clang/include/clang/Basic/StmtNodes.td +++ b/clang/include/clang/Basic/StmtNodes.td @@ -17,6 +17,7 @@ def ForStmt : StmtNode; def GotoStmt : StmtNode; def IndirectGotoStmt : StmtNode; def ReturnStmt : StmtNode; +def DeferStmt : StmtNode; def DeclStmt : StmtNode; def SwitchCase : StmtNode; def CaseStmt : StmtNode; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 564d6010181cc..8240d395d3e8f 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -293,6 +293,7 @@ PUNCTUATOR(greatergreatergreater, ">>>") // CHAR8SUPPORT - This is a keyword if 'char8_t' is a built-in type // KEYFIXEDPOINT - This is a keyword according to the N1169 fixed point // extension. +// KEYDEFERTS - This is a keyword if the C '_Defer' TS is enabled // KEYZOS - This is a keyword in C/C++ on z/OS // KEYWORD(auto , KEYALL) @@ -441,6 +442,9 @@ KEYWORD(_Float16 , KEYALL) C23_KEYWORD(typeof , KEYGNU) C23_KEYWORD(typeof_unqual , 0) +// '_Defer' TS +KEYWORD(_Defer , KEYDEFERTS) + // ISO/IEC JTC1 SC22 WG14 N1169 Extension KEYWORD(_Accum , KEYFIXEDPOINT) KEYWORD(_Fract , KEYFIXEDPOINT) diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index cac122d296624..1477cb243a4a6 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -1671,6 +1671,14 @@ defm named_loops PosFlag, NegFlag>; +// C '_Defer' TS +defm defer_ts : BoolFOption<"defer-ts", + LangOpts<"DeferTS">, DefaultFalse, + PosFlag, + NegFlag>, + ShouldParseIf; + // C++ Coroutines defm coroutines : BoolFOption<"coroutines", LangOpts<"Coroutines">, Default, diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 58eb1c0a7c114..47eedf216a44b 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -7500,6 +7500,16 @@ class Parser : public CodeCompletionHandler { StmtResult ParseBreakOrContinueStatement(bool IsContinue); + /// ParseDeferStatement + /// \verbatim + /// defer-statement: + /// '_Defer' deferred-block + /// + /// deferred-block: + /// unlabeled-statement + /// \endverbatim + StmtResult ParseDeferStatement(SourceLocation *TrailingElseLoc); + StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &Attrs, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d14b5dc5ffaa4..97b6bb3d1b3a8 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10935,6 +10935,10 @@ class Sema final : public SemaBase { /// Stack of active SEH __finally scopes. Can be empty. SmallVector CurrentSEHFinally; + /// Stack of '_Defer' statements that are currently being parsed, as well + /// as the locations of their '_Defer' keywords. Can be empty. + SmallVector, 2> CurrentDefer; + StmtResult ActOnExprStmt(ExprResult Arg, bool DiscardedValue = true); StmtResult ActOnExprStmtError(); @@ -11081,6 +11085,10 @@ class Sema final : public SemaBase { StmtResult ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope, LabelDecl *Label, SourceLocation LabelLoc); + void ActOnStartOfDeferStmt(SourceLocation DeferLoc, Scope *CurScope); + void ActOnDeferStmtError(Scope *CurScope); + StmtResult ActOnEndOfDeferStmt(Stmt *Body, Scope *CurScope); + struct NamedReturnInfo { const VarDecl *Candidate; diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index d7d429eacd67a..b48f02c601889 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -2061,6 +2061,7 @@ enum StmtCode { // HLSL Constructs EXPR_HLSL_OUT_ARG, + STMT_DEFER, }; /// The kinds of designators that can occur in a diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp index 11ece494490de..10aacd75a650a 100644 --- a/clang/lib/AST/Stmt.cpp +++ b/clang/lib/AST/Stmt.cpp @@ -1499,3 +1499,19 @@ const Stmt *LoopControlStmt::getNamedLoopOrSwitch() const { return nullptr; return getLabelDecl()->getStmt()->getInnermostLabeledStmt(); } + +DeferStmt::DeferStmt(EmptyShell Empty) : Stmt(DeferStmtClass, Empty) {} +DeferStmt::DeferStmt(SourceLocation DeferLoc, Stmt *Body) + : Stmt(DeferStmtClass) { + setDeferLoc(DeferLoc); + setBody(Body); +} + +DeferStmt *DeferStmt::CreateEmpty(ASTContext &Context, EmptyShell Empty) { + return new (Context) DeferStmt(Empty); +} + +DeferStmt *DeferStmt::Create(ASTContext &Context, SourceLocation DeferLoc, + Stmt *Body) { + return new (Context) DeferStmt(DeferLoc, Body); +} diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index ff8ca01ec5477..9bc5ee0c7f40e 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -491,6 +491,11 @@ void StmtPrinter::VisitBreakStmt(BreakStmt *Node) { if (Policy.IncludeNewlines) OS << NL; } +void StmtPrinter::VisitDeferStmt(DeferStmt *Node) { + Indent() << "_Defer"; + PrintControlledStmt(Node->getBody()); +} + void StmtPrinter::VisitReturnStmt(ReturnStmt *Node) { Indent() << "return"; if (Node->getRetValue()) { diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp index 4a8c638c85331..b6395a17547f7 100644 --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -323,6 +323,8 @@ void StmtProfiler::VisitReturnStmt(const ReturnStmt *S) { VisitStmt(S); } +void StmtProfiler::VisitDeferStmt(const DeferStmt *S) { VisitStmt(S); } + void StmtProfiler::VisitGCCAsmStmt(const GCCAsmStmt *S) { VisitStmt(S); ID.AddBoolean(S->isVolatile()); diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index d1c959b9687c4..9b4019834c4be 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -164,6 +164,8 @@ static KeywordStatus getKeywordStatusHelper(const LangOptions &LangOpts, return KS_Unknown; case KEYFIXEDPOINT: return LangOpts.FixedPoint ? KS_Enabled : KS_Disabled; + case KEYDEFERTS: + return LangOpts.DeferTS ? KS_Enabled : KS_Disabled; default: llvm_unreachable("Unknown KeywordStatus flag"); } diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 36be3295950b8..c050fd41ac0e9 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -114,6 +114,7 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef Attrs) { case Stmt::ContinueStmtClass: case Stmt::DefaultStmtClass: case Stmt::CaseStmtClass: + case Stmt::DeferStmtClass: case Stmt::SEHLeaveStmtClass: case Stmt::SYCLKernelCallStmtClass: llvm_unreachable("should have emitted these statements as simple"); @@ -539,6 +540,9 @@ bool CodeGenFunction::EmitSimpleStmt(const Stmt *S, case Stmt::CaseStmtClass: EmitCaseStmt(cast(*S), Attrs); break; + case Stmt::DeferStmtClass: + EmitDeferStmt(cast(*S)); + break; case Stmt::SEHLeaveStmtClass: EmitSEHLeaveStmt(cast(*S)); break; @@ -2000,6 +2004,87 @@ void CodeGenFunction::EmitDefaultStmt(const DefaultStmt &S, EmitStmt(S.getSubStmt()); } +namespace { +struct EmitDeferredStatement final : EHScopeStack::Cleanup { + const DeferStmt &Stmt; + EmitDeferredStatement(const DeferStmt *Stmt) : Stmt(*Stmt) {} + + void Emit(CodeGenFunction &CGF, Flags) override { + // Take care that any cleanups pushed by the body of a '_Defer' statement + // don't clobber the current cleanup slot value. + // + // Assume we have a scope that pushes a cleanup; when that scope is exited, + // we need to run that cleanup; this is accomplished by emitting the cleanup + // into a separate block and then branching to that block at scope exit. + // + // Where this gets complicated is if we exit the scope in multiple different + // ways; e.g. in a 'for' loop, we may exit the scope of its body by falling + // off the end (in which case we need to run the cleanup and then branch to + // the increment), or by 'break'ing out of the loop (in which case we need + // to run the cleanup and then branch to the loop exit block); in both cases + // we first branch to the cleanup block to run the cleanup, but the block we + // need to jump to *after* running the cleanup is different. + // + // This is accomplished using a local integer variable called the 'cleanup + // slot': before branching to the cleanup block, we store a value into that + // slot. Then, in the cleanup block, after running the cleanup, we load the + // value of that variable and 'switch' on it to branch to the appropriate + // continuation block. + // + // The problem that arises once '_Defer' statements are involved is that the + // body of a '_Defer' is an arbitrary statement which itself can create more + // cleanups. This means we may end up overwriting the cleanup slot before we + // ever have a chance to 'switch' on it, which means that once we *do* get + // to the 'switch', we end up in whatever block the cleanup code happened to + // pick as the default 'switch' exit label! + // + // That is, what is normally supposed to happen is something like: + // + // 1. Store 'X' to cleanup slot. + // 2. Branch to cleanup block. + // 3. Execute cleanup. + // 4. Read value from cleanup slot. + // 5. Branch to the block associated with 'X'. + // + // But if we encounter a _Defer' statement that contains a cleanup, then + // what might instead happen is: + // + // 1. Store 'X' to cleanup slot. + // 2. Branch to cleanup block. + // 3. Execute cleanup; this ends up pushing another cleanup, so: + // 3a. Store 'Y' to cleanup slot. + // 3b. Run steps 2–5 recursively. + // 4. Read value from cleanup slot, which is now 'Y' instead of 'X'. + // 5. Branch to the block associated with 'Y'... which doesn't even + // exist because the value 'Y' is only meaningful for the inner + // cleanup. The result is we just branch 'somewhere random'. + // + // The rest of the cleanup code simply isn't prepared to handle this case + // because most other cleanups can't push more cleanups, and thus, emitting + // other cleanups generally cannot clobber the cleanup slot. + // + // To prevent this from happening, save the current cleanup slot value and + // restore it after emitting the '_Defer' statement. + llvm::Value *SavedCleanupDest = nullptr; + if (CGF.NormalCleanupDest.isValid()) + SavedCleanupDest = + CGF.Builder.CreateLoad(CGF.NormalCleanupDest, "cleanup.dest.saved"); + + CGF.EmitStmt(Stmt.getBody()); + + if (SavedCleanupDest && CGF.HaveInsertPoint()) + CGF.Builder.CreateStore(SavedCleanupDest, CGF.NormalCleanupDest); + + // Cleanups must end with an insert point. + CGF.EnsureInsertPoint(); + } +}; +} // namespace + +void CodeGenFunction::EmitDeferStmt(const DeferStmt &S) { + EHStack.pushCleanup(NormalAndEHCleanup, &S); +} + /// CollectStatementsForCase - Given the body of a 'switch' statement and a /// constant value that is being switched on, see if we can dead code eliminate /// the body of the switch to a simple series of statements to emit. Basically, diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 8c4c1c8c2dc95..93cf70cb830c8 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3621,6 +3621,7 @@ class CodeGenFunction : public CodeGenTypeCache { void EmitDefaultStmt(const DefaultStmt &S, ArrayRef Attrs); void EmitCaseStmt(const CaseStmt &S, ArrayRef Attrs); void EmitCaseStmtRange(const CaseStmt &S, ArrayRef Attrs); + void EmitDeferStmt(const DeferStmt &S); void EmitAsmStmt(const AsmStmt &S); const BreakContinue *GetDestForLoopControlStmt(const LoopControlStmt &S); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 7187d1a158e01..fce194adf58d2 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -6994,6 +6994,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, types::isCXX(InputType)) CmdArgs.push_back("-fcoro-aligned-allocation"); + if (Args.hasFlag(options::OPT_fdefer_ts, options::OPT_fno_defer_ts, + /*Default=*/false)) + CmdArgs.push_back("-fdefer-ts"); + Args.AddLastArg(CmdArgs, options::OPT_fdouble_square_bracket_attributes, options::OPT_fno_double_square_bracket_attributes); diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index fd464d68b5b42..8253fad9e5503 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -498,6 +498,11 @@ static void InitializeStandardPredefinedMacros(const TargetInfo &TI, Builder.defineMacro("__STDC_EMBED_EMPTY__", llvm::itostr(static_cast(EmbedResult::Empty))); + // We define this to '1' here to indicate that we only support '_Defer' + // as a keyword. + if (LangOpts.DeferTS) + Builder.defineMacro("__STDC_DEFER_TS25755__", "1"); + if (LangOpts.ObjC) Builder.defineMacro("__OBJC__"); diff --git a/clang/lib/Headers/stddefer.h b/clang/lib/Headers/stddefer.h new file mode 100644 index 0000000000000..162876ddfa395 --- /dev/null +++ b/clang/lib/Headers/stddefer.h @@ -0,0 +1,19 @@ +/*===---- stddefer.h - Standard header for 'defer' -------------------------=== + * + * 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 + * + *===-----------------------------------------------------------------------=== + */ + +#ifndef __CLANG_STDDEFER_H +#define __CLANG_STDDEFER_H + +/* Provide 'defer' if '_Defer' is supported. */ +#ifdef __STDC_DEFER_TS25755__ +#define __STDC_VERSION_STDDEFER_H__ 202602L +#define defer _Defer +#endif + +#endif /* __CLANG_STDDEFER_H */ diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 7e73d89c2a18c..78ce4b76d29ae 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -28,6 +28,7 @@ #include "clang/Sema/SemaOpenMP.h" #include "clang/Sema/TypoCorrection.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/ScopeExit.h" #include using namespace clang; @@ -312,6 +313,8 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( Res = ParseReturnStatement(); SemiError = "co_return"; break; + case tok::kw__Defer: // C defer TS: defer-statement + return ParseDeferStatement(TrailingElseLoc); case tok::kw_asm: { for (const ParsedAttr &AL : CXX11Attrs) @@ -2370,6 +2373,29 @@ StmtResult Parser::ParseReturnStatement() { return Actions.ActOnReturnStmt(ReturnLoc, R.get(), getCurScope()); } +StmtResult Parser::ParseDeferStatement(SourceLocation *TrailingElseLoc) { + assert(Tok.is(tok::kw__Defer)); + SourceLocation DeferLoc = ConsumeToken(); + + Actions.ActOnStartOfDeferStmt(DeferLoc, getCurScope()); + + auto OnError = llvm::make_scope_exit( + [&] { Actions.ActOnDeferStmtError(getCurScope()); }); + + StmtResult Res = ParseStatement(TrailingElseLoc); + if (!Res.isUsable()) + return StmtError(); + + // The grammar specifically calls for an unlabeled-statement here. + if (auto *L = dyn_cast(Res.get())) { + Diag(L->getIdentLoc(), diag::err_defer_ts_labeled_stmt); + return StmtError(); + } + + OnError.release(); + return Actions.ActOnEndOfDeferStmt(Res.get(), getCurScope()); +} + StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp index 36704c3826dfd..36c9d9afb37f1 100644 --- a/clang/lib/Sema/JumpDiagnostics.cpp +++ b/clang/lib/Sema/JumpDiagnostics.cpp @@ -590,6 +590,27 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S, break; } + case Stmt::DeferStmtClass: { + auto *D = cast(S); + + { + // Disallow jumps over defer statements. + unsigned NewParentScope = Scopes.size(); + Scopes.emplace_back(ParentScope, diag::note_protected_by_defer_stmt, 0, + D->getDeferLoc()); + origParentScope = NewParentScope; + } + + // Disallow jumps into or out of defer statements. + { + unsigned NewParentScope = Scopes.size(); + Scopes.emplace_back(ParentScope, diag::note_enters_defer_stmt, + diag::note_exits_defer_stmt, D->getDeferLoc()); + BuildScopeInformation(D->getBody(), NewParentScope); + } + return; + } + case Stmt::CaseStmtClass: case Stmt::DefaultStmtClass: case Stmt::LabelStmtClass: @@ -972,7 +993,7 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc, // Common case: exactly the same scope, which is fine. if (FromScope == ToScope) return; - // Warn on gotos out of __finally blocks. + // Warn on gotos out of __finally blocks and defer statements. if (isa(From) || isa(From)) { // If FromScope > ToScope, FromScope is more nested and the jump goes to a // less nested scope. Check if it crosses a __finally along the way. @@ -990,6 +1011,10 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc, S.Diag(From->getBeginLoc(), diag::err_goto_into_protected_scope); S.Diag(Scopes[I].Loc, diag::note_acc_branch_out_of_compute_construct); return; + } else if (Scopes[I].OutDiag == diag::note_exits_defer_stmt) { + S.Diag(From->getBeginLoc(), diag::err_goto_into_protected_scope); + S.Diag(Scopes[I].Loc, diag::note_exits_defer_stmt); + return; } } } diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index a0483c3027199..b5ff1dbd26d68 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -1538,6 +1538,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) { case Stmt::SEHTryStmtClass: case Stmt::SwitchStmtClass: case Stmt::WhileStmtClass: + case Stmt::DeferStmtClass: return canSubStmtsThrow(*this, S); case Stmt::DeclStmtClass: { diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index a8c2e39b49923..5836587a6ffa5 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -6860,6 +6860,34 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl, FunctionDecl *FDecl = dyn_cast_or_null(NDecl); unsigned BuiltinID = (FDecl ? FDecl->getBuiltinID() : 0); + auto IsSJLJ = [&] { + switch (BuiltinID) { + case Builtin::BI__builtin_longjmp: + case Builtin::BI__builtin_setjmp: + case Builtin::BI__sigsetjmp: + case Builtin::BI_longjmp: + case Builtin::BI_setjmp: + case Builtin::BIlongjmp: + case Builtin::BIsetjmp: + case Builtin::BIsiglongjmp: + case Builtin::BIsigsetjmp: + return true; + default: + return false; + } + }; + + // Forbid any call to setjmp/longjmp and friends inside a '_Defer' statement. + if (!CurrentDefer.empty() && IsSJLJ()) { + // Note: If we ever start supporting '_Defer' in C++ we'll have to check + // for more than just blocks (e.g. lambdas, nested classes...). + Scope *DeferParent = CurrentDefer.back().first; + Scope *Block = CurScope->getBlockParent(); + if (DeferParent->Contains(*CurScope) && + (!Block || !DeferParent->Contains(*Block))) + Diag(Fn->getExprLoc(), diag::err_defer_invalid_sjlj) << FDecl; + } + // Functions with 'interrupt' attribute cannot be called directly. if (FDecl) { if (FDecl->hasAttr()) { diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 6bb1a27d1800c..1b1643250d05e 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3267,12 +3267,23 @@ Sema::ActOnIndirectGotoStmt(SourceLocation GotoLoc, SourceLocation StarLoc, return new (Context) IndirectGotoStmt(GotoLoc, StarLoc, E); } -static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc, - const Scope &DestScope) { +static void CheckJumpOutOfSEHFinallyOrDefer(Sema &S, SourceLocation Loc, + const Scope &DestScope, + unsigned DeferJumpKind) { if (!S.CurrentSEHFinally.empty() && DestScope.Contains(*S.CurrentSEHFinally.back())) { S.Diag(Loc, diag::warn_jump_out_of_seh_finally); } + + if (!S.CurrentDefer.empty()) { + Scope *Parent = S.CurrentDefer.back().first; + assert(Parent); + + // Note: We don't create a new scope for defer statements, so 'Parent' + // is actually the scope that contains the '_Defer'. + if (DestScope.Contains(*Parent) || &DestScope == Parent) + S.Diag(Loc, diag::err_jump_out_of_defer_stmt) << DeferJumpKind; + } } static Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope, @@ -3346,7 +3357,8 @@ StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope, Diag(ContinueLoc, diag::err_acc_branch_in_out_compute_construct) << /*branch*/ 0 << /*out of */ 0); - CheckJumpOutOfSEHFinally(*this, ContinueLoc, *S); + CheckJumpOutOfSEHFinallyOrDefer(*this, ContinueLoc, *S, + diag::DeferJumpKind::Continue); return new (Context) ContinueStmt(ContinueLoc, LabelLoc, Target); } @@ -3387,7 +3399,8 @@ StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope, Diag(BreakLoc, diag::err_acc_branch_in_out_compute_construct) << /*branch*/ 0 << /*out of */ 0); - CheckJumpOutOfSEHFinally(*this, BreakLoc, *S); + CheckJumpOutOfSEHFinallyOrDefer(*this, BreakLoc, *S, + diag::DeferJumpKind::Break); return new (Context) BreakStmt(BreakLoc, LabelLoc, Target); } @@ -3932,11 +3945,30 @@ Sema::ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp, CurScope->updateNRVOCandidate(VD); - CheckJumpOutOfSEHFinally(*this, ReturnLoc, *CurScope->getFnParent()); + CheckJumpOutOfSEHFinallyOrDefer(*this, ReturnLoc, *CurScope->getFnParent(), + diag::DeferJumpKind::Return); return R; } +void Sema::ActOnStartOfDeferStmt(SourceLocation DeferLoc, Scope *CurScope) { + CurrentDefer.emplace_back(CurScope, DeferLoc); +} + +void Sema::ActOnDeferStmtError([[maybe_unused]] Scope *CurScope) { + assert(!CurrentDefer.empty() && CurrentDefer.back().first == CurScope); + CurrentDefer.pop_back(); +} + +StmtResult Sema::ActOnEndOfDeferStmt(Stmt *Body, + [[maybe_unused]] Scope *CurScope) { + assert(!CurrentDefer.empty() && CurrentDefer.back().first == CurScope); + SourceLocation DeferLoc = CurrentDefer.pop_back_val().second; + DiagnoseEmptyStmtBody(DeferLoc, Body, diag::warn_empty_defer_body); + setFunctionHasBranchProtectedScope(); + return DeferStmt::Create(Context, DeferLoc, Body); +} + static bool CheckSimplerImplicitMovesMSVCWorkaround(const Sema &S, const Expr *E) { if (!E || !S.getLangOpts().CPlusPlus23 || !S.getLangOpts().MSVCCompat) @@ -4554,7 +4586,8 @@ Sema::ActOnSEHLeaveStmt(SourceLocation Loc, Scope *CurScope) { SEHTryParent = SEHTryParent->getParent(); if (!SEHTryParent) return StmtError(Diag(Loc, diag::err_ms___leave_not_in___try)); - CheckJumpOutOfSEHFinally(*this, Loc, *SEHTryParent); + CheckJumpOutOfSEHFinallyOrDefer(*this, Loc, *SEHTryParent, + diag::DeferJumpKind::SEHLeave); return new (Context) SEHLeaveStmt(Loc); } diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 8e5dbeb792348..d5b6fdd7dc405 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -8552,6 +8552,14 @@ TreeTransform::TransformBreakStmt(BreakStmt *S) { BreakStmt(S->getKwLoc(), S->getLabelLoc(), cast(LD)); } +template +StmtResult TreeTransform::TransformDeferStmt(DeferStmt *S) { + StmtResult Result = getDerived().TransformStmt(S->getBody()); + if (!Result.isUsable()) + return StmtError(); + return DeferStmt::Create(getSema().Context, S->getDeferLoc(), Result.get()); +} + template StmtResult TreeTransform::TransformReturnStmt(ReturnStmt *S) { diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index eef97a8588f0b..495517ccb31f3 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -335,6 +335,12 @@ void ASTStmtReader::VisitContinueStmt(ContinueStmt *S) { void ASTStmtReader::VisitBreakStmt(BreakStmt *S) { VisitLoopControlStmt(S); } +void ASTStmtReader::VisitDeferStmt(DeferStmt *S) { + VisitStmt(S); + S->setDeferLoc(readSourceLocation()); + S->setBody(Record.readSubStmt()); +} + void ASTStmtReader::VisitReturnStmt(ReturnStmt *S) { VisitStmt(S); @@ -3146,6 +3152,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) { S = new (Context) BreakStmt(Empty); break; + case STMT_DEFER: + S = DeferStmt::CreateEmpty(Context, Empty); + break; + case STMT_RETURN: S = ReturnStmt::CreateEmpty( Context, /* HasNRVOCandidate=*/Record[ASTStmtReader::NumStmtFields]); diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index acf345392aa1a..a457e627799c9 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -330,6 +330,13 @@ void ASTStmtWriter::VisitBreakStmt(BreakStmt *S) { Code = serialization::STMT_BREAK; } +void ASTStmtWriter::VisitDeferStmt(DeferStmt *S) { + VisitStmt(S); + Record.AddSourceLocation(S->getDeferLoc()); + Record.AddStmt(S->getBody()); + Code = serialization::STMT_DEFER; +} + void ASTStmtWriter::VisitReturnStmt(ReturnStmt *S) { VisitStmt(S); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index a759aee47b8ea..d3de632179e1d 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1874,6 +1874,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::NullStmtClass: case Stmt::SwitchStmtClass: case Stmt::WhileStmtClass: + case Stmt::DeferStmtClass: case Expr::MSDependentExistsStmtClass: llvm_unreachable("Stmt should not be in analyzer evaluation loop"); case Stmt::ImplicitValueInitExprClass: diff --git a/clang/test/AST/ast-dump-defer-ts.c b/clang/test/AST/ast-dump-defer-ts.c new file mode 100644 index 0000000000000..eba057f93c9c2 --- /dev/null +++ b/clang/test/AST/ast-dump-defer-ts.c @@ -0,0 +1,27 @@ +// Test without serialization: +// RUN: %clang_cc1 -std=c23 -fdefer-ts -ast-dump %s -triple x86_64-linux-gnu \ +// RUN: | FileCheck %s +// +// Test with serialization: +// RUN: %clang_cc1 -std=c23 -fdefer-ts -triple x86_64-linux-gnu -emit-pch -o %t %s +// RUN: %clang_cc1 -std=c23 -fdefer-ts -triple x86_64-linux-gnu -include-pch %t -ast-dump-all /dev/null \ +// RUN: | FileCheck %s + +static inline void f() { + _Defer 3; + _Defer { 4; } + _Defer _Defer if (true) {} +} + +// CHECK-LABEL: f 'void (void)' static inline +// CHECK-NEXT: `-CompoundStmt {{.*}} +// CHECK-NEXT: |-DeferStmt {{.*}} +// CHECK-NEXT: | `-IntegerLiteral {{.*}} 'int' 3 +// CHECK-NEXT: |-DeferStmt {{.*}} +// CHECK-NEXT: | `-CompoundStmt {{.*}} +// CHECK-NEXT: | `-IntegerLiteral {{.*}} 'int' 4 +// CHECK-NEXT: `-DeferStmt {{.*}} +// CHECK-NEXT: `-DeferStmt {{.*}} +// CHECK-NEXT: `-IfStmt {{.*}} +// CHECK-NEXT: |-CXXBoolLiteralExpr {{.*}} 'bool' true +// CHECK-NEXT: `-CompoundStmt {{.*}} diff --git a/clang/test/AST/ast-print-defer-ts.c b/clang/test/AST/ast-print-defer-ts.c new file mode 100644 index 0000000000000..bcc217a597778 --- /dev/null +++ b/clang/test/AST/ast-print-defer-ts.c @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -std=c23 -fdefer-ts -ast-print %s | FileCheck %s + +void g(); + +// CHECK: void f +void f() { + // CHECK-NEXT: _Defer + // CHECK-NEXT: g(); + // CHECK-NEXT: _Defer + // CHECK-NEXT: _Defer + // CHECK-NEXT: g(); + // CHECK-NEXT: _Defer { + // CHECK-NEXT: } + // CHECK-NEXT: _Defer { + // CHECK-NEXT: int x; + // CHECK-NEXT: } + // CHECK-NEXT: _Defer + // CHECK-NEXT: if (1) { + // CHECK-NEXT: } + _Defer + g(); + _Defer + _Defer + g(); + _Defer { + } + _Defer { + int x; + } + _Defer + if (1) { + } +} diff --git a/clang/test/CodeGen/defer-ts-musttail.c b/clang/test/CodeGen/defer-ts-musttail.c new file mode 100644 index 0000000000000..5622fecbb4fed --- /dev/null +++ b/clang/test/CodeGen/defer-ts-musttail.c @@ -0,0 +1,7 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c23 -fdefer-ts -emit-llvm %s -o /dev/null -verify + +int bar() { return 12; } +int foo() { + _Defer {}; + [[clang::musttail]] return bar(); // expected-error {{cannot compile this tail call skipping over cleanups yet}} +} diff --git a/clang/test/CodeGen/defer-ts-nested-cleanups.c b/clang/test/CodeGen/defer-ts-nested-cleanups.c new file mode 100644 index 0000000000000..d831b4380b929 --- /dev/null +++ b/clang/test/CodeGen/defer-ts-nested-cleanups.c @@ -0,0 +1,179 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c23 -fdefer-ts -emit-llvm %s -o - -O1 -disable-llvm-passes | FileCheck %s + +// Test that cleanups emitted in a '_Defer' don't clobber the cleanup slot; we +// test this using lifetime intrinsics, which are emitted starting at -O1. + +void g(); + +// CHECK-LABEL: define {{.*}} void @f1() +// CHECK: entry: +// CHECK-NEXT: %i = alloca i32, align 4 +// CHECK-NEXT: %cleanup.dest.slot = alloca i32, align 4 +// CHECK-NEXT: %j = alloca i32, align 4 +// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %i) +// CHECK-NEXT: store i32 0, ptr %i, align 4 +// CHECK-NEXT: br label %for.cond +// CHECK: for.cond: +// CHECK-NEXT: %0 = load i32, ptr %i, align 4 +// CHECK-NEXT: %cmp = icmp eq i32 %0, 1 +// CHECK-NEXT: br i1 %cmp, label %if.then, label %if.end +// CHECK: if.then: +// CHECK-NEXT: store i32 2, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: if.end: +// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: cleanup: +// CHECK-NEXT: %cleanup.dest.saved = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %j) +// CHECK-NEXT: store i32 0, ptr %j, align 4 +// CHECK-NEXT: br label %for.cond1 +// CHECK: for.cond1: +// CHECK-NEXT: %1 = load i32, ptr %j, align 4 +// CHECK-NEXT: %cmp2 = icmp ne i32 %1, 1 +// CHECK-NEXT: br i1 %cmp2, label %for.body, label %for.cond.cleanup +// CHECK: for.cond.cleanup: +// CHECK-NEXT: store i32 5, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %j) +// CHECK-NEXT: br label %for.end +// CHECK: for.body: +// CHECK-NEXT: call void @g() +// CHECK-NEXT: br label %for.inc +// CHECK: for.inc: +// CHECK-NEXT: %2 = load i32, ptr %j, align 4 +// CHECK-NEXT: %inc = add nsw i32 %2, 1 +// CHECK-NEXT: store i32 %inc, ptr %j, align 4 +// CHECK-NEXT: br label %for.cond1 +// CHECK: for.end: +// CHECK-NEXT: store i32 %cleanup.dest.saved, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: switch i32 %cleanup.dest, label %cleanup6 [ +// CHECK-NEXT: i32 0, label %cleanup.cont +// CHECK-NEXT: ] +// CHECK: cleanup.cont: +// CHECK-NEXT: br label %for.inc4 +// CHECK: for.inc4: +// CHECK-NEXT: %3 = load i32, ptr %i, align 4 +// CHECK-NEXT: %inc5 = add nsw i32 %3, 1 +// CHECK-NEXT: store i32 %inc5, ptr %i, align 4 +// CHECK-NEXT: br label %for.cond +// CHECK: cleanup6: +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %i) +// CHECK-NEXT: br label %for.end7 +// CHECK: for.end7: +// CHECK-NEXT: ret void +void f1() { + for (int i = 0;; i++) { + _Defer { + for (int j = 0; j != 1; j++) { + g(); + } + } + if (i == 1) break; + } +} + +// CHECK-LABEL: define {{.*}} void @f2() +// CHECK: entry: +// CHECK-NEXT: %i = alloca i32, align 4 +// CHECK-NEXT: %cleanup.dest.slot = alloca i32, align 4 +// CHECK-NEXT: %j = alloca i32, align 4 +// CHECK-NEXT: %k = alloca i32, align 4 +// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %i) +// CHECK-NEXT: store i32 0, ptr %i, align 4 +// CHECK-NEXT: br label %for.cond +// CHECK: for.cond: +// CHECK-NEXT: %0 = load i32, ptr %i, align 4 +// CHECK-NEXT: %cmp = icmp eq i32 %0, 1 +// CHECK-NEXT: br i1 %cmp, label %if.then, label %if.end +// CHECK: if.then: +// CHECK-NEXT: store i32 2, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: if.end: +// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: cleanup: +// CHECK-NEXT: %cleanup.dest.saved = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %j) +// CHECK-NEXT: store i32 0, ptr %j, align 4 +// CHECK-NEXT: br label %for.cond1 +// CHECK: for.cond1: +// CHECK-NEXT: %1 = load i32, ptr %j, align 4 +// CHECK-NEXT: %cmp2 = icmp eq i32 %1, 1 +// CHECK-NEXT: br i1 %cmp2, label %if.then3, label %if.end4 +// CHECK: if.then3: +// CHECK-NEXT: store i32 5, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup5 +// CHECK: if.end4: +// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup5 +// CHECK: cleanup5: +// CHECK-NEXT: %cleanup.dest.saved6 = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %k) +// CHECK-NEXT: store i32 0, ptr %k, align 4 +// CHECK-NEXT: br label %for.cond7 +// CHECK: for.cond7: +// CHECK-NEXT: %2 = load i32, ptr %k, align 4 +// CHECK-NEXT: %cmp8 = icmp ne i32 %2, 1 +// CHECK-NEXT: br i1 %cmp8, label %for.body, label %for.cond.cleanup +// CHECK: for.cond.cleanup: +// CHECK-NEXT: store i32 8, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %k) +// CHECK-NEXT: br label %for.end +// CHECK: for.body: +// CHECK-NEXT: call void @g() +// CHECK-NEXT: br label %for.inc +// CHECK: for.inc: +// CHECK-NEXT: %3 = load i32, ptr %k, align 4 +// CHECK-NEXT: %inc = add nsw i32 %3, 1 +// CHECK-NEXT: store i32 %inc, ptr %k, align 4 +// CHECK-NEXT: br label %for.cond7 +// CHECK: for.end: +// CHECK-NEXT: store i32 %cleanup.dest.saved6, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: switch i32 %cleanup.dest, label %cleanup12 [ +// CHECK-NEXT: i32 0, label %cleanup.cont +// CHECK-NEXT: ] +// CHECK: cleanup.cont: +// CHECK-NEXT: br label %for.inc10 +// CHECK: for.inc10: +// CHECK-NEXT: %4 = load i32, ptr %j, align 4 +// CHECK-NEXT: %inc11 = add nsw i32 %4, 1 +// CHECK-NEXT: store i32 %inc11, ptr %j, align 4 +// CHECK-NEXT: br label %for.cond1 +// CHECK: cleanup12: +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %j) +// CHECK-NEXT: br label %for.end13 +// CHECK: for.end13: +// CHECK-NEXT: store i32 %cleanup.dest.saved, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: %cleanup.dest14 = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: switch i32 %cleanup.dest14, label %cleanup18 [ +// CHECK-NEXT: i32 0, label %cleanup.cont15 +// CHECK-NEXT: ] +// CHECK: cleanup.cont15: +// CHECK-NEXT: br label %for.inc16 +// CHECK: for.inc16: +// CHECK-NEXT: %5 = load i32, ptr %i, align 4 +// CHECK-NEXT: %inc17 = add nsw i32 %5, 1 +// CHECK-NEXT: store i32 %inc17, ptr %i, align 4 +// CHECK-NEXT: br label %for.cond +// CHECK: cleanup18: +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %i) +// CHECK-NEXT: br label %for.end19 +// CHECK: for.end19: +// CHECK-NEXT: ret void +void f2() { + for (int i = 0;; i++) { + _Defer { + for (int j = 0;; j++) { + _Defer { + for (int k = 0; k != 1; k++) { + g(); + } + } + if (j == 1) break; + } + } + if (i == 1) break; + } +} diff --git a/clang/test/CodeGen/defer-ts-seh.c b/clang/test/CodeGen/defer-ts-seh.c new file mode 100644 index 0000000000000..a91816f50d8d5 --- /dev/null +++ b/clang/test/CodeGen/defer-ts-seh.c @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=c23 -fdefer-ts -fms-compatibility -emit-llvm %s -o - | FileCheck %s + +void g(); +void h(); + +void f() { + __try { + _Defer h(); + g(); + } __finally { + + } +} + +// CHECK-LABEL: define {{.*}} void @f() {{.*}} personality ptr @__C_specific_handler +// CHECK: entry: +// CHECK: invoke void @g() #4 +// CHECK: to label %invoke.cont unwind label %ehcleanup +// CHECK: invoke.cont: +// CHECK: invoke void @h() #4 +// CHECK: to label %invoke.cont1 unwind label %ehcleanup3 +// CHECK: invoke.cont1: +// CHECK: %0 = call ptr @llvm.localaddress() +// CHECK: call void @"?fin$0@0@f@@"(i8 {{.*}} 0, ptr {{.*}} %0) +// CHECK: ret void +// CHECK: ehcleanup: +// CHECK: %1 = cleanuppad within none [] +// CHECK: invoke void @h() #4 [ "funclet"(token %1) ] +// CHECK: to label %invoke.cont2 unwind label %ehcleanup3 +// CHECK: invoke.cont2: +// CHECK: cleanupret from %1 unwind label %ehcleanup3 +// CHECK: ehcleanup3: +// CHECK: %2 = cleanuppad within none [] +// CHECK: %3 = call ptr @llvm.localaddress() +// CHECK: call void @"?fin$0@0@f@@"(i8 {{.*}} 1, ptr {{.*}} %3) [ "funclet"(token %2) ] +// CHECK: cleanupret from %2 unwind to caller + +// CHECK-LABEL: define {{.*}} void @"?fin$0@0@f@@"(i8 {{.*}} %abnormal_termination, ptr {{.*}} %frame_pointer) +// CHECK: entry: +// CHECK: %frame_pointer.addr = alloca ptr, align 8 +// CHECK: %abnormal_termination.addr = alloca i8, align 1 +// CHECK: store ptr %frame_pointer, ptr %frame_pointer.addr, align 8 +// CHECK: store i8 %abnormal_termination, ptr %abnormal_termination.addr, align 1 +// CHECK: ret void diff --git a/clang/test/CodeGen/defer-ts.c b/clang/test/CodeGen/defer-ts.c new file mode 100644 index 0000000000000..79b09064d330c --- /dev/null +++ b/clang/test/CodeGen/defer-ts.c @@ -0,0 +1,652 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c23 -fdefer-ts -emit-llvm %s -o - | FileCheck %s + +#define defer _Defer + +void a(); +void b(); +void c(); +void x(int q); +bool q(int q); +[[noreturn]] void noreturn(); + +// CHECK-LABEL: define {{.*}} void @f1() +void f1() { + // CHECK: call void @c() + // CHECK: call void @b() + // CHECK: call void @a() + defer a(); + defer b(); + defer c(); +} + +// CHECK-LABEL: define {{.*}} void @f2() +void f2() { + // CHECK: call void @x(i32 {{.*}} 1) + // CHECK: call void @x(i32 {{.*}} 2) + // CHECK: call void @x(i32 {{.*}} 3) + // CHECK: call void @x(i32 {{.*}} 4) + // CHECK: call void @x(i32 {{.*}} 5) + defer x(5); + { + defer x(4); + { + defer x(2); + defer x(1); + } + x(3); + } +} + +// CHECK-LABEL: define {{.*}} void @f3(i1 {{.*}} %ret) +void f3(bool ret) { + // CHECK: entry: + // CHECK: %ret.addr = alloca i8, align 1 + // CHECK: %cleanup.dest.slot = alloca i32, align 4 + // CHECK: %storedv = zext i1 %ret to i8 + // CHECK: store i8 %storedv, ptr %ret.addr, align 1 + // CHECK: %0 = load i8, ptr %ret.addr, align 1 + // CHECK: %loadedv = trunc i8 %0 to i1 + // CHECK: br i1 %loadedv, label %if.then, label %if.end + // CHECK: if.then: + // CHECK: store i32 1, ptr %cleanup.dest.slot, align 4 + // CHECK: br label %cleanup + // CHECK: if.end: + // CHECK: %cleanup.dest.saved = load i32, ptr %cleanup.dest.slot, align 4 + // CHECK: call void @x(i32 {{.*}} 1) + // CHECK: store i32 %cleanup.dest.saved, ptr %cleanup.dest.slot, align 4 + // CHECK: store i32 0, ptr %cleanup.dest.slot, align 4 + // CHECK: br label %cleanup + // CHECK: cleanup: + // CHECK: %cleanup.dest.saved1 = load i32, ptr %cleanup.dest.slot, align 4 + // CHECK: call void @x(i32 {{.*}} 2) + // CHECK: store i32 %cleanup.dest.saved1, ptr %cleanup.dest.slot, align 4 + // CHECK: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4 + // CHECK: switch i32 %cleanup.dest, label %unreachable [ + // CHECK: i32 0, label %cleanup.cont + // CHECK: i32 1, label %cleanup.cont + // CHECK: ] + // CHECK: cleanup.cont: + // CHECK: ret void + // CHECK: unreachable: + // CHECK: unreachable + defer x(2); + if (ret) return; + defer x(1); +} + +// CHECK-LABEL: define {{.*}} void @ts_g() +void ts_g() { + // CHECK-NEXT: entry: + // CHECK-NEXT: ret void + // CHECK-NEXT: } + return; + defer x(42); +} + +// CHECK-LABEL: define {{.*}} void @ts_h() +void ts_h() { + // CHECK-NEXT: entry: + // CHECK-NEXT: br label %b + // CHECK-EMPTY: + goto b; + { + defer x(42); + } + + // CHECK-NEXT: b: + // CHECK-NEXT: ret void + // CHECK-NEXT: } + b: +} + +// CHECK-LABEL: define {{.*}} void @ts_i() +void ts_i() { + // CHECK: entry: + // CHECK: %cleanup.dest.slot = alloca i32, align 4 + // CHECK: store i32 2, ptr %cleanup.dest.slot, align 4 + // CHECK: %cleanup.dest.saved = load i32, ptr %cleanup.dest.slot, align 4 + // CHECK: call void @x(i32 {{.*}} 42) + // CHECK: store i32 %cleanup.dest.saved, ptr %cleanup.dest.slot, align 4 + // CHECK: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4 + // CHECK: switch i32 %cleanup.dest, label %unreachable [ + // CHECK: i32 2, label %b + // CHECK: ] + // CHECK: b: + // CHECK: ret void + // CHECK: unreachable: + // CHECK: unreachable + { + defer { x(42); } + goto b; + } + b: +} + + +// CHECK-LABEL: define {{.*}} void @ts_m() +void ts_m() { + // CHECK: entry: + // CHECK: br label %b + // CHECK: b: + // CHECK: call void @x(i32 {{.*}} 1) + // CHECK: ret void + goto b; + { + b: + defer x(1); + } +} + +// CHECK-LABEL: define {{.*}} void @ts_p() +void ts_p() { + // CHECK: entry: + // CHECK: br label %b + // CHECK: b: + // CHECK: ret void + { + goto b; + defer x(42); + } + b: +} + +// CHECK-LABEL: define {{.*}} void @ts_r() +void ts_r() { + // CHECK: entry: + // CHECK: br label %b + // CHECK: b: + // CHECK: call void @x(i32 {{.*}} 42) + // CHECK: br label %b + { + b: + defer x(42); + } + goto b; +} + +// CHECK-LABEL: define {{.*}} i32 @return_value() +int return_value() { + // CHECK: entry: + // CHECK: %r = alloca i32, align 4 + // CHECK: %p = alloca ptr, align 8 + // CHECK: store i32 4, ptr %r, align 4 + // CHECK: store ptr %r, ptr %p, align 8 + // CHECK: %0 = load ptr, ptr %p, align 8 + // CHECK: %1 = load i32, ptr %0, align 4 + // CHECK: %2 = load ptr, ptr %p, align 8 + // CHECK: store i32 5, ptr %2, align 4 + // CHECK: ret i32 %1 + int r = 4; + int* p = &r; + defer { *p = 5; } + return *p; +} + +void* malloc(__SIZE_TYPE__ size); +void free(void* ptr); +int use_buffer(__SIZE_TYPE__ size, void* ptr); + +// CHECK-LABEL: define {{.*}} i32 @malloc_free_example() +int malloc_free_example() { + // CHECK: entry: + // CHECK: %size = alloca i32, align 4 + // CHECK: %buf = alloca ptr, align 8 + // CHECK: store i32 20, ptr %size, align 4 + // CHECK: %call = call ptr @malloc(i64 {{.*}} 20) + // CHECK: store ptr %call, ptr %buf, align 8 + // CHECK: %0 = load ptr, ptr %buf, align 8 + // CHECK: %call1 = call i32 @use_buffer(i64 {{.*}} 20, ptr {{.*}} %0) + // CHECK: %1 = load ptr, ptr %buf, align 8 + // CHECK: call void @free(ptr {{.*}} %1) + // CHECK: ret i32 %call1 + const int size = 20; + void* buf = malloc(size); + defer { free(buf); } + return use_buffer(size, buf); +} + +// CHECK-LABEL: define {{.*}} void @sequencing_1() +void sequencing_1() { + // CHECK: entry: + // CHECK: call void @x(i32 {{.*}} 1) + // CHECK: call void @x(i32 {{.*}} 2) + // CHECK: call void @x(i32 {{.*}} 3) + // CHECK: ret void + { + defer { + x(3); + } + if (true) + defer x(1); + x(2); + } +} + +// CHECK-LABEL: define {{.*}} void @sequencing_2() +void sequencing_2() { + // CHECK: entry: + // CHECK: %arr = alloca [3 x i32], align 4 + // CHECK: %i = alloca i32, align 4 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %arr, ptr align 4 @__const.sequencing_2.arr, i64 12, i1 false) + // CHECK: store i32 0, ptr %i, align 4 + // CHECK: br label %for.cond + // CHECK: for.cond: + // CHECK: %0 = load i32, ptr %i, align 4 + // CHECK: %cmp = icmp ult i32 %0, 3 + // CHECK: br i1 %cmp, label %for.body, label %for.end + // CHECK: for.body: + // CHECK: %1 = load i32, ptr %i, align 4 + // CHECK: %idxprom = zext i32 %1 to i64 + // CHECK: %arrayidx = getelementptr inbounds nuw [3 x i32], ptr %arr, i64 0, i64 %idxprom + // CHECK: %2 = load i32, ptr %arrayidx, align 4 + // CHECK: call void @x(i32 {{.*}} %2) + // CHECK: br label %for.inc + // CHECK: for.inc: + // CHECK: %3 = load i32, ptr %i, align 4 + // CHECK: %inc = add i32 %3, 1 + // CHECK: store i32 %inc, ptr %i, align 4 + // CHECK: br label %for.cond + // CHECK: for.end: + // CHECK: call void @x(i32 {{.*}} 4) + // CHECK: call void @x(i32 {{.*}} 5) + // CHECK: ret void + { + int arr[] = {1, 2, 3}; + defer { + x(5); + } + for (unsigned i = 0; i < 3; ++i) + defer x(arr[i]); + x(4); + } +} + +// CHECK-LABEL: define {{.*}} void @sequencing_3() +void sequencing_3() { + // CHECK: entry: + // CHECK: %r = alloca i32, align 4 + // CHECK: store i32 0, ptr %r, align 4 + // CHECK: %0 = load i32, ptr %r, align 4 + // CHECK: %add = add nsw i32 %0, 1 + // CHECK: store i32 %add, ptr %r, align 4 + // CHECK: %1 = load i32, ptr %r, align 4 + // CHECK: %mul = mul nsw i32 %1, 2 + // CHECK: store i32 %mul, ptr %r, align 4 + // CHECK: %2 = load i32, ptr %r, align 4 + // CHECK: %add1 = add nsw i32 %2, 3 + // CHECK: store i32 %add1, ptr %r, align 4 + // CHECK: %3 = load i32, ptr %r, align 4 + // CHECK: %mul2 = mul nsw i32 %3, 4 + // CHECK: store i32 %mul2, ptr %r, align 4 + // CHECK: ret void + int r = 0; + { + defer { + defer r *= 4; + r *= 2; + defer { + r += 3; + } + } + defer r += 1; + } +} + +// CHECK-LABEL: define {{.*}} void @defer_stmt(i32 {{.*}} %q) +void defer_stmt(int q) { + // CHECK: entry: + // CHECK: %q.addr = alloca i32, align 4 + // CHECK: store i32 %q, ptr %q.addr, align 4 + // CHECK: %0 = load i32, ptr %q.addr, align 4 + // CHECK: %cmp = icmp eq i32 %0, 3 + // CHECK: br i1 %cmp, label %if.then, label %if.end + // CHECK: if.then: + // CHECK: call void @x(i32 {{.*}} 42) + // CHECK: br label %if.end + // CHECK: if.end: + // CHECK: ret void + defer if (q == 3) x(42); +} + +// CHECK-LABEL: define {{.*}} void @defer_defer() +void defer_defer() { + // CHECK: entry: + // CHECK: call void @x(i32 {{.*}} 0) + // CHECK: call void @x(i32 {{.*}} 1) + // CHECK: call void @x(i32 {{.*}} 2) + // CHECK: call void @x(i32 {{.*}} 3) + // CHECK: call void @x(i32 {{.*}} 4) + // CHECK: ret void + defer x(4); + defer defer x(3); + defer defer defer x(2); + defer defer defer defer x(1); + x(0); +} + +// CHECK-LABEL: define {{.*}} i32 @vla(ptr {{.*}} %p, i32 {{.*}} %x) +int vla(int* p, int x) { + // CHECK: entry: + // CHECK: %retval = alloca i32, align 4 + // CHECK: %p.addr = alloca ptr, align 8 + // CHECK: %x.addr = alloca i32, align 4 + // CHECK: %cleanup.dest.slot = alloca i32, align 4 + // CHECK: %saved_stack = alloca ptr, align 8 + // CHECK: %__vla_expr0 = alloca i64, align 8 + // CHECK: %saved_stack3 = alloca ptr, align 8 + // CHECK: %__vla_expr1 = alloca i64, align 8 + // CHECK: store ptr %p, ptr %p.addr, align 8 + // CHECK: store i32 %x, ptr %x.addr, align 4 + // CHECK: %0 = load i32, ptr %x.addr, align 4 + // CHECK: %cmp = icmp slt i32 %0, 5 + // CHECK: br i1 %cmp, label %if.then, label %if.end + // CHECK: if.then: + // CHECK: store i32 10, ptr %retval, align 4 + // CHECK: store i32 1, ptr %cleanup.dest.slot, align 4 + // CHECK: br label %cleanup + // CHECK: if.end: + // CHECK: store i32 7, ptr %retval, align 4 + // CHECK: store i32 1, ptr %cleanup.dest.slot, align 4 + // CHECK: %cleanup.dest.saved = load i32, ptr %cleanup.dest.slot, align 4 + // CHECK: %1 = load i32, ptr %x.addr, align 4 + // CHECK: %2 = zext i32 %1 to i64 + // CHECK: %3 = call ptr @llvm.stacksave.p0() + // CHECK: store ptr %3, ptr %saved_stack, align 8 + // CHECK: %vla = alloca i32, i64 %2, align 16 + // CHECK: store i64 %2, ptr %__vla_expr0, align 8 + // CHECK: %arrayidx = getelementptr inbounds i32, ptr %vla, i64 2 + // CHECK: store i32 4, ptr %arrayidx, align 8 + // CHECK: %arrayidx1 = getelementptr inbounds i32, ptr %vla, i64 2 + // CHECK: %4 = load i32, ptr %arrayidx1, align 8 + // CHECK: %5 = load ptr, ptr %p.addr, align 8 + // CHECK: store i32 %4, ptr %5, align 4 + // CHECK: %6 = load ptr, ptr %saved_stack, align 8 + // CHECK: call void @llvm.stackrestore.p0(ptr %6) + // CHECK: store i32 %cleanup.dest.saved, ptr %cleanup.dest.slot, align 4 + // CHECK: br label %cleanup + // CHECK: cleanup: + // CHECK: %cleanup.dest.saved2 = load i32, ptr %cleanup.dest.slot, align 4 + // CHECK: %7 = load i32, ptr %x.addr, align 4 + // CHECK: %8 = zext i32 %7 to i64 + // CHECK: %9 = call ptr @llvm.stacksave.p0() + // CHECK: store ptr %9, ptr %saved_stack3, align 8 + // CHECK: %vla4 = alloca i32, i64 %8, align 16 + // CHECK: store i64 %8, ptr %__vla_expr1, align 8 + // CHECK: %arrayidx5 = getelementptr inbounds i32, ptr %vla4, i64 2 + // CHECK: store i32 3, ptr %arrayidx5, align 8 + // CHECK: %arrayidx6 = getelementptr inbounds i32, ptr %vla4, i64 2 + // CHECK: %10 = load i32, ptr %arrayidx6, align 8 + // CHECK: %11 = load ptr, ptr %p.addr, align 8 + // CHECK: store i32 %10, ptr %11, align 4 + // CHECK: %12 = load ptr, ptr %saved_stack3, align 8 + // CHECK: call void @llvm.stackrestore.p0(ptr %12) + // CHECK: store i32 %cleanup.dest.saved2, ptr %cleanup.dest.slot, align 4 + // CHECK: %13 = load i32, ptr %retval, align 4 + // CHECK: ret i32 %13 + defer { + int a[x]; + a[2] = 3; + *p = a[2]; + } + if (x < 5) { return 10; } + defer { + int b[x]; + b[2] = 4; + *p = b[2]; + } + return 7; +} + +[[noreturn]] void exit(); +[[noreturn]] void _Exit(); +[[noreturn]] void foobar(); + +// CHECK-LABEL: define {{.*}} i32 @call_exit() +int call_exit() { + // CHECK: entry: + // CHECK: call void @exit() + // CHECK: unreachable + defer x(1); + exit(); +} + +// CHECK-LABEL: define {{.*}} i32 @call__Exit() +int call__Exit() { + // CHECK: entry: + // CHECK: call void @_Exit() + // CHECK: unreachable + defer x(1); + _Exit(); +} + +// CHECK-LABEL: define {{.*}} i32 @call_foobar() +int call_foobar() { + // CHECK: entry: + // CHECK: call void @foobar() + // CHECK: unreachable + defer x(1); + foobar(); +} + +// CHECK-LABEL: define {{.*}} i32 @main() +int main() { + // CHECK: entry: + // CHECK: %retval = alloca i32, align 4 + // CHECK: store i32 0, ptr %retval, align 4 + // CHECK: store i32 5, ptr %retval, align 4 + // CHECK: call void @x(i32 {{.*}} 42) + // CHECK: %0 = load i32, ptr %retval, align 4 + // CHECK: ret i32 %0 + defer x(42); + return 5; +} + +// CHECK-LABEL: define {{.*}} void @t() +// CHECK: entry: +// CHECK-NEXT: %count = alloca i32, align 4 +// CHECK-NEXT: %cleanup.dest.slot = alloca i32, align 4 +// CHECK-NEXT: store i32 0, ptr %count, align 4 +// CHECK-NEXT: br label %target +// CHECK: target: +// CHECK-NEXT: %0 = load i32, ptr %count, align 4 +// CHECK-NEXT: %inc = add nsw i32 %0, 1 +// CHECK-NEXT: store i32 %inc, ptr %count, align 4 +// CHECK-NEXT: %1 = load i32, ptr %count, align 4 +// CHECK-NEXT: %cmp = icmp sle i32 %1, 2 +// CHECK-NEXT: br i1 %cmp, label %if.then, label %if.end +// CHECK: if.then: +// CHECK-NEXT: store i32 2, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: if.end: +// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: cleanup: +// CHECK-NEXT: %cleanup.dest.saved = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: call void @x(i32 {{.*}} 1) +// CHECK-NEXT: store i32 %cleanup.dest.saved, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: switch i32 %cleanup.dest, label %unreachable [ +// CHECK-NEXT: i32 0, label %cleanup.cont +// CHECK-NEXT: i32 2, label %target +// CHECK-NEXT: ] +// CHECK: cleanup.cont: +// CHECK-NEXT: call void @x(i32 {{.*}} 2) +// CHECK-NEXT: ret void +// CHECK: unreachable: +// CHECK-NEXT: unreachable +void t() { + int count = 0; + + { + target: + _Defer { x(1); } + ++count; + if (count <= 2) { + goto target; + } + } + + x(2); +} + +// CHECK-LABEL: define {{.*}} void @stmt_expr() +// CHECK: entry: +// CHECK-NEXT: %tmp = alloca i32, align 4 +// CHECK-NEXT: call void @x(i32 {{.*}} 1) +// CHECK-NEXT: call void @x(i32 {{.*}} 2) +// CHECK-NEXT: call void @x(i32 {{.*}} 3) +// CHECK-NEXT: call void @x(i32 {{.*}} 4) +// CHECK-NEXT: store i32 6, ptr %tmp, align 4 +// CHECK-NEXT: call void @x(i32 {{.*}} 5) +// CHECK-NEXT: %0 = load i32, ptr %tmp, align 4 +// CHECK-NEXT: call void @x(i32 {{.*}} %0) +// CHECK-NEXT: ret void +void stmt_expr() { + ({ + _Defer x(4); + _Defer ({ + _Defer x(3); + x(2); + }); + x(1); + }); + + x(({ + _Defer x(5); + 6; + })); +} + +// CHECK-LABEL: define {{.*}} void @cleanup_no_insert_point() +// CHECK: entry: +// CHECK-NEXT: %cleanup.dest.slot = alloca i32, align 4 +// CHECK-NEXT: br label %while.cond +// CHECK: while.cond: +// CHECK-NEXT: %call = call {{.*}} i1 @q(i32 {{.*}} 1) +// CHECK-NEXT: br i1 %call, label %while.body, label %while.end +// CHECK: while.body: +// CHECK-NEXT: %call1 = call {{.*}} i1 @q(i32 {{.*}} 2) +// CHECK-NEXT: br i1 %call1, label %if.then, label %if.end +// CHECK: if.then: +// CHECK-NEXT: store i32 2, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: if.end: +// CHECK-NEXT: %call2 = call {{.*}} i1 @q(i32 {{.*}} 3) +// CHECK-NEXT: br i1 %call2, label %if.then3, label %if.end4 +// CHECK: if.then3: +// CHECK-NEXT: store i32 3, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: if.end4: +// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: cleanup: +// CHECK-NEXT: %cleanup.dest.saved = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: call void @noreturn() +// CHECK-NEXT: unreachable +// CHECK: 0: +// CHECK-NEXT: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: switch i32 %cleanup.dest, label %unreachable [ +// CHECK-NEXT: i32 0, label %cleanup.cont +// CHECK-NEXT: i32 2, label %while.cond +// CHECK-NEXT: i32 3, label %while.end +// CHECK-NEXT: ] +// CHECK: cleanup.cont: +// CHECK-NEXT: br label %while.cond +// CHECK: while.end: +// CHECK-NEXT: ret void +// CHECK: unreachable: +// CHECK-NEXT: unreachable +void cleanup_no_insert_point() { + while (q(1)) { + _Defer { + noreturn(); + }; + if (q(2)) continue; + if (q(3)) break; + } +} + +// CHECK-LABEL: define {{.*}} void @cleanup_nested() +// CHECK: entry: +// CHECK-NEXT: %cleanup.dest.slot = alloca i32, align 4 +// CHECK-NEXT: br label %while.cond +// CHECK: while.cond: +// CHECK-NEXT: %call = call {{.*}} i1 @q(i32 {{.*}} 1) +// CHECK-NEXT: br i1 %call, label %while.body, label %while.end19 +// CHECK: while.body: +// CHECK-NEXT: %call1 = call {{.*}} i1 @q(i32 {{.*}} 6) +// CHECK-NEXT: br i1 %call1, label %if.then, label %if.end +// CHECK: if.then: +// CHECK-NEXT: store i32 2, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: if.end: +// CHECK-NEXT: %call2 = call {{.*}} i1 @q(i32 {{.*}} 7) +// CHECK-NEXT: br i1 %call2, label %if.then3, label %if.end4 +// CHECK: if.then3: +// CHECK-NEXT: store i32 3, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: if.end4: +// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup +// CHECK: cleanup: +// CHECK-NEXT: %cleanup.dest.saved = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %while.cond5 +// CHECK: while.cond5: +// CHECK-NEXT: %call6 = call {{.*}} i1 @q(i32 {{.*}} 2) +// CHECK-NEXT: br i1 %call6, label %while.body7, label %while.end +// CHECK: while.body7: +// CHECK-NEXT: %call8 = call {{.*}} i1 @q(i32 {{.*}} 4) +// CHECK-NEXT: br i1 %call8, label %if.then9, label %if.end10 +// CHECK: if.then9: +// CHECK-NEXT: store i32 4, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup14 +// CHECK: if.end10: +// CHECK-NEXT: %call11 = call {{.*}} i1 @q(i32 {{.*}} 5) +// CHECK-NEXT: br i1 %call11, label %if.then12, label %if.end13 +// CHECK: if.then12: +// CHECK-NEXT: store i32 5, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup14 +// CHECK: if.end13: +// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: br label %cleanup14 +// CHECK: cleanup14: +// CHECK-NEXT: %cleanup.dest.saved15 = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: %call16 = call {{.*}} i1 @q(i32 {{.*}} 3) +// CHECK-NEXT: store i32 %cleanup.dest.saved15, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: switch i32 %cleanup.dest, label %unreachable [ +// CHECK-NEXT: i32 0, label %cleanup.cont +// CHECK-NEXT: i32 4, label %while.cond5 +// CHECK-NEXT: i32 5, label %while.end +// CHECK-NEXT: ] +// CHECK: cleanup.cont: +// CHECK-NEXT: br label %while.cond5 +// CHECK: while.end: +// CHECK-NEXT: store i32 %cleanup.dest.saved, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: %cleanup.dest17 = load i32, ptr %cleanup.dest.slot, align 4 +// CHECK-NEXT: switch i32 %cleanup.dest17, label %unreachable [ +// CHECK-NEXT: i32 0, label %cleanup.cont18 +// CHECK-NEXT: i32 2, label %while.cond +// CHECK-NEXT: i32 3, label %while.end19 +// CHECK-NEXT: ] +// CHECK: cleanup.cont18: +// CHECK-NEXT: br label %while.cond +// CHECK: while.end19: +// CHECK-NEXT: ret void +// CHECK: unreachable: +// CHECK-NEXT: unreachable +void cleanup_nested() { + while (q(1)) { + _Defer { + while (q(2)) { + _Defer { + q(3); + } + if (q(4)) continue; + if (q(5)) break; + } + }; + if (q(6)) continue; + if (q(7)) break; + } +} diff --git a/clang/test/Lexer/defer-keyword.cpp b/clang/test/Lexer/defer-keyword.cpp new file mode 100644 index 0000000000000..929f2c58f974a --- /dev/null +++ b/clang/test/Lexer/defer-keyword.cpp @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +// RUN: %clang_cc1 -fsyntax-only -verify -fdefer-ts %s + +// expected-no-diagnostics +int _Defer; diff --git a/clang/test/Parser/defer-ts.c b/clang/test/Parser/defer-ts.c new file mode 100644 index 0000000000000..118fe9ee3cc8f --- /dev/null +++ b/clang/test/Parser/defer-ts.c @@ -0,0 +1,58 @@ +// RUN: %clang_cc1 -std=c11 -fsyntax-only -fdefer-ts -verify %s +// RUN: %clang_cc1 -std=c23 -fsyntax-only -fdefer-ts -verify %s + +#define defer _Defer + +int g(void); +int h(int x); + +void f1(void) { + defer 1; // expected-warning {{expression result unused}} + defer 1 + 1; // expected-warning {{expression result unused}} + defer "a"; // expected-warning {{expression result unused}} + defer "a" "b" "c"; // expected-warning {{expression result unused}} + defer defer 1; // expected-warning {{expression result unused}} + defer defer defer defer 1; // expected-warning {{expression result unused}} + defer (int) 4; // expected-warning {{expression result unused}} + defer g(); + + defer {} + defer { defer {} } + defer { defer {} defer {} } + + defer if (g()) g(); + defer while (g()) g(); + defer for (int i = 0; i < 10; i++) h(i); + defer switch (g()) { case 1: g(); } + + defer; // expected-warning {{defer statement has empty body}} expected-note {{put the semicolon on a separate line}} + defer + ; + + defer a: g(); // expected-error {{substatement of defer must not be a label}} + defer b: {} // expected-error {{substatement of defer must not be a label}} + defer { c: g(); } + + if (g()) defer g(); + while (g()) defer g(); + defer ({}); + ({ defer g(); }); + + defer int x; // expected-error {{expected expression}} + defer void q() {} // expected-error {{expected expression}} +} + +void f2(void) { + [[some, attributes]] defer g(); // expected-warning 2 {{unknown attribute}} + __attribute__((some_attribute)) defer g(); // expected-warning {{unknown attribute}} + [[some, attributes]] defer { g(); } // expected-warning 2 {{unknown attribute}} + __attribute__((some_attribute)) defer { g(); } // expected-warning {{unknown attribute}} +} + +void f3(void) { + _Defer 1; // expected-warning {{expression result unused}} + _Defer {} + _Defer _Defer {} + _Defer { defer {} _Defer {} } + _Defer if (g()) g(); +} diff --git a/clang/test/Parser/defer-ts.cpp b/clang/test/Parser/defer-ts.cpp new file mode 100644 index 0000000000000..fa25cac8575f6 --- /dev/null +++ b/clang/test/Parser/defer-ts.cpp @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fdefer-ts -verify %s + +void f() { + _Defer {} // expected-error {{use of undeclared identifier '_Defer'}} +} diff --git a/clang/test/Preprocessor/defer-ts.c b/clang/test/Preprocessor/defer-ts.c new file mode 100644 index 0000000000000..e4995ac9b23ea --- /dev/null +++ b/clang/test/Preprocessor/defer-ts.c @@ -0,0 +1,9 @@ +// RUN: %clang_cc1 -fsyntax-only -fdefer-ts -verify=enabled %s +// RUN: %clang_cc1 -fsyntax-only -verify=disabled %s +// RUN: %clang_cc1 -x c++ -fsyntax-only -fdefer-ts -verify=disabled %s +// RUN: %clang_cc1 -x c++ -fsyntax-only -verify=disabled %s +// enabled-no-diagnostics +#if __STDC_DEFER_TS25755__ != 1 +// disabled-error@+1 {{Should have defined __STDC_DEFER_TS25755__ to 1}} +# error Should have defined __STDC_DEFER_TS25755__ to 1 +#endif diff --git a/clang/test/Sema/defer-ts-seh.c b/clang/test/Sema/defer-ts-seh.c new file mode 100644 index 0000000000000..4b773ed3f09a0 --- /dev/null +++ b/clang/test/Sema/defer-ts-seh.c @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -std=c23 -fdefer-ts -fms-compatibility -triple x86_64-windows-msvc -fsyntax-only -verify %s + +void f() { + __try { + _Defer { + __leave; // expected-error {{cannot __leave a defer statement}} + } + } __finally {} + + __try { + _Defer { + __try { + __leave; + } __finally {} + } + } __finally {} +} diff --git a/clang/test/Sema/defer-ts-sjlj.c b/clang/test/Sema/defer-ts-sjlj.c new file mode 100644 index 0000000000000..49230fa721e0f --- /dev/null +++ b/clang/test/Sema/defer-ts-sjlj.c @@ -0,0 +1,52 @@ +// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=gnu23 -fdefer-ts -fsyntax-only -fblocks -verify %s + +typedef void** jmp_buf; +typedef void** sigjmp_buf; + +int setjmp(jmp_buf env); +int _setjmp(jmp_buf env); +int sigsetjmp(sigjmp_buf env, int savesigs); +int __sigsetjmp(sigjmp_buf env, int savesigs); +void longjmp(jmp_buf env, int val); +void _longjmp(jmp_buf env, int val); +void siglongjmp(sigjmp_buf env, int val); + +jmp_buf x; +sigjmp_buf y; +void f() { + _Defer { + __builtin_setjmp(x); // expected-error {{cannot use '__builtin_setjmp' inside a defer statement}} + __builtin_longjmp(x, 1); // expected-error {{cannot use '__builtin_longjmp' inside a defer statement}} + setjmp(x); // expected-error {{cannot use 'setjmp' inside a defer statement}} + _setjmp(x); // expected-error {{cannot use '_setjmp' inside a defer statement}} + sigsetjmp(y, 0); // expected-error {{cannot use 'sigsetjmp' inside a defer statement}} + __sigsetjmp(y, 0); // expected-error {{cannot use '__sigsetjmp' inside a defer statement}} + longjmp(x, 0); // expected-error {{cannot use 'longjmp' inside a defer statement}} + _longjmp(x, 0); // expected-error {{cannot use '_longjmp' inside a defer statement}} + siglongjmp(y, 0); // expected-error {{cannot use 'siglongjmp' inside a defer statement}} + + (void) ^{ + __builtin_setjmp(x); + __builtin_longjmp(x, 1); + setjmp(x); + _setjmp(x); + sigsetjmp(y, 0); + __sigsetjmp(y, 0); + longjmp(x, 0); + _longjmp(x, 0); + siglongjmp(y, 0); + + _Defer { + __builtin_setjmp(x); // expected-error {{cannot use '__builtin_setjmp' inside a defer statement}} + __builtin_longjmp(x, 1); // expected-error {{cannot use '__builtin_longjmp' inside a defer statement}} + setjmp(x); // expected-error {{cannot use 'setjmp' inside a defer statement}} + _setjmp(x); // expected-error {{cannot use '_setjmp' inside a defer statement}} + sigsetjmp(y, 0); // expected-error {{cannot use 'sigsetjmp' inside a defer statement}} + __sigsetjmp(y, 0); // expected-error {{cannot use '__sigsetjmp' inside a defer statement}} + longjmp(x, 0); // expected-error {{cannot use 'longjmp' inside a defer statement}} + _longjmp(x, 0); // expected-error {{cannot use '_longjmp' inside a defer statement}} + siglongjmp(y, 0); // expected-error {{cannot use 'siglongjmp' inside a defer statement}} + } + }; + } +} diff --git a/clang/test/Sema/defer-ts.c b/clang/test/Sema/defer-ts.c new file mode 100644 index 0000000000000..95c68fa213eaa --- /dev/null +++ b/clang/test/Sema/defer-ts.c @@ -0,0 +1,172 @@ +// RUN: %clang_cc1 -std=c23 -fdefer-ts -fsyntax-only -verify %s + +#define defer _Defer + +void a(); + +void f1() { + defer { + goto l1; + l1: + } + + defer { + l2: + goto l2; + } +} + +void f2() { + goto l1; // expected-error {{cannot jump from this goto statement to its label}} + defer { // expected-note {{jump enters a defer statement}} + l1: + } + + goto l2; // expected-error {{cannot jump from this goto statement to its label}} + defer {} // expected-note {{jump bypasses defer statement}} + l2: +} + +void f3() { + x: + defer { // expected-note {{jump exits a defer statement}} + goto x; // expected-error {{cannot jump from this goto statement to its label}} + } +} + +void f4() { + defer { // expected-note {{jump exits a defer statement}} + goto y; // expected-error {{cannot jump from this goto statement to its label}} + } + y: +} + +void f5() { + defer { // expected-note {{jump enters a defer statement}} + l2: + } + goto l2; // expected-error {{cannot jump from this goto statement to its label}} +} + +void f6() { + goto b; // expected-error {{cannot jump from this goto statement to its label}} + { + defer {} // expected-note {{jump bypasses defer statement}} + b: + } + + { + defer {} // expected-note {{jump bypasses defer statement}} + b2: + } + goto b2; // expected-error {{cannot jump from this goto statement to its label}} +} + +void f7() { + defer { // expected-note {{jump bypasses defer statement}} + goto cross1; // expected-error {{cannot jump from this goto statement to its label}} + cross2: + } + defer { // expected-note {{jump exits a defer statement}} expected-note {{jump enters a defer statement}} + goto cross2; // expected-error {{cannot jump from this goto statement to its label}} + cross1: + } +} + +void f8() { + defer { + return; // expected-error {{cannot return from a defer statement}} + } + + { + defer { + return; // expected-error {{cannot return from a defer statement}} + } + } + + switch (1) { + case 1: defer { + break; // expected-error {{cannot break out of a defer statement}} + } + } + + for (;;) { + defer { + break; // expected-error {{cannot break out of a defer statement}} + } + } + + for (;;) { + defer { + continue; // expected-error {{cannot continue loop outside of enclosing defer statement}} + } + } + + switch (1) { + defer {} // expected-note {{jump bypasses defer statement}} + default: // expected-error {{cannot jump from switch statement to this case label}} + defer {} + break; + } + + switch (1) { + case 1: { + defer { // expected-note {{jump enters a defer statement}} + case 2: {} // expected-error {{cannot jump from switch statement to this case label}} + } + } + } + + switch (1) { + case 1: defer { + switch (2) { case 2: break; } + } + } + + for (;;) { + defer { for (;;) break; } + } + + for (;;) { + defer { for (;;) continue; } + } +} + +void f9() { + { + defer {} + goto l1; + } + l1: + + { + goto l2; + defer {} + } + l2: + + { + { defer {} } + goto l3; + } + l3: + + { + defer {} + { goto l4; } + } + l4: +} + +void f10(int i) { + switch (i) { + defer case 12: break; // expected-error {{cannot break out of a defer statement}} \ + expected-error {{cannot jump from switch statement to this case label}} \ + expected-note {{jump enters a defer statement}} \ + expected-note {{jump bypasses defer statement}} + + defer default: break; // expected-error {{cannot break out of a defer statement}} \ + expected-error {{cannot jump from switch statement to this case label}} \ + expected-note {{jump enters a defer statement}} + } +} diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp index 0a43d73063c1f..c49ca567049c7 100644 --- a/clang/tools/libclang/CXCursor.cpp +++ b/clang/tools/libclang/CXCursor.cpp @@ -224,6 +224,11 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent, K = CXCursor_ReturnStmt; break; + // Not exposed for now because '_Defer' is currently just a TS. + case Stmt::DeferStmtClass: + K = CXCursor_UnexposedStmt; + break; + case Stmt::GCCAsmStmtClass: K = CXCursor_GCCAsmStmt; break;