Incorrect Microsoft mangled name for the guard variable of a static local variable #83616
Description
Clang and MSVC generate different mangled names for the guard variable of a static local variable as demonstrated in the following example exercised with MSVC 19.35.32216.1 for x64 from Visual Studio 2022 and Clang 17. See https://godbolt.org/z/MznoPb8bb.
> type t.cpp
struct __declspec(IMPEXP) S {
static S& get();
static S& singleton() {
static S& s = get();
return s;
}
};
S& f() {
return S::singleton();
}
# MSVC compilation for dllexport:
> cl /nologo /c /DIMPEXP=dllexport t.cpp
t.cpp
> llvm-objdump.exe -t t.obj | find "singleton"
[18](sec 6)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 ?singleton@S@@SAAEAU1@XZ
...
[39](sec 11)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 ?s@?1??singleton@S@@SAAEAU2@XZ@4AEAU2@EA
[42](sec 12)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 ?$TSS0@?1??singleton@S@@SAAEAU2@XZ@4HA
# Clang compilation for dllexport:
> clang -c -DIMPEXP=dllexport t.cpp
> llvm-objdump -t t.o | find "singleton"
[13](sec 5)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 ?singleton@S@@SAAEAU1@XZ
[25](sec 8)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 ?s@?1??singleton@S@@SAAEAU2@XZ@4AEAU2@EA
[28](sec 9)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 ?$TSS0@?1??singleton@S@@SAAEAU1@XZ@4HA
...
The mangled names for S::singleton() and its static local variable s match, but the guard variable names differ with regard to a substitution reference ("2" vs "1"):
- MSVC:
?$TSS0@?1??singleton@S@@SAAEAU2@XZ@4HA - LLVM:
?$TSS0@?1??singleton@S@@SAAEAU1@XZ@4HA
Demangling demonstrates that the MSVC generated one is correct. Note that the return type is wrong for the second name below.
# MSVC:
> llvm-undname '?$TSS0@?1??singleton@S@@SAAEAU2@XZ@4HA'
?$TSS0@?1??singleton@S@@SAAEAU2@XZ@4HA
int `public: static struct S & __cdecl S::singleton(void)'::`2'::$TSS0
# LLVM:
> llvm-undname '?$TSS0@?1??singleton@S@@SAAEAU1@XZ@4HA'
?$TSS0@?1??singleton@S@@SAAEAU1@XZ@4HA
int `public: static struct singleton & __cdecl S::singleton(void)'::`2'::$TSS0
Since the guard variable is only accessible by S::singleton(), this mismatch normally isn't an issue; any TU that exports a definition of S::singleton() will provide an implementation that references the guard variable exported from the same TU. For TUs that import the definition, no references to the symbols for the local static variable or its guard variable will be emitted.
# MSVC compilation for dllimport:
> cl /nologo /c /DIMPEXP=dllimport t.cpp
t.cpp
> llvm-objdump.exe" -t t.obj | find "singleton"
[ 9](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 __imp_?singleton@S@@SAAEAU1@XZ
# Clang compilation for dllimport:
> clang -c -DIMPEXP=dllimport t.cpp
> llvm-objdump -t t.o | find "singleton"
[14](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 __imp_?singleton@S@@SAAEAU1@XZ
However, when S::singleton() is declared with __forceinline, problems ensue. MSVC apparently does not inline imported inline functions even when declared __forceinline. Meanwhile, Clang inlines the function definition, but emits import references for the static local variable and its guard variable. See https://godbolt.org/z/qKdazcTah.
# Same source file above with __forceinline added to the S::singleton() declaration.
> type t.cpp
struct __declspec(IMPEXP) S {
static S& get();
__forceinline static S& singleton() {
static S& s = get();
return s;
}
};
S& f() {
return S::singleton();
}
# MSVC compilation for dllimport with __forceinline:
> cl /nologo /c /DIMPEXP=dllimport t.cpp
t.cpp
> llvm-objdump.exe -t t.obj | find "singleton"
[ 9](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 __imp_?singleton@S@@SAAEAU1@XZ
# Clang compilation for dllimport with __forceinline:
> clang -c -DIMPEXP=dllimport t.cpp
> llvm-objdump -t t.o | find "singleton"
[14](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 __imp_?$TSS0@?1??singleton@S@@SAAEAU1@XZ@4HA
[19](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 __imp_?s@?1??singleton@S@@SAAEAU2@XZ@4AEAU2@EA
This difference in behavior results in link failures when a __forceinline function with a static local variable is:
- compiled for export via
declspec(dllexport)in a DLL using MSVC, and - compiled for import via
declspec(dllimport)in a DLL/Executable using Clang.
The link failure occurs because Clang emits a dependency on the guard variable symbol and that dependency isn't satisfied by the MSVC built DLL (due to the difference in name mangling).
Activity