From 4c3d15abe6d72d77e55d13b861a3d17cf242d2ad Mon Sep 17 00:00:00 2001 From: Ahmed Arif Date: Sun, 19 Jul 2026 16:08:02 +0200 Subject: [PATCH] [CLANG][SDK] Add the llvm-compat runtime shim library (#9094) llvm-mingw's static runtime (libc++, libmingwex, libc++abi, libunwind) references symbols the NT 5.2 export surface does not provide. Add a static library, linked into the Clang runtime chain below DLL_EXPORT_VERSION 0x601, providing: - C99 vsnprintf/snprintf on top of _vsnprintf/_vscprintf. - __imp_* aliases binding dllimport references to the static CRT definitions instead of ucrtbase import thunks, which collide with them (lld: " was replaced"). - K32EnumProcessModules, forwarded to psapi's EnumProcessModules. - The Win7 SRW lock and Vista condition variable surface, bound to the RTL implementation linked statically from rtl_vista. Modules get one self-contained, consistent synchronization implementation (ReactOS' lock layout is not Windows-compatible), no kernel32_vista.dll dependency, and stay runnable on any Windows version. Static SRW linking suggested by Timo Kreuzer. Address review feedback on the llvm-compat shims: sync_static.c now uses the proper SDK/NDK headers with WINAPI/NTAPI, imp_alias.h moved to sdk/include/reactos and fixes the msvcrtex slot decorations too, and a new InitOnceExecuteOnce shim lets us drop libkernel32_vista from the interface. --- sdk/include/reactos/imp_alias.h | 36 ++++++ sdk/lib/CMakeLists.txt | 3 + sdk/lib/crt/stdio/acrt_iob_func.c | 8 +- sdk/lib/crt/stdlib/rand_s.c | 9 +- sdk/lib/llvm-compat/CMakeLists.txt | 10 ++ sdk/lib/llvm-compat/c99_printf_hacks.c | 49 +++++++++ sdk/lib/llvm-compat/imp_alias_hacks.c | 19 ++++ sdk/lib/llvm-compat/k32_psapi_hacks.c | 23 ++++ sdk/lib/llvm-compat/sync_static.c | 145 +++++++++++++++++++++++++ 9 files changed, 291 insertions(+), 11 deletions(-) create mode 100644 sdk/include/reactos/imp_alias.h create mode 100644 sdk/lib/llvm-compat/CMakeLists.txt create mode 100644 sdk/lib/llvm-compat/c99_printf_hacks.c create mode 100644 sdk/lib/llvm-compat/imp_alias_hacks.c create mode 100644 sdk/lib/llvm-compat/k32_psapi_hacks.c create mode 100644 sdk/lib/llvm-compat/sync_static.c diff --git a/sdk/include/reactos/imp_alias.h b/sdk/include/reactos/imp_alias.h new file mode 100644 index 00000000000..f498077d16c --- /dev/null +++ b/sdk/include/reactos/imp_alias.h @@ -0,0 +1,36 @@ +/* + * PROJECT: ReactOS SDK + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Helpers to define dllimport slots bound to static functions + * COPYRIGHT: Copyright 2026 Ahmed Arif + */ + +#pragma once + +/* + * __imp_ carries 's platform decoration: on i386 cdecl foo gets __imp__foo, one-argument stdcall + * bar gets __imp__bar@4. Spell the slot names through asm labels, a plain C variable would be decorated + * again. The bound-to function is referenced through an asm label as well, so the macros work no matter + * how (or whether) the surrounding TU declares it. + */ + +#if defined(__i386__) +#define IMP_TARGET_CDECL(name) "_" #name +#define IMP_SYMBOL_CDECL(name) "__imp__" #name +#define IMP_SYMBOL_STDCALL(name, size) "__imp__" #name "@" #size +#else +#define IMP_TARGET_CDECL(name) #name +#define IMP_SYMBOL_CDECL(name) "__imp_" #name +#define IMP_SYMBOL_STDCALL(name, size) "__imp_" #name +#endif + +/* Slot for a cdecl function, bound to the function of the same name */ +#define IMP_ALIAS_CDECL(name) \ + extern char __imp_alias_target_##name[] __asm__(IMP_TARGET_CDECL(name)); \ + const void *__imp_alias_##name __asm__(IMP_SYMBOL_CDECL(name)) = \ + (const void *)&__imp_alias_target_##name + +/* Slot for a stdcall function with `size` argument bytes, bound to an ABI-compatible target */ +#define IMP_ALIAS_STDCALL(name, size, target) \ + const void *__imp_alias_##name __asm__(IMP_SYMBOL_STDCALL(name, size)) = \ + (const void *)&target diff --git a/sdk/lib/CMakeLists.txt b/sdk/lib/CMakeLists.txt index fbd79de8686..a9bcfb1004c 100644 --- a/sdk/lib/CMakeLists.txt +++ b/sdk/lib/CMakeLists.txt @@ -7,6 +7,9 @@ if(CMAKE_CROSSCOMPILING) add_subdirectory(3rdparty) add_subdirectory(ansi_sync_hacks) +if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND DLL_EXPORT_VERSION LESS 0x601) + add_subdirectory(llvm-compat) +endif() add_subdirectory(apisets) add_subdirectory(comsupp) add_subdirectory(conutils) diff --git a/sdk/lib/crt/stdio/acrt_iob_func.c b/sdk/lib/crt/stdio/acrt_iob_func.c index e92b7e813b0..bd02a91636d 100644 --- a/sdk/lib/crt/stdio/acrt_iob_func.c +++ b/sdk/lib/crt/stdio/acrt_iob_func.c @@ -19,10 +19,8 @@ FILE * CDECL __acrt_iob_func(int index) return &__iob_func()[index]; } -#ifdef WIN64 -const void* __imp___acrt_iob_func = __acrt_iob_func; -#else -const void* _imp____acrt_iob_func = __acrt_iob_func; -#endif +// Import slot bound to the static definition, so the GCC and LLVM C++ runtimes can link +#include +IMP_ALIAS_CDECL(__acrt_iob_func); #endif diff --git a/sdk/lib/crt/stdlib/rand_s.c b/sdk/lib/crt/stdlib/rand_s.c index 7450f7127d0..14effe04931 100644 --- a/sdk/lib/crt/stdlib/rand_s.c +++ b/sdk/lib/crt/stdlib/rand_s.c @@ -62,11 +62,8 @@ int CDECL rand_s(unsigned int *pval) return 0; } -// Small hack: import stub to allow GCC's stdc++ to link +// Small hack: import slot bound to the static definition, so the GCC and LLVM C++ runtimes can link #if defined(__GNUC__) && (DLL_EXPORT_VERSION < 0x600) -#ifdef WIN64 -const void* __imp_rand_s = rand_s; -#else -const void* _imp_rand_s = rand_s; -#endif +#include +IMP_ALIAS_CDECL(rand_s); #endif diff --git a/sdk/lib/llvm-compat/CMakeLists.txt b/sdk/lib/llvm-compat/CMakeLists.txt new file mode 100644 index 00000000000..0ce2fef5ce1 --- /dev/null +++ b/sdk/lib/llvm-compat/CMakeLists.txt @@ -0,0 +1,10 @@ +list(APPEND SOURCE + c99_printf_hacks.c + imp_alias_hacks.c + k32_psapi_hacks.c + sync_static.c) + +add_library(llvmcompat STATIC ${SOURCE}) +add_dependencies(llvmcompat psdk) +# rtl_vista: the static RTL SRW/condvar/run-once implementation; libntdll: RtlRaiseStatus, keyed events +target_link_libraries(llvmcompat INTERFACE libpsapi rtl_vista libntdll) diff --git a/sdk/lib/llvm-compat/c99_printf_hacks.c b/sdk/lib/llvm-compat/c99_printf_hacks.c new file mode 100644 index 00000000000..4514412482b --- /dev/null +++ b/sdk/lib/llvm-compat/c99_printf_hacks.c @@ -0,0 +1,49 @@ +/* + * PROJECT: ReactOS SDK + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: C99 printf-family shims for llvm-mingw runtime libraries + * COPYRIGHT: Copyright 2026 Ahmed Arif + */ + +#include +#include + +/* llvm-mingw's libc++ needs the C99 vsnprintf/snprintf contract, which msvcrt's _vsnprintf (aliased onto + * these names by the crt headers) does not follow: undo the mapping and bridge over _vsnprintf/_vscprintf */ +#undef vsnprintf +#undef snprintf + +int vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) +{ + va_list ap; + int result; + + va_copy(ap, argptr); + result = _vsnprintf(buffer, count, format, ap); + va_end(ap); + + if (result >= 0 && (size_t)result < count) + return result; + + /* Truncated: terminate and return the would-be length */ + if (count != 0) + buffer[count - 1] = '\0'; + + va_copy(ap, argptr); + result = _vscprintf(format, ap); + va_end(ap); + + return result; +} + +int snprintf(char *buffer, size_t count, const char *format, ...) +{ + va_list argptr; + int result; + + va_start(argptr, format); + result = vsnprintf(buffer, count, format, argptr); + va_end(argptr); + + return result; +} diff --git a/sdk/lib/llvm-compat/imp_alias_hacks.c b/sdk/lib/llvm-compat/imp_alias_hacks.c new file mode 100644 index 00000000000..756b769d1da --- /dev/null +++ b/sdk/lib/llvm-compat/imp_alias_hacks.c @@ -0,0 +1,19 @@ +/* + * PROJECT: ReactOS SDK + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: dllimport-slot aliases for llvm-mingw runtime references + * COPYRIGHT: Copyright 2026 Ahmed Arif + */ + +/* + * llvm-mingw's runtime objects reference these CRT functions through dllimport slots. Resolving the slots + * from the ucrtbase import library pulls import thunks that collide with the static definitions in + * libmingwex/msvcrtex (lld: " was replaced"), so bind the slots to the static definitions instead. + */ + +#include + +IMP_ALIAS_CDECL(btowc); +IMP_ALIAS_CDECL(mbrtowc); +IMP_ALIAS_CDECL(wcrtomb); +IMP_ALIAS_CDECL(wctob); diff --git a/sdk/lib/llvm-compat/k32_psapi_hacks.c b/sdk/lib/llvm-compat/k32_psapi_hacks.c new file mode 100644 index 00000000000..7e9b6d5b492 --- /dev/null +++ b/sdk/lib/llvm-compat/k32_psapi_hacks.c @@ -0,0 +1,23 @@ +/* + * PROJECT: ReactOS SDK + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Win7 K32 psapi export shims for llvm-mingw runtime libraries + * COPYRIGHT: Copyright 2026 Ahmed Arif + */ + +#include +#include +#include + +/* llvm-mingw's libunwind looks up module unwind sections through this Win7+ kernel32 export: forward to + * the classic psapi.dll one */ +BOOL +WINAPI +K32EnumProcessModules( + _In_ HANDLE hProcess, + _Out_ HMODULE *lphModule, + _In_ DWORD cb, + _Out_ LPDWORD lpcbNeeded) +{ + return EnumProcessModules(hProcess, lphModule, cb, lpcbNeeded); +} diff --git a/sdk/lib/llvm-compat/sync_static.c b/sdk/lib/llvm-compat/sync_static.c new file mode 100644 index 00000000000..c4714a00b31 --- /dev/null +++ b/sdk/lib/llvm-compat/sync_static.c @@ -0,0 +1,145 @@ +/* + * PROJECT: ReactOS SDK + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Statically linked SRW lock, condition variable and init-once surface for llvm-mingw runtimes + * COPYRIGHT: Copyright 2026 Ahmed Arif + */ + +/* + * libc++ references the Win7 SRW lock APIs (directly and through dllimport slots) and the Vista condition + * variable and one-time initialization APIs (through dllimport slots). Bind them to the RTL implementation + * linked statically from rtl_vista (sdk/lib/rtl's srw.c, condvar.c and runonce.c) instead of exporting them + * from kernel32_vista.dll: modules get one self-contained, consistent synchronization implementation + * (ReactOS' lock layout is not Windows-compatible, mixing implementations on the same lock would be fatal) + * and no import that real Windows cannot satisfy. + */ + +/* This TU defines the kernel32 Win7 surface itself: make the headers declare it, non-dllimport */ +#define _KERNEL32_ +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 + +/* Bind the Rtl references to the static rtl_vista implementation, not to ntdll(_vista).dll imports */ +#define _NTSYSTEM_ + +#define WIN32_NO_STATUS +#include +#include +#define NTOS_MODE_USER +#include + +#include + +/* The kernel32 names, for direct references */ + +VOID +WINAPI +InitializeSRWLock(PSRWLOCK SRWLock) +{ + RtlInitializeSRWLock((PRTL_SRWLOCK)SRWLock); +} + +VOID +WINAPI +AcquireSRWLockExclusive(PSRWLOCK SRWLock) +{ + RtlAcquireSRWLockExclusive((PRTL_SRWLOCK)SRWLock); +} + +VOID +WINAPI +AcquireSRWLockShared(PSRWLOCK SRWLock) +{ + RtlAcquireSRWLockShared((PRTL_SRWLOCK)SRWLock); +} + +VOID +WINAPI +ReleaseSRWLockExclusive(PSRWLOCK SRWLock) +{ + RtlReleaseSRWLockExclusive((PRTL_SRWLOCK)SRWLock); +} + +VOID +WINAPI +ReleaseSRWLockShared(PSRWLOCK SRWLock) +{ + RtlReleaseSRWLockShared((PRTL_SRWLOCK)SRWLock); +} + +BOOLEAN +WINAPI +TryAcquireSRWLockExclusive(PSRWLOCK SRWLock) +{ + return RtlTryAcquireSRWLockExclusive((PRTL_SRWLOCK)SRWLock); +} + +BOOLEAN +WINAPI +TryAcquireSRWLockShared(PSRWLOCK SRWLock) +{ + return RtlTryAcquireSRWLockShared((PRTL_SRWLOCK)SRWLock); +} + +VOID +WINAPI +WakeConditionVariable(PCONDITION_VARIABLE ConditionVariable) +{ + RtlWakeConditionVariable((PRTL_CONDITION_VARIABLE)ConditionVariable); +} + +VOID +WINAPI +WakeAllConditionVariable(PCONDITION_VARIABLE ConditionVariable) +{ + RtlWakeAllConditionVariable((PRTL_CONDITION_VARIABLE)ConditionVariable); +} + +FORCEINLINE +PLARGE_INTEGER +GetNtTimeout(PLARGE_INTEGER Time, DWORD Timeout) +{ + if (Timeout == INFINITE) return NULL; + Time->QuadPart = (ULONGLONG)Timeout * -10000; + return Time; +} + +BOOL +WINAPI +SleepConditionVariableSRW(PCONDITION_VARIABLE ConditionVariable, PSRWLOCK SRWLock, DWORD Timeout, ULONG Flags) +{ + NTSTATUS Status; + LARGE_INTEGER Time; + + Status = RtlSleepConditionVariableSRW(ConditionVariable, SRWLock, GetNtTimeout(&Time, Timeout), Flags); + if (!NT_SUCCESS(Status) || Status == STATUS_TIMEOUT) + { + SetLastError(RtlNtStatusToDosError(Status)); + return FALSE; + } + return TRUE; +} + +BOOL +WINAPI +InitOnceExecuteOnce(PINIT_ONCE InitOnce, PINIT_ONCE_FN InitFn, PVOID Parameter, LPVOID *Context) +{ + return NT_SUCCESS(RtlRunOnceExecuteOnce(InitOnce, + (PRTL_RUN_ONCE_INIT_FN)InitFn, + Parameter, + Context)); +} + +/* The dllimport slots, bound directly to the RTL functions where the signatures match */ + +IMP_ALIAS_STDCALL(InitializeSRWLock, 4, RtlInitializeSRWLock); +IMP_ALIAS_STDCALL(AcquireSRWLockExclusive, 4, RtlAcquireSRWLockExclusive); +IMP_ALIAS_STDCALL(AcquireSRWLockShared, 4, RtlAcquireSRWLockShared); +IMP_ALIAS_STDCALL(ReleaseSRWLockExclusive, 4, RtlReleaseSRWLockExclusive); +IMP_ALIAS_STDCALL(ReleaseSRWLockShared, 4, RtlReleaseSRWLockShared); +IMP_ALIAS_STDCALL(TryAcquireSRWLockExclusive, 4, RtlTryAcquireSRWLockExclusive); +IMP_ALIAS_STDCALL(TryAcquireSRWLockShared, 4, RtlTryAcquireSRWLockShared); +IMP_ALIAS_STDCALL(WakeConditionVariable, 4, RtlWakeConditionVariable); +IMP_ALIAS_STDCALL(WakeAllConditionVariable, 4, RtlWakeAllConditionVariable); +IMP_ALIAS_STDCALL(SleepConditionVariableSRW, 16, SleepConditionVariableSRW); +IMP_ALIAS_STDCALL(InitOnceExecuteOnce, 16, InitOnceExecuteOnce);