Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a2f523c
[Clang] Add support for the C 'defer' TS
Sirraide Aug 6, 2025
0a756c5
Implement codegen
Sirraide Aug 7, 2025
2505774
Check gotos around defer
Sirraide Aug 7, 2025
58a6c2d
Forbid break/continue/return/__leave out of defer
Sirraide Aug 7, 2025
0a5c8fa
Jumping out of a scope that contains a defer is fine
Sirraide Aug 7, 2025
b9e845d
Add a bunch of tests
Sirraide Aug 7, 2025
2eaecf5
Define __STDC_DEFER_TS25755__
Sirraide Aug 7, 2025
e74537c
Add test for 'defer defer'
Sirraide Aug 7, 2025
11a40d0
Add test for 'main' since the TS mentions it explicitly
Sirraide Aug 7, 2025
bbf4389
Fix AST printer
Sirraide Aug 7, 2025
3350e28
Add AST dump and serialisation test
Sirraide Aug 7, 2025
40b449b
Add VLA test and rename -fdefer-ts -> -fexperimental-defer-ts
Sirraide Aug 7, 2025
9706711
Add a SEH test
Sirraide Aug 7, 2025
d56cfb9
Add tests involving [[noreturn]] functions
Sirraide Aug 7, 2025
e51b68e
Forbid setjmp/longjmp within a defer statement
Sirraide Aug 7, 2025
8948a46
clang-format
Sirraide Aug 7, 2025
b944bde
Test __builtin_setjmp/longjmp as well
Sirraide Aug 7, 2025
fbc4f5a
Remove assertion
Sirraide Aug 7, 2025
65c2b88
Merge branch 'main' into defer
Sirraide Oct 10, 2025
2eb59e3
-fexperimental-defer-ts -> -fdefer-ts
Sirraide Oct 10, 2025
d7fc314
Undo whitespace changes
Sirraide Oct 10, 2025
62261b1
Use ShouldParseIf
Sirraide Oct 10, 2025
7106167
Update test
Sirraide Oct 10, 2025
a0b19b8
Add release note
Sirraide Oct 13, 2025
e8f5982
Address some of Aaron’s comments
Sirraide Oct 13, 2025
191b8f7
Add this horrible test case
Sirraide Oct 13, 2025
48a0433
Add musttail test
Sirraide Oct 13, 2025
f117c4b
Allow attributes as an extension
Sirraide Oct 13, 2025
48fc974
Add `_Defer`
Sirraide Oct 17, 2025
6b041e8
Add stddefer.h and make 'defer' no longer a keyword
Sirraide Nov 7, 2025
45c8113
Update stddefer.h
Sirraide Nov 10, 2025
3a8741e
Merge branch 'main' into defer
Sirraide Nov 10, 2025
004c40e
Move KEYDEFERTS to header
Sirraide Nov 10, 2025
34ec829
I accidentally deleted KEYFIXEDPOINT...
Sirraide Nov 10, 2025
0e238f1
Don't clobber the cleanup slot when emitting nested cleanups
Sirraide Dec 4, 2025
24d7212
Add scope reentry test
Sirraide Dec 4, 2025
c18ac11
Address review feedback
Sirraide Dec 4, 2025
53deeda
Merge branch 'main' into defer
Sirraide Dec 4, 2025
2ace560
Update clang/test/Lexer/defer-keyword.cpp
Sirraide Dec 8, 2025
fd13abb
Save and restore the current cleanup destination
Sirraide Dec 8, 2025
2965221
Merge branch 'main' into defer
Sirraide Dec 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/AST/RecursiveASTVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -2560,6 +2560,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, {})
Expand Down
51 changes: 51 additions & 0 deletions clang/include/clang/AST/Stmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -1318,6 +1328,7 @@ class alignas(void *) Stmt {
LoopControlStmtBitfields LoopControlStmtBits;
ReturnStmtBitfields ReturnStmtBits;
SwitchCaseBitfields SwitchCaseBits;
DeferStmtBitfields DeferStmtBits;

// Expressions
ExprBitfields ExprBits;
Expand Down Expand Up @@ -3232,6 +3243,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:
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,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'">;
Expand Down
15 changes: 15 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -6793,6 +6793,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<
Expand All @@ -6806,6 +6807,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))">;
Expand Down Expand Up @@ -6851,6 +6853,15 @@ 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">,
Expand Down Expand Up @@ -10926,6 +10937,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<
Expand All @@ -10936,6 +10949,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">;

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -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.")

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/StmtNodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,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>>;

// C++ Coroutines
defm coroutines : BoolFOption<"coroutines",
LangOpts<"Coroutines">, Default<cpp20.KeyPath>,
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -7501,6 +7501,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,
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -10915,6 +10915,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();

Expand Down Expand Up @@ -11061,6 +11065,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;

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Serialization/ASTBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -2060,6 +2060,7 @@ enum StmtCode {
// HLSL Constructs
EXPR_HLSL_OUT_ARG,

STMT_DEFER,
};

/// The kinds of designators that can occur in a
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/AST/StmtPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/AST/StmtProfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
5 changes: 4 additions & 1 deletion clang/lib/Basic/IdentifierTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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");
}
Expand Down
19 changes: 19 additions & 0 deletions clang/lib/CodeGen/CGStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -536,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;
Expand Down Expand Up @@ -1997,6 +2001,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,
Expand Down
1 change: 1 addition & 0 deletions clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

const BreakContinue *GetDestForLoopControlStmt(const LoopControlStmt &S);
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7066,6 +7066,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);

Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Frontend/InitPreprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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__");

Expand Down
Loading