11 #include <CLI/CLI.hpp>
20 #include "config/config.h"
24 #include "utils/logger.hpp"
64 namespace fs = std::filesystem;
65 using namespace nmodl;
67 using namespace visitor;
72 CLI::App app{fmt::format(
"NMODL : Source-to-Source Code Generation Framework [{}]",
76 std::vector<fs::path> mod_files;
79 std::string verbose(
"warning");
82 bool neuron_code(
false);
85 bool coreneuron_code(
true);
88 bool cpp_backend(
true);
91 bool oacc_backend(
false);
94 bool sympy_analytic(
false);
97 bool sympy_pade(
false);
100 bool sympy_cse(
false);
103 bool sympy_conductance(
false);
106 bool nmodl_inline(
false);
109 bool nmodl_unroll(
false);
112 bool nmodl_const_folding(
false);
115 bool nmodl_localize(
false);
118 bool nmodl_global_to_range(
false);
121 bool nmodl_local_to_range(
false);
124 bool codegen_cvode(
false);
127 bool localize_verbatim(
false);
130 bool local_rename(
true);
133 bool verbatim_inline(
false);
136 bool verbatim_rename(
true);
140 bool force_codegen(
false);
143 bool only_check_compatibility(
false);
146 bool optimize_ionvar_copies_codegen(
false);
149 std::string output_dir(
".");
152 std::string scratch_dir(
"tmp");
158 bool json_ast(
false);
161 bool nmodl_ast(
false);
164 bool json_perfstat(
false);
167 bool show_symtab(
false);
170 std::string data_type(
"double");
173 size_t blame_line = 0;
174 bool detailed_blame =
false;
177 app.get_formatter()->column_width(40);
178 app.set_help_all_flag(
"-H,--help-all",
"Print this help message including all sub-commands");
180 app.add_option(
"--verbose", verbose,
"Verbosity of logger output")
181 ->capture_default_str()
183 ->check(CLI::IsMember({
"trace",
"debug",
"info",
"warning",
"error",
"critical",
"off"}));
185 app.add_option(
"file", mod_files,
"One or more MOD files to process")
188 ->check(CLI::ExistingFile);
190 app.add_option(
"-o,--output", output_dir,
"Directory for backend code output")
191 ->capture_default_str()
193 app.add_option(
"--scratch", scratch_dir,
"Directory for intermediate code output")
194 ->capture_default_str()
196 app.add_option(
"--units", units_dir,
"Directory of units lib file")
197 ->capture_default_str()
199 app.add_flag(
"--neuron", neuron_code,
"Generate C++ code for NEURON");
200 app.add_flag(
"--coreneuron", coreneuron_code,
"Generate C++ code for CoreNEURON (Default)");
203 [](std::size_t count) {
207 "Print the version and exit");
208 auto host_opt = app.add_subcommand(
"host",
"HOST/CPU code backends")->ignore_case();
209 host_opt->add_flag(
"--c,--cpp", cpp_backend, fmt::format(
"C++ backend ({})", cpp_backend))
212 auto acc_opt = app.add_subcommand(
"acc",
"Accelerator code backends")->ignore_case();
216 fmt::format(
"C++ backend with OpenACC ({})", oacc_backend))
220 auto sympy_opt = app.add_subcommand(
"sympy",
"SymPy based analysis and optimizations")->ignore_case();
221 sympy_opt->add_flag(
"--analytic",
223 fmt::format(
"Solve ODEs using SymPy analytic integration ({})", sympy_analytic))->ignore_case();
224 sympy_opt->add_flag(
"--pade",
226 fmt::format(
"Pade approximation in SymPy analytic integration ({})", sympy_pade))->ignore_case();
227 sympy_opt->add_flag(
"--cse",
229 fmt::format(
"CSE (Common Subexpression Elimination) in SymPy analytic integration ({})", sympy_cse))->ignore_case();
230 sympy_opt->add_flag(
"--conductance",
232 fmt::format(
"Add CONDUCTANCE keyword in BREAKPOINT ({})", sympy_conductance))->ignore_case();
234 auto passes_opt = app.add_subcommand(
"passes",
"Analyse/Optimization passes")->ignore_case();
235 passes_opt->add_flag(
"--inline",
237 fmt::format(
"Perform inlining at NMODL level ({})", nmodl_inline))->ignore_case();
238 passes_opt->add_flag(
"--unroll",
240 fmt::format(
"Perform loop unroll at NMODL level ({})", nmodl_unroll))->ignore_case();
241 passes_opt->add_flag(
"--const-folding",
243 fmt::format(
"Perform constant folding at NMODL level ({})", nmodl_const_folding))->ignore_case();
244 passes_opt->add_flag(
"--localize",
246 fmt::format(
"Convert RANGE variables to LOCAL ({})", nmodl_localize))->ignore_case();
247 passes_opt->add_flag(
"--global-to-range",
248 nmodl_global_to_range,
249 fmt::format(
"Convert GLOBAL variables to RANGE ({})", nmodl_global_to_range))->ignore_case();
250 passes_opt->add_flag(
"--local-to-range",
251 nmodl_local_to_range,
252 fmt::format(
"Convert top level LOCAL variables to RANGE ({})", nmodl_local_to_range))->ignore_case();
253 passes_opt->add_flag(
"--localize-verbatim",
255 fmt::format(
"Convert RANGE variables to LOCAL even if verbatim block exist ({})", localize_verbatim))->ignore_case();
256 passes_opt->add_flag(
"--local-rename",
258 fmt::format(
"Rename LOCAL variable if variable of same name exist in global scope ({})", local_rename))->ignore_case();
259 passes_opt->add_flag(
"--verbatim-inline",
261 fmt::format(
"Inline even if verbatim block exist ({})", verbatim_inline))->ignore_case();
262 passes_opt->add_flag(
"--verbatim-rename",
264 fmt::format(
"Rename variables in verbatim block ({})", verbatim_rename))->ignore_case();
265 passes_opt->add_flag(
"--json-ast",
267 fmt::format(
"Write AST to JSON file ({})", json_ast))->ignore_case();
268 passes_opt->add_flag(
"--nmodl-ast",
270 fmt::format(
"Write the intermediate AST after each pass as a NMODL file to the scratch directory ({})", nmodl_ast))->ignore_case();
271 passes_opt->add_flag(
"--json-perf",
273 fmt::format(
"Write performance statistics to JSON file ({})", json_perfstat))->ignore_case();
274 passes_opt->add_flag(
"--show-symtab",
276 fmt::format(
"Write symbol table to stdout ({})", show_symtab))->ignore_case();
278 auto codegen_opt = app.add_subcommand(
"codegen",
"Code generation options")->ignore_case();
279 codegen_opt->add_option(
"--datatype",
281 "Data type for floating point variables")->capture_default_str()->ignore_case()->check(CLI::IsMember({
"float",
"double"}));
282 codegen_opt->add_flag(
"--force",
284 "Force code generation even if there is any incompatibility");
285 codegen_opt->add_flag(
"--only-check-compatibility",
286 only_check_compatibility,
287 "Check compatibility and return without generating code");
288 codegen_opt->add_flag(
"--opt-ionvar-copy",
289 optimize_ionvar_copies_codegen,
290 fmt::format(
"Optimize copies of ion variables ({})", optimize_ionvar_copies_codegen))->ignore_case();
291 codegen_opt->add_flag(
"--cvode",
293 fmt::format(
"Print code for CVODE ({})", codegen_cvode))->ignore_case();
296 auto blame_opt = app.add_subcommand(
"blame",
"Blame NMODL code that generated some code.");
297 blame_opt->add_option(
"--line", blame_line,
"Justify why this line was generated.");
298 blame_opt->add_flag(
"--detailed", detailed_blame,
"Justify by printing full backtraces.");
305 std::string simulator_name = neuron_code ?
"neuron" :
"coreneuron";
306 verbatim_rename = neuron_code ? false : verbatim_rename;
308 fs::create_directories(output_dir);
309 fs::create_directories(scratch_dir);
311 logger->set_level(spdlog::level::from_str(verbose));
314 const auto ast_to_nmodl = [nmodl_ast](
ast::Program& ast,
const std::string& filepath) {
317 logger->info(
"AST to NMODL transformation written to {}", filepath);
321 for (
const auto& file: mod_files) {
322 logger->info(
"Processing {}", file.string());
324 const auto modfile = file.stem().string();
327 auto filepath = [scratch_dir, modfile](
const std::string&
suffix) {
328 static int count = 0;
330 auto filename = fmt::format(
"{}.{:02d}.{}.mod", modfile, count++,
suffix);
331 return (std::filesystem::path(scratch_dir) / filename).string();
342 bool update_symtab =
false;
345 logger->info(
"Running argument renaming visitor");
351 logger->info(
"Running INITIAL block merge visitor");
354 ast_to_nmodl(*ast, filepath(
"merge_initial_block"));
359 logger->info(
"Running BREAKPOINT block merge visitor");
362 ast_to_nmodl(*ast, filepath(
"merge_breakpoint_block"));
367 logger->info(
"Running symtab visitor");
373 logger->info(
"Running semantic analysis visitor");
381 logger->info(
"Running state discontinuity visitor");
383 ast_to_nmodl(*ast, filepath(
"state_discontinuity"));
389 logger->info(
"Running CVode to cnexp visitor");
391 ast_to_nmodl(*ast, filepath(
"after_cvode_to_cnexp"));
395 if (nmodl_global_to_range) {
401 logger->info(
"Running GlobalToRange visitor");
404 ast_to_nmodl(*ast, filepath(
"global_to_range"));
408 if (nmodl_local_to_range) {
409 logger->info(
"Running LOCAL to ASSIGNED visitor");
413 ast_to_nmodl(*ast, filepath(
"local_to_assigned"));
418 logger->info(
"Running code compatibility checker");
422 auto compatibility_visitor = CodegenCompatibilityVisitor(simulator_name);
424 if (only_check_compatibility) {
425 return compatibility_visitor.find_unhandled_ast_nodes(*ast);
429 if (compatibility_visitor.find_unhandled_ast_nodes(*ast) && !force_codegen) {
435 logger->info(
"Printing symbol table");
437 symtab->
print(std::cout);
440 ast_to_nmodl(*ast, filepath(
"ast"));
443 std::filesystem::path file{scratch_dir};
444 file /= modfile +
".ast.json";
445 logger->info(
"Writing AST into {}", file.string());
449 if (verbatim_rename) {
450 logger->info(
"Running verbatim rename visitor");
452 ast_to_nmodl(*ast, filepath(
"verbatim_rename"));
455 if (nmodl_const_folding) {
456 logger->info(
"Running nmodl constant folding visitor");
458 ast_to_nmodl(*ast, filepath(
"constfold"));
462 logger->info(
"Running nmodl loop unroll visitor");
465 ast_to_nmodl(*ast, filepath(
"unroll"));
471 ast_to_nmodl(*ast, filepath(
"londifus"));
477 logger->info(
"Running SOLVE without METHOD visitor");
479 ast_to_nmodl(*ast, filepath(
"solve_without_method"));
486 logger->info(
"Running KINETIC block visitor");
488 kineticBlockVisitor.visit_program(*ast);
490 const auto filename = filepath(
"kinetic");
491 ast_to_nmodl(*ast, filename);
492 if (nmodl_ast && kineticBlockVisitor.get_conserve_statement_count()) {
494 fmt::format(
"{} presents non-standard CONSERVE statements in DERIVATIVE "
495 "blocks. Use it only for debugging/developing",
501 logger->info(
"Running STEADYSTATE visitor");
504 ast_to_nmodl(*ast, filepath(
"steadystate"));
509 logger->info(
"Parsing Units");
516 update_symtab =
true;
519 logger->info(
"Running nmodl inline visitor");
522 ast_to_nmodl(*ast, filepath(
"inline"));
526 logger->info(
"Running local variable rename visitor");
529 ast_to_nmodl(*ast, filepath(
"local_rename"));
532 if (nmodl_localize) {
534 logger->info(
"Running localize visitor");
538 ast_to_nmodl(*ast, filepath(
"localize"));
544 if (!sympy_analytic) {
545 auto enable_sympy = [&sympy_analytic](
bool enable,
const std::string& reason) {
550 if (!sympy_analytic) {
551 logger->info(
"Automatically enabling sympy_analytic.");
552 logger->info(
"Required by: {}.", reason);
555 sympy_analytic =
true;
558 enable_sympy(
solver_exists(*ast,
"derivimplicit"),
"'SOLVE ... METHOD derivimplicit'");
561 "'DERIVATIVE' block");
563 "'NONLINEAR' block");
564 enable_sympy(
solver_exists(*ast,
"sparse"),
"'SOLVE ... METHOD sparse'");
568 if (sympy_conductance || sympy_analytic) {
573 if (neuron_code && codegen_cvode) {
574 logger->info(
"Running CVODE visitor");
577 ast_to_nmodl(*ast, filepath(
"cvode"));
580 if (sympy_conductance) {
581 logger->info(
"Running sympy conductance visitor");
584 ast_to_nmodl(*ast, filepath(
"sympy_conductance"));
587 if (sympy_analytic) {
588 logger->info(
"Running sympy solve visitor");
591 ast_to_nmodl(*ast, filepath(
"sympy_solve"));
599 logger->info(
"Running cnexp visitor");
601 ast_to_nmodl(*ast, filepath(
"cnexp"));
607 ast_to_nmodl(*ast, filepath(
"solveblock"));
611 std::string file{scratch_dir};
613 file.append(modfile);
614 file.append(
".perf.json");
615 logger->info(
"Writing performance statistics to {}", file);
632 ast_to_nmodl(*ast, filepath(
"TransformVisitor"));
638 ast_to_nmodl(*ast, filepath(
"FunctionCallpathVisitor"));
643 auto output_stream = std::ofstream(std::filesystem::path(output_dir) /
647 if (coreneuron_code && oacc_backend) {
648 logger->info(
"Running OpenACC backend code generator for CoreNEURON");
649 CodegenAccVisitor visitor(modfile,
652 optimize_ionvar_copies_codegen,
654 visitor.visit_program(*ast);
657 else if (coreneuron_code && !neuron_code && cpp_backend) {
658 logger->info(
"Running C++ backend code generator for CoreNEURON");
659 CodegenCoreneuronCppVisitor visitor(modfile,
662 optimize_ionvar_copies_codegen,
664 visitor.visit_program(*ast);
667 else if (neuron_code && cpp_backend) {
668 logger->info(
"Running C++ backend code generator for NEURON");
669 CodegenNeuronCppVisitor visitor(modfile,
672 optimize_ionvar_copies_codegen,
675 visitor.visit_program(*ast);
679 throw std::runtime_error(
680 "Non valid code generation configuration. Code generation with NMODL is "
681 "supported for NEURON with C++ backend or CoreNEURON with C++/OpenACC "
692 }
catch (
const std::runtime_error& e) {
693 std::cerr <<
"[FATAL] NMODL encountered an unhandled exception.\n";
694 std::cerr <<
" cwd = " << std::filesystem::current_path() <<
"\n";
696 for (
int i = 0;
i <
argc; ++
i) {
697 std::cerr <<
argv[
i] <<
" ";
699 std::cerr << std::endl;
Visitor to change usage of after_cvode solver to cnexp.
Concrete visitor for all AST classes.
Represents top level AST node for whole NMODL input.
symtab::ModelSymbolTable * get_model_symbol_table()
Return global symbol table for the mod file.
Class that binds all pieces together for parsing nmodl file.
static EmbeddedPythonLoader & get_instance()
Construct (if not already done) and get the only instance of this class.
const pybind_wrap_api & api()
Get a pointer to the pybind_wrap_api struct.
void print(std::ostream &ostr) const
pretty print
Visitor to change usage of after_cvode solver to cnexp.
void visit_program(ast::Program &node) override
visit node of type ast::Program
Perform constant folding of integer/float/double expressions.
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor used for generating the necessary AST nodes for CVODE.
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor for traversing FunctionBlock s and ProcedureBlocks through their FunctionCall s
void visit_program(const ast::Program &node) override
visit node of type ast::Program
Visitor to convert GLOBAL variables to RANGE variables.
Visitor to inline local procedure and function calls
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor for printing AST in JSON format
JSONVisitor & write(const ast::Program &program)
Visitor for kinetic block statements
Visitor to convert top level LOCAL variables to ASSIGNED variables.
void visit_program(ast::Program &node) override
Visit ast::Program node to transform top level LOCAL variables to ASSIGNED if they are written in the...
Visitor to rename local variables conflicting with global scope
Visitor to transform global variable usage to local
void visit_program(const ast::Program &node) override
visit node of type ast::Program
Unroll for loop in the AST.
Visitor which merges given top-level blocks into one.
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor that solves ODEs using old solvers of NEURON
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor for printing AST back to NMODL
void visit_program(const ast::Program &node) override
visit node of type ast::Program
Visitor for measuring performance related information
void visit_program(const ast::Program &node) override
visit node of type ast::Program
Visitor to check some semantic rules on the AST
Replace solve block statements with actual solution node in the AST.
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor for adding an explicit method to a SOLVE block which has an implicit one
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor used for replacing literal calls to state_discontinuity in a NET_RECEIVE block.
Visitor for STEADYSTATE solve statements
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor for generating CONDUCTANCE statements for ions
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor for systems of algebraic and differential equations
void visit_program(ast::Program &node) override
visit node of type ast::Program
Concrete visitor for constructing symbol table from AST.
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor for Units blocks of AST.
void visit_program(ast::Program &node) override
Override visit_program function to parse the contents of the nrnunits.lib unit file before starting v...
Rename variable in verbatim block.
Visitor for printing C++ code with OpenACC backend
Visitor for printing compatibility issues of the mod file
Visitor for printing C++ code compatible with legacy api of CoreNEURON
Visitor for printing C++ code compatible with legacy api of NEURON
Common utility functions for file/dir manipulation.
Perform constant folding of integer/float/double expressions.
Visitor used for generating the necessary AST nodes for CVODE.
Visitor for traversing FunctionBlock s and ProcedureBlocks through their FunctionCall s
Visitor to convert GLOBAL variables to RANGE variables.
@ DERIVATIVE_BLOCK
type of ast::DerivativeBlock
@ NON_LINEAR_BLOCK
type of ast::NonLinearBlock
@ LINEAR_BLOCK
type of ast::LinearBlock
bool parse_file(const std::string &filename)
parse Units file
Visitor for adding implicit arguments to [Core]NEURON functions.
Get node name with indexed for the IndexedName node and the dependencies of DiffEqExpression node.
Visitor to inline local procedure and function calls
THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED.
Visitor for kinetic block statements
Visitor to convert top level LOCAL variables to ASSIGNED variables.
Visitor to rename local variables conflicting with global scope
Visitor to transform global variable usage to local
Unroll for loop in the AST.
Visitor which merges given top-level blocks into one.
static void check(VecTNode &)
std::unique_ptr< Blame > make_blame(size_t blame_line, BlameLevel blame_level)
encapsulates code generation backend implementations
bool node_exists(const ast::Ast &node, ast::AstNodeType ast_type)
Whether a node of type ast_type exists as a subnode of node.
bool solver_exists(const ast::Ast &node, const std::string &name)
Whether or not a solver of type name exists in the AST.
Visitor that solves ODEs using old solvers of NEURON
THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED.
Visitor for measuring performance related information
Auto generated AST classes declaration.
Visitor to check some semantic rules on the AST
Replace solve block statements with actual solution node in the AST.
Visitor for adding an explicit method to a SOLVE block which has an implicit one
int main(int argc, const char *argv[])
int run_nmodl(int argc, const char *argv[])
Visitor used for replacing literal calls to state_discontinuity in a NET_RECEIVE block.
Visitor for STEADYSTATE solve statements
static std::string get_path()
Return path of units database file.
static std::string get_content(const std::string &path)
Return content of units database file.
static std::string to_string()
return version string (version + git id) as a string
decltype(&initialize_interpreter_func) initialize_interpreter
decltype(&finalize_interpreter_func) finalize_interpreter
Visitor for adding implicit arguments to [Core]NEURON functions.
Visitor for generating CONDUCTANCE statements for ions
Visitor for systems of algebraic and differential equations
THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED.
nmodl::parser::UnitDriver driver
Visitor for Units blocks of AST.
Rename variable in verbatim block.
Visitor for verbatim blocks of AST
Utility functions for visitors implementation.