[Git][ghc/ghc][wip/T26009] 6 commits: rts/linker: Improve efficiency of proddable blocks structure

Ben Gamari pushed to branch wip/T26009 at Glasgow Haskell Compiler / GHC
Commits:
89031e59 by Ben Gamari at 2025-05-19T13:15:37-04:00
rts/linker: Improve efficiency of proddable blocks structure
Previously the linker's "proddable blocks" check relied on a simple
linked list of spans. This resulted in extremely poor complexity while
linking objects with lots of small sections (e.g. objects built with
split sections).
Rework the mechanism to instead use a simple interval set implemented
via binary search.
Fixes #26009.
- - - - -
3b644180 by Ben Gamari at 2025-05-19T13:51:31-04:00
testsuite: Add simple functional test for ProddableBlockSet
- - - - -
525894ab by Ben Gamari at 2025-05-19T14:29:33-04:00
rts/linker/PEi386: Drop check for LOAD_LIBRARY_SEARCH_*_DIRS
The `LOAD_LIBRARY_SEARCH_USER_DIRS` and
`LOAD_LIBRARY_SEARCH_DEFAULT_DIRS` were introduced in Windows Vista and
have been available every since. As we no longer support Windows XP we
can drop this check.
Addresses #26009.
- - - - -
b5fc6607 by Ben Gamari at 2025-05-19T15:08:38-04:00
rts/linker/PEi386: Clean up code style
- - - - -
ff9b8733 by Ben Gamari at 2025-05-19T15:27:52-04:00
rts/Hash: Factor out hashBuffer
This is a useful helper which can be used for non-strings as well.
- - - - -
295ed26f by Ben Gamari at 2025-05-19T15:28:36-04:00
rts/linker/PEi386: Maintain set of loaded DLLs
Addresses #26009.
- - - - -
7 changed files:
- rts/Hash.c
- rts/Hash.h
- rts/linker/PEi386.c
- rts/linker/ProddableBlocks.c
- rts/linker/ProddableBlocks.h
- + testsuite/tests/rts/TestProddableBlockSet.c
- testsuite/tests/rts/all.T
Changes:
=====================================
rts/Hash.c
=====================================
@@ -94,7 +94,7 @@ hashWord(const HashTable *table, StgWord key)
}
int
-hashStr(const HashTable *table, StgWord w)
+hashBuffer(const void *buf, size_t len)
{
const char *key = (char*) w;
#if WORD_SIZE_IN_BITS == 64
@@ -114,6 +114,12 @@ hashStr(const HashTable *table, StgWord w)
return bucket;
}
+int
+hashStr(const HashTable *table, StgWord w)
+{
+ return hashBuffer(key, strlen(key));
+}
+
STATIC_INLINE int
compareWord(StgWord key1, StgWord key2)
{
=====================================
rts/Hash.h
=====================================
@@ -69,6 +69,10 @@ void * removeStrHashTable ( StrHashTable *table, const char * key,
*/
typedef int HashFunction(const HashTable *table, StgWord key);
typedef int CompareFunction(StgWord key1, StgWord key2);
+
+// Helper for implementing hash functions
+int hashBuffer(const void *buf, size_t len);
+
int hashWord(const HashTable *table, StgWord key);
int hashStr(const HashTable *table, StgWord w);
void insertHashTable_ ( HashTable *table, StgWord key,
@@ -79,6 +83,7 @@ void * removeHashTable_ ( HashTable *table, StgWord key,
const void *data, HashFunction f,
CompareFunction cmp );
+
/* Freeing hash tables
*/
void freeHashTable ( HashTable *table, void (*freeDataFun)(void *) );
=====================================
rts/linker/PEi386.c
=====================================
@@ -427,8 +427,53 @@ const int default_alignment = 8;
the pointer as a redirect. Essentially it's a DATA DLL reference. */
const void* __rts_iob_func = (void*)&__acrt_iob_func;
+/*
+ * Note [Avoiding repeated DLL loading]
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * As LoadLibraryEx tends to be expensive and addDLL_PEi386 is called on every
+ * DLL-imported symbol, we use a hash-set to keep track of which DLLs have
+ * already been loaded. This hash-set is keyed on the dll_name passed to
+ * addDLL_PEi386 and serves as a quick check to avoid repeated calls to
+ * LoadLibraryEx for the identical DLL. See #26009.
+ */
+
+typedef struct {
+ HashTable *hash;
+} LoadedDllSet;
+
+LoadedDllSet loaded_dll_set;
+
+void initLoadedDllSet(LoadedDllSet *set) {
+ set->hash = allocHashTable();
+}
+
+int hash_path(const HashTable *table, StgWord key)
+{
+ const pathchar *key = (pathchar*) w;
+ return hashBuffer(key, sizeof(pathchar) * wcslen(key));
+}
+
+int compare_path(StgWord key1, StgWord key2)
+{
+ return wscmp((pathchar*) key1, (pathchar*) key2);
+}
+
+void addLoadedDll(LoadedDllSet *set, pathchar *dll_name)
+{
+ insertHashTable_(set->hash, (StgWord) dll_name, (void*) 1, hash_path);
+}
+
+bool isDllLoaded(LoadedDllSet *set, pathchar *dll_name)
+{
+ void * result = lookupHashTable_(set->hash, (StgWord) dll_name, hash_path, compare_path);
+ return result != NULL;
+}
+
+
void initLinker_PEi386(void)
{
+ initLoadedDllSet(&loaded_dll_set);
+
if (!ghciInsertSymbolTable(WSTR("(GHCi/Ld special symbols)"),
symhash, "__image_base__",
GetModuleHandleW (NULL), HS_BOOL_TRUE,
@@ -440,10 +485,11 @@ void initLinker_PEi386(void)
addDLLHandle(WSTR("*.exe"), GetModuleHandle(NULL));
#endif
- /* Register the cleanup routine as an exit handler, this gives other exit handlers
- a chance to run which may need linker information. Exit handlers are ran in
- reverse registration order so this needs to be before the linker loads anything. */
- atexit (exitLinker_PEi386);
+ /* Register the cleanup routine as an exit handler, this gives other exit handlers
+ * a chance to run which may need linker information. Exit handlers are ran in
+ * reverse registration order so this needs to be before the linker loads anything.
+ */
+ atexit (exitLinker_PEi386);
}
void exitLinker_PEi386(void)
@@ -798,12 +844,12 @@ uint8_t* getSymShortName ( COFF_HEADER_INFO *info, COFF_symbol* sym )
const char *
addDLL_PEi386( pathchar *dll_name, HINSTANCE *loaded )
{
- /* ------------------- Win32 DLL loader ------------------- */
-
- pathchar* buf;
- HINSTANCE instance;
+ /* ------------------- Win32 DLL loader ------------------- */
+ IF_DEBUG(linker, debugBelch("addDLL; dll_name = `%" PATH_FMT "'\n", dll_name));
- IF_DEBUG(linker, debugBelch("addDLL; dll_name = `%" PATH_FMT "'\n", dll_name));
+ if (isDllLoaded(loaded_dll_set)) {
+ return NULL;
+ }
/* The file name has no suffix (yet) so that we can try
both foo.dll and foo.drv
@@ -816,35 +862,23 @@ addDLL_PEi386( pathchar *dll_name, HINSTANCE *loaded )
extension. */
size_t bufsize = pathlen(dll_name) + 10;
- buf = stgMallocBytes(bufsize * sizeof(wchar_t), "addDLL");
+ pathchar *buf = stgMallocBytes(bufsize * sizeof(wchar_t), "addDLL");
/* These are ordered by probability of success and order we'd like them. */
const wchar_t *formats[] = { L"%ls.DLL", L"%ls.DRV", L"lib%ls.DLL", L"%ls" };
const DWORD flags[] = { LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, 0 };
- int cFormat, cFlag;
- int flags_start = 1; /* Assume we don't support the new API. */
-
- /* Detect if newer API are available, if not, skip the first flags entry. */
- if (GetProcAddress((HMODULE)LoadLibraryW(L"Kernel32.DLL"), "AddDllDirectory")) {
- flags_start = 0;
- }
-
/* Iterate through the possible flags and formats. */
- for (cFlag = flags_start; cFlag < 2; cFlag++)
- {
- for (cFormat = 0; cFormat < 4; cFormat++)
- {
+ HINSTANCE instance;
+ for (int cFlag = 0; cFlag < 2; cFlag++) {
+ for (int cFormat = 0; cFormat < 4; cFormat++) {
snwprintf(buf, bufsize, formats[cFormat], dll_name);
instance = LoadLibraryExW(buf, NULL, flags[cFlag]);
if (instance == NULL) {
- if (GetLastError() != ERROR_MOD_NOT_FOUND)
- {
+ if (GetLastError() != ERROR_MOD_NOT_FOUND) {
goto error;
}
- }
- else
- {
+ } else {
break; /* We're done. DLL has been loaded. */
}
}
@@ -855,6 +889,7 @@ addDLL_PEi386( pathchar *dll_name, HINSTANCE *loaded )
goto error;
}
+ addLoadedDll(&loaded_dll_set, dll_name);
addDLLHandle(buf, instance);
if (loaded) {
*loaded = instance;
=====================================
rts/linker/ProddableBlocks.c
=====================================
@@ -6,65 +6,125 @@
*
* ---------------------------------------------------------------------------*/
+
+/*
+ * Sanity checking. For each ObjectCode, maintain a list of address ranges
+ * which may be prodded during relocation, and abort if we try and write
+ * outside any of these.
+ */
+
#include "Rts.h"
#include "RtsUtils.h"
#include "linker/ProddableBlocks.h"
-struct _ProddableBlock {
- void* start;
- int size;
- struct _ProddableBlock* next;
-};
+#include
participants (1)
-
Ben Gamari (@bgamari)