Skip to content

Incorrect Microsoft mangled name for the guard variable of a static local variable #83616

Closed
@tahonermann

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:

  1. compiled for export via declspec(dllexport) in a DLL using MSVC, and
  2. 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

clang:frontendLanguage frontend issues, e.g. anything involving "Sema"

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions