Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions experiments/libomp_libiomp_repro.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Minimal reproducer for libomp/libiomp incompatibility (LLVM bug 43565).
* Call OpenMP functions from libomp first, then from libiomp.
*/
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

typedef int (*omp_get_max_threads_fn)(void);
typedef int (*omp_get_num_threads_fn)(void);

int main(void) {
void *libomp = dlopen("libomp.so", RTLD_NOW | RTLD_GLOBAL);
void *libiomp = dlopen("libiomp5.so", RTLD_NOW | RTLD_GLOBAL);

if (!libomp) {
fprintf(stderr, "Failed to load libomp: %s\n", dlerror());
return 1;
}
if (!libiomp) {
fprintf(stderr, "Failed to load libiomp5: %s\n", dlerror());
return 1;
}

omp_get_max_threads_fn omp_get_max_threads_llvm =
(omp_get_max_threads_fn)dlsym(libomp, "omp_get_max_threads");
omp_get_max_threads_fn omp_get_max_threads_intel =
(omp_get_max_threads_fn)dlsym(libiomp, "omp_get_max_threads");

if (!omp_get_max_threads_llvm || !omp_get_max_threads_intel) {
fprintf(stderr, "Failed to resolve omp_get_max_threads symbols\n");
return 1;
}

printf("Calling libomp omp_get_max_threads()...\n");
fflush(stdout);
int llvm_threads = omp_get_max_threads_llvm();
printf("libomp returned %d\n", llvm_threads);
fflush(stdout);

printf("Calling libiomp omp_get_max_threads()...\n");
fflush(stdout);
int intel_threads = omp_get_max_threads_intel();
printf("libiomp returned %d\n", intel_threads);
fflush(stdout);

printf("SUCCESS: both calls completed\n");
return 0;
}
54 changes: 54 additions & 0 deletions experiments/libomp_libiomp_variants.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Variants of the libomp/libiomp reproducer.
* Usage: ./libomp_libiomp_variants [llvm-first|intel-first|load-only]
*/
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int (*omp_get_max_threads_fn)(void);

static int call_llvm_first(void) {
void *libomp = dlopen("libomp.so", RTLD_NOW | RTLD_GLOBAL);
void *libiomp = dlopen("libiomp5.so", RTLD_NOW | RTLD_GLOBAL);
if (!libomp || !libiomp) return 1;
omp_get_max_threads_fn f_llvm = (omp_get_max_threads_fn)dlsym(libomp, "omp_get_max_threads");
omp_get_max_threads_fn f_intel = (omp_get_max_threads_fn)dlsym(libiomp, "omp_get_max_threads");
printf("llvm=%d\n", f_llvm());
fflush(stdout);
printf("intel=%d\n", f_intel());
fflush(stdout);
return 0;
}

static int call_intel_first(void) {
void *libomp = dlopen("libomp.so", RTLD_NOW | RTLD_GLOBAL);
void *libiomp = dlopen("libiomp5.so", RTLD_NOW | RTLD_GLOBAL);
if (!libomp || !libiomp) return 1;
omp_get_max_threads_fn f_llvm = (omp_get_max_threads_fn)dlsym(libomp, "omp_get_max_threads");
omp_get_max_threads_fn f_intel = (omp_get_max_threads_fn)dlsym(libiomp, "omp_get_max_threads");
printf("intel=%d\n", f_intel());
fflush(stdout);
printf("llvm=%d\n", f_llvm());
fflush(stdout);
return 0;
}

static int load_only(void) {
void *libomp = dlopen("libomp.so", RTLD_NOW | RTLD_GLOBAL);
void *libiomp = dlopen("libiomp5.so", RTLD_NOW | RTLD_GLOBAL);
if (!libomp || !libiomp) return 1;
printf("loaded both\n");
fflush(stdout);
return 0;
}

int main(int argc, char **argv) {
const char *mode = argc > 1 ? argv[1] : "llvm-first";
if (strcmp(mode, "llvm-first") == 0) return call_llvm_first();
if (strcmp(mode, "intel-first") == 0) return call_intel_first();
if (strcmp(mode, "load-only") == 0) return load_only();
fprintf(stderr, "unknown mode %s\n", mode);
return 2;
}
171 changes: 171 additions & 0 deletions experiments/run_experiments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""Experiments to test libomp + libiomp compatibility on Linux."""

import json
import os
import subprocess
import sys
import time
import warnings

from threadpoolctl import ThreadpoolController, threadpool_info, threadpool_limits


def run_with_timeout(cmd, timeout=30):
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
env=os.environ.copy(),
)
return {
"returncode": result.returncode,
"stdout": result.stdout,
"stderr": result.stderr,
"timed_out": False,
}
except subprocess.TimeoutExpired as exc:
return {
"returncode": None,
"stdout": exc.stdout or "",
"stderr": exc.stderr or "",
"timed_out": True,
}


def print_section(title):
print("\n" + "=" * 70)
print(title)
print("=" * 70)


def library_versions(conda_prefix):
info = {}
for lib in ("libiomp5.so", "libomp.so"):
path = os.path.join(conda_prefix, "lib", lib)
if os.path.exists(path):
info[lib] = path
# system libomp from llvm apt
for candidate in (
"/usr/lib/llvm-18/lib/libomp.so",
"/usr/lib/x86_64-linux-gnu/libomp.so",
):
if os.path.exists(candidate):
info["libomp.so (system)"] = candidate
for name, path in info.items():
try:
out = subprocess.check_output(["strings", path], text=True, stderr=subprocess.DEVNULL)
version_lines = [l for l in out.splitlines() if "OpenMP" in l or "version" in l.lower()][:5]
print(f"{name}: {path}")
for line in version_lines:
print(f" {line}")
except Exception as exc:
print(f"{name}: {path} (could not read: {exc})")
return info


def main():
print_section("Environment")
print(f"Python: {sys.version}")
print(f"Platform: {sys.platform}")
conda_prefix = os.environ.get("CONDA_PREFIX", "")
print(f"CONDA_PREFIX: {conda_prefix}")

try:
import numpy
import scipy

print(f"numpy: {numpy.__version__}")
print(f"scipy: {scipy.__version__}")
except ImportError as exc:
print(f"numpy/scipy not available: {exc}")

print_section("Library versions")
library_versions(conda_prefix)

print_section("Loaded OpenMP libraries (before imports)")
# Import extensions and numpy to load both runtimes
import tests._openmp_test_helper.openmp_helpers_outer # noqa: F401
import numpy.linalg # noqa: F401

with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
controller = ThreadpoolController()
prefixes = [c.prefix for c in controller.lib_controllers]
print("Prefixes:", prefixes)
print("Info:")
print(json.dumps(controller.info(), indent=2))
print(f"Warnings raised: {len(caught)}")
for w in caught:
print(f" {w.category.__name__}: {str(w.message)[:200]}")

print_section("Experiment 1: nested OpenMP loops (outer=libomp, inner=libgomp)")
from tests._openmp_test_helper.openmp_helpers_outer import check_nested_openmp_loops

result = run_with_timeout(
[
sys.executable,
"-c",
"from tests._openmp_test_helper.openmp_helpers_outer import check_nested_openmp_loops; "
"import numpy.linalg; "
"print(check_nested_openmp_loops(100, 4))",
],
timeout=15,
)
print(json.dumps(result, indent=2))

print_section("Experiment 2: threadpoolctl get_num_threads after nested prange+BLAS")
script = """
import numpy as np
from threadpoolctl import ThreadpoolController, threadpool_limits
from tests._openmp_test_helper.nested_prange_blas import check_nested_prange_blas

A = np.random.randn(64, 64)
B = np.random.randn(64, 64)
print("Running nested prange + BLAS...")
check_nested_prange_blas(A, B, 4)
print("Creating ThreadpoolController...")
controller = ThreadpoolController()
print("Calling get_original_num_threads...")
with threadpool_limits(limits=1) as ctx:
print(ctx.get_original_num_threads())
print("SUCCESS")
"""
result = run_with_timeout([sys.executable, "-c", script], timeout=30)
print(json.dumps(result, indent=2))

print_section("Experiment 3: repeated ThreadpoolController init + threadpool_limits")
script = """
import numpy.linalg
from tests._openmp_test_helper.openmp_helpers_outer import check_nested_openmp_loops
from threadpoolctl import ThreadpoolController, threadpool_limits

for i in range(20):
check_nested_openmp_loops(50, 2)
controller = ThreadpoolController()
with threadpool_limits(limits=1):
controller.info()
print("SUCCESS after 20 iterations")
"""
result = run_with_timeout([sys.executable, "-c", script], timeout=60)
print(json.dumps(result, indent=2))

print_section("Experiment 4: subprocess threadpool_info from inner module")
result = run_with_timeout(
[
sys.executable,
"-c",
"import json; from threadpoolctl import threadpool_info; "
"import tests._openmp_test_helper.openmp_helpers_inner; "
"import numpy.linalg; "
"print(json.dumps(threadpool_info()))",
],
timeout=15,
)
print(json.dumps(result, indent=2))


if __name__ == "__main__":
main()
Loading