| /* |
| * Copyright (c) 2016-2026 The Khronos Group Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * OpenCL is a trademark of Apple Inc. used under license by Khronos. |
| */ |
| |
| #include "icd.h" |
| #include "icd_dispatch.h" |
| #include "icd_envvars.h" |
| #include <stdlib.h> |
| #include <string.h> |
| |
| KHRicdVendor *khrIcdVendors = NULL; |
| static KHRicdVendor *lastVendor = NULL; |
| static int khrDisableLibraryUnloading = 0; |
| static int khrForceLegacyTermination = 0; |
| |
| #if defined(CL_ENABLE_LAYERS) |
| struct KHRLayer *khrFirstLayer = NULL; |
| #endif // defined(CL_ENABLE_LAYERS) |
| |
| static inline int khrIcdCheckEnvTrue(const char *variable) |
| { |
| return (variable && (strcmp(variable, "True") == 0 || |
| strcmp(variable, "true") == 0 || |
| strcmp(variable, "T") == 0 || |
| strcmp(variable, "1") == 0)); |
| } |
| |
| // Set a given flag if the given environement variable is true |
| static void khrInitializeFlagWithEnv(int *flag, const char *variable) |
| { |
| char *variableStr = khrIcd_getenv(variable); |
| if (khrIcdCheckEnvTrue(variableStr)) |
| { |
| *flag = 1; |
| } |
| if (variableStr) |
| { |
| khrIcd_free_getenv(variableStr); |
| } |
| } |
| |
| void khrIcdInitializeEnvOptions(void) |
| { |
| khrInitializeFlagWithEnv(&khrEnableTrace, "OCL_ICD_ENABLE_TRACE"); |
| khrInitializeFlagWithEnv(&khrDisableLibraryUnloading, "OCL_ICD_DISABLE_DYNAMIC_LIBRARY_UNLOADING"); |
| khrInitializeFlagWithEnv(&khrForceLegacyTermination, "OCL_ICD_FORCE_LEGACY_TERMINATION"); |
| } |
| |
| // entrypoint to initialize the ICD and add all vendors |
| void khrIcdInitialize(void) |
| { |
| // enumerate vendors present on the system |
| khrIcdOsVendorsEnumerateOnce(); |
| } |
| |
| void khrIcdVendorAdd(const char *libraryName) |
| { |
| void *library = NULL; |
| cl_int result = CL_SUCCESS; |
| pfn_clGetExtensionFunctionAddress p_clGetExtensionFunctionAddress = NULL; |
| pfn_clIcdGetPlatformIDs p_clIcdGetPlatformIDs = NULL; |
| #if defined(CL_ENABLE_LOADER_MANAGED_DISPATCH) |
| clIcdGetFunctionAddressForPlatformKHR_fn p_clIcdGetFunctionAddressForPlatform = NULL; |
| clIcdSetPlatformDispatchDataKHR_fn p_clIcdSetPlatformDispatchData = NULL; |
| #endif |
| cl_uint i = 0; |
| cl_uint platformCount = 0; |
| cl_platform_id *platforms = NULL; |
| KHRicdVendor *vendorIterator = NULL; |
| |
| // require that the library name be valid |
| if (!libraryName) |
| { |
| goto Done; |
| } |
| KHR_ICD_TRACE("attempting to add vendor %s...\n", libraryName); |
| |
| // load its library and query its function pointers |
| library = khrIcdOsLibraryLoad(libraryName); |
| if (!library) |
| { |
| KHR_ICD_TRACE("failed to load library %s\n", libraryName); |
| goto Done; |
| } |
| |
| // ensure that we haven't already loaded this vendor |
| for (vendorIterator = khrIcdVendors; vendorIterator; vendorIterator = vendorIterator->next) |
| { |
| if (vendorIterator->library == library) |
| { |
| KHR_ICD_TRACE("already loaded vendor %s, nothing to do here\n", libraryName); |
| goto Done; |
| } |
| } |
| |
| // get the library's clGetExtensionFunctionAddress pointer |
| p_clGetExtensionFunctionAddress = (pfn_clGetExtensionFunctionAddress)(size_t)khrIcdOsLibraryGetFunctionAddress(library, "clGetExtensionFunctionAddress"); |
| if (!p_clGetExtensionFunctionAddress) |
| { |
| KHR_ICD_TRACE("failed to get function address clGetExtensionFunctionAddress\n"); |
| goto Done; |
| } |
| |
| // use that function to get the clIcdGetPlatformIDsKHR function pointer |
| p_clIcdGetPlatformIDs = (pfn_clIcdGetPlatformIDs)(size_t)p_clGetExtensionFunctionAddress("clIcdGetPlatformIDsKHR"); |
| if (!p_clIcdGetPlatformIDs) |
| { |
| KHR_ICD_TRACE("failed to get extension function address clIcdGetPlatformIDsKHR\n"); |
| goto Done; |
| } |
| |
| #if defined(CL_ENABLE_LOADER_MANAGED_DISPATCH) |
| // try to get clIcdGetFunctionAddressForPlatformKHR and clIcdSetPlatformDispatchDataKHR to detect cl_khr_icd2 support |
| p_clIcdGetFunctionAddressForPlatform = (clIcdGetFunctionAddressForPlatformKHR_fn)(size_t)p_clGetExtensionFunctionAddress("clIcdGetFunctionAddressForPlatformKHR"); |
| p_clIcdSetPlatformDispatchData = (clIcdSetPlatformDispatchDataKHR_fn)(size_t)p_clGetExtensionFunctionAddress("clIcdSetPlatformDispatchDataKHR"); |
| #endif |
| |
| // query the number of platforms available and allocate space to store them |
| result = p_clIcdGetPlatformIDs(0, NULL, &platformCount); |
| if (CL_SUCCESS != result) |
| { |
| KHR_ICD_TRACE("failed clIcdGetPlatformIDs\n"); |
| goto Done; |
| } |
| platforms = (cl_platform_id *)malloc(platformCount * sizeof(cl_platform_id) ); |
| if (!platforms) |
| { |
| KHR_ICD_TRACE("failed to allocate memory\n"); |
| goto Done; |
| } |
| memset(platforms, 0, platformCount * sizeof(cl_platform_id) ); |
| result = p_clIcdGetPlatformIDs(platformCount, platforms, NULL); |
| if (CL_SUCCESS != result) |
| { |
| KHR_ICD_TRACE("failed clIcdGetPlatformIDs\n"); |
| goto Done; |
| } |
| |
| // for each platform, add it |
| for (i = 0; i < platformCount; ++i) |
| { |
| KHRicdVendor* vendor = NULL; |
| char *extensions; |
| size_t extensionsSize; |
| char *suffix; |
| size_t suffixSize; |
| |
| // skip NULL platforms and non dispatchable platforms |
| if (!platforms[i] || !platforms[i]->dispatch) |
| { |
| continue; |
| } |
| |
| #if defined(CL_ENABLE_LOADER_MANAGED_DISPATCH) |
| if (KHR_ICD2_HAS_TAG(platforms[i]) && !p_clIcdGetFunctionAddressForPlatform) |
| { |
| KHR_ICD_TRACE("found icd 2 object, but platform is missing clIcdGetFunctionAddressForPlatformKHR\n"); |
| continue; |
| } |
| if (KHR_ICD2_HAS_TAG(platforms[i]) && !p_clIcdSetPlatformDispatchData) |
| { |
| KHR_ICD_TRACE("found icd 2 object, but platform is missing clIcdSetPlatformDispatchDataKHR\n"); |
| continue; |
| } |
| if (KHR_ICD2_HAS_TAG(platforms[i]) && !KHR_ICD2_HAS_TAG2(platforms[i])) |
| { |
| KHR_ICD_TRACE("found icd 2 object, but platform is missing tag in clUnloadCompiler\n"); |
| continue; |
| } |
| #endif |
| |
| // allocate a structure for the vendor |
| vendor = (KHRicdVendor*)malloc(sizeof(*vendor) ); |
| if (!vendor) |
| { |
| KHR_ICD_TRACE("failed to allocate memory\n"); |
| continue; |
| } |
| memset(vendor, 0, sizeof(*vendor)); |
| |
| #if defined(CL_ENABLE_LOADER_MANAGED_DISPATCH) |
| // populate cl_khr_icd2 platform's loader managed dispatch tables |
| if (KHR_ICD2_HAS_TAG(platforms[i])) |
| { |
| khrIcd2PopulateDispatchTable(platforms[i], p_clIcdGetFunctionAddressForPlatform, &vendor->dispData.dispatch); |
| p_clIcdSetPlatformDispatchData(platforms[i], &vendor->dispData); |
| KHR_ICD_TRACE("found icd 2 platform, using loader managed dispatch\n"); |
| } |
| #endif |
| |
| // call clGetPlatformInfo on the returned platform to get the supported extensions |
| result = KHR_ICD2_DISPATCH(platforms[i])->clGetPlatformInfo( |
| platforms[i], |
| CL_PLATFORM_EXTENSIONS, |
| 0, |
| NULL, |
| &extensionsSize); |
| if (CL_SUCCESS != result) |
| { |
| KHR_ICD_TRACE("failed query platform extensions\n"); |
| free(vendor); |
| continue; |
| } |
| extensions = (char *)malloc(extensionsSize); |
| if (!extensions) |
| { |
| KHR_ICD_TRACE("failed to allocate memory\n"); |
| free(vendor); |
| continue; |
| } |
| result = KHR_ICD2_DISPATCH(platforms[i])->clGetPlatformInfo( |
| platforms[i], |
| CL_PLATFORM_EXTENSIONS, |
| extensionsSize, |
| extensions, |
| NULL); |
| if (CL_SUCCESS != result) |
| { |
| KHR_ICD_TRACE("failed query platform extensions\n"); |
| free(extensions); |
| free(vendor); |
| continue; |
| } |
| |
| if (strstr(extensions, "cl_khr_icd_unloadable")) |
| { |
| KHR_ICD_TRACE("found cl_khr_icd_unloadable extension support\n"); |
| free(extensions); |
| result = KHR_ICD2_DISPATCH(platforms[i])->clGetPlatformInfo( |
| platforms[i], |
| CL_PLATFORM_UNLOADABLE_KHR, |
| sizeof(vendor->unloadable), |
| &vendor->unloadable, |
| NULL); |
| if (vendor->unloadable) |
| { |
| KHR_ICD_TRACE("platform is unloadable\n"); |
| } |
| if (CL_SUCCESS != result) |
| { |
| KHR_ICD_TRACE("found cl_khr_icd_unloadable but clGetPlatformInfo CL_PLATFORM_UNLOADABLE_KHR query failed\n"); |
| free(vendor); |
| continue; |
| } |
| } |
| |
| // call clGetPlatformInfo on the returned platform to get the suffix |
| result = KHR_ICD2_DISPATCH(platforms[i])->clGetPlatformInfo( |
| platforms[i], |
| CL_PLATFORM_ICD_SUFFIX_KHR, |
| 0, |
| NULL, |
| &suffixSize); |
| if (CL_SUCCESS != result) |
| { |
| KHR_ICD_TRACE("failed query platform ICD suffix\n"); |
| free(vendor); |
| continue; |
| } |
| suffix = (char *)malloc(suffixSize); |
| if (!suffix) |
| { |
| KHR_ICD_TRACE("failed to allocate memory\n"); |
| free(vendor); |
| continue; |
| } |
| result = KHR_ICD2_DISPATCH(platforms[i])->clGetPlatformInfo( |
| platforms[i], |
| CL_PLATFORM_ICD_SUFFIX_KHR, |
| suffixSize, |
| suffix, |
| NULL); |
| if (CL_SUCCESS != result) |
| { |
| KHR_ICD_TRACE("failed query platform ICD suffix\n"); |
| free(suffix); |
| free(vendor); |
| continue; |
| } |
| |
| // populate vendor data |
| vendor->library = khrIcdOsLibraryLoad(libraryName); |
| if (!vendor->library) |
| { |
| free(suffix); |
| free(vendor); |
| KHR_ICD_TRACE("failed get platform handle to library\n"); |
| continue; |
| } |
| vendor->clGetExtensionFunctionAddress = p_clGetExtensionFunctionAddress; |
| vendor->platform = platforms[i]; |
| vendor->suffix = suffix; |
| |
| // add this vendor to the list of vendors at the tail |
| if (lastVendor) { |
| lastVendor->next = vendor; |
| vendor->prev = lastVendor; |
| } else { |
| khrIcdVendors = vendor; |
| } |
| lastVendor = vendor; |
| |
| KHR_ICD_TRACE("successfully added vendor %s with suffix %s\n", libraryName, suffix); |
| |
| } |
| |
| Done: |
| |
| if (library) |
| { |
| khrIcdOsLibraryUnload(library); |
| } |
| if (platforms) |
| { |
| free(platforms); |
| } |
| } |
| |
| #if defined(CL_ENABLE_LAYERS) |
| void khrIcdLayerAdd(const char *libraryName) |
| { |
| void *library = NULL; |
| cl_int result = CL_SUCCESS; |
| pfn_clGetLayerInfo p_clGetLayerInfo = NULL; |
| pfn_clInitLayer p_clInitLayer = NULL; |
| pfn_clInitLayerWithProperties p_clInitLayerWithProperties = NULL; |
| pfn_clDeinitLayer p_clDeinitLayer = NULL; |
| struct KHRLayer *layerIterator = NULL; |
| struct KHRLayer *layer = NULL; |
| cl_layer_api_version api_version = 0; |
| const struct _cl_icd_dispatch *targetDispatch = NULL; |
| const struct _cl_icd_dispatch *layerDispatch = NULL; |
| cl_uint layerDispatchNumEntries = 0; |
| cl_uint loaderDispatchNumEntries = 0; |
| |
| // require that the library name be valid |
| if (!libraryName) |
| { |
| goto Done; |
| } |
| KHR_ICD_TRACE("attempting to add layer %s...\n", libraryName); |
| |
| // load its library and query its function pointers |
| library = khrIcdOsLibraryLoad(libraryName); |
| if (!library) |
| { |
| KHR_ICD_TRACE("failed to load library %s\n", libraryName); |
| goto Done; |
| } |
| |
| // ensure that we haven't already loaded this layer |
| for (layerIterator = khrFirstLayer; layerIterator; layerIterator = layerIterator->next) |
| { |
| if (layerIterator->library == library) |
| { |
| KHR_ICD_TRACE("already loaded layer %s, nothing to do here\n", libraryName); |
| goto Done; |
| } |
| } |
| |
| // get the library's clGetLayerInfo pointer |
| p_clGetLayerInfo = (pfn_clGetLayerInfo)(size_t)khrIcdOsLibraryGetFunctionAddress(library, "clGetLayerInfo"); |
| if (!p_clGetLayerInfo) |
| { |
| KHR_ICD_TRACE("failed to get function address clGetLayerInfo\n"); |
| goto Done; |
| } |
| |
| result = p_clGetLayerInfo(CL_LAYER_API_VERSION, sizeof(api_version), &api_version, NULL); |
| if (CL_SUCCESS != result) |
| { |
| KHR_ICD_TRACE("failed to query layer version\n"); |
| goto Done; |
| } |
| |
| if (CL_LAYER_API_VERSION_100 != api_version) |
| { |
| KHR_ICD_TRACE("unsupported api version\n"); |
| goto Done; |
| } |
| |
| // Support old version of layers, which should rely on at_exit for termination. |
| // In this case use clInitLayer to initialize layers |
| if (khrForceLegacyTermination) |
| { |
| p_clInitLayer = (pfn_clInitLayer)(size_t)khrIcdOsLibraryGetFunctionAddress(library, "clInitLayer"); |
| if (!p_clInitLayer) |
| { |
| KHR_ICD_TRACE("failed to get function address clInitLayer\n"); |
| goto Done; |
| } |
| } else { // New scheme, relies on clInitLayerWithProperties and the optional clDeinitLayer for termination |
| p_clInitLayerWithProperties = (pfn_clInitLayerWithProperties)(size_t)khrIcdOsLibraryGetFunctionAddress(library, "clInitLayerWithProperties"); |
| if (!p_clInitLayerWithProperties) |
| { |
| KHR_ICD_TRACE("failed to get function address clInitLayerWithProperties\n"); |
| goto Done; |
| } |
| |
| p_clDeinitLayer = (pfn_clDeinitLayer)(size_t)khrIcdOsLibraryGetFunctionAddress(library, "clDeinitLayer"); |
| if (!p_clDeinitLayer) |
| { |
| KHR_ICD_TRACE("layer does not support clDeinitLayer\n"); |
| } |
| } |
| |
| layer = (struct KHRLayer*)calloc(sizeof(struct KHRLayer), 1); |
| if (!layer) |
| { |
| KHR_ICD_TRACE("failed to allocate memory\n"); |
| goto Done; |
| } |
| #ifdef CL_LAYER_INFO |
| { |
| // Not using strdup as it is not standard c |
| size_t sz_name = strlen(libraryName) + 1; |
| layer->libraryName = malloc(sz_name); |
| if (!layer->libraryName) |
| { |
| KHR_ICD_TRACE("failed to allocate memory\n"); |
| goto Done; |
| } |
| memcpy(layer->libraryName, libraryName, sz_name); |
| layer->p_clGetLayerInfo = p_clGetLayerInfo; |
| } |
| #endif |
| layer->p_clDeinitLayer = p_clDeinitLayer; |
| |
| if (khrFirstLayer) { |
| targetDispatch = &(khrFirstLayer->dispatch); |
| } else { |
| targetDispatch = &khrMainDispatch; |
| } |
| |
| loaderDispatchNumEntries = sizeof(khrMainDispatch)/sizeof(void*); |
| if (khrForceLegacyTermination) |
| { |
| result = p_clInitLayer( |
| loaderDispatchNumEntries, |
| targetDispatch, |
| &layerDispatchNumEntries, |
| &layerDispatch); |
| } else { |
| result = p_clInitLayerWithProperties( |
| loaderDispatchNumEntries, |
| targetDispatch, |
| &layerDispatchNumEntries, |
| &layerDispatch, |
| NULL); |
| } |
| if (CL_SUCCESS != result) |
| { |
| KHR_ICD_TRACE("failed to initialize layer\n"); |
| goto Done; |
| } |
| |
| layer->next = khrFirstLayer; |
| khrFirstLayer = layer; |
| layer->library = library; |
| |
| cl_uint limit = layerDispatchNumEntries < loaderDispatchNumEntries ? layerDispatchNumEntries : loaderDispatchNumEntries; |
| |
| for (cl_uint i = 0; i < limit; i++) { |
| ((void **)&(layer->dispatch))[i] = |
| ((void *const*)layerDispatch)[i] ? |
| ((void *const*)layerDispatch)[i] : ((void *const*)targetDispatch)[i]; |
| } |
| for (cl_uint i = limit; i < loaderDispatchNumEntries; i++) { |
| ((void **)&(layer->dispatch))[i] = ((void *const*)targetDispatch)[i]; |
| } |
| |
| KHR_ICD_TRACE("successfully added layer %s\n", libraryName); |
| return; |
| Done: |
| if (library) |
| { |
| khrIcdOsLibraryUnload(library); |
| } |
| if (layer) |
| { |
| free(layer); |
| } |
| } |
| #endif // defined(CL_ENABLE_LAYERS) |
| |
| // Get next file or dirname given a string list or registry key path. |
| // Note: the input string may be modified! |
| static char *loader_get_next_path(char *path) { |
| size_t len; |
| char *next; |
| |
| if (path == NULL) return NULL; |
| next = strchr(path, PATH_SEPARATOR); |
| if (next == NULL) { |
| len = strlen(path); |
| next = path + len; |
| } else { |
| *next = '\0'; |
| next++; |
| } |
| |
| return next; |
| } |
| |
| void khrIcdVendorsEnumerateEnv(void) |
| { |
| char* icdFilenames = khrIcd_secure_getenv("OCL_ICD_FILENAMES"); |
| char* cur_file = NULL; |
| char* next_file = NULL; |
| if (icdFilenames) |
| { |
| KHR_ICD_TRACE("Found OCL_ICD_FILENAMES environment variable.\n"); |
| |
| next_file = icdFilenames; |
| while (NULL != next_file && *next_file != '\0') { |
| cur_file = next_file; |
| next_file = loader_get_next_path(cur_file); |
| |
| khrIcdVendorAdd(cur_file); |
| } |
| |
| khrIcd_free_getenv(icdFilenames); |
| } |
| } |
| |
| #if defined(CL_ENABLE_LAYERS) |
| void khrIcdLayersEnumerateEnv(void) |
| { |
| char* layerFilenames = khrIcd_secure_getenv("OPENCL_LAYERS"); |
| char* cur_file = NULL; |
| char* next_file = NULL; |
| if (layerFilenames) |
| { |
| KHR_ICD_TRACE("Found OPENCL_LAYERS environment variable.\n"); |
| |
| next_file = layerFilenames; |
| while (NULL != next_file && *next_file != '\0') { |
| cur_file = next_file; |
| next_file = loader_get_next_path(cur_file); |
| |
| khrIcdLayerAdd(cur_file); |
| } |
| |
| khrIcd_free_getenv(layerFilenames); |
| } |
| } |
| #endif // defined(CL_ENABLE_LAYERS) |
| |
| void khrIcdContextPropertiesGetPlatform(const cl_context_properties *properties, cl_platform_id *outPlatform) |
| { |
| if (properties == NULL && khrIcdVendors != NULL) |
| { |
| *outPlatform = khrIcdVendors[0].platform; |
| } |
| else |
| { |
| const cl_context_properties *property = (cl_context_properties *)NULL; |
| *outPlatform = NULL; |
| for (property = properties; property && property[0]; property += 2) |
| { |
| if ((cl_context_properties)CL_CONTEXT_PLATFORM == property[0]) |
| { |
| *outPlatform = (cl_platform_id)property[1]; |
| } |
| } |
| } |
| } |
| |
| #if defined(CL_ENABLE_LAYERS) |
| static struct KHRLayer deinitLayer = {0}; |
| #endif |
| |
| void khrIcdDeinitialize(int unloadLibraries) { |
| if (khrForceLegacyTermination) |
| { |
| KHR_ICD_TRACE("ICD Loader deinitialization disabled\n"); |
| return; |
| } |
| KHR_ICD_TRACE("ICD Loader deinitialization\n"); |
| |
| #if defined(CL_ENABLE_LAYERS) |
| // free layers first in reverse order of their creation (front to back) |
| // they may still need to use vendors while terminating |
| KHR_ICD_TRACE("finalizing and unloading layers\n"); |
| struct KHRLayer *head = khrFirstLayer; |
| deinitLayer.dispatch = khrDeinitDispatch; |
| khrFirstLayer = &deinitLayer; |
| |
| while(head) { |
| struct KHRLayer *cur = head; |
| #ifdef CL_LAYER_INFO |
| free(cur->libraryName); |
| #endif |
| if (cur->p_clDeinitLayer) |
| { |
| cl_int res = cur->p_clDeinitLayer(); |
| if (CL_SUCCESS != res) |
| { |
| KHR_ICD_TRACE("error reported in layer deinitialization\n"); |
| } |
| } |
| if (unloadLibraries && !khrDisableLibraryUnloading) |
| khrIcdOsLibraryUnload(cur->library); |
| head = cur->next; |
| free(cur); |
| } |
| #endif // defined(CL_ENABLE_LAYERS) |
| |
| // free vendor in reverse order of their creation (back to front) |
| KHR_ICD_TRACE("finalizing and unloading vendors\n"); |
| while (lastVendor) { |
| KHRicdVendor *cur = lastVendor; |
| free(cur->suffix); |
| if (cur->unloadable && unloadLibraries && !khrDisableLibraryUnloading) |
| khrIcdOsLibraryUnload(cur->library); |
| lastVendor = cur->prev; |
| free(cur); |
| } |
| khrIcdVendors = NULL; |
| } |