NEURON
inithoc.cpp
Go to the documentation of this file.
1 #include "nrnmpiuse.h"
2 #include "nrnpthread.h"
3 #include <stdio.h>
4 #include <stdint.h>
5 #include "nrnmpi.h"
6 #include "nrnpython_config.h"
7 #if defined(__MINGW32__)
8 #define _hypot hypot
9 #endif
10 #include "nrnpy_utils.h"
11 #include <stdlib.h>
12 #include <ctype.h>
13 
14 #if defined(NRNPYTHON_DYNAMICLOAD) && NRNPYTHON_DYNAMICLOAD > 0
15 // when compiled with different Python.h, force correct value
16 #undef NRNPYTHON_DYNAMICLOAD
17 #define NRNPYTHON_DYNAMICLOAD PY_MAJOR_VERSION
18 #endif
19 
20 
21 extern int nrn_is_python_extension;
22 extern int nrn_nobanner_;
23 extern int ivocmain(int, const char**, const char**);
24 extern int nrn_main_launch;
25 
26 
27 // int nrn_global_argc;
28 extern char** nrn_global_argv;
29 
30 extern void nrnpy_augment_path();
31 extern void (*p_nrnpython_finalize)();
32 extern PyObject* nrnpy_hoc();
33 
34 #if NRNMPI_DYNAMICLOAD
35 extern void nrnmpi_stubs();
36 extern char* nrnmpi_load(int is_python);
37 #endif
38 #if NRNPYTHON_DYNAMICLOAD
39 extern int nrnpy_site_problem;
40 #endif
41 
42 #if USE_PTHREAD
43 #include <pthread.h>
44 static pthread_t main_thread_;
45 #endif
46 
47 /**
48  * Manage argc,argv for calling ivocmain
49  * add_arg(...) will only add if name is not already in the arg list
50  * returns 1 if added, 0 if not
51  */
52 static size_t arg_size;
53 static int argc;
54 static char** argv;
55 static int add_arg(const char* name, const char* value) {
56  if (size_t(argc + 2 + bool(value)) >= arg_size) {
57  arg_size += 10;
58  argv = (char**) realloc(argv, arg_size * sizeof(char*));
59  }
60  // Don't add if already in argv
61  for (int i = 1; i < argc; ++i) {
62  if (strcmp(name, argv[i]) == 0) {
63  return 0;
64  }
65  }
66  argv[argc++] = strdup(name);
67  if (value) {
68  argv[argc++] = strdup(value);
69  }
70  // To match C/C++ semantics for the main function then argv[argc] should
71  // always be null. https://en.cppreference.com/w/cpp/language/main_function,
72  // for example, says argv is a "Pointer to the first element of an array of
73  // argc + 1 pointers, of which the last one is null". Section 11.2.1 of the
74  // MPI 4.0 standard says "MPI_INIT accepts the argc and argv that are provided
75  // by the arguments to main or NULL", so it seems correct to follow the rules
76  // for a C/C++ main function. More pragmatically, OpenMPI assumes argv[argc]
77  // is nullptr and crashes if it is not.
78  argv[argc] = NULL;
79  return 1;
80 }
81 
82 /**
83  * Return 1 if string, 0 otherwise.
84  */
85 static int is_string(PyObject* po) {
86  if (PyUnicode_Check(po) || PyBytes_Check(po)) {
87  return 1;
88  }
89  return 0;
90 }
91 
92 /**
93  * Add all name:value from __main__.neuron_options dict if exists
94  * to the argc,argv for calling ivocmain
95  * Note: if value is None then only name is added to argv.
96  * The special "-print-options" name is not added to argv but
97  * causes a return value of 1. Otherwise the return value is 0.
98  */
99 static int add_neuron_options() {
100  PyObject* modules = PyImport_GetModuleDict();
101  PyObject* module = PyDict_GetItemString(modules, "__main__");
102  int rval = 0;
103  if (!module) {
104  PyErr_Clear();
105  PySys_WriteStdout("No __main__ module\n");
106  return rval;
107  }
108  PyObject* neuron_options = PyObject_GetAttrString(module, "neuron_options");
109  if (!neuron_options) {
110  PyErr_Clear();
111  return rval;
112  }
113  if (!PyDict_Check(neuron_options)) {
114  PySys_WriteStdout("__main__.neuron_options is not a dict\n");
115  return rval;
116  }
117  PyObject *key, *value;
118  Py_ssize_t pos = 0;
119  while (PyDict_Next(neuron_options, &pos, &key, &value)) {
120  if (!is_string(key) || (!is_string(value) && value != Py_None)) {
121  PySys_WriteStdout("A neuron_options key:value is not a string:string or string:None\n");
122  continue;
123  }
124  Py2NRNString skey(key);
125  Py2NRNString sval(value);
126  if (strcmp(skey.c_str(), "-print-options") == 0) {
127  rval = 1;
128  continue;
129  }
130  add_arg(skey.c_str(), sval.c_str());
131  }
132  return rval;
133 }
134 
135 /**
136  * Space separated options. Must handle escaped space, '...' and "...".
137  * Return 1 if contains a -print-options (not added to options)
138  */
139 
140 static int add_space_separated_options(const char* str) {
141  int rval = 0;
142  if (!str) {
143  return rval;
144  }
145  char* s = strdup(str);
146  // int state = 0; // 1 means in "...", 2 means in '...'
147  for (char* cp = s; *cp; cp++) {
148  while (isspace(*cp)) { // skip spaces
149  ++cp;
150  if (*cp == '\0') {
151  free(s);
152  return rval;
153  }
154  }
155  // start processing a token
156  char* cpbegin = cp;
157  char* cp1 = cpbegin; // in token pointer, escapes cause to lag behind
158  while (!isspace(*cp) && *cp != '\0') { // to next space delimiter
159  *cp1++ = *cp++;
160  if (cp1[-1] == '\\' && (isspace(*cp) || *cp == '"' || *cp == '\'')) {
161  // escaped space, ", or '
162  cp1[-1] = *cp++;
163  } else if (cp1[-1] == '"') { // consume to next (unescaped) "
164  cp1--; // backup over the "
165  while (*cp != '"' && *cp != '\0') {
166  *cp1++ = *cp++;
167  if (cp1[-1] == '\\' && *cp == '"') { // escaped " inside "..."
168  cp1[-1] = *cp++;
169  }
170  }
171  if (*cp == '"') {
172  cp++; // skip over the closing "
173  }
174  } else if (cp1[-1] == '\'') { // consume to next (unescaped) '
175  cp1--; // backup over the '
176  while (*cp != '\'' && *cp != '\0') {
177  *cp1++ = *cp++;
178  if (cp1[-1] == '\\' && *cp == '\'') { // escaped ' inside '...'
179  cp1[-1] = *cp++;
180  }
181  }
182  if (*cp == '\'') {
183  cp++; // skip over the closing '
184  }
185  }
186  }
187  if (*cp == '\0') { // at end of s. 'for' will return after it increments.
188  --cp;
189  }
190  if (cp1 > cpbegin) {
191  *cp1 = '\0';
192  }
193  if (strcmp(cpbegin, "-print-options") == 0) {
194  rval = 1;
195  } else {
196  add_arg(cpbegin, NULL);
197  }
198  }
199  free(s);
200  return rval;
201 }
202 
203 /**
204  * Return 1 if the option exists in argv[]
205  */
206 static int have_opt(const char* arg) {
207  if (!arg) {
208  return 0;
209  }
210  for (int i = 0; i < argc; ++i) {
211  if (strcmp(arg, argv[i]) == 0) {
212  return 1;
213  }
214  }
215  return 0;
216 }
217 
219 #if USE_PTHREAD
220  pthread_t now = pthread_self();
221  if (pthread_equal(main_thread_, now)) {
222 #else
223  {
224 #endif
225  Py_Finalize();
226  }
227 #if linux
228  if (system("stty sane > /dev/null 2>&1")) {
229  } // 'if' to avoid ignoring return value warning
230 #endif
231 }
232 
233 static char* env[] = {0};
234 
235 extern "C" PyObject* PyInit_hoc() {
236  char buf[200];
237 
238 #if USE_PTHREAD
239  main_thread_ = pthread_self();
240 #endif
241 
242  if (nrn_global_argv) { // ivocmain was already called so already loaded
243  return nrnpy_hoc();
244  }
245 
246  add_arg("NEURON", NULL);
247  int print_options = add_neuron_options();
248  print_options += add_space_separated_options(getenv("NEURON_MODULE_OPTIONS"));
249 
250 #ifdef NRNMPI
251 
252  int flag = 0; // true if MPI_Initialized is called
253  int mpi_mes = 0; // for printing mpi message only once
254  int libnrnmpi_is_loaded = 1; // becomes 0 if NEURON_INIT_MPI == 0 with dynamic mpi
255  char* pmes = NULL; // error message
256  char* env_mpi = getenv("NEURON_INIT_MPI");
257 
258 #if NRNMPI_DYNAMICLOAD
259  nrnmpi_stubs();
260  /**
261  * In case of dynamic mpi build we load MPI unless NEURON_INIT_MPI is explicitly set to 0.
262  * and there is no '-mpi' arg.
263  * We call nrnmpi_load to load MPI library which returns:
264  * - nil if loading is successfull
265  * - error message in case of loading error
266  */
267  if (env_mpi != NULL && strcmp(env_mpi, "0") == 0 && !have_opt("-mpi")) {
268  libnrnmpi_is_loaded = 0;
269  }
270  if (libnrnmpi_is_loaded) {
271  pmes = nrnmpi_load(1);
272  if (pmes && env_mpi == NULL) {
273  // common case on MAC distribution is no NEURON_INIT_MPI and
274  // no MPI installed (so nrnmpi_load fails)
275  libnrnmpi_is_loaded = 0;
276  }
277  if (pmes && libnrnmpi_is_loaded) {
278  printf(
279  "NEURON_INIT_MPI nonzero in env (or -mpi arg) but NEURON cannot initialize MPI "
280  "because:\n%s\n",
281  pmes);
282  exit(1);
283  }
284  }
285 #else
286  have_opt(NULL); // avoid 'defined but not used' warning
287 #endif
288 
289  /**
290  * In case of non-dynamic mpi build mpi library is already linked. We add -mpi
291  * argument in following scenario:
292  * - if user has not explicitly set NEURON_INIT_MPI to 0 and mpi is already initialized
293  * - if user has explicitly set NEURON_INIT_MPI to 1 to load mpi initialization
294  */
295  if (libnrnmpi_is_loaded) {
296  nrnmpi_wrap_mpi_init(&flag);
297  if (flag) {
298  mpi_mes = 1;
299  add_arg("-mpi", NULL);
300  } else if (env_mpi != NULL && strcmp(env_mpi, "1") == 0) {
301  mpi_mes = 2;
302  add_arg("-mpi", NULL);
303  } else {
304  mpi_mes = 3;
305  }
306  } else {
307  // no mpi
308  mpi_mes = 3;
309  }
310 
311  // merely avoids unused variable warning
312  if (pmes && mpi_mes == 2) {
313  exit(1);
314  }
315 
316 #endif // NRNMPI
317 
318  sprintf(buf, "%s/.libs/libnrnmech.so", NRNHOSTCPU);
319  // printf("buf = |%s|\n", buf);
320  FILE* f;
321  if ((f = fopen(buf, "r")) != 0) {
322  fclose(f);
323  add_arg("-dll", buf);
324  }
326  nrn_nobanner_ = 1;
327  const char* pyver = Py_GetVersion();
328  nrn_is_python_extension = (pyver[0] - '0') * 10 + (pyver[2] - '0');
329  if (isdigit(pyver[3])) { // minor >= 10 e.g. 3.10 is 310
330  nrn_is_python_extension = nrn_is_python_extension * 10 + pyver[3] - '0';
331  }
333 #if NRNMPI
334  if (libnrnmpi_is_loaded) {
335  nrnmpi_init(1, &argc, &argv); // may change argc and argv
336  }
337 #if 0 && !defined(NRNMPI_DYNAMICLOAD)
338  if (nrnmpi_myid == 0) {
339  switch(mpi_mes) {
340  case 0:
341  break;
342  case 1:
343  printf("MPI_Initialized==true, MPI functionality enabled by Python.\n");
344  break;
345  case 2:
346  printf("MPI functionality enabled by NEURON.\n");
347  break;
348  case 3:
349  printf("MPI_Initialized==false, MPI functionality not enabled.\n");
350  break;
351  }
352  }
353 #endif // 0 && !defined(NRNMPI_DYNAMICLOAD)
354  if (pmes) {
355  free(pmes);
356  }
357 #endif // NRNMPI
358 
359  char* env_nframe = getenv("NEURON_NFRAME");
360  if (env_nframe != NULL) {
361  char* endptr;
362  const int nframe_env_value = strtol(env_nframe, &endptr, 10);
363  if (*endptr == '\0') {
364  if (nframe_env_value > 0) {
365  add_arg("-NFRAME", env_nframe);
366  } else {
367  PySys_WriteStdout("NEURON_NFRAME env value must be positive\n");
368  }
369  } else {
370  PySys_WriteStdout("NEURON_NFRAME env value is invalid!\n");
371  }
372  }
373 
374  if (print_options) {
375  PySys_WriteStdout("ivocmain options:");
376  for (int i = 1; i < argc; ++i) {
377  PySys_WriteStdout(" '%s'", argv[i]);
378  }
379  PySys_WriteStdout("\n");
380  }
381 
382  nrn_main_launch = 2;
383  ivocmain(argc, (const char**) argv, (const char**) env);
384 // nrnpy_augment_path();
385 #if NRNPYTHON_DYNAMICLOAD
386  nrnpy_site_problem = 0;
387 #endif // NRNPYTHON_DYNAMICLOAD
388  return nrnpy_hoc();
389 }
390 
391 #if !defined(MINGW)
392 extern "C" void modl_reg() {}
393 #endif // !defined(MINGW)
char * c_str() const
Definition: nrnpy_utils.h:42
sprintf(buf, " if (secondorder) {\n" " int _i;\n" " for (_i = 0; _i < %d; ++_i) {\n" " _p[_slist%d[_i]] += dt*_p[_dlist%d[_i]];\n" " }}\n", numeqn, listnum, listnum)
char buf[512]
Definition: init.cpp:13
static int have_opt(const char *arg)
Return 1 if the option exists in argv[].
Definition: inithoc.cpp:206
PyObject * PyInit_hoc()
Definition: inithoc.cpp:235
void(* p_nrnpython_finalize)()
void modl_reg()
Definition: inithoc.cpp:392
static int add_neuron_options()
Add all name:value from main.neuron_options dict if exists to the argc,argv for calling ivocmain Note...
Definition: inithoc.cpp:99
void nrnpy_augment_path()
Definition: nrnpython.cpp:44
int nrn_nobanner_
Definition: hoc.cpp:146
static char * env[]
Definition: inithoc.cpp:233
int nrn_is_python_extension
Definition: fileio.cpp:842
static size_t arg_size
Manage argc,argv for calling ivocmain add_arg(...) will only add if name is not already in the arg li...
Definition: inithoc.cpp:52
void nrnpython_finalize()
Definition: inithoc.cpp:218
int nrn_main_launch
Definition: hoc_init.cpp:528
char ** nrn_global_argv
Definition: hoc.cpp:30
int ivocmain(int, const char **, const char **)
Main entrypoint function into the HOC interpeter.
Definition: ivocmain.cpp:407
static int add_space_separated_options(const char *str)
Space separated options.
Definition: inithoc.cpp:140
static int argc
Definition: inithoc.cpp:53
static int is_string(PyObject *po)
Return 1 if string, 0 otherwise.
Definition: inithoc.cpp:85
static char ** argv
Definition: inithoc.cpp:54
static int add_arg(const char *name, const char *value)
Definition: inithoc.cpp:55
PyObject * nrnpy_hoc()
Definition: nrnpy_hoc.cpp:3078
void
char * getenv(const char *s)
Definition: macprt.cpp:67
#define i
Definition: md1redef.h:12
char * name
Definition: init.cpp:16
#define printf
Definition: mwprefix.h:26
void nrnmpi_init(int nrnmpi_under_nrncontrol, int *pargc, char ***pargv)
Definition: nrnmpi.cpp:58
int nrnmpi_myid
#define arg
Definition: redef.h:28
static uint32_t value
Definition: scoprand.cpp:25
FILE * fopen()
#define key
Definition: spt2queue.cpp:20
#define NULL
Definition: sptree.h:16