From 8ffa1fa3acfbb4f91d070312617d83d64634adfb Mon Sep 17 00:00:00 2001 From: Fernando Pereira Date: Wed, 26 Jun 2024 13:23:28 +0100 Subject: [PATCH] Bring Nanobind. Fix object leak in `from_numpy` (#2545) --- .gitmodules | 3 + CMakeLists.txt | 11 ++- cmake/ExternalProjectHelper.cmake | 30 ++++-- cmake/NanoBindMinimal.cmake | 21 ++++ external/nanobind | 1 + src/coreneuron/CMakeLists.txt | 2 +- src/nrnpython/CMakeLists.txt | 9 +- src/nrnpython/grids.cpp | 142 ++++++++++++++------------- src/nrnpython/inithoc.cpp | 8 +- src/nrnpython/nrn_export.hpp | 7 ++ src/nrnpython/nrnpy_hoc.cpp | 115 ++++++++++++---------- src/nrnpython/nrnpy_p2h.cpp | 2 +- src/nrnpython/nrnpython.cpp | 2 +- src/nrnpython/nrnpython.h | 2 + src/nrnpython/rxd.cpp | 114 +++++++++++---------- src/nrnpython/rxd_extracellular.cpp | 28 +++--- src/nrnpython/rxd_intracellular.cpp | 20 ++-- src/nrnpython/rxd_llgramarea.cpp | 6 +- src/nrnpython/rxd_marching_cubes.cpp | 34 ++++--- src/nrnpython/rxdmath.cpp | 12 ++- 20 files changed, 325 insertions(+), 244 deletions(-) create mode 100644 cmake/NanoBindMinimal.cmake create mode 160000 external/nanobind create mode 100644 src/nrnpython/nrn_export.hpp diff --git a/.gitmodules b/.gitmodules index 637faa9b55..1bbbebe21c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "external/eigen"] path = external/eigen url = https://gitlab.com/libeigen/eigen.git +[submodule "external/nanobind"] + path = external/nanobind + url = https://github.com/wjakob/nanobind diff --git a/CMakeLists.txt b/CMakeLists.txt index bcae036fe9..ba948e02a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -432,6 +432,9 @@ endif() if(NRN_ENABLE_PYTHON) # Make sure the USE_PYTHON macro is defined in the C++ code list(APPEND NRN_COMPILE_DEFS USE_PYTHON) + # Ensure nanobind is there, but dont import, we don't want its CMake + nrn_add_external_project(nanobind DISABLE_ADD RECURSIVE SHALLOW) + include(NanoBindMinimal) endif() # ============================================================================= @@ -572,6 +575,10 @@ endif() # ============================================================================= add_subdirectory(src/sparse13) add_subdirectory(src/gnu) +if(NRN_ENABLE_PYTHON) + add_subdirectory(src/nrnpython) +endif() + add_subdirectory(src/nrniv) # Collect the environment variables that are needed to execute NEURON from the build directory. This @@ -603,10 +610,6 @@ if(NRN_ENABLE_PYTHON) endif() add_subdirectory(bin) -if(NRN_ENABLE_PYTHON) - add_subdirectory(src/nrnpython) -endif() - if(NRN_MACOS_BUILD) add_subdirectory(src/mac) endif() diff --git a/cmake/ExternalProjectHelper.cmake b/cmake/ExternalProjectHelper.cmake index 3abadf7e58..d1227d056c 100644 --- a/cmake/ExternalProjectHelper.cmake +++ b/cmake/ExternalProjectHelper.cmake @@ -23,13 +23,22 @@ endfunction(nrn_submodule_file_not_found) # initialize submodule with given path function(nrn_initialize_submodule path) + cmake_parse_arguments(PARSE_ARGV 1 opt "RECURSIVE;SHALLOW" "" "") + set(UPDATE_OPTIONS "") + if(opt_RECURSIVE) + list(APPEND UPDATE_OPTIONS --recursive) + endif() + if(opt_SHALLOW) + list(APPEND UPDATE_OPTIONS --depth 1) + endif() if(NOT ${GIT_FOUND}) message( FATAL_ERROR "git not found and ${path} sub-module not cloned (use git clone --recursive)") endif() - message(STATUS "Sub-module : missing ${path} : running git submodule update --init") + message( + STATUS "Sub-module : missing ${path} : running git submodule update ${UPDATE_OPTIONS} --init") execute_process( - COMMAND ${GIT_EXECUTABLE} submodule update --init -- ${path} + COMMAND ${GIT_EXECUTABLE} submodule update ${UPDATE_OPTIONS} --init -- ${path} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE ret) if(NOT ret EQUAL 0) @@ -39,6 +48,7 @@ endfunction() # check for external project and initialize submodule if it is missing function(nrn_add_external_project name) + cmake_parse_arguments(PARSE_ARGV 1 opt "RECURSIVE;SHALLOW;DISABLE_ADD" "" "") find_path( ${name}_PATH NAMES CMakeLists.txt @@ -46,16 +56,18 @@ function(nrn_add_external_project name) NO_DEFAULT_PATH) if(NOT EXISTS ${${name}_PATH}) nrn_submodule_file_not_found("${THIRD_PARTY_DIRECTORY}/${name}") - nrn_initialize_submodule("${THIRD_PARTY_DIRECTORY}/${name}") + set(OPTIONS "") + if(opt_RECURSIVE) + list(APPEND OPTIONS "RECURSIVE") + endif() + if(opt_SHALLOW) + list(APPEND OPTIONS "SHALLOW") + endif() + nrn_initialize_submodule("${THIRD_PARTY_DIRECTORY}/${name}" ${OPTIONS}) else() message(STATUS "Sub-project : using ${name} from from ${THIRD_PARTY_DIRECTORY}/${name}") endif() - # if second argument is passed and if it's OFF then skip add_subdirectory - if(${ARGC} GREATER 1) - if(${ARGV2}) - add_subdirectory("${THIRD_PARTY_DIRECTORY}/${name}") - endif() - else() + if(NOT opt_DISABLE_ADD) add_subdirectory("${THIRD_PARTY_DIRECTORY}/${name}") endif() endfunction() diff --git a/cmake/NanoBindMinimal.cmake b/cmake/NanoBindMinimal.cmake new file mode 100644 index 0000000000..f661e7e473 --- /dev/null +++ b/cmake/NanoBindMinimal.cmake @@ -0,0 +1,21 @@ +# ============================================================================= +# minimal nanobind interface +# ============================================================================= + +set(NB_DIR ${PROJECT_SOURCE_DIR}/external/nanobind) + +function(make_nanobind_target TARGET_NAME PYINC) + add_library(${TARGET_NAME} STATIC ${NB_DIR}/src/nb_combined.cpp) + target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${NB_DIR}/include) + target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${NB_DIR}/ext/robin_map/include ${PYINC}) + if(MSVC) + # Do not complain about vsnprintf + target_compile_definitions(${TARGET_NAME} PRIVATE -D_CRT_SECURE_NO_WARNINGS) + else() + # Generally needed to handle type punning in Python code + target_compile_options(${TARGET_NAME} PRIVATE -fno-strict-aliasing) + endif() + target_compile_features(${TARGET_NAME} PUBLIC cxx_std_17) + set_property(TARGET ${TARGET_NAME} PROPERTY POSITION_INDEPENDENT_CODE True) + set_property(TARGET ${TARGET_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) +endfunction() diff --git a/external/nanobind b/external/nanobind new file mode 160000 index 0000000000..8d7f1ee062 --- /dev/null +++ b/external/nanobind @@ -0,0 +1 @@ +Subproject commit 8d7f1ee0621c17fa370b704b2100ffa0243d5bfb diff --git a/src/coreneuron/CMakeLists.txt b/src/coreneuron/CMakeLists.txt index 3594329de6..6ec5a82389 100644 --- a/src/coreneuron/CMakeLists.txt +++ b/src/coreneuron/CMakeLists.txt @@ -267,7 +267,7 @@ else() set(NMODL_ENABLE_PYTHON_BINDINGS OFF CACHE BOOL "Enable NMODL python bindings") - nrn_add_external_project(nmodl OFF) + nrn_add_external_project(nmodl DISABLE_ADD) add_subdirectory(${PROJECT_SOURCE_DIR}/external/nmodl ${CMAKE_BINARY_DIR}/external/nmodl) set(CORENRN_NMODL_BINARY ${CMAKE_BINARY_DIR}/bin/nmodl${CMAKE_EXECUTABLE_SUFFIX}) set(CORENRN_NMODL_INCLUDE ${CMAKE_BINARY_DIR}/include) diff --git a/src/nrnpython/CMakeLists.txt b/src/nrnpython/CMakeLists.txt index d73a03b437..2646f95a60 100644 --- a/src/nrnpython/CMakeLists.txt +++ b/src/nrnpython/CMakeLists.txt @@ -49,11 +49,14 @@ if(NRN_ENABLE_PYTHON_DYNAMIC) list(GET NRN_PYTHON_VERSIONS ${val} pyver) list(GET NRN_PYTHON_INCLUDES ${val} pyinc) list(GET NRN_PYTHON_LIBRARIES ${val} pylib) + set(nanobind_target "nanobind_py${pyver}") + make_nanobind_target(${nanobind_target} ${pyinc}) add_library(nrnpython${pyver} SHARED ${NRNPYTHON_FILES_LIST}) target_include_directories(nrnpython${pyver} BEFORE PUBLIC ${pyinc} ${INCLUDE_DIRS}) - target_link_libraries(nrnpython${pyver} nrniv_lib ${Readline_LIBRARY}) + target_link_libraries(nrnpython${pyver} PUBLIC nrniv_lib) + target_link_libraries(nrnpython${pyver} PRIVATE ${Readline_LIBRARY} ${nanobind_target}) if(NRN_LINK_AGAINST_PYTHON) - target_link_libraries(nrnpython${pyver} ${pylib}) + target_link_libraries(nrnpython${pyver} PUBLIC ${pylib}) endif() add_dependencies(nrnpython${pyver} nrniv_lib) list(APPEND nrnpython_lib_list nrnpython${pyver}) @@ -68,6 +71,8 @@ else() target_link_libraries(nrnpython ${NRN_DEFAULT_PYTHON_LIBRARIES}) target_include_directories(nrnpython PUBLIC ${PROJECT_SOURCE_DIR}/${NRN_3RDPARTY_DIR}/eigen) target_include_directories(nrnpython PUBLIC ${PROJECT_BINARY_DIR}/src/nrniv/oc_generated) + make_nanobind_target(nanobind ${NRN_DEFAULT_PYTHON_INCLUDES}) + target_link_libraries(nrnpython nanobind) endif() configure_file(_config_params.py.in "${PROJECT_BINARY_DIR}/lib/python/neuron/_config_params.py" diff --git a/src/nrnpython/grids.cpp b/src/nrnpython/grids.cpp index 23040d396d..a02dd5b221 100644 --- a/src/nrnpython/grids.cpp +++ b/src/nrnpython/grids.cpp @@ -25,7 +25,7 @@ extern double* _rxd_induced_currents_ecs; extern double* _rxd_induced_currents_scale; // Set dt, t pointers -extern "C" void make_time_ptr(PyHocObject* my_dt_ptr, PyHocObject* my_t_ptr) { +extern "C" NRN_EXPORT void make_time_ptr(PyHocObject* my_dt_ptr, PyHocObject* my_t_ptr) { dt_ptr = static_cast(my_dt_ptr->u.px_); t_ptr = static_cast(my_t_ptr->u.px_); } @@ -194,22 +194,22 @@ ECS_Grid_node::ECS_Grid_node(PyHocObject* my_states, // Insert a Grid_node "new_Grid" into the list located at grid_list_index in Parallel_grids /* returns the grid number TODO: change this to returning the pointer */ -extern "C" int ECS_insert(int grid_list_index, - PyHocObject* my_states, - int my_num_states_x, - int my_num_states_y, - int my_num_states_z, - double my_dc_x, - double my_dc_y, - double my_dc_z, - double my_dx, - double my_dy, - double my_dz, - PyHocObject* my_alpha, - PyHocObject* my_permeability, - int bc, - double bc_value, - double atolscale) { +extern "C" NRN_EXPORT int ECS_insert(int grid_list_index, + PyHocObject* my_states, + int my_num_states_x, + int my_num_states_y, + int my_num_states_z, + double my_dc_x, + double my_dc_y, + double my_dc_z, + double my_dx, + double my_dy, + double my_dz, + PyHocObject* my_alpha, + PyHocObject* my_permeability, + int bc, + double bc_value, + double atolscale) { ECS_Grid_node* new_Grid = new ECS_Grid_node(my_states, my_num_states_x, my_num_states_y, @@ -381,21 +381,21 @@ ICS_Grid_node::ICS_Grid_node(PyHocObject* my_states, // Insert a Grid_node "new_Grid" into the list located at grid_list_index in Parallel_grids /* returns the grid number TODO: change this to returning the pointer */ -extern "C" int ICS_insert(int grid_list_index, - PyHocObject* my_states, - long num_nodes, - long* neighbors, - long* x_line_defs, - long x_lines_length, - long* y_line_defs, - long y_lines_length, - long* z_line_defs, - long z_lines_length, - double* dcs, - double dx, - bool is_diffusable, - double atolscale, - double* ics_alphas) { +extern "C" NRN_EXPORT int ICS_insert(int grid_list_index, + PyHocObject* my_states, + long num_nodes, + long* neighbors, + long* x_line_defs, + long x_lines_length, + long* y_line_defs, + long y_lines_length, + long* z_line_defs, + long z_lines_length, + double* dcs, + double dx, + bool is_diffusable, + double atolscale, + double* ics_alphas) { ICS_Grid_node* new_Grid = new ICS_Grid_node(my_states, num_nodes, neighbors, @@ -415,21 +415,21 @@ extern "C" int ICS_insert(int grid_list_index, return new_Grid->insert(grid_list_index); } -int ICS_insert_inhom(int grid_list_index, - PyHocObject* my_states, - long num_nodes, - long* neighbors, - long* x_line_defs, - long x_lines_length, - long* y_line_defs, - long y_lines_length, - long* z_line_defs, - long z_lines_length, - double* dcs, - double dx, - bool is_diffusable, - double atolscale, - double* ics_alphas) { +extern "C" NRN_EXPORT int ICS_insert_inhom(int grid_list_index, + PyHocObject* my_states, + long num_nodes, + long* neighbors, + long* x_line_defs, + long x_lines_length, + long* y_line_defs, + long y_lines_length, + long* z_line_defs, + long z_lines_length, + double* dcs, + double dx, + bool is_diffusable, + double atolscale, + double* ics_alphas) { ICS_Grid_node* new_Grid = new ICS_Grid_node(my_states, num_nodes, neighbors, @@ -449,7 +449,7 @@ int ICS_insert_inhom(int grid_list_index, } -extern "C" int set_diffusion(int grid_list_index, int grid_id, double* dc, int length) { +extern "C" NRN_EXPORT int set_diffusion(int grid_list_index, int grid_id, double* dc, int length) { int id = 0; Grid_node* node = Parallel_grids[grid_list_index]; while (id < grid_id) { @@ -462,7 +462,9 @@ extern "C" int set_diffusion(int grid_list_index, int grid_id, double* dc, int l return 0; } -extern "C" int set_tortuosity(int grid_list_index, int grid_id, PyHocObject* my_permeability) { +extern "C" NRN_EXPORT int set_tortuosity(int grid_list_index, + int grid_id, + PyHocObject* my_permeability) { int id = 0; Grid_node* node = Parallel_grids[grid_list_index]; while (id < grid_id) { @@ -513,7 +515,9 @@ void ECS_Grid_node::set_tortuosity(PyHocObject* my_permeability) { } } -extern "C" int set_volume_fraction(int grid_list_index, int grid_id, PyHocObject* my_alpha) { +extern "C" NRN_EXPORT int set_volume_fraction(int grid_list_index, + int grid_id, + PyHocObject* my_alpha) { int id = 0; Grid_node* node = Parallel_grids[grid_list_index]; while (id < grid_id) { @@ -585,11 +589,11 @@ void ECS_Grid_node::set_diffusion(double* dc, int) { } -extern "C" void ics_set_grid_concentrations(int grid_list_index, - int index_in_list, - int64_t* nodes_per_seg, - int64_t* nodes_per_seg_start_indices, - PyObject* neuron_pointers) { +extern "C" NRN_EXPORT void ics_set_grid_concentrations(int grid_list_index, + int index_in_list, + int64_t* nodes_per_seg, + int64_t* nodes_per_seg_start_indices, + PyObject* neuron_pointers) { Grid_node* g; ssize_t i; ssize_t n = (ssize_t) PyList_Size(neuron_pointers); // number of segments. @@ -610,10 +614,10 @@ extern "C" void ics_set_grid_concentrations(int grid_list_index, } } -extern "C" void ics_set_grid_currents(int grid_list_index, - int index_in_list, - PyObject* neuron_pointers, - double* scale_factors) { +extern "C" NRN_EXPORT void ics_set_grid_currents(int grid_list_index, + int index_in_list, + PyObject* neuron_pointers, + double* scale_factors) { Grid_node* g; ssize_t i; ssize_t n = (ssize_t) PyList_Size(neuron_pointers); @@ -634,10 +638,10 @@ extern "C" void ics_set_grid_currents(int grid_list_index, /* TODO: make this work with Grid_node ptrs instead of pairs of list indices */ -extern "C" void set_grid_concentrations(int grid_list_index, - int index_in_list, - PyObject* grid_indices, - PyObject* neuron_pointers) { +extern "C" NRN_EXPORT void set_grid_concentrations(int grid_list_index, + int index_in_list, + PyObject* grid_indices, + PyObject* neuron_pointers) { /* Preconditions: @@ -675,11 +679,11 @@ extern "C" void set_grid_concentrations(int grid_list_index, } /* TODO: make this work with Grid_node ptrs instead of pairs of list indices */ -extern "C" void set_grid_currents(int grid_list_index, - int index_in_list, - PyObject* grid_indices, - PyObject* neuron_pointers, - PyObject* scale_factors) { +extern "C" NRN_EXPORT void set_grid_currents(int grid_list_index, + int index_in_list, + PyObject* grid_indices, + PyObject* neuron_pointers, + PyObject* scale_factors) { /* Preconditions: @@ -780,7 +784,7 @@ int remove(Grid_node** head, Grid_node* find) { return 1; } -extern "C" void delete_by_id(int id) { +extern "C" NRN_EXPORT void delete_by_id(int id) { Grid_node* grid; int k; for (k = 0, grid = Parallel_grids[0]; grid != NULL; grid = grid->next, k++) { diff --git a/src/nrnpython/inithoc.cpp b/src/nrnpython/inithoc.cpp index 184ccafc00..9e9c9ef508 100644 --- a/src/nrnpython/inithoc.cpp +++ b/src/nrnpython/inithoc.cpp @@ -10,6 +10,8 @@ #include #include +#include "nrn_export.hpp" + #include #include @@ -22,7 +24,7 @@ extern int nrn_main_launch; // int nrn_global_argc; extern char** nrn_global_argv; extern void (*p_nrnpython_finalize)(); -extern PyObject* nrnpy_hoc(); +extern "C" PyObject* nrnpy_hoc(); #if NRNMPI_DYNAMICLOAD extern void nrnmpi_stubs(); @@ -221,7 +223,7 @@ void nrnpython_finalize() { static char* env[] = {0}; -extern "C" PyObject* PyInit_hoc() { +extern "C" NRN_EXPORT PyObject* PyInit_hoc() { #if NRN_ENABLE_THREADS main_thread_ = std::this_thread::get_id(); #endif @@ -368,5 +370,5 @@ extern "C" PyObject* PyInit_hoc() { } #if !defined(MINGW) -extern "C" void modl_reg() {} +extern "C" NRN_EXPORT void modl_reg() {} #endif // !defined(MINGW) diff --git a/src/nrnpython/nrn_export.hpp b/src/nrnpython/nrn_export.hpp new file mode 100644 index 0000000000..7b62b27a28 --- /dev/null +++ b/src/nrnpython/nrn_export.hpp @@ -0,0 +1,7 @@ +#pragma once + +#if defined(_WIN32) +#define NRN_EXPORT __declspec(dllexport) +#else +#define NRN_EXPORT __attribute__((visibility("default"))) +#endif diff --git a/src/nrnpython/nrnpy_hoc.cpp b/src/nrnpython/nrnpy_hoc.cpp index 5572283b00..29784687cf 100644 --- a/src/nrnpython/nrnpy_hoc.cpp +++ b/src/nrnpython/nrnpy_hoc.cpp @@ -22,6 +22,10 @@ #include #include +#include + +namespace nb = nanobind; + extern PyTypeObject* psection_type; extern std::vector py_exposed_classes; @@ -69,8 +73,6 @@ extern IvocVect* (*nrnpy_vec_from_python_p_)(void*); extern Object** (*nrnpy_vec_to_python_p_)(void*); extern Object** (*nrnpy_vec_as_numpy_helper_)(int, double*); extern Object* (*nrnpy_rvp_rxd_to_callable)(Object*); -extern "C" int nrnpy_set_vec_as_numpy(PyObject* (*p)(int, double*) ); // called by ctypes. -extern "C" int nrnpy_set_gui_callback(PyObject*); extern Symbol* ivoc_alias_lookup(const char* name, Object* ob); class NetCon; extern int nrn_netcon_weight(NetCon*, double**); @@ -936,7 +938,7 @@ PyObject* nrn_hocobj_handle(neuron::container::data_handle d) { return result; } -extern "C" PyObject* nrn_hocobj_ptr(double* pd) { +extern "C" NRN_EXPORT PyObject* nrn_hocobj_ptr(double* pd) { return nrn_hocobj_handle(neuron::container::data_handle{pd}); } @@ -2498,6 +2500,16 @@ static char* double_array_interface(PyObject* po, long& stride) { return static_cast(data); } + +inline double pyobj_to_double_or_fail(PyObject* obj, long obj_id) { + if (!PyNumber_Check(obj)) { + char buf[50]; + Sprintf(buf, "item %d is not a valid number", obj_id); + hoc_execerror(buf, 0); + } + return PyFloat_AsDouble(obj); +} + static IvocVect* nrnpy_vec_from_python(void* v) { Vect* hv = (Vect*) v; // printf("%s.from_array\n", hoc_object_name(hv->obj_)); @@ -2505,58 +2517,53 @@ static IvocVect* nrnpy_vec_from_python(void* v) { if (ho->ctemplate->sym != nrnpy_pyobj_sym_) { hoc_execerror(hoc_object_name(ho), " is not a PythonObject"); } - PyObject* po = nrnpy_hoc2pyobject(ho); - Py_INCREF(po); - if (!PySequence_Check(po)) { - if (!PyIter_Check(po)) { + // We borrow the list, so there's an INCREF and all items are alive + nb::object po = nb::borrow(nrnpy_hoc2pyobject(ho)); + + // If it's not a sequence, try iterating over it + if (!PySequence_Check(po.ptr())) { + if (!PyIter_Check(po.ptr())) { hoc_execerror(hoc_object_name(ho), " does not support the Python Sequence or Iterator protocol"); } - PyObject* iterator = PyObject_GetIter(po); - assert(iterator != NULL); - int i = 0; - PyObject* p; - while ((p = PyIter_Next(iterator)) != NULL) { - if (!PyNumber_Check(p)) { - char buf[50]; - Sprintf(buf, "item %d not a number", i); - hoc_execerror(buf, 0); - } - hv->push_back(PyFloat_AsDouble(p)); - Py_DECREF(p); - ++i; + long i = 0; + for (nb::handle item: po) { + hv->push_back(pyobj_to_double_or_fail(item.ptr(), i)); + i++; + } + return hv; + } + + int size = nb::len(po); + hv->resize(size); + double* x = vector_vec(hv); + + // If sequence provides __array_interface__ use it + long stride; + char* array_interface_ptr = double_array_interface(po.ptr(), stride); + if (array_interface_ptr) { + for (int i = 0, j = 0; i < size; ++i, j += stride) { + x[i] = *(double*) (array_interface_ptr + j); + } + return hv; + } + + // If it's a normal list, convert to the good type so operator[] is more efficient + if (PyList_Check(po.ptr())) { + nb::list list_obj{std::move(po)}; + for (long i = 0; i < size; ++i) { + x[i] = pyobj_to_double_or_fail(list_obj[i].ptr(), i); } - Py_DECREF(iterator); } else { - int size = PySequence_Size(po); - // printf("size = %d\n", size); - hv->resize(size); - double* x = vector_vec(hv); - long stride; - char* y = double_array_interface(po, stride); - if (y) { - for (int i = 0, j = 0; i < size; ++i, j += stride) { - x[i] = *(double*) (y + j); - } - } else { - for (int i = 0; i < size; ++i) { - PyObject* p = PySequence_GetItem(po, i); - if (!PyNumber_Check(p)) { - char buf[50]; - Sprintf(buf, "item %d not a number", i); - hoc_execerror(buf, 0); - } - x[i] = PyFloat_AsDouble(p); - Py_DECREF(p); - } + for (long i = 0; i < size; ++i) { + x[i] = pyobj_to_double_or_fail(po[i].ptr(), i); } } - Py_DECREF(po); return hv; } static PyObject* (*vec_as_numpy)(int, double*); -extern "C" int nrnpy_set_vec_as_numpy(PyObject* (*p)(int, double*) ) { +extern "C" NRN_EXPORT int nrnpy_set_vec_as_numpy(PyObject* (*p)(int, double*) ) { vec_as_numpy = p; return 0; } @@ -2610,11 +2617,11 @@ static void nrnpy_restore_savestate_(int64_t size, char* data) { } } -extern "C" int nrnpy_set_toplevel_callbacks(PyObject* rvp_plot0, - PyObject* plotshape_plot0, - PyObject* get_mech_object_0, - PyObject* store_savestate, - PyObject* restore_savestate) { +extern "C" NRN_EXPORT int nrnpy_set_toplevel_callbacks(PyObject* rvp_plot0, + PyObject* plotshape_plot0, + PyObject* get_mech_object_0, + PyObject* store_savestate, + PyObject* restore_savestate) { rvp_plot = rvp_plot0; plotshape_plot = plotshape_plot0; get_mech_object_ = get_mech_object_0; @@ -2626,7 +2633,7 @@ extern "C" int nrnpy_set_toplevel_callbacks(PyObject* rvp_plot0, } static PyObject* gui_callback = NULL; -extern "C" int nrnpy_set_gui_callback(PyObject* new_gui_callback) { +extern "C" NRN_EXPORT int nrnpy_set_gui_callback(PyObject* new_gui_callback) { gui_callback = new_gui_callback; return 0; } @@ -2846,7 +2853,7 @@ static Object* rvp_rxd_to_callable_(Object* obj) { } -extern "C" PyObject* get_plotshape_data(PyObject* sp) { +extern "C" NRN_EXPORT PyObject* get_plotshape_data(PyObject* sp) { PyHocObject* pho = (PyHocObject*) sp; ShapePlotInterface* spi; if (!is_obj_type(pho->ho_, "PlotShape")) { @@ -3090,12 +3097,12 @@ static void add2topdict(PyObject* dict) { static PyObject* nrnpy_vec_math = NULL; -extern "C" int nrnpy_vec_math_register(PyObject* callback) { +extern "C" NRN_EXPORT int nrnpy_vec_math_register(PyObject* callback) { nrnpy_vec_math = callback; return 0; } -extern "C" int nrnpy_rvp_pyobj_callback_register(PyObject* callback) { +extern "C" NRN_EXPORT int nrnpy_rvp_pyobj_callback_register(PyObject* callback) { nrnpy_rvp_pyobj_callback = callback; return 0; } @@ -3328,7 +3335,7 @@ static PyType_Spec obj_spec_from_name(const char* name) { }; } -PyObject* nrnpy_hoc() { +extern "C" NRN_EXPORT PyObject* nrnpy_hoc() { PyObject* m; PyObject* bases; PyTypeObject* pto; diff --git a/src/nrnpython/nrnpy_p2h.cpp b/src/nrnpython/nrnpy_p2h.cpp index 6848c723c0..0d765bb6a5 100644 --- a/src/nrnpython/nrnpy_p2h.cpp +++ b/src/nrnpython/nrnpy_p2h.cpp @@ -1118,7 +1118,7 @@ void nrnpython_reg_real_nrnpy_hoc_cpp(neuron::python::impl_ptrs* ptrs); * @brief Populate NEURON state with information from a specific Python. * @param ptrs Logically a return value; avoidi */ -extern "C" void nrnpython_reg_real(neuron::python::impl_ptrs* ptrs) { +extern "C" NRN_EXPORT void nrnpython_reg_real(neuron::python::impl_ptrs* ptrs) { assert(ptrs); class2oc("PythonObject", p_cons, p_destruct, p_members, nullptr, nullptr, nullptr); nrnpy_pyobj_sym_ = hoc_lookup("PythonObject"); diff --git a/src/nrnpython/nrnpython.cpp b/src/nrnpython/nrnpython.cpp index ad78b3d134..1cc4afd638 100644 --- a/src/nrnpython/nrnpython.cpp +++ b/src/nrnpython/nrnpython.cpp @@ -175,7 +175,7 @@ static int nrnmingw_pyrun_interactiveloop() { return 0; } -extern PyObject* nrnpy_hoc(); +extern "C" PyObject* nrnpy_hoc(); extern PyObject* nrnpy_nrn(); /** @brief Start the Python interpreter. diff --git a/src/nrnpython/nrnpython.h b/src/nrnpython/nrnpython.h index 310d3ff9d2..b664c6e841 100644 --- a/src/nrnpython/nrnpython.h +++ b/src/nrnpython/nrnpython.h @@ -14,6 +14,8 @@ #undef _XOPEN_SOURCE #include "nrnwrap_Python.h" +#include "nrn_export.hpp" + #endif /*USE_PYTHON*/ #include diff --git a/src/nrnpython/rxd.cpp b/src/nrnpython/rxd.cpp index 04f123e1e2..7cf61f7513 100644 --- a/src/nrnpython/rxd.cpp +++ b/src/nrnpython/rxd.cpp @@ -138,7 +138,7 @@ static inline void* allocopy(void* src, size_t size) { return dst; } -extern "C" void rxd_set_no_diffusion() { +extern "C" NRN_EXPORT void rxd_set_no_diffusion() { int i; diffusion = FALSE; if (_rxd_a != NULL) { @@ -154,7 +154,7 @@ extern "C" void rxd_set_no_diffusion() { } } -extern "C" void free_curr_ptrs() { +extern "C" NRN_EXPORT void free_curr_ptrs() { _curr_count = 0; if (_curr_indices != NULL) free(_curr_indices); @@ -165,7 +165,7 @@ extern "C" void free_curr_ptrs() { _curr_ptrs.clear(); } -extern "C" void free_conc_ptrs() { +extern "C" NRN_EXPORT void free_conc_ptrs() { _conc_count = 0; if (_conc_indices != NULL) free(_conc_indices); @@ -174,10 +174,10 @@ extern "C" void free_conc_ptrs() { } -extern "C" void rxd_setup_curr_ptrs(int num_currents, - int* curr_index, - double* curr_scale, - PyHocObject** curr_ptrs) { +extern "C" NRN_EXPORT void rxd_setup_curr_ptrs(int num_currents, + int* curr_index, + double* curr_scale, + PyHocObject** curr_ptrs) { free_curr_ptrs(); /* info for NEURON currents - to update states */ _curr_count = num_currents; @@ -192,7 +192,9 @@ extern "C" void rxd_setup_curr_ptrs(int num_currents, _curr_ptrs[i] = curr_ptrs[i]->u.px_; } -extern "C" void rxd_setup_conc_ptrs(int conc_count, int* conc_index, PyHocObject** conc_ptrs) { +extern "C" NRN_EXPORT void rxd_setup_conc_ptrs(int conc_count, + int* conc_index, + PyHocObject** conc_ptrs) { /* info for NEURON concentration - to transfer to legacy */ int i; free_conc_ptrs(); @@ -204,12 +206,12 @@ extern "C" void rxd_setup_conc_ptrs(int conc_count, int* conc_index, PyHocObject _conc_ptrs[i] = conc_ptrs[i]->u.px_; } -extern "C" void rxd_include_node_flux3D(int grid_count, - int* grid_counts, - int* grids, - long* index, - double* scales, - PyObject** sources) { +extern "C" NRN_EXPORT void rxd_include_node_flux3D(int grid_count, + int* grid_counts, + int* grids, + long* index, + double* scales, + PyObject** sources) { Grid_node* g; int i = 0, j, k, n, grid_id; int offset = 0; @@ -288,7 +290,10 @@ extern "C" void rxd_include_node_flux3D(int grid_count, } } -extern "C" void rxd_include_node_flux1D(int n, long* index, double* scales, PyObject** sources) { +extern "C" NRN_EXPORT void rxd_include_node_flux1D(int n, + long* index, + double* scales, + PyObject** sources) { if (_node_flux_count != 0) { free(_node_flux_idx); free(_node_flux_scale); @@ -348,12 +353,12 @@ static void apply_node_flux1D(double dt, double* states) { apply_node_flux(_node_flux_count, _node_flux_idx, _node_flux_scale, _node_flux_src, dt, states); } -extern "C" void rxd_set_euler_matrix(int nrow, - int nnonzero, - long* nonzero_i, - long* nonzero_j, - double* nonzero_values, - double* c_diagonal) { +extern "C" NRN_EXPORT void rxd_set_euler_matrix(int nrow, + int nnonzero, + long* nonzero_i, + long* nonzero_j, + double* nonzero_values, + double* c_diagonal) { long i, j, idx; double val; unsigned int k, ps; @@ -470,20 +475,20 @@ static void mul(int nnonzero, } } -extern "C" void set_setup(const fptr setup_fn) { +extern "C" NRN_EXPORT void set_setup(const fptr setup_fn) { _setup = setup_fn; } -extern "C" void set_initialize(const fptr initialize_fn) { +extern "C" NRN_EXPORT void set_initialize(const fptr initialize_fn) { _initialize = initialize_fn; set_num_threads(NUM_THREADS); } -extern "C" void set_setup_matrices(fptr setup_matrices) { +extern "C" NRN_EXPORT void set_setup_matrices(fptr setup_matrices) { _setup_matrices = setup_matrices; } -extern "C" void set_setup_units(fptr setup_units) { +extern "C" NRN_EXPORT void set_setup_units(fptr setup_units) { _setup_units = setup_units; } @@ -623,14 +628,14 @@ static void free_currents() { _membrane_flux = FALSE; } -extern "C" void setup_currents(int num_currents, - int num_fluxes, - int* num_species, - int* node_idxs, - double* scales, - PyHocObject** ptrs, - int* mapped, - int* mapped_ecs) { +extern "C" NRN_EXPORT void setup_currents(int num_currents, + int num_fluxes, + int* num_species, + int* node_idxs, + double* scales, + PyHocObject** ptrs, + int* mapped, + int* mapped_ecs) { int i, j, k, id, side, count; int* induced_currents_ecs_idx; int* induced_currents_grid_id; @@ -766,7 +771,7 @@ static void _currents(double* rhs) { } } -extern "C" int rxd_nonvint_block(int method, int size, double* p1, double* p2, int) { +extern "C" NRN_EXPORT int rxd_nonvint_block(int method, int size, double* p1, double* p2, int) { if (initialized) { if (structure_change_cnt != prev_structure_change_cnt) { /*TODO: Exclude irrelevant (non-rxd) structural changes*/ @@ -846,19 +851,19 @@ extern "C" int rxd_nonvint_block(int method, int size, double* p1, double* p2, i *****************************************************************************/ -extern "C" void register_rate(int nspecies, - int nparam, - int nregions, - int nseg, - int* sidx, - int necs, - int necsparam, - int* ecs_ids, - int* ecsidx, - int nmult, - double* mult, - PyHocObject** vptrs, - ReactionRate f) { +extern "C" NRN_EXPORT void register_rate(int nspecies, + int nparam, + int nregions, + int nseg, + int* sidx, + int necs, + int necsparam, + int* ecs_ids, + int* ecsidx, + int nmult, + double* mult, + PyHocObject** vptrs, + ReactionRate f) { int i, j, k, idx, ecs_id, ecs_index, ecs_offset; unsigned char counted; Grid_node* g; @@ -965,7 +970,7 @@ extern "C" void register_rate(int nspecies, } } -extern "C" void clear_rates() { +extern "C" NRN_EXPORT void clear_rates() { ICSReactions *react, *prev; int i, j; for (react = _reactions; react != NULL;) { @@ -1003,7 +1008,7 @@ extern "C" void clear_rates() { } -extern "C" void species_atolscale(int id, double scale, int len, int* idx) { +extern "C" NRN_EXPORT void species_atolscale(int id, double scale, int len, int* idx) { SpeciesIndexList* list; SpeciesIndexList* prev; if (species_indices != NULL) { @@ -1028,7 +1033,7 @@ extern "C" void species_atolscale(int id, double scale, int len, int* idx) { list->next = NULL; } -extern "C" void remove_species_atolscale(int id) { +extern "C" NRN_EXPORT void remove_species_atolscale(int id) { SpeciesIndexList* list; SpeciesIndexList* prev; for (list = species_indices, prev = NULL; list != NULL; prev = list, list = list->next) { @@ -1044,7 +1049,10 @@ extern "C" void remove_species_atolscale(int id) { } } -extern "C" void setup_solver(double* my_states, int my_num_states, long* zvi, int num_zvi) { +extern "C" NRN_EXPORT void setup_solver(double* my_states, + int my_num_states, + long* zvi, + int num_zvi) { free_currents(); states = my_states; num_states = my_num_states; @@ -1130,7 +1138,7 @@ void TaskQueue_exe_tasks(std::size_t thread_index, TaskQueue* q) { } -void set_num_threads(const int n) { +extern "C" NRN_EXPORT void set_num_threads(const int n) { assert(n > 0); assert(NUM_THREADS > 0); // n and NUM_THREADS include the main thread, old_num and new_num refer to @@ -1184,7 +1192,7 @@ void TaskQueue_sync(TaskQueue* q) { q->waiting_cond.wait(lock, [q] { return q->length == 0; }); } -int get_num_threads(void) { +extern "C" NRN_EXPORT int get_num_threads(void) { return NUM_THREADS; } diff --git a/src/nrnpython/rxd_extracellular.cpp b/src/nrnpython/rxd_extracellular.cpp index 6c4e910800..aeb377eed4 100644 --- a/src/nrnpython/rxd_extracellular.cpp +++ b/src/nrnpython/rxd_extracellular.cpp @@ -163,14 +163,14 @@ Reaction* ecs_create_reaction(int list_idx, * grid_id - the grid id within the linked list - this corresponds to species * ECSReactionRate - the reaction function */ -extern "C" void ics_register_reaction(int list_idx, - int num_species, - int num_params, - int* species_id, - uint64_t* mc3d_start_indices, - int mc3d_region_size, - double* mc3d_mults, - ECSReactionRate f) { +extern "C" NRN_EXPORT void ics_register_reaction(int list_idx, + int num_species, + int num_params, + int* species_id, + uint64_t* mc3d_start_indices, + int mc3d_region_size, + double* mc3d_mults, + ECSReactionRate f) { ecs_create_reaction(list_idx, num_species, num_params, @@ -189,11 +189,11 @@ extern "C" void ics_register_reaction(int list_idx, * grid_id - the grid id within the linked list - this corresponds to species * ECSReactionRate - the reaction function */ -extern "C" void ecs_register_reaction(int list_idx, - int num_species, - int num_params, - int* species_id, - ECSReactionRate f) { +extern "C" NRN_EXPORT void ecs_register_reaction(int list_idx, + int num_species, + int num_params, + int* species_id, + ECSReactionRate f) { ecs_create_reaction(list_idx, num_species, num_params, species_id, f, NULL, NULL, 0, NULL); ecs_refresh_reactions(NUM_THREADS); } @@ -599,7 +599,7 @@ void _fadvance_fixed_step_3D(void) { scatter_concentrations(); } -extern "C" void scatter_concentrations(void) { +extern "C" NRN_EXPORT void scatter_concentrations(void) { /* transfer concentrations to classic NEURON */ Grid_node* grid; diff --git a/src/nrnpython/rxd_intracellular.cpp b/src/nrnpython/rxd_intracellular.cpp index d083eeb569..44fbb96c65 100644 --- a/src/nrnpython/rxd_intracellular.cpp +++ b/src/nrnpython/rxd_intracellular.cpp @@ -19,16 +19,16 @@ const int ICS_PREFETCH = 3; /* * Sets the data to be used by the grids for 1D/3D hybrid models */ -extern "C" void set_hybrid_data(int64_t* num_1d_indices_per_grid, - int64_t* num_3d_indices_per_grid, - int64_t* hybrid_indices1d, - int64_t* hybrid_indices3d, - int64_t* num_3d_indices_per_1d_seg, - int64_t* hybrid_grid_ids, - double* rates, - double* volumes1d, - double* volumes3d, - double* dxs) { +extern "C" NRN_EXPORT void set_hybrid_data(int64_t* num_1d_indices_per_grid, + int64_t* num_3d_indices_per_grid, + int64_t* hybrid_indices1d, + int64_t* hybrid_indices3d, + int64_t* num_3d_indices_per_1d_seg, + int64_t* hybrid_grid_ids, + double* rates, + double* volumes1d, + double* volumes3d, + double* dxs) { Grid_node* grid; int i, j, k, id; int grid_id_check = 0; diff --git a/src/nrnpython/rxd_llgramarea.cpp b/src/nrnpython/rxd_llgramarea.cpp index c4875f7c54..04c673e6cc 100644 --- a/src/nrnpython/rxd_llgramarea.cpp +++ b/src/nrnpython/rxd_llgramarea.cpp @@ -1,6 +1,8 @@ #include -extern "C" double llgramarea(double* p0, double* p1, double* p2) { +#include "nrn_export.hpp" + +extern "C" NRN_EXPORT double llgramarea(double* p0, double* p1, double* p2) { /* setup the vectors */ double a[] = {p0[0] - p1[0], p0[1] - p1[1], p0[2] - p1[2]}; double b[] = {p0[0] - p2[0], p0[1] - p2[1], p0[2] - p2[2]}; @@ -13,7 +15,7 @@ extern "C" double llgramarea(double* p0, double* p1, double* p2) { } -extern "C" double llpipedfromoriginvolume(double* p0, double* p1, double* p2) { +extern "C" NRN_EXPORT double llpipedfromoriginvolume(double* p0, double* p1, double* p2) { /* take the cross-product */ double cpx = p1[1] * p2[2] - p1[2] * p2[1]; double cpy = p1[2] * p2[0] - p1[0] * p2[2]; diff --git a/src/nrnpython/rxd_marching_cubes.cpp b/src/nrnpython/rxd_marching_cubes.cpp index 7c3c7e16c1..e8f865c108 100644 --- a/src/nrnpython/rxd_marching_cubes.cpp +++ b/src/nrnpython/rxd_marching_cubes.cpp @@ -8,6 +8,8 @@ #include #include +#include "nrn_export.hpp" + const int edgeTable[] = { 0x0, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 0x190, 0x99, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, @@ -333,22 +335,22 @@ void vi(double* p1, double* p2, double v1, double v2, double* out) { out[2] = p1[2] + mu * (p2[2] - p1[2]); } -extern "C" int find_triangles(double thresh, - double value0, - double value1, - double value2, - double value3, - double value4, - double value5, - double value6, - double value7, - double x0, - double x1, - double y0, - double y1, - double z0, - double z1, - double* out) { +extern "C" NRN_EXPORT int find_triangles(double thresh, + double value0, + double value1, + double value2, + double value3, + double value4, + double value5, + double value6, + double value7, + double x0, + double x1, + double y0, + double y1, + double z0, + double z1, + double* out) { double position[8][3] = {{x0, y0, z0}, {x1, y0, z0}, {x1, y1, z0}, diff --git a/src/nrnpython/rxdmath.cpp b/src/nrnpython/rxdmath.cpp index 3b3e4a2aed..300b24b280 100644 --- a/src/nrnpython/rxdmath.cpp +++ b/src/nrnpython/rxdmath.cpp @@ -3,29 +3,31 @@ #define M_PI (3.14159265358979323846) #endif +#include "nrn_export.hpp" + /*Some functions supported by numpy that aren't included in math.h * names and arguments match the wrappers used in rxdmath.py */ -extern "C" double factorial(const double x) { +extern "C" NRN_EXPORT double factorial(const double x) { return tgamma(x + 1.); } -extern "C" double degrees(const double radians) { +extern "C" NRN_EXPORT double degrees(const double radians) { return radians * (180. / M_PI); } -extern "C" void radians(const double degrees, double* radians) { +extern "C" NRN_EXPORT void radians(const double degrees, double* radians) { *radians = degrees * (M_PI / 180.); } -extern "C" double log1p(const double x) { +extern "C" NRN_EXPORT double log1p(const double x) { return log(x + 1.); } -extern "C" double vtrap(const double x, const double y) { +extern "C" NRN_EXPORT double vtrap(const double x, const double y) { if (fabs(x / y) < 1e-6) { return y * (1.0 - x / y / 2.0); } else {