Skip to content

Fix drogon_ctl self-registration with clang-cl on Windows (COMDAT elimination of DrObject<T>::alloc_)#2477

Merged
an-tao merged 1 commit intodrogonframework:masterfrom
Greisby:fix/clang-cl
Mar 31, 2026
Merged

Fix drogon_ctl self-registration with clang-cl on Windows (COMDAT elimination of DrObject<T>::alloc_)#2477
an-tao merged 1 commit intodrogonframework:masterfrom
Greisby:fix/clang-cl

Conversation

@Greisby
Copy link
Copy Markdown
Contributor

@Greisby Greisby commented Mar 30, 2026

Problem

When building drogon with clang-cl (LLVM's MSVC-compatible compiler on Windows), drogon_ctl links successfully but fails at runtime: every command prints "command error! use help command to get usage!" because DrClassMap is empty - no command handler class is registered.

This affects clang-cl 17 through 19 (at least) with lld-link.
MSVC's cl.exe and clang on Linux/macOS are not affected.

Root cause

Root cause analysis and fix were found with the help of GitHub Copilot (Claude Opus 4.6), through iterative investigation: reproducer construction, COMDAT section analysis, and testing of multiple candidate fixes ([[gnu::used]], /OPT:NOREF, constructor side-effects) before converging on explicit template instantiation.

DrObject<T>::alloc_ is a static template member whose constructor registers T into DrClassMap before main(). This self-registration relies on the CRT running the static initializer.

On COFF targets (Windows), clang places the CRT initializer for a template static member in the same COMDAT section group as the variable itself.
When the optimizer determines that no code in the translation unit directly references alloc_ - which happens because clang aggressively inlines className() and eliminates the implicit this -> alloc_ reference - the entire COMDAT group becomes unreferenced.
The linker then discards it, so DrAllocator's constructor never runs.

On ELF targets (Linux, macOS), .init_array / .ctors entries are treated as GC roots and are never discarded, which is why this bug is invisible on those platforms.
MSVC's link.exe is also more conservative with CRT initializer sections and preserves them even when unreferenced.

Fix

This PR adds explicit template instantiation (template class drogon::DrObject<T>) at the end of each drogon_ctl/*.cc file that defines a DrObject subclass.
This forces the compiler to emit a strong (non-COMDAT) definition that the linker cannot discard.

This is standard C++ and has no effect on compilers/linkers that already preserve the initializer (MSVC, GCC, non-microsoft clang).

Scope and limitations

This PR only fixes drogon_ctl.
The same problem could potentially affect any application that statically links the drogon library with clang-cl on Windows and relies on DrObject self-registration without directly referencing the derived classes.
Users building their own apps with clang-cl would need to add similar explicit instantiations for their own DrObject subclasses.

There may be a better or more elegant compiler/linker-level solution, but we were unable to find one that works on COFF targets.
[[gnu::used]] / attribute((used)) do not prevent COMDAT elimination on COFF.
/OPT:NOREF does not help either — the issue is at the compiler level (clang-cl does not emit the initializer when it determines alloc_ is unused), not at the linker level.

Reproducer

A self-contained multi-file reproducer is attached to this PR.

Environment

Windows 10, Visual Studio 2022 Professional
clang-cl 19.1.5 (VS-bundled LLVM), lld-link
Also tested with MSVC 19.44 (works without the fix)

@Greisby
Copy link
Copy Markdown
Contributor Author

Greisby commented Mar 30, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a Windows-only runtime failure of drogon_ctl when built with clang-cl + lld-link, where command handler self-registration is discarded and DrClassMap ends up empty.

Changes:

  • Add explicit template instantiations of drogon::DrObject<drogon_ctl::<command>> in each command implementation TU to force emission/retention of the registration static initializer on COFF.
  • Add a detailed rationale comment in create.cc and reference it from the other command files.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
File Description
drogon_ctl/create.cc Adds detailed rationale comment and explicit instantiation for create command registration.
drogon_ctl/create_controller.cc Forces create_controller self-registration via explicit DrObject<> instantiation.
drogon_ctl/create_filter.cc Forces create_filter self-registration via explicit DrObject<> instantiation.
drogon_ctl/create_model.cc Forces create_model self-registration via explicit DrObject<> instantiation.
drogon_ctl/create_plugin.cc Forces create_plugin self-registration via explicit DrObject<> instantiation.
drogon_ctl/create_project.cc Forces create_project self-registration via explicit DrObject<> instantiation.
drogon_ctl/create_view.cc Forces create_view self-registration via explicit DrObject<> instantiation.
drogon_ctl/help.cc Forces help self-registration via explicit DrObject<> instantiation.
drogon_ctl/press.cc Forces press self-registration via explicit DrObject<> instantiation.
drogon_ctl/version.cc Forces version self-registration via explicit DrObject<> instantiation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Greisby Greisby requested review from an-tao and marty1885 March 30, 2026 14:25
@Greisby Greisby marked this pull request as ready for review March 30, 2026 14:25
@an-tao an-tao merged commit 21722ef into drogonframework:master Mar 31, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants