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