Skip to content

[llvm] get cl::opt instantiations working with MSVC DLL build #147810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

andrurogerz
Copy link
Contributor

@andrurogerz andrurogerz commented Jul 9, 2025

Purpose

This patch is one in a series of code-mods that annotate LLVM’s public interface for export. This patch annotates the llvm::cl::opt explicit template instantiations for export with LLVM_TEMPLATE_ABI and LLVM_EXPORT_TEMPLATE. This annotation currently has no meaningful impact on the LLVM build; however, it is a prerequisite to support an LLVM Windows DLL (shared library) build.

Background

This effort is tracked in #109483. Additional context is provided in this discourse, and documentation for LLVM_ABI and related annotations is found in the LLVM repo here.

Annotating the llvm::cl::opt template instances for DLL export was not straight-forward like other explicit template instances that have already been annotated. Annotating them as documented here results in link errors when building a Windows DLL using MSVC.

Overview

There are two specific issues that appear when exporting the llvm::cl::opt templates and compiling a Windows DLL with MSVC:

  1. We cannot export opt<std::string>. This is because MSVC exports all ancestor classes when exporting an instantiated template class. Since one of opt's ancestor classes is its type argument (via opt_storage), it is an ancestor of std::string. Therefore, if we export opt<std::string> from the LLVM DLL, MSVC forces std::basic_string to also be exported. This leads to duplicate symbol errors and generally seems like a bad idea. Compiling with clang-cl does not exhibit this behavior.
  2. The opt template instances other than opt<bool> get optimized out because they are not referenced in the TU (opt<bool> actually is). It is unclear exactly why MSVC optimizes these template instances away, but clang-cl does not. Adding explicit references to the instantiated opt template classes' vtables via implicit virtual destructor forces MSVC to export them.

Validation

Windows with MSVC
Windows with Clang

@andrurogerz andrurogerz marked this pull request as ready for review July 10, 2025 00:16
@llvmbot
Copy link
Member

llvmbot commented Jul 10, 2025

@llvm/pr-subscribers-llvm-support

Author: Andrew Rogers (andrurogerz)

Changes

Purpose

This patch is one in a series of code-mods that annotate LLVM’s public interface for export. This patch annotates the llvm::cl::opt explicit template instantiations for export with LLVM_TEMPLATE_ABI and LLVM_EXPORT_TEMPLATE. This annotation currently has no meaningful impact on the LLVM build; however, it is a prerequisite to support an LLVM Windows DLL (shared library) build.

Background

This effort is tracked in #109483. Additional context is provided in this discourse, and documentation for LLVM_ABI and related annotations is found in the LLVM repo here.

Annotating the llvm::cl::opt template instances for DLL export was not straight-forward like other explicit template instances that have already been annotated. Annotating them as documented here results in link errors when building a Windows DLL using MSVC.

Overview

There are two specific issues that appear when exporting the llvm::cl::opt templates and compiling a Windows DLL with MSVC:

  1. We cannot export opt&lt;std::string&gt;. This is because MSVC exports all ancestor classes when exporting an instantiated template class. Since one of opt's ancestor classes is its type argument (via opt_storage), it is an ancestor of std::string. Therefore, if we export opt&lt;std::string&gt; from the LLVM DLL, MSVC forces std::basic_string to also be exported. This leads to duplicate symbol errors and generally seems like a bad idea. Compiling with clang-cl does not exhibit this behavior.
  2. The opt template instances other than opt&lt;bool&gt; get optimized out because they are not referenced in the TU (opt&lt;bool&gt; actually is). It is unclear exactly why MSVC optimizes these template instances away, but clang-cl does not. Adding explicit references to the instantiated opt template classes' vtables via implicit virtual destructor forces MSVC to export them.

Validation

Windows with MSVC
Windows with Clang


Full diff: https://github.com/llvm/llvm-project/pull/147810.diff

2 Files Affected:

  • (modified) llvm/include/llvm/Support/CommandLine.h (+14-5)
  • (modified) llvm/lib/Support/CommandLine.cpp (+24-5)
diff --git a/llvm/include/llvm/Support/CommandLine.h b/llvm/include/llvm/Support/CommandLine.h
index adaa75cc6c348..ab3ab9b52ac12 100644
--- a/llvm/include/llvm/Support/CommandLine.h
+++ b/llvm/include/llvm/Support/CommandLine.h
@@ -1518,11 +1518,20 @@ class opt
       [](const typename ParserClass::parser_data_type &) {};
 };
 
-extern template class opt<unsigned>;
-extern template class opt<int>;
-extern template class opt<std::string>;
-extern template class opt<char>;
-extern template class opt<bool>;
+#if !defined(LLVM_ENABLE_LLVM_EXPORT_ANNOTATIONS) ||                           \
+    !(defined(_MSC_VER) && !defined(__clang__))
+// Only instantiate opt<std::string> when not building a Windows DLL with MSVC.
+// When exporting opt<std::string>, MSVC cl implicitly exports symbols for
+// std::basic_string through transitive inheritance via std::string. These
+// symbols may appear in other TUs with different linkage, leading to duplicate
+// symbol conflicts.
+extern template class LLVM_TEMPLATE_ABI opt<std::string>;
+#endif
+
+extern template class LLVM_TEMPLATE_ABI opt<unsigned>;
+extern template class LLVM_TEMPLATE_ABI opt<int>;
+extern template class LLVM_TEMPLATE_ABI opt<char>;
+extern template class LLVM_TEMPLATE_ABI opt<bool>;
 
 //===----------------------------------------------------------------------===//
 // Default storage class definition: external storage.  This implementation
diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp
index d5c3cba13e030..c9541289aa3bd 100644
--- a/llvm/lib/Support/CommandLine.cpp
+++ b/llvm/lib/Support/CommandLine.cpp
@@ -68,11 +68,21 @@ template class LLVM_EXPORT_TEMPLATE basic_parser<float>;
 template class LLVM_EXPORT_TEMPLATE basic_parser<std::string>;
 template class LLVM_EXPORT_TEMPLATE basic_parser<char>;
 
-template class opt<unsigned>;
-template class opt<int>;
-template class opt<std::string>;
-template class opt<char>;
-template class opt<bool>;
+#if !defined(LLVM_ENABLE_LLVM_EXPORT_ANNOTATIONS) ||                           \
+    !(defined(_MSC_VER) && !defined(__clang__))
+// Only instantiate opt<std::string> when not building a Windows DLL with MSVC.
+// When exporting opt<std::string>, MSVC cl implicitly exports symbols for
+// std::basic_string through transitive inheritance via std::string. These
+// symbols may appear in other TUs with different linkage, leading to duplicate
+// symbol conflicts.
+template class LLVM_EXPORT_TEMPLATE opt<std::string>;
+#endif
+
+template class LLVM_EXPORT_TEMPLATE opt<bool>;
+template class LLVM_EXPORT_TEMPLATE opt<char>;
+template class LLVM_EXPORT_TEMPLATE opt<int>;
+template class LLVM_EXPORT_TEMPLATE opt<unsigned>;
+
 } // namespace cl
 } // namespace llvm
 
@@ -95,6 +105,15 @@ void parser<float>::anchor() {}
 void parser<std::string>::anchor() {}
 void parser<char>::anchor() {}
 
+// These anchor functions instantiate opt<T> and reference its virtual
+// destructor to ensure MSVC exports the corresponding vtable and typeinfo when
+// building a Windows DLL. Without an explicit reference, MSVC may omit the
+// instantiation at link time even if it is marked DLL-export.
+void opt_bool_anchor() { opt<bool> anchor{""}; }
+void opt_char_anchor() { opt<char> anchor{""}; }
+void opt_int_anchor() { opt<int> anchor{""}; }
+void opt_unsigned_anchor() { opt<unsigned> anchor{""}; }
+
 //===----------------------------------------------------------------------===//
 
 const static size_t DefaultPad = 2;

@andrurogerz
Copy link
Contributor Author

@compnerd @vgvassilev this one is a bit weird. I did some digging, and MSVC definitely has some quirks around exporting template instances. I will do another pass through the other exported template instances to see if there are any similar situations that didn't fail to link.

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

Successfully merging this pull request may close these issues.

2 participants