[Clang] [C2y] Implement N3355 ‘Named Loops’ (#152870)
This implements support for [named loops](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm) for C2y. When parsing a `LabelStmt`, we create the `LabeDecl` early before we parse the substatement; this label is then passed down to `ParseWhileStatement()` and friends, which then store it in the loop’s (or switch statement’s) `Scope`; when we encounter a `break/continue` statement, we perform a lookup for the label (and error if it doesn’t exist), and then walk the scope stack and check if there is a scope whose preceding label is the target label, which identifies the jump target. The feature is only supported in C2y mode, though a cc1-only option exists for testing (`-fnamed-loops`), which is mostly intended to try and make sure that we don’t have to refactor this entire implementation when/if we start supporting it in C++. --------- Co-authored-by: Balazs Benics <benicsbalazs@gmail.com>
This commit is contained in:
parent
83f390859e
commit
e4a1b5f36e
@ -1106,11 +1106,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
bool VisitBreakStmt(BreakStmt *B) {
|
||||
found(Break, B->getBreakLoc());
|
||||
found(Break, B->getKwLoc());
|
||||
return true;
|
||||
}
|
||||
bool VisitContinueStmt(ContinueStmt *C) {
|
||||
found(Continue, C->getContinueLoc());
|
||||
found(Continue, C->getKwLoc());
|
||||
return true;
|
||||
}
|
||||
bool VisitSwitchCase(SwitchCase *C) {
|
||||
|
||||
@ -134,6 +134,7 @@ C Language Changes
|
||||
|
||||
C2y Feature Support
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
- Clang now supports `N3355 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm>`_ Named Loops.
|
||||
|
||||
C23 Feature Support
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -333,6 +333,7 @@ public:
|
||||
void VisitStringLiteral(const StringLiteral *SL);
|
||||
void VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *BLE);
|
||||
|
||||
void VisitLoopControlStmt(const LoopControlStmt *LS);
|
||||
void VisitIfStmt(const IfStmt *IS);
|
||||
void VisitSwitchStmt(const SwitchStmt *SS);
|
||||
void VisitCaseStmt(const CaseStmt *CS);
|
||||
|
||||
@ -277,24 +277,14 @@ protected:
|
||||
SourceLocation GotoLoc;
|
||||
};
|
||||
|
||||
class ContinueStmtBitfields {
|
||||
friend class ContinueStmt;
|
||||
class LoopControlStmtBitfields {
|
||||
friend class LoopControlStmt;
|
||||
|
||||
LLVM_PREFERRED_TYPE(StmtBitfields)
|
||||
unsigned : NumStmtBits;
|
||||
|
||||
/// The location of the "continue".
|
||||
SourceLocation ContinueLoc;
|
||||
};
|
||||
|
||||
class BreakStmtBitfields {
|
||||
friend class BreakStmt;
|
||||
|
||||
LLVM_PREFERRED_TYPE(StmtBitfields)
|
||||
unsigned : NumStmtBits;
|
||||
|
||||
/// The location of the "break".
|
||||
SourceLocation BreakLoc;
|
||||
/// The location of the "continue"/"break".
|
||||
SourceLocation KwLoc;
|
||||
};
|
||||
|
||||
class ReturnStmtBitfields {
|
||||
@ -1325,8 +1315,7 @@ protected:
|
||||
DoStmtBitfields DoStmtBits;
|
||||
ForStmtBitfields ForStmtBits;
|
||||
GotoStmtBitfields GotoStmtBits;
|
||||
ContinueStmtBitfields ContinueStmtBits;
|
||||
BreakStmtBitfields BreakStmtBits;
|
||||
LoopControlStmtBitfields LoopControlStmtBits;
|
||||
ReturnStmtBitfields ReturnStmtBits;
|
||||
SwitchCaseBitfields SwitchCaseBits;
|
||||
|
||||
@ -2184,6 +2173,14 @@ public:
|
||||
SourceLocation getBeginLoc() const { return getIdentLoc(); }
|
||||
SourceLocation getEndLoc() const LLVM_READONLY { return SubStmt->getEndLoc();}
|
||||
|
||||
/// Look through nested labels and return the first non-label statement; e.g.
|
||||
/// if this is 'a:' in 'a: b: c: for(;;)', this returns the for loop.
|
||||
const Stmt *getInnermostLabeledStmt() const;
|
||||
Stmt *getInnermostLabeledStmt() {
|
||||
return const_cast<Stmt *>(
|
||||
const_cast<const LabelStmt *>(this)->getInnermostLabeledStmt());
|
||||
}
|
||||
|
||||
child_range children() { return child_range(&SubStmt, &SubStmt + 1); }
|
||||
|
||||
const_child_range children() const {
|
||||
@ -3056,64 +3053,98 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/// ContinueStmt - This represents a continue.
|
||||
class ContinueStmt : public Stmt {
|
||||
public:
|
||||
ContinueStmt(SourceLocation CL) : Stmt(ContinueStmtClass) {
|
||||
setContinueLoc(CL);
|
||||
/// Base class for BreakStmt and ContinueStmt.
|
||||
class LoopControlStmt : public Stmt {
|
||||
/// If this is a named break/continue, the label whose statement we're
|
||||
/// targeting, as well as the source location of the label after the
|
||||
/// keyword; for example:
|
||||
///
|
||||
/// a: // <-- TargetLabel
|
||||
/// for (;;)
|
||||
/// break a; // <-- LabelLoc
|
||||
///
|
||||
LabelDecl *TargetLabel = nullptr;
|
||||
SourceLocation LabelLoc;
|
||||
|
||||
protected:
|
||||
LoopControlStmt(StmtClass Class, SourceLocation Loc, SourceLocation LabelLoc,
|
||||
LabelDecl *Target)
|
||||
: Stmt(Class), TargetLabel(Target), LabelLoc(LabelLoc) {
|
||||
setKwLoc(Loc);
|
||||
}
|
||||
|
||||
LoopControlStmt(StmtClass Class, SourceLocation Loc)
|
||||
: LoopControlStmt(Class, Loc, SourceLocation(), nullptr) {}
|
||||
|
||||
LoopControlStmt(StmtClass Class, EmptyShell ES) : Stmt(Class, ES) {}
|
||||
|
||||
public:
|
||||
SourceLocation getKwLoc() const { return LoopControlStmtBits.KwLoc; }
|
||||
void setKwLoc(SourceLocation L) { LoopControlStmtBits.KwLoc = L; }
|
||||
|
||||
SourceLocation getBeginLoc() const { return getKwLoc(); }
|
||||
SourceLocation getEndLoc() const {
|
||||
return hasLabelTarget() ? getLabelLoc() : getKwLoc();
|
||||
}
|
||||
|
||||
bool hasLabelTarget() const { return TargetLabel != nullptr; }
|
||||
|
||||
SourceLocation getLabelLoc() const { return LabelLoc; }
|
||||
void setLabelLoc(SourceLocation L) { LabelLoc = L; }
|
||||
|
||||
LabelDecl *getLabelDecl() { return TargetLabel; }
|
||||
const LabelDecl *getLabelDecl() const { return TargetLabel; }
|
||||
void setLabelDecl(LabelDecl *S) { TargetLabel = S; }
|
||||
|
||||
/// If this is a named break/continue, get the loop or switch statement
|
||||
/// that this targets.
|
||||
const Stmt *getNamedLoopOrSwitch() const;
|
||||
|
||||
// Iterators
|
||||
child_range children() {
|
||||
return child_range(child_iterator(), child_iterator());
|
||||
}
|
||||
|
||||
const_child_range children() const {
|
||||
return const_child_range(const_child_iterator(), const_child_iterator());
|
||||
}
|
||||
|
||||
static bool classof(const Stmt *T) {
|
||||
StmtClass Class = T->getStmtClass();
|
||||
return Class == ContinueStmtClass || Class == BreakStmtClass;
|
||||
}
|
||||
};
|
||||
|
||||
/// ContinueStmt - This represents a continue.
|
||||
class ContinueStmt : public LoopControlStmt {
|
||||
public:
|
||||
ContinueStmt(SourceLocation CL) : LoopControlStmt(ContinueStmtClass, CL) {}
|
||||
ContinueStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
|
||||
: LoopControlStmt(ContinueStmtClass, CL, LabelLoc, Target) {}
|
||||
|
||||
/// Build an empty continue statement.
|
||||
explicit ContinueStmt(EmptyShell Empty) : Stmt(ContinueStmtClass, Empty) {}
|
||||
|
||||
SourceLocation getContinueLoc() const { return ContinueStmtBits.ContinueLoc; }
|
||||
void setContinueLoc(SourceLocation L) { ContinueStmtBits.ContinueLoc = L; }
|
||||
|
||||
SourceLocation getBeginLoc() const { return getContinueLoc(); }
|
||||
SourceLocation getEndLoc() const { return getContinueLoc(); }
|
||||
explicit ContinueStmt(EmptyShell Empty)
|
||||
: LoopControlStmt(ContinueStmtClass, Empty) {}
|
||||
|
||||
static bool classof(const Stmt *T) {
|
||||
return T->getStmtClass() == ContinueStmtClass;
|
||||
}
|
||||
|
||||
// Iterators
|
||||
child_range children() {
|
||||
return child_range(child_iterator(), child_iterator());
|
||||
}
|
||||
|
||||
const_child_range children() const {
|
||||
return const_child_range(const_child_iterator(), const_child_iterator());
|
||||
}
|
||||
};
|
||||
|
||||
/// BreakStmt - This represents a break.
|
||||
class BreakStmt : public Stmt {
|
||||
class BreakStmt : public LoopControlStmt {
|
||||
public:
|
||||
BreakStmt(SourceLocation BL) : Stmt(BreakStmtClass) {
|
||||
setBreakLoc(BL);
|
||||
}
|
||||
BreakStmt(SourceLocation BL) : LoopControlStmt(BreakStmtClass, BL) {}
|
||||
BreakStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
|
||||
: LoopControlStmt(BreakStmtClass, CL, LabelLoc, Target) {}
|
||||
|
||||
/// Build an empty break statement.
|
||||
explicit BreakStmt(EmptyShell Empty) : Stmt(BreakStmtClass, Empty) {}
|
||||
|
||||
SourceLocation getBreakLoc() const { return BreakStmtBits.BreakLoc; }
|
||||
void setBreakLoc(SourceLocation L) { BreakStmtBits.BreakLoc = L; }
|
||||
|
||||
SourceLocation getBeginLoc() const { return getBreakLoc(); }
|
||||
SourceLocation getEndLoc() const { return getBreakLoc(); }
|
||||
explicit BreakStmt(EmptyShell Empty)
|
||||
: LoopControlStmt(BreakStmtClass, Empty) {}
|
||||
|
||||
static bool classof(const Stmt *T) {
|
||||
return T->getStmtClass() == BreakStmtClass;
|
||||
}
|
||||
|
||||
// Iterators
|
||||
child_range children() {
|
||||
return child_range(child_iterator(), child_iterator());
|
||||
}
|
||||
|
||||
const_child_range children() const {
|
||||
return const_child_range(const_child_iterator(), const_child_iterator());
|
||||
}
|
||||
};
|
||||
|
||||
/// ReturnStmt - This represents a return, optionally of an expression:
|
||||
|
||||
@ -255,6 +255,7 @@ public:
|
||||
void VisitExpressionTemplateArgument(const TemplateArgument &TA);
|
||||
void VisitPackTemplateArgument(const TemplateArgument &TA);
|
||||
|
||||
void VisitLoopControlStmt(const LoopControlStmt *L);
|
||||
void VisitIfStmt(const IfStmt *Node);
|
||||
void VisitSwitchStmt(const SwitchStmt *Node);
|
||||
void VisitWhileStmt(const WhileStmt *Node);
|
||||
|
||||
@ -215,6 +215,8 @@ def warn_c23_compat_case_range : Warning<
|
||||
DefaultIgnore, InGroup<CPre2yCompat>;
|
||||
def ext_c2y_case_range : Extension<
|
||||
"case ranges are a C2y extension">, InGroup<C2y>;
|
||||
def err_c2y_labeled_break_continue : Error<
|
||||
"named %select{'break'|'continue'}0 is only supported in C2y">;
|
||||
|
||||
// Generic errors.
|
||||
def err_expected_expression : Error<"expected expression">;
|
||||
|
||||
@ -10824,6 +10824,11 @@ def err_continue_not_in_loop : Error<
|
||||
"'continue' statement not in loop statement">;
|
||||
def err_break_not_in_loop_or_switch : Error<
|
||||
"'break' statement not in loop or switch statement">;
|
||||
def err_break_continue_label_not_found : Error<
|
||||
"'%select{break|continue}0' label does not name an enclosing "
|
||||
"%select{loop or 'switch'|loop}0">;
|
||||
def err_continue_switch : Error<
|
||||
"label of 'continue' refers to a switch statement">;
|
||||
def warn_loop_ctrl_binds_to_inner : Warning<
|
||||
"'%0' is bound to current loop, GCC binds it to the enclosing loop">,
|
||||
InGroup<GccCompat>;
|
||||
|
||||
@ -193,6 +193,7 @@ LANGOPT(NoHonorInfs , 1, 0, Benign, "Permit Floating Point optimization wi
|
||||
LANGOPT(NoSignedZero , 1, 0, Benign, "Permit Floating Point optimization without regard to signed zeros")
|
||||
LANGOPT(AllowRecip , 1, 0, Benign, "Permit Floating Point reciprocal")
|
||||
LANGOPT(ApproxFunc , 1, 0, Benign, "Permit Floating Point approximation")
|
||||
LANGOPT(NamedLoops , 1, 0, Benign, "Permit named break/continue")
|
||||
|
||||
ENUM_LANGOPT(ComplexRange, ComplexRangeKind, 3, CX_None, NotCompatible, "Enable use of range reduction for complex arithmetics.")
|
||||
|
||||
|
||||
@ -16,8 +16,6 @@ def DoStmt : StmtNode<Stmt>;
|
||||
def ForStmt : StmtNode<Stmt>;
|
||||
def GotoStmt : StmtNode<Stmt>;
|
||||
def IndirectGotoStmt : StmtNode<Stmt>;
|
||||
def ContinueStmt : StmtNode<Stmt>;
|
||||
def BreakStmt : StmtNode<Stmt>;
|
||||
def ReturnStmt : StmtNode<Stmt>;
|
||||
def DeclStmt : StmtNode<Stmt>;
|
||||
def SwitchCase : StmtNode<Stmt, 1>;
|
||||
@ -26,6 +24,11 @@ def DefaultStmt : StmtNode<SwitchCase>;
|
||||
def CapturedStmt : StmtNode<Stmt>;
|
||||
def SYCLKernelCallStmt : StmtNode<Stmt>;
|
||||
|
||||
// Break/continue.
|
||||
def LoopControlStmt : StmtNode<Stmt, 1>;
|
||||
def ContinueStmt : StmtNode<LoopControlStmt>;
|
||||
def BreakStmt : StmtNode<LoopControlStmt>;
|
||||
|
||||
// Statements that might produce a value (for example, as the last non-null
|
||||
// statement in a GNU statement-expression).
|
||||
def ValueStmt : StmtNode<Stmt, 1>;
|
||||
|
||||
@ -1642,6 +1642,17 @@ defm auto_import : BoolFOption<"auto-import",
|
||||
def offload_EQ : CommaJoined<["--"], "offload=">, Flags<[NoXarchOption]>, Alias<offload_targets_EQ>,
|
||||
HelpText<"Specify comma-separated list of offloading target triples (CUDA and HIP only)">;
|
||||
|
||||
// This flag is only here so we can test the named loops implementation
|
||||
// in C++ mode and C language modes before C2y to make sure it actually
|
||||
// works; it should be removed once the syntax of the feature is stable
|
||||
// enough to backport it to earlier language modes (and to C++ if it ever
|
||||
// gets standardised as a C++ feature).
|
||||
defm named_loops
|
||||
: BoolFOption<
|
||||
"named-loops", LangOpts<"NamedLoops">, DefaultFalse,
|
||||
PosFlag<SetTrue, [], [CC1Option], "Enable support for named loops">,
|
||||
NegFlag<SetFalse>>;
|
||||
|
||||
// C++ Coroutines
|
||||
defm coroutines : BoolFOption<"coroutines",
|
||||
LangOpts<"Coroutines">, Default<cpp20.KeyPath>,
|
||||
|
||||
@ -7213,7 +7213,8 @@ public:
|
||||
/// 'while', or 'for').
|
||||
StmtResult
|
||||
ParseStatement(SourceLocation *TrailingElseLoc = nullptr,
|
||||
ParsedStmtContext StmtCtx = ParsedStmtContext::SubStmt);
|
||||
ParsedStmtContext StmtCtx = ParsedStmtContext::SubStmt,
|
||||
LabelDecl *PrecedingLabel = nullptr);
|
||||
|
||||
/// ParseStatementOrDeclaration - Read 'statement' or 'declaration'.
|
||||
/// \verbatim
|
||||
@ -7268,12 +7269,13 @@ public:
|
||||
///
|
||||
StmtResult
|
||||
ParseStatementOrDeclaration(StmtVector &Stmts, ParsedStmtContext StmtCtx,
|
||||
SourceLocation *TrailingElseLoc = nullptr);
|
||||
SourceLocation *TrailingElseLoc = nullptr,
|
||||
LabelDecl *PrecedingLabel = nullptr);
|
||||
|
||||
StmtResult ParseStatementOrDeclarationAfterAttributes(
|
||||
StmtVector &Stmts, ParsedStmtContext StmtCtx,
|
||||
SourceLocation *TrailingElseLoc, ParsedAttributes &DeclAttrs,
|
||||
ParsedAttributes &DeclSpecAttrs);
|
||||
ParsedAttributes &DeclSpecAttrs, LabelDecl *PrecedingLabel);
|
||||
|
||||
/// Parse an expression statement.
|
||||
StmtResult ParseExprStatement(ParsedStmtContext StmtCtx);
|
||||
@ -7398,7 +7400,8 @@ public:
|
||||
/// 'switch' '(' expression ')' statement
|
||||
/// [C++] 'switch' '(' condition ')' statement
|
||||
/// \endverbatim
|
||||
StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc);
|
||||
StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc,
|
||||
LabelDecl *PrecedingLabel);
|
||||
|
||||
/// ParseWhileStatement
|
||||
/// \verbatim
|
||||
@ -7406,7 +7409,8 @@ public:
|
||||
/// 'while' '(' expression ')' statement
|
||||
/// [C++] 'while' '(' condition ')' statement
|
||||
/// \endverbatim
|
||||
StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc);
|
||||
StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc,
|
||||
LabelDecl *PrecedingLabel);
|
||||
|
||||
/// ParseDoStatement
|
||||
/// \verbatim
|
||||
@ -7414,7 +7418,7 @@ public:
|
||||
/// 'do' statement 'while' '(' expression ')' ';'
|
||||
/// \endverbatim
|
||||
/// Note: this lets the caller parse the end ';'.
|
||||
StmtResult ParseDoStatement();
|
||||
StmtResult ParseDoStatement(LabelDecl *PrecedingLabel);
|
||||
|
||||
/// ParseForStatement
|
||||
/// \verbatim
|
||||
@ -7441,7 +7445,8 @@ public:
|
||||
/// [C++0x] expression
|
||||
/// [C++0x] braced-init-list [TODO]
|
||||
/// \endverbatim
|
||||
StmtResult ParseForStatement(SourceLocation *TrailingElseLoc);
|
||||
StmtResult ParseForStatement(SourceLocation *TrailingElseLoc,
|
||||
LabelDecl *PrecedingLabel);
|
||||
|
||||
/// ParseGotoStatement
|
||||
/// \verbatim
|
||||
@ -7458,6 +7463,7 @@ public:
|
||||
/// \verbatim
|
||||
/// jump-statement:
|
||||
/// 'continue' ';'
|
||||
/// [C2y] 'continue' identifier ';'
|
||||
/// \endverbatim
|
||||
///
|
||||
/// Note: this lets the caller parse the end ';'.
|
||||
@ -7468,6 +7474,7 @@ public:
|
||||
/// \verbatim
|
||||
/// jump-statement:
|
||||
/// 'break' ';'
|
||||
/// [C2y] 'break' identifier ';'
|
||||
/// \endverbatim
|
||||
///
|
||||
/// Note: this lets the caller parse the end ';'.
|
||||
@ -7484,9 +7491,12 @@ public:
|
||||
/// \endverbatim
|
||||
StmtResult ParseReturnStatement();
|
||||
|
||||
StmtResult ParseBreakOrContinueStatement(bool IsContinue);
|
||||
|
||||
StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx,
|
||||
SourceLocation *TrailingElseLoc,
|
||||
ParsedAttributes &Attrs);
|
||||
ParsedAttributes &Attrs,
|
||||
LabelDecl *PrecedingLabel);
|
||||
|
||||
void ParseMicrosoftIfExistsStatement(StmtVector &Stmts);
|
||||
|
||||
|
||||
@ -255,6 +255,10 @@ private:
|
||||
/// available for this variable in the current scope.
|
||||
llvm::SmallPtrSet<VarDecl *, 8> ReturnSlots;
|
||||
|
||||
/// If this scope belongs to a loop or switch statement, the label that
|
||||
/// directly precedes it, if any.
|
||||
LabelDecl *PrecedingLabel;
|
||||
|
||||
void setFlags(Scope *Parent, unsigned F);
|
||||
|
||||
public:
|
||||
@ -268,6 +272,14 @@ public:
|
||||
|
||||
void setFlags(unsigned F) { setFlags(getParent(), F); }
|
||||
|
||||
/// Get the label that precedes this scope.
|
||||
LabelDecl *getPrecedingLabel() const { return PrecedingLabel; }
|
||||
void setPrecedingLabel(LabelDecl *LD) {
|
||||
assert((Flags & BreakScope || Flags & ContinueScope) &&
|
||||
"not a loop or switch");
|
||||
PrecedingLabel = LD;
|
||||
}
|
||||
|
||||
/// isBlockScope - Return true if this scope correspond to a closure.
|
||||
bool isBlockScope() const { return Flags & BlockScope; }
|
||||
|
||||
@ -583,6 +595,12 @@ public:
|
||||
return getFlags() & ScopeFlags::ContinueScope;
|
||||
}
|
||||
|
||||
/// Determine whether this is a scope which can have 'break' or 'continue'
|
||||
/// statements embedded into it.
|
||||
bool isBreakOrContinueScope() const {
|
||||
return getFlags() & (ContinueScope | BreakScope);
|
||||
}
|
||||
|
||||
/// Determine whether this scope is a C++ 'try' block.
|
||||
bool isTryScope() const { return getFlags() & Scope::TryScope; }
|
||||
|
||||
|
||||
@ -9488,6 +9488,10 @@ public:
|
||||
LabelDecl *LookupOrCreateLabel(IdentifierInfo *II, SourceLocation IdentLoc,
|
||||
SourceLocation GnuLabelLoc = SourceLocation());
|
||||
|
||||
/// Perform a name lookup for a label with the specified name; this does not
|
||||
/// create a new label if the lookup fails.
|
||||
LabelDecl *LookupExistingLabel(IdentifierInfo *II, SourceLocation IdentLoc);
|
||||
|
||||
/// Look up the constructors for the given class.
|
||||
DeclContextLookupResult LookupConstructors(CXXRecordDecl *Class);
|
||||
|
||||
@ -11042,8 +11046,10 @@ public:
|
||||
LabelDecl *TheDecl);
|
||||
StmtResult ActOnIndirectGotoStmt(SourceLocation GotoLoc,
|
||||
SourceLocation StarLoc, Expr *DestExp);
|
||||
StmtResult ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope);
|
||||
StmtResult ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope);
|
||||
StmtResult ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
|
||||
LabelDecl *Label, SourceLocation LabelLoc);
|
||||
StmtResult ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
|
||||
LabelDecl *Label, SourceLocation LabelLoc);
|
||||
|
||||
struct NamedReturnInfo {
|
||||
const VarDecl *Candidate;
|
||||
|
||||
@ -7337,18 +7337,28 @@ ExpectedStmt ASTNodeImporter::VisitIndirectGotoStmt(IndirectGotoStmt *S) {
|
||||
ToGotoLoc, ToStarLoc, ToTarget);
|
||||
}
|
||||
|
||||
template <typename StmtClass>
|
||||
static ExpectedStmt ImportLoopControlStmt(ASTNodeImporter &NodeImporter,
|
||||
ASTImporter &Importer, StmtClass *S) {
|
||||
Error Err = Error::success();
|
||||
auto ToLoc = NodeImporter.importChecked(Err, S->getKwLoc());
|
||||
auto ToLabelLoc = S->hasLabelTarget()
|
||||
? NodeImporter.importChecked(Err, S->getLabelLoc())
|
||||
: SourceLocation();
|
||||
auto ToDecl = S->hasLabelTarget()
|
||||
? NodeImporter.importChecked(Err, S->getLabelDecl())
|
||||
: nullptr;
|
||||
if (Err)
|
||||
return std::move(Err);
|
||||
return new (Importer.getToContext()) StmtClass(ToLoc, ToLabelLoc, ToDecl);
|
||||
}
|
||||
|
||||
ExpectedStmt ASTNodeImporter::VisitContinueStmt(ContinueStmt *S) {
|
||||
ExpectedSLoc ToContinueLocOrErr = import(S->getContinueLoc());
|
||||
if (!ToContinueLocOrErr)
|
||||
return ToContinueLocOrErr.takeError();
|
||||
return new (Importer.getToContext()) ContinueStmt(*ToContinueLocOrErr);
|
||||
return ImportLoopControlStmt(*this, Importer, S);
|
||||
}
|
||||
|
||||
ExpectedStmt ASTNodeImporter::VisitBreakStmt(BreakStmt *S) {
|
||||
auto ToBreakLocOrErr = import(S->getBreakLoc());
|
||||
if (!ToBreakLocOrErr)
|
||||
return ToBreakLocOrErr.takeError();
|
||||
return new (Importer.getToContext()) BreakStmt(*ToBreakLocOrErr);
|
||||
return ImportLoopControlStmt(*this, Importer, S);
|
||||
}
|
||||
|
||||
ExpectedStmt ASTNodeImporter::VisitReturnStmt(ReturnStmt *S) {
|
||||
|
||||
@ -885,6 +885,11 @@ namespace {
|
||||
/// declaration whose initializer is being evaluated, if any.
|
||||
APValue *EvaluatingDeclValue;
|
||||
|
||||
/// Stack of loops and 'switch' statements which we're currently
|
||||
/// breaking/continuing; null entries are used to mark unlabeled
|
||||
/// break/continue.
|
||||
SmallVector<const Stmt *> BreakContinueStack;
|
||||
|
||||
/// Set of objects that are currently being constructed.
|
||||
llvm::DenseMap<ObjectUnderConstruction, ConstructionPhase>
|
||||
ObjectsUnderConstruction;
|
||||
@ -5377,6 +5382,44 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
|
||||
const Stmt *S,
|
||||
const SwitchCase *SC = nullptr);
|
||||
|
||||
/// Helper to implement named break/continue. Returns 'true' if the evaluation
|
||||
/// result should be propagated up. Otherwise, it sets the evaluation result
|
||||
/// to either Continue to continue the current loop, or Succeeded to break it.
|
||||
static bool ShouldPropagateBreakContinue(EvalInfo &Info,
|
||||
const Stmt *LoopOrSwitch,
|
||||
ArrayRef<BlockScopeRAII *> Scopes,
|
||||
EvalStmtResult &ESR) {
|
||||
bool IsSwitch = isa<SwitchStmt>(LoopOrSwitch);
|
||||
|
||||
// For loops, map Succeeded to Continue so we don't have to check for both.
|
||||
if (!IsSwitch && ESR == ESR_Succeeded) {
|
||||
ESR = ESR_Continue;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ESR != ESR_Break && ESR != ESR_Continue)
|
||||
return false;
|
||||
|
||||
// Are we breaking out of or continuing this statement?
|
||||
bool CanBreakOrContinue = !IsSwitch || ESR == ESR_Break;
|
||||
const Stmt *StackTop = Info.BreakContinueStack.back();
|
||||
if (CanBreakOrContinue && (StackTop == nullptr || StackTop == LoopOrSwitch)) {
|
||||
Info.BreakContinueStack.pop_back();
|
||||
if (ESR == ESR_Break)
|
||||
ESR = ESR_Succeeded;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're not. Propagate the result up.
|
||||
for (BlockScopeRAII *S : Scopes) {
|
||||
if (!S->destroy()) {
|
||||
ESR = ESR_Failed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Evaluate the body of a loop, and translate the result as appropriate.
|
||||
static EvalStmtResult EvaluateLoopBody(StmtResult &Result, EvalInfo &Info,
|
||||
const Stmt *Body,
|
||||
@ -5387,18 +5430,7 @@ static EvalStmtResult EvaluateLoopBody(StmtResult &Result, EvalInfo &Info,
|
||||
if (ESR != ESR_Failed && ESR != ESR_CaseNotFound && !Scope.destroy())
|
||||
ESR = ESR_Failed;
|
||||
|
||||
switch (ESR) {
|
||||
case ESR_Break:
|
||||
return ESR_Succeeded;
|
||||
case ESR_Succeeded:
|
||||
case ESR_Continue:
|
||||
return ESR_Continue;
|
||||
case ESR_Failed:
|
||||
case ESR_Returned:
|
||||
case ESR_CaseNotFound:
|
||||
return ESR;
|
||||
}
|
||||
llvm_unreachable("Invalid EvalStmtResult!");
|
||||
return ESR;
|
||||
}
|
||||
|
||||
/// Evaluate a switch statement.
|
||||
@ -5464,10 +5496,12 @@ static EvalStmtResult EvaluateSwitch(StmtResult &Result, EvalInfo &Info,
|
||||
EvalStmtResult ESR = EvaluateStmt(Result, Info, SS->getBody(), Found);
|
||||
if (ESR != ESR_Failed && ESR != ESR_CaseNotFound && !Scope.destroy())
|
||||
return ESR_Failed;
|
||||
if (ShouldPropagateBreakContinue(Info, SS, /*Scopes=*/{}, ESR))
|
||||
return ESR;
|
||||
|
||||
switch (ESR) {
|
||||
case ESR_Break:
|
||||
return ESR_Succeeded;
|
||||
llvm_unreachable("Should have been converted to Succeeded");
|
||||
case ESR_Succeeded:
|
||||
case ESR_Continue:
|
||||
case ESR_Failed:
|
||||
@ -5565,6 +5599,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
|
||||
case Stmt::WhileStmtClass: {
|
||||
EvalStmtResult ESR =
|
||||
EvaluateLoopBody(Result, Info, cast<WhileStmt>(S)->getBody(), Case);
|
||||
if (ShouldPropagateBreakContinue(Info, S, /*Scopes=*/{}, ESR))
|
||||
return ESR;
|
||||
if (ESR != ESR_Continue)
|
||||
return ESR;
|
||||
break;
|
||||
@ -5586,6 +5622,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
|
||||
|
||||
EvalStmtResult ESR =
|
||||
EvaluateLoopBody(Result, Info, FS->getBody(), Case);
|
||||
if (ShouldPropagateBreakContinue(Info, FS, /*Scopes=*/{}, ESR))
|
||||
return ESR;
|
||||
if (ESR != ESR_Continue)
|
||||
return ESR;
|
||||
if (const auto *Inc = FS->getInc()) {
|
||||
@ -5748,6 +5786,9 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
|
||||
break;
|
||||
|
||||
EvalStmtResult ESR = EvaluateLoopBody(Result, Info, WS->getBody());
|
||||
if (ShouldPropagateBreakContinue(Info, WS, &Scope, ESR))
|
||||
return ESR;
|
||||
|
||||
if (ESR != ESR_Continue) {
|
||||
if (ESR != ESR_Failed && !Scope.destroy())
|
||||
return ESR_Failed;
|
||||
@ -5764,6 +5805,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
|
||||
bool Continue;
|
||||
do {
|
||||
EvalStmtResult ESR = EvaluateLoopBody(Result, Info, DS->getBody(), Case);
|
||||
if (ShouldPropagateBreakContinue(Info, DS, /*Scopes=*/{}, ESR))
|
||||
return ESR;
|
||||
if (ESR != ESR_Continue)
|
||||
return ESR;
|
||||
Case = nullptr;
|
||||
@ -5806,6 +5849,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
|
||||
}
|
||||
|
||||
EvalStmtResult ESR = EvaluateLoopBody(Result, Info, FS->getBody());
|
||||
if (ShouldPropagateBreakContinue(Info, FS, {&IterScope, &ForScope}, ESR))
|
||||
return ESR;
|
||||
if (ESR != ESR_Continue) {
|
||||
if (ESR != ESR_Failed && (!IterScope.destroy() || !ForScope.destroy()))
|
||||
return ESR_Failed;
|
||||
@ -5897,6 +5942,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
|
||||
|
||||
// Loop body.
|
||||
ESR = EvaluateLoopBody(Result, Info, FS->getBody());
|
||||
if (ShouldPropagateBreakContinue(Info, FS, {&InnerScope, &Scope}, ESR))
|
||||
return ESR;
|
||||
if (ESR != ESR_Continue) {
|
||||
if (ESR != ESR_Failed && (!InnerScope.destroy() || !Scope.destroy()))
|
||||
return ESR_Failed;
|
||||
@ -5922,10 +5969,11 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
|
||||
return EvaluateSwitch(Result, Info, cast<SwitchStmt>(S));
|
||||
|
||||
case Stmt::ContinueStmtClass:
|
||||
return ESR_Continue;
|
||||
|
||||
case Stmt::BreakStmtClass:
|
||||
return ESR_Break;
|
||||
case Stmt::BreakStmtClass: {
|
||||
auto *B = cast<LoopControlStmt>(S);
|
||||
Info.BreakContinueStack.push_back(B->getNamedLoopOrSwitch());
|
||||
return isa<ContinueStmt>(S) ? ESR_Continue : ESR_Break;
|
||||
}
|
||||
|
||||
case Stmt::LabelStmtClass:
|
||||
return EvaluateStmt(Result, Info, cast<LabelStmt>(S)->getSubStmt(), Case);
|
||||
|
||||
@ -1671,6 +1671,13 @@ void JSONNodeDumper::VisitLabelStmt(const LabelStmt *LS) {
|
||||
JOS.attribute("declId", createPointerRepresentation(LS->getDecl()));
|
||||
attributeOnlyIfTrue("sideEntry", LS->isSideEntry());
|
||||
}
|
||||
|
||||
void JSONNodeDumper::VisitLoopControlStmt(const LoopControlStmt *LS) {
|
||||
if (LS->hasLabelTarget())
|
||||
JOS.attribute("targetLabelDeclId",
|
||||
createPointerRepresentation(LS->getLabelDecl()));
|
||||
}
|
||||
|
||||
void JSONNodeDumper::VisitGotoStmt(const GotoStmt *GS) {
|
||||
JOS.attribute("targetLabelDeclId",
|
||||
createPointerRepresentation(GS->getLabel()));
|
||||
|
||||
@ -1482,3 +1482,16 @@ bool CapturedStmt::capturesVariable(const VarDecl *Var) const {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const Stmt *LabelStmt::getInnermostLabeledStmt() const {
|
||||
const Stmt *S = getSubStmt();
|
||||
while (isa_and_present<LabelStmt>(S))
|
||||
S = cast<LabelStmt>(S)->getSubStmt();
|
||||
return S;
|
||||
}
|
||||
|
||||
const Stmt *LoopControlStmt::getNamedLoopOrSwitch() const {
|
||||
if (!hasLabelTarget())
|
||||
return nullptr;
|
||||
return getLabelDecl()->getStmt()->getInnermostLabeledStmt();
|
||||
}
|
||||
|
||||
@ -473,12 +473,21 @@ void StmtPrinter::VisitIndirectGotoStmt(IndirectGotoStmt *Node) {
|
||||
}
|
||||
|
||||
void StmtPrinter::VisitContinueStmt(ContinueStmt *Node) {
|
||||
Indent() << "continue;";
|
||||
Indent();
|
||||
if (Node->hasLabelTarget())
|
||||
OS << "continue " << Node->getLabelDecl()->getIdentifier()->getName()
|
||||
<< ';';
|
||||
else
|
||||
OS << "continue;";
|
||||
if (Policy.IncludeNewlines) OS << NL;
|
||||
}
|
||||
|
||||
void StmtPrinter::VisitBreakStmt(BreakStmt *Node) {
|
||||
Indent() << "break;";
|
||||
Indent();
|
||||
if (Node->hasLabelTarget())
|
||||
OS << "break " << Node->getLabelDecl()->getIdentifier()->getName() << ';';
|
||||
else
|
||||
OS << "break;";
|
||||
if (Policy.IncludeNewlines) OS << NL;
|
||||
}
|
||||
|
||||
|
||||
@ -1412,6 +1412,26 @@ static void dumpBasePath(raw_ostream &OS, const CastExpr *Node) {
|
||||
OS << ')';
|
||||
}
|
||||
|
||||
void TextNodeDumper::VisitLoopControlStmt(const LoopControlStmt *Node) {
|
||||
if (!Node->hasLabelTarget())
|
||||
return;
|
||||
|
||||
OS << " '" << Node->getLabelDecl()->getIdentifier()->getName() << "' (";
|
||||
|
||||
auto *Target = Node->getNamedLoopOrSwitch();
|
||||
if (!Target) {
|
||||
ColorScope Color(OS, ShowColors, NullColor);
|
||||
OS << "<<<NULL>>>";
|
||||
} else {
|
||||
{
|
||||
ColorScope Color(OS, ShowColors, StmtColor);
|
||||
OS << Target->getStmtClassName();
|
||||
}
|
||||
dumpPointer(Target);
|
||||
}
|
||||
OS << ")";
|
||||
}
|
||||
|
||||
void TextNodeDumper::VisitIfStmt(const IfStmt *Node) {
|
||||
if (Node->hasInitStorage())
|
||||
OS << " has_init";
|
||||
|
||||
@ -128,6 +128,7 @@ void LangOptions::setLangDefaults(LangOptions &Opts, Language Lang,
|
||||
Opts.WChar = Std.isCPlusPlus();
|
||||
Opts.Digraphs = Std.hasDigraphs();
|
||||
Opts.RawStringLiterals = Std.hasRawStringLiterals();
|
||||
Opts.NamedLoops = Std.isC2y();
|
||||
|
||||
Opts.HLSL = Lang == Language::HLSL;
|
||||
if (Opts.HLSL && Opts.IncludeDefaultHeader)
|
||||
|
||||
@ -2056,7 +2056,7 @@ void CodeGenFunction::EmitObjCForCollectionStmt(const ObjCForCollectionStmt &S){
|
||||
EmitAutoVarCleanups(variable);
|
||||
|
||||
// Perform the loop body, setting up break and continue labels.
|
||||
BreakContinueStack.push_back(BreakContinue(LoopEnd, AfterBody));
|
||||
BreakContinueStack.push_back(BreakContinue(S, LoopEnd, AfterBody));
|
||||
{
|
||||
RunCleanupsScope Scope(*this);
|
||||
EmitStmt(S.getBody());
|
||||
|
||||
@ -1088,7 +1088,7 @@ void CodeGenFunction::EmitWhileStmt(const WhileStmt &S,
|
||||
JumpDest LoopExit = getJumpDestInCurrentScope("while.end");
|
||||
|
||||
// Store the blocks to use for break and continue.
|
||||
BreakContinueStack.push_back(BreakContinue(LoopExit, LoopHeader));
|
||||
BreakContinueStack.push_back(BreakContinue(S, LoopExit, LoopHeader));
|
||||
|
||||
// C++ [stmt.while]p2:
|
||||
// When the condition of a while statement is a declaration, the
|
||||
@ -1207,7 +1207,7 @@ void CodeGenFunction::EmitDoStmt(const DoStmt &S,
|
||||
uint64_t ParentCount = getCurrentProfileCount();
|
||||
|
||||
// Store the blocks to use for break and continue.
|
||||
BreakContinueStack.push_back(BreakContinue(LoopExit, LoopCond));
|
||||
BreakContinueStack.push_back(BreakContinue(S, LoopExit, LoopCond));
|
||||
|
||||
// Emit the body of the loop.
|
||||
llvm::BasicBlock *LoopBody = createBasicBlock("do.body");
|
||||
@ -1328,7 +1328,7 @@ void CodeGenFunction::EmitForStmt(const ForStmt &S,
|
||||
Continue = CondDest;
|
||||
else if (!S.getConditionVariable())
|
||||
Continue = getJumpDestInCurrentScope("for.inc");
|
||||
BreakContinueStack.push_back(BreakContinue(LoopExit, Continue));
|
||||
BreakContinueStack.push_back(BreakContinue(S, LoopExit, Continue));
|
||||
|
||||
if (S.getCond()) {
|
||||
// If the for statement has a condition scope, emit the local variable
|
||||
@ -1510,7 +1510,7 @@ CodeGenFunction::EmitCXXForRangeStmt(const CXXForRangeStmt &S,
|
||||
JumpDest Continue = getJumpDestInCurrentScope("for.inc");
|
||||
|
||||
// Store the blocks to use for break and continue.
|
||||
BreakContinueStack.push_back(BreakContinue(LoopExit, Continue));
|
||||
BreakContinueStack.push_back(BreakContinue(S, LoopExit, Continue));
|
||||
|
||||
{
|
||||
// Create a separate cleanup scope for the loop variable and body.
|
||||
@ -1732,6 +1732,20 @@ void CodeGenFunction::EmitDeclStmt(const DeclStmt &S) {
|
||||
EmitDecl(*I, /*EvaluateConditionDecl=*/true);
|
||||
}
|
||||
|
||||
auto CodeGenFunction::GetDestForLoopControlStmt(const LoopControlStmt &S)
|
||||
-> const BreakContinue * {
|
||||
if (!S.hasLabelTarget())
|
||||
return &BreakContinueStack.back();
|
||||
|
||||
const Stmt *LoopOrSwitch = S.getNamedLoopOrSwitch();
|
||||
assert(LoopOrSwitch && "break/continue target not set?");
|
||||
for (const BreakContinue &BC : llvm::reverse(BreakContinueStack))
|
||||
if (BC.LoopOrSwitch == LoopOrSwitch)
|
||||
return &BC;
|
||||
|
||||
llvm_unreachable("break/continue target not found");
|
||||
}
|
||||
|
||||
void CodeGenFunction::EmitBreakStmt(const BreakStmt &S) {
|
||||
assert(!BreakContinueStack.empty() && "break stmt not in a loop or switch!");
|
||||
|
||||
@ -1742,7 +1756,7 @@ void CodeGenFunction::EmitBreakStmt(const BreakStmt &S) {
|
||||
EmitStopPoint(&S);
|
||||
|
||||
ApplyAtomGroup Grp(getDebugInfo());
|
||||
EmitBranchThroughCleanup(BreakContinueStack.back().BreakBlock);
|
||||
EmitBranchThroughCleanup(GetDestForLoopControlStmt(S)->BreakBlock);
|
||||
}
|
||||
|
||||
void CodeGenFunction::EmitContinueStmt(const ContinueStmt &S) {
|
||||
@ -1755,7 +1769,7 @@ void CodeGenFunction::EmitContinueStmt(const ContinueStmt &S) {
|
||||
EmitStopPoint(&S);
|
||||
|
||||
ApplyAtomGroup Grp(getDebugInfo());
|
||||
EmitBranchThroughCleanup(BreakContinueStack.back().ContinueBlock);
|
||||
EmitBranchThroughCleanup(GetDestForLoopControlStmt(S)->ContinueBlock);
|
||||
}
|
||||
|
||||
/// EmitCaseStmtRange - If case statement range is not too big then
|
||||
@ -2384,7 +2398,7 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
|
||||
if (!BreakContinueStack.empty())
|
||||
OuterContinue = BreakContinueStack.back().ContinueBlock;
|
||||
|
||||
BreakContinueStack.push_back(BreakContinue(SwitchExit, OuterContinue));
|
||||
BreakContinueStack.push_back(BreakContinue(S, SwitchExit, OuterContinue));
|
||||
|
||||
// Emit switch body.
|
||||
EmitStmt(S.getBody());
|
||||
|
||||
@ -1981,7 +1981,7 @@ void CodeGenFunction::EmitOMPLoopBody(const OMPLoopDirective &D,
|
||||
|
||||
// On a continue in the body, jump to the end.
|
||||
JumpDest Continue = getJumpDestInCurrentScope("omp.body.continue");
|
||||
BreakContinueStack.push_back(BreakContinue(LoopExit, Continue));
|
||||
BreakContinueStack.push_back(BreakContinue(D, LoopExit, Continue));
|
||||
for (const Expr *E : D.finals_conditions()) {
|
||||
if (!E)
|
||||
continue;
|
||||
@ -2210,7 +2210,7 @@ void CodeGenFunction::EmitOMPInnerLoop(
|
||||
|
||||
// Create a block for the increment.
|
||||
JumpDest Continue = getJumpDestInCurrentScope("omp.inner.for.inc");
|
||||
BreakContinueStack.push_back(BreakContinue(LoopExit, Continue));
|
||||
BreakContinueStack.push_back(BreakContinue(S, LoopExit, Continue));
|
||||
|
||||
BodyGen(*this);
|
||||
|
||||
@ -3055,7 +3055,7 @@ void CodeGenFunction::EmitOMPOuterLoop(
|
||||
|
||||
// Create a block for the increment.
|
||||
JumpDest Continue = getJumpDestInCurrentScope("omp.dispatch.inc");
|
||||
BreakContinueStack.push_back(BreakContinue(LoopExit, Continue));
|
||||
BreakContinueStack.push_back(BreakContinue(S, LoopExit, Continue));
|
||||
|
||||
OpenMPDirectiveKind EKind = getEffectiveDirectiveKind(S);
|
||||
emitCommonSimdLoop(
|
||||
|
||||
@ -1552,9 +1552,11 @@ private:
|
||||
// BreakContinueStack - This keeps track of where break and continue
|
||||
// statements should jump to.
|
||||
struct BreakContinue {
|
||||
BreakContinue(JumpDest Break, JumpDest Continue)
|
||||
: BreakBlock(Break), ContinueBlock(Continue) {}
|
||||
BreakContinue(const Stmt &LoopOrSwitch, JumpDest Break, JumpDest Continue)
|
||||
: LoopOrSwitch(&LoopOrSwitch), BreakBlock(Break),
|
||||
ContinueBlock(Continue) {}
|
||||
|
||||
const Stmt *LoopOrSwitch;
|
||||
JumpDest BreakBlock;
|
||||
JumpDest ContinueBlock;
|
||||
};
|
||||
@ -3606,6 +3608,8 @@ public:
|
||||
void EmitCaseStmtRange(const CaseStmt &S, ArrayRef<const Attr *> Attrs);
|
||||
void EmitAsmStmt(const AsmStmt &S);
|
||||
|
||||
const BreakContinue *GetDestForLoopControlStmt(const LoopControlStmt &S);
|
||||
|
||||
void EmitObjCForCollectionStmt(const ObjCForCollectionStmt &S);
|
||||
void EmitObjCAtTryStmt(const ObjCAtTryStmt &S);
|
||||
void EmitObjCAtThrowStmt(const ObjCAtThrowStmt &S);
|
||||
|
||||
@ -623,6 +623,9 @@ static bool FixupInvocation(CompilerInvocation &Invocation,
|
||||
LangOpts.RawStringLiterals = true;
|
||||
}
|
||||
|
||||
LangOpts.NamedLoops =
|
||||
Args.hasFlag(OPT_fnamed_loops, OPT_fno_named_loops, LangOpts.C2y);
|
||||
|
||||
// Prevent the user from specifying both -fsycl-is-device and -fsycl-is-host.
|
||||
if (LangOpts.SYCLIsDevice && LangOpts.SYCLIsHost)
|
||||
Diags.Report(diag::err_drv_argument_not_allowed_with) << "-fsycl-is-device"
|
||||
|
||||
@ -37,23 +37,25 @@ using namespace clang;
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
StmtResult Parser::ParseStatement(SourceLocation *TrailingElseLoc,
|
||||
ParsedStmtContext StmtCtx) {
|
||||
ParsedStmtContext StmtCtx,
|
||||
LabelDecl *PrecedingLabel) {
|
||||
StmtResult Res;
|
||||
|
||||
// We may get back a null statement if we found a #pragma. Keep going until
|
||||
// we get an actual statement.
|
||||
StmtVector Stmts;
|
||||
do {
|
||||
Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc);
|
||||
Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc,
|
||||
PrecedingLabel);
|
||||
} while (!Res.isInvalid() && !Res.get());
|
||||
|
||||
return Res;
|
||||
}
|
||||
|
||||
StmtResult
|
||||
Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
|
||||
ParsedStmtContext StmtCtx,
|
||||
SourceLocation *TrailingElseLoc) {
|
||||
StmtResult Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
|
||||
ParsedStmtContext StmtCtx,
|
||||
SourceLocation *TrailingElseLoc,
|
||||
LabelDecl *PrecedingLabel) {
|
||||
|
||||
ParenBraceBracketBalancer BalancerRAIIObj(*this);
|
||||
|
||||
@ -73,7 +75,8 @@ Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
|
||||
MaybeParseMicrosoftAttributes(GNUOrMSAttrs);
|
||||
|
||||
StmtResult Res = ParseStatementOrDeclarationAfterAttributes(
|
||||
Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs);
|
||||
Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs,
|
||||
PrecedingLabel);
|
||||
MaybeDestroyTemplateIds();
|
||||
|
||||
takeAndConcatenateAttrs(CXX11Attrs, std::move(GNUOrMSAttrs));
|
||||
@ -130,7 +133,7 @@ private:
|
||||
StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
|
||||
StmtVector &Stmts, ParsedStmtContext StmtCtx,
|
||||
SourceLocation *TrailingElseLoc, ParsedAttributes &CXX11Attrs,
|
||||
ParsedAttributes &GNUAttrs) {
|
||||
ParsedAttributes &GNUAttrs, LabelDecl *PrecedingLabel) {
|
||||
const char *SemiError = nullptr;
|
||||
StmtResult Res;
|
||||
SourceLocation GNUAttributeLoc;
|
||||
@ -278,16 +281,16 @@ Retry:
|
||||
case tok::kw_if: // C99 6.8.4.1: if-statement
|
||||
return ParseIfStatement(TrailingElseLoc);
|
||||
case tok::kw_switch: // C99 6.8.4.2: switch-statement
|
||||
return ParseSwitchStatement(TrailingElseLoc);
|
||||
return ParseSwitchStatement(TrailingElseLoc, PrecedingLabel);
|
||||
|
||||
case tok::kw_while: // C99 6.8.5.1: while-statement
|
||||
return ParseWhileStatement(TrailingElseLoc);
|
||||
return ParseWhileStatement(TrailingElseLoc, PrecedingLabel);
|
||||
case tok::kw_do: // C99 6.8.5.2: do-statement
|
||||
Res = ParseDoStatement();
|
||||
Res = ParseDoStatement(PrecedingLabel);
|
||||
SemiError = "do/while";
|
||||
break;
|
||||
case tok::kw_for: // C99 6.8.5.3: for-statement
|
||||
return ParseForStatement(TrailingElseLoc);
|
||||
return ParseForStatement(TrailingElseLoc, PrecedingLabel);
|
||||
|
||||
case tok::kw_goto: // C99 6.8.6.1: goto-statement
|
||||
Res = ParseGotoStatement();
|
||||
@ -483,7 +486,8 @@ Retry:
|
||||
case tok::annot_pragma_loop_hint:
|
||||
ProhibitAttributes(CXX11Attrs);
|
||||
ProhibitAttributes(GNUAttrs);
|
||||
return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs);
|
||||
return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs,
|
||||
PrecedingLabel);
|
||||
|
||||
case tok::annot_pragma_dump:
|
||||
ProhibitAttributes(CXX11Attrs);
|
||||
@ -697,6 +701,9 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
|
||||
// identifier ':' statement
|
||||
SourceLocation ColonLoc = ConsumeToken();
|
||||
|
||||
LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(),
|
||||
IdentTok.getLocation());
|
||||
|
||||
// Read label attributes, if present.
|
||||
StmtResult SubStmt;
|
||||
if (Tok.is(tok::kw___attribute)) {
|
||||
@ -716,7 +723,8 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
|
||||
StmtVector Stmts;
|
||||
ParsedAttributes EmptyCXX11Attrs(AttrFactory);
|
||||
SubStmt = ParseStatementOrDeclarationAfterAttributes(
|
||||
Stmts, StmtCtx, nullptr, EmptyCXX11Attrs, TempAttrs);
|
||||
Stmts, StmtCtx, /*TrailingElseLoc=*/nullptr, EmptyCXX11Attrs,
|
||||
TempAttrs, LD);
|
||||
if (!TempAttrs.empty() && !SubStmt.isInvalid())
|
||||
SubStmt = Actions.ActOnAttributedStmt(TempAttrs, SubStmt.get());
|
||||
}
|
||||
@ -730,7 +738,7 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
|
||||
|
||||
// If we've not parsed a statement yet, parse one now.
|
||||
if (SubStmt.isUnset())
|
||||
SubStmt = ParseStatement(nullptr, StmtCtx);
|
||||
SubStmt = ParseStatement(nullptr, StmtCtx, LD);
|
||||
|
||||
// Broken substmt shouldn't prevent the label from being added to the AST.
|
||||
if (SubStmt.isInvalid())
|
||||
@ -738,8 +746,6 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
|
||||
|
||||
DiagnoseLabelFollowedByDecl(*this, SubStmt.get());
|
||||
|
||||
LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(),
|
||||
IdentTok.getLocation());
|
||||
Actions.ProcessDeclAttributeList(Actions.CurScope, LD, Attrs);
|
||||
Attrs.clear();
|
||||
|
||||
@ -1620,7 +1626,8 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) {
|
||||
ThenStmt.get(), ElseLoc, ElseStmt.get());
|
||||
}
|
||||
|
||||
StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc) {
|
||||
StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc,
|
||||
LabelDecl *PrecedingLabel) {
|
||||
assert(Tok.is(tok::kw_switch) && "Not a switch stmt!");
|
||||
SourceLocation SwitchLoc = ConsumeToken(); // eat the 'switch'.
|
||||
|
||||
@ -1686,6 +1693,7 @@ StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc) {
|
||||
// condition and a new scope for substatement in C++.
|
||||
//
|
||||
getCurScope()->AddFlags(Scope::BreakScope);
|
||||
getCurScope()->setPrecedingLabel(PrecedingLabel);
|
||||
ParseScope InnerScope(this, Scope::DeclScope, C99orCXX, Tok.is(tok::l_brace));
|
||||
|
||||
// We have incremented the mangling number for the SwitchScope and the
|
||||
@ -1703,7 +1711,8 @@ StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc) {
|
||||
return Actions.ActOnFinishSwitchStmt(SwitchLoc, Switch.get(), Body.get());
|
||||
}
|
||||
|
||||
StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc) {
|
||||
StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc,
|
||||
LabelDecl *PrecedingLabel) {
|
||||
assert(Tok.is(tok::kw_while) && "Not a while stmt!");
|
||||
SourceLocation WhileLoc = Tok.getLocation();
|
||||
ConsumeToken(); // eat the 'while'.
|
||||
@ -1748,6 +1757,7 @@ StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc) {
|
||||
// combinations, so diagnose that here in OpenACC mode.
|
||||
SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()};
|
||||
getActions().OpenACC().ActOnWhileStmt(WhileLoc);
|
||||
getCurScope()->setPrecedingLabel(PrecedingLabel);
|
||||
|
||||
// C99 6.8.5p5 - In C99, the body of the while statement is a scope, even if
|
||||
// there is no compound stmt. C90 does not have this clause. We only do this
|
||||
@ -1779,7 +1789,7 @@ StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc) {
|
||||
return Actions.ActOnWhileStmt(WhileLoc, LParen, Cond, RParen, Body.get());
|
||||
}
|
||||
|
||||
StmtResult Parser::ParseDoStatement() {
|
||||
StmtResult Parser::ParseDoStatement(LabelDecl *PrecedingLabel) {
|
||||
assert(Tok.is(tok::kw_do) && "Not a do stmt!");
|
||||
SourceLocation DoLoc = ConsumeToken(); // eat the 'do'.
|
||||
|
||||
@ -1797,6 +1807,7 @@ StmtResult Parser::ParseDoStatement() {
|
||||
// combinations, so diagnose that here in OpenACC mode.
|
||||
SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()};
|
||||
getActions().OpenACC().ActOnDoStmt(DoLoc);
|
||||
getCurScope()->setPrecedingLabel(PrecedingLabel);
|
||||
|
||||
// C99 6.8.5p5 - In C99, the body of the do statement is a scope, even if
|
||||
// there is no compound stmt. C90 does not have this clause. We only do this
|
||||
@ -1815,6 +1826,9 @@ StmtResult Parser::ParseDoStatement() {
|
||||
// Pop the body scope if needed.
|
||||
InnerScope.Exit();
|
||||
|
||||
// Reset this to disallow break/continue out of the condition.
|
||||
getCurScope()->setPrecedingLabel(nullptr);
|
||||
|
||||
if (Tok.isNot(tok::kw_while)) {
|
||||
if (!Body.isInvalid()) {
|
||||
Diag(Tok, diag::err_expected_while);
|
||||
@ -1876,7 +1890,8 @@ bool Parser::isForRangeIdentifier() {
|
||||
return false;
|
||||
}
|
||||
|
||||
StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) {
|
||||
StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
|
||||
LabelDecl *PrecedingLabel) {
|
||||
assert(Tok.is(tok::kw_for) && "Not a for stmt!");
|
||||
SourceLocation ForLoc = ConsumeToken(); // eat the 'for'.
|
||||
|
||||
@ -2208,6 +2223,10 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) {
|
||||
getActions().OpenACC().ActOnForStmtBegin(
|
||||
ForLoc, FirstPart.get(), SecondPart.get().second, ThirdPart.get());
|
||||
|
||||
// Set this only right before parsing the body to disallow break/continue in
|
||||
// the other parts.
|
||||
getCurScope()->setPrecedingLabel(PrecedingLabel);
|
||||
|
||||
// C99 6.8.5p5 - In C99, the body of the for statement is a scope, even if
|
||||
// there is no compound stmt. C90 does not have this clause. We only do this
|
||||
// if the body isn't a compound statement to avoid push/pop in common cases.
|
||||
@ -2288,14 +2307,35 @@ StmtResult Parser::ParseGotoStatement() {
|
||||
return Res;
|
||||
}
|
||||
|
||||
StmtResult Parser::ParseBreakOrContinueStatement(bool IsContinue) {
|
||||
SourceLocation KwLoc = ConsumeToken(); // Eat the keyword.
|
||||
SourceLocation LabelLoc;
|
||||
LabelDecl *Target = nullptr;
|
||||
if (Tok.is(tok::identifier)) {
|
||||
Target =
|
||||
Actions.LookupExistingLabel(Tok.getIdentifierInfo(), Tok.getLocation());
|
||||
LabelLoc = ConsumeToken();
|
||||
if (!getLangOpts().NamedLoops)
|
||||
// TODO: Make this a compatibility/extension warning instead once the
|
||||
// syntax of this feature is finalised.
|
||||
Diag(LabelLoc, diag::err_c2y_labeled_break_continue) << IsContinue;
|
||||
if (!Target) {
|
||||
Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsContinue;
|
||||
return StmtError();
|
||||
}
|
||||
}
|
||||
|
||||
if (IsContinue)
|
||||
return Actions.ActOnContinueStmt(KwLoc, getCurScope(), Target, LabelLoc);
|
||||
return Actions.ActOnBreakStmt(KwLoc, getCurScope(), Target, LabelLoc);
|
||||
}
|
||||
|
||||
StmtResult Parser::ParseContinueStatement() {
|
||||
SourceLocation ContinueLoc = ConsumeToken(); // eat the 'continue'.
|
||||
return Actions.ActOnContinueStmt(ContinueLoc, getCurScope());
|
||||
return ParseBreakOrContinueStatement(/*IsContinue=*/true);
|
||||
}
|
||||
|
||||
StmtResult Parser::ParseBreakStatement() {
|
||||
SourceLocation BreakLoc = ConsumeToken(); // eat the 'break'.
|
||||
return Actions.ActOnBreakStmt(BreakLoc, getCurScope());
|
||||
return ParseBreakOrContinueStatement(/*IsContinue=*/false);
|
||||
}
|
||||
|
||||
StmtResult Parser::ParseReturnStatement() {
|
||||
@ -2339,7 +2379,8 @@ StmtResult Parser::ParseReturnStatement() {
|
||||
StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts,
|
||||
ParsedStmtContext StmtCtx,
|
||||
SourceLocation *TrailingElseLoc,
|
||||
ParsedAttributes &Attrs) {
|
||||
ParsedAttributes &Attrs,
|
||||
LabelDecl *PrecedingLabel) {
|
||||
// Create temporary attribute list.
|
||||
ParsedAttributes TempAttrs(AttrFactory);
|
||||
|
||||
@ -2363,7 +2404,8 @@ StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts,
|
||||
|
||||
ParsedAttributes EmptyDeclSpecAttrs(AttrFactory);
|
||||
StmtResult S = ParseStatementOrDeclarationAfterAttributes(
|
||||
Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs);
|
||||
Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs,
|
||||
PrecedingLabel);
|
||||
|
||||
Attrs.takeAllFrom(TempAttrs);
|
||||
|
||||
|
||||
@ -99,6 +99,7 @@ void Scope::Init(Scope *parent, unsigned flags) {
|
||||
UsingDirectives.clear();
|
||||
Entity = nullptr;
|
||||
ErrorTrap.reset();
|
||||
PrecedingLabel = nullptr;
|
||||
NRVO = std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
@ -4453,26 +4453,28 @@ void Sema::LookupVisibleDecls(DeclContext *Ctx, LookupNameKind Kind,
|
||||
H.lookupVisibleDecls(*this, Ctx, Kind, IncludeGlobalScope);
|
||||
}
|
||||
|
||||
LabelDecl *Sema::LookupExistingLabel(IdentifierInfo *II, SourceLocation Loc) {
|
||||
NamedDecl *Res = LookupSingleName(CurScope, II, Loc, LookupLabel,
|
||||
RedeclarationKind::NotForRedeclaration);
|
||||
// If we found a label, check to see if it is in the same context as us.
|
||||
// When in a Block, we don't want to reuse a label in an enclosing function.
|
||||
if (!Res || Res->getDeclContext() != CurContext)
|
||||
return nullptr;
|
||||
return cast<LabelDecl>(Res);
|
||||
}
|
||||
|
||||
LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
|
||||
SourceLocation GnuLabelLoc) {
|
||||
// Do a lookup to see if we have a label with this name already.
|
||||
NamedDecl *Res = nullptr;
|
||||
|
||||
if (GnuLabelLoc.isValid()) {
|
||||
// Local label definitions always shadow existing labels.
|
||||
Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc);
|
||||
auto *Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc);
|
||||
Scope *S = CurScope;
|
||||
PushOnScopeChains(Res, S, true);
|
||||
return cast<LabelDecl>(Res);
|
||||
}
|
||||
|
||||
// Not a GNU local label.
|
||||
Res = LookupSingleName(CurScope, II, Loc, LookupLabel,
|
||||
RedeclarationKind::NotForRedeclaration);
|
||||
// If we found a label, check to see if it is in the same context as us.
|
||||
// When in a Block, we don't want to reuse a label in an enclosing function.
|
||||
if (Res && Res->getDeclContext() != CurContext)
|
||||
Res = nullptr;
|
||||
LabelDecl *Res = LookupExistingLabel(II, Loc);
|
||||
if (!Res) {
|
||||
// If not forward referenced or defined already, create the backing decl.
|
||||
Res = LabelDecl::Create(Context, CurContext, Loc, II);
|
||||
@ -4480,7 +4482,7 @@ LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
|
||||
assert(S && "Not in a function?");
|
||||
PushOnScopeChains(Res, S, true);
|
||||
}
|
||||
return cast<LabelDecl>(Res);
|
||||
return Res;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@ -2122,12 +2122,12 @@ namespace {
|
||||
typedef ConstEvaluatedExprVisitor<BreakContinueFinder> Inherited;
|
||||
|
||||
void VisitContinueStmt(const ContinueStmt* E) {
|
||||
ContinueLoc = E->getContinueLoc();
|
||||
ContinueLoc = E->getKwLoc();
|
||||
}
|
||||
|
||||
void VisitBreakStmt(const BreakStmt* E) {
|
||||
if (!InSwitch)
|
||||
BreakLoc = E->getBreakLoc();
|
||||
BreakLoc = E->getKwLoc();
|
||||
}
|
||||
|
||||
void VisitSwitchStmt(const SwitchStmt* S) {
|
||||
@ -3275,9 +3275,55 @@ static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc,
|
||||
}
|
||||
}
|
||||
|
||||
StmtResult
|
||||
Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope) {
|
||||
Scope *S = CurScope->getContinueParent();
|
||||
static Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope,
|
||||
SourceLocation KWLoc,
|
||||
LabelDecl *Target,
|
||||
SourceLocation LabelLoc,
|
||||
bool IsContinue) {
|
||||
assert(Target && "not a named break/continue?");
|
||||
Scope *Found = nullptr;
|
||||
for (Scope *Scope = CurScope; Scope; Scope = Scope->getParent()) {
|
||||
if (Scope->isFunctionScope())
|
||||
break;
|
||||
|
||||
if (Scope->isOpenACCComputeConstructScope()) {
|
||||
S.Diag(KWLoc, diag::err_acc_branch_in_out_compute_construct)
|
||||
<< /*branch*/ 0 << /*out of*/ 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (Scope->isBreakOrContinueScope() &&
|
||||
Scope->getPrecedingLabel() == Target) {
|
||||
Found = Scope;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Found) {
|
||||
if (IsContinue && !Found->isContinueScope()) {
|
||||
S.Diag(LabelLoc, diag::err_continue_switch);
|
||||
return nullptr;
|
||||
}
|
||||
return Found;
|
||||
}
|
||||
|
||||
S.Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsContinue;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
|
||||
LabelDecl *Target, SourceLocation LabelLoc) {
|
||||
Scope *S;
|
||||
if (Target) {
|
||||
S = FindLabeledBreakContinueScope(*this, CurScope, ContinueLoc, Target,
|
||||
LabelLoc,
|
||||
/*IsContinue=*/true);
|
||||
if (!S)
|
||||
return StmtError();
|
||||
} else {
|
||||
S = CurScope->getContinueParent();
|
||||
}
|
||||
|
||||
if (!S) {
|
||||
// C99 6.8.6.2p1: A break shall appear only in or as a loop body.
|
||||
return StmtError(Diag(ContinueLoc, diag::err_continue_not_in_loop));
|
||||
@ -3299,16 +3345,27 @@ Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope) {
|
||||
|
||||
CheckJumpOutOfSEHFinally(*this, ContinueLoc, *S);
|
||||
|
||||
return new (Context) ContinueStmt(ContinueLoc);
|
||||
return new (Context) ContinueStmt(ContinueLoc, LabelLoc, Target);
|
||||
}
|
||||
|
||||
StmtResult
|
||||
Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope) {
|
||||
Scope *S = CurScope->getBreakParent();
|
||||
StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
|
||||
LabelDecl *Target, SourceLocation LabelLoc) {
|
||||
Scope *S;
|
||||
if (Target) {
|
||||
S = FindLabeledBreakContinueScope(*this, CurScope, BreakLoc, Target,
|
||||
LabelLoc,
|
||||
/*IsContinue=*/false);
|
||||
if (!S)
|
||||
return StmtError();
|
||||
} else {
|
||||
S = CurScope->getBreakParent();
|
||||
}
|
||||
|
||||
if (!S) {
|
||||
// C99 6.8.6.3p1: A break shall appear only in or as a switch/loop body.
|
||||
return StmtError(Diag(BreakLoc, diag::err_break_not_in_loop_or_switch));
|
||||
}
|
||||
|
||||
if (S->isOpenMPLoopScope())
|
||||
return StmtError(Diag(BreakLoc, diag::err_omp_loop_cannot_use_stmt)
|
||||
<< "break");
|
||||
@ -3329,7 +3386,7 @@ Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope) {
|
||||
|
||||
CheckJumpOutOfSEHFinally(*this, BreakLoc, *S);
|
||||
|
||||
return new (Context) BreakStmt(BreakLoc);
|
||||
return new (Context) BreakStmt(BreakLoc, LabelLoc, Target);
|
||||
}
|
||||
|
||||
Sema::NamedReturnInfo Sema::getNamedReturnInfo(Expr *&E,
|
||||
|
||||
@ -8548,13 +8548,31 @@ TreeTransform<Derived>::TransformIndirectGotoStmt(IndirectGotoStmt *S) {
|
||||
template<typename Derived>
|
||||
StmtResult
|
||||
TreeTransform<Derived>::TransformContinueStmt(ContinueStmt *S) {
|
||||
return S;
|
||||
if (!S->hasLabelTarget())
|
||||
return S;
|
||||
|
||||
Decl *LD = getDerived().TransformDecl(S->getLabelDecl()->getLocation(),
|
||||
S->getLabelDecl());
|
||||
if (!LD)
|
||||
return StmtError();
|
||||
|
||||
return new (SemaRef.Context)
|
||||
ContinueStmt(S->getKwLoc(), S->getLabelLoc(), cast<LabelDecl>(LD));
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
StmtResult
|
||||
TreeTransform<Derived>::TransformBreakStmt(BreakStmt *S) {
|
||||
return S;
|
||||
if (!S->hasLabelTarget())
|
||||
return S;
|
||||
|
||||
Decl *LD = getDerived().TransformDecl(S->getLabelDecl()->getLocation(),
|
||||
S->getLabelDecl());
|
||||
if (!LD)
|
||||
return StmtError();
|
||||
|
||||
return new (SemaRef.Context)
|
||||
BreakStmt(S->getKwLoc(), S->getLabelLoc(), cast<LabelDecl>(LD));
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
|
||||
@ -320,16 +320,21 @@ void ASTStmtReader::VisitIndirectGotoStmt(IndirectGotoStmt *S) {
|
||||
S->setTarget(Record.readSubExpr());
|
||||
}
|
||||
|
||||
void ASTStmtReader::VisitContinueStmt(ContinueStmt *S) {
|
||||
void ASTStmtReader::VisitLoopControlStmt(LoopControlStmt *S) {
|
||||
VisitStmt(S);
|
||||
S->setContinueLoc(readSourceLocation());
|
||||
S->setKwLoc(readSourceLocation());
|
||||
if (Record.readBool()) {
|
||||
S->setLabelDecl(readDeclAs<LabelDecl>());
|
||||
S->setLabelLoc(readSourceLocation());
|
||||
}
|
||||
}
|
||||
|
||||
void ASTStmtReader::VisitBreakStmt(BreakStmt *S) {
|
||||
VisitStmt(S);
|
||||
S->setBreakLoc(readSourceLocation());
|
||||
void ASTStmtReader::VisitContinueStmt(ContinueStmt *S) {
|
||||
VisitLoopControlStmt(S);
|
||||
}
|
||||
|
||||
void ASTStmtReader::VisitBreakStmt(BreakStmt *S) { VisitLoopControlStmt(S); }
|
||||
|
||||
void ASTStmtReader::VisitReturnStmt(ReturnStmt *S) {
|
||||
VisitStmt(S);
|
||||
|
||||
|
||||
@ -310,15 +310,23 @@ void ASTStmtWriter::VisitIndirectGotoStmt(IndirectGotoStmt *S) {
|
||||
Code = serialization::STMT_INDIRECT_GOTO;
|
||||
}
|
||||
|
||||
void ASTStmtWriter::VisitContinueStmt(ContinueStmt *S) {
|
||||
void ASTStmtWriter::VisitLoopControlStmt(LoopControlStmt *S) {
|
||||
VisitStmt(S);
|
||||
Record.AddSourceLocation(S->getContinueLoc());
|
||||
Record.AddSourceLocation(S->getKwLoc());
|
||||
Record.push_back(S->hasLabelTarget());
|
||||
if (S->hasLabelTarget()) {
|
||||
Record.AddDeclRef(S->getLabelDecl());
|
||||
Record.AddSourceLocation(S->getLabelLoc());
|
||||
}
|
||||
}
|
||||
|
||||
void ASTStmtWriter::VisitContinueStmt(ContinueStmt *S) {
|
||||
VisitLoopControlStmt(S);
|
||||
Code = serialization::STMT_CONTINUE;
|
||||
}
|
||||
|
||||
void ASTStmtWriter::VisitBreakStmt(BreakStmt *S) {
|
||||
VisitStmt(S);
|
||||
Record.AddSourceLocation(S->getBreakLoc());
|
||||
VisitLoopControlStmt(S);
|
||||
Code = serialization::STMT_BREAK;
|
||||
}
|
||||
|
||||
|
||||
@ -1482,16 +1482,14 @@ public:
|
||||
}
|
||||
|
||||
bool WalkUpFromContinueStmt(ContinueStmt *S) {
|
||||
Builder.markChildToken(S->getContinueLoc(),
|
||||
syntax::NodeRole::IntroducerKeyword);
|
||||
Builder.markChildToken(S->getKwLoc(), syntax::NodeRole::IntroducerKeyword);
|
||||
Builder.foldNode(Builder.getStmtRange(S),
|
||||
new (allocator()) syntax::ContinueStatement, S);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WalkUpFromBreakStmt(BreakStmt *S) {
|
||||
Builder.markChildToken(S->getBreakLoc(),
|
||||
syntax::NodeRole::IntroducerKeyword);
|
||||
Builder.markChildToken(S->getKwLoc(), syntax::NodeRole::IntroducerKeyword);
|
||||
Builder.foldNode(Builder.getStmtRange(S),
|
||||
new (allocator()) syntax::BreakStatement, S);
|
||||
return true;
|
||||
|
||||
306
clang/test/AST/ast-dump-labeled-break-continue-json.c
Normal file
306
clang/test/AST/ast-dump-labeled-break-continue-json.c
Normal file
@ -0,0 +1,306 @@
|
||||
// RUN: %clang_cc1 -std=c2y -ast-dump=json -ast-dump-filter Test %s | FileCheck %s
|
||||
|
||||
void TestLabeledBreakContinue() {
|
||||
a: while (true) {
|
||||
break a;
|
||||
continue a;
|
||||
c: for (;;) {
|
||||
break a;
|
||||
continue a;
|
||||
break c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: CHECK lines have been autogenerated by gen_ast_dump_json_test.py
|
||||
|
||||
|
||||
// CHECK-NOT: {{^}}Dumping
|
||||
// CHECK: "kind": "FunctionDecl",
|
||||
// CHECK-NEXT: "loc": {
|
||||
// CHECK-NEXT: "offset": 89,
|
||||
// CHECK-NEXT: "file": "{{.*}}",
|
||||
// CHECK-NEXT: "line": 3,
|
||||
// CHECK-NEXT: "col": 6,
|
||||
// CHECK-NEXT: "tokLen": 24
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 84,
|
||||
// CHECK-NEXT: "col": 1,
|
||||
// CHECK-NEXT: "tokLen": 4
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 243,
|
||||
// CHECK-NEXT: "line": 13,
|
||||
// CHECK-NEXT: "col": 1,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "name": "TestLabeledBreakContinue",
|
||||
// CHECK-NEXT: "mangledName": "TestLabeledBreakContinue",
|
||||
// CHECK-NEXT: "type": {
|
||||
// CHECK-NEXT: "qualType": "void (void)"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "inner": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "CompoundStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 116,
|
||||
// CHECK-NEXT: "line": 3,
|
||||
// CHECK-NEXT: "col": 33,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 243,
|
||||
// CHECK-NEXT: "line": 13,
|
||||
// CHECK-NEXT: "col": 1,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "inner": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "LabelStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 120,
|
||||
// CHECK-NEXT: "line": 4,
|
||||
// CHECK-NEXT: "col": 3,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 241,
|
||||
// CHECK-NEXT: "line": 12,
|
||||
// CHECK-NEXT: "col": 3,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "name": "a",
|
||||
// CHECK-NEXT: "declId": "0x{{.*}}",
|
||||
// CHECK-NEXT: "inner": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "WhileStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 123,
|
||||
// CHECK-NEXT: "line": 4,
|
||||
// CHECK-NEXT: "col": 6,
|
||||
// CHECK-NEXT: "tokLen": 5
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 241,
|
||||
// CHECK-NEXT: "line": 12,
|
||||
// CHECK-NEXT: "col": 3,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "inner": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "CXXBoolLiteralExpr",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 130,
|
||||
// CHECK-NEXT: "line": 4,
|
||||
// CHECK-NEXT: "col": 13,
|
||||
// CHECK-NEXT: "tokLen": 4
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 130,
|
||||
// CHECK-NEXT: "col": 13,
|
||||
// CHECK-NEXT: "tokLen": 4
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "type": {
|
||||
// CHECK-NEXT: "qualType": "bool"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "valueCategory": "prvalue",
|
||||
// CHECK-NEXT: "value": true
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "CompoundStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 136,
|
||||
// CHECK-NEXT: "col": 19,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 241,
|
||||
// CHECK-NEXT: "line": 12,
|
||||
// CHECK-NEXT: "col": 3,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "inner": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "BreakStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 142,
|
||||
// CHECK-NEXT: "line": 5,
|
||||
// CHECK-NEXT: "col": 5,
|
||||
// CHECK-NEXT: "tokLen": 5
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 148,
|
||||
// CHECK-NEXT: "col": 11,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "targetLabelDeclId": "0x{{.*}}"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "ContinueStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 155,
|
||||
// CHECK-NEXT: "line": 6,
|
||||
// CHECK-NEXT: "col": 5,
|
||||
// CHECK-NEXT: "tokLen": 8
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 164,
|
||||
// CHECK-NEXT: "col": 14,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "targetLabelDeclId": "0x{{.*}}"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "LabelStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 171,
|
||||
// CHECK-NEXT: "line": 7,
|
||||
// CHECK-NEXT: "col": 5,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 237,
|
||||
// CHECK-NEXT: "line": 11,
|
||||
// CHECK-NEXT: "col": 5,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "name": "c",
|
||||
// CHECK-NEXT: "declId": "0x{{.*}}",
|
||||
// CHECK-NEXT: "inner": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "ForStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 174,
|
||||
// CHECK-NEXT: "line": 7,
|
||||
// CHECK-NEXT: "col": 8,
|
||||
// CHECK-NEXT: "tokLen": 3
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 237,
|
||||
// CHECK-NEXT: "line": 11,
|
||||
// CHECK-NEXT: "col": 5,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "inner": [
|
||||
// CHECK-NEXT: {},
|
||||
// CHECK-NEXT: {},
|
||||
// CHECK-NEXT: {},
|
||||
// CHECK-NEXT: {},
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "CompoundStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 183,
|
||||
// CHECK-NEXT: "line": 7,
|
||||
// CHECK-NEXT: "col": 17,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 237,
|
||||
// CHECK-NEXT: "line": 11,
|
||||
// CHECK-NEXT: "col": 5,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "inner": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "BreakStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 191,
|
||||
// CHECK-NEXT: "line": 8,
|
||||
// CHECK-NEXT: "col": 7,
|
||||
// CHECK-NEXT: "tokLen": 5
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 197,
|
||||
// CHECK-NEXT: "col": 13,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "targetLabelDeclId": "0x{{.*}}"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "ContinueStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 206,
|
||||
// CHECK-NEXT: "line": 9,
|
||||
// CHECK-NEXT: "col": 7,
|
||||
// CHECK-NEXT: "tokLen": 8
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 215,
|
||||
// CHECK-NEXT: "col": 16,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "targetLabelDeclId": "0x{{.*}}"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "id": "0x{{.*}}",
|
||||
// CHECK-NEXT: "kind": "BreakStmt",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "begin": {
|
||||
// CHECK-NEXT: "offset": 224,
|
||||
// CHECK-NEXT: "line": 10,
|
||||
// CHECK-NEXT: "col": 7,
|
||||
// CHECK-NEXT: "tokLen": 5
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "offset": 230,
|
||||
// CHECK-NEXT: "col": 13,
|
||||
// CHECK-NEXT: "tokLen": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "targetLabelDeclId": "0x{{.*}}"
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: }
|
||||
40
clang/test/AST/ast-dump-labeled-break-continue.c
Normal file
40
clang/test/AST/ast-dump-labeled-break-continue.c
Normal file
@ -0,0 +1,40 @@
|
||||
// Test without serialization:
|
||||
// RUN: %clang_cc1 -std=c2y -ast-dump %s \
|
||||
// RUN: | FileCheck -strict-whitespace %s
|
||||
//
|
||||
// Test with serialization:
|
||||
// RUN: %clang_cc1 -std=c2y -emit-pch -o %t %s
|
||||
// RUN: %clang_cc1 -x c -std=c2y -include-pch %t -ast-dump-all /dev/null \
|
||||
// RUN: | sed -e "s/ <undeserialized declarations>//" -e "s/ imported//" \
|
||||
// RUN: | FileCheck -strict-whitespace %s
|
||||
|
||||
void TestLabeledBreakContinue() {
|
||||
a: while (true) {
|
||||
break a;
|
||||
continue a;
|
||||
c: for (;;) {
|
||||
break a;
|
||||
continue a;
|
||||
break c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: `-FunctionDecl {{.*}} TestLabeledBreakContinue
|
||||
// CHECK-NEXT: `-CompoundStmt {{.*}} <col:33, line:21:1>
|
||||
// CHECK-NEXT: `-LabelStmt {{.*}} <line:12:3, line:20:3> 'a'
|
||||
// CHECK-NEXT: `-WhileStmt {{.*}} <line:12:6, line:20:3>
|
||||
// CHECK-NEXT: |-CXXBoolLiteralExpr {{.*}} <line:12:13> 'bool' true
|
||||
// CHECK-NEXT: `-CompoundStmt {{.*}} <col:19, line:20:3>
|
||||
// CHECK-NEXT: |-BreakStmt {{.*}} <line:13:5, col:11> 'a' (WhileStmt {{.*}})
|
||||
// CHECK-NEXT: |-ContinueStmt {{.*}} <line:14:5, col:14> 'a' (WhileStmt {{.*}})
|
||||
// CHECK-NEXT: `-LabelStmt {{.*}} <line:15:5, line:19:5> 'c'
|
||||
// CHECK-NEXT: `-ForStmt {{.*}} <line:15:8, line:19:5>
|
||||
// CHECK-NEXT: |-<<<NULL>>>
|
||||
// CHECK-NEXT: |-<<<NULL>>>
|
||||
// CHECK-NEXT: |-<<<NULL>>>
|
||||
// CHECK-NEXT: |-<<<NULL>>>
|
||||
// CHECK-NEXT: `-CompoundStmt {{.*}} <line:15:17, line:19:5>
|
||||
// CHECK-NEXT: |-BreakStmt {{.*}} <line:16:7, col:13> 'a' (WhileStmt {{.*}})
|
||||
// CHECK-NEXT: |-ContinueStmt {{.*}} <line:17:7, col:16> 'a' (WhileStmt {{.*}})
|
||||
// CHECK-NEXT: `-BreakStmt {{.*}} <line:18:7, col:13> 'c' (ForStmt {{.*}})
|
||||
28
clang/test/AST/ast-print-labeled-break-continue.c
Normal file
28
clang/test/AST/ast-print-labeled-break-continue.c
Normal file
@ -0,0 +1,28 @@
|
||||
// RUN: %clang_cc1 -std=c2y -ast-print %s | FileCheck %s
|
||||
|
||||
void TestLabeledBreakContinue() {
|
||||
a: while (true) {
|
||||
break a;
|
||||
continue a;
|
||||
c: for (;;) {
|
||||
break a;
|
||||
continue a;
|
||||
break c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: void TestLabeledBreakContinue(void) {
|
||||
// CHECK-NEXT: a:
|
||||
// CHECK-NEXT: while (true)
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: break a;
|
||||
// CHECK-NEXT: continue a;
|
||||
// CHECK-NEXT: c:
|
||||
// CHECK-NEXT: for (;;) {
|
||||
// CHECK-NEXT: break a;
|
||||
// CHECK-NEXT: continue a;
|
||||
// CHECK-NEXT: break c;
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: }
|
||||
@ -1,6 +1,9 @@
|
||||
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -Wno-error=invalid-gnu-asm-cast %s > %t 2>&1
|
||||
// RUN: FileCheck --input-file=%t --check-prefix=CHECK %s
|
||||
|
||||
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -std=c2y -Wno-error=invalid-gnu-asm-cast %s > %t 2>&1
|
||||
// RUN: FileCheck --input-file=%t --check-prefixes=CHECK,SINCE-C26 %s
|
||||
|
||||
// This file is the C version of cfg.cpp.
|
||||
// Tests that are C-specific should go into this file.
|
||||
|
||||
@ -118,3 +121,144 @@ void vla_type_indirect(int x) {
|
||||
// Do not evaluate x
|
||||
void (*fp_vla)(int[x]);
|
||||
}
|
||||
|
||||
#if __STDC_VERSION__ >= 202400L // If C26 or above
|
||||
// SINCE-C26: int labeled_break_and_continue(int x)
|
||||
// SINCE-C26-NEXT: [B17 (ENTRY)]
|
||||
// SINCE-C26-NEXT: Succs (1): B2
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B1]
|
||||
// SINCE-C26-NEXT: 1: 0
|
||||
// SINCE-C26-NEXT: 2: return [B1.1];
|
||||
// SINCE-C26-NEXT: Preds (1): B9
|
||||
// SINCE-C26-NEXT: Succs (1): B0
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B2]
|
||||
// SINCE-C26-NEXT: a:
|
||||
// SINCE-C26-NEXT: 1: x
|
||||
// SINCE-C26-NEXT: 2: [B2.1] (ImplicitCastExpr, LValueToRValue, int)
|
||||
// SINCE-C26-NEXT: T: switch [B2.2]
|
||||
// SINCE-C26-NEXT: Preds (1): B17
|
||||
// SINCE-C26-NEXT: Succs (3): B9 B16 B8
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B3]
|
||||
// SINCE-C26-NEXT: 1: x
|
||||
// SINCE-C26-NEXT: 2: [B3.1] (ImplicitCastExpr, LValueToRValue, int)
|
||||
// SINCE-C26-NEXT: 3: 2
|
||||
// SINCE-C26-NEXT: 4: [B3.2] + [B3.3]
|
||||
// SINCE-C26-NEXT: 5: return [B3.4];
|
||||
// SINCE-C26-NEXT: Preds (3): B6 B7 B4
|
||||
// SINCE-C26-NEXT: Succs (1): B0
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B4]
|
||||
// SINCE-C26-NEXT: c:
|
||||
// SINCE-C26-NEXT: 1: x
|
||||
// SINCE-C26-NEXT: 2: [B4.1] (ImplicitCastExpr, LValueToRValue, int)
|
||||
// SINCE-C26-NEXT: T: switch [B4.2]
|
||||
// SINCE-C26-NEXT: Preds (1): B8
|
||||
// SINCE-C26-NEXT: Succs (3): B6 B7 B3
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B5]
|
||||
// SINCE-C26-NEXT: 1: x
|
||||
// SINCE-C26-NEXT: 2: [B5.1] (ImplicitCastExpr, LValueToRValue, int)
|
||||
// SINCE-C26-NEXT: 3: 3
|
||||
// SINCE-C26-NEXT: 4: [B5.2] + [B5.3]
|
||||
// SINCE-C26-NEXT: 5: return [B5.4];
|
||||
// SINCE-C26-NEXT: Succs (1): B0
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B6]
|
||||
// SINCE-C26-NEXT: case 30:
|
||||
// SINCE-C26-NEXT: T: break c;
|
||||
// SINCE-C26-NEXT: Preds (1): B4
|
||||
// SINCE-C26-NEXT: Succs (1): B3
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B7]
|
||||
// SINCE-C26-NEXT: case 10:
|
||||
// SINCE-C26-NEXT: T: break a;
|
||||
// SINCE-C26-NEXT: Preds (1): B4
|
||||
// SINCE-C26-NEXT: Succs (1): B3
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B8]
|
||||
// SINCE-C26-NEXT: default:
|
||||
// SINCE-C26-NEXT: Preds (1): B2
|
||||
// SINCE-C26-NEXT: Succs (1): B4
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B9]
|
||||
// SINCE-C26-NEXT: case 2:
|
||||
// SINCE-C26-NEXT: T: break a;
|
||||
// SINCE-C26-NEXT: Preds (2): B2 B11
|
||||
// SINCE-C26-NEXT: Succs (1): B1
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B10]
|
||||
// SINCE-C26-NEXT: 1: 1
|
||||
// SINCE-C26-NEXT: T: do ... while [B10.1]
|
||||
// SINCE-C26-NEXT: Preds (1): B12
|
||||
// SINCE-C26-NEXT: Succs (2): B14 NULL
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B11]
|
||||
// SINCE-C26-NEXT: T: break b;
|
||||
// SINCE-C26-NEXT: Preds (1): B13
|
||||
// SINCE-C26-NEXT: Succs (1): B9
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B12]
|
||||
// SINCE-C26-NEXT: 1: x
|
||||
// SINCE-C26-NEXT: 2: ++[B12.1]
|
||||
// SINCE-C26-NEXT: T: continue b;
|
||||
// SINCE-C26-NEXT: Preds (1): B13
|
||||
// SINCE-C26-NEXT: Succs (1): B10
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B13]
|
||||
// SINCE-C26-NEXT: 1: x
|
||||
// SINCE-C26-NEXT: 2: [B13.1] (ImplicitCastExpr, LValueToRValue, int)
|
||||
// SINCE-C26-NEXT: 3: x
|
||||
// SINCE-C26-NEXT: 4: [B13.3] (ImplicitCastExpr, LValueToRValue, int)
|
||||
// SINCE-C26-NEXT: 5: [B13.2] * [B13.4]
|
||||
// SINCE-C26-NEXT: 6: 100
|
||||
// SINCE-C26-NEXT: 7: [B13.5] > [B13.6]
|
||||
// SINCE-C26-NEXT: T: if [B13.7]
|
||||
// SINCE-C26-NEXT: Preds (2): B14 B15
|
||||
// SINCE-C26-NEXT: Succs (2): B12 B11
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B14]
|
||||
// SINCE-C26-NEXT: Preds (1): B10
|
||||
// SINCE-C26-NEXT: Succs (1): B13
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B15]
|
||||
// SINCE-C26-NEXT: b:
|
||||
// SINCE-C26-NEXT: Preds (1): B16
|
||||
// SINCE-C26-NEXT: Succs (1): B13
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B16]
|
||||
// SINCE-C26-NEXT: case 1:
|
||||
// SINCE-C26-NEXT: Preds (1): B2
|
||||
// SINCE-C26-NEXT: Succs (1): B15
|
||||
// SINCE-C26-EMPTY:
|
||||
// SINCE-C26-NEXT: [B0 (EXIT)]
|
||||
// SINCE-C26-NEXT: Preds (3): B1 B3 B5
|
||||
int labeled_break_and_continue(int x) {
|
||||
a: switch (x) {
|
||||
case 1:
|
||||
b: do {
|
||||
if (x * x > 100) {
|
||||
++x;
|
||||
continue b;
|
||||
}
|
||||
break b;
|
||||
} while (1);
|
||||
case 2:
|
||||
break a;
|
||||
default:
|
||||
c: switch (x) {
|
||||
case 10:
|
||||
break a;
|
||||
case 30:
|
||||
break c;
|
||||
return x + 3; // dead code
|
||||
}
|
||||
return x + 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // __STDC_VERSION__ >= 202400L // If C26 or above
|
||||
|
||||
281
clang/test/CodeGen/labeled-break-continue.c
Normal file
281
clang/test/CodeGen/labeled-break-continue.c
Normal file
@ -0,0 +1,281 @@
|
||||
// RUN: %clang_cc1 -std=c2y -triple x86_64-unknown-linux -emit-llvm -o - %s | FileCheck %s
|
||||
|
||||
bool g1();
|
||||
bool g2();
|
||||
bool g3();
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @f1()
|
||||
// CHECK: entry:
|
||||
// CHECK: br label %l1
|
||||
// CHECK: l1:
|
||||
// CHECK: br label %while.body
|
||||
// CHECK: while.body:
|
||||
// CHECK: br label %while.end
|
||||
// CHECK: while.end:
|
||||
// CHECK: br label %l2
|
||||
// CHECK: l2:
|
||||
// CHECK: br label %while.body1
|
||||
// CHECK: while.body1:
|
||||
// CHECK: br label %while.body1
|
||||
void f1() {
|
||||
l1: while (true) break l1;
|
||||
l2: while (true) continue l2;
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @f2()
|
||||
// CHECK: entry:
|
||||
// CHECK: br label %l1
|
||||
// CHECK: l1:
|
||||
// CHECK: br label %for.cond
|
||||
// CHECK: for.cond:
|
||||
// CHECK: br label %for.end
|
||||
// CHECK: for.end:
|
||||
// CHECK: br label %l2
|
||||
// CHECK: l2:
|
||||
// CHECK: br label %for.cond1
|
||||
// CHECK: for.cond1:
|
||||
// CHECK: br label %for.cond1
|
||||
void f2() {
|
||||
l1: for (;;) break l1;
|
||||
l2: for (;;) continue l2;
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @f3()
|
||||
// CHECK: entry:
|
||||
// CHECK: br label %l1
|
||||
// CHECK: l1:
|
||||
// CHECK: br label %do.body
|
||||
// CHECK: do.body:
|
||||
// CHECK: br label %do.end
|
||||
// CHECK: do.cond:
|
||||
// CHECK: br i1 true, label %do.body, label %do.end
|
||||
// CHECK: do.end:
|
||||
// CHECK: br label %l2
|
||||
// CHECK: l2:
|
||||
// CHECK: br label %do.body1
|
||||
// CHECK: do.body1:
|
||||
// CHECK: br label %do.cond2
|
||||
// CHECK: do.cond2:
|
||||
// CHECK: br i1 true, label %do.body1, label %do.end3
|
||||
// CHECK: do.end3:
|
||||
// CHECK: ret void
|
||||
void f3() {
|
||||
l1: do { break l1; } while (true);
|
||||
l2: do { continue l2; } while (true);
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @f4()
|
||||
// CHECK: entry:
|
||||
// CHECK: br label %l1
|
||||
// CHECK: l1:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.cond:
|
||||
// CHECK: %call = call {{.*}} i1 @g1()
|
||||
// CHECK: br i1 %call, label %while.body, label %while.end14
|
||||
// CHECK: while.body:
|
||||
// CHECK: br label %l2
|
||||
// CHECK: l2:
|
||||
// CHECK: br label %while.cond1
|
||||
// CHECK: while.cond1:
|
||||
// CHECK: %call2 = call {{.*}} i1 @g2()
|
||||
// CHECK: br i1 %call2, label %while.body3, label %while.end
|
||||
// CHECK: while.body3:
|
||||
// CHECK: %call4 = call {{.*}} i1 @g3()
|
||||
// CHECK: br i1 %call4, label %if.then, label %if.end
|
||||
// CHECK: if.then:
|
||||
// CHECK: br label %while.end14
|
||||
// CHECK: if.end:
|
||||
// CHECK: %call5 = call {{.*}} i1 @g3()
|
||||
// CHECK: br i1 %call5, label %if.then6, label %if.end7
|
||||
// CHECK: if.then6:
|
||||
// CHECK: br label %while.end
|
||||
// CHECK: if.end7:
|
||||
// CHECK: %call8 = call {{.*}} i1 @g3()
|
||||
// CHECK: br i1 %call8, label %if.then9, label %if.end10
|
||||
// CHECK: if.then9:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: if.end10:
|
||||
// CHECK: %call11 = call {{.*}} i1 @g3()
|
||||
// CHECK: br i1 %call11, label %if.then12, label %if.end13
|
||||
// CHECK: if.then12:
|
||||
// CHECK: br label %while.cond1
|
||||
// CHECK: if.end13:
|
||||
// CHECK: br label %while.cond1
|
||||
// CHECK: while.end:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.end14:
|
||||
// CHECK: ret void
|
||||
void f4() {
|
||||
l1: while (g1()) {
|
||||
l2: while (g2()) {
|
||||
if (g3()) break l1;
|
||||
if (g3()) break l2;
|
||||
if (g3()) continue l1;
|
||||
if (g3()) continue l2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @f5()
|
||||
// CHECK: entry:
|
||||
// CHECK: br label %l1
|
||||
// CHECK: l1:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.cond:
|
||||
// CHECK: %call = call {{.*}} i1 @g1()
|
||||
// CHECK: br i1 %call, label %while.body, label %while.end
|
||||
// CHECK: while.body:
|
||||
// CHECK: br label %l2
|
||||
// CHECK: l2:
|
||||
// CHECK: %call1 = call {{.*}} i1 @g2()
|
||||
// CHECK: %conv = zext i1 %call1 to i32
|
||||
// CHECK: switch i32 %conv, label %sw.epilog [
|
||||
// CHECK: i32 1, label %sw.bb
|
||||
// CHECK: i32 2, label %sw.bb2
|
||||
// CHECK: i32 3, label %sw.bb3
|
||||
// CHECK: ]
|
||||
// CHECK: sw.bb:
|
||||
// CHECK: br label %while.end
|
||||
// CHECK: sw.bb2:
|
||||
// CHECK: br label %sw.epilog
|
||||
// CHECK: sw.bb3:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: sw.epilog:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.end:
|
||||
// CHECK: ret void
|
||||
void f5() {
|
||||
l1: while (g1()) {
|
||||
l2: switch (g2()) {
|
||||
case 1: break l1;
|
||||
case 2: break l2;
|
||||
case 3: continue l1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @f6()
|
||||
// CHECK: entry:
|
||||
// CHECK: br label %l1
|
||||
// CHECK: l1:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.cond:
|
||||
// CHECK: %call = call {{.*}} i1 @g1()
|
||||
// CHECK: br i1 %call, label %while.body, label %while.end28
|
||||
// CHECK: while.body:
|
||||
// CHECK: br label %l2
|
||||
// CHECK: l2:
|
||||
// CHECK: br label %for.cond
|
||||
// CHECK: for.cond:
|
||||
// CHECK: %call1 = call {{.*}} i1 @g1()
|
||||
// CHECK: br i1 %call1, label %for.body, label %for.end
|
||||
// CHECK: for.body:
|
||||
// CHECK: br label %l3
|
||||
// CHECK: l3:
|
||||
// CHECK: br label %do.body
|
||||
// CHECK: do.body:
|
||||
// CHECK: br label %l4
|
||||
// CHECK: l4:
|
||||
// CHECK: br label %while.cond2
|
||||
// CHECK: while.cond2:
|
||||
// CHECK: %call3 = call {{.*}} i1 @g1()
|
||||
// CHECK: br i1 %call3, label %while.body4, label %while.end
|
||||
// CHECK: while.body4:
|
||||
// CHECK: %call5 = call {{.*}} i1 @g2()
|
||||
// CHECK: br i1 %call5, label %if.then, label %if.end
|
||||
// CHECK: if.then:
|
||||
// CHECK: br label %while.end28
|
||||
// CHECK: if.end:
|
||||
// CHECK: %call6 = call {{.*}} i1 @g2()
|
||||
// CHECK: br i1 %call6, label %if.then7, label %if.end8
|
||||
// CHECK: if.then7:
|
||||
// CHECK: br label %for.end
|
||||
// CHECK: if.end8:
|
||||
// CHECK: %call9 = call {{.*}} i1 @g2()
|
||||
// CHECK: br i1 %call9, label %if.then10, label %if.end11
|
||||
// CHECK: if.then10:
|
||||
// CHECK: br label %do.end
|
||||
// CHECK: if.end11:
|
||||
// CHECK: %call12 = call {{.*}} i1 @g2()
|
||||
// CHECK: br i1 %call12, label %if.then13, label %if.end14
|
||||
// CHECK: if.then13:
|
||||
// CHECK: br label %while.end
|
||||
// CHECK: if.end14:
|
||||
// CHECK: %call15 = call {{.*}} i1 @g2()
|
||||
// CHECK: br i1 %call15, label %if.then16, label %if.end17
|
||||
// CHECK: if.then16:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: if.end17:
|
||||
// CHECK: %call18 = call {{.*}} i1 @g2()
|
||||
// CHECK: br i1 %call18, label %if.then19, label %if.end20
|
||||
// CHECK: if.then19:
|
||||
// CHECK: br label %for.cond
|
||||
// CHECK: if.end20:
|
||||
// CHECK: %call21 = call {{.*}} i1 @g2()
|
||||
// CHECK: br i1 %call21, label %if.then22, label %if.end23
|
||||
// CHECK: if.then22:
|
||||
// CHECK: br label %do.cond
|
||||
// CHECK: if.end23:
|
||||
// CHECK: %call24 = call {{.*}} i1 @g2()
|
||||
// CHECK: br i1 %call24, label %if.then25, label %if.end26
|
||||
// CHECK: if.then25:
|
||||
// CHECK: br label %while.cond2
|
||||
// CHECK: if.end26:
|
||||
// CHECK: br label %while.cond2
|
||||
// CHECK: while.end:
|
||||
// CHECK: br label %do.cond
|
||||
// CHECK: do.cond:
|
||||
// CHECK: %call27 = call {{.*}} i1 @g1()
|
||||
// CHECK: br i1 %call27, label %do.body, label %do.end
|
||||
// CHECK: do.end:
|
||||
// CHECK: br label %for.cond
|
||||
// CHECK: for.end:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.end28:
|
||||
// CHECK: ret void
|
||||
void f6() {
|
||||
l1: while (g1()) {
|
||||
l2: for (; g1();) {
|
||||
l3: do {
|
||||
l4: while (g1()) {
|
||||
if (g2()) break l1;
|
||||
if (g2()) break l2;
|
||||
if (g2()) break l3;
|
||||
if (g2()) break l4;
|
||||
if (g2()) continue l1;
|
||||
if (g2()) continue l2;
|
||||
if (g2()) continue l3;
|
||||
if (g2()) continue l4;
|
||||
}
|
||||
} while (g1());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @f7()
|
||||
// CHECK: entry:
|
||||
// CHECK: br label %loop
|
||||
// CHECK: loop:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.cond:
|
||||
// CHECK: %call = call {{.*}} i1 @g1()
|
||||
// CHECK: br i1 %call, label %while.body, label %while.end
|
||||
// CHECK: while.body:
|
||||
// CHECK: %call1 = call {{.*}} i1 @g2()
|
||||
// CHECK: %conv = zext i1 %call1 to i32
|
||||
// CHECK: switch i32 %conv, label %sw.epilog [
|
||||
// CHECK: i32 1, label %sw.bb
|
||||
// CHECK: ]
|
||||
// CHECK: sw.bb:
|
||||
// CHECK: br label %while.end
|
||||
// CHECK: sw.epilog:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.end:
|
||||
// CHECK: ret void
|
||||
void f7() {
|
||||
loop: while (g1()) {
|
||||
switch (g2()) {
|
||||
case 1: break loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
221
clang/test/CodeGenCXX/labeled-break-continue.cpp
Normal file
221
clang/test/CodeGenCXX/labeled-break-continue.cpp
Normal file
@ -0,0 +1,221 @@
|
||||
// RUN: %clang_cc1 -fnamed-loops -triple x86_64-unknown-linux -std=c++20 -emit-llvm -o - %s | FileCheck %s
|
||||
|
||||
static int a[10]{};
|
||||
struct NonTrivialDestructor {
|
||||
~NonTrivialDestructor();
|
||||
};
|
||||
|
||||
bool g(int);
|
||||
bool h();
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @_Z2f1v()
|
||||
// CHECK: entry:
|
||||
// CHECK: %__range1 = alloca ptr, align 8
|
||||
// CHECK: %__begin1 = alloca ptr, align 8
|
||||
// CHECK: %__end1 = alloca ptr, align 8
|
||||
// CHECK: %i = alloca i32, align 4
|
||||
// CHECK: br label %x
|
||||
// CHECK: x:
|
||||
// CHECK: store ptr @_ZL1a, ptr %__range1, align 8
|
||||
// CHECK: store ptr @_ZL1a, ptr %__begin1, align 8
|
||||
// CHECK: store ptr getelementptr inbounds (i32, ptr @_ZL1a, i64 10), ptr %__end1, align 8
|
||||
// CHECK: br label %for.cond
|
||||
// CHECK: for.cond:
|
||||
// CHECK: %0 = load ptr, ptr %__begin1, align 8
|
||||
// CHECK: %1 = load ptr, ptr %__end1, align 8
|
||||
// CHECK: %cmp = icmp ne ptr %0, %1
|
||||
// CHECK: br i1 %cmp, label %for.body, label %for.end
|
||||
// CHECK: for.body:
|
||||
// CHECK: %2 = load ptr, ptr %__begin1, align 8
|
||||
// CHECK: %3 = load i32, ptr %2, align 4
|
||||
// CHECK: store i32 %3, ptr %i, align 4
|
||||
// CHECK: %4 = load i32, ptr %i, align 4
|
||||
// CHECK: %call = call {{.*}} i1 @_Z1gi(i32 {{.*}} %4)
|
||||
// CHECK: br i1 %call, label %if.then, label %if.end
|
||||
// CHECK: if.then:
|
||||
// CHECK: br label %for.end
|
||||
// CHECK: if.end:
|
||||
// CHECK: %5 = load i32, ptr %i, align 4
|
||||
// CHECK: %call1 = call {{.*}} i1 @_Z1gi(i32 {{.*}} %5)
|
||||
// CHECK: br i1 %call1, label %if.then2, label %if.end3
|
||||
// CHECK: if.then2:
|
||||
// CHECK: br label %for.inc
|
||||
// CHECK: if.end3:
|
||||
// CHECK: br label %for.inc
|
||||
// CHECK: for.inc:
|
||||
// CHECK: %6 = load ptr, ptr %__begin1, align 8
|
||||
// CHECK: %incdec.ptr = getelementptr inbounds nuw i32, ptr %6, i32 1
|
||||
// CHECK: store ptr %incdec.ptr, ptr %__begin1, align 8
|
||||
// CHECK: br label %for.cond
|
||||
// CHECK: for.end:
|
||||
// CHECK: ret void
|
||||
void f1() {
|
||||
x: for (int i : a) {
|
||||
if (g(i)) break x;
|
||||
if (g(i)) continue x;
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @_Z2f2v()
|
||||
// CHECK: entry:
|
||||
// CHECK: %n1 = alloca %struct.NonTrivialDestructor, align 1
|
||||
// CHECK: %__range2 = alloca ptr, align 8
|
||||
// CHECK: %__begin2 = alloca ptr, align 8
|
||||
// CHECK: %__end2 = alloca ptr, align 8
|
||||
// CHECK: %i = alloca i32, align 4
|
||||
// CHECK: %n2 = alloca %struct.NonTrivialDestructor, align 1
|
||||
// CHECK: %cleanup.dest.slot = alloca i32, align 4
|
||||
// CHECK: %n3 = alloca %struct.NonTrivialDestructor, align 1
|
||||
// CHECK: %n4 = alloca %struct.NonTrivialDestructor, align 1
|
||||
// CHECK: br label %l1
|
||||
// CHECK: l1:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.cond:
|
||||
// CHECK: %call = call {{.*}} i1 @_Z1gi(i32 {{.*}} 0)
|
||||
// CHECK: br i1 %call, label %while.body, label %while.end
|
||||
// CHECK: while.body:
|
||||
// CHECK: br label %l2
|
||||
// CHECK: l2:
|
||||
// CHECK: store ptr @_ZL1a, ptr %__range2, align 8
|
||||
// CHECK: store ptr @_ZL1a, ptr %__begin2, align 8
|
||||
// CHECK: store ptr getelementptr inbounds (i32, ptr @_ZL1a, i64 10), ptr %__end2, align 8
|
||||
// CHECK: br label %for.cond
|
||||
// CHECK: for.cond:
|
||||
// CHECK: %0 = load ptr, ptr %__begin2, align 8
|
||||
// CHECK: %1 = load ptr, ptr %__end2, align 8
|
||||
// CHECK: %cmp = icmp ne ptr %0, %1
|
||||
// CHECK: br i1 %cmp, label %for.body, label %for.end
|
||||
// CHECK: for.body:
|
||||
// CHECK: %2 = load ptr, ptr %__begin2, align 8
|
||||
// CHECK: %3 = load i32, ptr %2, align 4
|
||||
// CHECK: store i32 %3, ptr %i, align 4
|
||||
// CHECK: %4 = load i32, ptr %i, align 4
|
||||
// CHECK: %call1 = call {{.*}} i1 @_Z1gi(i32 {{.*}} %4)
|
||||
// CHECK: br i1 %call1, label %if.then, label %if.end
|
||||
// CHECK: if.then:
|
||||
// CHECK: store i32 4, ptr %cleanup.dest.slot, align 4
|
||||
// CHECK: br label %cleanup
|
||||
// CHECK: if.end:
|
||||
// CHECK: %5 = load i32, ptr %i, align 4
|
||||
// CHECK: %call2 = call {{.*}} i1 @_Z1gi(i32 {{.*}} %5)
|
||||
// CHECK: br i1 %call2, label %if.then3, label %if.end4
|
||||
// CHECK: if.then3:
|
||||
// CHECK: store i32 3, ptr %cleanup.dest.slot, align 4
|
||||
// CHECK: br label %cleanup
|
||||
// CHECK: if.end4:
|
||||
// CHECK: %6 = load i32, ptr %i, align 4
|
||||
// CHECK: %call5 = call {{.*}} i1 @_Z1gi(i32 {{.*}} %6)
|
||||
// CHECK: br i1 %call5, label %if.then6, label %if.end7
|
||||
// CHECK: if.then6:
|
||||
// CHECK: store i32 6, ptr %cleanup.dest.slot, align 4
|
||||
// CHECK: br label %cleanup
|
||||
// CHECK: if.end7:
|
||||
// CHECK: %7 = load i32, ptr %i, align 4
|
||||
// CHECK: %call8 = call {{.*}} i1 @_Z1gi(i32 {{.*}} %7)
|
||||
// CHECK: br i1 %call8, label %if.then9, label %if.end10
|
||||
// CHECK: if.then9:
|
||||
// CHECK: store i32 7, ptr %cleanup.dest.slot, align 4
|
||||
// CHECK: br label %cleanup
|
||||
// CHECK: if.end10:
|
||||
// CHECK: call void @_ZN20NonTrivialDestructorD1Ev(ptr {{.*}} %n3)
|
||||
// CHECK: store i32 0, ptr %cleanup.dest.slot, align 4
|
||||
// CHECK: br label %cleanup
|
||||
// CHECK: cleanup:
|
||||
// CHECK: call void @_ZN20NonTrivialDestructorD1Ev(ptr {{.*}} %n2)
|
||||
// CHECK: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4
|
||||
// CHECK: switch i32 %cleanup.dest, label %cleanup11 [
|
||||
// CHECK: i32 0, label %cleanup.cont
|
||||
// CHECK: i32 6, label %for.end
|
||||
// CHECK: i32 7, label %for.inc
|
||||
// CHECK: ]
|
||||
// CHECK: cleanup.cont:
|
||||
// CHECK: br label %for.inc
|
||||
// CHECK: for.inc:
|
||||
// CHECK: %8 = load ptr, ptr %__begin2, align 8
|
||||
// CHECK: %incdec.ptr = getelementptr inbounds nuw i32, ptr %8, i32 1
|
||||
// CHECK: store ptr %incdec.ptr, ptr %__begin2, align 8
|
||||
// CHECK: br label %for.cond
|
||||
// CHECK: for.end:
|
||||
// CHECK: call void @_ZN20NonTrivialDestructorD1Ev(ptr {{.*}} %n4)
|
||||
// CHECK: store i32 0, ptr %cleanup.dest.slot, align 4
|
||||
// CHECK: br label %cleanup11
|
||||
// CHECK: cleanup11:
|
||||
// CHECK: call void @_ZN20NonTrivialDestructorD1Ev(ptr {{.*}} %n1)
|
||||
// CHECK: %cleanup.dest12 = load i32, ptr %cleanup.dest.slot, align 4
|
||||
// CHECK: switch i32 %cleanup.dest12, label %unreachable [
|
||||
// CHECK: i32 0, label %cleanup.cont13
|
||||
// CHECK: i32 4, label %while.end
|
||||
// CHECK: i32 3, label %while.cond
|
||||
// CHECK: ]
|
||||
// CHECK: cleanup.cont13:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.end:
|
||||
// CHECK: ret void
|
||||
// CHECK: unreachable:
|
||||
// CHECK: unreachable
|
||||
void f2() {
|
||||
l1: while (g(0)) {
|
||||
NonTrivialDestructor n1;
|
||||
l2: for (int i : a) {
|
||||
NonTrivialDestructor n2;
|
||||
if (g(i)) break l1;
|
||||
if (g(i)) continue l1;
|
||||
if (g(i)) break l2;
|
||||
if (g(i)) continue l2;
|
||||
NonTrivialDestructor n3;
|
||||
}
|
||||
NonTrivialDestructor n4;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool Continue>
|
||||
void f3() {
|
||||
l1: while (g(1)) {
|
||||
for (;g(2);) {
|
||||
if constexpr (Continue) continue l1;
|
||||
else break l1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @_Z2f3ILb1EEvv()
|
||||
// CHECK: entry:
|
||||
// CHECK: br label %l1
|
||||
// CHECK: l1:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.cond:
|
||||
// CHECK: %call = call {{.*}} i1 @_Z1gi(i32 {{.*}} 1)
|
||||
// CHECK: br i1 %call, label %while.body, label %while.end
|
||||
// CHECK: while.body:
|
||||
// CHECK: br label %for.cond
|
||||
// CHECK: for.cond:
|
||||
// CHECK: %call1 = call {{.*}} i1 @_Z1gi(i32 {{.*}} 2)
|
||||
// CHECK: br i1 %call1, label %for.body, label %for.end
|
||||
// CHECK: for.body:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: for.end:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.end:
|
||||
// CHECK: ret void
|
||||
template void f3<true>();
|
||||
|
||||
// CHECK-LABEL: define {{.*}} void @_Z2f3ILb0EEvv()
|
||||
// CHECK: entry:
|
||||
// CHECK: br label %l1
|
||||
// CHECK: l1:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.cond:
|
||||
// CHECK: %call = call {{.*}} i1 @_Z1gi(i32 {{.*}} 1)
|
||||
// CHECK: br i1 %call, label %while.body, label %while.end
|
||||
// CHECK: while.body:
|
||||
// CHECK: br label %for.cond
|
||||
// CHECK: for.cond:
|
||||
// CHECK: %call1 = call {{.*}} i1 @_Z1gi(i32 {{.*}} 2)
|
||||
// CHECK: br i1 %call1, label %for.body, label %for.end
|
||||
// CHECK: for.body:
|
||||
// CHECK: br label %while.end
|
||||
// CHECK: for.end:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.end:
|
||||
// CHECK: ret void
|
||||
template void f3<false>();
|
||||
174
clang/test/CodeGenObjC/labeled-break-continue.m
Normal file
174
clang/test/CodeGenObjC/labeled-break-continue.m
Normal file
@ -0,0 +1,174 @@
|
||||
// RUN: %clang_cc1 -std=c2y -triple x86_64-apple-darwin -Wno-objc-root-class -emit-llvm -o - %s | FileCheck %s
|
||||
|
||||
int g(id x);
|
||||
|
||||
// CHECK-LABEL: define void @f1(ptr {{.*}} %y)
|
||||
// CHECK: entry:
|
||||
// CHECK: %y.addr = alloca ptr, align 8
|
||||
// CHECK: %x1 = alloca ptr, align 8
|
||||
// CHECK: %state.ptr = alloca %struct.__objcFastEnumerationState, align 8
|
||||
// CHECK: %items.ptr = alloca [16 x ptr], align 8
|
||||
// CHECK: store ptr %y, ptr %y.addr, align 8
|
||||
// CHECK: br label %x
|
||||
// CHECK: x:
|
||||
// CHECK: call void @llvm.memset.p0.i64(ptr align 8 %state.ptr, i8 0, i64 64, i1 false)
|
||||
// CHECK: %0 = load ptr, ptr %y.addr, align 8
|
||||
// CHECK: %1 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_, align 8
|
||||
// CHECK: %call = call i64 @objc_msgSend(ptr {{.*}} %0, ptr {{.*}} %1, ptr {{.*}} %state.ptr, ptr {{.*}} %items.ptr, i64 {{.*}} 16)
|
||||
// CHECK: %iszero = icmp eq i64 %call, 0
|
||||
// CHECK: br i1 %iszero, label %forcoll.empty, label %forcoll.loopinit
|
||||
// CHECK: forcoll.loopinit:
|
||||
// CHECK: %mutationsptr.ptr = getelementptr inbounds nuw %struct.__objcFastEnumerationState, ptr %state.ptr, i32 0, i32 2
|
||||
// CHECK: %mutationsptr = load ptr, ptr %mutationsptr.ptr, align 8
|
||||
// CHECK: %forcoll.initial-mutations = load i64, ptr %mutationsptr, align 8
|
||||
// CHECK: br label %forcoll.loopbody
|
||||
// CHECK: forcoll.loopbody:
|
||||
// CHECK: %forcoll.index = phi i64 [ 0, %forcoll.loopinit ], [ %6, %forcoll.next ], [ 0, %forcoll.refetch ]
|
||||
// CHECK: %forcoll.count = phi i64 [ %call, %forcoll.loopinit ], [ %forcoll.count, %forcoll.next ], [ %call8, %forcoll.refetch ]
|
||||
// CHECK: %mutationsptr2 = load ptr, ptr %mutationsptr.ptr, align 8
|
||||
// CHECK: %statemutations = load i64, ptr %mutationsptr2, align 8
|
||||
// CHECK: %2 = icmp eq i64 %statemutations, %forcoll.initial-mutations
|
||||
// CHECK: br i1 %2, label %forcoll.notmutated, label %forcoll.mutated
|
||||
// CHECK: forcoll.mutated:
|
||||
// CHECK: call void @objc_enumerationMutation(ptr {{.*}} %0)
|
||||
// CHECK: br label %forcoll.notmutated
|
||||
// CHECK: forcoll.notmutated:
|
||||
// CHECK: %stateitems.ptr = getelementptr inbounds nuw %struct.__objcFastEnumerationState, ptr %state.ptr, i32 0, i32 1
|
||||
// CHECK: %stateitems = load ptr, ptr %stateitems.ptr, align 8
|
||||
// CHECK: %currentitem.ptr = getelementptr inbounds ptr, ptr %stateitems, i64 %forcoll.index
|
||||
// CHECK: %3 = load ptr, ptr %currentitem.ptr, align 8
|
||||
// CHECK: store ptr %3, ptr %x1, align 8
|
||||
// CHECK: %4 = load ptr, ptr %x1, align 8
|
||||
// CHECK: %call3 = call i32 @g(ptr {{.*}} %4)
|
||||
// CHECK: %tobool = icmp ne i32 %call3, 0
|
||||
// CHECK: br i1 %tobool, label %if.then, label %if.end
|
||||
// CHECK: if.then:
|
||||
// CHECK: br label %forcoll.end
|
||||
// CHECK: if.end:
|
||||
// CHECK: %5 = load ptr, ptr %x1, align 8
|
||||
// CHECK: %call4 = call i32 @g(ptr {{.*}} %5)
|
||||
// CHECK: %tobool5 = icmp ne i32 %call4, 0
|
||||
// CHECK: br i1 %tobool5, label %if.then6, label %if.end7
|
||||
// CHECK: if.then6:
|
||||
// CHECK: br label %forcoll.next
|
||||
// CHECK: if.end7:
|
||||
// CHECK: br label %forcoll.next
|
||||
// CHECK: forcoll.next:
|
||||
// CHECK: %6 = add nuw i64 %forcoll.index, 1
|
||||
// CHECK: %7 = icmp ult i64 %6, %forcoll.count
|
||||
// CHECK: br i1 %7, label %forcoll.loopbody, label %forcoll.refetch
|
||||
// CHECK: forcoll.refetch:
|
||||
// CHECK: %8 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_, align 8
|
||||
// CHECK: %call8 = call i64 @objc_msgSend(ptr {{.*}} %0, ptr {{.*}} %8, ptr {{.*}} %state.ptr, ptr {{.*}} %items.ptr, i64 {{.*}} 16)
|
||||
// CHECK: %9 = icmp eq i64 %call8, 0
|
||||
// CHECK: br i1 %9, label %forcoll.empty, label %forcoll.loopbody
|
||||
// CHECK: forcoll.empty:
|
||||
// CHECK: br label %forcoll.end
|
||||
// CHECK: forcoll.end:
|
||||
// CHECK: ret void
|
||||
void f1(id y) {
|
||||
x: for (id x in y) {
|
||||
if (g(x)) break x;
|
||||
if (g(x)) continue x;
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define void @f2(ptr {{.*}} %y)
|
||||
// CHECK: entry:
|
||||
// CHECK: %y.addr = alloca ptr, align 8
|
||||
// CHECK: %x = alloca ptr, align 8
|
||||
// CHECK: %state.ptr = alloca %struct.__objcFastEnumerationState, align 8
|
||||
// CHECK: %items.ptr = alloca [16 x ptr], align 8
|
||||
// CHECK: store ptr %y, ptr %y.addr, align 8
|
||||
// CHECK: br label %a
|
||||
// CHECK: a:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.cond:
|
||||
// CHECK: %0 = load ptr, ptr %y.addr, align 8
|
||||
// CHECK: %call = call i32 @g(ptr {{.*}} %0)
|
||||
// CHECK: %tobool = icmp ne i32 %call, 0
|
||||
// CHECK: br i1 %tobool, label %while.body, label %while.end
|
||||
// CHECK: while.body:
|
||||
// CHECK: br label %b
|
||||
// CHECK: b:
|
||||
// CHECK: call void @llvm.memset.p0.i64(ptr align 8 %state.ptr, i8 0, i64 64, i1 false)
|
||||
// CHECK: %1 = load ptr, ptr %y.addr, align 8
|
||||
// CHECK: %2 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_, align 8
|
||||
// CHECK: %call1 = call i64 @objc_msgSend(ptr {{.*}} %1, ptr {{.*}} %2, ptr {{.*}} %state.ptr, ptr {{.*}} %items.ptr, i64 {{.*}} 16)
|
||||
// CHECK: %iszero = icmp eq i64 %call1, 0
|
||||
// CHECK: br i1 %iszero, label %forcoll.empty, label %forcoll.loopinit
|
||||
// CHECK: forcoll.loopinit:
|
||||
// CHECK: %mutationsptr.ptr = getelementptr inbounds nuw %struct.__objcFastEnumerationState, ptr %state.ptr, i32 0, i32 2
|
||||
// CHECK: %mutationsptr = load ptr, ptr %mutationsptr.ptr, align 8
|
||||
// CHECK: %forcoll.initial-mutations = load i64, ptr %mutationsptr, align 8
|
||||
// CHECK: br label %forcoll.loopbody
|
||||
// CHECK: forcoll.loopbody:
|
||||
// CHECK: %forcoll.index = phi i64 [ 0, %forcoll.loopinit ], [ %9, %forcoll.next ], [ 0, %forcoll.refetch ]
|
||||
// CHECK: %forcoll.count = phi i64 [ %call1, %forcoll.loopinit ], [ %forcoll.count, %forcoll.next ], [ %call17, %forcoll.refetch ]
|
||||
// CHECK: %mutationsptr2 = load ptr, ptr %mutationsptr.ptr, align 8
|
||||
// CHECK: %statemutations = load i64, ptr %mutationsptr2, align 8
|
||||
// CHECK: %3 = icmp eq i64 %statemutations, %forcoll.initial-mutations
|
||||
// CHECK: br i1 %3, label %forcoll.notmutated, label %forcoll.mutated
|
||||
// CHECK: forcoll.mutated:
|
||||
// CHECK: call void @objc_enumerationMutation(ptr {{.*}} %1)
|
||||
// CHECK: br label %forcoll.notmutated
|
||||
// CHECK: forcoll.notmutated:
|
||||
// CHECK: %stateitems.ptr = getelementptr inbounds nuw %struct.__objcFastEnumerationState, ptr %state.ptr, i32 0, i32 1
|
||||
// CHECK: %stateitems = load ptr, ptr %stateitems.ptr, align 8
|
||||
// CHECK: %currentitem.ptr = getelementptr inbounds ptr, ptr %stateitems, i64 %forcoll.index
|
||||
// CHECK: %4 = load ptr, ptr %currentitem.ptr, align 8
|
||||
// CHECK: store ptr %4, ptr %x, align 8
|
||||
// CHECK: %5 = load ptr, ptr %x, align 8
|
||||
// CHECK: %call3 = call i32 @g(ptr {{.*}} %5)
|
||||
// CHECK: %tobool4 = icmp ne i32 %call3, 0
|
||||
// CHECK: br i1 %tobool4, label %if.then, label %if.end
|
||||
// CHECK: if.then:
|
||||
// CHECK: br label %while.end
|
||||
// CHECK: if.end:
|
||||
// CHECK: %6 = load ptr, ptr %x, align 8
|
||||
// CHECK: %call5 = call i32 @g(ptr {{.*}} %6)
|
||||
// CHECK: %tobool6 = icmp ne i32 %call5, 0
|
||||
// CHECK: br i1 %tobool6, label %if.then7, label %if.end8
|
||||
// CHECK: if.then7:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: if.end8:
|
||||
// CHECK: %7 = load ptr, ptr %x, align 8
|
||||
// CHECK: %call9 = call i32 @g(ptr {{.*}} %7)
|
||||
// CHECK: %tobool10 = icmp ne i32 %call9, 0
|
||||
// CHECK: br i1 %tobool10, label %if.then11, label %if.end12
|
||||
// CHECK: if.then11:
|
||||
// CHECK: br label %forcoll.end
|
||||
// CHECK: if.end12:
|
||||
// CHECK: %8 = load ptr, ptr %x, align 8
|
||||
// CHECK: %call13 = call i32 @g(ptr {{.*}} %8)
|
||||
// CHECK: %tobool14 = icmp ne i32 %call13, 0
|
||||
// CHECK: br i1 %tobool14, label %if.then15, label %if.end16
|
||||
// CHECK: if.then15:
|
||||
// CHECK: br label %forcoll.next
|
||||
// CHECK: if.end16:
|
||||
// CHECK: br label %forcoll.next
|
||||
// CHECK: forcoll.next:
|
||||
// CHECK: %9 = add nuw i64 %forcoll.index, 1
|
||||
// CHECK: %10 = icmp ult i64 %9, %forcoll.count
|
||||
// CHECK: br i1 %10, label %forcoll.loopbody, label %forcoll.refetch
|
||||
// CHECK: forcoll.refetch:
|
||||
// CHECK: %11 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_, align 8
|
||||
// CHECK: %call17 = call i64 @objc_msgSend(ptr {{.*}} %1, ptr {{.*}} %11, ptr {{.*}} %state.ptr, ptr {{.*}} %items.ptr, i64 {{.*}} 16)
|
||||
// CHECK: %12 = icmp eq i64 %call17, 0
|
||||
// CHECK: br i1 %12, label %forcoll.empty, label %forcoll.loopbody
|
||||
// CHECK: forcoll.empty:
|
||||
// CHECK: br label %forcoll.end
|
||||
// CHECK: forcoll.end:
|
||||
// CHECK: br label %while.cond
|
||||
// CHECK: while.end:
|
||||
// CHECK: ret void
|
||||
void f2(id y) {
|
||||
a: while (g(y)) {
|
||||
b: for (id x in y) {
|
||||
if (g(x)) break a;
|
||||
if (g(x)) continue a;
|
||||
if (g(x)) break b;
|
||||
if (g(x)) continue b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
// RUN: %clang_cc1 -fsyntax-only -fopenmp -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp4 %s -Wuninitialized
|
||||
// RUN: %clang_cc1 -fsyntax-only -fopenmp -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp5 %s -Wuninitialized
|
||||
// RUN: %clang_cc1 -fsyntax-only -fopenmp -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp4 %s -Wuninitialized
|
||||
// RUN: %clang_cc1 -fsyntax-only -fopenmp -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp5 %s -Wuninitialized
|
||||
|
||||
// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp4 %s -Wuninitialized
|
||||
// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp5 %s -Wuninitialized
|
||||
// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp4 %s -Wuninitialized
|
||||
// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp5 %s -Wuninitialized
|
||||
|
||||
class S {
|
||||
int a;
|
||||
@ -842,3 +842,22 @@ void test_static_data_member() {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void test_labeled_break() {
|
||||
#pragma omp parallel
|
||||
#pragma omp for
|
||||
a: // expected-error {{statement after '#pragma omp for' must be a for loop}}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
break a; // expected-error {{'break' statement cannot be used in OpenMP for loop}}
|
||||
continue a;
|
||||
}
|
||||
|
||||
b: while (1) {
|
||||
#pragma omp parallel
|
||||
#pragma omp for
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue b; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
clang/test/Parser/labeled-break-continue.c
Normal file
13
clang/test/Parser/labeled-break-continue.c
Normal file
@ -0,0 +1,13 @@
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify -std=c2y %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify -fnamed-loops -std=c23 %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify -fnamed-loops -x c++ %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=disabled -std=c23 %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=disabled -x c++ %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=disabled -std=c23 -pedantic %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=disabled -x c++ -pedantic %s
|
||||
// expected-no-diagnostics
|
||||
|
||||
void f() {
|
||||
x1: while (1) break x1; // disabled-error {{named 'break' is only supported in C2y}}
|
||||
x2: while (1) continue x2; // disabled-error {{named 'continue' is only supported in C2y}}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
// RUN: %clang_cc1 -triple x86_64-windows -fborland-extensions -DBORLAND -fsyntax-only -verify -fblocks %s
|
||||
// RUN: %clang_cc1 -triple x86_64-windows -fms-extensions -fsyntax-only -verify -fblocks %s
|
||||
// RUN: %clang_cc1 -triple x86_64-windows -fborland-extensions -DBORLAND -fsyntax-only -verify -fblocks -fnamed-loops %s
|
||||
// RUN: %clang_cc1 -triple x86_64-windows -fms-extensions -fsyntax-only -verify -fblocks -fnamed-loops %s
|
||||
|
||||
#define JOIN2(x,y) x ## y
|
||||
#define JOIN(x,y) JOIN2(x,y)
|
||||
@ -287,3 +287,19 @@ void test_typo_in_except(void) {
|
||||
} __except(undeclared_identifier) { // expected-error {{use of undeclared identifier 'undeclared_identifier'}} expected-error {{expected expression}}
|
||||
}
|
||||
}
|
||||
|
||||
void test_jump_out_of___finally_labeled(void) {
|
||||
a: while(1) {
|
||||
__try {
|
||||
} __finally {
|
||||
continue a; // expected-warning{{jump out of __finally block has undefined behavior}}
|
||||
break a; // expected-warning{{jump out of __finally block has undefined behavior}}
|
||||
b: while (1) {
|
||||
continue a; // expected-warning{{jump out of __finally block has undefined behavior}}
|
||||
break a; // expected-warning{{jump out of __finally block has undefined behavior}}
|
||||
continue b;
|
||||
break b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
161
clang/test/Sema/labeled-break-continue.c
Normal file
161
clang/test/Sema/labeled-break-continue.c
Normal file
@ -0,0 +1,161 @@
|
||||
// RUN: %clang_cc1 -std=c2y -verify -fsyntax-only -fblocks %s
|
||||
// RUN: %clang_cc1 -std=c23 -verify -fsyntax-only -fblocks -fnamed-loops %s
|
||||
// RUN: %clang_cc1 -x c++ -verify -fsyntax-only -fblocks -fnamed-loops %s
|
||||
|
||||
void f1() {
|
||||
l1: while (true) {
|
||||
break l1;
|
||||
continue l1;
|
||||
}
|
||||
|
||||
l2: for (;;) {
|
||||
break l2;
|
||||
continue l2;
|
||||
}
|
||||
|
||||
l3: do {
|
||||
break l3;
|
||||
continue l3;
|
||||
} while (true);
|
||||
|
||||
l4: switch (1) {
|
||||
case 1:
|
||||
break l4;
|
||||
}
|
||||
}
|
||||
|
||||
void f2() {
|
||||
l1:;
|
||||
break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
|
||||
l2: while (true) {
|
||||
break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
break l2; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue l2; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
}
|
||||
|
||||
break l3; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue l3; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
l3: while (true) {}
|
||||
}
|
||||
|
||||
void f3() {
|
||||
a: b: c: d: while (true) {
|
||||
break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
break d;
|
||||
|
||||
continue a; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
continue b; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
continue c; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
continue d;
|
||||
|
||||
e: while (true) {
|
||||
break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
break d;
|
||||
break e;
|
||||
|
||||
continue a; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
continue b; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
continue c; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
continue d;
|
||||
continue e;
|
||||
}
|
||||
|
||||
break e; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue e; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
}
|
||||
}
|
||||
|
||||
void f4() {
|
||||
a: switch (1) {
|
||||
case 1: {
|
||||
continue a; // expected-error {{label of 'continue' refers to a switch statement}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void f5() {
|
||||
a: {
|
||||
break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
}
|
||||
|
||||
b: {
|
||||
while (true)
|
||||
break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
}
|
||||
}
|
||||
|
||||
void f6() {
|
||||
a: while (({
|
||||
break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue a; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
1;
|
||||
})) {
|
||||
({ break a; });
|
||||
({ continue a; });
|
||||
}
|
||||
|
||||
b: for (
|
||||
int x = ({
|
||||
break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue b; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
1;
|
||||
});
|
||||
({
|
||||
break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue b; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
1;
|
||||
});
|
||||
(void) ({
|
||||
break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue b; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
1;
|
||||
})
|
||||
) {
|
||||
({ break b; });
|
||||
({ continue b; });
|
||||
}
|
||||
|
||||
c: do {
|
||||
({ break c; });
|
||||
({ continue c; });
|
||||
} while (({
|
||||
break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue c; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
1;
|
||||
}));
|
||||
|
||||
d: switch (({
|
||||
break d; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue d; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
1;
|
||||
})) {
|
||||
case 1: {
|
||||
({ break d; });
|
||||
({ continue d; }); // expected-error {{label of 'continue' refers to a switch statement}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void f7() {
|
||||
a: while (true) {
|
||||
(void) ^{
|
||||
break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue a; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
};
|
||||
}
|
||||
|
||||
while (true) {
|
||||
break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue d; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
}
|
||||
}
|
||||
169
clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
Normal file
169
clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
// RUN: %clang_cc1 -fnamed-loops -std=c++23 -fsyntax-only -verify %s
|
||||
// expected-no-diagnostics
|
||||
|
||||
struct Tracker {
|
||||
bool& destroyed;
|
||||
constexpr Tracker(bool& destroyed) : destroyed{destroyed} {}
|
||||
constexpr ~Tracker() { destroyed = true; }
|
||||
};
|
||||
|
||||
constexpr int f1() {
|
||||
a: for (;;) {
|
||||
for (;;) {
|
||||
break a;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
static_assert(f1() == 1);
|
||||
|
||||
constexpr int f2() {
|
||||
int x{};
|
||||
a: for (int i = 0; i < 10; i++) {
|
||||
b: for (int j = 0; j < 10; j++) {
|
||||
x += j;
|
||||
if (i == 2 && j == 2) break a;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
static_assert(f2() == 93);
|
||||
|
||||
constexpr int f3() {
|
||||
int x{};
|
||||
a: for (int i = 0; i < 10; i++) {
|
||||
x += i;
|
||||
continue a;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
static_assert(f3() == 45);
|
||||
|
||||
constexpr int f4() {
|
||||
int x{};
|
||||
a: for (int i = 1; i < 10; i++) {
|
||||
x += i;
|
||||
break a;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
static_assert(f4() == 1);
|
||||
|
||||
constexpr bool f5(bool should_break) {
|
||||
bool destroyed = false;
|
||||
a: while (!destroyed) {
|
||||
while (true) {
|
||||
Tracker _{destroyed};
|
||||
if (should_break) break a;
|
||||
continue a;
|
||||
}
|
||||
}
|
||||
return destroyed;
|
||||
}
|
||||
static_assert(f5(true));
|
||||
static_assert(f5(false));
|
||||
|
||||
constexpr bool f6(bool should_break) {
|
||||
bool destroyed = false;
|
||||
a: while (!destroyed) {
|
||||
while (true) {
|
||||
while (true) {
|
||||
Tracker _{destroyed};
|
||||
while (true) {
|
||||
while (true) {
|
||||
if (should_break) break a;
|
||||
continue a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return destroyed;
|
||||
}
|
||||
static_assert(f6(true));
|
||||
static_assert(f6(false));
|
||||
|
||||
constexpr int f7(bool should_break) {
|
||||
int x = 100;
|
||||
a: for (int i = 0; i < 10; i++) {
|
||||
b: switch (1) {
|
||||
case 1:
|
||||
x += i;
|
||||
if (should_break) break a;
|
||||
break b;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
static_assert(f7(true) == 100);
|
||||
static_assert(f7(false) == 145);
|
||||
|
||||
constexpr bool f8() {
|
||||
a: switch (1) {
|
||||
case 1: {
|
||||
while (true) {
|
||||
switch (1) {
|
||||
case 1: break a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static_assert(f8());
|
||||
|
||||
constexpr bool f9() {
|
||||
a: do {
|
||||
while (true) {
|
||||
break a;
|
||||
}
|
||||
} while (true);
|
||||
return true;
|
||||
}
|
||||
static_assert(f9());
|
||||
|
||||
constexpr int f10(bool should_break) {
|
||||
int a[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||
int x{};
|
||||
a: for (int v : a) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
x += v;
|
||||
if (should_break && v == 5) break a;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static_assert(f10(true) == 35);
|
||||
static_assert(f10(false) == 165);
|
||||
|
||||
constexpr bool f11() {
|
||||
struct X {
|
||||
int a[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||
Tracker t;
|
||||
constexpr X(bool& b) : t{b} {}
|
||||
};
|
||||
|
||||
bool destroyed = false;
|
||||
a: for (int v : X(destroyed).a) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (v == 5) break a;
|
||||
}
|
||||
}
|
||||
return destroyed;
|
||||
}
|
||||
static_assert(f11());
|
||||
|
||||
template <typename T>
|
||||
constexpr T f12() {
|
||||
T x{};
|
||||
a: for (T i = 0; i < 10; i++) {
|
||||
b: for (T j = 0; j < 10; j++) {
|
||||
x += j;
|
||||
if (i == 2 && j == 2) break a;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
static_assert(f12<int>() == 93);
|
||||
static_assert(f12<unsigned>() == 93u);
|
||||
51
clang/test/SemaCXX/labeled-break-continue.cpp
Normal file
51
clang/test/SemaCXX/labeled-break-continue.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
// RUN: %clang_cc1 -std=c++20 -verify -fsyntax-only -fnamed-loops %s
|
||||
|
||||
int a[10]{};
|
||||
struct S {
|
||||
int a[10]{};
|
||||
};
|
||||
|
||||
void f1() {
|
||||
l1: for (int x : a) {
|
||||
break l1;
|
||||
continue l1;
|
||||
}
|
||||
|
||||
l2: for (int x : a) {
|
||||
break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
}
|
||||
|
||||
l3: for (int x : a) {
|
||||
l4: for (int x : a) {
|
||||
break l3;
|
||||
break l4;
|
||||
continue l3;
|
||||
continue l4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void f2() {
|
||||
l1: for (
|
||||
int x = ({
|
||||
break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
1;
|
||||
});
|
||||
int y : ({
|
||||
break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
S();
|
||||
}).a
|
||||
) {}
|
||||
}
|
||||
|
||||
void f3() {
|
||||
a: while (true) {
|
||||
(void) []{
|
||||
break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue a; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
};
|
||||
}
|
||||
}
|
||||
39
clang/test/SemaObjC/labeled-break-continue.m
Normal file
39
clang/test/SemaObjC/labeled-break-continue.m
Normal file
@ -0,0 +1,39 @@
|
||||
// RUN: %clang_cc1 -std=c2y -fsyntax-only -verify -fblocks %s
|
||||
|
||||
void f1(id y) {
|
||||
l1: for (id x in y) {
|
||||
break l1;
|
||||
continue l1;
|
||||
}
|
||||
|
||||
l2: for (id x in y) {
|
||||
break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
}
|
||||
|
||||
l3: for (id x in y) {
|
||||
l4: for (id x in y) {
|
||||
break l3;
|
||||
break l4;
|
||||
continue l3;
|
||||
continue l4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void f2(id y) {
|
||||
l1: for (id x in ({
|
||||
break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
y;
|
||||
})) {}
|
||||
}
|
||||
|
||||
void f3(id y) {
|
||||
a: b: for (id x in y) {
|
||||
(void) ^{
|
||||
break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
|
||||
continue b; // expected-error {{'continue' label does not name an enclosing loop}}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// RUN: %clang_cc1 %s -verify -fopenacc
|
||||
// RUN: %clang_cc1 %s -verify -fopenacc -fnamed-loops
|
||||
|
||||
void BreakContinue() {
|
||||
|
||||
@ -687,3 +687,26 @@ void DuffsDeviceLoop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LabeledBreakContinue() {
|
||||
a: for (int i =0; i < 5; ++i) {
|
||||
#pragma acc parallel
|
||||
{
|
||||
continue a; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
|
||||
break a; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma acc parallel
|
||||
b: c: for (int i =0; i < 5; ++i) {
|
||||
switch(i) {
|
||||
case 0: break; // leaves switch, not 'for'.
|
||||
}
|
||||
|
||||
break b; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
|
||||
break c; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
|
||||
while (1) break b; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
|
||||
while (1) break c; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
|
||||
d: while (1) break d;
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,7 +252,7 @@ conformance.</p>
|
||||
<tr>
|
||||
<td>Named loops, v3</td>
|
||||
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm">N3355</a></td>
|
||||
<td class="none" align="center">No</td>
|
||||
<td class="unreleased" align="center">Clang 22</td>
|
||||
</tr>
|
||||
<!-- Graz Feb 2025 Papers -->
|
||||
<tr>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user