LINUX.ORG.RU

История изменений

Исправление nikitos, (текущая версия) :

Попробую объяснить на примере с кодом, как есть, и чего хотелось бы достичь.

Работа с OpenCL организована таким образом: в ocl_defs.h находятся описания структур и битовых полей из OpenCL, также есть ocl_api.h следующего содержания:

#include "ocl_defs.h"

typedef CL_API_ENTRY cl_int CL_API_CALL tclGetPlatformIDs(cl_uint, cl_platform_id*, cl_uint*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetPlatformInfo(cl_platform_id, cl_platform_info, size_t, void*, size_t*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetDeviceIDs(cl_platform_id, cl_device_type, cl_uint, cl_device_id*, cl_uint*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetDeviceInfo(cl_device_id, cl_device_info, size_t, void *, size_t *);
/* и так далее описаны типы функций из OpenCL 1.2 */

Пользовательские подсистемы, которые хотят использовать OpenCL для своих вычислений, подключают файл ocl_trampoline.h:

#include "ocl_api.h"

extern tclGetPlatformIDs   *clGetPlatformIDs;
extern tclGetPlatformInfo  *clGetPlatformInfo;
extern tclGetDeviceIDs     *clGetDeviceIDs;
extern tclGetDeviceInfo    *clGetDeviceInfo;

/* далее описаны указатели на остальные функции из OpenCL 1.2 */

extern int CL_API_CALL OpenCLRuntimeDynload();

Таким образом, подключив файл ocl_trampoline.h, пользовательский код использует точно такие же имена функций, как и в стандарте OpenCL без необходимости связываться (линковаться) с OpenCL ICD, так как работает с указателями-трамплинами имеющими такие же имена.

Перед всякой работой связанной с реальными вызовами функций из OpenCL, пользовательский код обязан вызвать функцию OpenCLRuntimeDynload(), чтоб быть уверенным в том, что указатели 'имитирующие' функции, были инициализированы правильными адресами.

OpenCLRuntimeDynload() как раз и содержит в себе пары dlopen/dlsym:


tclGetPlatformIDs *clGetPlatformIDs = nullptr;
tclGetPlatformInfo *clGetPlatformInfo = nullptr;
tclGetDeviceIDs *clGetDeviceIDs = nullptr;
tclGetDeviceInfo *clGetDeviceInfo = nullptr;
/* далее обнуляются и остальные указатели,
  есть также опциональная функция, которая их все обнуляет.*/

int CL_API_CALL OpenCLRuntimeDynload()
{
  OCLDynloadError dl_err = le_OK;

#define QUOTE(x) #x

#if (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_WIN)
#define SET_PROC_ADDR(name) name = reinterpret_cast<t##name *>(GetProcAddress(reinterpret_cast<HMODULE>(ocl_lib_handle_instance().get()), QUOTE(name))); if (!name) { dl_err = le_##name; break; }
#elif (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_UNIX)
#define SET_PROC_ADDR(name) name = reinterpret_cast<t##name *>(dlsym(ocl_lib_handle_instance().get(), QUOTE(name))); if (!name) { dl_err = le_##name; break; }
#endif

  for(;;) {
    if (!ocl_lib_handle_instance()) {
      std::unique_lock<std::mutex> lock(ocl_lib_handle_mutex_instance());

#if(OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_WIN)
      ocl_lib_handle_instance().reset(reinterpret_cast<void*>(LoadLibraryW(L"OpenCL.dll")));
#elif (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_UNIX)
      ocl_lib_handle_instance().reset(dlopen("libOpenCL.so", RTLD_LAZY));
#endif
    }

    if (!ocl_lib_handle_instance()) {
      dl_err = le_ICD_LIB_NOT_FOUND;
      break;
    }

    SET_PROC_ADDR(clGetPlatformIDs);
    SET_PROC_ADDR(clGetPlatformInfo);
    SET_PROC_ADDR(clGetDeviceIDs);
    SET_PROC_ADDR(clGetDeviceInfo);
    /* далее устанавливаем адреса всех оставшихся OpenCL 1.2 функций */
    
    break;
  }

  int32_t res = PXP_OK; /* унифицированный код ошибки */

  /* если во время dlsym что-то пошло не так - формируем
     унифицированный код ошибки, содержащий коды
     подсистемы (facility) и собственно ошибки (error code), в виде
     severity | facility | error_code, в данном случае
     Error | OpenCLDynloadSubsystem | SomeFnNotFound
   */
  if (dl_err != le_OK)
    res = DOCLDL_MAKE_ERROR(dl_err);

  return res;

#undef QUOTE
#undef SET_PROC_ADDR
}

Хотелось бы соорудить некую сущность - «контекст инициализации», передав ее параметром в функцию OpenCLRuntimeDynload() быть уверенным в том, что инициализированы только те функции, которые в данной подсистеме будут использованы а не вообще весь набор.

Для чего это нужно: например есть библиотека, которая использует OpenCL только для того, чтоб перечислить OpenCL устройства в системе, для ее работы достаточно лишь нескольких функций, а не всего набора.

Другие подсистемы используют чуть больше возможностей чем просто перечисление устройств, а уже создают контексты, компилирую кернелы итд, им соответственно контекст инициализации другой.

Как достичь цели с помощью «структуры с полями» я пока не совсем понимаю, так как структура будет каждый раз иметь разный тип, если ее использовать в качестве параметра для OpenCLRuntimeDynload().

Возможно нагородил тут все слишком сложно и неправильно, то интересно как сделать проще и правильнее по-другому. Вдохновлялся примерами из CUDA для их работы с их Driver API, когда сочинил такое.

Возможно Vulkan со своими validation layers имеет нужную мне архитектуру и решил бы мои хотелки, но пока с ним слабо знаком.

Понимаю, что изобретаю велосипед, который раз и навсегда решит проблему dll-hell но то такое, может кто изобрел получше :)

Исходная версия nikitos, :

Попробую объяснить на примере с кодом, как есть, и чего хотелось бы достичь.

Работа с OpenCL организована таким образом: в ocl_defs.h находятся описания структур и битовых полей из OpenCL, также есть ocl_api.h следующего содержания:

#include "ocl_defs.h"

typedef CL_API_ENTRY cl_int CL_API_CALL tclGetPlatformIDs(cl_uint, cl_platform_id*, cl_uint*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetPlatformInfo(cl_platform_id, cl_platform_info, size_t, void*, size_t*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetDeviceIDs(cl_platform_id, cl_device_type, cl_uint, cl_device_id*, cl_uint*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetDeviceInfo(cl_device_id, cl_device_info, size_t, void *, size_t *);
/* и так далее описаны типы функций из OpenCL 1.2 */

Пользовательские подсистемы, которые хотят использовать OpenCL для своих вычислений, подключают файл ocl_trampoline.h:

#include "ocl_api.h"

extern tclGetPlatformIDs   *clGetPlatformIDs;
extern tclGetPlatformInfo  *clGetPlatformInfo;
extern tclGetDeviceIDs     *clGetDeviceIDs;
extern tclGetDeviceInfo    *clGetDeviceInfo;

/* далее описаны указатели на остальные функции из OpenCL 1.2 */

extern int CL_API_CALL OpenCLRuntimeDynload();

Таким образом, подключив файл ocl_trampoline.h, пользовательский код использует точно такие же имена функций, как и в стандарте OpenCL без необходимости связываться (линковаться) с OpenCL ICD, так как работает с указателями-трамплинами имеющими такие же имена.

Перед всякой работой связанной с реальными вызовами функций из OpenCL, пользовательский код обязан вызвать функцию OpenCLRuntimeDynload(), чтоб быть уверенным в том, что указатели 'имитирующие' функции, были инициализированны правильными адресами.

OpenCLRuntimeDynload() как раз и содержит в себе пары dlopen/dlsym:


tclGetPlatformIDs *clGetPlatformIDs = nullptr;
tclGetPlatformInfo *clGetPlatformInfo = nullptr;
tclGetDeviceIDs *clGetDeviceIDs = nullptr;
tclGetDeviceInfo *clGetDeviceInfo = nullptr;
/* далее обнуляются и остальные указатели,
  есть также опциональная функция, которая их все обнуляет.*/

int CL_API_CALL OpenCLRuntimeDynload()
{
  OCLDynloadError dl_err = le_OK;

#define QUOTE(x) #x

#if (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_WIN)
#define SET_PROC_ADDR(name) name = reinterpret_cast<t##name *>(GetProcAddress(reinterpret_cast<HMODULE>(ocl_lib_handle_instance().get()), QUOTE(name))); if (!name) { dl_err = le_##name; break; }
#elif (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_UNIX)
#define SET_PROC_ADDR(name) name = reinterpret_cast<t##name *>(dlsym(ocl_lib_handle_instance().get(), QUOTE(name))); if (!name) { dl_err = le_##name; break; }
#endif

  for(;;) {
    if (!ocl_lib_handle_instance()) {
      std::unique_lock<std::mutex> lock(ocl_lib_handle_mutex_instance());

#if(OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_WIN)
      ocl_lib_handle_instance().reset(reinterpret_cast<void*>(LoadLibraryW(L"OpenCL.dll")));
#elif (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_UNIX)
      ocl_lib_handle_instance().reset(dlopen("libOpenCL.so", RTLD_LAZY));
#endif
    }

    if (!ocl_lib_handle_instance()) {
      dl_err = le_ICD_LIB_NOT_FOUND;
      break;
    }

    SET_PROC_ADDR(clGetPlatformIDs);
    SET_PROC_ADDR(clGetPlatformInfo);
    SET_PROC_ADDR(clGetDeviceIDs);
    SET_PROC_ADDR(clGetDeviceInfo);
    /* далее устанавливаем адреса всех оставшихся OpenCL 1.2 функций */
    
    break;
  }

  int32_t res = PXP_OK; /* унифицированный код ошибки */

  /* если во время dlsym что-то пошло не так - формируем
     унифицированный код ошибки, содержащий коды
     подсистемы (facility) и собственно ошибки (error code), в виде
     severity | facility | error_code, в данном случае
     Error | OpenCLDynloadSubsystem | SomeFnNotFound
   */
  if (dl_err != le_OK)
    res = DOCLDL_MAKE_ERROR(dl_err);

  return res;

#undef QUOTE
#undef SET_PROC_ADDR
}

Хотелось бы соорудить некую сущность - «контекст инициализации», передав ее параметром в функцию OpenCLRuntimeDynload() быть уверенным в том, что инициализированы только те функции, которые в данной подсистеме будут использованы а не вообще весь набор.

Для чего это нужно: например есть библиотека, которая использует OpenCL только для того, чтоб перечислить OpenCL устройства в системе, для ее работы достаточно лишь нескольких функций, а не всего набора.

Другие подсистемы используют чуть больше возможностей чем просто перечисление устройств, а уже создают контексты, компилирую кернелы итд, им соответсвенно контекст инициализации другой.

Как достичь цели с помощью «структуры с полями» я пока не совсем понимаю, так как структура будет каждый раз иметь разный тип, если ее использовать в качестве параметра для OpenCLRuntimeDynload().

Возможно нагородил тут все слишком сложно и неправильно, то интересно как сделать проще и правильнее по-другому. Вдохновлялся примерами из CUDA для их работы с их Driver API, когда сочинил такое.

Возможно Vulkan со своими validation layers имеет нужную мне архитектуру и решил бы мои хотелки, но пока с ним слабо знаком.

Понимаю, что изобретаю велосипед, который раз и навсегда решит проблему dll-hell но то такое, может кто изобрел получше :)