NEURON
nrnmpi_dynam.cpp
Go to the documentation of this file.
1 #include <../../nrnconf.h>
2 #include "nrnmpiuse.h"
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <assert.h>
8 
9 #include <array>
10 #include <cstdlib>
11 #include <iostream>
12 #include <string>
13 
14 #if NRNMPI_DYNAMICLOAD /* to end of file */
15 
16 #include "nrnwrap_dlfcn.h"
17 
18 #include "nrnmpi.h"
19 
20 #include "utils/logger.hpp"
21 
22 extern char* cxx_char_alloc(size_t);
23 extern std::string corenrn_mpi_library;
24 
25 #if DARWIN
26 extern void nrn_possible_mismatched_arch(const char*);
27 #endif
28 
29 #if DARWIN || defined(__linux__)
30 extern const char* path_prefix_to_libnrniv();
31 #endif
32 
33 #include <cstddef>
34 #include <string> // for nrnmpi_str_broadcast_world
35 
36 #include "mpispike.h"
37 #include "nrnmpi_def_cinc" /* nrnmpi global variables */
38 extern "C" {
39 #include "nrnmpi_dynam_cinc" /* autogenerated file */
40 }
41 #include "nrnmpi_dynam_wrappers.inc" /* autogenerated file */
42 #include "nrnmpi_dynam_stubs.cpp"
43 
44 static void* load_mpi(const char* name, std::string& mes) {
46  mes.append(name);
47  mes.append(": ");
48  if (!handle) {
49 #if DARWIN
50  nrn_possible_mismatched_arch(name);
51 #endif
52  mes.append(dlerror());
53  } else {
54  mes.append("successful");
55  }
56  mes.append(1, '\n');
57  return handle;
58 }
59 
60 static void* load_nrnmpi(const char* name, std::string& mes) {
62  mes.append("load_nrnmpi: ");
63  if (!handle) {
64  mes.append(dlerror());
65  mes.append("\n");
66  return nullptr;
67  }
68  mes.append(name);
69  mes.append("successful\n");
70  for (int i = 0; ftable[i].name; ++i) {
71  void* p = dlsym(handle, ftable[i].name);
72  if (!p) {
73  mes.append("load_nrnmpi: ");
74  mes.append(ftable[i].name);
75  mes.append(1, ' ');
76  mes.append(dlerror());
77  mes.append(1, '\n');
78  dlclose(handle);
79  return nullptr;
80  }
81  *ftable[i].ppf = p;
82  }
83  {
84  auto* const p = reinterpret_cast<char* (**) (std::size_t)>(
85  dlsym(handle, "p_cxx_char_alloc"));
86  if (!p) {
87  mes.append("load_nrnmpi: p_cxx_char_alloc ");
88  mes.append(dlerror());
89  mes.append("\n");
90  dlclose(handle);
91  return nullptr;
92  }
93  *p = cxx_char_alloc;
94  }
95  return handle;
96 }
97 
98 std::string nrnmpi_load() {
99  std::string pmes;
100  void* handle = nullptr;
101  // If libmpi already in memory, find name and dlopen that.
102  void* sym = dlsym(RTLD_DEFAULT, "MPI_Initialized");
103  if (sym) {
104  Dl_info info;
105  if (dladdr(sym, &info)) {
106  if (info.dli_fname[0] == '/' || strchr(info.dli_fname, ':')) {
107  pmes = "<libmpi> is loaded in the sense the MPI_Initialized has an address\n";
108  handle = load_mpi(info.dli_fname, pmes);
109  if (handle) {
110  // Normally corenrn_mpi_library points to an
111  // libcorenrnmpi_X.{so, ...} file, why do we want it to
112  // point to libmpi.{so, ...} in this case?
113  corenrn_mpi_library = info.dli_fname;
114  }
115  }
116  }
117  }
118 
119  if (!handle) {
120  // Otherwise, try to "dlopen(libmpi)", trying a few different search paths
121  // that are slightly different for different platforms. MPI_LIB_NRN_PATH may
122  // be set explicitly by the user, or by NEURON's Python code via
123  // ctypes.find_library().
124  using const_char_ptr = const char*;
125  std::array libmpi_names {
126 #if defined(DARWIN)
127  "libmpi.dylib", const_char_ptr{std::getenv("MPI_LIB_NRN_PATH")},
128 #elif defined(MINGW)
129  "msmpi.dll"
130 #else // Linux
131  // libmpi.so is not standard but used by most of the implemenntation
132  // (mpich, openmpi, intel-mpi, parastation-mpi, hpe-mpt) but not
133  // cray-mpich. we first load libmpi and then libmpich.so as a fallaback
134  // for cray systems.
135  "libmpi.so", const_char_ptr{std::getenv("MPI_LIB_NRN_PATH")}, "libmpich.so"
136 #endif
137  };
138 
139  // Look for the MPI implementation in this search path
140  pmes = "Tried loading an MPI library from:\n";
141  for (auto const* mpi_path: libmpi_names) {
142  if (!mpi_path) {
143  // MPI_LIB_NRN_PATH might not be set
144  continue;
145  }
146  handle = load_mpi(mpi_path, pmes);
147  if (handle) {
148  // Success
149  break;
150  }
151  }
152  }
153 
154  if (!handle) {
155  // Failed to find an MPI implementation
156  pmes.append(
157  "Is an MPI library such as openmpi, mpich, intel-mpi or sgi-mpt installed? If yes, it "
158  "may be installed in a non-standard location that you can add to LD_LIBRARY_PATH (or "
159  "DYLD_LIBRARY_PATH on macOS), or on Linux or macOS you can provide a full path in "
160  "MPI_LIB_NRN_PATH\n");
161  return pmes;
162  }
163 
164 #if !defined(DARWIN) && !defined(MINGW)
165  // Linux-specific hack; with CMake the problem of Python launch on Linux not
166  // resolving variables from already loaded shared libraries has returned.
167  {
168  std::string error{"Promoted none of"};
169  auto const promote_to_global = [&error](const char* lib) {
170  if (!dlopen(lib, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL)) {
171  char const* dlerr = dlerror();
172  error = error + ' ' + lib + " (" + (dlerr ? dlerr : "nullptr") + ')';
173  return false;
174  }
175  return true;
176  };
177  if (!promote_to_global("libnrniv.so") && !promote_to_global("libnrniv-without-nvidia.so")) {
178  Fprintf(stderr, fmt::format("{} to RTLD_GLOBAL\n", error).c_str());
179  }
180  }
181 #endif
182 
183  // Found the MPI implementation, `handle` refers to it . Now deduce which
184  // MPI implementation that actually is
185  assert(handle);
186  auto const mpi_implementation = [handle] {
187 #ifdef MINGW
188  return "msmpi";
189 #else
190  if (dlsym(handle, "ompi_mpi_init")) {
191  // OpenMPI
192  return "ompi";
193  } else if (dlsym(handle, "MPI_SGI_vtune_is_running")) {
194  // Got sgi-mpt. MPI_SGI_init exists in both mpt and hmpt, so we look
195  // for MPI_SGI_vtune_is_running which only exists in the non-hmpt
196  // version.
197  return "mpt";
198  } else {
199  // Assume mpich. Could check for MPID_nem_mpich_init...
200  return "mpich";
201  }
202 #endif
203  }();
204 
205  // Figure out where to find lib[core]nrnmpi{...} libraries. Older versions
206  // of this code used @loader_path on macOS, which caused problems in now that the code that
207  // calls dlopen(libcorenrnmpi_...) is in libcorenrnmech.so (in some
208  // model-specific directory) rather than the CoreNEURON installation
209  // directory where libcorenrnmpi_*.so live. Now libcorenrnmpi_*.so will be
210  // looked for in the same directory as libnrniv.so, which will be incorrect
211  // if CoreNEURON is built externally with dynamic MPI enabled.
212  auto const libnrnmpi_prefix = []() -> std::string {
213 #ifdef MINGW
214  // Preserve old behaviour on Windows
215  return {};
216 #else
217  if (const char* nrn_home = std::getenv("NRNHOME")) {
218  // TODO: what about windows path separators?
219  return std::string{nrn_home} + "/lib/";
220  } else {
221  // Use the directory libnrniv.so is in
222  return path_prefix_to_libnrniv();
223  }
224 #endif
225  }();
226 
227 
228  auto const mpi_path = [&](std::string_view middle) {
229  std::string name{libnrnmpi_prefix};
230  name.append(neuron::config::shared_library_prefix);
231  name.append(middle);
232  name.append(mpi_implementation);
233  name.append(neuron::config::shared_library_suffix);
234  return name;
235  };
236  auto const nrn_mpi_library = mpi_path("nrnmpi_");
237  corenrn_mpi_library = mpi_path("corenrnmpi_");
238 
239  // This env variable is only needed in usage like neurodamus where
240  // `solve_core()` is directly called by MOD file and it doesn't have
241  // an easy way to know which MPI library to load.
242  // TODO: remove when BlueBrain/neurodamus/issues/17 is fixed.
243 #if defined(HAVE_SETENV)
244  setenv("NRN_CORENRN_MPI_LIB", corenrn_mpi_library.c_str(), 0);
245 #endif
246 
247  if (!load_nrnmpi(nrn_mpi_library.c_str(), pmes)) {
248  return pmes;
249  }
250 
251  // No error, return an empty string. We have called dlopen(...) on the
252  // libnrnmpi_* shared library and potentially on "libmpi" without
253  // corresponding calls to dlclose().
254  return {};
255 }
256 
257 // nrnmpi_load cannot safely be called from nrnmpi.cpp because of pre/post-C++11
258 // ABI compatibility issues with std::string. See
259 // https://github.com/neuronsimulator/nrn/issues/1963 for more information.
260 void nrnmpi_load_or_exit() {
261  auto const err = nrnmpi_load();
262  if (!err.empty()) {
263  Printf(fmt::format("{}\n", err).c_str());
264  std::exit(1);
265  }
266 }
267 
268 #endif
#define i
Definition: md1redef.h:19
DLFCN_EXPORT void * dlopen(const char *file, int mode)
Definition: dlfcn.c:331
DLFCN_EXPORT char * dlerror(void)
Definition: dlfcn.c:548
DLFCN_EXPORT int dlclose(void *handle)
Definition: dlfcn.c:423
DLFCN_EXPORT int dladdr(const void *addr, Dl_info *info)
Definition: dlfcn.c:731
DLFCN_NOINLINE DLFCN_EXPORT void * dlsym(void *handle, const char *name)
Definition: dlfcn.c:447
#define RTLD_DEFAULT
Definition: dlfcn.h:66
#define RTLD_NOW
Definition: dlfcn.h:47
#define RTLD_GLOBAL
Definition: dlfcn.h:56
#define assert(ex)
Definition: hocassrt.h:24
char * cxx_char_alloc(size_t sz)
Definition: ivoc.cpp:169
error
Definition: extdef.h:3
const char * name
Definition: init.cpp:16
handle_interface< non_owning_identifier< storage > > handle
Non-owning handle to a Mechanism instance.
static List * info
std::string corenrn_mpi_library
size_t p
Definition: dlfcn.h:72
int Fprintf(FILE *stream, const char *fmt, Args... args)
Definition: logger.hpp:8
int Printf(const char *fmt, Args... args)
Definition: logger.hpp:18