[clang] [Clang] support storage-class specifiers in C23 compound literals (PR #212559)

Oleksandr Tarasiuk via cfe-commits cfe-commits at lists.llvm.org
Wed Jul 29 03:26:03 PDT 2026


https://github.com/a-tarasyuk updated https://github.com/llvm/llvm-project/pull/212559

>From 0c41fd3fcbbdd487201d26a0fbadbebf24c4b669 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Tue, 28 Jul 2026 17:14:33 +0300
Subject: [PATCH 1/4] [Clang] support storage-class specifiers in C23 compound
 literals

---
 clang/docs/ReleaseNotes.md                    |   2 +
 clang/include/clang/AST/Expr.h                |  66 +++-
 clang/include/clang/AST/Stmt.h                |  17 +
 .../clang/Basic/DiagnosticParseKinds.td       |   4 +
 .../clang/Basic/DiagnosticSemaKinds.td        |  18 +-
 clang/include/clang/Parse/Parser.h            |   6 +-
 clang/include/clang/Sema/DelayedDiagnostic.h  |  15 +-
 clang/include/clang/Sema/Initialization.h     |  24 +-
 clang/include/clang/Sema/Scope.h              |   6 +-
 clang/include/clang/Sema/Sema.h               |  24 +-
 clang/lib/AST/ASTImporter.cpp                 |   6 +-
 clang/lib/AST/Expr.cpp                        |   9 +-
 clang/lib/AST/ExprConstant.cpp                |  93 +++--
 clang/lib/AST/StmtPrinter.cpp                 |  24 +-
 clang/lib/AST/StmtProfile.cpp                 |   3 +
 clang/lib/CodeGen/CGExpr.cpp                  |  17 +-
 clang/lib/CodeGen/CGExprAgg.cpp               |   7 +-
 clang/lib/CodeGen/CGExprConstant.cpp          |  10 +-
 clang/lib/CodeGen/CodeGenFunction.cpp         |   4 +-
 clang/lib/Parse/ParseExpr.cpp                 |  98 ++++-
 clang/lib/Sema/CheckExprLifetime.cpp          |   3 +-
 clang/lib/Sema/DelayedDiagnostic.cpp          |   1 +
 clang/lib/Sema/Scope.cpp                      |  15 +-
 clang/lib/Sema/SemaDecl.cpp                   |  39 +-
 clang/lib/Sema/SemaDeclAttr.cpp               |   8 +
 clang/lib/Sema/SemaExpr.cpp                   | 370 ++++++++++++------
 clang/lib/Sema/SemaInit.cpp                   |  45 ++-
 clang/lib/Sema/TreeTransform.h                |  18 +-
 clang/lib/Serialization/ASTReaderStmt.cpp     |   3 +
 clang/lib/Serialization/ASTWriterStmt.cpp     |   3 +
 clang/test/AST/c23-compound-literal-print.c   |  33 ++
 clang/test/C/drs/dr3xx.c                      |   1 +
 clang/test/CodeGen/c23-compound-literal.c     | 275 +++++++++++++
 clang/test/CodeGenObjC/c23-compound-literal.m |  19 +
 clang/test/Parser/expressions.c               |  18 +-
 clang/test/Sema/c23-compound-literal.c        | 343 ++++++++++++++++
 clang/test/Sema/c23-compound-literal.m        |  11 +
 clang/test/Sema/constexpr.c                   |   2 +-
 38 files changed, 1418 insertions(+), 242 deletions(-)
 create mode 100644 clang/test/AST/c23-compound-literal-print.c
 create mode 100644 clang/test/CodeGen/c23-compound-literal.c
 create mode 100644 clang/test/CodeGenObjC/c23-compound-literal.m
 create mode 100644 clang/test/Sema/c23-compound-literal.c
 create mode 100644 clang/test/Sema/c23-compound-literal.m

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index c348ddaf23017..bd7689b2fc3c3 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -142,6 +142,8 @@ features cannot lower the translation-unit ABI level;
 
 #### C23 Feature Support
 
+- Clang now supports storage-class specifiers in compound literals. (#GH129365)
+
 ### Objective-C Language Changes
 
 ### Non-comprehensive list of changes in this release
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index f95f87cc4e8e0..6454a750b61c9 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -765,6 +765,8 @@ class Expr : public ValueStmt {
     /// evaluation is not part of the evaluation, but all other temporaries
     /// are destroyed.
     ImmediateInvocation,
+    /// The initializer of a compound literal constant.
+    CompoundLiteralInitializer,
   };
 
   /// Evaluate an expression that is required to be a constant expression. Does
@@ -3606,7 +3608,7 @@ class MemberExpr final
   }
 };
 
-/// CompoundLiteralExpr - [C99 6.5.2.5]
+/// CompoundLiteralExpr - [C99 6.5.2.5, C23 6.5.3.6]
 ///
 class CompoundLiteralExpr : public Expr {
   /// LParenLoc - If non-null, this is the location of the left paren in a
@@ -3624,11 +3626,20 @@ class CompoundLiteralExpr : public Expr {
   mutable APValue *StaticValue = nullptr;
 
 public:
-  CompoundLiteralExpr(SourceLocation lparenloc, TypeSourceInfo *tinfo,
-                      QualType T, ExprValueKind VK, Expr *init, bool fileScope)
+  CompoundLiteralExpr(
+      SourceLocation LParenLoc, TypeSourceInfo *TInfo, QualType T,
+      ExprValueKind VK, Expr *Init, bool FileScope, StorageClass SC = SC_None,
+      ThreadStorageClassSpecifier TSC = TSCS_unspecified,
+      ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified)
       : Expr(CompoundLiteralExprClass, T, VK, OK_Ordinary),
-        LParenLoc(lparenloc), TInfoAndScope(tinfo, fileScope), Init(init) {
+        LParenLoc(LParenLoc), TInfoAndScope(TInfo, FileScope), Init(Init) {
     assert(Init && "Init is a nullptr");
+    assert((ConstexprKind == ConstexprSpecKind::Unspecified ||
+            ConstexprKind == ConstexprSpecKind::Constexpr) &&
+           "invalid compound literal constexpr specifier");
+    setStorageClass(SC);
+    setTSCSpec(TSC);
+    setConstexpr(ConstexprKind == ConstexprSpecKind::Constexpr);
     setDependence(computeDependence(this));
   }
 
@@ -3649,11 +3660,52 @@ class CompoundLiteralExpr : public Expr {
   TypeSourceInfo *getTypeSourceInfo() const {
     return TInfoAndScope.getPointer();
   }
-  void setTypeSourceInfo(TypeSourceInfo *tinfo) {
-    TInfoAndScope.setPointer(tinfo);
+
+  void setTypeSourceInfo(TypeSourceInfo *TInfo) {
+    TInfoAndScope.setPointer(TInfo);
+  }
+
+  bool hasStaticStorage() const {
+    return isGLValue() && !hasThreadStorage() &&
+           (isFileScope() || getStorageClass() == SC_Static);
+  }
+
+  bool hasThreadStorage() const { return getTSCSpec() != TSCS_unspecified; }
+
+  bool hasGlobalStorage() const {
+    return hasStaticStorage() || hasThreadStorage();
+  }
+
+  StorageClass getStorageClass() const {
+    return static_cast<StorageClass>(CompoundLiteralExprBits.SClass);
+  }
+
+  void setStorageClass(StorageClass SC) {
+    assert((SC == SC_None || SC == SC_Static || SC == SC_Register) &&
+           "invalid compound literal storage class");
+    CompoundLiteralExprBits.SClass = SC;
+    assert(getStorageClass() == SC && "truncation");
+  }
+
+  ThreadStorageClassSpecifier getTSCSpec() const {
+    return static_cast<ThreadStorageClassSpecifier>(
+        CompoundLiteralExprBits.TSCSpec);
+  }
+
+  void setTSCSpec(ThreadStorageClassSpecifier TSC) {
+    assert((TSC == TSCS_unspecified || TSC == TSCS_thread_local ||
+            TSC == TSCS__Thread_local) &&
+           "invalid compound literal thread storage class");
+    CompoundLiteralExprBits.TSCSpec = TSC;
+    assert(getTSCSpec() == TSC && "truncation");
+  }
+
+  bool isConstexpr() const { return CompoundLiteralExprBits.IsConstexpr; }
+
+  void setConstexpr(bool IsConstexpr) {
+    CompoundLiteralExprBits.IsConstexpr = IsConstexpr;
   }
 
-  bool hasStaticStorage() const { return isFileScope() && isGLValue(); }
   APValue &getOrCreateStaticValue(ASTContext &Ctx) const;
   APValue &getStaticValue() const;
 
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 69db8252f931e..791def70e72fd 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -668,6 +668,22 @@ class alignas(void *) Stmt {
     SourceLocation OpLoc;
   };
 
+  class CompoundLiteralExprBitfields {
+    friend class CompoundLiteralExpr;
+
+    LLVM_PREFERRED_TYPE(ExprBitfields)
+    unsigned : NumExprBits;
+
+    LLVM_PREFERRED_TYPE(StorageClass)
+    unsigned SClass : 3;
+
+    LLVM_PREFERRED_TYPE(ThreadStorageClassSpecifier)
+    unsigned TSCSpec : 2;
+
+    LLVM_PREFERRED_TYPE(bool)
+    unsigned IsConstexpr : 1;
+  };
+
   class InitListExprBitfields {
     friend class ASTStmtReader;
     friend class InitListExpr;
@@ -1363,6 +1379,7 @@ class alignas(void *) Stmt {
     MemberExprBitfields MemberExprBits;
     CastExprBitfields CastExprBits;
     BinaryOperatorBitfields BinaryOperatorBits;
+    CompoundLiteralExprBitfields CompoundLiteralExprBits;
     InitListExprBitfields InitListExprBits;
     ParenListExprBitfields ParenListExprBits;
     GenericSelectionExprBitfields GenericSelectionExprBits;
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 2672bfb2952c8..17ba28c5c094c 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -185,6 +185,10 @@ def warn_c11_compat_keyword : Warning<
 def warn_c23_compat_keyword : Warning<
  "'%0' is incompatible with C standards before C23">,
  InGroup<CPre23Compat>, DefaultIgnore;
+def warn_c23_compat_compound_literal_storage_class : Warning<
+  "compound literal storage-class specifiers are incompatible with C "
+  "standards before C23">,
+  InGroup<CPre23Compat>, DefaultIgnore;
 def warn_c2y_compat_keyword : Warning<
   "'%0' is incompatible with C standards before C2y">,
   InGroup<CPre2yCompat>, DefaultIgnore;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index cce6f70a58893..85528249c4be4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2471,7 +2471,8 @@ def err_init_conversion_failed : Error<
   "base class|a constructor delegation|a vector element|a matrix element|a "
   "block element|a block element|a complex element|a lambda capture|a compound"
   " literal initializer|a related result|a parameter of CF audited function|a "
-  "structured binding|a member subobject}0 "
+  "structured binding|a member subobject|a constexpr compound literal "
+  "initializer}0 "
   "%diff{of type $ with an %select{rvalue|lvalue}2 of type $|"
   "with an %select{rvalue|lvalue}2 of incompatible type}1,3"
   "%select{|: different classes%diff{ ($ vs $)|}5,6"
@@ -3177,6 +3178,15 @@ def err_c23_constexpr_init_type_mismatch : Error<
   "constexpr initializer for type %0 is of type %1">;
 def err_c23_constexpr_pointer_not_null : Error<
   "constexpr pointer initializer is not null">;
+def err_register_compound_literal_file_scope : Error<
+  "file-scope compound literal specifies 'register'">;
+def err_thread_local_compound_literal_without_static : Error<
+  "compound literal with 'thread_local' storage duration at block scope must "
+  "also specify 'static'">;
+def err_constexpr_compound_literal_invalid_type : Error<
+  "constexpr compound literal cannot have type %0">;
+def err_compound_literal_initializer_not_constant : Error<
+  "initializer of compound literal must be a constant expression">;
 
 // C++ Concepts
 def err_concept_decls_may_only_appear_in_global_namespace_scope : Error<
@@ -3761,6 +3771,9 @@ def err_aix_attr_unsupported : Error<"%0 attribute is not yet supported on AIX">
 def err_tls_var_aligned_over_maximum : Error<
   "alignment (%0) of thread-local variable %1 is greater than the maximum supported "
   "alignment (%2) for a thread-local variable on this target">;
+def err_tls_compound_literal_aligned_over_maximum : Error<
+  "alignment (%0) of thread-local compound literal is greater than the maximum "
+  "supported alignment (%1) for thread-local storage on this target">;
 
 def err_only_annotate_after_access_spec : Error<
   "access specifier can only have annotation attributes">;
@@ -7950,7 +7963,8 @@ def err_typecheck_sclass_func : Error<"illegal storage class on function">;
 def err_static_block_func : Error<
   "function declared in block scope cannot have 'static' storage class">;
 def err_typecheck_address_of : Error<"address of %select{bit-field"
-  "|vector element|property expression|register variable|matrix element}0 requested">;
+  "|vector element|property expression|register variable|matrix element"
+  "|register compound literal}0 requested">;
 def ext_typecheck_addrof_void : Extension<
   "ISO C forbids taking the address of an expression of type 'void'">;
 def err_unqualified_pointer_member_function : Error<
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 163aa483a84e3..43c88344fbb2e 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -4294,7 +4294,8 @@ class Parser : public CodeCompletionHandler {
   /// \endverbatim
   ExprResult ParseCompoundLiteralExpression(ParsedType Ty,
                                             SourceLocation LParenLoc,
-                                            SourceLocation RParenLoc);
+                                            SourceLocation RParenLoc,
+                                            const DeclSpec *DS = nullptr);
 
   /// ParseGenericSelectionExpression - Parse a C11 generic-selection
   /// [C11 6.5.1.1].
@@ -5091,6 +5092,9 @@ class Parser : public CodeCompletionHandler {
     return isTypeIdInParens(isAmbiguous);
   }
 
+  bool isCompoundLiteralStorageClassSpecifier(const Token &Tok) const;
+  void ParseCompoundLiteralStorageClassSpecifiers(DeclSpec &DS);
+
   /// Finish parsing a C++ unqualified-id that is a template-id of
   /// some form.
   ///
diff --git a/clang/include/clang/Sema/DelayedDiagnostic.h b/clang/include/clang/Sema/DelayedDiagnostic.h
index 0105089a393f1..914fb2d6bc370 100644
--- a/clang/include/clang/Sema/DelayedDiagnostic.h
+++ b/clang/include/clang/Sema/DelayedDiagnostic.h
@@ -125,7 +125,12 @@ class AccessedEntity {
 /// the complete parsing of the current declaration.
 class DelayedDiagnostic {
 public:
-  enum DDKind : unsigned char { Availability, Access, ForbiddenType };
+  enum DDKind : unsigned char {
+    Availability,
+    Access,
+    ForbiddenType,
+    ForbiddenStatic
+  };
 
   DDKind Kind;
   bool Triggered;
@@ -167,6 +172,14 @@ class DelayedDiagnostic {
     return DD;
   }
 
+  static DelayedDiagnostic makeForbiddenStatic(SourceLocation Loc) {
+    DelayedDiagnostic DD;
+    DD.Kind = ForbiddenStatic;
+    DD.Triggered = false;
+    DD.Loc = Loc;
+    return DD;
+  }
+
   AccessedEntity &getAccessData() {
     assert(Kind == Access && "Not an access diagnostic.");
     return *reinterpret_cast<AccessedEntity*>(AccessData);
diff --git a/clang/include/clang/Sema/Initialization.h b/clang/include/clang/Sema/Initialization.h
index 2c2df1f03747b..b338151cded83 100644
--- a/clang/include/clang/Sema/Initialization.h
+++ b/clang/include/clang/Sema/Initialization.h
@@ -131,6 +131,9 @@ class alignas(8) InitializedEntity {
     /// object initialized via parenthesized aggregate initialization.
     EK_ParenAggInitMember,
 
+    /// The entity initialized by a constexpr compound literal.
+    EK_ConstexprCompoundLiteralInit,
+
     // Note: err_init_conversion_failed in DiagnosticSemaKinds.td uses this
     // enum as an index for its first %select.  When modifying this list,
     // that diagnostic text needs to be updated as well.
@@ -216,8 +219,8 @@ class alignas(8) InitializedEntity {
     /// integer indicating whether the parameter is "consumed".
     llvm::PointerIntPair<ParmVarDecl *, 1> Parameter;
 
-    /// When Kind == EK_Temporary or EK_CompoundLiteralInit, the type
-    /// source information for the temporary.
+    /// When Kind is EK_Temporary, EK_CompoundLiteralInit, or
+    /// EK_ConstexprCompoundLiteralInit, the type source information.
     TypeSourceInfo *TypeInfo;
 
     struct LN LocAndNRVO;
@@ -465,13 +468,21 @@ class alignas(8) InitializedEntity {
   }
 
   /// Create the entity for a compound literal initializer.
-  static InitializedEntity InitializeCompoundLiteralInit(TypeSourceInfo *TSI) {
-    InitializedEntity Result(EK_CompoundLiteralInit, SourceLocation(),
-                             TSI->getType());
+  static InitializedEntity
+  InitializeCompoundLiteralInit(TypeSourceInfo *TSI, QualType Type,
+                                ConstexprSpecKind ConstexprKind) {
+    InitializedEntity Result(ConstexprKind == ConstexprSpecKind::Constexpr
+                                 ? EK_ConstexprCompoundLiteralInit
+                                 : EK_CompoundLiteralInit,
+                             SourceLocation(), Type);
     Result.TypeInfo = TSI;
     return Result;
   }
 
+  bool isConstexprCompoundLiteral() const {
+    return Kind == EK_ConstexprCompoundLiteralInit;
+  }
+
   /// Determine the kind of initialization.
   EntityKind getKind() const { return Kind; }
 
@@ -486,7 +497,8 @@ class alignas(8) InitializedEntity {
   /// Retrieve complete type-source information for the object being
   /// constructed, if known.
   TypeSourceInfo *getTypeSourceInfo() const {
-    if (Kind == EK_Temporary || Kind == EK_CompoundLiteralInit)
+    if (Kind == EK_Temporary || Kind == EK_CompoundLiteralInit ||
+        Kind == EK_ConstexprCompoundLiteralInit)
       return TypeInfo;
 
     return nullptr;
diff --git a/clang/include/clang/Sema/Scope.h b/clang/include/clang/Sema/Scope.h
index 58ca2c0738f3c..4f69a18fb72a5 100644
--- a/clang/include/clang/Sema/Scope.h
+++ b/clang/include/clang/Sema/Scope.h
@@ -632,8 +632,10 @@ class Scope {
   /// loops where the condition follows the loop body.
   void LeaveLoopBody();
 
-  /// containedInPrototypeScope - Return true if this or a parent scope
-  /// is a FunctionPrototypeScope.
+  /// Return the innermost function prototype scope containing this scope.
+  const Scope *getEnclosingFunctionPrototypeScope() const;
+
+  /// Return true if this or a parent scope is a function prototype scope.
   bool containedInPrototypeScope() const;
 
   void PushUsingDirective(UsingDirectiveDecl *UDir) {
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d46edeb0d2872..45955c40756be 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4051,6 +4051,8 @@ class Sema final : public SemaBase {
   /// Returns true if the variable declaration is a redeclaration.
   bool CheckVariableDeclaration(VarDecl *NewVD, LookupResult &Previous);
   void CheckVariableDeclarationType(VarDecl *NewVD);
+  bool CheckConstexprType(SourceLocation Loc, QualType T, unsigned DiagID);
+  void DiagnoseStaticInInline(SourceLocation Loc, const FunctionDecl *FD);
   void CheckCompleteVariableDeclaration(VarDecl *VD);
 
   NamedDecl *ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
@@ -7595,13 +7597,23 @@ class Sema final : public SemaBase {
   /// the ParenListExpr into a sequence of comma binary operators.
   ExprResult MaybeConvertParenListExprToParenExpr(Scope *S, Expr *ME);
 
-  ExprResult ActOnCompoundLiteral(SourceLocation LParenLoc, ParsedType Ty,
-                                  SourceLocation RParenLoc, Expr *InitExpr);
+private:
+  ExprResult BuildCompoundLiteralExpr(
+      SourceLocation LParenLoc, TypeSourceInfo *TInfo, SourceLocation RParenLoc,
+      Expr *LiteralExpr, StorageClass SC, SourceLocation StorageClassLoc,
+      ThreadStorageClassSpecifier TSC, SourceLocation ThreadStorageClassLoc,
+      ConstexprSpecKind ConstexprKind, SourceLocation ConstexprLoc);
 
-  ExprResult BuildCompoundLiteralExpr(SourceLocation LParenLoc,
-                                      TypeSourceInfo *TInfo,
-                                      SourceLocation RParenLoc,
-                                      Expr *LiteralExpr);
+public:
+  ExprResult ActOnCompoundLiteral(SourceLocation LParenLoc, ParsedType Ty,
+                                  SourceLocation RParenLoc, Expr *InitExpr,
+                                  const DeclSpec *DS = nullptr);
+
+  ExprResult BuildCompoundLiteralExpr(
+      SourceLocation LParenLoc, TypeSourceInfo *TInfo, SourceLocation RParenLoc,
+      Expr *LiteralExpr, StorageClass SC = SC_None,
+      ThreadStorageClassSpecifier TSC = TSCS_unspecified,
+      ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified);
 
   ExprResult ActOnInitList(SourceLocation LBraceLoc, MultiExprArg InitArgList,
                            SourceLocation RBraceLoc);
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 3ad71a223903c..97261a632390b 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -8100,8 +8100,10 @@ ExpectedStmt ASTNodeImporter::VisitCompoundLiteralExpr(CompoundLiteralExpr *E) {
     return std::move(Err);
 
   return new (Importer.getToContext()) CompoundLiteralExpr(
-        ToLParenLoc, ToTypeSourceInfo, ToType, E->getValueKind(),
-        ToInitializer, E->isFileScope());
+      ToLParenLoc, ToTypeSourceInfo, ToType, E->getValueKind(), ToInitializer,
+      E->isFileScope(), E->getStorageClass(), E->getTSCSpec(),
+      E->isConstexpr() ? ConstexprSpecKind::Constexpr
+                       : ConstexprSpecKind::Unspecified);
 }
 
 ExpectedStmt ASTNodeImporter::VisitAtomicExpr(AtomicExpr *E) {
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 9a9a76e265f6a..de6aaf503f767 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3416,8 +3416,13 @@ bool Expr::isConstantInitializer(ASTContext &Ctx, bool IsForRef,
     // This handles gcc's extension that allows global initializers like
     // "struct x {int x;} x = (struct x) {};".
     // FIXME: This accepts other cases it shouldn't!
-    const Expr *Exp = cast<CompoundLiteralExpr>(this)->getInitializer();
-    return Exp->isConstantInitializer(Ctx, false, Culprit);
+    const auto *CLE = cast<CompoundLiteralExpr>(this);
+    if (CLE->hasThreadStorage()) {
+      if (Culprit)
+        *Culprit = this;
+      return false;
+    }
+    return CLE->getInitializer()->isConstantInitializer(Ctx, false, Culprit);
   }
   case DesignatedInitUpdateExprClass: {
     const DesignatedInitUpdateExpr *DIUE = cast<DesignatedInitUpdateExpr>(this);
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index a3385d3b7318f..c5d31c96c980f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -157,6 +157,7 @@ namespace {
     case ConstantExprKind::Normal:
     case ConstantExprKind::ClassTemplateArgument:
     case ConstantExprKind::ImmediateInvocation:
+    case ConstantExprKind::CompoundLiteralInitializer:
       // Note that non-type template arguments of class type are emitted as
       // template parameter objects.
       return false;
@@ -171,6 +172,7 @@ namespace {
     switch (Kind) {
     case ConstantExprKind::Normal:
     case ConstantExprKind::ImmediateInvocation:
+    case ConstantExprKind::CompoundLiteralInitializer:
       return false;
 
     case ConstantExprKind::ClassTemplateArgument:
@@ -813,6 +815,8 @@ namespace {
     /// not supported by the interpreter, an error is triggered.
     bool EnableNewConstInterp;
 
+    ConstantExprKind ConstantKind;
+
     /// BottomFrame - The frame in which evaluation started. This must be
     /// initialized after CurrentCall and CallStackDepth.
     CallStackFrame BottomFrame;
@@ -913,11 +917,13 @@ namespace {
     /// initialization.
     uint64_t ArrayInitIndex = -1;
 
-    EvalInfo(const ASTContext &C, Expr::EvalStatus &S, EvaluationMode Mode)
+    EvalInfo(const ASTContext &C, Expr::EvalStatus &S, EvaluationMode Mode,
+             ConstantExprKind Kind = ConstantExprKind::Normal)
         : State(const_cast<ASTContext &>(C), S), CurrentCall(nullptr),
           CallStackDepth(0), NextCallIndex(1),
           StepsLeft(C.getLangOpts().ConstexprStepLimit),
           EnableNewConstInterp(C.getLangOpts().EnableNewConstInterp),
+          ConstantKind(Kind),
           BottomFrame(*this, SourceLocation(), /*Callee=*/nullptr,
                       /*This=*/nullptr,
                       /*CallExpr=*/nullptr, CallRef()),
@@ -1980,7 +1986,7 @@ static bool IsGlobalLValue(APValue::LValueBase B) {
     return false;
   case Expr::CompoundLiteralExprClass: {
     const CompoundLiteralExpr *CLE = cast<CompoundLiteralExpr>(E);
-    return CLE->isFileScope() && CLE->isLValue();
+    return CLE->hasStaticStorage() && CLE->isLValue();
   }
   case Expr::MaterializeTemporaryExprClass:
     // A materialized temporary might have been lifetime-extended to static
@@ -4653,6 +4659,11 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
   }
 
   bool IsAccess = isAnyAccess(AK);
+  bool IsConstexprInitializer =
+      Info.ConstantKind == ConstantExprKind::CompoundLiteralInitializer;
+  if (const auto *VD = dyn_cast_if_present<VarDecl>(
+          Info.EvaluatingDecl.dyn_cast<const ValueDecl *>()))
+    IsConstexprInitializer |= VD->isConstexpr();
 
   // C++11 DR1311: An lvalue-to-rvalue conversion on a volatile-qualified type
   // is not a constant expression (even if the object is non-volatile). We also
@@ -4731,11 +4742,6 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
     }
 
     bool IsConstant = BaseType.isConstant(Info.Ctx);
-    bool ConstexprVar = false;
-    if (const auto *VD = dyn_cast_if_present<VarDecl>(
-            Info.EvaluatingDecl.dyn_cast<const ValueDecl *>()))
-      ConstexprVar = VD->isConstexpr();
-
     // Unless we're looking at a local variable or argument in a constexpr call,
     // the variable we're reading must be const (unless we are binding to a
     // reference).
@@ -4755,7 +4761,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
         return CompleteObject();
       } else if (VD->isConstexpr()) {
         // OK, we can read this variable.
-      } else if (Info.getLangOpts().C23 && ConstexprVar) {
+      } else if (Info.getLangOpts().C23 && IsConstexprInitializer) {
         Info.FFDiag(E);
         return CompleteObject();
       } else if (BaseType->isIntegralOrEnumerationType()) {
@@ -4879,6 +4885,14 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
         assert(BaseVal && "got reference to unevaluated temporary");
       } else if (const CompoundLiteralExpr *CLE =
                      dyn_cast_or_null<CompoundLiteralExpr>(Base)) {
+        if (CLE->hasThreadStorage()) {
+          if (IsAccess) {
+            Info.FFDiag(E);
+            return CompleteObject();
+          }
+          return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
+        }
+
         // According to GCC info page:
         //
         // 6.28 Compound Literals
@@ -9754,6 +9768,11 @@ bool
 LValueExprEvaluator::VisitCompoundLiteralExpr(const CompoundLiteralExpr *E) {
   assert((!Info.getLangOpts().CPlusPlus || E->isFileScope()) &&
          "lvalue compound literal in c++?");
+  if (E->hasThreadStorage()) {
+    Info.FFDiag(E);
+    return false;
+  }
+
   APValue *Lit;
   // If CompountLiteral has static storage, its value can be used outside
   // this expression. So evaluate it once and store it in ASTContext.
@@ -21977,13 +21996,14 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx,
   assert(!isValueDependent() &&
          "Expression evaluator can't be called on a dependent expression.");
   bool IsConst;
-  if (FastEvaluateAsRValue(this, Result.Val, Ctx, IsConst) &&
+  if (Kind != ConstantExprKind::CompoundLiteralInitializer &&
+      FastEvaluateAsRValue(this, Result.Val, Ctx, IsConst) &&
       Result.Val.hasValue())
     return true;
 
   ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsConstantExpr");
   EvaluationMode EM = EvaluationMode::ConstantExpression;
-  EvalInfo Info(Ctx, Result, EM);
+  EvalInfo Info(Ctx, Result, EM, Kind);
   Info.InConstantContext = true;
 
   if (Info.EnableNewConstInterp) {
@@ -22278,6 +22298,27 @@ static ICEDiag CheckEvalInICE(const Expr* E, const ASTContext &Ctx) {
   return NoDiag();
 }
 
+static const Expr *getMemberAccess(const Expr *E) {
+  E = E->IgnoreParenImpCasts();
+  while (const auto *ME = dyn_cast<MemberExpr>(E)) {
+    if (ME->isArrow())
+      return nullptr;
+    E = ME->getBase()->IgnoreParenImpCasts();
+  }
+  return E;
+}
+
+static bool isConstexprNamedConstant(const Expr *E) {
+  const auto *DRE = dyn_cast_or_null<DeclRefExpr>(getMemberAccess(E));
+  const auto *VD = DRE ? dyn_cast<VarDecl>(DRE->getDecl()) : nullptr;
+  return VD && VD->isConstexpr();
+}
+
+static bool isCompoundLiteralConstant(const Expr *E) {
+  const auto *CLE = dyn_cast_or_null<CompoundLiteralExpr>(getMemberAccess(E));
+  return CLE && CLE->isConstexpr();
+}
+
 static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
   assert(!E->isValueDependent() && "Should not see value dependent exprs!");
   if (!E->getType()->isIntegralOrEnumerationType())
@@ -22288,6 +22329,13 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
 #define STMT(Node, Base) case Expr::Node##Class:
 #define EXPR(Node, Base)
 #include "clang/AST/StmtNodes.inc"
+  case Expr::CompoundLiteralExprClass: {
+    const CompoundLiteralExpr *CLE = cast<CompoundLiteralExpr>(E);
+    if (CLE->isConstexpr())
+      return CheckEvalInICE(E, Ctx);
+    return ICEDiag(IK_NotICE, E->getBeginLoc());
+  }
+
   case Expr::PredefinedExprClass:
   case Expr::FloatingLiteralClass:
   case Expr::ImaginaryLiteralClass:
@@ -22299,7 +22347,6 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
   case Expr::OMPArrayShapingExprClass:
   case Expr::OMPIteratorExprClass:
   case Expr::CompoundAssignOperatorClass:
-  case Expr::CompoundLiteralExprClass:
   case Expr::ExtVectorElementExprClass:
   case Expr::MatrixElementExprClass:
   case Expr::DesignatedInitExprClass:
@@ -22377,20 +22424,9 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
     return ICEDiag(IK_NotICE, E->getBeginLoc());
 
   case Expr::MemberExprClass: {
-    if (Ctx.getLangOpts().C23) {
-      const Expr *ME = E->IgnoreParenImpCasts();
-      while (const auto *M = dyn_cast<MemberExpr>(ME)) {
-        if (M->isArrow())
-          return ICEDiag(IK_NotICE, E->getBeginLoc());
-        ME = M->getBase()->IgnoreParenImpCasts();
-      }
-      const auto *DRE = dyn_cast<DeclRefExpr>(ME);
-      if (DRE) {
-        if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
-            VD && VD->isConstexpr())
-          return CheckEvalInICE(E, Ctx);
-      }
-    }
+    if (Ctx.getLangOpts().C23 &&
+        (isConstexprNamedConstant(E) || isCompoundLiteralConstant(E)))
+      return CheckEvalInICE(E, Ctx);
     return ICEDiag(IK_NotICE, E->getBeginLoc());
   }
 
@@ -22629,8 +22665,8 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
   case Expr::ObjCBridgedCastExprClass: {
     const Expr *SubExpr = cast<CastExpr>(E)->getSubExpr();
     if (isa<ExplicitCastExpr>(E)) {
-      if (const FloatingLiteral *FL
-            = dyn_cast<FloatingLiteral>(SubExpr->IgnoreParenImpCasts())) {
+      const Expr *Sub = SubExpr->IgnoreParenImpCasts();
+      if (const FloatingLiteral *FL = dyn_cast<FloatingLiteral>(Sub)) {
         unsigned DestWidth = Ctx.getIntWidth(E->getType());
         bool DestSigned = E->getType()->isSignedIntegerOrEnumerationType();
         APSInt IgnoredVal(DestWidth, !DestSigned);
@@ -22644,6 +22680,9 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
           return ICEDiag(IK_NotICE, E->getBeginLoc());
         return NoDiag();
       }
+      if (Ctx.getLangOpts().C23 && Sub->getType()->isArithmeticType() &&
+          (isConstexprNamedConstant(Sub) || isCompoundLiteralConstant(Sub)))
+        return CheckEvalInICE(E, Ctx);
     }
     switch (cast<CastExpr>(E)->getCastKind()) {
     case CK_LValueToRValue:
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index 877191d456b35..5bc65c6a76cf2 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -1909,7 +1909,29 @@ void StmtPrinter::VisitCStyleCastExpr(CStyleCastExpr *Node) {
 
 void StmtPrinter::VisitCompoundLiteralExpr(CompoundLiteralExpr *Node) {
   OS << '(';
-  Node->getType().print(OS, Policy);
+  if (Node->getStorageClass() == SC_Static)
+    OS << "static ";
+  else if (Node->getStorageClass() == SC_Register)
+    OS << "register ";
+  switch (Node->getTSCSpec()) {
+  case TSCS_thread_local:
+    OS << "thread_local ";
+    break;
+  case TSCS__Thread_local:
+    OS << "_Thread_local ";
+    break;
+  case TSCS___thread:
+    OS << "__thread ";
+    break;
+  case TSCS_unspecified:
+    break;
+  }
+  QualType T = Node->getType();
+  if (Node->isConstexpr()) {
+    T = Node->getTypeSourceInfo()->getType();
+    OS << "constexpr ";
+  }
+  T.print(OS, Policy);
   OS << ')';
   PrintExpr(Node->getInitializer());
 }
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 00c132f1ed9e0..ef617ddfd49fb 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -1600,6 +1600,9 @@ void StmtProfiler::VisitMemberExpr(const MemberExpr *S) {
 void StmtProfiler::VisitCompoundLiteralExpr(const CompoundLiteralExpr *S) {
   VisitExpr(S);
   ID.AddBoolean(S->isFileScope());
+  ID.AddInteger(S->getStorageClass());
+  ID.AddInteger(S->getTSCSpec());
+  ID.AddBoolean(S->isConstexpr());
 }
 
 void StmtProfiler::VisitCastExpr(const CastExpr *S) {
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 9201e40bc13a1..fadf00a606f3e 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5993,8 +5993,17 @@ CodeGenFunction::EmitLValueForFieldInitialization(LValue Base,
 }
 
 LValue CodeGenFunction::EmitCompoundLiteralLValue(const CompoundLiteralExpr *E){
-  if (E->isFileScope()) {
+  if (E->isFileScope() || E->hasGlobalStorage()) {
+    if (E->getType()->isVariablyModifiedType())
+      EmitVariablyModifiedType(E->getType());
+
     ConstantAddress GlobalPtr = CGM.GetAddrOfConstantCompoundLiteral(E);
+    if (E->hasThreadStorage()) {
+      llvm::Value *V = Builder.CreateThreadLocalAddress(GlobalPtr.getPointer());
+      return MakeAddrLValue(
+          Address(V, ConvertTypeForMem(E->getType()), GlobalPtr.getAlignment()),
+          E->getType(), AlignmentSource::Decl);
+    }
     return MakeAddrLValue(GlobalPtr, E->getType(), AlignmentSource::Decl);
   }
   if (E->getType()->isVariablyModifiedType())
@@ -6005,8 +6014,10 @@ LValue CodeGenFunction::EmitCompoundLiteralLValue(const CompoundLiteralExpr *E){
   const Expr *InitExpr = E->getInitializer();
   LValue Result = MakeAddrLValue(DeclPtr, E->getType(), AlignmentSource::Decl);
 
-  EmitAnyExprToMem(InitExpr, DeclPtr, E->getType().getQualifiers(),
-                   /*Init*/ true);
+  if (E->getType()->isAtomicType())
+    EmitAtomicInit(const_cast<Expr *>(InitExpr), Result);
+  else
+    EmitInitializationToLValue(InitExpr, Result);
 
   // Block-scope compound literals are destroyed at the end of the enclosing
   // scope in C.
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index bc35ffdaad2bd..c1309c456d4e3 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -789,9 +789,10 @@ void AggExprEmitter::VisitOpaqueValueExpr(OpaqueValueExpr *e) {
 }
 
 void AggExprEmitter::VisitCompoundLiteralExpr(CompoundLiteralExpr *E) {
-  if (Dest.isPotentiallyAliased()) {
-    // Just emit a load of the lvalue + a copy, because our compound literal
-    // might alias the destination.
+  if (E->hasGlobalStorage() || E->getType()->isAtomicType() ||
+      (E->getStorageClass() == SC_Register &&
+       E->getType().isVolatileQualified()) ||
+      Dest.isPotentiallyAliased()) {
     EmitAggLoadOfLValue(E);
     return;
   }
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp
index 257de0145855b..2123eab890779 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -1089,8 +1089,8 @@ tryEmitGlobalCompoundLiteral(ConstantEmitter &emitter,
   llvm::Constant *C = emitter.tryEmitForInitializer(E->getInitializer(),
                                                     addressSpace, E->getType());
   if (!C) {
-    assert(!E->isFileScope() &&
-           "file-scope compound literal did not have constant initializer!");
+    assert(!E->isFileScope() && !E->hasGlobalStorage() &&
+           "global compound literal did not have constant initializer!");
     return ConstantAddress::invalid();
   }
 
@@ -1098,7 +1098,8 @@ tryEmitGlobalCompoundLiteral(ConstantEmitter &emitter,
       CGM.getModule(), C->getType(),
       E->getType().isConstantStorage(CGM.getContext(), true, false),
       llvm::GlobalValue::InternalLinkage, C, ".compoundliteral", nullptr,
-      llvm::GlobalVariable::NotThreadLocal,
+      E->hasThreadStorage() ? CGM.GetDefaultLLVMTLSModel()
+                            : llvm::GlobalVariable::NotThreadLocal,
       CGM.getContext().getTargetAddressSpace(addressSpace));
   emitter.finalize(GV);
   GV->setAlignment(Align.getAsAlign());
@@ -2770,7 +2771,8 @@ void CodeGenModule::setAddrOfConstantCompoundLiteral(
 
 ConstantAddress
 CodeGenModule::GetAddrOfConstantCompoundLiteral(const CompoundLiteralExpr *E) {
-  assert(E->isFileScope() && "not a file-scope compound literal expr");
+  assert((E->isFileScope() || E->hasGlobalStorage()) &&
+         "not a global compound literal expression");
   ConstantEmitter emitter(*this);
   return tryEmitGlobalCompoundLiteral(emitter, E);
 }
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index 119aebb673789..861dfa1f9f1c7 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -1360,8 +1360,10 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
       else
         Ty = VD->getType();
 
-      if (Ty->isVariablyModifiedType())
+      if (Ty->isVariablyModifiedType()) {
+        RunCleanupsScope Scope(*this);
         EmitVariablyModifiedType(Ty);
+      }
     }
   }
   // Emit a location at the end of the prologue.
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 6ca036664094e..55e1d489efb39 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -2656,6 +2656,59 @@ bool Parser::tryParseOpenMPArrayShapingCastPart() {
   return !ErrorFound;
 }
 
+bool Parser::isCompoundLiteralStorageClassSpecifier(const Token &Tok) const {
+  if (!getLangOpts().C23)
+    return false;
+  switch (Tok.getKind()) {
+  case tok::kw_constexpr:
+  case tok::kw_register:
+  case tok::kw_static:
+  case tok::kw_thread_local:
+  case tok::kw__Thread_local:
+    return true;
+  default:
+    return false;
+  }
+}
+
+void Parser::ParseCompoundLiteralStorageClassSpecifiers(DeclSpec &DS) {
+  DS.SetRangeStart(Tok.getLocation());
+  const PrintingPolicy &Policy = Actions.getASTContext().getPrintingPolicy();
+  while (isCompoundLiteralStorageClassSpecifier(Tok)) {
+    SourceLocation Loc = Tok.getLocation();
+    const char *PrevSpec = nullptr;
+    unsigned DiagID = 0;
+    bool IsInvalid = false;
+    switch (Tok.getKind()) {
+    case tok::kw_static:
+      IsInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_static, Loc,
+                                         PrevSpec, DiagID, Policy);
+      break;
+    case tok::kw_register:
+      IsInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_register, Loc,
+                                         PrevSpec, DiagID, Policy);
+      break;
+    case tok::kw_thread_local:
+    case tok::kw__Thread_local:
+      IsInvalid = DS.SetStorageClassSpecThread(DeclSpec::TSCS__Thread_local,
+                                               Loc, PrevSpec, DiagID);
+      break;
+    case tok::kw_constexpr:
+      IsInvalid = DS.SetConstexprSpec(ConstexprSpecKind::Constexpr, Loc,
+                                      PrevSpec, DiagID);
+      break;
+    default:
+      llvm_unreachable("unexpected compound literal storage class specifier");
+    }
+
+    if (IsInvalid)
+      Diag(Loc, DiagID) << PrevSpec;
+
+    DS.SetRangeEnd(Loc);
+    ConsumeToken();
+  }
+}
+
 ExprResult
 Parser::ParseParenExpression(ParenParseOption &ExprType, bool StopIfCastExpr,
                              ParenExprKind ParenBehavior,
@@ -2671,7 +2724,7 @@ Parser::ParseParenExpression(ParenParseOption &ExprType, bool StopIfCastExpr,
   PreferredType.enterParenExpr(Tok.getLocation(), OpenLoc);
 
   ExprResult Result(true);
-  bool isAmbiguousTypeId;
+  bool isAmbiguousTypeId = false;
   CastTy = nullptr;
 
   if (Tok.is(tok::code_completion)) {
@@ -2773,7 +2826,8 @@ Parser::ParseParenExpression(ParenParseOption &ExprType, bool StopIfCastExpr,
                                                BridgeKeywordLoc, Ty.get(),
                                                RParenLoc, SubExpr.get());
   } else if (ExprType >= ParenParseOption::CompoundLiteral &&
-             isTypeIdInParens(isAmbiguousTypeId)) {
+             (isCompoundLiteralStorageClassSpecifier(Tok) ||
+              isTypeIdInParens(isAmbiguousTypeId))) {
 
     // Otherwise, this is a compound literal expression or cast expression.
 
@@ -2789,7 +2843,12 @@ Parser::ParseParenExpression(ParenParseOption &ExprType, bool StopIfCastExpr,
       return res;
     }
 
-    // Parse the type declarator.
+    DeclSpec CompoundDS(AttrFactory);
+    if (isCompoundLiteralStorageClassSpecifier(Tok)) {
+      ParseCompoundLiteralStorageClassSpecifiers(CompoundDS);
+      CompoundDS.Finish(Actions, Actions.getASTContext().getPrintingPolicy());
+    }
+
     DeclSpec DS(AttrFactory);
     ParseSpecifierQualifierList(DS);
     Declarator DeclaratorInfo(DS, ParsedAttributesView::none(),
@@ -2817,12 +2876,32 @@ Parser::ParseParenExpression(ParenParseOption &ExprType, bool StopIfCastExpr,
       RParenLoc = T.getCloseLocation();
       if (ParenBehavior == ParenExprKind::Unknown && Tok.is(tok::l_brace)) {
         ExprType = ParenParseOption::CompoundLiteral;
+
+        if (CompoundDS.getBeginLoc().isValid())
+          Diag(CompoundDS.getBeginLoc(),
+               diag::warn_c23_compat_compound_literal_storage_class);
+
         TypeResult Ty;
         {
           InMessageExpressionRAIIObject InMessage(*this, false);
           Ty = Actions.ActOnTypeName(DeclaratorInfo);
         }
-        return ParseCompoundLiteralExpression(Ty.get(), OpenLoc, RParenLoc);
+        return ParseCompoundLiteralExpression(Ty.get(), OpenLoc, RParenLoc,
+                                              &CompoundDS);
+      }
+
+      if (!DeclaratorInfo.isInvalidType()) {
+        if (CompoundDS.getParsedSpecifiers() &
+            DeclSpec::PQ_StorageClassSpecifier) {
+          SourceLocation Loc = CompoundDS.getStorageClassSpecLoc();
+          if (Loc.isInvalid())
+            Loc = CompoundDS.getThreadStorageClassSpecLoc();
+          Diag(Loc, diag::err_typename_invalid_storageclass);
+        }
+        if (CompoundDS.hasConstexprSpecifier())
+          Diag(CompoundDS.getConstexprSpecLoc(),
+               diag::err_typename_invalid_constexpr)
+              << static_cast<int>(CompoundDS.getConstexprSpecifier());
       }
 
       if (ParenBehavior == ParenExprKind::Unknown && Tok.is(tok::l_paren)) {
@@ -2996,17 +3075,18 @@ Parser::ParseParenExpression(ParenParseOption &ExprType, bool StopIfCastExpr,
   return Result;
 }
 
-ExprResult
-Parser::ParseCompoundLiteralExpression(ParsedType Ty,
-                                       SourceLocation LParenLoc,
-                                       SourceLocation RParenLoc) {
+ExprResult Parser::ParseCompoundLiteralExpression(ParsedType Ty,
+                                                  SourceLocation LParenLoc,
+                                                  SourceLocation RParenLoc,
+                                                  const DeclSpec *DS) {
   assert(Tok.is(tok::l_brace) && "Not a compound literal!");
   if (!getLangOpts().C99)   // Compound literals don't exist in C90.
     Diag(LParenLoc, diag::ext_c99_compound_literal);
   PreferredType.enterTypeCast(Tok.getLocation(), Ty.get());
   ExprResult Result = ParseInitializer();
   if (!Result.isInvalid() && Ty)
-    return Actions.ActOnCompoundLiteral(LParenLoc, Ty, RParenLoc, Result.get());
+    return Actions.ActOnCompoundLiteral(LParenLoc, Ty, RParenLoc, Result.get(),
+                                        DS);
   return Result;
 }
 
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index fafee76ec18c3..e5cfc3086fb58 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -131,6 +131,7 @@ getEntityLifetime(const InitializedEntity *Entity,
 
   case InitializedEntity::EK_Temporary:
   case InitializedEntity::EK_CompoundLiteralInit:
+  case InitializedEntity::EK_ConstexprCompoundLiteralInit:
   case InitializedEntity::EK_RelatedResult:
     // We don't yet know the storage duration of the surrounding temporary.
     // Assume it's got full-expression duration for now, it will patch up our
@@ -658,7 +659,7 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
 
   case Stmt::CompoundLiteralExprClass: {
     if (auto *CLE = dyn_cast<CompoundLiteralExpr>(Init)) {
-      if (!CLE->isFileScope())
+      if (!CLE->isFileScope() && !CLE->hasGlobalStorage())
         Visit(Path, Local(CLE), RK);
     }
     break;
diff --git a/clang/lib/Sema/DelayedDiagnostic.cpp b/clang/lib/Sema/DelayedDiagnostic.cpp
index cb2721b92090e..4f94da0e64fba 100644
--- a/clang/lib/Sema/DelayedDiagnostic.cpp
+++ b/clang/lib/Sema/DelayedDiagnostic.cpp
@@ -68,6 +68,7 @@ void DelayedDiagnostic::Destroy() {
     break;
 
   case ForbiddenType:
+  case ForbiddenStatic:
     break;
   }
 }
diff --git a/clang/lib/Sema/Scope.cpp b/clang/lib/Sema/Scope.cpp
index fc79b1a056ed9..1cd7a21893ba4 100644
--- a/clang/lib/Sema/Scope.cpp
+++ b/clang/lib/Sema/Scope.cpp
@@ -103,14 +103,15 @@ void Scope::Init(Scope *parent, unsigned flags) {
   NRVO = std::nullopt;
 }
 
-bool Scope::containedInPrototypeScope() const {
-  const Scope *S = this;
-  while (S) {
+const Scope *Scope::getEnclosingFunctionPrototypeScope() const {
+  for (const Scope *S = this; S; S = S->getParent())
     if (S->isFunctionPrototypeScope())
-      return true;
-    S = S->getParent();
-  }
-  return false;
+      return S;
+  return nullptr;
+}
+
+bool Scope::containedInPrototypeScope() const {
+  return getEnclosingFunctionPrototypeScope() != nullptr;
 }
 
 void Scope::EnterLoopBody(LabelDecl *LD) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index a553c6994f0ed..cf09bf2615b07 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7419,7 +7419,7 @@ static void checkDLLAttributeRedeclaration(Sema &S, NamedDecl *OldDecl,
 /// Given that we are within the definition of the given function,
 /// will that definition behave like C99's 'inline', where the
 /// definition is discarded except for optimization purposes?
-static bool isFunctionDefinitionDiscarded(Sema &S, FunctionDecl *FD) {
+static bool isFunctionDefinitionDiscarded(Sema &S, const FunctionDecl *FD) {
   // Try to avoid calling GetGVALinkageForFunction.
 
   // All cases of this require the 'inline' keyword.
@@ -7433,6 +7433,14 @@ static bool isFunctionDefinitionDiscarded(Sema &S, FunctionDecl *FD) {
   return S.Context.GetGVALinkageForFunction(FD) == GVA_AvailableExternally;
 }
 
+void Sema::DiagnoseStaticInInline(SourceLocation Loc, const FunctionDecl *FD) {
+  if (!FD || !isFunctionDefinitionDiscarded(*this, FD))
+    return;
+
+  Diag(Loc, diag::warn_static_local_in_extern_inline);
+  MaybeSuggestAddingStaticToDecl(FD);
+}
+
 /// Determine whether a variable is extern "C" prior to attaching
 /// an initializer. We can't just call isExternC() here, because that
 /// will also compute and cache whether the declaration is externally
@@ -8190,14 +8198,9 @@ NamedDecl *Sema::ActOnVariableDeclarator(
   // be marked 'static'.  Also note that it's possible to get these
   // semantics in C++ using __attribute__((gnu_inline)).
   if (SC == SC_Static && S->getFnParent() != nullptr &&
-      !NewVD->getType().isConstQualified()) {
-    FunctionDecl *CurFD = getCurFunctionDecl();
-    if (CurFD && isFunctionDefinitionDiscarded(*this, CurFD)) {
-      Diag(D.getDeclSpec().getStorageClassSpecLoc(),
-           diag::warn_static_local_in_extern_inline);
-      MaybeSuggestAddingStaticToDecl(CurFD);
-    }
-  }
+      !NewVD->getType().isConstQualified())
+    DiagnoseStaticInInline(D.getDeclSpec().getStorageClassSpecLoc(),
+                           getCurFunctionDecl());
 
   if (D.getDeclSpec().isModulePrivateSpecified()) {
     if (IsVariableTemplateSpecialization)
@@ -8898,32 +8901,31 @@ static bool checkForConflictWithNonVisibleExternC(Sema &S, const T *ND,
   return false;
 }
 
-static bool CheckC23ConstexprVarType(Sema &SemaRef, SourceLocation VarLoc,
-                                     QualType T) {
-  QualType CanonT = SemaRef.Context.getCanonicalType(T);
+bool Sema::CheckConstexprType(SourceLocation Loc, QualType T, unsigned DiagID) {
+  QualType CanonT = Context.getCanonicalType(T);
   // C23 6.7.1p5: An object declared with storage-class specifier constexpr or
   // any of its members, even recursively, shall not have an atomic type, or a
   // variably modified type, or a type that is volatile or restrict qualified.
   if (CanonT->isVariablyModifiedType()) {
-    SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T;
+    Diag(Loc, DiagID) << T;
     return true;
   }
 
   // Arrays are qualified by their element type, so get the base type (this
   // works on non-arrays as well).
-  CanonT = SemaRef.Context.getBaseElementType(CanonT);
+  CanonT = Context.getBaseElementType(CanonT);
 
   if (CanonT->isAtomicType() || CanonT.isVolatileQualified() ||
       CanonT.isRestrictQualified()) {
-    SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T;
+    Diag(Loc, DiagID) << T;
     return true;
   }
 
   if (CanonT->isRecordType()) {
     const RecordDecl *RD = CanonT->getAsRecordDecl();
     if (!RD->isInvalidDecl() &&
-        llvm::any_of(RD->fields(), [&SemaRef, VarLoc](const FieldDecl *F) {
-          return CheckC23ConstexprVarType(SemaRef, VarLoc, F->getType());
+        llvm::any_of(RD->fields(), [this, Loc, DiagID](const FieldDecl *F) {
+          return CheckConstexprType(Loc, F->getType(), DiagID);
         }))
       return true;
   }
@@ -9199,7 +9201,8 @@ void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
   }
 
   if (getLangOpts().C23 && NewVD->isConstexpr() &&
-      CheckC23ConstexprVarType(*this, NewVD->getLocation(), T)) {
+      CheckConstexprType(NewVD->getLocation(), T,
+                         diag::err_c23_constexpr_invalid_type)) {
     NewVD->setInvalidDecl();
     return;
   }
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 1b272b5416860..cf6ce9b5508be 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -9017,6 +9017,14 @@ void Sema::PopParsingDeclaration(ParsingDeclState state, Decl *decl) {
       case DelayedDiagnostic::ForbiddenType:
         handleDelayedForbiddenType(*this, diag, decl);
         break;
+
+      case DelayedDiagnostic::ForbiddenStatic:
+        if (const FunctionDecl *FD = decl->getAsFunction();
+            FD && FD->isThisDeclarationADefinition()) {
+          diag.Triggered = true;
+          DiagnoseStaticInInline(diag.Loc, FD);
+        }
+        break;
       }
     }
   } while ((pool = pool->getParent()));
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index ed3d27b5adc27..b5f19964cbefd 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -63,6 +63,7 @@
 #include "clang/Sema/SemaOpenMP.h"
 #include "clang/Sema/SemaPseudoObject.h"
 #include "clang/Sema/Template.h"
+#include "llvm/ADT/PointerUnion.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/ConvertUTF.h"
@@ -123,6 +124,40 @@ static void DiagnoseUnusedOfDecl(Sema &S, NamedDecl *D, SourceLocation Loc) {
   }
 }
 
+static bool checkThreadLocalCompoundLiteral(Sema &S, SourceLocation Loc,
+                                            QualType T) {
+  if (S.getLangOpts().ObjCAutoRefCount && T->isObjCLifetimeType()) {
+    Qualifiers::ObjCLifetime Lifetime = T.getObjCLifetime();
+    if (Lifetime == Qualifiers::OCL_None)
+      Lifetime = T->getObjCARCImplicitLifetime();
+
+    if (Lifetime && Lifetime != Qualifiers::OCL_ExplicitNone) {
+      S.Diag(Loc, diag::err_arc_thread_ownership)
+          << S.Context.getLifetimeQualifiedType(T, Lifetime);
+      return true;
+    }
+  }
+
+  if (T.isDestructedType()) {
+    S.Diag(Loc, diag::err_thread_nontrivial_dtor);
+    return true;
+  }
+
+  unsigned MaxAlign = S.Context.getTargetInfo().getMaxTLSAlign();
+  if (!MaxAlign || T->isDependentType())
+    return false;
+
+  CharUnits MaxAlignChars = S.Context.toCharUnitsFromBits(MaxAlign);
+  CharUnits TypeAlign = S.Context.getTypeAlignInChars(T);
+  if (TypeAlign <= MaxAlignChars)
+    return false;
+
+  S.Diag(Loc, diag::err_tls_compound_literal_aligned_over_maximum)
+      << (unsigned)TypeAlign.getQuantity()
+      << (unsigned)MaxAlignChars.getQuantity();
+  return true;
+}
+
 void Sema::NoteDeletedFunction(FunctionDecl *Decl) {
   assert(Decl && Decl->isDeleted());
 
@@ -519,6 +554,54 @@ SourceRange Sema::getExprRange(Expr *E) const {
 //  Standard Promotions and Conversions
 //===----------------------------------------------------------------------===//
 
+namespace {
+enum {
+  AO_Bit_Field = 0,
+  AO_Vector_Element = 1,
+  AO_Property_Expansion = 2,
+  AO_Register_Variable = 3,
+  AO_Matrix_Element = 4,
+  AO_Register_Compound_Literal = 5,
+  AO_No_Error = 6
+};
+}
+
+using PrimaryObject = llvm::PointerUnion<ValueDecl *, CompoundLiteralExpr *>;
+
+static PrimaryObject getPrimaryObject(Expr *E) {
+  E = E->IgnoreParens();
+  switch (E->getStmtClass()) {
+  case Stmt::DeclRefExprClass:
+    return cast<DeclRefExpr>(E)->getDecl();
+  case Stmt::CompoundLiteralExprClass:
+    return cast<CompoundLiteralExpr>(E);
+  case Stmt::MemberExprClass:
+    if (cast<MemberExpr>(E)->isArrow())
+      return {};
+    return getPrimaryObject(cast<MemberExpr>(E)->getBase());
+  case Stmt::ArraySubscriptExprClass: {
+    Expr *Base = cast<ArraySubscriptExpr>(E)->getBase()->IgnoreParens();
+    if (auto *ICE = dyn_cast<ImplicitCastExpr>(Base);
+        ICE && ICE->getSubExpr()->getType()->isArrayType())
+      return getPrimaryObject(ICE->getSubExpr());
+    return {};
+  }
+  case Stmt::UnaryOperatorClass: {
+    UnaryOperator *UO = cast<UnaryOperator>(E);
+    if (UO->getOpcode() == UO_Real || UO->getOpcode() == UO_Imag ||
+        UO->getOpcode() == UO_Extension)
+      return getPrimaryObject(UO->getSubExpr());
+    return {};
+  }
+  case Stmt::ImplicitCastExprClass:
+    return getPrimaryObject(cast<ImplicitCastExpr>(E)->getSubExpr());
+  case Stmt::CXXUuidofExprClass:
+    return cast<CXXUuidofExpr>(E)->getGuidDecl();
+  default:
+    return {};
+  }
+}
+
 /// DefaultFunctionArrayConversion (C99 6.3.2.1p3, C99 6.3.2.1p4).
 ExprResult Sema::DefaultFunctionArrayConversion(Expr *E, bool Diagnose) {
   // Handle any placeholder expressions which made it here.
@@ -552,6 +635,13 @@ ExprResult Sema::DefaultFunctionArrayConversion(Expr *E, bool Diagnose) {
     // T" can be converted to an rvalue of type "pointer to T".
     //
     if (getLangOpts().C99 || getLangOpts().CPlusPlus || E->isLValue()) {
+      if (!getLangOpts().CPlusPlus) {
+        auto *CLE = getPrimaryObject(E).dyn_cast<CompoundLiteralExpr *>();
+        if (CLE && CLE->getStorageClass() == SC_Register)
+          return Diag(E->getExprLoc(), diag::err_typecheck_address_of)
+                 << AO_Register_Compound_Literal << CLE->getSourceRange();
+      }
+
       ExprResult Res = ImpCastExprToType(E, Context.getArrayDecayedType(Ty),
                                          CK_ArrayToPointerDecay);
       if (Res.isInvalid())
@@ -7425,9 +7515,9 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl,
   return CheckForImmediateInvocation(MaybeBindToTemporary(TheCall), FDecl);
 }
 
-ExprResult
-Sema::ActOnCompoundLiteral(SourceLocation LParenLoc, ParsedType Ty,
-                           SourceLocation RParenLoc, Expr *InitExpr) {
+ExprResult Sema::ActOnCompoundLiteral(SourceLocation LParenLoc, ParsedType Ty,
+                                      SourceLocation RParenLoc, Expr *InitExpr,
+                                      const DeclSpec *DS) {
   assert(Ty && "ActOnCompoundLiteral(): missing type");
   assert(InitExpr && "ActOnCompoundLiteral(): missing expression");
 
@@ -7436,13 +7526,90 @@ Sema::ActOnCompoundLiteral(SourceLocation LParenLoc, ParsedType Ty,
   if (!TInfo)
     TInfo = Context.getTrivialTypeSourceInfo(literalType);
 
-  return BuildCompoundLiteralExpr(LParenLoc, TInfo, RParenLoc, InitExpr);
-}
-
-ExprResult
-Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo,
-                               SourceLocation RParenLoc, Expr *LiteralExpr) {
+  StorageClass SC = SC_None;
+  ThreadStorageClassSpecifier TSC = TSCS_unspecified;
+  ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified;
+  SourceLocation StorageClassLoc;
+  SourceLocation ThreadStorageClassLoc;
+  SourceLocation ConstexprLoc;
+  if (DS) {
+    if (DS->getStorageClassSpec() == DeclSpec::SCS_static)
+      SC = SC_Static;
+    else if (DS->getStorageClassSpec() == DeclSpec::SCS_register)
+      SC = SC_Register;
+
+    TSC = DS->getThreadStorageClassSpec();
+    ConstexprKind = DS->getConstexprSpecifier();
+    StorageClassLoc = DS->getStorageClassSpecLoc();
+    ThreadStorageClassLoc = DS->getThreadStorageClassSpecLoc();
+    ConstexprLoc = DS->getConstexprSpecLoc();
+  }
+
+  return BuildCompoundLiteralExpr(LParenLoc, TInfo, RParenLoc, InitExpr, SC,
+                                  StorageClassLoc, TSC, ThreadStorageClassLoc,
+                                  ConstexprKind, ConstexprLoc);
+}
+
+ExprResult Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc,
+                                          TypeSourceInfo *TInfo,
+                                          SourceLocation RParenLoc,
+                                          Expr *LiteralExpr, StorageClass SC,
+                                          ThreadStorageClassSpecifier TSC,
+                                          ConstexprSpecKind ConstexprKind) {
+  return BuildCompoundLiteralExpr(LParenLoc, TInfo, RParenLoc, LiteralExpr, SC,
+                                  LParenLoc, TSC, LParenLoc, ConstexprKind,
+                                  LParenLoc);
+}
+
+ExprResult Sema::BuildCompoundLiteralExpr(
+    SourceLocation LParenLoc, TypeSourceInfo *TInfo, SourceLocation RParenLoc,
+    Expr *LiteralExpr, StorageClass SC, SourceLocation StorageClassLoc,
+    ThreadStorageClassSpecifier TSC, SourceLocation ThreadStorageClassLoc,
+    ConstexprSpecKind ConstexprKind, SourceLocation ConstexprLoc) {
   QualType literalType = TInfo->getType();
+  const Scope *S = getCurScope();
+  const Scope *PrototypeScope = S->getEnclosingFunctionPrototypeScope();
+
+  bool HasStatic = SC == SC_Static;
+  bool HasRegister = SC == SC_Register;
+  bool HasThreadStorage = TSC != TSCS_unspecified;
+  bool HasConstexpr = ConstexprKind == ConstexprSpecKind::Constexpr;
+
+  bool IsPrototypeScope = PrototypeScope;
+  bool IsFunctionDeclarationScope =
+      PrototypeScope && PrototypeScope->isFunctionDeclarationScope();
+  bool IsFileScope = !CurContext->isFunctionOrMethod() &&
+                     !S->isInCFunctionScope() && !PrototypeScope;
+
+  if (HasConstexpr && literalType->isObjectType())
+    literalType.addConst();
+
+  if (getLangOpts().C23 &&
+      (SC != SC_None || HasThreadStorage || HasConstexpr)) {
+    if (HasRegister && IsFileScope) {
+      Diag(StorageClassLoc, diag::err_register_compound_literal_file_scope);
+      return ExprError();
+    }
+    if (HasThreadStorage && !IsFileScope && !HasStatic) {
+      Diag(ThreadStorageClassLoc,
+           diag::err_thread_local_compound_literal_without_static);
+      return ExprError();
+    }
+    if (HasThreadStorage && !Context.getTargetInfo().isTLSSupported()) {
+      Diag(ThreadStorageClassLoc, diag::err_thread_unsupported);
+      return ExprError();
+    }
+    if (HasConstexpr &&
+        CheckConstexprType(ConstexprLoc, literalType,
+                           diag::err_constexpr_compound_literal_invalid_type))
+      return ExprError();
+  }
+
+  if (getLangOpts().C23 && IsFileScope &&
+      literalType->isVariablyModifiedType()) {
+    Diag(LParenLoc, diag::err_vm_decl_in_file_scope);
+    return ExprError();
+  }
 
   if (literalType->isArrayType()) {
     if (RequireCompleteSizedType(
@@ -7477,32 +7644,47 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo,
         return ExprError();
     }
   } else if (!literalType->isDependentType() &&
-             RequireCompleteType(LParenLoc, literalType,
-               diag::err_typecheck_decl_incomplete_type,
-               SourceRange(LParenLoc, LiteralExpr->getSourceRange().getEnd())))
+             RequireCompleteType(
+                 LParenLoc, literalType,
+                 diag::err_typecheck_decl_incomplete_type,
+                 SourceRange(LParenLoc,
+                             LiteralExpr->getSourceRange().getEnd())))
     return ExprError();
 
-  InitializedEntity Entity
-    = InitializedEntity::InitializeCompoundLiteralInit(TInfo);
-  InitializationKind Kind
-    = InitializationKind::CreateCStyleCast(LParenLoc,
-                                           SourceRange(LParenLoc, RParenLoc),
-                                           /*InitList=*/true);
+  InitializedEntity Entity = InitializedEntity::InitializeCompoundLiteralInit(
+      TInfo, literalType, ConstexprKind);
+  InitializationKind Kind = InitializationKind::CreateCStyleCast(
+      LParenLoc, SourceRange(LParenLoc, RParenLoc),
+      /*InitList=*/true);
   InitializationSequence InitSeq(*this, Entity, Kind, LiteralExpr);
-  ExprResult Result = InitSeq.Perform(*this, Entity, Kind, LiteralExpr,
-                                      &literalType);
+  ExprResult Result =
+      InitSeq.Perform(*this, Entity, Kind, LiteralExpr, &literalType);
   if (Result.isInvalid())
     return ExprError();
   LiteralExpr = Result.get();
 
-  // We treat the compound literal as being at file scope if it's not in a
-  // function or method body, or within the function's prototype scope. This
-  // means the following compound literal is not at file scope:
-  //   void func(char *para[(int [1]){ 0 }[0]);
-  const Scope *S = getCurScope();
-  bool IsFileScope = !CurContext->isFunctionOrMethod() &&
-                     !S->isInCFunctionScope() &&
-                     (!S || !S->isFunctionPrototypeScope());
+  if (HasConstexpr && !LiteralExpr->isTypeDependent() &&
+      !LiteralExpr->isValueDependent() && !literalType->isDependentType()) {
+    SmallVector<PartialDiagnosticAt, 4> Notes;
+    Expr::EvalResult Eval;
+    Eval.Diag = &Notes;
+    if (!LiteralExpr->EvaluateAsConstantExpr(
+            Eval, Context,
+            Expr::ConstantExprKind::CompoundLiteralInitializer) ||
+        Notes.size() > 0) {
+      SourceLocation DiagLoc = ConstexprLoc;
+      if (Notes.size() == 1 && Notes.front().second.getDiagID() ==
+                                   diag::note_invalid_subexpr_in_const_expr) {
+        DiagLoc = Notes.front().first;
+        Notes.clear();
+      }
+      Diag(DiagLoc, diag::err_compound_literal_initializer_not_constant)
+          << LiteralExpr->getSourceRange();
+      for (const PartialDiagnosticAt &Note : Notes)
+        Diag(Note.first, Note.second);
+      return ExprError();
+    }
+  }
 
   // In C, compound literals are l-values for some reason.
   // For GCC compatibility, in C++, file-scope array compound literals with
@@ -7527,33 +7709,52 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo,
           ? VK_PRValue
           : VK_LValue;
 
+  bool HasGlobalStorage =
+      IsFileScope || (getLangOpts().C23 && (HasStatic || HasThreadStorage));
+
+  if (HasThreadStorage && checkThreadLocalCompoundLiteral(
+                              *this, ThreadStorageClassLoc, literalType))
+    return ExprError();
+
   // C99 6.5.2.5
   //  "If the compound literal occurs outside the body of a function, the
   //  initializer list shall consist of constant expressions."
-  if (IsFileScope)
+  if (HasGlobalStorage || HasConstexpr)
     if (auto ILE = dyn_cast<InitListExpr>(LiteralExpr))
       for (unsigned i = 0, j = ILE->getNumInits(); i != j; i++) {
         Expr *Init = ILE->getInit(i);
-        if (!Init->isTypeDependent() && !Init->isValueDependent() &&
+        if (!HasConstexpr && !Init->isTypeDependent() &&
+            !Init->isValueDependent() &&
             !Init->isConstantInitializer(Context)) {
           Diag(Init->getExprLoc(), diag::err_init_element_not_constant)
               << Init->getSourceBitField();
           return ExprError();
         }
 
-        ILE->setInit(i, ConstantExpr::Create(Context, Init));
+        Expr::EvalResult Eval;
+        if (HasConstexpr &&
+            Init->EvaluateAsRValue(Eval, Context,
+                                   /*InConstantContext=*/true) &&
+            Eval.Val.hasValue())
+          ILE->setInit(i, ConstantExpr::Create(Context, Init, Eval.Val));
+        else
+          ILE->setInit(i, ConstantExpr::Create(Context, Init));
       }
 
-  auto *E = new (Context) CompoundLiteralExpr(LParenLoc, TInfo, literalType, VK,
-                                              LiteralExpr, IsFileScope);
-  if (IsFileScope) {
-    if (!LiteralExpr->isTypeDependent() &&
-        !LiteralExpr->isValueDependent() &&
-        !literalType->isDependentType()) // C99 6.5.2.5p3
+  auto *E = new (Context)
+      CompoundLiteralExpr(LParenLoc, TInfo, literalType, VK, LiteralExpr,
+                          IsFileScope, SC, TSC, ConstexprKind);
+
+  if (HasGlobalStorage && !HasConstexpr) {
+    if (!LiteralExpr->isTypeDependent() && !LiteralExpr->isValueDependent() &&
+        !literalType->isDependentType()) { // C99 6.5.2.5p3
       if (CheckForConstantInitializer(LiteralExpr))
         return ExprError();
-  } else if (literalType.getAddressSpace() != LangAS::opencl_private &&
-             literalType.getAddressSpace() != LangAS::Default) {
+    }
+  }
+
+  if (!IsFileScope && literalType.getAddressSpace() != LangAS::opencl_private &&
+      literalType.getAddressSpace() != LangAS::Default) {
     // Embedded-C extensions to C99 6.5.2.5:
     //   "If the compound literal occurs inside the body of a function, the
     //   type name shall not be qualified by an address-space qualifier."
@@ -7562,7 +7763,17 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo,
     return ExprError();
   }
 
-  if (!IsFileScope && !getLangOpts().CPlusPlus) {
+  if (HasStatic && !literalType.isConstQualified()) {
+    if (!IsPrototypeScope) {
+      DiagnoseStaticInInline(StorageClassLoc, getCurFunctionDecl());
+    } else if (IsFunctionDeclarationScope &&
+               DelayedDiagnostics.shouldDelayDiagnostics()) {
+      DelayedDiagnostics.add(
+          sema::DelayedDiagnostic::makeForbiddenStatic(StorageClassLoc));
+    }
+  }
+
+  if (!HasGlobalStorage && !IsPrototypeScope && !getLangOpts().CPlusPlus) {
     // Compound literals that have automatic storage duration are destroyed at
     // the end of the scope in C; in C++, they're just temporaries.
 
@@ -14843,78 +15054,6 @@ static QualType CheckIncrementDecrementOperand(Sema &S, Expr *Op,
   }
 }
 
-/// getPrimaryDecl - Helper function for CheckAddressOfOperand().
-/// This routine allows us to typecheck complex/recursive expressions
-/// where the declaration is needed for type checking. We only need to
-/// handle cases when the expression references a function designator
-/// or is an lvalue. Here are some examples:
-///  - &(x) => x
-///  - &*****f => f for f a function designator.
-///  - &s.xx => s
-///  - &s.zz[1].yy -> s, if zz is an array
-///  - *(x + 1) -> x, if x is an array
-///  - &"123"[2] -> 0
-///  - & __real__ x -> x
-///
-/// FIXME: We don't recurse to the RHS of a comma, nor handle pointers to
-/// members.
-static ValueDecl *getPrimaryDecl(Expr *E) {
-  switch (E->getStmtClass()) {
-  case Stmt::DeclRefExprClass:
-    return cast<DeclRefExpr>(E)->getDecl();
-  case Stmt::MemberExprClass:
-    // If this is an arrow operator, the address is an offset from
-    // the base's value, so the object the base refers to is
-    // irrelevant.
-    if (cast<MemberExpr>(E)->isArrow())
-      return nullptr;
-    // Otherwise, the expression refers to a part of the base
-    return getPrimaryDecl(cast<MemberExpr>(E)->getBase());
-  case Stmt::ArraySubscriptExprClass: {
-    // FIXME: This code shouldn't be necessary!  We should catch the implicit
-    // promotion of register arrays earlier.
-    Expr* Base = cast<ArraySubscriptExpr>(E)->getBase();
-    if (ImplicitCastExpr* ICE = dyn_cast<ImplicitCastExpr>(Base)) {
-      if (ICE->getSubExpr()->getType()->isArrayType())
-        return getPrimaryDecl(ICE->getSubExpr());
-    }
-    return nullptr;
-  }
-  case Stmt::UnaryOperatorClass: {
-    UnaryOperator *UO = cast<UnaryOperator>(E);
-
-    switch(UO->getOpcode()) {
-    case UO_Real:
-    case UO_Imag:
-    case UO_Extension:
-      return getPrimaryDecl(UO->getSubExpr());
-    default:
-      return nullptr;
-    }
-  }
-  case Stmt::ParenExprClass:
-    return getPrimaryDecl(cast<ParenExpr>(E)->getSubExpr());
-  case Stmt::ImplicitCastExprClass:
-    // If the result of an implicit cast is an l-value, we care about
-    // the sub-expression; otherwise, the result here doesn't matter.
-    return getPrimaryDecl(cast<ImplicitCastExpr>(E)->getSubExpr());
-  case Stmt::CXXUuidofExprClass:
-    return cast<CXXUuidofExpr>(E)->getGuidDecl();
-  default:
-    return nullptr;
-  }
-}
-
-namespace {
-enum {
-  AO_Bit_Field = 0,
-  AO_Vector_Element = 1,
-  AO_Property_Expansion = 2,
-  AO_Register_Variable = 3,
-  AO_Matrix_Element = 4,
-  AO_No_Error = 5
-};
-}
 /// Diagnose invalid operand for address of operations.
 ///
 /// \param Type The type of operand which cannot have its address taken.
@@ -15018,7 +15157,8 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) {
     // Technically, there should be a check for array subscript
     // expressions here, but the result of one is always an lvalue anyway.
   }
-  ValueDecl *dcl = getPrimaryDecl(op);
+  PrimaryObject Primary = getPrimaryObject(op);
+  ValueDecl *dcl = Primary.dyn_cast<ValueDecl *>();
 
   if (auto *FD = dyn_cast_or_null<FunctionDecl>(dcl))
     if (!checkAddressOfFunctionIsAvailable(FD, /*Complain=*/true,
@@ -15169,6 +15309,10 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) {
                     NonTypeTemplateParmDecl, BindingDecl, MSGuidDecl,
                     UnnamedGlobalConstantDecl>(dcl))
       llvm_unreachable("Unknown/unexpected decl type");
+  } else if (!getLangOpts().CPlusPlus) {
+    if (auto *CLE = Primary.dyn_cast<CompoundLiteralExpr *>();
+        CLE && CLE->getStorageClass() == SC_Register)
+      AddressOfError = AO_Register_Compound_Literal;
   }
 
   if (AddressOfError != AO_No_Error) {
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 09d9f1eabd058..7f0a691a1cb28 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -194,18 +194,15 @@ static void updateGNUCompoundLiteralRValue(Expr *E) {
   }
 }
 
-static bool initializingConstexprVariable(const InitializedEntity &Entity) {
-  Decl *D = Entity.getDecl();
-  const InitializedEntity *Parent = &Entity;
-
-  while (Parent) {
-    D = Parent->getDecl();
-    Parent = Parent->getParent();
+static bool initializingConstexprObject(const InitializedEntity &Entity) {
+  for (const InitializedEntity *Parent = &Entity; Parent;
+       Parent = Parent->getParent()) {
+    if (Parent->isConstexprCompoundLiteral())
+      return true;
+    if (const auto *VD = dyn_cast_if_present<VarDecl>(Parent->getDecl());
+        VD && VD->isConstexpr())
+      return true;
   }
-
-  if (const auto *VD = dyn_cast_if_present<VarDecl>(D); VD && VD->isConstexpr())
-    return true;
-
   return false;
 }
 
@@ -1311,6 +1308,7 @@ static void warnBracedScalarInit(Sema &S, const InitializedEntity &Entity,
   case InitializedEntity::EK_New:
   case InitializedEntity::EK_Temporary:
   case InitializedEntity::EK_CompoundLiteralInit:
+  case InitializedEntity::EK_ConstexprCompoundLiteralInit:
     // No warning, braces are part of the syntax of the underlying construct.
     break;
 
@@ -1436,7 +1434,8 @@ void InitListChecker::CheckListElementTypes(const InitializedEntity &Entity,
     // parts.
     CheckComplexType(Entity, IList, DeclType, Index,
                      StructuredList, StructuredIndex);
-  } else if (DeclType->isScalarType()) {
+  } else if (DeclType->isScalarType() ||
+             (!SemaRef.getLangOpts().CPlusPlus && DeclType->isAtomicType())) {
     CheckScalarType(Entity, IList, DeclType, Index,
                     StructuredList, StructuredIndex);
   } else if (DeclType->isVectorType()) {
@@ -1626,7 +1625,7 @@ void InitListChecker::CheckSubElementType(const InitializedEntity &Entity,
       if (!VerifyOnly)
         CheckStringInit(expr, ElemType, arrayType, SemaRef, Entity,
                         SemaRef.getLangOpts().C23 &&
-                            initializingConstexprVariable(Entity));
+                            initializingConstexprObject(Entity));
       if (StructuredList)
         UpdateStructuredListElement(StructuredList, StructuredIndex, expr);
       ++Index;
@@ -2177,7 +2176,7 @@ void InitListChecker::CheckArrayType(const InitializedEntity &Entity,
       if (!VerifyOnly)
         CheckStringInit(
             IList->getInit(Index), DeclType, arrayType, SemaRef, Entity,
-            SemaRef.getLangOpts().C23 && initializingConstexprVariable(Entity));
+            SemaRef.getLangOpts().C23 && initializingConstexprObject(Entity));
       if (StructuredList) {
         UpdateStructuredListElement(StructuredList, StructuredIndex,
                                     IList->getInit(Index));
@@ -3809,6 +3808,7 @@ DeclarationName InitializedEntity::getName() const {
   case EK_BlockElement:
   case EK_LambdaToBlockConversionBlockElement:
   case EK_CompoundLiteralInit:
+  case EK_ConstexprCompoundLiteralInit:
   case EK_RelatedResult:
     return DeclarationName();
   }
@@ -3844,6 +3844,7 @@ ValueDecl *InitializedEntity::getDecl() const {
   case EK_LambdaToBlockConversionBlockElement:
   case EK_LambdaCapture:
   case EK_CompoundLiteralInit:
+  case EK_ConstexprCompoundLiteralInit:
   case EK_RelatedResult:
     return nullptr;
   }
@@ -3868,6 +3869,7 @@ bool InitializedEntity::allowsNRVO() const {
   case EK_New:
   case EK_Temporary:
   case EK_CompoundLiteralInit:
+  case EK_ConstexprCompoundLiteralInit:
   case EK_Base:
   case EK_Delegating:
   case EK_ArrayElement:
@@ -3907,6 +3909,9 @@ unsigned InitializedEntity::dumpImpl(raw_ostream &OS) const {
   case EK_New: OS << "New"; break;
   case EK_Temporary: OS << "Temporary"; break;
   case EK_CompoundLiteralInit: OS << "CompoundLiteral";break;
+  case EK_ConstexprCompoundLiteralInit:
+    OS << "ConstexprCompoundLiteral";
+    break;
   case EK_RelatedResult: OS << "RelatedResult"; break;
   case EK_Base: OS << "Base"; break;
   case EK_Delegating: OS << "Delegating"; break;
@@ -4930,7 +4935,8 @@ static void TryReferenceListInitialization(Sema &S,
     return;
   }
   // Can't reference initialize a compound literal.
-  if (Entity.getKind() == InitializedEntity::EK_CompoundLiteralInit) {
+  if (Entity.getKind() == InitializedEntity::EK_CompoundLiteralInit ||
+      Entity.getKind() == InitializedEntity::EK_ConstexprCompoundLiteralInit) {
     Sequence.SetFailed(InitializationSequence::FK_ReferenceBindingToInitList);
     return;
   }
@@ -7196,6 +7202,7 @@ static AssignmentAction getAssignmentAction(const InitializedEntity &Entity,
   case InitializedEntity::EK_LambdaToBlockConversionBlockElement:
   case InitializedEntity::EK_LambdaCapture:
   case InitializedEntity::EK_CompoundLiteralInit:
+  case InitializedEntity::EK_ConstexprCompoundLiteralInit:
     return AssignmentAction::Initializing;
   }
 
@@ -7223,6 +7230,7 @@ static bool shouldBindAsTemporary(const InitializedEntity &Entity) {
   case InitializedEntity::EK_LambdaToBlockConversionBlockElement:
   case InitializedEntity::EK_LambdaCapture:
   case InitializedEntity::EK_CompoundLiteralInit:
+  case InitializedEntity::EK_ConstexprCompoundLiteralInit:
   case InitializedEntity::EK_TemplateParameter:
     return false;
 
@@ -7265,6 +7273,7 @@ static bool shouldDestroyEntity(const InitializedEntity &Entity) {
     case InitializedEntity::EK_ArrayElement:
     case InitializedEntity::EK_Exception:
     case InitializedEntity::EK_CompoundLiteralInit:
+    case InitializedEntity::EK_ConstexprCompoundLiteralInit:
     case InitializedEntity::EK_RelatedResult:
       return true;
   }
@@ -7306,6 +7315,7 @@ static SourceLocation getInitializationLoc(const InitializedEntity &Entity,
   case InitializedEntity::EK_BlockElement:
   case InitializedEntity::EK_LambdaToBlockConversionBlockElement:
   case InitializedEntity::EK_CompoundLiteralInit:
+  case InitializedEntity::EK_ConstexprCompoundLiteralInit:
   case InitializedEntity::EK_RelatedResult:
     return Initializer->getBeginLoc();
   }
@@ -7562,6 +7572,7 @@ static bool isExplicitTemporary(const InitializedEntity &Entity,
   switch (Entity.getKind()) {
   case InitializedEntity::EK_Temporary:
   case InitializedEntity::EK_CompoundLiteralInit:
+  case InitializedEntity::EK_ConstexprCompoundLiteralInit:
   case InitializedEntity::EK_RelatedResult:
     break;
   default:
@@ -8579,7 +8590,7 @@ ExprResult InitializationSequence::Perform(Sema &S,
         return ExprError();
       CurInit = CurInitExprRes;
 
-      if (S.getLangOpts().C23 && initializingConstexprVariable(Entity)) {
+      if (S.getLangOpts().C23 && initializingConstexprObject(Entity)) {
         CheckC23ConstexprInitConversion(S, SourceType, Entity.getType(),
                                         CurInit.get());
 
@@ -8623,7 +8634,7 @@ ExprResult InitializationSequence::Perform(Sema &S,
       CheckStringInit(CurInit.get(), UpdateType ? *ResultType : Ty,
                       S.Context.getAsArrayType(Ty), S, Entity,
                       S.getLangOpts().C23 &&
-                          initializingConstexprVariable(Entity));
+                          initializingConstexprObject(Entity));
       break;
     }
 
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 0725664a4e050..239700db3d0eb 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -3103,12 +3103,13 @@ class TreeTransform {
   ///
   /// By default, performs semantic analysis to build the new expression.
   /// Subclasses may override this routine to provide different behavior.
-  ExprResult RebuildCompoundLiteralExpr(SourceLocation LParenLoc,
-                                              TypeSourceInfo *TInfo,
-                                              SourceLocation RParenLoc,
-                                              Expr *Init) {
-    return getSema().BuildCompoundLiteralExpr(LParenLoc, TInfo, RParenLoc,
-                                              Init);
+  ExprResult RebuildCompoundLiteralExpr(
+      SourceLocation LParenLoc, TypeSourceInfo *TInfo, SourceLocation RParenLoc,
+      Expr *Init, StorageClass SC = SC_None,
+      ThreadStorageClassSpecifier TSC = TSCS_unspecified,
+      ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified) {
+    return getSema().BuildCompoundLiteralExpr(LParenLoc, TInfo, RParenLoc, Init,
+                                              SC, TSC, ConstexprKind);
   }
 
   /// Build a new extended vector or matrix element access expression.
@@ -14325,7 +14326,10 @@ TreeTransform<Derived>::TransformCompoundLiteralExpr(CompoundLiteralExpr *E) {
 
   return getDerived().RebuildCompoundLiteralExpr(
       E->getLParenLoc(), NewT,
-      /*FIXME:*/ E->getInitializer()->getEndLoc(), Init.get());
+      /*FIXME:*/ E->getInitializer()->getEndLoc(), Init.get(),
+      E->getStorageClass(), E->getTSCSpec(),
+      E->isConstexpr() ? ConstexprSpecKind::Constexpr
+                       : ConstexprSpecKind::Unspecified);
 }
 
 template<typename Derived>
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 97f1857e200ca..2e8c055a8ac6a 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1239,6 +1239,9 @@ void ASTStmtReader::VisitCompoundLiteralExpr(CompoundLiteralExpr *E) {
   E->setTypeSourceInfo(readTypeSourceInfo());
   E->setInitializer(Record.readSubExpr());
   E->setFileScope(Record.readInt());
+  E->setStorageClass(static_cast<StorageClass>(Record.readInt()));
+  E->setTSCSpec(static_cast<ThreadStorageClassSpecifier>(Record.readInt()));
+  E->setConstexpr(Record.readInt());
 }
 
 void ASTStmtReader::VisitExtVectorElementExpr(ExtVectorElementExpr *E) {
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 70477f4cf4001..f0ff9eb1ed80f 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1206,6 +1206,9 @@ void ASTStmtWriter::VisitCompoundLiteralExpr(CompoundLiteralExpr *E) {
   Record.AddTypeSourceInfo(E->getTypeSourceInfo());
   Record.AddStmt(E->getInitializer());
   Record.push_back(E->isFileScope());
+  Record.push_back(E->getStorageClass());
+  Record.push_back(E->getTSCSpec());
+  Record.push_back(E->isConstexpr());
   Code = serialization::EXPR_COMPOUND_LITERAL;
 }
 
diff --git a/clang/test/AST/c23-compound-literal-print.c b/clang/test/AST/c23-compound-literal-print.c
new file mode 100644
index 0000000000000..c8e4fb7d1e8e3
--- /dev/null
+++ b/clang/test/AST/c23-compound-literal-print.c
@@ -0,0 +1,33 @@
+// RUN: %clang_cc1 -std=c23 -ast-print %s | FileCheck %s
+// RUN: %clang_cc1 -std=c23 -emit-pch -o %t %s
+// RUN: %clang_cc1 -std=c23 -include-pch %t -ast-print -x c /dev/null | FileCheck %s
+
+// CHECK-LABEL: int f1(void)
+// CHECK: return (constexpr int){1} + (static _Thread_local int){2} + (register int){3} + (constexpr const int){4};
+int f1(void) {
+  return (constexpr int){1} + (_Thread_local static int){2} + (register int){3} +
+         (constexpr const int){4};
+}
+
+// CHECK-LABEL: int f2(void)
+// CHECK: return (int[3]){1, 2, 3}[0] + (constexpr int[3]){1, 2, 3}[0];
+int f2(void) {
+  return (int[3]){1, 2, 3}[0] + (constexpr int[3]){1, 2, 3}[0];
+}
+
+typedef const int T;
+
+// CHECK-LABEL: int f3(void)
+// CHECK: return (constexpr T){1} + (constexpr int[]){1}[0] + (constexpr const int[]){1}[0] + (constexpr T[]){1}[0];
+int f3(void) {
+  return (constexpr T){1} + (constexpr int[]){1}[0] +
+         (constexpr const int[]){1}[0] + (constexpr T[]){1}[0];
+}
+
+// CHECK-LABEL: void f4(void)
+// CHECK: typedef typeof (*(int (*)[(static _Thread_local int){1}])0) T1;
+// CHECK: typedef typeof (*(int (*)[(register int){1}])0) T2;
+void f4(void) {
+  typedef typeof(*(int (*)[(static thread_local int){1}])0) T1;
+  typedef typeof(*(int (*)[(register int){1}])0) T2;
+}
diff --git a/clang/test/C/drs/dr3xx.c b/clang/test/C/drs/dr3xx.c
index 0f67470b013a5..e5d85dca78122 100644
--- a/clang/test/C/drs/dr3xx.c
+++ b/clang/test/C/drs/dr3xx.c
@@ -226,6 +226,7 @@ extern int dr339_v;
 void *dr339 = &(int (*)[dr339_v]){ 0 }; /* c89only-warning {{variable length arrays are a C99 feature}}
                                            c99andup-warning {{variable length array used}}
                                            c89only-warning {{compound literals are a C99-specific feature}}
+                                           c23andup-error {{variably modified type declaration not allowed at file scope}}
                                          */
 
 /* WG14 DR340: yes
diff --git a/clang/test/CodeGen/c23-compound-literal.c b/clang/test/CodeGen/c23-compound-literal.c
new file mode 100644
index 0000000000000..d255e72a23d1b
--- /dev/null
+++ b/clang/test/CodeGen/c23-compound-literal.c
@@ -0,0 +1,275 @@
+// RUN: %clang_cc1 -std=c23 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+struct S { int a; int b; };
+
+// CHECK-LABEL: define dso_local i32 @f1()
+// CHECK: %[[VALUE:.*]] = load i32, ptr @.compoundliteral, align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f1(void) {
+  return (static int){42};
+}
+
+// CHECK-LABEL: define dso_local i32 @f2()
+// CHECK: %.compoundliteral = alloca i32
+// CHECK: store i32 7, ptr %.compoundliteral
+// CHECK-NEXT: %[[VALUE:.*]] = load i32, ptr %.compoundliteral, align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f2(void) {
+  return (constexpr int){7};
+}
+
+// CHECK-LABEL: define dso_local ptr @f3()
+// CHECK: ret ptr @.compoundliteral.1
+const int *f3(void) {
+  return &(static constexpr int){15};
+}
+
+// CHECK-LABEL: define dso_local i32 @f4()
+// CHECK: [[ADDR:%.*]] = call align 4 ptr @llvm.threadlocal.address.p0(ptr align 4 @.compoundliteral.2)
+// CHECK-NEXT: %[[VALUE:.*]] = load i32, ptr [[ADDR]], align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f4(void) {
+  return (thread_local static int){2};
+}
+
+// CHECK-LABEL: define dso_local ptr @f5()
+// CHECK: [[ADDR:%.*]] = call align 4 ptr @llvm.threadlocal.address.p0(ptr align 4 @.compoundliteral.3)
+// CHECK-NEXT: ret ptr [[ADDR]]
+int *f5(void) {
+  return &(thread_local static int){2};
+}
+
+// CHECK-LABEL: define dso_local i32 @f6()
+// CHECK: store ptr @.compoundliteral.4, ptr %a, align 8
+// CHECK-NEXT: %[[BASE:.*]] = load ptr, ptr %a, align 8
+// CHECK-NEXT: %[[MEMBER:.*]] = getelementptr inbounds nuw %struct.S, ptr %[[BASE]], i32 0, i32 0
+// CHECK-NEXT: %[[VALUE:.*]] = load i32, ptr %[[MEMBER]], align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f6(void) {
+  struct S *a = &(static struct S){1, 2};
+  return a->a;
+}
+
+// CHECK-LABEL: define dso_local i64 @f7()
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %retval, ptr align 4 @.compoundliteral.5, i64 8, i1 true)
+// CHECK-NEXT: %[[VALUE:.*]] = load i64, ptr %retval, align 4
+// CHECK-NEXT: ret i64 %[[VALUE]]
+struct S f7(void) {
+  return (static volatile struct S){9, 10};
+}
+
+// CHECK-LABEL: define dso_local i64 @f8()
+// CHECK: [[ADDR:%.*]] = call align 4 ptr @llvm.threadlocal.address.p0(ptr align 4 @.compoundliteral.6)
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %retval, ptr align 4 [[ADDR]], i64 8, i1 true)
+// CHECK-NEXT: %[[VALUE:.*]] = load i64, ptr %retval, align 4
+// CHECK-NEXT: ret i64 %[[VALUE]]
+struct S f8(void) {
+  return (thread_local static volatile struct S){11, 12};
+}
+
+// CHECK-LABEL: define dso_local i64 @f9()
+// CHECK: %.compoundliteral = alloca %struct.S
+// CHECK: store i32 13
+// CHECK: store i32 14
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %retval, ptr align 4 %.compoundliteral, i64 8, i1 true)
+// CHECK-NEXT: %[[VALUE:.*]] = load i64, ptr %retval, align 4
+// CHECK-NEXT: ret i64 %[[VALUE]]
+struct S f9(void) {
+  return (register volatile struct S){13, 14};
+}
+
+// CHECK-LABEL: define dso_local i32 @f10()
+// CHECK: %.compoundliteral = alloca %struct.S
+// CHECK: store i32 3
+// CHECK: store i32 4
+// CHECK: store ptr %.compoundliteral, ptr %a, align 8
+// CHECK-NEXT: %[[BASE:.*]] = load ptr, ptr %a, align 8
+// CHECK-NEXT: %[[MEMBER:.*]] = getelementptr inbounds nuw %struct.S, ptr %[[BASE]], i32 0, i32 0
+// CHECK-NEXT: %[[VALUE:.*]] = load i32, ptr %[[MEMBER]], align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f10(void) {
+  const struct S *a = &(constexpr struct S){3, 4};
+  return a->a;
+}
+
+// CHECK-LABEL: define dso_local i32 @f11()
+// CHECK: %[[VALUE:.*]] = load i32, ptr @.compoundliteral.7, align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f11(void) {
+  return (static int[]){5, 6, 7}[0];
+}
+
+// CHECK-LABEL: define dso_local i32 @f12()
+// CHECK: %.compoundliteral = alloca [3 x i32]
+// CHECK: store i32 8, ptr %.compoundliteral
+// CHECK: store i32 9
+// CHECK: store i32 10
+// CHECK: %[[ELEMENT:.*]] = getelementptr inbounds [3 x i32], ptr %.compoundliteral, i64 0, i64 0
+// CHECK-NEXT: %[[VALUE:.*]] = load i32, ptr %[[ELEMENT]], align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f12(void) {
+  return (constexpr int[]){8, 9, 10}[0];
+}
+
+// CHECK-LABEL: define dso_local i32 @f13()
+// CHECK: %.compoundliteral = alloca i32
+// CHECK: store i32 99, ptr %.compoundliteral
+// CHECK-NEXT: %[[VALUE:.*]] = load i32, ptr %.compoundliteral, align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f13(void) {
+  return (register constexpr int){99};
+}
+
+// CHECK-LABEL: define dso_local i32 @f14()
+// CHECK: %a = alloca i32
+// CHECK: store i32 16, ptr %a
+// CHECK-NEXT: %[[VALUE:.*]] = load atomic i32, ptr %a seq_cst, align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f14(void) {
+  register _Atomic int a = 16;
+  return a;
+}
+
+// CHECK-LABEL: define dso_local i32 @f15()
+// CHECK: %.compoundliteral = alloca i32
+// CHECK: store i32 16, ptr %.compoundliteral
+// CHECK-NEXT: %[[VALUE:.*]] = load atomic i32, ptr %.compoundliteral seq_cst, align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f15(void) {
+  return (register _Atomic int){16};
+}
+
+// CHECK-LABEL: define dso_local i64 @f16()
+// CHECK: %a = alloca %struct.S, align 8
+// CHECK: store i32 17
+// CHECK: store i32 18
+// CHECK: %[[VALUE:.*]] = load atomic i64, ptr %a seq_cst, align 8
+// CHECK-NEXT: store i64 %[[VALUE]], ptr %retval, align 4
+// CHECK-NEXT: %[[RESULT:.*]] = load i64, ptr %retval, align 4
+// CHECK-NEXT: ret i64 %[[RESULT]]
+struct S f16(void) {
+  register _Atomic(struct S) a = {(struct S){17, 18}};
+  return a;
+}
+
+// CHECK-LABEL: define dso_local i64 @f17()
+// CHECK: %.compoundliteral = alloca %struct.S, align 8
+// CHECK: store i32 17
+// CHECK: store i32 18
+// CHECK: %[[VALUE:.*]] = load atomic i64, ptr %.compoundliteral seq_cst, align 8
+// CHECK-NEXT: store i64 %[[VALUE]], ptr %retval, align 4
+// CHECK-NEXT: %[[RESULT:.*]] = load i64, ptr %retval, align 4
+// CHECK-NEXT: ret i64 %[[RESULT]]
+struct S f17(void) {
+  return (register _Atomic(struct S)){(struct S){17, 18}};
+}
+
+// CHECK-LABEL: define dso_local i32 @f18()
+// CHECK: %.compoundliteral = alloca i32
+// CHECK: store i32 5, ptr %.compoundliteral
+// CHECK-NEXT: %[[VALUE:.*]] = load i32, ptr %.compoundliteral, align 4
+// CHECK-NEXT: ret i32 %[[VALUE]]
+int f18(void) {
+  return (int){5};
+}
+
+// CHECK-LABEL: define dso_local i32 @f19(i32 noundef %a)
+// CHECK: %[[OLD:.*]] = load i32, ptr %a.addr, align 4
+// CHECK-NEXT: %[[INC:.*]] = add nsw i32 %[[OLD]], 1
+// CHECK-NEXT: store i32 %[[INC]], ptr %a.addr, align 4
+// CHECK: load ptr, ptr @.compoundliteral.8, align 8
+// CHECK: %[[RESULT:.*]] = load i32, ptr %a.addr, align 4
+// CHECK-NEXT: ret i32 %[[RESULT]]
+int f19(int a) {
+  (void)(static int (*)[a++]){0};
+  return a;
+}
+
+// CHECK-LABEL: define dso_local i32 @f20(i32 noundef %a)
+// CHECK: %[[OLD:.*]] = load i32, ptr %a.addr, align 4
+// CHECK-NEXT: %[[INC:.*]] = add nsw i32 %[[OLD]], 1
+// CHECK-NEXT: store i32 %[[INC]], ptr %a.addr, align 4
+// CHECK: %[[ADDR:.*]] = call align 8 ptr @llvm.threadlocal.address.p0(ptr align 8 @.compoundliteral.9)
+// CHECK-NEXT: load ptr, ptr %[[ADDR]], align 8
+// CHECK: %[[RESULT:.*]] = load i32, ptr %a.addr, align 4
+// CHECK-NEXT: ret i32 %[[RESULT]]
+int f20(int a) {
+  (void)(thread_local static int (*)[a++]){0};
+  return a;
+}
+
+int f21(void);
+
+// CHECK-LABEL: define dso_local i32 @f22(
+// CHECK: %.compoundliteral = alloca i32
+// CHECK: [[CALL:%call]] = call i32 @f21()
+// CHECK-NEXT: store i32 [[CALL]], ptr %.compoundliteral
+// CHECK-NEXT: %[[BOUND:.*]] = load i32, ptr %.compoundliteral, align 4
+// CHECK-NEXT: zext i32 %[[BOUND]] to i64
+// CHECK: ret i32
+// CHECK-NEXT: }
+int f22(int a[(int){f21()}]) {
+  return a[0];
+}
+
+// CHECK-LABEL: define dso_local i32 @f23(
+// CHECK: %.compoundliteral = alloca i32
+// CHECK: [[CALL:%call]] = call i32 @f21()
+// CHECK-NEXT: store i32 [[CALL]], ptr %.compoundliteral
+// CHECK-NEXT: %[[BOUND:.*]] = load i32, ptr %.compoundliteral, align 4
+// CHECK-NEXT: zext i32 %[[BOUND]] to i64
+// CHECK: ret i32
+// CHECK-NEXT: }
+int f23(int a[(register int){f21()}]) {
+  return a[0];
+}
+
+// CHECK-LABEL: define dso_local i32 @f24(
+// CHECK: %[[BOUND:.*]] = load volatile i32, ptr @.compoundliteral.10, align 4
+// CHECK-NEXT: zext i32 %[[BOUND]] to i64
+// CHECK: ret i32
+// CHECK-NEXT: }
+int f24(int a[(static volatile int){13}]) {
+  return a[0];
+}
+
+// CHECK-LABEL: define dso_local i32 @f25(
+// CHECK: [[ADDR:%.*]] = call align 4 ptr @llvm.threadlocal.address.p0(ptr align 4 @.compoundliteral.11)
+// CHECK-NEXT: %[[BOUND:.*]] = load volatile i32, ptr [[ADDR]], align 4
+// CHECK-NEXT: zext i32 %[[BOUND]] to i64
+// CHECK: ret i32
+// CHECK-NEXT: }
+int f25(int a[(static thread_local volatile int){14}]) {
+  return a[0];
+}
+
+// CHECK-LABEL: define dso_local i32 @f26(
+// CHECK-NOT: call i32 @f21()
+// CHECK-NOT: load volatile i32
+// CHECK: ret i32 0
+int f26(int b(int a[((int){f21()} + (static volatile int){15})])) {
+  return 0;
+}
+
+// CHECK-LABEL: define dso_local i32 @f27(
+// CHECK: %.compoundliteral = alloca i32
+// CHECK: [[CALL:%call]] = call i32 @f21()
+// CHECK-NEXT: store i32 [[CALL]], ptr %.compoundliteral
+// CHECK-NEXT: load i32, ptr %.compoundliteral, align 4
+// CHECK-NEXT: ret i32 0
+int f27(int (*a(void))[((void)(int){f21()}, 1)]) {
+  return 0;
+}
+
+int f28(const int *);
+
+// CHECK-LABEL: define dso_local i32 @f29(
+// CHECK: %.compoundliteral = alloca i32
+// CHECK: store i32 3, ptr %.compoundliteral
+// CHECK-NEXT: %[[CALL:.*]] = call i32 @f28(ptr noundef %.compoundliteral)
+// CHECK-NEXT: zext i32 %[[CALL]] to i64
+// CHECK: ret i32
+// CHECK-NEXT: }
+int f29(int a[f28(&(constexpr int){3})]) {
+  return a[0];
+}
diff --git a/clang/test/CodeGenObjC/c23-compound-literal.m b/clang/test/CodeGenObjC/c23-compound-literal.m
new file mode 100644
index 0000000000000..245284cb23017
--- /dev/null
+++ b/clang/test/CodeGenObjC/c23-compound-literal.m
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -std=c23 -triple x86_64-apple-macosx10.15 -emit-llvm -fobjc-arc -disable-llvm-passes -o - %s | FileCheck %s
+
+typedef struct {
+  id a;
+} S;
+int f1(const S *);
+
+// CHECK-LABEL: define i32 @f2(
+// CHECK: %.compoundliteral = alloca %struct.S, align 8
+// CHECK: store ptr null, ptr %{{.*}}, align 8
+// CHECK: %[[CALL:.*]] = call i32 @f1(ptr noundef %.compoundliteral)
+// CHECK-NEXT: zext i32 %[[CALL]] to i64
+// CHECK: %[[RESULT:.*]] = load i32, ptr %{{.*}}, align 4
+// CHECK-NEXT: call void @__destructor_8_s0(ptr %.compoundliteral)
+// CHECK-NEXT: ret i32 %[[RESULT]]
+// CHECK-NEXT: }
+int f2(int a[f1(&(constexpr S){.a = 0})]) {
+  return a[0];
+}
diff --git a/clang/test/Parser/expressions.c b/clang/test/Parser/expressions.c
index 82556c091206f..2921158dd5f35 100644
--- a/clang/test/Parser/expressions.c
+++ b/clang/test/Parser/expressions.c
@@ -1,4 +1,6 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -verify=expected,c17 %s
+// RUN: %clang_cc1 -std=c23 -Wpre-c23-compat -fsyntax-only -verify=expected,c23 %s
+// RUN: %clang_cc1 -std=c23 -triple x86_64-apple-darwin10 -fsyntax-only -verify=expected,notls %s
 
 void test1(void) {
   if (sizeof (int){ 1}) {}   // sizeof compound literal
@@ -73,3 +75,17 @@ void test8(void) {
   callee(foobar,   // expected-error {{use of undeclared identifier 'foobar'}}
          fizbin);  // expected-error {{use of undeclared identifier 'fizbin'}}
 }
+
+void test9(void) {
+  (void)(static int){1}; // c17-error {{expected expression}} \
+                         // c23-warning {{compound literal storage-class specifiers are incompatible with C standards before C23}}
+
+  (void)(register int){2}; // c17-error {{expected expression}} \
+                           // c23-warning {{compound literal storage-class specifiers are incompatible with C standards before C23}}
+
+  (void)(constexpr int){3}; // c17-error {{use of undeclared identifier 'constexpr'}} \
+                            // c23-warning {{compound literal storage-class specifiers are incompatible with C standards before C23}}
+
+  (void)(_Thread_local static int){4}; // c17-error {{expected expression}} notls-error {{thread-local storage is not supported for the current target}} \
+                                       // c23-warning {{compound literal storage-class specifiers are incompatible with C standards before C23}}
+}
diff --git a/clang/test/Sema/c23-compound-literal.c b/clang/test/Sema/c23-compound-literal.c
new file mode 100644
index 0000000000000..44759108bcd3a
--- /dev/null
+++ b/clang/test/Sema/c23-compound-literal.c
@@ -0,0 +1,343 @@
+// RUN: %clang_cc1 -std=c23 -triple x86_64-unknown-linux-gnu -verify -fsyntax-only %s
+// RUN: %clang_cc1 -std=c23 -triple x86_64-scei-ps4 -verify=expected,ps4 -fsyntax-only %s
+
+#define M static
+struct S { int a; char b; };
+int f1(void);
+
+void test1(void) {
+  (void)(constexpr int){1};
+  (void)&(static int){42};
+  (void)(register int){0};
+  (void)(static thread_local int){1};
+  (void)(constexpr struct S){1, 'a'};
+  (void)(static struct S){2, 'b'};
+  (void)(register struct S){3, 'c'};
+}
+
+void test2(void) {
+  (void)(static constexpr int){1};
+  (void)(constexpr static int){2};
+  (void)(static thread_local int){3};
+  (void)(thread_local static int){4};
+  (void)(constexpr register int){5};
+  (void)(constexpr static thread_local int){6}; // expected-error {{cannot combine with previous '_Thread_local' declaration specifier}}
+}
+
+void test3(void) {
+  (void)(static static int){1};             // expected-warning {{duplicate 'static' declaration specifier}}
+  (void)(constexpr constexpr int){2};       // expected-warning {{duplicate 'constexpr' declaration specifier}}
+  (void)(register register int){3};         // expected-warning {{duplicate 'register' declaration specifier}}
+  (void)(thread_local thread_local int){4}; // expected-warning {{duplicate '_Thread_local' declaration specifier}} expected-error {{compound literal with 'thread_local' storage duration at block scope must also specify 'static'}}
+}
+
+void test4(void) {
+  (void)(register static int){1};       // expected-error {{cannot combine with previous 'register' declaration specifier}}
+  (void)(register thread_local int){2}; // expected-error {{cannot combine with previous 'register' declaration specifier}}
+  (void)(register constexpr int){3};
+  (void)(static register int){4};       // expected-error {{cannot combine with previous 'static' declaration specifier}}
+  (void)(register _Atomic int){5};
+}
+
+void test5(void) {
+  (void)&(thread_local int){1}; // expected-error {{compound literal with 'thread_local' storage duration at block scope must also specify 'static'}}
+}
+
+int *a1 = &(register int){42}; // expected-error {{file-scope compound literal specifies 'register'}}
+
+void test6(void) {
+  (void)(constexpr volatile int){1}; // expected-error {{constexpr compound literal cannot have type 'const volatile int'}}
+  (void)(constexpr _Atomic int){1};  // expected-error {{constexpr compound literal cannot have type 'const _Atomic(int)'}}
+
+  int c;
+  (void)(constexpr int[c]){0}; // expected-error {{constexpr compound literal cannot have type 'const int[c]'}}
+}
+
+void test7(void) {
+  (void)(constexpr int){f1()};          // expected-error {{initializer of compound literal must be a constant expression}}
+  (void)(static int){f1()};             // expected-error {{initializer element is not a compile-time constant}}
+  (void)(register constexpr int){f1()}; // expected-error {{initializer of compound literal must be a constant expression}}
+  (void)(constexpr int){1 / 0};
+  // expected-error at -1 {{initializer of compound literal must be a constant expression}}
+  // expected-note at -2 {{division by zero}}
+  // expected-warning at -3 {{division by zero is undefined}}
+}
+
+const int a2 = 1;
+const double a3 = 1.0;
+
+void test8(void) {
+  (void)(constexpr int){a2};    // expected-error {{initializer of compound literal must be a constant expression}}
+  (void)(constexpr double){a3}; // expected-error {{initializer of compound literal must be a constant expression}}
+
+  const int a = 2;
+  (void)(constexpr int){a}; // expected-error {{initializer of compound literal must be a constant expression}}
+
+  struct S1 {
+    int a;
+  };
+
+  (void)(constexpr int){(constexpr int){1}};
+  (void)(constexpr struct S1){.a = (constexpr int){1}};
+  (void)(constexpr int){(int){1}};
+  (void)(constexpr int){(static int){1}};
+}
+
+void test9(void) {
+  int *a = &(register int){1};   // expected-error {{address of register compound literal requested}}
+  int *b = (register int[1]){1}; // expected-error {{address of register compound literal requested}}
+  struct S2 { int a; };
+  int *c = &(register struct S2){1}.a;                   // expected-error {{address of register compound literal requested}}
+  double *d = &__real__ (register _Complex double){1};   // expected-error {{address of register compound literal requested}}
+  double *e = &__imag__ (register _Complex double){1};   // expected-error {{address of register compound literal requested}}
+  int *f = &_Generic(0, int: (register int){1});         // expected-error {{address of register compound literal requested}}
+  int *g = _Generic(0, int: (register int[1]){1});       // expected-error {{address of register compound literal requested}}
+  int *h = &_Generic(0, int: (register struct S2){1}).a; // expected-error {{address of register compound literal requested}}
+  int *i = &__extension__ (register int){1};             // expected-error {{address of register compound literal requested}}
+}
+
+int a5[1];
+struct S3 {
+  int *a;
+};
+struct S4 {
+  int a[1];
+};
+
+int *test10(void) {
+  return &((register struct S3){a5}.a[0]);
+}
+
+int *test11(void) {
+  return &(register struct S4){{1}}.a[0]; // expected-error {{address of register compound literal requested}}
+}
+
+enum { E1 = (constexpr int){42} };
+enum { E2 = (constexpr int){} };
+static int a6[(constexpr int){10}];
+static int a7[(constexpr int){} + 1];
+static const int *a8 = &(constexpr int){5};
+static int a9 = (constexpr int){7};
+static_assert(!(constexpr int){});
+
+struct S5 { int a; };
+enum { E3 = (constexpr struct S5){42}.a };
+enum { E4 = (int)(constexpr double){1.0} };
+
+union U1 {
+  int a;
+  long b;
+};
+enum { E5 = (constexpr union U1){.a = 9}.a };
+enum {
+  E6 = (constexpr union U1){.a = 9}.b
+  // expected-error at -1 {{expression is not an integer constant expression}}
+  // expected-note at -2 {{read of member 'b' of union with active member 'a' is not allowed in a constant expression}}
+};
+
+void test12(void) {
+  static int a = (constexpr int){8};
+  int b[(constexpr int){3}];
+  switch ((constexpr int){1}) {
+  case (constexpr int){1}:
+    break;
+  }
+}
+
+void test13(void) {
+  (constexpr int){1} = 2;             // expected-error {{read-only variable is not assignable}}
+  (constexpr int[1]){1}[0] = 2;       // expected-error {{read-only variable is not assignable}}
+  (constexpr struct S){1, 'a'}.a = 2; // expected-error {{read-only variable is not assignable}}
+}
+
+int *a10 = &(static int){100};
+int *a11 = &(thread_local int){200}; // expected-error {{initializer element is not a compile-time constant}}
+const int *a12 = &(constexpr int){300};
+
+void test14(void) {
+  const int *a = (constexpr int[]){1, 2, 3};
+  int *b = (static int[]){4, 5, 6};
+}
+
+int a13 = (thread_local int){1};              // expected-error {{initializer element is not a compile-time constant}}
+int a14 = (thread_local static int){1};       // expected-error {{initializer element is not a compile-time constant}}
+int a15 = 1 + (thread_local int){1};          // expected-error {{initializer element is not a compile-time constant}}
+int a16 = +(thread_local int){1};             // expected-error {{initializer element is not a compile-time constant}}
+int a17 = (thread_local struct S5){1}.a;      // expected-error {{initializer element is not a compile-time constant}}
+int a18 = *(thread_local int[1]){1};          // expected-error {{initializer element is not a compile-time constant}}
+
+struct S7 {
+  unsigned char a;
+};
+
+void test15(void) {
+  static int a;
+  (void)(constexpr int *){&a};     // expected-error {{constexpr pointer initializer is not null}}
+  (void)(constexpr struct S7){-1}; // expected-error {{constexpr initializer evaluates to -1 which is not exactly representable in type 'unsigned char'}}
+}
+
+int a19[3];
+static int *a20 = a19 + (constexpr int){1};
+static int *a21 = (constexpr int){0}; // expected-warning {{expression which evaluates to zero treated as a null pointer constant of type 'int *'}}
+static int *a22 = (constexpr int *){0};
+
+struct S8 {
+  int *restrict a;
+};
+void test16(void) {
+  (void)(constexpr struct S8){0}; // expected-error {{constexpr compound literal cannot have type 'int *restrict'}}
+}
+
+void test17(void) {
+  (void)(constexpr char[]){"\xFF"};
+  (void)(constexpr unsigned char[]){"\xFF"}; // expected-error {{constexpr initializer evaluates to -1 which is not exactly representable in type 'const unsigned char'}}
+}
+
+void test18(void) {
+  static const int *a = &(constexpr int){1}; // expected-error {{initializer element is not a compile-time constant}}
+  static const int *b = &(static constexpr int){1};
+}
+
+void f2(int a[sizeof((static int){1})]);
+void f3(int a[sizeof((register int){1})]);
+void f4(int a[sizeof((constexpr int){1})]);
+void f5(int a[sizeof((thread_local static int){1})]);
+int f6(void);
+
+void f7(int a[(constexpr int){f6()}]); // expected-error {{initializer of compound literal must be a constant expression}}
+void f8(int a[(static int){f6()}]);    // expected-error {{initializer element is not a compile-time constant}}
+
+typedef void F1(int a[(int){f6()}]);
+typedef void F2(int a[(thread_local int){1}]); // expected-error {{compound literal with 'thread_local' storage duration at block scope must also specify 'static'}}
+void (*a23)(int a[(register int){f6()}]);
+
+typedef int T __attribute__((address_space(1)));
+void f9(int a[sizeof((T){0})]);  // expected-error {{compound literal in function scope may not be qualified with an address space}}
+int f10(int a[sizeof((T){0})]) { // expected-error {{compound literal in function scope may not be qualified with an address space}}
+  return a[0];
+}
+
+void test19(void) {
+  (static T){0}; // expected-error {{compound literal in function scope may not be qualified with an address space}}
+}
+
+int f11(
+    typeof(sizeof((register T){0})) a(void)) { // expected-error {{compound literal in function scope may not be qualified with an address space}}
+  return a();
+}
+
+inline int f12(void) {    // expected-note {{use 'static' to give inline function 'f12' internal linkage}}
+  return (static int){1}; // expected-warning {{non-constant static local variable in inline function may be different in different files}}
+}
+
+inline int f13(int a[(static int){1}]) { // expected-warning {{non-constant static local variable in inline function may be different in different files}} \
+                                         // expected-note {{use 'static' to give inline function 'f13' internal linkage}}
+  return a[0];
+}
+
+inline int f14(int a[(static int){1}]);
+
+inline int f15(int a[(static const int){1}]) {
+  return a[0];
+}
+
+extern inline int f16(int a[(static int){1}]) {
+  return a[0];
+}
+
+static inline int f17(int a[(static int){1}]) {
+  return a[0];
+}
+
+inline int f18(int b(int a[sizeof((static int){1})])) {
+  return 0;
+}
+
+inline int f19(int (*a(void))[sizeof((static int){1})]) { // expected-warning {{non-constant static local variable in inline function may be different in different files}}
+                                                          // expected-note at -1 {{use 'static' to give inline function 'f19' internal linkage}}
+  return 0;
+}
+
+inline typeof((static int){1}) f20(void) {
+  return 0;
+}
+typeof((register int){1}) f21(void); // expected-error {{file-scope compound literal specifies 'register'}}
+
+inline int f22(int a) __attribute__((enable_if((static int){1}, "enabled"))) { // expected-warning {{non-constant static local variable in inline function may be different in different files}} \
+                                                                               // expected-note {{use 'static' to give inline function 'f22' internal linkage}}
+  return a;
+}
+
+int f23(int a) __attribute__((enable_if(sizeof((register T){0}), "enabled"))) { // expected-error {{compound literal in function scope may not be qualified with an address space}}
+  return a;
+}
+
+int f24(int a) __attribute__((enable_if( sizeof((register T){0}), "enabled"))); // expected-error {{compound literal in function scope may not be qualified with an address space}}
+int f24(int a) {
+  return a;
+}
+
+int a24;
+int a25 = sizeof((thread_local static int (*)[a24]){0}); // expected-error {{variably modified type declaration not allowed at file scope}}
+
+int *f25(void) {
+  return &(static int){1};
+}
+
+int *f26(void) {
+  return &(thread_local static int){1};
+}
+
+int *f27(void) {
+  return &(int){1}; // expected-warning {{address of stack memory associated with compound literal '{1}' returned}}
+}
+
+const int *f28(void) {
+  return &(constexpr int){1}; // expected-warning {{address of stack memory associated with compound literal '{1}' returned}}
+}
+
+inline int f29(void) {                 // expected-note {{use 'static' to give inline function 'f29' internal linkage}}
+  return (thread_local static int){1}; // expected-warning {{non-constant static local variable in inline function may be different in different files}}
+}
+
+void test20(void) {
+  (void)(M const int){1};
+  (void)(constexpr unsigned long){2};
+  (void)sizeof (static int){1};
+  (void)sizeof (static thread_local int){1};
+  (void)sizeof (constexpr int){1};
+}
+
+void test21(void) {
+  (void)(const static int){1};   // expected-error {{type name does not allow storage class to be specified}}
+  (void)(const int register){1}; // expected-error {{type name does not allow storage class to be specified}}
+}
+
+void test22(void) {
+  (void)(auto int){1};        // expected-error {{expected expression}}
+  (void)(extern int){2};      // expected-error {{expected expression}}
+  (void)(typedef int){3};     // expected-error {{expected expression}}
+  (void)(__auto_type int){4}; // expected-error {{expected expression}}
+  (void)(__thread int){5};    // expected-error {{expected expression}}
+}
+
+void test23(void) {
+  (void)(static int constexpr){1};     // expected-error {{type name does not allow constexpr specifier to be specified}}
+  (void)(static int thread_local){1};  // expected-error {{type name does not allow storage class to be specified}}
+  (void)(static int _Thread_local){1}; // expected-error {{type name does not allow storage class to be specified}}
+  (void)(static int __thread){1};      // expected-error {{type name does not allow storage class to be specified}}
+}
+
+void test24(void) {
+  (void)(static int)1;              // expected-error {{type name does not allow storage class to be specified}}
+  (void)sizeof(register int);       // expected-error {{type name does not allow storage class to be specified}}
+  (void)_Alignof(thread_local int); // expected-error {{type name does not allow storage class to be specified}}
+  (void)(constexpr int)1;           // expected-error {{type name does not allow constexpr specifier to be specified}}
+}
+
+int a26 = (static)3; // expected-error {{type name requires a specifier or qualifier}}
+
+typedef int A __attribute__((aligned(64)));
+A *f30(void) {
+  return &(thread_local static A){1}; // ps4-error {{alignment (64) of thread-local compound literal is greater than the maximum supported alignment (32) for thread-local storage on this target}}
+}
diff --git a/clang/test/Sema/c23-compound-literal.m b/clang/test/Sema/c23-compound-literal.m
new file mode 100644
index 0000000000000..142e5a49499eb
--- /dev/null
+++ b/clang/test/Sema/c23-compound-literal.m
@@ -0,0 +1,11 @@
+// RUN: %clang_cc1 -std=c23 -fobjc-arc -verify %s
+
+typedef struct {
+  id a;
+} S;
+
+void test1(void) {
+  (void)(thread_local static __unsafe_unretained id){0};
+  (void)(thread_local static id){0}; // expected-error {{thread-local variable has non-trivial ownership: type is '__strong id'}}
+  (void)(thread_local static S){0};  // expected-error {{type of thread-local variable has non-trivial destruction}}
+}
diff --git a/clang/test/Sema/constexpr.c b/clang/test/Sema/constexpr.c
index 0a9b5724a8343..936e0a3a32e19 100644
--- a/clang/test/Sema/constexpr.c
+++ b/clang/test/Sema/constexpr.c
@@ -22,7 +22,7 @@ struct S3 {
 constexpr; // expected-error {{'constexpr' can only be used in variable declarations}}
 constexpr int V1 = 3;
 constexpr float V2 = 7.0;
-int V3 = (constexpr)3; // expected-error {{expected expression}}
+int V3 = (constexpr)3; // expected-error {{type name requires a specifier or qualifier}}
 
 void f2() {
   constexpr int a = 0;

>From f7af8028bf85cdc59b72cdeeb89fa17379910742 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Jul 2026 10:04:08 +0300
Subject: [PATCH 2/4] use hasGlobalStorage for compound literal emission

---
 clang/lib/CodeGen/CGExpr.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index fadf00a606f3e..705d2dbb49a80 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5993,7 +5993,7 @@ CodeGenFunction::EmitLValueForFieldInitialization(LValue Base,
 }
 
 LValue CodeGenFunction::EmitCompoundLiteralLValue(const CompoundLiteralExpr *E){
-  if (E->isFileScope() || E->hasGlobalStorage()) {
+  if (E->hasGlobalStorage()) {
     if (E->getType()->isVariablyModifiedType())
       EmitVariablyModifiedType(E->getType());
 

>From 6ac710f83045b359a0ca12d448480edcd8e418df Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Jul 2026 11:32:54 +0300
Subject: [PATCH 3/4] parse all storage-class specifiers in compound literals

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td |  2 ++
 clang/lib/Parse/ParseExpr.cpp                    | 15 +++++++++++++++
 clang/lib/Sema/SemaExpr.cpp                      | 11 +++++++++--
 clang/test/Sema/c23-compound-literal.c           |  6 +++---
 4 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 85528249c4be4..19d086abd8c0e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3183,6 +3183,8 @@ def err_register_compound_literal_file_scope : Error<
 def err_thread_local_compound_literal_without_static : Error<
   "compound literal with 'thread_local' storage duration at block scope must "
   "also specify 'static'">;
+def err_compound_literal_invalid_storage_class : Error<
+  "storage class specifier '%0' is not permitted in a compound literal">;
 def err_constexpr_compound_literal_invalid_type : Error<
   "constexpr compound literal cannot have type %0">;
 def err_compound_literal_initializer_not_constant : Error<
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 55e1d489efb39..9ae51290fa0ad 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -2660,11 +2660,14 @@ bool Parser::isCompoundLiteralStorageClassSpecifier(const Token &Tok) const {
   if (!getLangOpts().C23)
     return false;
   switch (Tok.getKind()) {
+  case tok::kw_auto:
   case tok::kw_constexpr:
+  case tok::kw_extern:
   case tok::kw_register:
   case tok::kw_static:
   case tok::kw_thread_local:
   case tok::kw__Thread_local:
+  case tok::kw_typedef:
     return true;
   default:
     return false;
@@ -2680,10 +2683,22 @@ void Parser::ParseCompoundLiteralStorageClassSpecifiers(DeclSpec &DS) {
     unsigned DiagID = 0;
     bool IsInvalid = false;
     switch (Tok.getKind()) {
+    case tok::kw_typedef:
+      IsInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_typedef, Loc,
+                                         PrevSpec, DiagID, Policy);
+      break;
+    case tok::kw_extern:
+      IsInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_extern, Loc,
+                                         PrevSpec, DiagID, Policy);
+      break;
     case tok::kw_static:
       IsInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_static, Loc,
                                          PrevSpec, DiagID, Policy);
       break;
+    case tok::kw_auto:
+      IsInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_auto, Loc,
+                                         PrevSpec, DiagID, Policy);
+      break;
     case tok::kw_register:
       IsInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_register, Loc,
                                          PrevSpec, DiagID, Policy);
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index b5f19964cbefd..9d2c027e29fa5 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -7533,10 +7533,17 @@ ExprResult Sema::ActOnCompoundLiteral(SourceLocation LParenLoc, ParsedType Ty,
   SourceLocation ThreadStorageClassLoc;
   SourceLocation ConstexprLoc;
   if (DS) {
-    if (DS->getStorageClassSpec() == DeclSpec::SCS_static)
+    DeclSpec::SCS StorageClassSpec = DS->getStorageClassSpec();
+    if (StorageClassSpec == DeclSpec::SCS_static)
       SC = SC_Static;
-    else if (DS->getStorageClassSpec() == DeclSpec::SCS_register)
+    else if (StorageClassSpec == DeclSpec::SCS_register)
       SC = SC_Register;
+    else if (StorageClassSpec != DeclSpec::SCS_unspecified) {
+      Diag(DS->getStorageClassSpecLoc(),
+           diag::err_compound_literal_invalid_storage_class)
+          << DeclSpec::getSpecifierName(StorageClassSpec);
+      return ExprError();
+    }
 
     TSC = DS->getThreadStorageClassSpec();
     ConstexprKind = DS->getConstexprSpecifier();
diff --git a/clang/test/Sema/c23-compound-literal.c b/clang/test/Sema/c23-compound-literal.c
index 44759108bcd3a..187ea81608b55 100644
--- a/clang/test/Sema/c23-compound-literal.c
+++ b/clang/test/Sema/c23-compound-literal.c
@@ -314,9 +314,9 @@ void test21(void) {
 }
 
 void test22(void) {
-  (void)(auto int){1};        // expected-error {{expected expression}}
-  (void)(extern int){2};      // expected-error {{expected expression}}
-  (void)(typedef int){3};     // expected-error {{expected expression}}
+  (void)(auto int){1};        // expected-error {{storage class specifier 'auto' is not permitted in a compound literal}}
+  (void)(extern int){2};      // expected-error {{storage class specifier 'extern' is not permitted in a compound literal}}
+  (void)(typedef int){3};     // expected-error {{storage class specifier 'typedef' is not permitted in a compound literal}}
   (void)(__auto_type int){4}; // expected-error {{expected expression}}
   (void)(__thread int){5};    // expected-error {{expected expression}}
 }

>From 58a103790483ce9d7a688b84fd62d3c1564764cb Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Jul 2026 13:25:41 +0300
Subject: [PATCH 4/4] fix compound literal aggrs emit

---
 clang/lib/CodeGen/CGExprAgg.cpp           | 13 ++++++-------
 clang/test/CodeGen/c23-compound-literal.c | 15 +++++++++++++--
 2 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index c1309c456d4e3..a54ea7c599b1a 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -789,15 +789,14 @@ void AggExprEmitter::VisitOpaqueValueExpr(OpaqueValueExpr *e) {
 }
 
 void AggExprEmitter::VisitCompoundLiteralExpr(CompoundLiteralExpr *E) {
-  if (E->hasGlobalStorage() || E->getType()->isAtomicType() ||
-      (E->getStorageClass() == SC_Register &&
-       E->getType().isVolatileQualified()) ||
-      Dest.isPotentiallyAliased()) {
+  QualType Ty = E->getType();
+  if (Dest.isPotentiallyAliased() || E->hasGlobalStorage() ||
+      Ty->isAtomicType() || Ty.isVolatileQualified()) {
     EmitAggLoadOfLValue(E);
     return;
   }
 
-  AggValueSlot Slot = EnsureSlot(E->getType());
+  AggValueSlot Slot = EnsureSlot(Ty);
 
   // Block-scope compound literals are destroyed at the end of the enclosing
   // scope in C.
@@ -809,9 +808,9 @@ void AggExprEmitter::VisitCompoundLiteralExpr(CompoundLiteralExpr *E) {
   CGF.EmitAggExpr(E->getInitializer(), Slot);
 
   if (Destruct)
-    if (QualType::DestructionKind DtorKind = E->getType().isDestructedType())
+    if (QualType::DestructionKind DtorKind = Ty.isDestructedType())
       CGF.pushLifetimeExtendedDestroy(
-          CGF.getCleanupKind(DtorKind), Slot.getAddress(), E->getType(),
+          CGF.getCleanupKind(DtorKind), Slot.getAddress(), Ty,
           CGF.getDestroyer(DtorKind), DtorKind & EHCleanup);
 }
 
diff --git a/clang/test/CodeGen/c23-compound-literal.c b/clang/test/CodeGen/c23-compound-literal.c
index d255e72a23d1b..3f542021f5e21 100644
--- a/clang/test/CodeGen/c23-compound-literal.c
+++ b/clang/test/CodeGen/c23-compound-literal.c
@@ -51,11 +51,11 @@ int f6(void) {
 }
 
 // CHECK-LABEL: define dso_local i64 @f7()
-// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %retval, ptr align 4 @.compoundliteral.5, i64 8, i1 true)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %retval, ptr align 4 @.compoundliteral.5, i64 8, i1 false)
 // CHECK-NEXT: %[[VALUE:.*]] = load i64, ptr %retval, align 4
 // CHECK-NEXT: ret i64 %[[VALUE]]
 struct S f7(void) {
-  return (static volatile struct S){9, 10};
+  return (static struct S){9, 10};
 }
 
 // CHECK-LABEL: define dso_local i64 @f8()
@@ -273,3 +273,14 @@ int f28(const int *);
 int f29(int a[f28(&(constexpr int){3})]) {
   return a[0];
 }
+
+// CHECK-LABEL: define dso_local i64 @f30()
+// CHECK: %.compoundliteral = alloca %struct.S
+// CHECK: store i32 19
+// CHECK: store i32 20
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %retval, ptr align 4 %.compoundliteral, i64 8, i1 true)
+// CHECK-NEXT: %[[VALUE:.*]] = load i64, ptr %retval, align 4
+// CHECK-NEXT: ret i64 %[[VALUE]]
+struct S f30(void) {
+  return (volatile struct S){19, 20};
+}



More information about the cfe-commits mailing list