11 #include <CLI/CLI.hpp>
20 #include "config/config.h"
24 #include "utils/logger.hpp"
65 namespace fs = std::filesystem;
66 using namespace nmodl;
68 using namespace visitor;
73 CLI::App app{fmt::format(
"NMODL : Source-to-Source Code Generation Framework [{}]",
77 std::vector<fs::path> mod_files;
80 std::string verbose(
"warning");
83 bool neuron_code(
false);
86 bool coreneuron_code(
true);
89 bool cpp_backend(
true);
92 bool oacc_backend(
false);
95 bool sympy_analytic(
false);
98 bool sympy_pade(
false);
101 bool sympy_cse(
false);
104 bool sympy_conductance(
false);
107 bool nmodl_inline(
false);
110 bool nmodl_unroll(
false);
113 bool nmodl_const_folding(
false);
116 bool nmodl_localize(
false);
119 bool nmodl_global_to_range(
false);
122 bool nmodl_local_to_range(
false);
125 bool codegen_cvode(
false);
128 bool localize_verbatim(
false);
131 bool local_rename(
true);
134 bool verbatim_inline(
false);
137 bool verbatim_rename(
true);
141 bool force_codegen(
false);
144 bool only_check_compatibility(
false);
147 bool optimize_ionvar_copies_codegen(
false);
150 std::string output_dir(
".");
153 std::string scratch_dir(
"tmp");
159 bool json_ast(
false);
162 bool nmodl_ast(
false);
165 bool json_perfstat(
false);
168 bool show_symtab(
false);
171 std::string data_type(
"double");
174 size_t blame_line = 0;
175 bool detailed_blame =
false;
178 app.get_formatter()->column_width(40);
179 app.set_help_all_flag(
"-H,--help-all",
"Print this help message including all sub-commands");
181 app.add_option(
"--verbose", verbose,
"Verbosity of logger output")
182 ->capture_default_str()
184 ->check(CLI::IsMember({
"trace",
"debug",
"info",
"warning",
"error",
"critical",
"off"}));
186 app.add_option(
"file", mod_files,
"One or more MOD files to process")
189 ->check(CLI::ExistingFile);
191 app.add_option(
"-o,--output", output_dir,
"Directory for backend code output")
192 ->capture_default_str()
194 app.add_option(
"--scratch", scratch_dir,
"Directory for intermediate code output")
195 ->capture_default_str()
197 app.add_option(
"--units", units_dir,
"Directory of units lib file")
198 ->capture_default_str()
200 app.add_flag(
"--neuron", neuron_code,
"Generate C++ code for NEURON");
201 app.add_flag(
"--coreneuron", coreneuron_code,
"Generate C++ code for CoreNEURON (Default)");
204 [](std::size_t count) {
208 "Print the version and exit");
209 auto host_opt = app.add_subcommand(
"host",
"HOST/CPU code backends")->ignore_case();
210 host_opt->add_flag(
"--c,--cpp", cpp_backend, fmt::format(
"C++ backend ({})", cpp_backend))
213 auto acc_opt = app.add_subcommand(
"acc",
"Accelerator code backends")->ignore_case();
217 fmt::format(
"C++ backend with OpenACC ({})", oacc_backend))
221 auto sympy_opt = app.add_subcommand(
"sympy",
"SymPy based analysis and optimizations")->ignore_case();
222 sympy_opt->add_flag(
"--analytic",
224 fmt::format(
"Solve ODEs using SymPy analytic integration ({})", sympy_analytic))->ignore_case();
225 sympy_opt->add_flag(
"--pade",
227 fmt::format(
"Pade approximation in SymPy analytic integration ({})", sympy_pade))->ignore_case();
228 sympy_opt->add_flag(
"--cse",
230 fmt::format(
"CSE (Common Subexpression Elimination) in SymPy analytic integration ({})", sympy_cse))->ignore_case();
231 sympy_opt->add_flag(
"--conductance",
233 fmt::format(
"Add CONDUCTANCE keyword in BREAKPOINT ({})", sympy_conductance))->ignore_case();
235 auto passes_opt = app.add_subcommand(
"passes",
"Analyse/Optimization passes")->ignore_case();
236 passes_opt->add_flag(
"--inline",
238 fmt::format(
"Perform inlining at NMODL level ({})", nmodl_inline))->ignore_case();
239 passes_opt->add_flag(
"--unroll",
241 fmt::format(
"Perform loop unroll at NMODL level ({})", nmodl_unroll))->ignore_case();
242 passes_opt->add_flag(
"--const-folding",
244 fmt::format(
"Perform constant folding at NMODL level ({})", nmodl_const_folding))->ignore_case();
245 passes_opt->add_flag(
"--localize",
247 fmt::format(
"Convert RANGE variables to LOCAL ({})", nmodl_localize))->ignore_case();
248 passes_opt->add_flag(
"--global-to-range",
249 nmodl_global_to_range,
250 fmt::format(
"Convert GLOBAL variables to RANGE ({})", nmodl_global_to_range))->ignore_case();
251 passes_opt->add_flag(
"--local-to-range",
252 nmodl_local_to_range,
253 fmt::format(
"Convert top level LOCAL variables to RANGE ({})", nmodl_local_to_range))->ignore_case();
254 passes_opt->add_flag(
"--localize-verbatim",
256 fmt::format(
"Convert RANGE variables to LOCAL even if verbatim block exist ({})", localize_verbatim))->ignore_case();
257 passes_opt->add_flag(
"--local-rename",
259 fmt::format(
"Rename LOCAL variable if variable of same name exist in global scope ({})", local_rename))->ignore_case();
260 passes_opt->add_flag(
"--verbatim-inline",
262 fmt::format(
"Inline even if verbatim block exist ({})", verbatim_inline))->ignore_case();
263 passes_opt->add_flag(
"--verbatim-rename",
265 fmt::format(
"Rename variables in verbatim block ({})", verbatim_rename))->ignore_case();
266 passes_opt->add_flag(
"--json-ast",
268 fmt::format(
"Write AST to JSON file ({})", json_ast))->ignore_case();
269 passes_opt->add_flag(
"--nmodl-ast",
271 fmt::format(
"Write the intermediate AST after each pass as a NMODL file to the scratch directory ({})", nmodl_ast))->ignore_case();
272 passes_opt->add_flag(
"--json-perf",
274 fmt::format(
"Write performance statistics to JSON file ({})", json_perfstat))->ignore_case();
275 passes_opt->add_flag(
"--show-symtab",
277 fmt::format(
"Write symbol table to stdout ({})", show_symtab))->ignore_case();
279 auto codegen_opt = app.add_subcommand(
"codegen",
"Code generation options")->ignore_case();
280 codegen_opt->add_option(
"--datatype",
282 "Data type for floating point variables")->capture_default_str()->ignore_case()->check(CLI::IsMember({
"float",
"double"}));
283 codegen_opt->add_flag(
"--force",
285 "Force code generation even if there is any incompatibility");
286 codegen_opt->add_flag(
"--only-check-compatibility",
287 only_check_compatibility,
288 "Check compatibility and return without generating code");
289 codegen_opt->add_flag(
"--opt-ionvar-copy",
290 optimize_ionvar_copies_codegen,
291 fmt::format(
"Optimize copies of ion variables ({})", optimize_ionvar_copies_codegen))->ignore_case();
292 codegen_opt->add_flag(
"--cvode",
294 fmt::format(
"Print code for CVODE ({})", codegen_cvode))->ignore_case();
297 auto blame_opt = app.add_subcommand(
"blame",
"Blame NMODL code that generated some code.");
298 blame_opt->add_option(
"--line", blame_line,
"Justify why this line was generated.");
299 blame_opt->add_flag(
"--detailed", detailed_blame,
"Justify by printing full backtraces.");
306 std::string simulator_name = neuron_code ?
"neuron" :
"coreneuron";
307 verbatim_rename = neuron_code ? false : verbatim_rename;
309 fs::create_directories(output_dir);
310 fs::create_directories(scratch_dir);
312 logger->set_level(spdlog::level::from_str(verbose));
315 const auto ast_to_nmodl = [nmodl_ast](
ast::Program& ast,
const std::string& filepath) {
318 logger->info(
"AST to NMODL transformation written to {}", filepath);
322 for (
const auto& file: mod_files) {
323 logger->info(
"Processing {}", file.string());
325 const auto modfile = file.stem().string();
328 auto filepath = [scratch_dir, modfile](
const std::string&
suffix) {
329 static int count = 0;
331 auto filename = fmt::format(
"{}.{:02d}.{}.mod", modfile, count++,
suffix);
332 return (std::filesystem::path(scratch_dir) / filename).string();
343 bool update_symtab =
false;
346 logger->info(
"Running argument renaming visitor");
352 logger->info(
"Running INITIAL block merge visitor");
355 ast_to_nmodl(*ast, filepath(
"merge_initial_block"));
360 logger->info(
"Running BREAKPOINT block merge visitor");
363 ast_to_nmodl(*ast, filepath(
"merge_breakpoint_block"));
368 logger->info(
"Running symtab visitor");
374 logger->info(
"Running semantic analysis visitor");
382 logger->info(
"Running state discontinuity visitor");
384 ast_to_nmodl(*ast, filepath(
"state_discontinuity"));
390 logger->info(
"Running CVode to cnexp visitor");
392 ast_to_nmodl(*ast, filepath(
"after_cvode_to_cnexp"));
396 if (nmodl_global_to_range) {
402 logger->info(
"Running GlobalToRange visitor");
405 ast_to_nmodl(*ast, filepath(
"global_to_range"));
409 if (nmodl_local_to_range) {
410 logger->info(
"Running LOCAL to ASSIGNED visitor");
414 ast_to_nmodl(*ast, filepath(
"local_to_assigned"));
419 logger->info(
"Running code compatibility checker");
423 auto compatibility_visitor = CodegenCompatibilityVisitor(simulator_name);
425 if (only_check_compatibility) {
426 return compatibility_visitor.find_unhandled_ast_nodes(*ast);
430 if (compatibility_visitor.find_unhandled_ast_nodes(*ast) && !force_codegen) {
436 logger->info(
"Printing symbol table");
438 symtab->
print(std::cout);
441 ast_to_nmodl(*ast, filepath(
"ast"));
444 std::filesystem::path file{scratch_dir};
445 file /= modfile +
".ast.json";
446 logger->info(
"Writing AST into {}", file.string());
450 if (verbatim_rename) {
451 logger->info(
"Running verbatim rename visitor");
453 ast_to_nmodl(*ast, filepath(
"verbatim_rename"));
456 if (nmodl_const_folding) {
457 logger->info(
"Running nmodl constant folding visitor");
459 ast_to_nmodl(*ast, filepath(
"constfold"));
463 logger->info(
"Running nmodl loop unroll visitor");
466 ast_to_nmodl(*ast, filepath(
"unroll"));
472 ast_to_nmodl(*ast, filepath(
"londifus"));
478 logger->info(
"Running SOLVE without METHOD visitor");
480 ast_to_nmodl(*ast, filepath(
"solve_without_method"));
484 logger->info(
"Running matexp solver visitor");
487 ast_to_nmodl(*ast, filepath(
"matexp"));
494 logger->info(
"Running KINETIC block visitor");
496 kineticBlockVisitor.visit_program(*ast);
498 const auto filename = filepath(
"kinetic");
499 ast_to_nmodl(*ast, filename);
500 if (nmodl_ast && kineticBlockVisitor.get_conserve_statement_count()) {
502 fmt::format(
"{} presents non-standard CONSERVE statements in DERIVATIVE "
503 "blocks. Use it only for debugging/developing",
509 logger->info(
"Running STEADYSTATE visitor");
512 ast_to_nmodl(*ast, filepath(
"steadystate"));
517 logger->info(
"Parsing Units");
524 update_symtab =
true;
527 logger->info(
"Running nmodl inline visitor");
530 ast_to_nmodl(*ast, filepath(
"inline"));
534 logger->info(
"Running local variable rename visitor");
537 ast_to_nmodl(*ast, filepath(
"local_rename"));
540 if (nmodl_localize) {
542 logger->info(
"Running localize visitor");
546 ast_to_nmodl(*ast, filepath(
"localize"));
552 if (!sympy_analytic) {
553 auto enable_sympy = [&sympy_analytic](
bool enable,
const std::string& reason) {
558 if (!sympy_analytic) {
559 logger->info(
"Automatically enabling sympy_analytic.");
560 logger->info(
"Required by: {}.", reason);
563 sympy_analytic =
true;
566 enable_sympy(
solver_exists(*ast,
"derivimplicit"),
"'SOLVE ... METHOD derivimplicit'");
569 "'DERIVATIVE' block");
571 "'NONLINEAR' block");
572 enable_sympy(
solver_exists(*ast,
"sparse"),
"'SOLVE ... METHOD sparse'");
576 if (sympy_conductance || sympy_analytic) {
581 if (neuron_code && codegen_cvode) {
582 logger->info(
"Running CVODE visitor");
585 ast_to_nmodl(*ast, filepath(
"cvode"));
588 if (sympy_conductance) {
589 logger->info(
"Running sympy conductance visitor");
592 ast_to_nmodl(*ast, filepath(
"sympy_conductance"));
595 if (sympy_analytic) {
596 logger->info(
"Running sympy solve visitor");
599 ast_to_nmodl(*ast, filepath(
"sympy_solve"));
607 logger->info(
"Running cnexp visitor");
609 ast_to_nmodl(*ast, filepath(
"cnexp"));
615 ast_to_nmodl(*ast, filepath(
"solveblock"));
619 std::string file{scratch_dir};
621 file.append(modfile);
622 file.append(
".perf.json");
623 logger->info(
"Writing performance statistics to {}", file);
640 ast_to_nmodl(*ast, filepath(
"TransformVisitor"));
646 ast_to_nmodl(*ast, filepath(
"FunctionCallpathVisitor"));
651 auto output_stream = std::ofstream(std::filesystem::path(output_dir) /
655 if (coreneuron_code && oacc_backend) {
656 logger->info(
"Running OpenACC backend code generator for CoreNEURON");
657 CodegenAccVisitor visitor(modfile,
660 optimize_ionvar_copies_codegen,
662 visitor.visit_program(*ast);
665 else if (coreneuron_code && !neuron_code && cpp_backend) {
666 logger->info(
"Running C++ backend code generator for CoreNEURON");
667 CodegenCoreneuronCppVisitor visitor(modfile,
670 optimize_ionvar_copies_codegen,
672 visitor.visit_program(*ast);
675 else if (neuron_code && cpp_backend) {
676 logger->info(
"Running C++ backend code generator for NEURON");
677 CodegenNeuronCppVisitor visitor(modfile,
680 optimize_ionvar_copies_codegen,
683 visitor.visit_program(*ast);
687 throw std::runtime_error(
688 "Non valid code generation configuration. Code generation with NMODL is "
689 "supported for NEURON with C++ backend or CoreNEURON with C++/OpenACC "
700 }
catch (
const std::runtime_error& e) {
701 std::cerr <<
"[FATAL] NMODL encountered an unhandled exception.\n";
702 std::cerr <<
" cwd = " << std::filesystem::current_path() <<
"\n";
704 for (
int i = 0;
i <
argc; ++
i) {
705 std::cerr <<
argv[
i] <<
" ";
707 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 used for generating the necessary AST nodes for matexp solver.
void visit_program(ast::Program &node) override
visit node of type ast::Program
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 used for generating the necessary AST nodes for matexp solver.
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.