Issue 203722
Summary [clang][CodeGen][COFF] Backend report_fatal_error "Associative COMDAT symbol '…' does not exist" in getComdatGVForCOFF for a comdat whose leader is absent (passes the IR verifier)
Labels clang
Assignees
Reporter emabrey
    This bug report was generated with AI based upon analysis of my crash building a private cpp project, but I think it's a genuine bug because there is a clean reproducible example, so I'm posting it. If I've misunderstood something or something is flawed about this post let me know.

### Summary
On `x86_64-pc-windows-msvc`, COFF codegen aborts with a backend `report_fatal_error` when it lowers a global that belongs to a comdat whose **named leader `GlobalValue` is absent**:

```
fatal error: error in backend: Associative COMDAT symbol 'missing_leader' does not exist.
```

The malformed module **passes the IR verifier**, so it reaches the backend, which crashes instead of producing a diagnostic. This is hit in practice by **full LTO** of large C++ code that uses `thread_local` static members of class templates (details at the end), but it reproduces trivially from a few lines of IR.

### Minimal reproducer
`missing.ll`:
```llvm
target triple = "x86_64-pc-windows-msvc"

$missing_leader = comdat any
@assoc = global i32 0, comdat($missing_leader)   ; member references the comdat; there is no @missing_leader leader
```
```
clang --target=x86_64-pc-windows-msvc -c missing.ll
```
```
fatal error: error in backend: Associative COMDAT symbol 'missing_leader' does not exist.
clang: error: clang frontend command failed with exit code 70 (use -v to see invocation)
```

Reproduced on **clang 20.1.8**. It crashes **identically with and without** `-Xclang -disable-llvm-verifier`, i.e. the IR verifier accepts the leaderless comdat.

### Root cause
The error comes from `getComdatGVForCOFF` in `llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp`:

```cpp
static const GlobalValue *getComdatGVForCOFF(const GlobalValue *GV) {
  const Comdat *C = GV->getComdat();
  assert(C && "expected GV to have a Comdat!");

  StringRef ComdatGVName = C->getName();
  const GlobalValue *ComdatGV = GV->getParent()->getNamedValue(ComdatGVName);
  if (!ComdatGV)
    report_fatal_error("Associative COMDAT symbol '" + ComdatGVName +
                       "' does not exist.");
  ...
}
```

COFF requires a comdat's name to match an existing `GlobalValue` (the comdat leader/key). When lowering a member global whose leader is a different symbol, COFF emits the member's section as `IMAGE_COMDAT_SELECT_ASSOCIATIVE`, associated to that leader. If `M->getNamedValue(C->getName())` returns null — the leader no longer exists — codegen `report_fatal_error`s rather than emitting a diagnostic.

### How it arises in practice (full LTO)
Original crash, full LTO link of a large project (`clang-cl` + `lld-link`):
```
LLVM ERROR: Associative COMDAT symbol '?tls_data@?$SafeBinaryMutex@$00@@0UTLSData@1@A' does not exist.
```
The symbol demangles to `SafeBinaryMutex<1>::tls_data`, a `thread_local static` member of a class template. On COFF the TLS support data is emitted in a comdat keyed on that variable. Across modules the variable is `linkonce_odr`; during LTO symbol resolution the **comdat leader is dropped (or renamed) while an associated member survives**, producing exactly the leaderless-comdat module above. It is deterministic for the affected module under `lto=full` and never occurs without LTO (each TU emits a self-consistent comdat group). A complete `lld --reproduce` archive of the failing LTO link can be attached on request. There is an existing regression test for the comdat-rename class of issue: `llvm/test/Transforms/LowerTypeTests/cfi-coff-comdat-rename.ll`.

### Suggested fixes
1. **Keep comdat groups atomic across LTO (the real fix):** whichever pass removes/renames a comdat leader must keep the group consistent — keep the leader alive while any associated member survives, drop the whole group together, or rename the comdat to a surviving member. GlobalDCE/Internalize already have comdat-group logic; the COFF-associated support globals of a `thread_local` template static appear to slip through it.
2. **Verifier check (confirmed missing):** the minimal reproducer passes verification, so the malformed module reaches codegen. Rejecting a COFF comdat whose name has no corresponding `GlobalValue` in the verifier would turn the backend crash into a deterministic verifier error that names the producing pass.
3. **Graceful backend diagnostic:** at minimum, `getComdatGVForCOFF` should emit a normal error rather than `report_fatal_error`.

### Environment
- clang/LLVM **20.1.8**, target `x86_64-pc-windows-msvc`
- Crash observed both via `clang -c <ll>` (minimal) and via `clang-cl` + `lld-link` full-LTO link (real build)
- Host: Windows Server 2025 (10.0.26100); toolchain bundled with Visual Studio 2026 (component `VC.Llvm.Clang` 18.6.x) and standalone LLVM 20.1.8
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to