[clang-cl][MSVC ABI] Vtable slot points to 1-byte NOP stub instead of out-of-class template override when base is dllimport and declaration omits virtual/override
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Concise Background
I am working on the problem of compiling a Trilinos project on Windows. I use the MSVC and clang-cl compilers for this, but for the OpenMP backend, you definitely need to use clang-cl, because it makes it possible to use the new OpenMP standards (thanks a lot to the developers, I really appreciate and respect you!), while MSVC supports only OpenMP 2.0. When running tests, one of the tests however, upon detailed analysis using CDB, it turns out that clang-cl code generation bug: the compiler generates an empty body (1 byte `NOP`) for `runUnitTestImpl`, the second template class declared via `TEUCHOS_UNIT_TEST_TEMPLATE_2_DECL`, due to the lack of `virtual`/`override` in the dllimport database. gcc/clang on Linux handle the same pattern correctly.
## Assumption
This is a **frontend/codegen bug**, not a linker bug: the **1-byte NOP** is already present in the COFF object file before linking.
## Details
[crash_main_90f8_2026-05-21_23-16-36-792.log](https://github.com/user-attachments/files/28119455/crash_main_90f8_2026-05-21_23-16-36-792.log)
[crash_main_vtable_diag_90f8_2026-05-21_23-16-36-963.log](https://github.com/user-attachments/files/28119450/crash_main_vtable_diag_90f8_2026-05-21_23-16-36-963.log)
[crash_main_slots_diag_90f8_2026-05-21_23-16-37-015.log](https://github.com/user-attachments/files/28119453/crash_main_slots_diag_90f8_2026-05-21_23-16-37-015.log)
Per the C++ standard ([[class.virtual] §11.7.3](https://eel.is/c++draft/class.virtual)), a matching member function in a derived class is an override and is itself virtual, regardless of whether virtual or override appears in the declaration:
```
[class.virtual] p1: "A non-static member function is a virtual function if it is
first declared with the keyword virtual or if it overrides a
virtual member function declared in a base class."
[class.virtual] p2: "If a virtual member function F is declared in a class B, and,
in a class D derived (directly or indirectly) from B, a declaration of a member function
G corresponds to a declaration of F [...] then G overrides F."
Footnote 86: "The use of the virtual specifier in the declaration of an overriding
function is valid but redundant (has empty semantics)."
```
## How to Reproduce
Sorry for that, I really tried to write minimal repro without any deps with pure C++, but all of them passing correctly, I guess the problem is in heavy weight macros, dlls, etc. But here is my last try of pure C++ example:
[repro_base.h.txt](https://github.com/user-attachments/files/28119293/repro_base.h.txt)
[repro_dll.cpp.txt](https://github.com/user-attachments/files/28119296/repro_dll.cpp.txt)
[repro_exe.cpp.txt](https://github.com/user-attachments/files/28119298/repro_exe.cpp.txt)
**Full reproduce**:
1. clone Trilinos repo (sha1: bf5f6317533c03924f35431a866aa11b69c45660)
2. use interactive [build script](https://github.com/user-attachments/files/28119341/build.ps1.txt) to configure and compile Teuchos package (No 36)
3. run tests (example PowerShell cmd below)
```ps1
$savedPath = $env:PATH
$buildDir = "D:\Develop\Trilinos\btomp"
$env:PATH = "$buildDir\lib;$env:PATH"
try {
Push-Location "$buildDir"
& ctest -V --output-on-failure -R TeuchosCore_TypeConversions_UnitTest
}
finally {
Pop-Location
$env:PATH = $savedPath
}
```
4. use CDB scripts to see the issue (example logs from _Details_)
[crash_diag.cdb.txt](https://github.com/user-attachments/files/28119421/crash_diag.cdb.txt)
[slots_diag.cdb.txt](https://github.com/user-attachments/files/28119422/slots_diag.cdb.txt)
[vtable_diag.cdb.txt](https://github.com/user-attachments/files/28119423/vtable_diag.cdb.txt)
[crash_diag.ps1.txt](https://github.com/user-attachments/files/28119426/crash_diag.ps1.txt)
[crash_diag.example.md](https://github.com/user-attachments/files/28119444/crash_diag.example.md)
## Environment
| Item | Value |
| ----------------- | ------------------------------------------------- |
| OS | Windows 11 Pro x64, build 26100 |
| CPU | Intel Core i9-12900H |
| CMake | 3.31.2 |
| Ninja | 1.12.1 |
| Build type | Release, shared libraries, C++20 |
| Target triple | `x86_64-pc-windows-msvc` |
| Key compile flags | `/MD /O2 /EHsc /bigobj /openmp -std:c++20 -mcx16` |
## Clang-cl Versions
```log
clang version 21.1.5
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: D:\local\LLVM\bin
```
```log
clang version 22.1.6 (https://github.com/llvm/llvm-project fc4aad7b5db3fff421df9a9637605b9ca5667881)
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: D:\local\LLVM-22.1.6\bin
```
## Related Issues
- [Trilinos PR](https://github.com/trilinos/Trilinos/pull/15283)
## Additional Information
I can't check how it works on MSVC for make a comparison because this crash is **only** for OpenMP 4.0+, when MSVC provides only 2.0.
This problem was solved in the [above mentioned PR](https://github.com/trilinos/Trilinos/pull/15283) (fix is working, tested locally).
## Questions
- is this really codegen bug? or I'm doing smth wrong and my understanding is wrong?
- is there any related issues/PRs for this problem? If so, can someone please attach them for the reference, it will be very interesting for me to read and research all of them.
- what else can I provide to make this issue more full?
Contributor guide
Assessment
This issue has not been assessed yet.