[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.
This commit is contained in:
Sirraide 2025-12-11 05:54:09 +01:00 committed by GitHub
parent 3b04094f36
commit 71bfdd1304
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 1667 additions and 8 deletions

View File

@ -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);

View File

@ -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

View File

@ -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, {})

View File

@ -317,6 +317,16 @@ protected:
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 @@ protected:
LoopControlStmtBitfields LoopControlStmtBits;
ReturnStmtBitfields ReturnStmtBits;
SwitchCaseBitfields SwitchCaseBits;
DeferStmtBitfields DeferStmtBits;
// Expressions
ExprBitfields ExprBits;
@ -3211,6 +3222,47 @@ public:
}
};
/// 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:

View File

@ -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'">;

View File

@ -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">;

View File

@ -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.

View File

@ -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.")

View File

@ -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>;

View File

@ -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)

View File

@ -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>,

View File

@ -7500,6 +7500,16 @@ public:
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,

View File

@ -10935,6 +10935,10 @@ public:
/// 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 @@ public:
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;

View File

@ -2061,6 +2061,7 @@ enum StmtCode {
// HLSL Constructs
EXPR_HLSL_OUT_ARG,
STMT_DEFER,
};
/// The kinds of designators that can occur in a

View File

@ -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);
}

View File

@ -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()) {

View File

@ -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());

View File

@ -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");
}

View File

@ -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 different
// ways; e.g. in a 'for' loop, we may exit the scope of its body by falling
// off the end (in which case we need to run the cleanup and then branch to
// the increment), or by 'break'ing out of the loop (in which case we need
// to run the cleanup and then branch to the loop exit block); in both cases
// we first branch to the cleanup block to run the cleanup, but the block we
// need to jump to *after* running the cleanup is different.
//
// This is accomplished using a local integer variable called the 'cleanup
// slot': before branching to the cleanup block, we store a value into that
// slot. Then, in the cleanup block, after running the cleanup, we load the
// value of that variable and 'switch' on it to branch to the appropriate
// continuation block.
//
// The problem that arises once '_Defer' statements are involved is that the
// body of a '_Defer' is an arbitrary statement which itself can create more
// cleanups. This means we may end up overwriting the cleanup slot before we
// ever have a chance to 'switch' on it, which means that once we *do* get
// to the 'switch', we end up in whatever block the cleanup code happened to
// pick as the default 'switch' exit label!
//
// That is, what is normally supposed to happen is something like:
//
// 1. Store 'X' to cleanup slot.
// 2. Branch to cleanup block.
// 3. Execute cleanup.
// 4. Read value from cleanup slot.
// 5. Branch to the block associated with 'X'.
//
// But if we encounter a _Defer' statement that contains a cleanup, then
// what might instead happen is:
//
// 1. Store 'X' to cleanup slot.
// 2. Branch to cleanup block.
// 3. Execute cleanup; this ends up pushing another cleanup, so:
// 3a. Store 'Y' to cleanup slot.
// 3b. Run steps 25 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,

View File

@ -3622,6 +3622,7 @@ public:
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);

View File

@ -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);

View File

@ -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__");

View File

@ -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 */

View File

@ -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 @@ Retry:
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,

View File

@ -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;
}
}
}

View File

@ -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: {

View File

@ -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>()) {

View File

@ -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);
}

View File

@ -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) {

View File

@ -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]);

View File

@ -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);

View File

@ -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:

View File

@ -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>

View File

@ -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) {
}
}

View File

@ -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}}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,44 @@
// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=c23 -fdefer-ts -fms-compatibility -emit-llvm %s -o - | FileCheck %s
void g();
void h();
void f() {
__try {
_Defer h();
g();
} __finally {
}
}
// CHECK-LABEL: define {{.*}} void @f() {{.*}} personality ptr @__C_specific_handler
// CHECK: entry:
// CHECK: invoke void @g() #4
// CHECK: to label %invoke.cont unwind label %ehcleanup
// CHECK: invoke.cont:
// CHECK: invoke void @h() #4
// CHECK: to label %invoke.cont1 unwind label %ehcleanup3
// CHECK: invoke.cont1:
// CHECK: %0 = call ptr @llvm.localaddress()
// CHECK: call void @"?fin$0@0@f@@"(i8 {{.*}} 0, ptr {{.*}} %0)
// CHECK: ret void
// CHECK: ehcleanup:
// CHECK: %1 = cleanuppad within none []
// CHECK: invoke void @h() #4 [ "funclet"(token %1) ]
// CHECK: to label %invoke.cont2 unwind label %ehcleanup3
// CHECK: invoke.cont2:
// CHECK: cleanupret from %1 unwind label %ehcleanup3
// CHECK: ehcleanup3:
// CHECK: %2 = cleanuppad within none []
// CHECK: %3 = call ptr @llvm.localaddress()
// CHECK: call void @"?fin$0@0@f@@"(i8 {{.*}} 1, ptr {{.*}} %3) [ "funclet"(token %2) ]
// CHECK: cleanupret from %2 unwind to caller
// CHECK-LABEL: define {{.*}} void @"?fin$0@0@f@@"(i8 {{.*}} %abnormal_termination, ptr {{.*}} %frame_pointer)
// CHECK: entry:
// CHECK: %frame_pointer.addr = alloca ptr, align 8
// CHECK: %abnormal_termination.addr = alloca i8, align 1
// CHECK: store ptr %frame_pointer, ptr %frame_pointer.addr, align 8
// CHECK: store i8 %abnormal_termination, ptr %abnormal_termination.addr, align 1
// CHECK: ret void

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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'}}
}

View File

@ -0,0 +1,9 @@
// RUN: %clang_cc1 -fsyntax-only -fdefer-ts -verify=enabled %s
// RUN: %clang_cc1 -fsyntax-only -verify=disabled %s
// RUN: %clang_cc1 -x c++ -fsyntax-only -fdefer-ts -verify=disabled %s
// RUN: %clang_cc1 -x c++ -fsyntax-only -verify=disabled %s
// enabled-no-diagnostics
#if __STDC_DEFER_TS25755__ != 1
// disabled-error@+1 {{Should have defined __STDC_DEFER_TS25755__ to 1}}
# error Should have defined __STDC_DEFER_TS25755__ to 1
#endif

View File

@ -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 {}
}

View File

@ -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}}
}
};
}
}

172
clang/test/Sema/defer-ts.c Normal file
View File

@ -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}}
}
}

View File

@ -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;