11 #include <CLI/CLI.hpp>
20 #include "config/config.h"
24 #include "utils/logger.hpp"
61 namespace fs = std::filesystem;
62 using namespace nmodl;
64 using namespace visitor;
69 CLI::App app{fmt::format(
"NMODL : Source-to-Source Code Generation Framework [{}]",
73 std::vector<fs::path> mod_files;
76 std::string verbose(
"warning");
79 bool neuron_code(
false);
82 bool coreneuron_code(
true);
85 bool cpp_backend(
true);
88 bool oacc_backend(
false);
91 bool sympy_analytic(
false);
94 bool sympy_pade(
false);
97 bool sympy_cse(
false);
100 bool sympy_conductance(
false);
103 bool nmodl_inline(
false);
106 bool nmodl_unroll(
false);
109 bool nmodl_const_folding(
false);
112 bool nmodl_localize(
false);
115 bool nmodl_global_to_range(
false);
118 bool nmodl_local_to_range(
false);
121 bool codegen_cvode(
false);
124 bool localize_verbatim(
false);
127 bool local_rename(
true);
130 bool verbatim_inline(
false);
133 bool verbatim_rename(
true);
137 bool force_codegen(
false);
140 bool only_check_compatibility(
false);
143 bool optimize_ionvar_copies_codegen(
false);
146 std::string output_dir(
".");
149 std::string scratch_dir(
"tmp");
155 bool json_ast(
false);
158 bool nmodl_ast(
false);
161 bool json_perfstat(
false);
164 bool show_symtab(
false);
167 std::string data_type(
"double");
170 size_t blame_line = 0;
171 bool detailed_blame =
false;
174 app.get_formatter()->column_width(40);
175 app.set_help_all_flag(
"-H,--help-all",
"Print this help message including all sub-commands");
177 app.add_option(
"--verbose", verbose,
"Verbosity of logger output")
178 ->capture_default_str()
180 ->check(CLI::IsMember({
"trace",
"debug",
"info",
"warning",
"error",
"critical",
"off"}));
182 app.add_option(
"file", mod_files,
"One or more MOD files to process")
185 ->check(CLI::ExistingFile);
187 app.add_option(
"-o,--output", output_dir,
"Directory for backend code output")
188 ->capture_default_str()
190 app.add_option(
"--scratch", scratch_dir,
"Directory for intermediate code output")
191 ->capture_default_str()
193 app.add_option(
"--units", units_dir,
"Directory of units lib file")
194 ->capture_default_str()
196 app.add_flag(
"--neuron", neuron_code,
"Generate C++ code for NEURON");
197 app.add_flag(
"--coreneuron", coreneuron_code,
"Generate C++ code for CoreNEURON (Default)");
200 [](std::size_t count) {
204 "Print the version and exit");
205 auto host_opt = app.add_subcommand(
"host",
"HOST/CPU code backends")->ignore_case();
206 host_opt->add_flag(
"--c,--cpp", cpp_backend, fmt::format(
"C++ backend ({})", cpp_backend))
209 auto acc_opt = app.add_subcommand(
"acc",
"Accelerator code backends")->ignore_case();
213 fmt::format(
"C++ backend with OpenACC ({})", oacc_backend))
217 auto sympy_opt = app.add_subcommand(
"sympy",
"SymPy based analysis and optimizations")->ignore_case();
218 sympy_opt->add_flag(
"--analytic",
220 fmt::format(
"Solve ODEs using SymPy analytic integration ({})", sympy_analytic))->ignore_case();
221 sympy_opt->add_flag(
"--pade",
223 fmt::format(
"Pade approximation in SymPy analytic integration ({})", sympy_pade))->ignore_case();
224 sympy_opt->add_flag(
"--cse",
226 fmt::format(
"CSE (Common Subexpression Elimination) in SymPy analytic integration ({})", sympy_cse))->ignore_case();
227 sympy_opt->add_flag(
"--conductance",
229 fmt::format(
"Add CONDUCTANCE keyword in BREAKPOINT ({})", sympy_conductance))->ignore_case();
231 auto passes_opt = app.add_subcommand(
"passes",
"Analyse/Optimization passes")->ignore_case();
232 passes_opt->add_flag(
"--inline",
234 fmt::format(
"Perform inlining at NMODL level ({})", nmodl_inline))->ignore_case();
235 passes_opt->add_flag(
"--unroll",
237 fmt::format(
"Perform loop unroll at NMODL level ({})", nmodl_unroll))->ignore_case();
238 passes_opt->add_flag(
"--const-folding",
240 fmt::format(
"Perform constant folding at NMODL level ({})", nmodl_const_folding))->ignore_case();
241 passes_opt->add_flag(
"--localize",
243 fmt::format(
"Convert RANGE variables to LOCAL ({})", nmodl_localize))->ignore_case();
244 passes_opt->add_flag(
"--global-to-range",
245 nmodl_global_to_range,
246 fmt::format(
"Convert GLOBAL variables to RANGE ({})", nmodl_global_to_range))->ignore_case();
247 passes_opt->add_flag(
"--local-to-range",
248 nmodl_local_to_range,
249 fmt::format(
"Convert top level LOCAL variables to RANGE ({})", nmodl_local_to_range))->ignore_case();
250 passes_opt->add_flag(
"--localize-verbatim",
252 fmt::format(
"Convert RANGE variables to LOCAL even if verbatim block exist ({})", localize_verbatim))->ignore_case();
253 passes_opt->add_flag(
"--local-rename",
255 fmt::format(
"Rename LOCAL variable if variable of same name exist in global scope ({})", local_rename))->ignore_case();
256 passes_opt->add_flag(
"--verbatim-inline",
258 fmt::format(
"Inline even if verbatim block exist ({})", verbatim_inline))->ignore_case();
259 passes_opt->add_flag(
"--verbatim-rename",
261 fmt::format(
"Rename variables in verbatim block ({})", verbatim_rename))->ignore_case();
262 passes_opt->add_flag(
"--json-ast",
264 fmt::format(
"Write AST to JSON file ({})", json_ast))->ignore_case();
265 passes_opt->add_flag(
"--nmodl-ast",
267 fmt::format(
"Write the intermediate AST after each pass as a NMODL file to the scratch directory ({})", nmodl_ast))->ignore_case();
268 passes_opt->add_flag(
"--json-perf",
270 fmt::format(
"Write performance statistics to JSON file ({})", json_perfstat))->ignore_case();
271 passes_opt->add_flag(
"--show-symtab",
273 fmt::format(
"Write symbol table to stdout ({})", show_symtab))->ignore_case();
275 auto codegen_opt = app.add_subcommand(
"codegen",
"Code generation options")->ignore_case();
276 codegen_opt->add_option(
"--datatype",
278 "Data type for floating point variables")->capture_default_str()->ignore_case()->check(CLI::IsMember({
"float",
"double"}));
279 codegen_opt->add_flag(
"--force",
281 "Force code generation even if there is any incompatibility");
282 codegen_opt->add_flag(
"--only-check-compatibility",
283 only_check_compatibility,
284 "Check compatibility and return without generating code");
285 codegen_opt->add_flag(
"--opt-ionvar-copy",
286 optimize_ionvar_copies_codegen,
287 fmt::format(
"Optimize copies of ion variables ({})", optimize_ionvar_copies_codegen))->ignore_case();
288 codegen_opt->add_flag(
"--cvode",
290 fmt::format(
"Print code for CVODE ({})", codegen_cvode))->ignore_case();
293 auto blame_opt = app.add_subcommand(
"blame",
"Blame NMODL code that generated some code.");
294 blame_opt->add_option(
"--line", blame_line,
"Justify why this line was generated.");
295 blame_opt->add_flag(
"--detailed", detailed_blame,
"Justify by printing full backtraces.");
302 std::string simulator_name = neuron_code ?
"neuron" :
"coreneuron";
303 verbatim_rename = neuron_code ? false : verbatim_rename;
305 fs::create_directories(output_dir);
306 fs::create_directories(scratch_dir);
308 logger->set_level(spdlog::level::from_str(verbose));
311 const auto ast_to_nmodl = [nmodl_ast](
ast::Program& ast,
const std::string& filepath) {
314 logger->info(
"AST to NMODL transformation written to {}", filepath);
318 for (
const auto& file: mod_files) {
319 logger->info(
"Processing {}", file.string());
321 const auto modfile = file.stem().string();
324 auto filepath = [scratch_dir, modfile](
const std::string&
suffix) {
325 static int count = 0;
327 auto filename = fmt::format(
"{}.{:02d}.{}.mod", modfile, count++,
suffix);
328 return (std::filesystem::path(scratch_dir) / filename).string();
339 bool update_symtab =
false;
342 logger->info(
"Running argument renaming visitor");
348 logger->info(
"Running symtab visitor");
354 logger->info(
"Running semantic analysis visitor");
362 logger->info(
"Running CVode to cnexp visitor");
364 ast_to_nmodl(*ast, filepath(
"after_cvode_to_cnexp"));
368 if (nmodl_global_to_range) {
374 logger->info(
"Running GlobalToRange visitor");
377 ast_to_nmodl(*ast, filepath(
"global_to_range"));
381 if (nmodl_local_to_range) {
382 logger->info(
"Running LOCAL to ASSIGNED visitor");
386 ast_to_nmodl(*ast, filepath(
"local_to_assigned"));
391 logger->info(
"Running code compatibility checker");
395 auto compatibility_visitor = CodegenCompatibilityVisitor(simulator_name);
397 if (only_check_compatibility) {
398 return compatibility_visitor.find_unhandled_ast_nodes(*ast);
402 if (compatibility_visitor.find_unhandled_ast_nodes(*ast) && !force_codegen) {
408 logger->info(
"Printing symbol table");
410 symtab->
print(std::cout);
413 ast_to_nmodl(*ast, filepath(
"ast"));
416 std::filesystem::path file{scratch_dir};
417 file /= modfile +
".ast.json";
418 logger->info(
"Writing AST into {}", file.string());
422 if (verbatim_rename) {
423 logger->info(
"Running verbatim rename visitor");
425 ast_to_nmodl(*ast, filepath(
"verbatim_rename"));
428 if (nmodl_const_folding) {
429 logger->info(
"Running nmodl constant folding visitor");
431 ast_to_nmodl(*ast, filepath(
"constfold"));
435 logger->info(
"Running nmodl loop unroll visitor");
438 ast_to_nmodl(*ast, filepath(
"unroll"));
444 ast_to_nmodl(*ast, filepath(
"londifus"));
453 logger->info(
"Running KINETIC block visitor");
455 kineticBlockVisitor.visit_program(*ast);
457 const auto filename = filepath(
"kinetic");
458 ast_to_nmodl(*ast, filename);
459 if (nmodl_ast && kineticBlockVisitor.get_conserve_statement_count()) {
461 fmt::format(
"{} presents non-standard CONSERVE statements in DERIVATIVE "
462 "blocks. Use it only for debugging/developing",
468 logger->info(
"Running STEADYSTATE visitor");
471 ast_to_nmodl(*ast, filepath(
"steadystate"));
476 logger->info(
"Parsing Units");
483 update_symtab =
true;
486 logger->info(
"Running nmodl inline visitor");
489 ast_to_nmodl(*ast, filepath(
"inline"));
493 logger->info(
"Running local variable rename visitor");
496 ast_to_nmodl(*ast, filepath(
"local_rename"));
499 if (nmodl_localize) {
501 logger->info(
"Running localize visitor");
505 ast_to_nmodl(*ast, filepath(
"localize"));
511 if (!sympy_analytic) {
512 auto enable_sympy = [&sympy_analytic](
bool enable,
const std::string& reason) {
517 if (!sympy_analytic) {
518 logger->info(
"Automatically enabling sympy_analytic.");
519 logger->info(
"Required by: {}.", reason);
522 sympy_analytic =
true;
525 enable_sympy(
solver_exists(*ast,
"derivimplicit"),
"'SOLVE ... METHOD derivimplicit'");
528 "'DERIVATIVE' block");
530 "'NONLINEAR' block");
531 enable_sympy(
solver_exists(*ast,
"sparse"),
"'SOLVE ... METHOD sparse'");
535 if (sympy_conductance || sympy_analytic) {
540 if (neuron_code && codegen_cvode) {
541 logger->info(
"Running CVODE visitor");
544 ast_to_nmodl(*ast, filepath(
"cvode"));
547 if (sympy_conductance) {
548 logger->info(
"Running sympy conductance visitor");
551 ast_to_nmodl(*ast, filepath(
"sympy_conductance"));
554 if (sympy_analytic) {
555 logger->info(
"Running sympy solve visitor");
558 ast_to_nmodl(*ast, filepath(
"sympy_solve"));
566 logger->info(
"Running cnexp visitor");
568 ast_to_nmodl(*ast, filepath(
"cnexp"));
574 ast_to_nmodl(*ast, filepath(
"solveblock"));
578 std::string file{scratch_dir};
580 file.append(modfile);
581 file.append(
".perf.json");
582 logger->info(
"Writing performance statistics to {}", file);
599 ast_to_nmodl(*ast, filepath(
"TransformVisitor"));
605 ast_to_nmodl(*ast, filepath(
"FunctionCallpathVisitor"));
610 auto output_stream = std::ofstream(std::filesystem::path(output_dir) /
614 if (coreneuron_code && oacc_backend) {
615 logger->info(
"Running OpenACC backend code generator for CoreNEURON");
616 CodegenAccVisitor visitor(modfile,
619 optimize_ionvar_copies_codegen,
621 visitor.visit_program(*ast);
624 else if (coreneuron_code && !neuron_code && cpp_backend) {
625 logger->info(
"Running C++ backend code generator for CoreNEURON");
626 CodegenCoreneuronCppVisitor visitor(modfile,
629 optimize_ionvar_copies_codegen,
631 visitor.visit_program(*ast);
634 else if (neuron_code && cpp_backend) {
635 logger->info(
"Running C++ backend code generator for NEURON");
636 CodegenNeuronCppVisitor visitor(modfile,
639 optimize_ionvar_copies_codegen,
642 visitor.visit_program(*ast);
646 throw std::runtime_error(
647 "Non valid code generation configuration. Code generation with NMODL is "
648 "supported for NEURON with C++ backend or CoreNEURON with C++/OpenACC "
659 }
catch (
const std::runtime_error& e) {
660 std::cerr <<
"[FATAL] NMODL encountered an unhandled exception.\n";
661 std::cerr <<
" cwd = " << std::filesystem::current_path() <<
"\n";
663 for (
int i = 0;
i <
argc; ++
i) {
664 std::cerr <<
argv[
i] <<
" ";
666 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 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 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 nrnunits.lib unit file before starting visiting the AST ...
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.
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.
int main(int argc, const char *argv[])
int run_nmodl(int argc, const char *argv[])
Visitor for STEADYSTATE solve statements
static std::string get_path()
Return path 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.