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