https://github.com/Sirraide created https://github.com/llvm/llvm-project/pull/162848
I was talking to @AaronBallman about this, and we decided it would make sense to open a PR for this at this point, even if we ultimately decide to defer merging it to a later point in time. >From a2f523c46338d53bc2c9efb522ddfc8309d44212 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 01:48:41 +0200 Subject: [PATCH 01/20] [Clang] Add support for the C 'defer' TS --- .../clang-tidy/bugprone/BranchCloneCheck.cpp | 6 +++ clang/include/clang/AST/RecursiveASTVisitor.h | 1 + clang/include/clang/AST/Stmt.h | 51 +++++++++++++++++++ .../clang/Basic/DiagnosticParseKinds.td | 3 ++ .../clang/Basic/DiagnosticSemaKinds.td | 4 ++ clang/include/clang/Basic/LangOptions.def | 1 + clang/include/clang/Basic/StmtNodes.td | 1 + clang/include/clang/Basic/TokenKinds.def | 4 ++ clang/include/clang/Driver/Options.td | 8 +++ clang/include/clang/Parse/Parser.h | 10 ++++ clang/include/clang/Sema/Sema.h | 2 + .../include/clang/Serialization/ASTBitCodes.h | 1 + clang/lib/AST/StmtPrinter.cpp | 7 +++ clang/lib/AST/StmtProfile.cpp | 2 + clang/lib/Basic/IdentifierTable.cpp | 5 +- clang/lib/CodeGen/CGStmt.cpp | 1 + clang/lib/Driver/ToolChains/Clang.cpp | 3 ++ clang/lib/Parse/ParseStmt.cpp | 27 ++++++++++ clang/lib/Sema/SemaExceptionSpec.cpp | 1 + clang/lib/Sema/SemaStmt.cpp | 6 +++ clang/lib/Sema/TreeTransform.h | 8 +++ clang/lib/Serialization/ASTReaderStmt.cpp | 10 ++++ clang/lib/Serialization/ASTWriterStmt.cpp | 7 +++ clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 1 + clang/test/Lexer/defer-keyword.cpp | 5 ++ clang/test/Parser/defer-ts.c | 41 +++++++++++++++ clang/test/Parser/defer-ts.cpp | 9 ++++ clang/tools/libclang/CXCursor.cpp | 5 ++ 28 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 clang/test/Lexer/defer-keyword.cpp create mode 100644 clang/test/Parser/defer-ts.c create mode 100644 clang/test/Parser/defer-ts.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp index a6cd68edda55e..80b1006a70b0f 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp @@ -241,6 +241,12 @@ static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1, return false; return true; } + case Stmt::DeferStmtClass: { + const auto *DefStmt1 = cast<DeferStmt>(Stmt1); + const auto *DefStmt2 = cast<DeferStmt>(Stmt2); + return isIdenticalStmt(Ctx, DefStmt1->getBody(), DefStmt2->getBody(), + IgnoreSideEffects); + } case Stmt::CompoundStmtClass: { const auto *CompStmt1 = cast<CompoundStmt>(Stmt1); const auto *CompStmt2 = cast<CompoundStmt>(Stmt2); diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index 62991d986e675..0527e5cbd7875 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2476,6 +2476,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 a5b0d5053003f..22daceb77d490 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -327,6 +327,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 { @@ -1329,6 +1339,7 @@ class alignas(void *) Stmt { BreakStmtBitfields BreakStmtBits; ReturnStmtBitfields ReturnStmtBits; SwitchCaseBitfields SwitchCaseBits; + DeferStmtBitfields DeferStmtBits; // Expressions ExprBitfields ExprBits; @@ -3201,6 +3212,46 @@ class ReturnStmt final } }; +/// DeferStmt - This represents a deferred statement. +class DeferStmt : public Stmt { + friend class ASTStmtReader; + + /// The deferred statement. + Stmt *Body; + +public: + DeferStmt(SourceLocation DeferLoc, Stmt *Body) : Stmt(DeferStmtClass) { + setDeferLoc(DeferLoc); + setBody(Body); + } + + explicit DeferStmt(EmptyShell Empty) : Stmt(DeferStmtClass, Empty) {} + + SourceLocation getDeferLoc() const { return DeferStmtBits.DeferLoc; } + void setDeferLoc(SourceLocation DeferLoc) { + DeferStmtBits.DeferLoc = DeferLoc; + } + + 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 0042afccba2c8..4b88e7cbf3ec2 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -345,6 +345,9 @@ 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<"body of 'defer' statement cannot start with a label">; +def err_defer_unsupported : Error<"'defer' statements are only supported in C">; 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 cf23594201143..d90d7dc97f590 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10884,6 +10884,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<EmptyBody>; def warn_empty_for_body : Warning< @@ -10894,6 +10896,8 @@ def warn_empty_while_body : Warning< "while loop has empty body">, InGroup<EmptyBody>; def warn_empty_switch_body : Warning< "switch statement has empty body">, InGroup<EmptyBody>; +def warn_empty_defer_body : Warning<"defer statement has empty body">, + InGroup<EmptyBody>; 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/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 08d98a77e0252..dcf1bc3d8a508 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -191,6 +191,7 @@ LANGOPT(NoHonorInfs , 1, 0, Benign, "Permit Floating Point optimization wi LANGOPT(NoSignedZero , 1, 0, Benign, "Permit Floating Point optimization without regard to signed zeros") LANGOPT(AllowRecip , 1, 0, Benign, "Permit Floating Point reciprocal") LANGOPT(ApproxFunc , 1, 0, Benign, "Permit Floating Point approximation") +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 c9c173f5c7469..f38e3896cbb40 100644 --- a/clang/include/clang/Basic/StmtNodes.td +++ b/clang/include/clang/Basic/StmtNodes.td @@ -19,6 +19,7 @@ def IndirectGotoStmt : StmtNode<Stmt>; def ContinueStmt : StmtNode<Stmt>; def BreakStmt : StmtNode<Stmt>; def ReturnStmt : StmtNode<Stmt>; +def DeferStmt : StmtNode<Stmt>; def DeclStmt : StmtNode<Stmt>; def SwitchCase : StmtNode<Stmt, 1>; def CaseStmt : StmtNode<SwitchCase>; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 94e72fea56a68..fdf4078e9af32 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/Driver/Options.td b/clang/include/clang/Driver/Options.td index 6aab43c9ed57f..4457c397d27cf 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1642,6 +1642,14 @@ defm auto_import : BoolFOption<"auto-import", def offload_EQ : CommaJoined<["--"], "offload=">, Flags<[NoXarchOption]>, Alias<offload_targets_EQ>, HelpText<"Specify comma-separated list of offloading target triples (CUDA and HIP only)">; +// C 'defer' TS +defm defer_ts + : BoolFOption< + "defer-ts", LangOpts<"DeferTS">, DefaultFalse, + PosFlag<SetTrue, [], [ClangOption, CC1Option], + "Enable support for the C 'defer' Technical Specification">, + NegFlag<SetFalse>>; + // C++ Coroutines defm coroutines : BoolFOption<"coroutines", LangOpts<"Coroutines">, Default<cpp20.KeyPath>, diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index e9437e6d46366..8feb6577e7921 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -7484,6 +7484,16 @@ class Parser : public CodeCompletionHandler { /// \endverbatim StmtResult ParseReturnStatement(); + /// 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 5211373367677..1c413fdb13b84 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -11035,6 +11035,8 @@ class Sema final : public SemaBase { SourceLocation StarLoc, Expr *DestExp); StmtResult ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope); StmtResult ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope); + StmtResult ActOnDeferStmt(SourceLocation DeferLoc, 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 441047d64f48c..b287539681ded 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -2060,6 +2060,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/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index 6ba5ec89964a9..1503c049dffb0 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -485,6 +485,13 @@ void StmtPrinter::VisitBreakStmt(BreakStmt *Node) { if (Policy.IncludeNewlines) OS << NL; } +void StmtPrinter::VisitDeferStmt(DeferStmt *Node) { + Indent() << "defer "; + PrintStmt(Node->getBody()); + if (Policy.IncludeNewlines) + OS << NL; +} + 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 0297f9c38dee3..122cbb7f8d76a 100644 --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -324,6 +324,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 4a2b77cd16bfc..79f9eb696d9e8 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -110,7 +110,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. @@ -215,6 +216,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 1a8c6f015bda1..22bc97031fd53 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -114,6 +114,7 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef<const Attr *> 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"); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 6eb77610079b7..8c09a873bbeb6 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7050,6 +7050,9 @@ 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, 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/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index bf1978c22ee9f..9654c31504879 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -309,6 +309,10 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( Res = ParseReturnStatement(); SemiError = "co_return"; break; + case tok::kw_defer: // C defer TS: defer-statement + ProhibitAttributes(GNUAttrs); + ProhibitAttributes(CXX11Attrs); + return ParseDeferStatement(TrailingElseLoc); case tok::kw_asm: { for (const ParsedAttr &AL : CXX11Attrs) @@ -2336,6 +2340,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(); + + StmtResult Res = ParseStatement(TrailingElseLoc); + if (!Res.isUsable()) + return StmtError(); + + // Diagnose this *after* parsing the body for better synchronisation. + if (getLangOpts().CPlusPlus) { + Diag(DeferLoc, diag::err_defer_unsupported); + return StmtError(); + } + + // The grammar specifically calls for an unlabeled-statement here. + if (auto *L = dyn_cast<LabelStmt>(Res.get())) { + Diag(L->getIdentLoc(), diag::err_defer_ts_labeled_stmt); + return StmtError(); + } + + return Actions.ActOnDeferStmt(DeferLoc, Res.get(), getCurScope()); +} + StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index 0a6cea8869c14..b5539c19bdc86 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -1537,6 +1537,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/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index a5f92020f49f8..966ba3c96bdf9 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3879,6 +3879,12 @@ Sema::ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp, return R; } +StmtResult Sema::ActOnDeferStmt(SourceLocation DeferLoc, Stmt *Body, Scope *) { + DiagnoseEmptyStmtBody(DeferLoc, Body, diag::warn_empty_defer_body); + setFunctionHasBranchProtectedScope(); + return new (Context) DeferStmt(DeferLoc, Body); +} + static bool CheckSimplerImplicitMovesMSVCWorkaround(const Sema &S, const Expr *E) { if (!E || !S.getLangOpts().CPlusPlus23 || !S.getLangOpts().MSVCCompat) diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 0030946301a93..3a4988c656aea 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -8562,6 +8562,14 @@ TreeTransform<Derived>::TransformBreakStmt(BreakStmt *S) { return S; } +template <typename Derived> +StmtResult TreeTransform<Derived>::TransformDeferStmt(DeferStmt *S) { + StmtResult Result = getDerived().TransformStmt(S->getBody()); + if (!Result.isUsable()) + return StmtError(); + return new (getSema().Context) DeferStmt(S->getDeferLoc(), Result.get()); +} + template<typename Derived> StmtResult TreeTransform<Derived>::TransformReturnStmt(ReturnStmt *S) { diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 3f37dfbc3dea9..18e0c1858d509 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -330,6 +330,12 @@ void ASTStmtReader::VisitBreakStmt(BreakStmt *S) { S->setBreakLoc(readSourceLocation()); } +void ASTStmtReader::VisitDeferStmt(DeferStmt *S) { + VisitStmt(S); + S->setDeferLoc(readSourceLocation()); + S->setBody(Record.readSubStmt()); +} + void ASTStmtReader::VisitReturnStmt(ReturnStmt *S) { VisitStmt(S); @@ -3126,6 +3132,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) { S = new (Context) BreakStmt(Empty); break; + case STMT_DEFER: + S = new (Context) DeferStmt(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 be9bad9e96cc1..42ec46425a862 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -322,6 +322,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 d87484470f8b5..adb5b25157bc8 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1868,6 +1868,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/Lexer/defer-keyword.cpp b/clang/test/Lexer/defer-keyword.cpp new file mode 100644 index 0000000000000..692d351241cb3 --- /dev/null +++ b/clang/test/Lexer/defer-keyword.cpp @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -fsyntax-only -verify=disabled %s +// RUN: %clang_cc1 -fsyntax-only -verify=enabled -fdefer-ts %s + +// disabled-no-diagnostics +int defer; // enabled-error {{expected unqualified-id}} diff --git a/clang/test/Parser/defer-ts.c b/clang/test/Parser/defer-ts.c new file mode 100644 index 0000000000000..1638f6a22d363 --- /dev/null +++ b/clang/test/Parser/defer-ts.c @@ -0,0 +1,41 @@ +// RUN: %clang_cc1 -std=c11 -fsyntax-only -fdefer-ts -verify %s +// RUN: %clang_cc1 -std=c23 -fsyntax-only -fdefer-ts -verify %s + +int g(void); +int h(int x); + +void f(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 {{body of 'defer' statement cannot start with a label}} + defer b: {} // expected-error {{body of 'defer' statement cannot start with 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}} +} diff --git a/clang/test/Parser/defer-ts.cpp b/clang/test/Parser/defer-ts.cpp new file mode 100644 index 0000000000000..57067743dda46 --- /dev/null +++ b/clang/test/Parser/defer-ts.cpp @@ -0,0 +1,9 @@ +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fdefer-ts -verify %s + +// FIXME: Do we want to support 'defer' in C++? For now, we just reject +// it in the parser if '-fdefer-ts' is passed, but if we decide *not* to +// support it in C++, then we should probably strip out and warn about +// that flag in the driver (or frontend?) instead. +void f() { + defer {} // expected-error {{'defer' statements are only supported in C}} +} diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp index a6301daa672c3..f713689b59431 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; >From 0a756c55e2760aebe75217525ce7ef4e641af323 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 02:44:29 +0200 Subject: [PATCH 02/20] Implement codegen --- clang/lib/CodeGen/CGStmt.cpp | 18 ++++++++ clang/lib/CodeGen/CodeGenFunction.h | 1 + clang/test/CodeGen/defer-ts.c | 66 +++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 clang/test/CodeGen/defer-ts.c diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 22bc97031fd53..55b7fc820d610 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -537,6 +537,9 @@ bool CodeGenFunction::EmitSimpleStmt(const Stmt *S, case Stmt::CaseStmtClass: EmitCaseStmt(cast<CaseStmt>(*S), Attrs); break; + case Stmt::DeferStmtClass: + EmitDeferStmt(cast<DeferStmt>(*S)); + break; case Stmt::SEHLeaveStmtClass: EmitSEHLeaveStmt(cast<SEHLeaveStmt>(*S)); break; @@ -1984,6 +1987,21 @@ 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 flags) override { + CGF.EmitStmt(Stmt.getBody()); + } +}; +} // namespace + +void CodeGenFunction::EmitDeferStmt(const DeferStmt &S) { + EHStack.pushCleanup<EmitDeferredStatement>(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 6c32c98cec011..d958ba1c94ea3 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3606,6 +3606,7 @@ class CodeGenFunction : public CodeGenTypeCache { void EmitDefaultStmt(const DefaultStmt &S, ArrayRef<const Attr *> Attrs); void EmitCaseStmt(const CaseStmt &S, ArrayRef<const Attr *> Attrs); void EmitCaseStmtRange(const CaseStmt &S, ArrayRef<const Attr *> Attrs); + void EmitDeferStmt(const DeferStmt &S); void EmitAsmStmt(const AsmStmt &S); void EmitObjCForCollectionStmt(const ObjCForCollectionStmt &S); diff --git a/clang/test/CodeGen/defer-ts.c b/clang/test/CodeGen/defer-ts.c new file mode 100644 index 0000000000000..af789c0ec1691 --- /dev/null +++ b/clang/test/CodeGen/defer-ts.c @@ -0,0 +1,66 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c23 -fdefer-ts -emit-llvm %s -o - | FileCheck %s + +void a(); +void b(); +void c(); +void x(int q); + +// 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: %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: call void @x(i32 noundef 1) + // CHECK: store i32 0, ptr %cleanup.dest.slot, align 4 + // CHECK: br label %cleanup + // CHECK: cleanup: + // CHECK: call void @x(i32 noundef 2) + // 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); +} >From 2505774f506459cb0360d0d5358b5cf79541239c Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 03:56:58 +0200 Subject: [PATCH 03/20] Check gotos around defer --- .../clang/Basic/DiagnosticSemaKinds.td | 3 ++ clang/lib/Sema/JumpDiagnostics.cpp | 27 +++++++++- clang/lib/Sema/SemaStmt.cpp | 1 + clang/test/Sema/defer-ts.c | 51 +++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 clang/test/Sema/defer-ts.c diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index d90d7dc97f590..7fc525a2e2a32 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6765,6 +6765,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< @@ -6778,6 +6779,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))">; @@ -6823,6 +6825,7 @@ 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_func_returning_qualified_void : ExtWarn< "function cannot return qualified void type %0">, diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp index 36704c3826dfd..304137f1e4a87 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<DeferStmt>(S); + + { + // Disallow jumps over defer statements. + unsigned NewParentScope = Scopes.size(); + Scopes.emplace_back(ParentScope, diag::note_protected_by_defer_stmt, + diag::note_exits_defer_stmt, 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<GotoStmt>(From) || isa<IndirectGotoStmt>(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/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 966ba3c96bdf9..c3b4a98a80ad0 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3274,6 +3274,7 @@ Sema::ActOnIndirectGotoStmt(SourceLocation GotoLoc, SourceLocation StarLoc, return new (Context) IndirectGotoStmt(GotoLoc, StarLoc, E); } +// TODO: Also check for 'defer' here. static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc, const Scope &DestScope) { if (!S.CurrentSEHFinally.empty() && diff --git a/clang/test/Sema/defer-ts.c b/clang/test/Sema/defer-ts.c new file mode 100644 index 0000000000000..78e0362e400ed --- /dev/null +++ b/clang/test/Sema/defer-ts.c @@ -0,0 +1,51 @@ +// RUN: %clang_cc1 -std=c23 -fdefer-ts -fsyntax-only -verify %s + +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 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: + } +} >From 58a6c2d854380b02d4cc954f8eac90236fca3977 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 04:52:49 +0200 Subject: [PATCH 04/20] Forbid break/continue/return/__leave out of defer --- .../clang/Basic/DiagnosticSemaKinds.td | 7 +++ clang/include/clang/Sema/Sema.h | 9 +++- clang/lib/Parse/ParseStmt.cpp | 7 ++- clang/lib/Sema/SemaStmt.cpp | 41 +++++++++++++---- clang/test/Sema/defer-ts-seh.c | 17 +++++++ clang/test/Sema/defer-ts.c | 44 +++++++++++++++++++ 6 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 clang/test/Sema/defer-ts-seh.c diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 7fc525a2e2a32..67e66992179c8 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6826,6 +6826,13 @@ def note_exits_block_captures_non_trivial_c_struct : Note< 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<DeferJumpKind>{" + "%Break{break out of a}|" + "%Continue{continue loop outside of enclosing}|" + "%Return{return from a}|" + "%SEHLeave{__leave a}" + "}0 defer statement">; def err_func_returning_qualified_void : ExtWarn< "function cannot return qualified void type %0">, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 1c413fdb13b84..9dccddd559a82 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10892,6 +10892,10 @@ class Sema final : public SemaBase { /// Stack of active SEH __finally scopes. Can be empty. SmallVector<Scope *, 2> CurrentSEHFinally; + /// Stack of 'defer' statements that are currently being parsed, as well + /// as the locations of their 'defer' keywords. Can be empty. + SmallVector<std::pair<Scope *, SourceLocation>, 2> CurrentDefer; + StmtResult ActOnExprStmt(ExprResult Arg, bool DiscardedValue = true); StmtResult ActOnExprStmtError(); @@ -11035,8 +11039,9 @@ class Sema final : public SemaBase { SourceLocation StarLoc, Expr *DestExp); StmtResult ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope); StmtResult ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope); - StmtResult ActOnDeferStmt(SourceLocation DeferLoc, Stmt *Body, - Scope *CurScope); + 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/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 9654c31504879..3f0834e87fa59 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 <optional> using namespace clang; @@ -2343,6 +2344,9 @@ StmtResult Parser::ParseReturnStatement() { 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()) @@ -2360,7 +2364,8 @@ StmtResult Parser::ParseDeferStatement(SourceLocation *TrailingElseLoc) { return StmtError(); } - return Actions.ActOnDeferStmt(DeferLoc, Res.get(), getCurScope()); + OnError.release(); + return Actions.ActOnEndOfDeferStmt(Res.get(), getCurScope()); } StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts, diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index c3b4a98a80ad0..cc0038c9f6b00 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3274,13 +3274,22 @@ Sema::ActOnIndirectGotoStmt(SourceLocation GotoLoc, SourceLocation StarLoc, return new (Context) IndirectGotoStmt(GotoLoc, StarLoc, E); } -// TODO: Also check for 'defer' here. -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; + + // 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; + } } StmtResult @@ -3305,7 +3314,8 @@ 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); } @@ -3335,7 +3345,8 @@ 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); } @@ -3875,12 +3886,25 @@ Sema::ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp, CurScope->updateNRVOCandidate(VD); - CheckJumpOutOfSEHFinally(*this, ReturnLoc, *CurScope->getFnParent()); + CheckJumpOutOfSEHFinallyOrDefer(*this, ReturnLoc, *CurScope->getFnParent(), + diag::DeferJumpKind::Return); return R; } -StmtResult Sema::ActOnDeferStmt(SourceLocation DeferLoc, Stmt *Body, Scope *) { +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 new (Context) DeferStmt(DeferLoc, Body); @@ -4503,7 +4527,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/test/Sema/defer-ts-seh.c b/clang/test/Sema/defer-ts-seh.c new file mode 100644 index 0000000000000..015306ca107d7 --- /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.c b/clang/test/Sema/defer-ts.c index 78e0362e400ed..69c587fa47aa4 100644 --- a/clang/test/Sema/defer-ts.c +++ b/clang/test/Sema/defer-ts.c @@ -49,3 +49,47 @@ void f5() { cross1: } } + +void f6() { + 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) { + case 1: defer { + switch (2) { case 2: break; } + } + } + + for (;;) { + defer { for (;;) break; } + } + + for (;;) { + defer { for (;;) continue; } + } +} >From 0a5c8fa1aec43e223f8490a591c0760d33e52822 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 05:11:58 +0200 Subject: [PATCH 05/20] Jumping out of a scope that contains a defer is fine --- clang/lib/Sema/JumpDiagnostics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp index 304137f1e4a87..36c9d9afb37f1 100644 --- a/clang/lib/Sema/JumpDiagnostics.cpp +++ b/clang/lib/Sema/JumpDiagnostics.cpp @@ -596,8 +596,8 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S, { // Disallow jumps over defer statements. unsigned NewParentScope = Scopes.size(); - Scopes.emplace_back(ParentScope, diag::note_protected_by_defer_stmt, - diag::note_exits_defer_stmt, D->getDeferLoc()); + Scopes.emplace_back(ParentScope, diag::note_protected_by_defer_stmt, 0, + D->getDeferLoc()); origParentScope = NewParentScope; } >From b9e845de4a622f3af4d20a18b9708459c430ed4e Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 05:57:41 +0200 Subject: [PATCH 06/20] Add a bunch of tests --- clang/test/CodeGen/defer-ts.c | 236 +++++++++++++++++++++++++++++++++- clang/test/Sema/defer-ts.c | 64 ++++++++- 2 files changed, 297 insertions(+), 3 deletions(-) diff --git a/clang/test/CodeGen/defer-ts.c b/clang/test/CodeGen/defer-ts.c index af789c0ec1691..86232b3d5e561 100644 --- a/clang/test/CodeGen/defer-ts.c +++ b/clang/test/CodeGen/defer-ts.c @@ -46,11 +46,11 @@ void f3(bool ret) { // CHECK: store i32 1, ptr %cleanup.dest.slot, align 4 // CHECK: br label %cleanup // CHECK: if.end: - // CHECK: call void @x(i32 noundef 1) + // CHECK: call void @x(i32 {{.*}} 1) // CHECK: store i32 0, ptr %cleanup.dest.slot, align 4 // CHECK: br label %cleanup // CHECK: cleanup: - // CHECK: call void @x(i32 noundef 2) + // CHECK: call void @x(i32 {{.*}} 2) // CHECK: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4 // CHECK: switch i32 %cleanup.dest, label %unreachable [ // CHECK: i32 0, label %cleanup.cont @@ -64,3 +64,235 @@ void f3(bool ret) { 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: call void @x(i32 {{.*}} 42) + // 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); +} diff --git a/clang/test/Sema/defer-ts.c b/clang/test/Sema/defer-ts.c index 69c587fa47aa4..7423c7df1d3f1 100644 --- a/clang/test/Sema/defer-ts.c +++ b/clang/test/Sema/defer-ts.c @@ -40,6 +40,27 @@ void f4() { } 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: @@ -50,7 +71,7 @@ void f5() { } } -void f6() { +void f8() { defer { return; // expected-error {{cannot return from a defer statement}} } @@ -79,6 +100,21 @@ void f6() { } } + 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; } @@ -93,3 +129,29 @@ void f6() { defer { for (;;) continue; } } } + +void f9() { + { + defer {} + goto l1; + } + l1: + + { + goto l2; + defer {} + } + l2: + + { + { defer {} } + goto l3; + } + l3: + + { + defer {} + { goto l4; } + } + l4: +} >From 2eaecf5fddf8a86e07e1c5ea055a292edeb0d435 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 06:00:34 +0200 Subject: [PATCH 07/20] Define __STDC_DEFER_TS25755__ --- clang/lib/Frontend/InitPreprocessor.cpp | 3 +++ clang/test/Preprocessor/defer-ts.c | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 clang/test/Preprocessor/defer-ts.c diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 008a35d5265e1..6fef8a8e60988 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -529,6 +529,9 @@ static void InitializeStandardPredefinedMacros(const TargetInfo &TI, Builder.defineMacro("__STDC_EMBED_EMPTY__", llvm::itostr(static_cast<int>(EmbedResult::Empty))); + if (LangOpts.DeferTS) + Builder.defineMacro("__STDC_DEFER_TS25755__", "1"); + if (LangOpts.ObjC) Builder.defineMacro("__OBJC__"); diff --git a/clang/test/Preprocessor/defer-ts.c b/clang/test/Preprocessor/defer-ts.c new file mode 100644 index 0000000000000..ef7268bc9f432 --- /dev/null +++ b/clang/test/Preprocessor/defer-ts.c @@ -0,0 +1,4 @@ +// RUN: %clang_cc1 -fdefer-ts -fsyntax-only %s +#if __STDC_DEFER_TS25755__ != 1 +# error Should have defined __STDC_DEFER_TS25755__ +#endif >From e74537cf0d34d61257ca6748ced0d7aee2472f15 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 06:03:16 +0200 Subject: [PATCH 08/20] Add test for 'defer defer' --- clang/test/CodeGen/defer-ts.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clang/test/CodeGen/defer-ts.c b/clang/test/CodeGen/defer-ts.c index 86232b3d5e561..6efa20d40528b 100644 --- a/clang/test/CodeGen/defer-ts.c +++ b/clang/test/CodeGen/defer-ts.c @@ -296,3 +296,19 @@ void defer_stmt(int q) { // 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); +} >From 11a40d0f52cab457bafdfb33ea736e2ac80bba9c Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 06:05:52 +0200 Subject: [PATCH 09/20] Add test for 'main' since the TS mentions it explicitly --- clang/test/CodeGen/defer-ts.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/clang/test/CodeGen/defer-ts.c b/clang/test/CodeGen/defer-ts.c index 6efa20d40528b..f86e354d78b42 100644 --- a/clang/test/CodeGen/defer-ts.c +++ b/clang/test/CodeGen/defer-ts.c @@ -312,3 +312,16 @@ void defer_defer() { defer defer defer defer x(1); x(0); } + +// 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 noundef 42) + // CHECK: %0 = load i32, ptr %retval, align 4 + // CHECK: ret i32 %0 + defer x(42); + return 5; +} >From bbf4389346e2cf9f450a90dd48909bc472b931ec Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 06:17:40 +0200 Subject: [PATCH 10/20] Fix AST printer --- clang/lib/AST/StmtPrinter.cpp | 6 ++---- clang/test/AST/ast-print-defer-ts.c | 33 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 clang/test/AST/ast-print-defer-ts.c diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index 1503c049dffb0..3f111ed5e4117 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -486,10 +486,8 @@ void StmtPrinter::VisitBreakStmt(BreakStmt *Node) { } void StmtPrinter::VisitDeferStmt(DeferStmt *Node) { - Indent() << "defer "; - PrintStmt(Node->getBody()); - if (Policy.IncludeNewlines) - OS << NL; + Indent() << "defer"; + PrintControlledStmt(Node->getBody()); } void StmtPrinter::VisitReturnStmt(ReturnStmt *Node) { 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..be59fe1059a2a --- /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) { + } +} >From 3350e288bbe2352e80e2cf25238901406bbc3f25 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 06:24:42 +0200 Subject: [PATCH 11/20] Add AST dump and serialisation test --- clang/test/AST/ast-dump-defer-ts.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 clang/test/AST/ast-dump-defer-ts.c 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..cd11d92f23e8f --- /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 {{.*}} <col:24, line:14:1> +// CHECK-NEXT: |-DeferStmt {{.*}} <line:11:3, col:9> +// CHECK-NEXT: | `-IntegerLiteral {{.*}} <col:9> 'int' 3 +// CHECK-NEXT: |-DeferStmt {{.*}} <line:12:3, col:14> +// CHECK-NEXT: | `-CompoundStmt {{.*}} <col:9, col:14> +// CHECK-NEXT: | `-IntegerLiteral {{.*}} <col:11> 'int' 4 +// CHECK-NEXT: `-DeferStmt {{.*}} <line:13:3, col:26> +// CHECK-NEXT: `-DeferStmt {{.*}} <col:9, col:26> +// CHECK-NEXT: `-IfStmt {{.*}} <col:15, col:26> +// CHECK-NEXT: |-CXXBoolLiteralExpr {{.*}} <col:19> 'bool' true +// CHECK-NEXT: `-CompoundStmt {{.*}} <col:25, col:26> >From 40b449bae40d12de7f5ac096d6bf4e4b10a11b10 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 13:38:19 +0200 Subject: [PATCH 12/20] Add VLA test and rename -fdefer-ts -> -fexperimental-defer-ts --- clang/include/clang/Driver/Options.td | 4 +- clang/lib/Driver/ToolChains/Clang.cpp | 5 +- clang/test/AST/ast-dump-defer-ts.c | 6 +-- clang/test/AST/ast-print-defer-ts.c | 2 +- clang/test/CodeGen/defer-ts.c | 73 ++++++++++++++++++++++++++- clang/test/Lexer/defer-keyword.cpp | 2 +- clang/test/Parser/defer-ts.c | 4 +- clang/test/Parser/defer-ts.cpp | 4 +- clang/test/Preprocessor/defer-ts.c | 2 +- clang/test/Sema/defer-ts-seh.c | 2 +- clang/test/Sema/defer-ts.c | 2 +- 11 files changed, 88 insertions(+), 18 deletions(-) diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 4457c397d27cf..eb62320c82854 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1643,9 +1643,9 @@ def offload_EQ : CommaJoined<["--"], "offload=">, Flags<[NoXarchOption]>, Alias< HelpText<"Specify comma-separated list of offloading target triples (CUDA and HIP only)">; // C 'defer' TS -defm defer_ts +defm experimental_defer_ts : BoolFOption< - "defer-ts", LangOpts<"DeferTS">, DefaultFalse, + "experimental-defer-ts", LangOpts<"DeferTS">, DefaultFalse, PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable support for the C 'defer' Technical Specification">, NegFlag<SetFalse>>; diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 8c09a873bbeb6..b93cd8a4ba49d 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7050,8 +7050,9 @@ 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, false)) - CmdArgs.push_back("-fdefer-ts"); + if (Args.hasFlag(options::OPT_fexperimental_defer_ts, + options::OPT_fno_experimental_defer_ts, false)) + CmdArgs.push_back("-fexperimental-defer-ts"); Args.AddLastArg(CmdArgs, options::OPT_fdouble_square_bracket_attributes, options::OPT_fno_double_square_bracket_attributes); diff --git a/clang/test/AST/ast-dump-defer-ts.c b/clang/test/AST/ast-dump-defer-ts.c index cd11d92f23e8f..097ce85fb257b 100644 --- a/clang/test/AST/ast-dump-defer-ts.c +++ b/clang/test/AST/ast-dump-defer-ts.c @@ -1,10 +1,10 @@ // Test without serialization: -// RUN: %clang_cc1 -std=c23 -fdefer-ts -ast-dump %s -triple x86_64-linux-gnu \ +// RUN: %clang_cc1 -std=c23 -fexperimental-defer-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: %clang_cc1 -std=c23 -fexperimental-defer-ts -triple x86_64-linux-gnu -emit-pch -o %t %s +// RUN: %clang_cc1 -std=c23 -fexperimental-defer-ts -triple x86_64-linux-gnu -include-pch %t -ast-dump-all /dev/null \ // RUN: | FileCheck %s static inline void f() { diff --git a/clang/test/AST/ast-print-defer-ts.c b/clang/test/AST/ast-print-defer-ts.c index be59fe1059a2a..b767fd3be9f77 100644 --- a/clang/test/AST/ast-print-defer-ts.c +++ b/clang/test/AST/ast-print-defer-ts.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c23 -fdefer-ts -ast-print %s | FileCheck %s +// RUN: %clang_cc1 -std=c23 -fexperimental-defer-ts -ast-print %s | FileCheck %s void g(); diff --git a/clang/test/CodeGen/defer-ts.c b/clang/test/CodeGen/defer-ts.c index f86e354d78b42..a10248f1cb617 100644 --- a/clang/test/CodeGen/defer-ts.c +++ b/clang/test/CodeGen/defer-ts.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c23 -fdefer-ts -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c23 -fexperimental-defer-ts -emit-llvm %s -o - | FileCheck %s void a(); void b(); @@ -313,13 +313,82 @@ void defer_defer() { 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_stack2 = 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: %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: br label %cleanup + // CHECK: cleanup: + // 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_stack2, align 8 + // CHECK: %vla3 = alloca i32, i64 %8, align 16 + // CHECK: store i64 %8, ptr %__vla_expr1, align 8 + // CHECK: %arrayidx4 = getelementptr inbounds i32, ptr %vla3, i64 2 + // CHECK: store i32 3, ptr %arrayidx4, align 8 + // CHECK: %arrayidx5 = getelementptr inbounds i32, ptr %vla3, i64 2 + // CHECK: %10 = load i32, ptr %arrayidx5, 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_stack2, align 8 + // CHECK: call void @llvm.stackrestore.p0(ptr %12) + // 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; +} + // 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 noundef 42) + // CHECK: call void @x(i32 {{.*}} 42) // CHECK: %0 = load i32, ptr %retval, align 4 // CHECK: ret i32 %0 defer x(42); diff --git a/clang/test/Lexer/defer-keyword.cpp b/clang/test/Lexer/defer-keyword.cpp index 692d351241cb3..d7dd6524b5615 100644 --- a/clang/test/Lexer/defer-keyword.cpp +++ b/clang/test/Lexer/defer-keyword.cpp @@ -1,5 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -verify=disabled %s -// RUN: %clang_cc1 -fsyntax-only -verify=enabled -fdefer-ts %s +// RUN: %clang_cc1 -fsyntax-only -verify=enabled -fexperimental-defer-ts %s // disabled-no-diagnostics int defer; // enabled-error {{expected unqualified-id}} diff --git a/clang/test/Parser/defer-ts.c b/clang/test/Parser/defer-ts.c index 1638f6a22d363..5ca02d51702e9 100644 --- a/clang/test/Parser/defer-ts.c +++ b/clang/test/Parser/defer-ts.c @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -std=c11 -fsyntax-only -fdefer-ts -verify %s -// RUN: %clang_cc1 -std=c23 -fsyntax-only -fdefer-ts -verify %s +// RUN: %clang_cc1 -std=c11 -fsyntax-only -fexperimental-defer-ts -verify %s +// RUN: %clang_cc1 -std=c23 -fsyntax-only -fexperimental-defer-ts -verify %s int g(void); int h(int x); diff --git a/clang/test/Parser/defer-ts.cpp b/clang/test/Parser/defer-ts.cpp index 57067743dda46..a8158c2bc0779 100644 --- a/clang/test/Parser/defer-ts.cpp +++ b/clang/test/Parser/defer-ts.cpp @@ -1,7 +1,7 @@ -// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fdefer-ts -verify %s +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fexperimental-defer-ts -verify %s // FIXME: Do we want to support 'defer' in C++? For now, we just reject -// it in the parser if '-fdefer-ts' is passed, but if we decide *not* to +// it in the parser if '-fexperimental-defer-ts' is passed, but if we decide *not* to // support it in C++, then we should probably strip out and warn about // that flag in the driver (or frontend?) instead. void f() { diff --git a/clang/test/Preprocessor/defer-ts.c b/clang/test/Preprocessor/defer-ts.c index ef7268bc9f432..d61cc740e3843 100644 --- a/clang/test/Preprocessor/defer-ts.c +++ b/clang/test/Preprocessor/defer-ts.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fdefer-ts -fsyntax-only %s +// RUN: %clang_cc1 -fexperimental-defer-ts -fsyntax-only %s #if __STDC_DEFER_TS25755__ != 1 # error Should have defined __STDC_DEFER_TS25755__ #endif diff --git a/clang/test/Sema/defer-ts-seh.c b/clang/test/Sema/defer-ts-seh.c index 015306ca107d7..e9c955efa596b 100644 --- a/clang/test/Sema/defer-ts-seh.c +++ b/clang/test/Sema/defer-ts-seh.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c23 -fdefer-ts -fms-compatibility -triple x86_64-windows-msvc -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c23 -fexperimental-defer-ts -fms-compatibility -triple x86_64-windows-msvc -fsyntax-only -verify %s void f() { __try { diff --git a/clang/test/Sema/defer-ts.c b/clang/test/Sema/defer-ts.c index 7423c7df1d3f1..8aef754de3089 100644 --- a/clang/test/Sema/defer-ts.c +++ b/clang/test/Sema/defer-ts.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c23 -fdefer-ts -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c23 -fexperimental-defer-ts -fsyntax-only -verify %s void a(); >From 97067116f48efa4d1b20e45a4e1e5f9552804645 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 13:54:29 +0200 Subject: [PATCH 13/20] Add a SEH test --- clang/test/CodeGen/defer-ts-seh.c | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 clang/test/CodeGen/defer-ts-seh.c diff --git a/clang/test/CodeGen/defer-ts-seh.c b/clang/test/CodeGen/defer-ts-seh.c new file mode 100644 index 0000000000000..be85ce7ce6b48 --- /dev/null +++ b/clang/test/CodeGen/defer-ts-seh.c @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=c23 -fexperimental-defer-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 >From d56cfb9d47aa10b89151256d64e7ac0cee62e391 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 13:57:36 +0200 Subject: [PATCH 14/20] Add tests involving [[noreturn]] functions --- clang/test/CodeGen/defer-ts.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/clang/test/CodeGen/defer-ts.c b/clang/test/CodeGen/defer-ts.c index a10248f1cb617..86e5b183118b3 100644 --- a/clang/test/CodeGen/defer-ts.c +++ b/clang/test/CodeGen/defer-ts.c @@ -382,6 +382,37 @@ int vla(int* p, int x) { 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: >From e51b68e9e76bbf7786b70f30f2454566b24cd985 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 14:34:21 +0200 Subject: [PATCH 15/20] Forbid setjmp/longjmp within a defer statement --- .../clang/Basic/DiagnosticSemaKinds.td | 1 + clang/lib/Sema/SemaExpr.cpp | 28 +++++++++++ clang/test/Sema/defer-ts-sjlj.c | 47 +++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 clang/test/Sema/defer-ts-sjlj.c diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 67e66992179c8..b55dba2b2bc37 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6833,6 +6833,7 @@ def err_jump_out_of_defer_stmt "%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">, diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 6793d6da85cb1..2469df23f050b 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -6838,6 +6838,34 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl, FunctionDecl *FDecl = dyn_cast_or_null<FunctionDecl>(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()) { + assert(!LangOpts.CPlusPlus); + 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->getDeclName(); + } + // Functions with 'interrupt' attribute cannot be called directly. if (FDecl) { if (FDecl->hasAttr<AnyX86InterruptAttr>()) { diff --git a/clang/test/Sema/defer-ts-sjlj.c b/clang/test/Sema/defer-ts-sjlj.c new file mode 100644 index 0000000000000..0e431a7c21be0 --- /dev/null +++ b/clang/test/Sema/defer-ts-sjlj.c @@ -0,0 +1,47 @@ +// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=gnu23 -fexperimental-defer-ts -fsyntax-only -fblocks -verify %s + +typedef long jmp_buf[100]; +typedef long sigjmp_buf[100]; + +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 { + 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) ^{ + setjmp(x); + _setjmp(x); + sigsetjmp(y, 0); + __sigsetjmp(y, 0); + longjmp(x, 0); + _longjmp(x, 0); + siglongjmp(y, 0); + + defer { + 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}} + } + }; + } +} >From 8948a464c4eceaf18dc466f3dc505060a6fda6b3 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 14:34:46 +0200 Subject: [PATCH 16/20] clang-format --- clang/lib/Sema/SemaExpr.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 2469df23f050b..302257424efa6 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -6840,18 +6840,18 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl, 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; + 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; } }; >From b944bde3819002544ed0f05de7abb12bff73ce0e Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 14:37:19 +0200 Subject: [PATCH 17/20] Test __builtin_setjmp/longjmp as well --- clang/test/Sema/defer-ts-sjlj.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/clang/test/Sema/defer-ts-sjlj.c b/clang/test/Sema/defer-ts-sjlj.c index 0e431a7c21be0..0cd27d595fbd5 100644 --- a/clang/test/Sema/defer-ts-sjlj.c +++ b/clang/test/Sema/defer-ts-sjlj.c @@ -1,7 +1,7 @@ // RUN: %clang_cc1 -triple x86_64-windows-msvc -std=gnu23 -fexperimental-defer-ts -fsyntax-only -fblocks -verify %s -typedef long jmp_buf[100]; -typedef long sigjmp_buf[100]; +typedef void** jmp_buf; +typedef void** sigjmp_buf; int setjmp(jmp_buf env); int _setjmp(jmp_buf env); @@ -13,9 +13,10 @@ 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}} @@ -25,6 +26,8 @@ void f() { 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); @@ -34,6 +37,8 @@ void f() { 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}} >From fbc4f5aa8414e794a4927f1209a85a0c06a7e646 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Thu, 7 Aug 2025 14:38:52 +0200 Subject: [PATCH 18/20] Remove assertion --- clang/lib/Sema/SemaExpr.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 302257424efa6..0c94bb4aa4701 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -6857,7 +6857,8 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl, // Forbid any call to setjmp/longjmp and friends inside a 'defer' statement. if (!CurrentDefer.empty() && IsSJLJ()) { - assert(!LangOpts.CPlusPlus); + // 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) && >From 2eb59e358abed50659e50334191499bf09d6821e Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Fri, 10 Oct 2025 15:43:50 +0200 Subject: [PATCH 19/20] -fexperimental-defer-ts -> -fdefer-ts --- clang/include/clang/Driver/Options.td | 4 ++-- clang/lib/Driver/ToolChains/Clang.cpp | 5 ++--- clang/test/AST/ast-dump-defer-ts.c | 6 +++--- clang/test/AST/ast-print-defer-ts.c | 2 +- clang/test/CodeGen/defer-ts-seh.c | 2 +- clang/test/CodeGen/defer-ts.c | 2 +- clang/test/Lexer/defer-keyword.cpp | 2 +- clang/test/Parser/defer-ts.c | 4 ++-- clang/test/Parser/defer-ts.cpp | 4 ++-- clang/test/Preprocessor/defer-ts.c | 2 +- clang/test/Sema/defer-ts-seh.c | 2 +- clang/test/Sema/defer-ts-sjlj.c | 2 +- clang/test/Sema/defer-ts.c | 2 +- 13 files changed, 19 insertions(+), 20 deletions(-) diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index b48df8cc64dbe..3d03bf311b6a3 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1654,9 +1654,9 @@ defm named_loops NegFlag<SetFalse>>; // C 'defer' TS -defm experimental_defer_ts +defm defer_ts : BoolFOption< - "experimental-defer-ts", LangOpts<"DeferTS">, DefaultFalse, + "defer-ts", LangOpts<"DeferTS">, DefaultFalse, PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable support for the C 'defer' Technical Specification">, NegFlag<SetFalse>>; diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index cf899bf9f81ad..169127a71e76e 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7066,9 +7066,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, types::isCXX(InputType)) CmdArgs.push_back("-fcoro-aligned-allocation"); - if (Args.hasFlag(options::OPT_fexperimental_defer_ts, - options::OPT_fno_experimental_defer_ts, false)) - CmdArgs.push_back("-fexperimental-defer-ts"); + if (Args.hasFlag(options::OPT_fdefer_ts, options::OPT_fno_defer_ts, 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/test/AST/ast-dump-defer-ts.c b/clang/test/AST/ast-dump-defer-ts.c index 097ce85fb257b..cd11d92f23e8f 100644 --- a/clang/test/AST/ast-dump-defer-ts.c +++ b/clang/test/AST/ast-dump-defer-ts.c @@ -1,10 +1,10 @@ // Test without serialization: -// RUN: %clang_cc1 -std=c23 -fexperimental-defer-ts -ast-dump %s -triple x86_64-linux-gnu \ +// 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 -fexperimental-defer-ts -triple x86_64-linux-gnu -emit-pch -o %t %s -// RUN: %clang_cc1 -std=c23 -fexperimental-defer-ts -triple x86_64-linux-gnu -include-pch %t -ast-dump-all /dev/null \ +// 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() { diff --git a/clang/test/AST/ast-print-defer-ts.c b/clang/test/AST/ast-print-defer-ts.c index b767fd3be9f77..be59fe1059a2a 100644 --- a/clang/test/AST/ast-print-defer-ts.c +++ b/clang/test/AST/ast-print-defer-ts.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c23 -fexperimental-defer-ts -ast-print %s | FileCheck %s +// RUN: %clang_cc1 -std=c23 -fdefer-ts -ast-print %s | FileCheck %s void g(); diff --git a/clang/test/CodeGen/defer-ts-seh.c b/clang/test/CodeGen/defer-ts-seh.c index be85ce7ce6b48..57f216bbe64cb 100644 --- a/clang/test/CodeGen/defer-ts-seh.c +++ b/clang/test/CodeGen/defer-ts-seh.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=c23 -fexperimental-defer-ts -fms-compatibility -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=c23 -fdefer-ts -fms-compatibility -emit-llvm %s -o - | FileCheck %s void g(); void h(); diff --git a/clang/test/CodeGen/defer-ts.c b/clang/test/CodeGen/defer-ts.c index 86e5b183118b3..f72eb5bca8539 100644 --- a/clang/test/CodeGen/defer-ts.c +++ b/clang/test/CodeGen/defer-ts.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c23 -fexperimental-defer-ts -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c23 -fdefer-ts -emit-llvm %s -o - | FileCheck %s void a(); void b(); diff --git a/clang/test/Lexer/defer-keyword.cpp b/clang/test/Lexer/defer-keyword.cpp index d7dd6524b5615..692d351241cb3 100644 --- a/clang/test/Lexer/defer-keyword.cpp +++ b/clang/test/Lexer/defer-keyword.cpp @@ -1,5 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -verify=disabled %s -// RUN: %clang_cc1 -fsyntax-only -verify=enabled -fexperimental-defer-ts %s +// RUN: %clang_cc1 -fsyntax-only -verify=enabled -fdefer-ts %s // disabled-no-diagnostics int defer; // enabled-error {{expected unqualified-id}} diff --git a/clang/test/Parser/defer-ts.c b/clang/test/Parser/defer-ts.c index 5ca02d51702e9..1638f6a22d363 100644 --- a/clang/test/Parser/defer-ts.c +++ b/clang/test/Parser/defer-ts.c @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -std=c11 -fsyntax-only -fexperimental-defer-ts -verify %s -// RUN: %clang_cc1 -std=c23 -fsyntax-only -fexperimental-defer-ts -verify %s +// RUN: %clang_cc1 -std=c11 -fsyntax-only -fdefer-ts -verify %s +// RUN: %clang_cc1 -std=c23 -fsyntax-only -fdefer-ts -verify %s int g(void); int h(int x); diff --git a/clang/test/Parser/defer-ts.cpp b/clang/test/Parser/defer-ts.cpp index a8158c2bc0779..57067743dda46 100644 --- a/clang/test/Parser/defer-ts.cpp +++ b/clang/test/Parser/defer-ts.cpp @@ -1,7 +1,7 @@ -// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fexperimental-defer-ts -verify %s +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fdefer-ts -verify %s // FIXME: Do we want to support 'defer' in C++? For now, we just reject -// it in the parser if '-fexperimental-defer-ts' is passed, but if we decide *not* to +// it in the parser if '-fdefer-ts' is passed, but if we decide *not* to // support it in C++, then we should probably strip out and warn about // that flag in the driver (or frontend?) instead. void f() { diff --git a/clang/test/Preprocessor/defer-ts.c b/clang/test/Preprocessor/defer-ts.c index d61cc740e3843..ef7268bc9f432 100644 --- a/clang/test/Preprocessor/defer-ts.c +++ b/clang/test/Preprocessor/defer-ts.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fexperimental-defer-ts -fsyntax-only %s +// RUN: %clang_cc1 -fdefer-ts -fsyntax-only %s #if __STDC_DEFER_TS25755__ != 1 # error Should have defined __STDC_DEFER_TS25755__ #endif diff --git a/clang/test/Sema/defer-ts-seh.c b/clang/test/Sema/defer-ts-seh.c index e9c955efa596b..015306ca107d7 100644 --- a/clang/test/Sema/defer-ts-seh.c +++ b/clang/test/Sema/defer-ts-seh.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c23 -fexperimental-defer-ts -fms-compatibility -triple x86_64-windows-msvc -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c23 -fdefer-ts -fms-compatibility -triple x86_64-windows-msvc -fsyntax-only -verify %s void f() { __try { diff --git a/clang/test/Sema/defer-ts-sjlj.c b/clang/test/Sema/defer-ts-sjlj.c index 0cd27d595fbd5..2b642696e21b2 100644 --- a/clang/test/Sema/defer-ts-sjlj.c +++ b/clang/test/Sema/defer-ts-sjlj.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=gnu23 -fexperimental-defer-ts -fsyntax-only -fblocks -verify %s +// 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; diff --git a/clang/test/Sema/defer-ts.c b/clang/test/Sema/defer-ts.c index 8aef754de3089..7423c7df1d3f1 100644 --- a/clang/test/Sema/defer-ts.c +++ b/clang/test/Sema/defer-ts.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c23 -fexperimental-defer-ts -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c23 -fdefer-ts -fsyntax-only -verify %s void a(); >From d7fc314f31d7e5679f58721811446f551a71eadb Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Fri, 10 Oct 2025 16:06:24 +0200 Subject: [PATCH 20/20] Undo whitespace changes --- clang/include/clang/Driver/Options.td | 4 ++-- clang/include/clang/Parse/Parser.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 3d03bf311b6a3..28429c3aff83e 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -4338,7 +4338,7 @@ def fno_trigraphs : Flag<["-"], "fno-trigraphs">, Group<f_Group>, HelpText<"Do not process trigraph sequences">, Visibility<[ClangOption, CC1Option]>; def funique_source_file_names: Flag<["-"], "funique-source-file-names">, Group<f_Group>, - HelpText<"Allow the compiler to assume that each translation unit has a unique " + HelpText<"Allow the compiler to assume that each translation unit has a unique " "source file identifier (see -funique-source-file-identifier) at link time">; def fno_unique_source_file_names: Flag<["-"], "fno-unique-source-file-names">; def unique_source_file_identifier_EQ: Joined<["-"], "funique-source-file-identifier=">, Group<f_Group>, @@ -7145,7 +7145,7 @@ defm android_pad_segment : BooleanFFlag<"android-pad-segment">, Group<f_Group>; def shared_libflangrt : Flag<["-"], "shared-libflangrt">, HelpText<"Link the flang-rt shared library">, Group<Link_Group>, Visibility<[FlangOption]>, Flags<[NoArgumentUnused]>; -def static_libflangrt : Flag<["-"], "static-libflangrt">, +def static_libflangrt : Flag<["-"], "static-libflangrt">, HelpText<"Link the flang-rt static library">, Group<Link_Group>, Visibility<[FlangOption]>, Flags<[NoArgumentUnused]>; diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index e83da0ae88800..52d8a0238cb2a 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -7684,7 +7684,7 @@ class Parser : public CodeCompletionHandler { /// [GNU] asm-clobbers: /// asm-string-literal /// asm-clobbers ',' asm-string-literal - /// \endverbatim + /// \endverbatim /// StmtResult ParseAsmStatement(bool &msAsm); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
