Skip to content

Commit 07c3509

Browse files
committed
Add LBT_FORCE_* environment variable overrides
This provides a flexible mechanism through which LBT's autodetection facilities can be overridden. It enables debugging strange behaviors without needing to recompile LBT from scratch to disable a certain layer of its behavior.
1 parent 48f2cf6 commit 07c3509

File tree

8 files changed

+151
-10
lines changed

8 files changed

+151
-10
lines changed

src/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ maintarget=$(word 1,$(TARGET_LIBRARIES))
1515
all: $(maintarget)
1616

1717
# Objects we'll build
18-
MAIN_OBJS := libblastrampoline.o dl_utils.o config.o \
18+
MAIN_OBJS := libblastrampoline.o dl_utils.o env_utils.o config.o \
1919
autodetection.o \
2020
threading.o deepbindless.o trampolines/trampolines_$(ARCH).o
2121

src/autodetection.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ const char * autodetect_symbol_suffix(void * handle, const char * suffix_hint) {
9696
* incorrect `N` to cause it to change its return value based on how it is interpreting arugments.
9797
*/
9898
int32_t autodetect_blas_interface(void * isamax_addr) {
99+
if (env_lowercase_match("LBT_FORCE_INTERFACE", "ilp64")) {
100+
return LBT_INTERFACE_ILP64;
101+
}
102+
if (env_lowercase_match("LBT_FORCE_INTERFACE", "lp64")) {
103+
return LBT_INTERFACE_LP64;
104+
}
99105
// Typecast to function pointer for easier usage below
100106
int64_t (*isamax)(int64_t *, float *, int64_t *) = isamax_addr;
101107

@@ -145,6 +151,12 @@ int32_t autodetect_blas_interface(void * isamax_addr) {
145151
* and determine if the internal pointer dereferences were 32-bit or 64-bit.
146152
*/
147153
int32_t autodetect_lapack_interface(void * dpotrf_addr) {
154+
if (env_lowercase_match("LBT_FORCE_INTERFACE", "ilp64")) {
155+
return LBT_INTERFACE_ILP64;
156+
}
157+
if (env_lowercase_match("LBT_FORCE_INTERFACE", "lp64")) {
158+
return LBT_INTERFACE_LP64;
159+
}
148160
// Typecast to function pointer for easier usage below
149161
void (*dpotrf)(char *, int64_t *, double *, int64_t *, int64_t *) = dpotrf_addr;
150162

@@ -196,6 +208,12 @@ int32_t autodetect_interface(void * handle, const char * suffix) {
196208

197209
#ifdef COMPLEX_RETSTYLE_AUTODETECTION
198210
int32_t autodetect_complex_return_style(void * handle, const char * suffix) {
211+
if (env_lowercase_match("LBT_FORCE_RETSTYLE", "normal")) {
212+
return LBT_COMPLEX_RETSTYLE_NORMAL;
213+
}
214+
if (env_lowercase_match("LBT_FORCE_RETSTYLE", "argument")) {
215+
return LBT_COMPLEX_RETSTYLE_ARGUMENT;
216+
}
199217
char symbol_name[MAX_SYMBOL_LEN];
200218

201219
build_symbol_name(symbol_name, "zdotc_", suffix);
@@ -241,6 +259,13 @@ int32_t autodetect_complex_return_style(void * handle, const char * suffix) {
241259

242260
#ifdef F2C_AUTODETECTION
243261
int32_t autodetect_f2c(void * handle, const char * suffix) {
262+
if (env_lowercase_match("LBT_FORCE_F2C", "plain")) {
263+
return LBT_F2C_PLAIN;
264+
}
265+
if (env_lowercase_match("LBT_FORCE_F2C", "required")) {
266+
return LBT_F2C_REQUIRED;
267+
}
268+
244269
char symbol_name[MAX_SYMBOL_LEN];
245270

246271
// Attempt BLAS `sdot()` test
@@ -278,6 +303,13 @@ int32_t autodetect_f2c(void * handle, const char * suffix) {
278303

279304
#ifdef CBLAS_DIVERGENCE_AUTODETECTION
280305
int32_t autodetect_cblas_divergence(void * handle, const char * suffix) {
306+
if (env_lowercase_match("LBT_FORCE_CBLAS", "conformant")) {
307+
return LBT_CBLAS_CONFORMANT;
308+
}
309+
if (env_lowercase_match("LBT_FORCE_CBLAS", "divergent")) {
310+
return LBT_CBLAS_DIVERGENT;
311+
}
312+
281313
char symbol_name[MAX_SYMBOL_LEN];
282314

283315
build_symbol_name(symbol_name, "zdotc_", suffix);

src/env_utils.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include "libblastrampoline_internal.h"
2+
#include <ctype.h>
3+
4+
const char * env_lowercase(const char * env_name) {
5+
// Get environment value, if it's not set, return false
6+
char * env_value = getenv(env_name);
7+
if (env_value == NULL) {
8+
return NULL;
9+
}
10+
11+
// If it is set, convert to lowercase.
12+
env_value = strdup(env_value);
13+
for (size_t idx=0; idx<strlen(env_value); ++idx) {
14+
env_value[idx] = tolower(env_value[idx]);
15+
}
16+
return env_value;
17+
}
18+
19+
20+
uint8_t env_lowercase_match(const char * env_name, const char * value) {
21+
const char * env_value = env_lowercase(env_name);
22+
if (env_value == NULL) {
23+
return 0;
24+
}
25+
26+
int ret = strcmp(env_value, value) == 0;
27+
free((void *)env_value);
28+
return ret;
29+
}
30+
31+
uint8_t env_lowercase_match_any(const char * env_name, uint32_t num_values, ...) {
32+
va_list args;
33+
va_start(args, num_values);
34+
35+
// Get environment value
36+
const char * env_value = env_lowercase(env_name);
37+
if (env_value == NULL) {
38+
return 0;
39+
}
40+
41+
// Search through our varargs for a match
42+
for (uint32_t idx=0; idx<num_values; idx++ ) {
43+
const char *value = va_arg(args, const char *);
44+
if (strcmp(env_value, value) == 0) {
45+
free((void *)env_value);
46+
return 1;
47+
}
48+
}
49+
free((void *)env_value);
50+
return 0;
51+
}
52+
53+
// Check to see if `env_name` matches any "boolean"-like
54+
uint8_t env_match_bool(const char * env_name, uint8_t default_value) {
55+
if (env_lowercase_match_any(env_name, 3, "0", "false", "no")) {
56+
return 0;
57+
}
58+
if (env_lowercase_match_any(env_name, 3, "1", "true", "yes")) {
59+
return 1;
60+
}
61+
return default_value;
62+
}

src/libblastrampoline.c

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,9 @@ __attribute__((constructor)) void init(void) {
416416
// Initialize config structures
417417
init_config();
418418

419-
// If LBT_VERBOSE == "1", the startup invocation should be verbose
419+
// If LBT_VERBOSE is set, the startup invocation should be verbose
420420
int verbose = 0;
421-
const char * verbose_str = getenv("LBT_VERBOSE");
422-
if (verbose_str != NULL && strcmp(verbose_str, "1") == 0) {
421+
if (env_match_bool("LBT_VERBOSE", 0)) {
423422
verbose = 1;
424423
printf("libblastrampoline initializing from %s\n", lookup_self_path());
425424
}
@@ -428,17 +427,15 @@ __attribute__((constructor)) void init(void) {
428427
// If LBT_USE_RTLD_DEEPBIND == "0", we avoid using RTLD_DEEPBIND on a
429428
// deepbind-capable system. This is mostly useful for sanitizers, which
430429
// abhor such library loading shenanigans.
431-
const char * deepbindless_str = getenv("LBT_USE_RTLD_DEEPBIND");
432-
if (deepbindless_str != NULL && strcmp(deepbindless_str, "0") == 0) {
430+
if (env_match_bool("LBT_USE_RTLD_DEEPBIND", 1)) {
433431
use_deepbind = 0x00;
434432
if (verbose) {
435433
printf("LBT_USE_RTLD_DEEPBIND=0 detected; avoiding usage of RTLD_DEEPBIND\n");
436434
}
437435
}
438436
#endif // !defined(LBT_DEEPBINDLESS)
439437

440-
const char * strict_str = getenv("LBT_STRICT");
441-
if (strict_str != NULL && strcmp(strict_str, "1") == 0) {
438+
if (env_match_bool("LBT_STRICT", 0)) {
442439
if (verbose) {
443440
printf("LBT_STRICT=1 detected; calling missing symbols will print an error, then exit\n");
444441
}

src/libblastrampoline_internal.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <stdint.h>
33
#include <stdlib.h>
44
#include <string.h>
5+
#include <stdarg.h>
56
#include <unistd.h>
67

78
// Load in our publicly-defined functions/types
@@ -75,6 +76,11 @@ void * lookup_self_symbol(const char * symbol_name);
7576
const char * lookup_self_path();
7677
void close_library(void * handle);
7778

79+
// Functions in `env_utils.c`
80+
uint8_t env_lowercase_match(const char * env_name, const char * value);
81+
uint8_t env_lowercase_match_any(const char * env_name, uint32_t num_values, ...);
82+
uint8_t env_match_bool(const char * env_name, uint8_t default_value);
83+
7884
// Functions in `autodetection.c`
7985
void build_symbol_name(char * out, const char *symbol_name, const char *suffix);
8086
const char * autodetect_symbol_suffix(void * handle, const char * suffix_hint);

test/isamax_test/Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
include ../../src/Make.inc
2+
3+
all: $(prefix)/isamax_test$(EXE)
4+
5+
$(prefix):
6+
@mkdir -p $@
7+
8+
$(prefix)/isamax_test$(EXE): isamax_test.c | $(prefix)
9+
@$(CC) -o $@ $(CFLAGS) $^ $(LDFLAGS)
10+
11+
clean:
12+
@rm -f $(prefix)/isamax_test$(EXE)
13+
14+
run: $(prefix)/isamax_test$(EXE)
15+
@$(prefix)/isamax_test$(EXE)

test/isamax_test/isamax_test.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <stdio.h>
2+
#include <stdint.h>
3+
4+
extern int64_t isamax_64_(int64_t *, float *, int64_t *);
5+
6+
#define N 4
7+
int main()
8+
{
9+
int64_t n = 0xffffffff00000003;
10+
float X[3] = {1.0f, 2.0f, 1.0f};
11+
int64_t incx = 1;
12+
13+
int64_t max_idx = isamax_64_(&n, X, &incx);
14+
printf("max_idx: %lld\n", max_idx);
15+
return 0;
16+
}

test/runtests.jl

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function run_test((test_name, test_expected_outputs, expect_success), libblas_na
5454
cmd = `$(dir)/$(test_name)`
5555
p, output = capture_output(addenv(cmd, env))
5656

57-
expected_return_value = success(p) ^ expect_success
57+
expected_return_value = !xor(success(p), expect_success)
5858
if !expected_return_value
5959
@error("Test failed", env, p.exitcode, p.termsignal, expect_success)
6060
println(output)
@@ -63,7 +63,7 @@ function run_test((test_name, test_expected_outputs, expect_success), libblas_na
6363

6464
# Expect to see the path to `libblastrampoline` within the output,
6565
# since we have `LBT_VERBOSE=1` and at startup, it announces its own path:
66-
if startswith(libblas_name, "blastrampoline")
66+
if startswith(libblas_name, "blastrampoline") && expect_success
6767
lbt_libdir = first(libdirs)
6868
@test occursin(lbt_libdir, output)
6969
end
@@ -131,12 +131,25 @@ lbt_dir = joinpath(lbt_dir, binlib)
131131
@testset "LBT -> OpenBLAS_jll ($(openblas_interface))" begin
132132
libdirs = unique(vcat(lbt_dir, OpenBLAS_jll.LIBPATH_list..., CompilerSupportLibraries_jll.LIBPATH_list...))
133133
run_all_tests(blastrampoline_link_name(), libdirs, openblas_interface, OpenBLAS_jll.libopenblas_path)
134+
135+
# Test that setting bad `LBT_FORCE_*` values actually breaks things
136+
withenv("LBT_FORCE_RETSTYLE" => "ARGUMENT") do
137+
zdotc_fail = ("zdotc_test", [], false)
138+
run_test(zdotc_fail, blastrampoline_link_name(), libdirs, openblas_interface, OpenBLAS_jll.libopenblas_path)
139+
end
134140
end
135141

136142
# And again, but this time with OpenBLAS32_jll
137143
@testset "LBT -> OpenBLAS32_jll (LP64)" begin
138144
libdirs = unique(vcat(lbt_dir, OpenBLAS32_jll.LIBPATH_list..., CompilerSupportLibraries_jll.LIBPATH_list...))
139145
run_all_tests(blastrampoline_link_name(), libdirs, :LP64, OpenBLAS32_jll.libopenblas_path)
146+
147+
# Test that setting bad `LBT_FORCE_*` values actually breaks things
148+
withenv("LBT_FORCE_INTERFACE" => "ILP64") do
149+
# `max_idx: 2` is incorrect, it's what happens when ILP64 data is given to an LP64 backend
150+
isamax_fail = ("isamax_test", ["max_idx: 2"], true)
151+
run_test(isamax_fail, blastrampoline_link_name(), libdirs, :ILP64, OpenBLAS32_jll.libopenblas_path)
152+
end
140153
end
141154

142155
# Test against MKL_jll using `libmkl_rt`, which is :LP64 by default

0 commit comments

Comments
 (0)