https://github.com/dmoody256 created
https://github.com/llvm/llvm-project/pull/229122
When targeting the MSVC ABI, HandleTagDeclDefinition emits in-class initialized
static data members of integral type as soon as the class definition is
complete, because MSVC treats such declarations as definitions. This also
applied to C++17 inline static data members. An integral member's dynamic
initializer was therefore added to llvm.global_ctors before those of the
preceding members of class type, which are emitted later when the class is
handled as a top-level declaration. Both entries get the same lexical order
key, so the final sort of global_ctors does not restore source order:
struct B {
static inline A a = A(1);
static inline int b = a.get(); // Ran before A(1).
};
Inline variables are definitions in their own right and are already emitted in
declaration order along with the rest of the class, so skip them in this MSVC
compatibility path. Non-inline members with in-class initializers are still
emitted there; they can only have constant initializers, so they do not affect
dynamic initialization order.
Fixes #110975
Assisted-by: Claude Code (Claude Opus 5.5)
>From 1ebcba6616ae407a2166d2ef039d7dd9c69bcc02 Mon Sep 17 00:00:00 2001
From: Daniel Moody <[email protected]>
Date: Mon, 5 Oct 2026 10:20:48 -0500
Subject: [PATCH] [clang][MS] Initialize inline static data members in
declaration order
When targeting the MSVC ABI, HandleTagDeclDefinition emits in-class
initialized static data members of integral type as soon as the class
definition is complete, because MSVC treats such declarations as
definitions. This also applied to C++17 inline static data members. An
integral member's dynamic initializer was therefore added to
llvm.global_ctors before those of the preceding members of class type,
which are emitted later when the class is handled as a top-level
declaration. Both entries get the same lexical order key, so the final
sort of global_ctors does not restore source order:
struct B {
static inline A a = A(1);
static inline int b = a.get(); // Ran before A(1).
};
Inline variables are definitions in their own right and are already
emitted in declaration order along with the rest of the class, so skip
them in this MSVC compatibility path. Non-inline members with in-class
initializers are still emitted there; they can only have constant
initializers, so they do not affect dynamic initialization order.
Fixes #110975
Assisted-by: Claude Code (Claude Opus 5.5)
---
clang/docs/ReleaseNotes.md | 6 +++
clang/lib/CodeGen/ModuleBuilder.cpp | 6 +++
...s-inline-static-data-member-init-order.cpp | 53 +++++++++++++++++++
3 files changed, 65 insertions(+)
create mode 100644
clang/test/CodeGenCXX/ms-inline-static-data-member-init-order.cpp
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 7ec126a065ae5ab..d3d9908758acf84 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -940,6 +940,12 @@ features cannot lower the translation-unit ABI level;
subsequent base; Clang now does the same.
([#210174](https://github.com/llvm/llvm-project/issues/210174))
+- Fixed the dynamic initialization order of C++17 inline static data members
+ when targeting the MSVC ABI. A member of integral type was initialized before
+ the members of class type that precede it, so it could observe them before
+ they were constructed. Members are now initialized in declaration order.
+ (#GH110975)
+
#### LoongArch Support
- `loongarch32-*-none-elf` and `loongarch64-*-none-elf` targets now use the
diff --git a/clang/lib/CodeGen/ModuleBuilder.cpp
b/clang/lib/CodeGen/ModuleBuilder.cpp
index 0b00362487d2af2..e69135a76715443 100644
--- a/clang/lib/CodeGen/ModuleBuilder.cpp
+++ b/clang/lib/CodeGen/ModuleBuilder.cpp
@@ -260,6 +260,12 @@ namespace {
if (Ctx->getTargetInfo().getCXXABI().isMicrosoft()) {
for (Decl *Member : D->decls()) {
if (VarDecl *VD = dyn_cast<VarDecl>(Member)) {
+ // Inline variables are already definitions, and are emitted in
+ // declaration order along with the rest of the class. Emitting
+ // them early here would run their dynamic initializers before
+ // those of the members that precede them.
+ if (VD->isInline())
+ continue;
if (Ctx->isMSStaticDataMemberInlineDefinition(VD) &&
Ctx->DeclMustBeEmitted(VD)) {
Builder->EmitGlobal(VD);
diff --git a/clang/test/CodeGenCXX/ms-inline-static-data-member-init-order.cpp
b/clang/test/CodeGenCXX/ms-inline-static-data-member-init-order.cpp
new file mode 100644
index 000000000000000..5e622c81d3693fd
--- /dev/null
+++ b/clang/test/CodeGenCXX/ms-inline-static-data-member-init-order.cpp
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-extensions
-fms-compatibility -std=c++17 -emit-llvm -o - %s | FileCheck %s
+
+// Dynamic initializers of inline static data members must run in declaration
+// order. In the MS ABI, Clang used to emit in-class initialized static data
+// members of integral type as soon as the class definition was complete, which
+// placed their initializers ahead of preceding members of class type.
+// See https://github.com/llvm/llvm-project/issues/110975.
+
+struct A {
+ A(int);
+ int get() const;
+};
+
+struct S {
+ static const inline A a = A(1);
+ static const inline int b = a.get();
+ static inline A c = A(b);
+ static inline int d = c.get();
+};
+
+struct Outer {
+ static inline A e = A(2);
+ struct Inner {
+ static inline int f = e.get();
+ };
+};
+
+template <typename> struct Spec;
+template <> struct Spec<int> {
+ static inline A g = A(3);
+ static inline int h = g.get();
+};
+
+// Unreferenced members of a dllexport class must still be emitted.
+struct __declspec(dllexport) Exported {
+ static inline A i = A(4);
+ static inline int j = i.get();
+};
+
+// CHECK-DAG: @"?i@Exported@@2UA@@A" = weak_odr dso_local dllexport global
%struct.A
+// CHECK-DAG: @"?j@Exported@@2HA" = weak_odr dso_local dllexport global i32
+
+// CHECK: @llvm.global_ctors = appending global [10 x { i32, ptr, ptr }]
+// CHECK-SAME: { i32 65535, ptr @"??__E?a@S@@2UA@@B@@YAXXZ", ptr
@"?a@S@@2UA@@B" }
+// CHECK-SAME: { i32 65535, ptr @"??__E?b@S@@2HB@@YAXXZ", ptr @"?b@S@@2HB" }
+// CHECK-SAME: { i32 65535, ptr @"??__E?c@S@@2UA@@A@@YAXXZ", ptr
@"?c@S@@2UA@@A" }
+// CHECK-SAME: { i32 65535, ptr @"??__E?d@S@@2HA@@YAXXZ", ptr @"?d@S@@2HA" }
+// CHECK-SAME: { i32 65535, ptr @"??__E?e@Outer@@2UA@@A@@YAXXZ", ptr
@"?e@Outer@@2UA@@A" }
+// CHECK-SAME: { i32 65535, ptr @"??__E?f@Inner@Outer@@2HA@@YAXXZ", ptr
@"?f@Inner@Outer@@2HA" }
+// CHECK-SAME: { i32 65535, ptr @"??__E?g@?$Spec@H@@2UA@@A@@YAXXZ", ptr
@"?g@?$Spec@H@@2UA@@A" }
+// CHECK-SAME: { i32 65535, ptr @"??__E?h@?$Spec@H@@2HA@@YAXXZ", ptr
@"?h@?$Spec@H@@2HA" }
+// CHECK-SAME: { i32 65535, ptr @"??__E?i@Exported@@2UA@@A@@YAXXZ", ptr
@"?i@Exported@@2UA@@A" }
+// CHECK-SAME: { i32 65535, ptr @"??__E?j@Exported@@2HA@@YAXXZ", ptr
@"?j@Exported@@2HA" }]
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits