Skip to content

Conversation

@GkvJwa
Copy link
Contributor

@GkvJwa GkvJwa commented Dec 9, 2025

Fix #154361

Because the destruction processes for EXEs and DLLs differ on Windows, DLLs, as modules, require more additional processing in the CRT. When RPMALLOC is enabled, RPMALLOC data initializes from the CRT by calling rpmalloc_initialize, but because there's no explicit dllmain, it can't destroy the RPMALLOC data. This causes the problem, and this patch adds dllmain to fix it.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:as-a-library libclang and C++ API labels Dec 9, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 9, 2025

@llvm/pr-subscribers-clang

Author: None (GkvJwa)

Changes

Fix #154361

Because the destruction processes for EXEs and DLLs differ on Windows, DLLs, as modules, require more additional processing in the CRT (Crt Request Tree). When RPMALLOC is enabled, it initializes from the CRT by calling rpmalloc_initialize, but because there's no explicit dllmain, it can't destroy the RPMALLOC data. This causes the problem, and this patch adds dllmain to fix it.


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

2 Files Affected:

  • (modified) clang/tools/libclang/CMakeLists.txt (+4)
  • (added) clang/tools/libclang/DllMain.cpp (+24)
diff --git a/clang/tools/libclang/CMakeLists.txt b/clang/tools/libclang/CMakeLists.txt
index b0105f5a5f79f..a3b95fd49b68d 100644
--- a/clang/tools/libclang/CMakeLists.txt
+++ b/clang/tools/libclang/CMakeLists.txt
@@ -135,6 +135,7 @@ endif()
 add_clang_library(libclang ${ENABLE_SHARED} ${ENABLE_STATIC} INSTALL_WITH_TOOLCHAIN
   OUTPUT_NAME ${output_name}
   ${SOURCES}
+  PARTIAL_SOURCES_INTENDED
 
   DEPENDS
   ClangDriverOptions
@@ -168,6 +169,9 @@ if(ENABLE_SHARED)
       # If llvm/libclang-cpp dll is also being built for windows clang c++ symbols will still be
       # implicitly be exported from libclang.
       target_compile_definitions(libclang PRIVATE CLANG_BUILD_STATIC)
+      if(LLVM_ENABLE_RPMALLOC)
+        target_sources(libclang PRIVATE DllMain.cpp)
+      endif()
   elseif(APPLE)
     set(LIBCLANG_LINK_FLAGS " -Wl,-compatibility_version -Wl,1")
     set(LIBCLANG_LINK_FLAGS "${LIBCLANG_LINK_FLAGS} -Wl,-current_version -Wl,${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}")
diff --git a/clang/tools/libclang/DllMain.cpp b/clang/tools/libclang/DllMain.cpp
new file mode 100644
index 0000000000000..262525e65e055
--- /dev/null
+++ b/clang/tools/libclang/DllMain.cpp
@@ -0,0 +1,24 @@
+
+
+#include <windows.h>
+
+extern "C" {
+
+int rpmalloc_initialize(void);
+void rpmalloc_finalize(void);
+void rpmalloc_thread_initialize(void);
+void rpmalloc_thread_finalize(int release_caches);
+
+BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
+  if (reason == DLL_PROCESS_ATTACH) {
+    rpmalloc_initialize();
+  } else if (reason == DLL_PROCESS_DETACH) {
+    rpmalloc_finalize();
+  } else if (reason == DLL_THREAD_ATTACH) {
+    rpmalloc_thread_initialize();
+  } else if (reason == DLL_THREAD_DETACH) {
+    rpmalloc_thread_finalize(1);
+  }
+  return TRUE;
+}
+}

@GkvJwa
Copy link
Contributor Author

GkvJwa commented Dec 9, 2025

hello, @slydiman @aganea @zmodem @naveen-seth @AaronBallman @mrexodia
If anyone has time, help review this.

add_clang_library(libclang ${ENABLE_SHARED} ${ENABLE_STATIC} INSTALL_WITH_TOOLCHAIN
OUTPUT_NAME ${output_name}
${SOURCES}
PARTIAL_SOURCES_INTENDED
Copy link
Collaborator

Choose a reason for hiding this comment

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

As noted below, I think we should always include DllMain.cpp (on Windows), and then I think we don't need this?

# If llvm/libclang-cpp dll is also being built for windows clang c++ symbols will still be
# implicitly be exported from libclang.
target_compile_definitions(libclang PRIVATE CLANG_BUILD_STATIC)
if(LLVM_ENABLE_RPMALLOC)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it would be nicer to always include this file (on Windows), and then inside the file we can do different things depending on the allocator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, the allocator lacks global macro definitions; adding them might introduce more pollution.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe adding a macro would be worth it.

Or, if we want an rpmalloc-specific DllMain.cpp, I think it should live closer to rpmalloc in llvm/lib/Support/ and get added from llvm/lib/Support/CMakeLists.txt

@@ -0,0 +1,24 @@

Copy link
Collaborator

Choose a reason for hiding this comment

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

This needs the LLVM license etc. header.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 13 to 15
if (reason == DLL_PROCESS_ATTACH) {
rpmalloc_initialize();
} else if (reason == DLL_PROCESS_DETACH) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since we build rpmalloc with ENABLE_PRELOAD, I think we already call _global_rpmalloc_init() at load time, so maybe we shouldn't be doing the initialization calls here?

Comment on lines 5 to 14
extern "C" {

int rpmalloc_initialize(void);
void rpmalloc_finalize(void);
void rpmalloc_thread_initialize(void);
void rpmalloc_thread_finalize(int release_caches);
Copy link
Member

Choose a reason for hiding this comment

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

Can we use a header instead of redeclaring these?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason is the same as the reason for checking LLVM_ENABLE_RPMALLOC in CMake: the overloaded malloc lacks a global macro definition.

void rpmalloc_thread_finalize(int release_caches);

BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
if (reason == DLL_PROCESS_ATTACH) {
Copy link
Member

Choose a reason for hiding this comment

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

I think that we should use the normal pattern of switch (reason) {.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

# implicitly be exported from libclang.
target_compile_definitions(libclang PRIVATE CLANG_BUILD_STATIC)
if(LLVM_ENABLE_RPMALLOC)
target_sources(libclang PRIVATE DllMain.cpp)
Copy link
Member

@aganea aganea Dec 10, 2025

Choose a reason for hiding this comment

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

Since all the code added here in DllMain.cpp already exists in tree here: https://github.com/llvm/llvm-project/blob/main/llvm/lib/Support/rpmalloc/malloc.c#L556 - would it be desirable to add that to {SOURCES} and setting it COMPILE_DEFINITIONS "ENABLE_PRELOAD;BUILD_DYNAMIC_LINK=1"? If we do that, we should also remove extern __declspec(dllexport) on the DllMain definition in rpmalloc, DllMain symbols shouldn't be exported.

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

Labels

clang:as-a-library libclang and C++ API clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

libclang.dll crashes process on exit when unloaded

5 participants