[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:
parent
3b04094f36
commit
71bfdd1304
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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, {})
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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'">;
|
||||
|
||||
@ -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">;
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.")
|
||||
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -2061,6 +2061,7 @@ enum StmtCode {
|
||||
// HLSL Constructs
|
||||
EXPR_HLSL_OUT_ARG,
|
||||
|
||||
STMT_DEFER,
|
||||
};
|
||||
|
||||
/// The kinds of designators that can occur in a
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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 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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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__");
|
||||
|
||||
|
||||
19
clang/lib/Headers/stddefer.h
Normal file
19
clang/lib/Headers/stddefer.h
Normal 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 */
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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>()) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
27
clang/test/AST/ast-dump-defer-ts.c
Normal file
27
clang/test/AST/ast-dump-defer-ts.c
Normal 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>
|
||||
33
clang/test/AST/ast-print-defer-ts.c
Normal file
33
clang/test/AST/ast-print-defer-ts.c
Normal 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) {
|
||||
}
|
||||
}
|
||||
7
clang/test/CodeGen/defer-ts-musttail.c
Normal file
7
clang/test/CodeGen/defer-ts-musttail.c
Normal 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}}
|
||||
}
|
||||
179
clang/test/CodeGen/defer-ts-nested-cleanups.c
Normal file
179
clang/test/CodeGen/defer-ts-nested-cleanups.c
Normal 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;
|
||||
}
|
||||
}
|
||||
44
clang/test/CodeGen/defer-ts-seh.c
Normal file
44
clang/test/CodeGen/defer-ts-seh.c
Normal 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
|
||||
652
clang/test/CodeGen/defer-ts.c
Normal file
652
clang/test/CodeGen/defer-ts.c
Normal 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;
|
||||
}
|
||||
}
|
||||
5
clang/test/Lexer/defer-keyword.cpp
Normal file
5
clang/test/Lexer/defer-keyword.cpp
Normal 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;
|
||||
58
clang/test/Parser/defer-ts.c
Normal file
58
clang/test/Parser/defer-ts.c
Normal 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();
|
||||
}
|
||||
5
clang/test/Parser/defer-ts.cpp
Normal file
5
clang/test/Parser/defer-ts.cpp
Normal 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'}}
|
||||
}
|
||||
9
clang/test/Preprocessor/defer-ts.c
Normal file
9
clang/test/Preprocessor/defer-ts.c
Normal 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
|
||||
17
clang/test/Sema/defer-ts-seh.c
Normal file
17
clang/test/Sema/defer-ts-seh.c
Normal 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 {}
|
||||
}
|
||||
52
clang/test/Sema/defer-ts-sjlj.c
Normal file
52
clang/test/Sema/defer-ts-sjlj.c
Normal 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
172
clang/test/Sema/defer-ts.c
Normal 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}}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user