[clang] [clang-tools-extra] [Clang] Add support for the C `defer` TS (PR #162848)

via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 10 07:11:18 PDT 2025


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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 at 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 at 0@f@@"(i8 {{.*}} 1, ptr {{.*}} %3) [ "funclet"(token %2) ]
+// CHECK:   cleanupret from %2 unwind to caller
+
+// CHECK-LABEL: define {{.*}} void @"?fin$0 at 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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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);
 



More information about the cfe-commits mailing list