Thunks: Implement support for CUDA

This is enough to get less complex cuda applications running, and is a
good starting spot to slowly finish off the remaining implementation.

Some information:
- 429 functions in total
- 153 only compiled for 64-bit (35.6%)
- 7 functions disabled entirely (1.6%)

The main thing /not/ working with this initial implementation is .cu
files compiled in to an ELF using the static cuda runtime. This is due
to the `cuGetExportTable` function being stubbed out and the static cuda
RT requires at least two interfaces from that function before it
continues.

This function isn't publicly documented by NVIDIA but has been publicly
reverse engineered to be fairly trivial. It's just a jump table with the
first element being the size of the table in bytes.

That will be the next step of the implementation.
This commit is contained in:
Ryan Houdek committed 2026-06-01 16:32:45 -07:00
1 parent f5fafa5b96
commit 6fa27aac42
8 files changed
+2781

No files matched your search

+7
View File
@@ -46,6 +46,13 @@
"@PREFIX_LIB@/libwayland-client.so.0",
"@PREFIX_LIB@/libwayland-client.so.0.20.0"
]
},
"cuda": {
"Library" : "libcuda-guest.so",
"Overlay": [
"@PREFIX_LIB@/libcuda.so",
"@PREFIX_LIB@/libcuda.so.1"
]
}
}
}
+3
View File
@@ -266,3 +266,6 @@ add_library(PlaceholderX11 SHARED ../libX11/libX11_NativeGuest.cpp)
target_link_options(PlaceholderX11 PRIVATE "LINKER:-soname,libX11.so.6")
set_target_properties(PlaceholderX11 PROPERTIES NO_SONAME ON)
target_link_libraries(GL-guest PRIVATE PlaceholderX11)
generate(libcuda ${CMAKE_CURRENT_SOURCE_DIR}/../libcuda/libcuda_interface.cpp)
add_guest_lib(cuda "libcuda.so.1")
+3
View File
@@ -167,6 +167,9 @@ foreach(GUEST_BITNESS IN LISTS BITNESS_LIST)
find_package(OpenGL REQUIRED)
target_link_libraries(GL-host-${GUEST_BITNESS} PRIVATE OpenGL::GL)
generate(libcuda ${CMAKE_CURRENT_SOURCE_DIR}/../libcuda/libcuda_interface.cpp ${GUEST_BITNESS})
add_host_lib(cuda ${GUEST_BITNESS})
endforeach()
if (BUILD_FEX_LINUX_TESTS)
File diff suppressed because it is too large. Load diff
+15
View File
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
#pragma once
#include <cstddef>
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*CUhostFn)(void*);
enum cudaError_enum {};
enum CUgreenCtxCreate_flags {};
enum CUdevResourceType {};
#ifdef __cplusplus
}
#endif
+84
View File
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT
#include "common/Guest.h"
#include "cuda_defines.h"
#include "thunkgen_guest_libcuda.inl"
#include <cstdio>
#include <dlfcn.h>
#include <functional>
#include <string_view>
#include <unordered_map>
#include <cstdint>
// Maps cuda API function names to the address of a guest function which is
// linked to the corresponding host function pointer
const std::unordered_map<std::string_view, uintptr_t /* guest function address */> HostPtrInvokers = std::invoke([]() {
#define PAIR(name, unused) Ret[#name] = reinterpret_cast<uintptr_t>(GetCallerForHostFunction(name));
std::unordered_map<std::string_view, uintptr_t> Ret;
FOREACH_internal_SYMBOL(PAIR);
return Ret;
#undef PAIR
});
extern "C" {
// This variable controls the behavior of cuGetProcAddress for functions we don't know the signature of:
// - if false (default), we return a nullptr (since the application might have a fallback code path)
// - if true, we return a stub function that fatally errors upon being called
constexpr bool stub_unknown_functions = false;
// Fatally erroring function with a thunk-like interface. This is used as a placeholder for unknown CUDA functions
[[noreturn]]
static void FatalError(void* raw_args) {
auto called_function = reinterpret_cast<PackedArguments<void, uintptr_t>*>(raw_args)->a0;
fprintf(stderr, "FATAL: Called unknown CUDA function at address %p\n", reinterpret_cast<void*>(called_function));
__builtin_trap();
}
static void* MakeGuestCallable(const char* origin, void* func, const char* name) {
auto It = HostPtrInvokers.find(name);
if (It == HostPtrInvokers.end()) {
fprintf(stderr, "%s: Unknown cuda function at address %p: %s\n", origin, func, name);
if (stub_unknown_functions) {
const auto StubHostPtrInvoker = CallHostFunction<FatalError, void>;
LinkAddressToFunction((uintptr_t)func, reinterpret_cast<uintptr_t>(StubHostPtrInvoker));
return func;
}
return nullptr;
}
LinkAddressToFunction((uintptr_t)func, It->second);
return func;
}
struct override_entry {
std::string_view name;
void* ptr;
};
constexpr static std::array<override_entry, 2> proc_override = {
{{"cuGetProcAddress", (void*)cuGetProcAddress_v2}, {"cuGetProcAddress_v2", (void*)cuGetProcAddress_v2}}};
CUresult cuGetProcAddress_v2(const char* symbol, void** pfn, int cudaVersion, cuuint64_t flags, CUdriverProcAddressQueryResult* symbolStatus) {
for (auto& over : proc_override) {
if (symbol == over.name) {
*pfn = over.ptr;
if (symbolStatus) {
*symbolStatus = (CUdriverProcAddressQueryResult)0;
}
return (CUresult)0; // CUDA_SUCCESS
}
}
void* ptr {};
auto Ret = fexfn_pack_cuGetProcAddress_v2(symbol, &ptr, cudaVersion, flags, symbolStatus);
if (!Ret) {
*pfn = MakeGuestCallable(__FUNCTION__, ptr, symbol);
}
return Ret;
}
}
LOAD_LIB(libcuda)
+81
View File
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
#include "common/Host.h"
#include "cuda_defines.h"
#include <stdio.h>
#include <dlfcn.h>
#include <string>
#include <sys/mman.h>
#include <unistd.h>
#include "thunkgen_host_libcuda.inl"
#define FEXFN_IMPL(fn) fexfn_impl_libcuda_##fn
#define LDR_PTR(fn) fexldr_ptr_libcuda_##fn
struct override_entry {
std::string_view name;
void* ptr;
};
constexpr static std::array<override_entry, 2> proc_override = {
{{"cuCtxCreate_v2", (void*)FEXFN_IMPL(cuCtxCreate_v2)}, {"cuGetExportTable", (void*)FEXFN_IMPL(cuGetExportTable)}}};
static CUresult FEXFN_IMPL(cuGetProcAddress_v2)(const char* symbol, guest_layout<void**> pfn, int cudaVersion, cuuint64_t flags,
CUdriverProcAddressQueryResult* symbolStatus) {
host_layout<void*> host_data {};
void* ptr {};
CUresult ret {};
for (auto& over : proc_override) {
if (symbol == over.name) {
ptr = over.ptr;
if (symbolStatus) {
*symbolStatus = (CUdriverProcAddressQueryResult)0; // CU_GET_PROC_ADDRESS_SUCCESS
}
ret = (CUresult)0; // CUDA_SUCCESS
break;
}
}
if (!ptr) {
ret = LDR_PTR(cuGetProcAddress_v2)(symbol, &ptr, cudaVersion, flags, symbolStatus);
}
host_data.data = ptr;
*pfn.get_pointer() = to_guest(host_data);
return ret;
}
CUresult FEXFN_IMPL(cuCtxCreate_v2)(guest_layout<CUcontext*> pctx, unsigned int flags, CUdevice dev) {
host_layout<CUcontext> host_data {};
CUcontext ctx;
auto ret = LDR_PTR(cuCtxCreate_v2)(&ctx, flags, dev);
host_data.data = ctx;
*pctx.get_pointer() = to_guest(host_data);
return ret;
}
CUresult FEXFN_IMPL(cuGetExportTable)(guest_layout<const void**> ppExportTable, const CUuuid* pExportTableId) {
// This function returns a pointer to an driver internal export table that is undocumented publicly.
//
// Some documentation about the UUIDs that has been reversed.
// https://github.com/vosen/ZLUDA/blob/1b9ba2b2333746c5e2b05a2bf24fa6ec3828dcdf/zluda_dark_api/src/lib.rs#L197
//
// These two UUIDs are required by the static cuda runtime at startup. Might require more but it currently halts without these
// implemented. UUID: [0x6b, 0xd5, 0xfb, 0x6c, 0x5b, 0xf4, 0xe7, 0x4a, 0x89, 0x87, 0xd9, 0x39, 0x12, 0xfd, 0x9d, 0xf9]
// - cudart interface (0x68 bytes?)
// UUID: [0xa0, 0x94, 0x79, 0x8c, 0x2e, 0x74, 0x2e, 0x74, 0x93, 0xf2, 0x08, 0x00, 0x20, 0x0c, 0x0a, 0x66]
// - Tools runtime callback hooks (0x38 bytes?)
std::string uuid {};
for (size_t i = 0; i < sizeof(CUuuid); ++i) {
const bool last = (i + 1) == sizeof(CUuuid);
char tmp[8];
auto size = snprintf(tmp, 8, "0x%02x%s", pExportTableId->_0[i], last ? "" : ", ");
uuid += std::string_view(tmp, size);
}
fprintf(stderr, "cuGetExportTable not implemented\n");
fprintf(stderr, "UUID: %s\n", uuid.c_str());
return (CUresult)4; // CUDA_ERROR_DEINITIALIZED
}
EXPORTS(libcuda)
File diff suppressed because it is too large. Load diff