Skip to content
Open
2 changes: 1 addition & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
266 changes: 266 additions & 0 deletions src/lib_version.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
#include "libblastrampoline_internal.h"

#include <ctype.h>

/* 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;
}
7 changes: 7 additions & 0 deletions src/libblastrampoline.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 12 additions & 0 deletions test/backends/accelerate/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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()
24 changes: 24 additions & 0 deletions test/backends/direct/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
4 changes: 4 additions & 0 deletions test/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
Loading