diff --git a/src/Makefile b/src/Makefile index 886f8bc..eba1e5b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -16,7 +16,7 @@ all: $(maintarget) # Objects we'll build MAIN_OBJS := libblastrampoline.o dl_utils.o env_utils.o config.o \ - autodetection.o \ + autodetection.o lib_version.o \ threading.o deepbindless.o trampolines/trampolines_$(ARCH).o # Include win_utils.c on windws diff --git a/src/lib_version.c b/src/lib_version.c new file mode 100644 index 0000000..b168f11 --- /dev/null +++ b/src/lib_version.c @@ -0,0 +1,266 @@ +#include "libblastrampoline_internal.h" + +#include + +/* We need to ask MKL for information about itself to get better information for the config. + * + * These are from mkl_types.h + */ +typedef struct { + int MajorVersion; + int MinorVersion; + int UpdateVersion; + int PatchVersion; + char * ProductStatus; + char * Build; + char * Processor; + char * Platform; +} MKLVersion; + + +// These are the addresses of the LBT functions for ilaver we export +extern void ** ilaver_; +extern void ** ilaver_64_; + +#define LEN_INFO_STR 512 //< Length of the entire string used to hold the library info +#define LEN_SHORT_INFO 300 //< Length of the string used to get information from individual libraries +#define LEN_LAPACK_INFO 25 //< Length of the string containing the LAPACK version info +#define LEN_LBT_INFO 50 //< Length of the string containing LBT forwarding information + +void lbt_info_string(char* lbt_info, lbt_library_info_t* library) +{ + char lbt_int[6]; + char lbt_f2c[6]; + char lbt_cmplx[6]; + char lbt_cblas[6]; + + // Integer interface detected + switch (library->interface) { + case LBT_INTERFACE_LP64: + snprintf(lbt_int, 6, "LP64"); + break; + + case LBT_INTERFACE_ILP64: + snprintf(lbt_int, 6, "ILP64"); + break; + + default: + snprintf(lbt_int, 6, "Unkwn"); + break; + } + + // F2C interface detected + switch (library->f2c) + { + case LBT_F2C_PLAIN: + snprintf(lbt_f2c, 6, "Plain"); + break; + + case LBT_F2C_REQUIRED: + snprintf(lbt_f2c, 6, "Reqd"); + break; + + default: + snprintf(lbt_f2c, 6, "Unkwn"); + break; + } + + // Complex return style + switch (library->complex_retstyle) + { + case LBT_COMPLEX_RETSTYLE_NORMAL: + snprintf(lbt_f2c, 6, "Nrml"); + break; + + case LBT_COMPLEX_RETSTYLE_ARGUMENT: + snprintf(lbt_f2c, 6, "Arg"); + break; + + case LBT_COMPLEX_RETSTYLE_FNDA: + snprintf(lbt_f2c, 6, "FNDA"); + break; + + default: + snprintf(lbt_f2c, 6, "Unkwn"); + break; + } + + // CBLAS type + switch (library->cblas) + { + case LBT_CBLAS_CONFORMANT: + snprintf(lbt_f2c, 6, "Nrml"); + break; + + case LBT_CBLAS_DIVERGENT: + snprintf(lbt_f2c, 6, "Diver"); + break; + + default: + snprintf(lbt_f2c, 6, "Unkwn"); + break; + } + + // Form the final string + snprintf(lbt_info, LEN_LBT_INFO, "; LBT: %s, f2c %s, cmplx , cblas %s", lbt_int, lbt_f2c, lbt_cmplx, lbt_cblas); +} + +// Every library implements their version string handling differently, so this is a ratsnest +// of conditions for the various libraries to try and get information that is useful to us... +char* lbt_get_library_info(lbt_library_info_t* library) +{ + // Keep the string as a static lifetime so that it never gets deleted + static char info[LEN_INFO_STR]; + + // Remove stale information from info + info[0] = '\0'; + + // Get LAPACK information, which will say the LAPACK API the library uses + char lapack_ver[LEN_LAPACK_INFO]; + char symbol_name_ilaver[MAX_SYMBOL_LEN]; + build_symbol_name(symbol_name_ilaver, "ilaver_", library->suffix); + void* (*fptr_ilaver)(int*, int*, int*) = lookup_symbol(library->handle, symbol_name_ilaver); + + // Make sure we don't accidentally call our own version of ilaver + if ((fptr_ilaver != NULL) && ((void*)fptr_ilaver != &ilaver_) && ((void*)fptr_ilaver != &ilaver_64_)) { + int lapack_major = 0; + int lapack_minor = 0; + int lapack_patch = 0; + + fptr_ilaver(&lapack_major, &lapack_minor, &lapack_patch); + snprintf(lapack_ver, LEN_LAPACK_INFO, "; LAPACK v%d.%d.%d", lapack_major, lapack_minor, lapack_patch); + } else { + // Clear the version string if we can't compute one + lapack_ver[0] = '\0'; + } + + // Build a string to capture all the LBT forwarding information + char lbt_info[LEN_LBT_INFO]; + lbt_info_string(lbt_info, library); + + // OpenBLAS, config will have same suffix as the other functions + char symbol_name[MAX_SYMBOL_LEN]; + build_symbol_name(symbol_name, "openblas_get_config", library->suffix); + char* (*fptr_openblas)() = lookup_symbol(library->handle, symbol_name); + if (fptr_openblas != NULL) { + char* tmp_info = fptr_openblas(); + + snprintf(info, LEN_INFO_STR, "%s%s%s", tmp_info, lapack_ver, lbt_info); + return info; + } + + // MKL + char* (*fptr_mkl)(char*, int) = lookup_symbol(library->handle, "mkl_get_version_string"); + if (fptr_mkl != NULL) { + char mkl_info[LEN_SHORT_INFO]; + memset(mkl_info, 0, LEN_SHORT_INFO); + + fptr_mkl(mkl_info, LEN_SHORT_INFO); + + // MKL pads the output with spaces, so trim it to only the needed parts + char* back = mkl_info + strlen(mkl_info); + while(isspace(*--back)); + *(back+1) = '\0'; + + snprintf(info, LEN_INFO_STR, "%s%s%s", mkl_info, lapack_ver, lbt_info); + return info; + } + + // NVPL + int (*fptr_nvpl)() = lookup_symbol(library->handle, "nvpl_blas_get_version"); + if (fptr_nvpl != NULL) { + int version = fptr_nvpl(); + + // The version int is of the form: + // NVPL_BLAS_VERSION_MAJOR * 10000 + NVPL_BLAS_VERSION_MINOR * 100 + NVPL_BLAS_VERSION_PATCH + int major = version / 10000; + int minor = (version - (major*10000)) / 100; + int patch = (version - (major*10000) - (minor*100)); + + snprintf(info, LEN_INFO_STR, "NVPL %d.%d.%d%s%s", major, minor, patch, lapack_ver, lbt_info); + return info; + } + + // ARMPL + int (*fptr_armpl)(int*, int*, int*, char**) = lookup_symbol(library->handle, "armplversion"); + if (fptr_armpl != NULL) { + int major = 0, minor = 0, patch = 0; + char* tag = NULL; + fptr_armpl(&major, &minor, &patch, &tag); + + snprintf(info, LEN_INFO_STR, "ARMPL %d.%d.%d.%s%s%s", major, minor, patch, tag, lapack_ver, lbt_info); + return info; + } + + // AOCL and BLIS share the same methods + char * (*fptr_blis_ver)() = lookup_symbol(library->handle, "bli_info_get_version_str"); + if (fptr_blis_ver != NULL) { + int aocl_detected = 0; + int int_size = 0; + char* config = NULL; + + // Raw version string + char* ver_str = fptr_blis_ver(); + + // Integer size + int (*fptr_blis_int)() = lookup_symbol(library->handle, "bli_info_get_blas_int_type_size"); + if (fptr_blis_int != NULL) { + int_size = fptr_blis_int(); + } + + // Current architecture + int (*fptr_blis_arch)() = lookup_symbol(library->handle, "bli_arch_query_id"); + char* (*fptr_blis_arch_str)(int) = lookup_symbol(library->handle, "bli_arch_string"); + if (fptr_blis_arch != NULL && fptr_blis_arch_str != NULL) { + int arch = fptr_blis_arch(); + config = fptr_blis_arch_str(arch); + } + + // Determine if the library is AOCL or not - it uses the same exact symbols as BLIS, but it also exposes some new symbols + // that are AOCL-only that we can use to check if it is AOCL. + int (*fptr_aocl)() = lookup_symbol(library->handle, "bli_aocl_enable_instruction_query"); + if (fptr_aocl != NULL) { + // AOCL + aocl_detected = 1; + } + + snprintf(info, LEN_INFO_STR, "%s %s, %d-bit integer, %s%s%s", + aocl_detected == 1 ? "AMD" : "BLIS", // AOCL includes it's name in the string, BLIS does not + ver_str, + int_size, + config, + lapack_ver, + lbt_info); + + return info; + } + + // FlexiBLAS + void (*fptr_flexi_ver)(int*, int*, int*) = lookup_symbol(library->handle, "flexiblas_get_version"); + if (fptr_flexi_ver != NULL) { + int major = 0, minor = 0, patch = 0; + char backend[LEN_SHORT_INFO]; + + fptr_flexi_ver(&major, &minor, &patch); + + int(*fptr_flexi_backend)(char*, int) = lookup_symbol(library->handle, "flexiblas_current_backend"); + if (fptr_flexi_backend != NULL) { + fptr_flexi_backend(backend, LEN_SHORT_INFO); + } + + snprintf(info, LEN_INFO_STR, "FlexiBLAS %d.%d.%d, backend: %s%s%s", major, minor, patch, backend, lapack_ver, lbt_info); + return info; + } + + // Apple Accelerate + // Look for a special Apple-only symbol to detect the Accelerate library + void (*fptr_appaccel)() = lookup_symbol(library->handle, "appleblas_sgeadd"); + if (fptr_appaccel != NULL) { + snprintf(info, LEN_INFO_STR, "Apple Accelerate%s%s", lapack_ver, lbt_info); + return info; + } + + // Fallback is unknown, but still report LAPACK (if possible) and LBT information + snprintf(info, LEN_INFO_STR, "Unknown library%s%s", lapack_ver, lbt_info); + return info; +} diff --git a/src/libblastrampoline.h b/src/libblastrampoline.h index 0293cd9..f25c49f 100644 --- a/src/libblastrampoline.h +++ b/src/libblastrampoline.h @@ -239,6 +239,13 @@ LBT_DLLEXPORT const void * lbt_get_forward(const char * symbol_name, int32_t int */ LBT_DLLEXPORT int32_t lbt_set_forward(const char * symbol_name, const void * addr, int32_t interface, int32_t complex_retstyle, int32_t f2c, int32_t verbose); + +/* + * Gets the information string about the requested `library` containing relevant information about + * its version and configuration (if available). + */ +LBT_DLLEXPORT char * lbt_get_library_info(lbt_library_info_t * library); + #ifdef __cplusplus } // extern "C" #endif diff --git a/test/backends/accelerate/runtests.jl b/test/backends/accelerate/runtests.jl index 54b03e1..27e924e 100644 --- a/test/backends/accelerate/runtests.jl +++ b/test/backends/accelerate/runtests.jl @@ -130,4 +130,16 @@ end end end +@testset "Accelerate info" begin + lbt_forward(lbt_handle, libacc; clear=true) + + # Get the first loaded library + config = lbt_get_config(lbt_handle) + lib = unsafe_load(config.loaded_libs, 1) + + info_str = lbt_get_library_info(lbt_handle, lib) + + @test occursin("Apple", info_str) +end + end # if Sys.isapple() diff --git a/test/backends/direct/runtests.jl b/test/backends/direct/runtests.jl index 7703c7a..17f2c48 100644 --- a/test/backends/direct/runtests.jl +++ b/test/backends/direct/runtests.jl @@ -210,6 +210,18 @@ end @test length(self_traces) == 3 end +@testset "OpenBLAS info" begin + lbt_forward(lbt_handle, OpenBLAS_jll.libopenblas_path; clear=true) + + # Get the first loaded library + config = lbt_get_config(lbt_handle) + lib = unsafe_load(config.loaded_libs, 1) + + info_str = lbt_get_library_info(lbt_handle, lib) + + @test occursin("OpenBLAS", info_str) +end + if MKL_jll.is_available() && Sys.ARCH == :x86_64 # Since MKL v2022, we can explicitly link against ILP64-suffixed symbols @testset "MKL v2022 ILP64 loading" begin @@ -316,4 +328,16 @@ if MKL_jll.is_available() && Sys.ARCH == :x86_64 @test ccall((:MKL_Domain_Get_Max_Threads, libmkl_rt), Cint, (Cint,), 1) == nthreads @test ccall((:MKL_Domain_Get_Max_Threads, libmkl_rt), Cint, (Cint,), 2) != nthreads end + + @testset "MKL info" begin + lbt_forward(lbt_handle, libmkl_rt; clear=true) + + # Get the first loaded library + config = lbt_get_config(lbt_handle) + lib = unsafe_load(config.loaded_libs, 1) + + info_str = lbt_get_library_info(lbt_handle, lib) + + @test occursin("oneAPI", info_str) + end end diff --git a/test/utils.jl b/test/utils.jl index 78cc3a9..c03a1c7 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -214,6 +214,10 @@ function lbt_get_default_func(handle) return ccall(dlsym(handle, :lbt_get_default_func), Ptr{Cvoid}, ()) end +function lbt_get_library_info(handle, lib) + return strip(unsafe_string(ccall(dlsym(handle, :lbt_get_library_info), Ptr{UInt8}, (Ptr{Cvoid},), lib))) +end + # Helpers for inspecting an `lbt_config_t` from the in-process (`direct`/`accelerate`) tests function unpack_loaded_libraries(config::lbt_config_t) libs = LBTLibraryInfo[]