Add first prototype of cllayerinfo. (#175)

* Add first prototype of cllayerinfo.

* Added scheme to silence layers during loading and exit.

* Added a first test of cllayerinfo.

* Fix lock if no layers are found.

* Improve style.

* Copy layer library name as it is transient.

* Add new required definitions.

* Remove deprecated OPENCL_ICD_LOADER_DISABLE_OPENCLON12 CMake option.

* Factor compile definitions.

* Add variable to disable cllayerinfo build.

* Refactor CMake test file.

* Remove options for now as suggested by the working group.

* Remove constants

* Include share.h for mingw

Co-authored-by: Ben Ashbaugh <ben.ashbaugh@intel.com>

* Add cllayerinfo to install and export target.

* Use CMakeDependOption.

Co-authored-by: Nagy-Egri Máté Ferenc <beiktatas+github@outlook.hu>

---------

Co-authored-by: Ben Ashbaugh <ben.ashbaugh@intel.com>
Co-authored-by: Nagy-Egri Máté Ferenc <beiktatas+github@outlook.hu>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 93facbe..e855054 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,6 +52,8 @@
 # It is currently needed default while the specification is being formalized,
 # and to study the performance impact.
 option (ENABLE_OPENCL_LAYERS "Enable OpenCL Layers" ON)
+include(CMakeDependentOption)
+cmake_dependent_option(ENABLE_OPENCL_LAYERINFO "Enable building cllayerinfo tool" ON ENABLE_OPENCL_LAYERS OFF)
 
 set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
 include(JoinPaths)
@@ -150,8 +152,7 @@
     target_link_libraries (OpenCL PUBLIC OpenCL::Headers)
 endif ()
 
-target_compile_definitions (OpenCL
-  PRIVATE
+set (OPENCL_COMPILE_DEFINITIONS
     CL_TARGET_OPENCL_VERSION=300
     OPENCL_ICD_LOADER_VERSION_MAJOR=3
     OPENCL_ICD_LOADER_VERSION_MINOR=0
@@ -159,6 +160,11 @@
     $<$<BOOL:${ENABLE_OPENCL_LAYERS}>:CL_ENABLE_LAYERS>
 )
 
+target_compile_definitions (OpenCL
+  PRIVATE
+    ${OPENCL_COMPILE_DEFINITIONS}
+)
+
 target_include_directories (OpenCL
   PRIVATE
     ${CMAKE_CURRENT_BINARY_DIR}
@@ -166,6 +172,44 @@
 )
 target_link_libraries (OpenCL PUBLIC ${CMAKE_DL_LIBS})
 
+if (ENABLE_OPENCL_LAYERINFO)
+
+  set (OPENCL_LAYER_INFO_SOURCES
+      loader/cllayerinfo.c
+      ${OPENCL_ICD_LOADER_SOURCES}
+  )
+
+  add_executable(cllayerinfo ${OPENCL_LAYER_INFO_SOURCES})
+
+  add_executable(OpenCL::cllayerinfo ALIAS cllayerinfo)
+
+  target_compile_definitions (cllayerinfo
+    PRIVATE
+      CL_LAYER_INFO
+      ${OPENCL_COMPILE_DEFINITIONS}
+  )
+
+  if (EXISTS ${OPENCL_ICD_LOADER_HEADERS_DIR}/CL/cl.h)
+      target_include_directories (cllayerinfo PUBLIC $<BUILD_INTERFACE:${OPENCL_ICD_LOADER_HEADERS_DIR}>)
+  else ()
+      target_link_libraries (cllayerinfo PUBLIC OpenCL::Headers)
+  endif ()
+
+  if (WIN32)
+    target_link_libraries (cllayerinfo PRIVATE cfgmgr32.lib runtimeobject.lib)
+  else ()
+    target_link_libraries (cllayerinfo PRIVATE ${CMAKE_THREAD_LIBS_INIT})
+  endif ()
+
+  target_link_libraries (cllayerinfo PUBLIC ${CMAKE_DL_LIBS})
+
+  target_include_directories (cllayerinfo
+    PRIVATE
+      ${CMAKE_CURRENT_BINARY_DIR}
+      loader
+  )
+endif ()
+
 option (OPENCL_ICD_LOADER_BUILD_TESTING "Enable support for OpenCL ICD Loader testing." OFF)
 
 if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR OPENCL_ICD_LOADER_BUILD_TESTING)
@@ -191,6 +235,15 @@
   OPTIONAL
 )
 
+if (ENABLE_OPENCL_LAYERINFO)
+  install(
+    TARGETS cllayerinfo
+    EXPORT OpenCLICDLoaderTargets
+    RUNTIME
+      DESTINATION ${CMAKE_INSTALL_BINDIR}
+  )
+endif()
+
 export(
   EXPORT OpenCLICDLoaderTargets
   FILE ${PROJECT_BINARY_DIR}/OpenCLICDLoader/OpenCLICDLoaderTargets.cmake
diff --git a/loader/cllayerinfo.c b/loader/cllayerinfo.c
new file mode 100644
index 0000000..5a85ba8
--- /dev/null
+++ b/loader/cllayerinfo.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2022 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 <stdio.h>
+#include <stdlib.h>
+#include <CL/cl_layer.h>
+#if defined(_WIN32)
+#include <io.h>
+#include <share.h>
+#include <sys/stat.h>
+#else
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+
+int stdout_bak, stderr_bak;
+
+// Temporarily deactivate stdout:
+// https://stackoverflow.com/a/4832902
+
+#if defined(_WIN32)
+#define SECURE 1
+#define OPEN _open
+#define OPEN_FLAGS _O_WRONLY
+#define CLOSE _close
+#define DUP _dup
+#define DUP2 _dup2
+#define NULL_STREAM "nul"
+#else
+#define OPEN open
+#define OPEN_FLAGS O_WRONLY
+#define CLOSE close
+#define DUP dup
+#define DUP2 dup2
+#define NULL_STREAM "/dev/null"
+#endif
+
+static inline int
+silence_stream(FILE *file, int fd)
+{
+    int new_fd, fd_bak;
+    fflush(file);
+    fd_bak = DUP(fd);
+#if defined(_WIN32) && SECURE
+    _sopen_s(&new_fd, NULL_STREAM, OPEN_FLAGS, _SH_DENYNO, _S_IWRITE);
+#else
+    new_fd = OPEN(NULL_STREAM, OPEN_FLAGS);
+#endif
+    DUP2(new_fd, fd);
+    CLOSE(new_fd);
+    return fd_bak;
+}
+
+static void silence_layers(void)
+{
+    stdout_bak = silence_stream(stdout, 1);
+    stderr_bak = silence_stream(stderr, 2);
+}
+
+static inline void
+restore_stream(FILE *file, int fd, int fd_bak)
+{
+    fflush(file);
+    DUP2(fd_bak, fd);
+    CLOSE(fd_bak);
+}
+
+static void restore_outputs(void)
+{
+    restore_stream(stdout, 1, stdout_bak);
+    restore_stream(stderr, 2, stderr_bak);
+}
+
+void printLayerInfo(const struct KHRLayer *layer)
+{
+    cl_layer_api_version api_version = 0;
+    pfn_clGetLayerInfo p_clGetLayerInfo = (pfn_clGetLayerInfo)(size_t)layer->p_clGetLayerInfo;
+    cl_int result = CL_SUCCESS;
+    size_t sz;
+
+    printf("%s:\n", layer->libraryName);
+    result = p_clGetLayerInfo(CL_LAYER_API_VERSION, sizeof(api_version), &api_version, NULL);
+    if (CL_SUCCESS == result)
+        printf("\tCL_LAYER_API_VERSION: %d\n", (int)api_version);
+
+    result = p_clGetLayerInfo(CL_LAYER_NAME, 0, NULL, &sz);
+    if (CL_SUCCESS == result)
+    {
+        char *name = (char *)malloc(sz);
+        if (name)
+        {
+            result = p_clGetLayerInfo(CL_LAYER_NAME, sz, name, NULL);
+            if (CL_SUCCESS == result)
+                 printf("\tCL_LAYER_NAME: %s\n", name);
+            free(name);
+        }
+    }
+}
+
+int main (int argc, char *argv[])
+{
+    (void)argc;
+    (void)argv;
+    silence_layers();
+    atexit(restore_outputs);
+    khrIcdInitialize();
+    restore_outputs();
+    atexit(silence_layers);
+    const struct KHRLayer *layer = khrFirstLayer;
+    while (layer)
+    {
+        printLayerInfo(layer);
+        layer = layer->next;
+    }
+    return 0;
+}
diff --git a/loader/icd.c b/loader/icd.c
index 5c227fa..bbd6ec3 100644
--- a/loader/icd.c
+++ b/loader/icd.c
@@ -285,6 +285,20 @@
         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 = (void *)(size_t)p_clGetLayerInfo;
+    }
+#endif
 
     if (khrFirstLayer) {
         targetDispatch = &(khrFirstLayer->dispatch);
diff --git a/loader/icd.h b/loader/icd.h
index 6727155..7d4a191 100644
--- a/loader/icd.h
+++ b/loader/icd.h
@@ -114,6 +114,12 @@
     struct _cl_icd_dispatch dispatch;
     // The next layer in the chain
     struct KHRLayer *next;
+#ifdef CL_LAYER_INFO
+    // The layer library name
+    char *libraryName;
+    // the pointer to the clGetLayerInfo funciton
+    void *p_clGetLayerInfo;
+#endif
 };
 
 // the global layer state
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index bf720ea..0d78a79 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -18,17 +18,30 @@
     NAME opencl_icd_loader_test
     COMMAND icd_loader_test
 )
-get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
-if(GENERATOR_IS_MULTI_CONFIG)
-    set_tests_properties(opencl_icd_loader_test
-        PROPERTIES
-            ENVIRONMENT OCL_ICD_FILENAMES=$<TARGET_FILE:OpenCLDriverStub>
-            WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIG>"
+
+if (ENABLE_OPENCL_LAYERINFO)
+    add_test (
+        NAME cllayerinfo_test
+        COMMAND cllayerinfo
     )
-else()
-    set_tests_properties(opencl_icd_loader_test
+endif ()
+
+get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if (GENERATOR_IS_MULTI_CONFIG)
+    set (TEST_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIG>")
+else ()
+    set (TEST_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
+endif()
+
+set_tests_properties(opencl_icd_loader_test
+    PROPERTIES
+        ENVIRONMENT OCL_ICD_FILENAMES=$<TARGET_FILE:OpenCLDriverStub>
+        WORKING_DIRECTORY "${TEST_WORKING_DIRECTORY}"
+)
+if (ENABLE_OPENCL_LAYERINFO)
+    set_tests_properties(cllayerinfo_test
         PROPERTIES
-            ENVIRONMENT OCL_ICD_FILENAMES=$<TARGET_FILE:OpenCLDriverStub>
-            WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
+            ENVIRONMENT OPENCL_LAYERS=$<TARGET_FILE:PrintLayer>
+            WORKING_DIRECTORY "${TEST_WORKING_DIRECTORY}"
     )
 endif()
diff --git a/test/layer/icd_print_layer.c b/test/layer/icd_print_layer.c
index 06525bf..d8bf462 100644
--- a/test/layer/icd_print_layer.c
+++ b/test/layer/icd_print_layer.c
@@ -17,34 +17,58 @@
  */
 
 #include "icd_print_layer.h"
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
 
 struct _cl_icd_dispatch dispatch;
 
 const struct _cl_icd_dispatch *tdispatch;
 
+static cl_layer_api_version api_version = CL_LAYER_API_VERSION_100;
+static const char name[] = "print_layer";
+
+static inline cl_int
+set_param_value(
+    size_t      param_value_size,
+    void       *param_value,
+    size_t     *param_value_size_ret,
+    size_t      src_size,
+    const void *src) {
+  if (param_value && param_value_size < src_size)
+    return CL_INVALID_VALUE;
+  if (param_value)
+    memcpy(param_value, src, src_size);
+  if (param_value_size_ret)
+    *param_value_size_ret = src_size;
+  return CL_SUCCESS;
+}
+
 CL_API_ENTRY cl_int CL_API_CALL
 clGetLayerInfo(
     cl_layer_info  param_name,
     size_t         param_value_size,
     void          *param_value,
     size_t        *param_value_size_ret) {
+  size_t sz = 0;
+  const void *src = NULL;
   if (param_value_size && !param_value)
     return CL_INVALID_VALUE;
   if (!param_value && !param_value_size_ret)
     return CL_INVALID_VALUE;
   switch (param_name) {
   case CL_LAYER_API_VERSION:
-    if (param_value_size < sizeof(cl_layer_api_version))
-      return CL_INVALID_VALUE;
-    if (param_value)
-      *((cl_layer_api_version *)param_value) = CL_LAYER_API_VERSION_100;
-    if (param_value_size_ret)
-      *param_value_size_ret = sizeof(cl_layer_api_version);
+    sz = sizeof(api_version);
+    src = &api_version;
+    break;
+  case CL_LAYER_NAME:
+    sz = sizeof(name);
+    src = name;
     break;
   default:
     return CL_INVALID_VALUE;
   }
-  return CL_SUCCESS;
+  return set_param_value(param_value_size, param_value, param_value_size_ret, sz, src);
 }
 
 CL_API_ENTRY cl_int CL_API_CALL
@@ -53,7 +77,7 @@
     const struct _cl_icd_dispatch  *target_dispatch,
     cl_uint                        *num_entries_out,
     const struct _cl_icd_dispatch **layer_dispatch_ret) {
-  if (!target_dispatch || !layer_dispatch_ret ||!num_entries_out || num_entries < sizeof(dispatch)/sizeof(dispatch.clGetPlatformIDs))
+  if (!target_dispatch || !layer_dispatch_ret || !num_entries_out || num_entries < sizeof(dispatch)/sizeof(dispatch.clGetPlatformIDs))
     return CL_INVALID_VALUE;
 
   _init_dispatch();
@@ -61,6 +85,7 @@
   tdispatch = target_dispatch;
   *layer_dispatch_ret = &dispatch;
   *num_entries_out = sizeof(dispatch)/sizeof(dispatch.clGetPlatformIDs);
+
   return CL_SUCCESS;
 }