[clang] [clang][bytecode] Implement C23 named loops (PR #156856)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 4 04:08:24 PDT 2025
https://github.com/tbaederr created https://github.com/llvm/llvm-project/pull/156856
None
>From 9c3e449ddf0aaa392547024041a8605b635cd988 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 4 Sep 2025 12:31:24 +0200
Subject: [PATCH] [clang][bytecode] Implement C23 named loops
---
clang/lib/AST/ByteCode/Compiler.cpp | 169 ++++++++++++------
clang/lib/AST/ByteCode/Compiler.h | 29 +--
.../labeled-break-continue-constexpr.cpp | 1 +
3 files changed, 131 insertions(+), 68 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 56552f3969216..d97f8ad2add73 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -114,31 +114,25 @@ template <class Emitter> class LoopScope final {
public:
using LabelTy = typename Compiler<Emitter>::LabelTy;
using OptLabelTy = typename Compiler<Emitter>::OptLabelTy;
+ using LabelInfo = typename Compiler<Emitter>::LabelInfo;
- LoopScope(Compiler<Emitter> *Ctx, LabelTy BreakLabel, LabelTy ContinueLabel)
- : Ctx(Ctx), OldBreakLabel(Ctx->BreakLabel),
- OldContinueLabel(Ctx->ContinueLabel),
- OldBreakVarScope(Ctx->BreakVarScope),
- OldContinueVarScope(Ctx->ContinueVarScope) {
- this->Ctx->BreakLabel = BreakLabel;
- this->Ctx->ContinueLabel = ContinueLabel;
- this->Ctx->BreakVarScope = this->Ctx->VarScope;
- this->Ctx->ContinueVarScope = this->Ctx->VarScope;
- }
+ LoopScope(Compiler<Emitter> *Ctx, const Stmt *Name, LabelTy BreakLabel,
+ LabelTy ContinueLabel)
+ : Ctx(Ctx) {
+#ifndef NDEBUG
+ for (const LabelInfo &LI : Ctx->LabelInfoStack)
+ assert(LI.Name != Name);
+#endif
- ~LoopScope() {
- this->Ctx->BreakLabel = OldBreakLabel;
- this->Ctx->ContinueLabel = OldContinueLabel;
- this->Ctx->ContinueVarScope = OldContinueVarScope;
- this->Ctx->BreakVarScope = OldBreakVarScope;
+ this->Ctx->LabelInfoStack.emplace_back(Name, BreakLabel, ContinueLabel,
+ /*DefaultLabel=*/std::nullopt,
+ Ctx->VarScope);
}
+ ~LoopScope() { this->Ctx->LabelInfoStack.pop_back(); }
+
private:
Compiler<Emitter> *Ctx;
- OptLabelTy OldBreakLabel;
- OptLabelTy OldContinueLabel;
- VariableScope<Emitter> *OldBreakVarScope;
- VariableScope<Emitter> *OldContinueVarScope;
};
// Sets the context for a switch scope, mapping labels.
@@ -147,32 +141,30 @@ template <class Emitter> class SwitchScope final {
using LabelTy = typename Compiler<Emitter>::LabelTy;
using OptLabelTy = typename Compiler<Emitter>::OptLabelTy;
using CaseMap = typename Compiler<Emitter>::CaseMap;
+ using LabelInfo = typename Compiler<Emitter>::LabelInfo;
+
+ SwitchScope(Compiler<Emitter> *Ctx, const Stmt *Name, CaseMap &&CaseLabels,
+ LabelTy BreakLabel, OptLabelTy DefaultLabel)
+ : Ctx(Ctx), OldCaseLabels(std::move(this->Ctx->CaseLabels)) {
+#ifndef NDEBUG
+ for (const LabelInfo &LI : Ctx->LabelInfoStack)
+ assert(LI.Name != Name);
+#endif
- SwitchScope(Compiler<Emitter> *Ctx, CaseMap &&CaseLabels, LabelTy BreakLabel,
- OptLabelTy DefaultLabel)
- : Ctx(Ctx), OldBreakLabel(Ctx->BreakLabel),
- OldDefaultLabel(this->Ctx->DefaultLabel),
- OldCaseLabels(std::move(this->Ctx->CaseLabels)),
- OldLabelVarScope(Ctx->BreakVarScope) {
- this->Ctx->BreakLabel = BreakLabel;
- this->Ctx->DefaultLabel = DefaultLabel;
this->Ctx->CaseLabels = std::move(CaseLabels);
- this->Ctx->BreakVarScope = this->Ctx->VarScope;
+ this->Ctx->LabelInfoStack.emplace_back(Name, BreakLabel,
+ /*ContinueLabel=*/std::nullopt,
+ DefaultLabel, Ctx->VarScope);
}
~SwitchScope() {
- this->Ctx->BreakLabel = OldBreakLabel;
- this->Ctx->DefaultLabel = OldDefaultLabel;
this->Ctx->CaseLabels = std::move(OldCaseLabels);
- this->Ctx->BreakVarScope = OldLabelVarScope;
+ this->Ctx->LabelInfoStack.pop_back();
}
private:
Compiler<Emitter> *Ctx;
- OptLabelTy OldBreakLabel;
- OptLabelTy OldDefaultLabel;
CaseMap OldCaseLabels;
- VariableScope<Emitter> *OldLabelVarScope;
};
template <class Emitter> class StmtExprScope final {
@@ -5682,7 +5674,8 @@ bool Compiler<Emitter>::visitWhileStmt(const WhileStmt *S) {
LabelTy CondLabel = this->getLabel(); // Label before the condition.
LabelTy EndLabel = this->getLabel(); // Label after the loop.
- LoopScope<Emitter> LS(this, EndLabel, CondLabel);
+ LocalScope<Emitter> WholeLoopScope(this);
+ LoopScope<Emitter> LS(this, S, EndLabel, CondLabel);
this->fallthrough(CondLabel);
this->emitLabel(CondLabel);
@@ -5712,8 +5705,7 @@ bool Compiler<Emitter>::visitWhileStmt(const WhileStmt *S) {
return false;
this->fallthrough(EndLabel);
this->emitLabel(EndLabel);
-
- return true;
+ return WholeLoopScope.destroyLocals();
}
template <class Emitter> bool Compiler<Emitter>::visitDoStmt(const DoStmt *S) {
@@ -5723,7 +5715,8 @@ template <class Emitter> bool Compiler<Emitter>::visitDoStmt(const DoStmt *S) {
LabelTy StartLabel = this->getLabel();
LabelTy EndLabel = this->getLabel();
LabelTy CondLabel = this->getLabel();
- LoopScope<Emitter> LS(this, EndLabel, CondLabel);
+ LocalScope<Emitter> WholeLoopScope(this);
+ LoopScope<Emitter> LS(this, S, EndLabel, CondLabel);
this->fallthrough(StartLabel);
this->emitLabel(StartLabel);
@@ -5745,7 +5738,7 @@ template <class Emitter> bool Compiler<Emitter>::visitDoStmt(const DoStmt *S) {
this->fallthrough(EndLabel);
this->emitLabel(EndLabel);
- return true;
+ return WholeLoopScope.destroyLocals();
}
template <class Emitter>
@@ -5759,19 +5752,21 @@ bool Compiler<Emitter>::visitForStmt(const ForStmt *S) {
LabelTy EndLabel = this->getLabel();
LabelTy CondLabel = this->getLabel();
LabelTy IncLabel = this->getLabel();
- LoopScope<Emitter> LS(this, EndLabel, IncLabel);
+ LocalScope<Emitter> WholeLoopScope(this);
if (Init && !this->visitStmt(Init))
return false;
+ // Start of the loop body {
this->fallthrough(CondLabel);
this->emitLabel(CondLabel);
- // Start of loop body.
LocalScope<Emitter> CondScope(this);
- if (const DeclStmt *CondDecl = S->getConditionVariableDeclStmt())
+ LoopScope<Emitter> LS(this, S, EndLabel, IncLabel);
+ if (const DeclStmt *CondDecl = S->getConditionVariableDeclStmt()) {
if (!visitDeclStmt(CondDecl))
return false;
+ }
if (Cond) {
if (!this->visitBool(Cond))
@@ -5794,12 +5789,12 @@ bool Compiler<Emitter>::visitForStmt(const ForStmt *S) {
return false;
if (!this->jump(CondLabel))
return false;
- // End of loop body.
+ // } End of loop body.
this->emitLabel(EndLabel);
// If we jumped out of the loop above, we still need to clean up the condition
// scope.
- return CondScope.destroyLocals();
+ return CondScope.destroyLocals() && WholeLoopScope.destroyLocals();
}
template <class Emitter>
@@ -5815,7 +5810,8 @@ bool Compiler<Emitter>::visitCXXForRangeStmt(const CXXForRangeStmt *S) {
LabelTy EndLabel = this->getLabel();
LabelTy CondLabel = this->getLabel();
LabelTy IncLabel = this->getLabel();
- LoopScope<Emitter> LS(this, EndLabel, IncLabel);
+ LocalScope<Emitter> WholeLoopScope(this);
+ LoopScope<Emitter> LS(this, S, EndLabel, IncLabel);
// Emit declarations needed in the loop.
if (Init && !this->visitStmt(Init))
@@ -5854,29 +5850,78 @@ bool Compiler<Emitter>::visitCXXForRangeStmt(const CXXForRangeStmt *S) {
this->fallthrough(EndLabel);
this->emitLabel(EndLabel);
- return true;
+ return WholeLoopScope.destroyLocals();
}
template <class Emitter>
bool Compiler<Emitter>::visitBreakStmt(const BreakStmt *S) {
- if (!BreakLabel)
+ if (LabelInfoStack.empty())
return false;
- for (VariableScope<Emitter> *C = VarScope; C != BreakVarScope;
+ OptLabelTy TargetLabel = std::nullopt;
+ const Stmt *TargetLoop = S->getNamedLoopOrSwitch();
+ const VariableScope<Emitter> *BreakScope = nullptr;
+
+ if (!TargetLoop) {
+ for (const auto &LI : llvm::reverse(LabelInfoStack)) {
+ if (LI.BreakLabel) {
+ TargetLabel = *LI.BreakLabel;
+ BreakScope = LI.BreakOrContinueScope;
+ break;
+ }
+ }
+ } else {
+ for (auto LI : LabelInfoStack) {
+ if (LI.Name == TargetLoop) {
+ TargetLabel = *LI.BreakLabel;
+ BreakScope = LI.BreakOrContinueScope;
+ break;
+ }
+ }
+ }
+
+ assert(TargetLabel);
+
+ for (VariableScope<Emitter> *C = this->VarScope; C != BreakScope;
C = C->getParent())
C->emitDestruction();
- return this->jump(*BreakLabel);
+
+ return this->jump(*TargetLabel);
}
template <class Emitter>
bool Compiler<Emitter>::visitContinueStmt(const ContinueStmt *S) {
- if (!ContinueLabel)
+ if (LabelInfoStack.empty())
return false;
- for (VariableScope<Emitter> *C = VarScope;
- C && C->getParent() != ContinueVarScope; C = C->getParent())
+ OptLabelTy TargetLabel = std::nullopt;
+ const Stmt *TargetLoop = S->getNamedLoopOrSwitch();
+ const VariableScope<Emitter> *ContinueScope = nullptr;
+
+ if (!TargetLoop) {
+ for (const auto &LI : llvm::reverse(LabelInfoStack)) {
+ if (LI.ContinueLabel) {
+ TargetLabel = *LI.ContinueLabel;
+ ContinueScope = LI.BreakOrContinueScope;
+ break;
+ }
+ }
+ } else {
+ for (auto LI : LabelInfoStack) {
+ if (LI.Name == TargetLoop) {
+ TargetLabel = *LI.ContinueLabel;
+ ContinueScope = LI.BreakOrContinueScope;
+ break;
+ }
+ }
+ }
+ assert(TargetLabel);
+
+ for (VariableScope<Emitter> *C = VarScope; C != ContinueScope;
+ C = C->getParent())
C->emitDestruction();
- return this->jump(*ContinueLabel);
+
+ return this->jump(*TargetLabel);
}
template <class Emitter>
@@ -5889,7 +5934,7 @@ bool Compiler<Emitter>::visitSwitchStmt(const SwitchStmt *S) {
LocalScope<Emitter> LS(this);
LabelTy EndLabel = this->getLabel();
- OptLabelTy DefaultLabel = std::nullopt;
+ UnsignedOrNone DefaultLabel = std::nullopt;
unsigned CondVar =
this->allocateLocalPrimitive(Cond, CondT, /*IsConst=*/true);
@@ -5950,7 +5995,8 @@ bool Compiler<Emitter>::visitSwitchStmt(const SwitchStmt *S) {
return false;
}
- SwitchScope<Emitter> SS(this, std::move(CaseLabels), EndLabel, DefaultLabel);
+ SwitchScope<Emitter> SS(this, S, std::move(CaseLabels), EndLabel,
+ DefaultLabel);
if (!this->visitStmt(S->getBody()))
return false;
this->emitLabel(EndLabel);
@@ -5966,7 +6012,18 @@ bool Compiler<Emitter>::visitCaseStmt(const CaseStmt *S) {
template <class Emitter>
bool Compiler<Emitter>::visitDefaultStmt(const DefaultStmt *S) {
- this->emitLabel(*DefaultLabel);
+ if (LabelInfoStack.empty())
+ return false;
+
+ LabelTy DefaultLabel;
+ for (const LabelInfo &LI : llvm::reverse(LabelInfoStack)) {
+ if (LI.DefaultLabel) {
+ DefaultLabel = *LI.DefaultLabel;
+ break;
+ }
+ }
+
+ this->emitLabel(DefaultLabel);
return this->visitStmt(S->getSubStmt());
}
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 475faee4d3fde..63e0fdada0393 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -112,9 +112,23 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
// Aliases for types defined in the emitter.
using LabelTy = typename Emitter::LabelTy;
using AddrTy = typename Emitter::AddrTy;
- using OptLabelTy = std::optional<LabelTy>;
+ using OptLabelTy = UnsignedOrNone;
using CaseMap = llvm::DenseMap<const SwitchCase *, LabelTy>;
+ struct LabelInfo {
+ const Stmt *Name;
+ const VariableScope<Emitter> *BreakOrContinueScope;
+ OptLabelTy BreakLabel;
+ OptLabelTy ContinueLabel;
+ OptLabelTy DefaultLabel;
+ LabelInfo(const Stmt *Name, OptLabelTy BreakLabel, OptLabelTy ContinueLabel,
+ OptLabelTy DefaultLabel,
+ const VariableScope<Emitter> *BreakOrContinueScope)
+ : Name(Name), BreakOrContinueScope(BreakOrContinueScope),
+ BreakLabel(BreakLabel), ContinueLabel(ContinueLabel),
+ DefaultLabel(DefaultLabel) {}
+ };
+
/// Current compilation context.
Context &Ctx;
/// Program to link to.
@@ -443,17 +457,8 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
/// Switch case mapping.
CaseMap CaseLabels;
-
- /// Scope to cleanup until when we see a break statement.
- VariableScope<Emitter> *BreakVarScope = nullptr;
- /// Point to break to.
- OptLabelTy BreakLabel;
- /// Scope to cleanup until when we see a continue statement.
- VariableScope<Emitter> *ContinueVarScope = nullptr;
- /// Point to continue to.
- OptLabelTy ContinueLabel;
- /// Default case label.
- OptLabelTy DefaultLabel;
+ /// Stack of label information for loops and switch statements.
+ llvm::SmallVector<LabelInfo> LabelInfoStack;
const FunctionDecl *CompilingFunction = nullptr;
};
diff --git a/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
index bec6c582a1f0d..d1b57adca91c1 100644
--- a/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
+++ b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fnamed-loops -std=c++23 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fnamed-loops -std=c++23 -fsyntax-only -verify %s -fexperimental-new-constant-interpreter
// expected-no-diagnostics
struct Tracker {
More information about the cfe-commits
mailing list