[clang] e4a1b5f - [Clang] [C2y] Implement N3355 ‘Named Loops’ (#152870)

via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 2 09:37:24 PDT 2025


Author: Sirraide
Date: 2025-09-02T16:37:19Z
New Revision: e4a1b5f36e71c8c382bdd531867c5f6eb3f7deac

URL: https://github.com/llvm/llvm-project/commit/e4a1b5f36e71c8c382bdd531867c5f6eb3f7deac
DIFF: https://github.com/llvm/llvm-project/commit/e4a1b5f36e71c8c382bdd531867c5f6eb3f7deac.diff

LOG: [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 at gmail.com>

Added: 
    clang/test/AST/ast-dump-labeled-break-continue-json.c
    clang/test/AST/ast-dump-labeled-break-continue.c
    clang/test/AST/ast-print-labeled-break-continue.c
    clang/test/CodeGen/labeled-break-continue.c
    clang/test/CodeGenCXX/labeled-break-continue.cpp
    clang/test/CodeGenObjC/labeled-break-continue.m
    clang/test/Parser/labeled-break-continue.c
    clang/test/Sema/labeled-break-continue.c
    clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
    clang/test/SemaCXX/labeled-break-continue.cpp
    clang/test/SemaObjC/labeled-break-continue.m

Modified: 
    clang-tools-extra/clangd/XRefs.cpp
    clang/docs/ReleaseNotes.rst
    clang/include/clang/AST/JSONNodeDumper.h
    clang/include/clang/AST/Stmt.h
    clang/include/clang/AST/TextNodeDumper.h
    clang/include/clang/Basic/DiagnosticParseKinds.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Basic/LangOptions.def
    clang/include/clang/Basic/StmtNodes.td
    clang/include/clang/Driver/Options.td
    clang/include/clang/Parse/Parser.h
    clang/include/clang/Sema/Scope.h
    clang/include/clang/Sema/Sema.h
    clang/lib/AST/ASTImporter.cpp
    clang/lib/AST/ExprConstant.cpp
    clang/lib/AST/JSONNodeDumper.cpp
    clang/lib/AST/Stmt.cpp
    clang/lib/AST/StmtPrinter.cpp
    clang/lib/AST/TextNodeDumper.cpp
    clang/lib/Basic/LangOptions.cpp
    clang/lib/CodeGen/CGObjC.cpp
    clang/lib/CodeGen/CGStmt.cpp
    clang/lib/CodeGen/CGStmtOpenMP.cpp
    clang/lib/CodeGen/CodeGenFunction.h
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/lib/Parse/ParseStmt.cpp
    clang/lib/Sema/Scope.cpp
    clang/lib/Sema/SemaLookup.cpp
    clang/lib/Sema/SemaStmt.cpp
    clang/lib/Sema/TreeTransform.h
    clang/lib/Serialization/ASTReaderStmt.cpp
    clang/lib/Serialization/ASTWriterStmt.cpp
    clang/lib/Tooling/Syntax/BuildTree.cpp
    clang/test/Analysis/cfg.c
    clang/test/OpenMP/for_loop_messages.cpp
    clang/test/Sema/__try.c
    clang/test/SemaOpenACC/no-branch-in-out.c
    clang/www/c_status.html

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index a253a630a48cc..e1c50f906de08 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1106,11 +1106,11 @@ class FindControlFlow : public RecursiveASTVisitor<FindControlFlow> {
     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) {

diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c0c2766f76b4b..dd53b4d46f3cc 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -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
 ^^^^^^^^^^^^^^^^^^^

diff  --git a/clang/include/clang/AST/JSONNodeDumper.h b/clang/include/clang/AST/JSONNodeDumper.h
index 8640780206dba..427a9c51ece1b 100644
--- a/clang/include/clang/AST/JSONNodeDumper.h
+++ b/clang/include/clang/AST/JSONNodeDumper.h
@@ -333,6 +333,7 @@ class JSONNodeDumper
   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);

diff  --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index a5b0d5053003f..76942f1a84f9a 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -277,24 +277,14 @@ class alignas(void *) Stmt {
     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 @@ class alignas(void *) Stmt {
     DoStmtBitfields DoStmtBits;
     ForStmtBitfields ForStmtBits;
     GotoStmtBitfields GotoStmtBits;
-    ContinueStmtBitfields ContinueStmtBits;
-    BreakStmtBitfields BreakStmtBits;
+    LoopControlStmtBitfields LoopControlStmtBits;
     ReturnStmtBitfields ReturnStmtBits;
     SwitchCaseBitfields SwitchCaseBits;
 
@@ -2184,6 +2173,14 @@ class LabelStmt : public ValueStmt {
   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,26 +3053,53 @@ class IndirectGotoStmt : public Stmt {
   }
 };
 
-/// 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);
   }
 
-  /// Build an empty continue statement.
-  explicit ContinueStmt(EmptyShell Empty) : Stmt(ContinueStmtClass, Empty) {}
+  LoopControlStmt(StmtClass Class, SourceLocation Loc)
+      : LoopControlStmt(Class, Loc, SourceLocation(), nullptr) {}
 
-  SourceLocation getContinueLoc() const { return ContinueStmtBits.ContinueLoc; }
-  void setContinueLoc(SourceLocation L) { ContinueStmtBits.ContinueLoc = L; }
+  LoopControlStmt(StmtClass Class, EmptyShell ES) : Stmt(Class, ES) {}
 
-  SourceLocation getBeginLoc() const { return getContinueLoc(); }
-  SourceLocation getEndLoc() const { return getContinueLoc(); }
+public:
+  SourceLocation getKwLoc() const { return LoopControlStmtBits.KwLoc; }
+  void setKwLoc(SourceLocation L) { LoopControlStmtBits.KwLoc = L; }
 
-  static bool classof(const Stmt *T) {
-    return T->getStmtClass() == ContinueStmtClass;
+  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());
@@ -3084,35 +3108,42 @@ class ContinueStmt : public Stmt {
   const_child_range children() const {
     return const_child_range(const_child_iterator(), const_child_iterator());
   }
-};
 
-/// BreakStmt - This represents a break.
-class BreakStmt : public Stmt {
-public:
-  BreakStmt(SourceLocation BL) : Stmt(BreakStmtClass) {
-    setBreakLoc(BL);
+  static bool classof(const Stmt *T) {
+    StmtClass Class = T->getStmtClass();
+    return Class == ContinueStmtClass || Class == BreakStmtClass;
   }
+};
 
-  /// 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; }
+/// 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) {}
 
-  SourceLocation getBeginLoc() const { return getBreakLoc(); }
-  SourceLocation getEndLoc() const { return getBreakLoc(); }
+  /// Build an empty continue statement.
+  explicit ContinueStmt(EmptyShell Empty)
+      : LoopControlStmt(ContinueStmtClass, Empty) {}
 
   static bool classof(const Stmt *T) {
-    return T->getStmtClass() == BreakStmtClass;
+    return T->getStmtClass() == ContinueStmtClass;
   }
+};
 
-  // Iterators
-  child_range children() {
-    return child_range(child_iterator(), child_iterator());
-  }
+/// BreakStmt - This represents a break.
+class BreakStmt : public LoopControlStmt {
+public:
+  BreakStmt(SourceLocation BL) : LoopControlStmt(BreakStmtClass, BL) {}
+  BreakStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
+      : LoopControlStmt(BreakStmtClass, CL, LabelLoc, Target) {}
 
-  const_child_range children() const {
-    return const_child_range(const_child_iterator(), const_child_iterator());
+  /// Build an empty break statement.
+  explicit BreakStmt(EmptyShell Empty)
+      : LoopControlStmt(BreakStmtClass, Empty) {}
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == BreakStmtClass;
   }
 };
 

diff  --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 6d2795111685a..88ecd526e3d7e 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -255,6 +255,7 @@ class TextNodeDumper
   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);

diff  --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index ff506fb258b64..bc7a6e231d93c 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -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">;

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c934fed2c7462..0f3aa9aea215f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -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>;

diff  --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index e0a5351143dfd..84f5ab3443a59 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -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.")
 

diff  --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index c9c173f5c7469..046ef4f30e232 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -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>;

diff  --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index f507968d30670..902a28d60b349 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -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>,

diff  --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index e9437e6d46366..a9a87fb586fc2 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7213,7 +7213,8 @@ class Parser : public CodeCompletionHandler {
   /// '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 @@ class Parser : public CodeCompletionHandler {
   ///
   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 @@ class Parser : public CodeCompletionHandler {
   ///         'switch' '(' expression ')' statement
   /// [C++]   'switch' '(' condition ')' statement
   /// \endverbatim
-  StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc);
+  StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc,
+                                  LabelDecl *PrecedingLabel);
 
   /// ParseWhileStatement
   /// \verbatim
@@ -7406,7 +7409,8 @@ class Parser : public CodeCompletionHandler {
   ///         'while' '(' expression ')' statement
   /// [C++]   'while' '(' condition ')' statement
   /// \endverbatim
-  StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc);
+  StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc,
+                                 LabelDecl *PrecedingLabel);
 
   /// ParseDoStatement
   /// \verbatim
@@ -7414,7 +7418,7 @@ class Parser : public CodeCompletionHandler {
   ///         '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 @@ class Parser : public CodeCompletionHandler {
   /// [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 @@ class Parser : public CodeCompletionHandler {
   /// \verbatim
   ///       jump-statement:
   ///         'continue' ';'
+  /// [C2y]   'continue' identifier ';'
   /// \endverbatim
   ///
   /// Note: this lets the caller parse the end ';'.
@@ -7468,6 +7474,7 @@ class Parser : public CodeCompletionHandler {
   /// \verbatim
   ///       jump-statement:
   ///         'break' ';'
+  /// [C2y]   'break' identifier ';'
   /// \endverbatim
   ///
   /// Note: this lets the caller parse the end ';'.
@@ -7484,9 +7491,12 @@ class Parser : public CodeCompletionHandler {
   /// \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);
 

diff  --git a/clang/include/clang/Sema/Scope.h b/clang/include/clang/Sema/Scope.h
index 757f3dcc3fe8d..0d1c0ff6a1e91 100644
--- a/clang/include/clang/Sema/Scope.h
+++ b/clang/include/clang/Sema/Scope.h
@@ -255,6 +255,10 @@ class Scope {
   /// 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 @@ class Scope {
 
   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 @@ class Scope {
     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; }
 

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index c3fb57774c8dc..aa035a1555950 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9488,6 +9488,10 @@ class Sema final : public SemaBase {
   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 @@ class Sema final : public SemaBase {
                            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;

diff  --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 6299efaf6bbfc..0899e86c2e25d 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -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) {

diff  --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index b4f1e76187e25..798e19f38f093 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -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);

diff  --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp
index ca8e2af284c2b..2f4aebd0845dd 100644
--- a/clang/lib/AST/JSONNodeDumper.cpp
+++ b/clang/lib/AST/JSONNodeDumper.cpp
@@ -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()));

diff  --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp
index 4fc4a99ad2405..9ae8aea3ab37a 100644
--- a/clang/lib/AST/Stmt.cpp
+++ b/clang/lib/AST/Stmt.cpp
@@ -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();
+}

diff  --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index afccba8778fd2..0030300521128 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -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;
 }
 

diff  --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 9dca5cf088a85..8c59dbd345439 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -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";

diff  --git a/clang/lib/Basic/LangOptions.cpp b/clang/lib/Basic/LangOptions.cpp
index 9c14a25699f89..f034514466d3f 100644
--- a/clang/lib/Basic/LangOptions.cpp
+++ b/clang/lib/Basic/LangOptions.cpp
@@ -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)

diff  --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index b01d5471a9836..10aad2e26938d 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -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());

diff  --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 031ef73214e76..aeff73d525c10 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -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());

diff  --git a/clang/lib/CodeGen/CGStmtOpenMP.cpp b/clang/lib/CodeGen/CGStmtOpenMP.cpp
index 1074ee5d34fde..2708fc0470f5b 100644
--- a/clang/lib/CodeGen/CGStmtOpenMP.cpp
+++ b/clang/lib/CodeGen/CGStmtOpenMP.cpp
@@ -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(

diff  --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index c02ac18ec0198..123cb4f51f828 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1552,9 +1552,11 @@ class CodeGenFunction : public CodeGenTypeCache {
   // 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 @@ class CodeGenFunction : public CodeGenTypeCache {
   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);

diff  --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 29f9cf3a7f0e3..aadda694a0854 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -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"

diff  --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index bf1978c22ee9f..62361c066a3f3 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -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 @@ class StatementFilterCCC final : public CorrectionCandidateCallback {
 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 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
   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 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
   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);
 

diff  --git a/clang/lib/Sema/Scope.cpp b/clang/lib/Sema/Scope.cpp
index ab04fe554be82..e66cce255230b 100644
--- a/clang/lib/Sema/Scope.cpp
+++ b/clang/lib/Sema/Scope.cpp
@@ -99,6 +99,7 @@ void Scope::Init(Scope *parent, unsigned flags) {
   UsingDirectives.clear();
   Entity = nullptr;
   ErrorTrap.reset();
+  PrecedingLabel = nullptr;
   NRVO = std::nullopt;
 }
 

diff  --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index 86ffae9363beb..54918c560b655 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -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;
 }
 
 //===----------------------------------------------------------------------===//

diff  --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 5625fb359807a..ae0bb616beb82 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -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,

diff  --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index aa1bb3232d6fa..fbadb8d6881a5 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -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>

diff  --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 3f37dfbc3dea9..76fdd4024b0b7 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -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);
 

diff  --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 301ed9b23c206..e36d83fe4559b 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -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;
 }
 

diff  --git a/clang/lib/Tooling/Syntax/BuildTree.cpp b/clang/lib/Tooling/Syntax/BuildTree.cpp
index 546161cee33f4..b75f8ff6defee 100644
--- a/clang/lib/Tooling/Syntax/BuildTree.cpp
+++ b/clang/lib/Tooling/Syntax/BuildTree.cpp
@@ -1482,16 +1482,14 @@ class BuildTreeVisitor : public RecursiveASTVisitor<BuildTreeVisitor> {
   }
 
   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;

diff  --git a/clang/test/AST/ast-dump-labeled-break-continue-json.c b/clang/test/AST/ast-dump-labeled-break-continue-json.c
new file mode 100644
index 0000000000000..19f8ff300a187
--- /dev/null
+++ b/clang/test/AST/ast-dump-labeled-break-continue-json.c
@@ -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: }

diff  --git a/clang/test/AST/ast-dump-labeled-break-continue.c b/clang/test/AST/ast-dump-labeled-break-continue.c
new file mode 100644
index 0000000000000..a1ec812017557
--- /dev/null
+++ b/clang/test/AST/ast-dump-labeled-break-continue.c
@@ -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 {{.*}})

diff  --git a/clang/test/AST/ast-print-labeled-break-continue.c b/clang/test/AST/ast-print-labeled-break-continue.c
new file mode 100644
index 0000000000000..163bb759aa59e
--- /dev/null
+++ b/clang/test/AST/ast-print-labeled-break-continue.c
@@ -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: }

diff  --git a/clang/test/Analysis/cfg.c b/clang/test/Analysis/cfg.c
index e21f6109dbd59..0db82ef2f3d70 100644
--- a/clang/test/Analysis/cfg.c
+++ b/clang/test/Analysis/cfg.c
@@ -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

diff  --git a/clang/test/CodeGen/labeled-break-continue.c b/clang/test/CodeGen/labeled-break-continue.c
new file mode 100644
index 0000000000000..f307a1bd79ab8
--- /dev/null
+++ b/clang/test/CodeGen/labeled-break-continue.c
@@ -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;
+    }
+  }
+}

diff  --git a/clang/test/CodeGenCXX/labeled-break-continue.cpp b/clang/test/CodeGenCXX/labeled-break-continue.cpp
new file mode 100644
index 0000000000000..4bdb5369aa8f1
--- /dev/null
+++ b/clang/test/CodeGenCXX/labeled-break-continue.cpp
@@ -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>();

diff  --git a/clang/test/CodeGenObjC/labeled-break-continue.m b/clang/test/CodeGenObjC/labeled-break-continue.m
new file mode 100644
index 0000000000000..e9979fe437b61
--- /dev/null
+++ b/clang/test/CodeGenObjC/labeled-break-continue.m
@@ -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;
+    }
+  }
+}

diff  --git a/clang/test/OpenMP/for_loop_messages.cpp b/clang/test/OpenMP/for_loop_messages.cpp
index e62ec07acc049..5f6f9c9a3fbc9 100644
--- a/clang/test/OpenMP/for_loop_messages.cpp
+++ b/clang/test/OpenMP/for_loop_messages.cpp
@@ -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}}
+    }
+  }
+}

diff  --git a/clang/test/Parser/labeled-break-continue.c b/clang/test/Parser/labeled-break-continue.c
new file mode 100644
index 0000000000000..81935884023ac
--- /dev/null
+++ b/clang/test/Parser/labeled-break-continue.c
@@ -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}}
+}

diff  --git a/clang/test/Sema/__try.c b/clang/test/Sema/__try.c
index 9bfd914c013c1..06360cb0a5dcf 100644
--- a/clang/test/Sema/__try.c
+++ b/clang/test/Sema/__try.c
@@ -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;
+      }
+    }
+  }
+}

diff  --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c
new file mode 100644
index 0000000000000..78f81c484c3d5
--- /dev/null
+++ b/clang/test/Sema/labeled-break-continue.c
@@ -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}}
+  }
+}

diff  --git a/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
new file mode 100644
index 0000000000000..bec6c582a1f0d
--- /dev/null
+++ b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
@@ -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);

diff  --git a/clang/test/SemaCXX/labeled-break-continue.cpp b/clang/test/SemaCXX/labeled-break-continue.cpp
new file mode 100644
index 0000000000000..3d34211ed745a
--- /dev/null
+++ b/clang/test/SemaCXX/labeled-break-continue.cpp
@@ -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}}
+    };
+  }
+}

diff  --git a/clang/test/SemaObjC/labeled-break-continue.m b/clang/test/SemaObjC/labeled-break-continue.m
new file mode 100644
index 0000000000000..2791474a579b7
--- /dev/null
+++ b/clang/test/SemaObjC/labeled-break-continue.m
@@ -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}}
+    };
+  }
+}

diff  --git a/clang/test/SemaOpenACC/no-branch-in-out.c b/clang/test/SemaOpenACC/no-branch-in-out.c
index 37126d8f2200e..370722b52ab19 100644
--- a/clang/test/SemaOpenACC/no-branch-in-out.c
+++ b/clang/test/SemaOpenACC/no-branch-in-out.c
@@ -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;
+  }
+}

diff  --git a/clang/www/c_status.html b/clang/www/c_status.html
index 5b31f97e7a2e5..f65c34cc64022 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -252,7 +252,7 @@ <h2 id="c2y">C2y implementation status</h2>
     <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>


        


More information about the cfe-commits mailing list