11 #include <CLI/CLI.hpp>
20 #include "config/config.h"
24 #include "utils/logger.hpp"
62 namespace fs = std::filesystem;
63 using namespace nmodl;
65 using namespace visitor;
70 CLI::App app{fmt::format(
"NMODL : Source-to-Source Code Generation Framework [{}]",
74 std::vector<fs::path> mod_files;
77 std::string verbose(
"warning");
80 bool neuron_code(
false);
83 bool coreneuron_code(
true);
86 bool cpp_backend(
true);
89 bool oacc_backend(
false);
92 bool sympy_analytic(
false);
95 bool sympy_pade(
false);
98 bool sympy_cse(
false);
101 bool sympy_conductance(
false);
104 bool nmodl_inline(
false);
107 bool nmodl_unroll(
false);
110 bool nmodl_const_folding(
false);
113 bool nmodl_localize(
false);
116 bool nmodl_global_to_range(
false);
119 bool nmodl_local_to_range(
false);
122 bool codegen_cvode(
false);
125 bool localize_verbatim(
false);
128 bool local_rename(
true);
131 bool verbatim_inline(
false);
134 bool verbatim_rename(
true);
138 bool force_codegen(
false);
141 bool only_check_compatibility(
false);
144 bool optimize_ionvar_copies_codegen(
false);
147 std::string output_dir(
".");
150 std::string scratch_dir(
"tmp");
156 bool json_ast(
false);
159 bool nmodl_ast(
false);
162 bool json_perfstat(
false);
165 bool show_symtab(
false);
168 std::string data_type(
"double");
171 size_t blame_line = 0;
172 bool detailed_blame =
false;
175 app.get_formatter()->column_width(40);
176 app.set_help_all_flag(
"-H,--help-all",
"Print this help message including all sub-commands");
178 app.add_option(
"--verbose", verbose,
"Verbosity of logger output")
179 ->capture_default_str()
181 ->check(CLI::IsMember({
"trace",
"debug",
"info",
"warning",
"error",
"critical",
"off"}));
183 app.add_option(
"file", mod_files,
"One or more MOD files to process")
186 ->check(CLI::ExistingFile);
188 app.add_option(
"-o,--output", output_dir,
"Directory for backend code output")
189 ->capture_default_str()
191 app.add_option(
"--scratch", scratch_dir,
"Directory for intermediate code output")
192 ->capture_default_str()
194 app.add_option(
"--units", units_dir,
"Directory of units lib file")
195 ->capture_default_str()
197 app.add_flag(
"--neuron", neuron_code,
"Generate C++ code for NEURON");
198 app.add_flag(
"--coreneuron", coreneuron_code,
"Generate C++ code for CoreNEURON (Default)");
201 [](std::size_t count) {
205 "Print the version and exit");
206 auto host_opt = app.add_subcommand(
"host",
"HOST/CPU code backends")->ignore_case();
207 host_opt->add_flag(
"--c,--cpp", cpp_backend, fmt::format(
"C++ backend ({})", cpp_backend))
210 auto acc_opt = app.add_subcommand(
"acc",
"Accelerator code backends")->ignore_case();
214 fmt::format(
"C++ backend with OpenACC ({})", oacc_backend))
218 auto sympy_opt = app.add_subcommand(
"sympy",
"SymPy based analysis and optimizations")->ignore_case();
219 sympy_opt->add_flag(
"--analytic",
221 fmt::format(
"Solve ODEs using SymPy analytic integration ({})", sympy_analytic))->ignore_case();
222 sympy_opt->add_flag(
"--pade",
224 fmt::format(
"Pade approximation in SymPy analytic integration ({})", sympy_pade))->ignore_case();
225 sympy_opt->add_flag(
"--cse",
227 fmt::format(
"CSE (Common Subexpression Elimination) in SymPy analytic integration ({})", sympy_cse))->ignore_case();
228 sympy_opt->add_flag(
"--conductance",
230 fmt::format(
"Add CONDUCTANCE keyword in BREAKPOINT ({})", sympy_conductance))->ignore_case();
232 auto passes_opt = app.add_subcommand(
"passes",
"Analyse/Optimization passes")->ignore_case();
233 passes_opt->add_flag(
"--inline",
235 fmt::format(
"Perform inlining at NMODL level ({})", nmodl_inline))->ignore_case();
236 passes_opt->add_flag(
"--unroll",
238 fmt::format(
"Perform loop unroll at NMODL level ({})", nmodl_unroll))->ignore_case();
239 passes_opt->add_flag(
"--const-folding",
241 fmt::format(
"Perform constant folding at NMODL level ({})", nmodl_const_folding))->ignore_case();
242 passes_opt->add_flag(
"--localize",
244 fmt::format(
"Convert RANGE variables to LOCAL ({})", nmodl_localize))->ignore_case();
245 passes_opt->add_flag(
"--global-to-range",
246 nmodl_global_to_range,
247 fmt::format(
"Convert GLOBAL variables to RANGE ({})", nmodl_global_to_range))->ignore_case();
248 passes_opt->add_flag(
"--local-to-range",
249 nmodl_local_to_range,
250 fmt::format(
"Convert top level LOCAL variables to RANGE ({})", nmodl_local_to_range))->ignore_case();
251 passes_opt->add_flag(
"--localize-verbatim",
253 fmt::format(
"Convert RANGE variables to LOCAL even if verbatim block exist ({})", localize_verbatim))->ignore_case();
254 passes_opt->add_flag(
"--local-rename",
256 fmt::format(
"Rename LOCAL variable if variable of same name exist in global scope ({})", local_rename))->ignore_case();
257 passes_opt->add_flag(
"--verbatim-inline",
259 fmt::format(
"Inline even if verbatim block exist ({})", verbatim_inline))->ignore_case();
260 passes_opt->add_flag(
"--verbatim-rename",
262 fmt::format(
"Rename variables in verbatim block ({})", verbatim_rename))->ignore_case();
263 passes_opt->add_flag(
"--json-ast",
265 fmt::format(
"Write AST to JSON file ({})", json_ast))->ignore_case();
266 passes_opt->add_flag(
"--nmodl-ast",
268 fmt::format(
"Write the intermediate AST after each pass as a NMODL file to the scratch directory ({})", nmodl_ast))->ignore_case();
269 passes_opt->add_flag(
"--json-perf",
271 fmt::format(
"Write performance statistics to JSON file ({})", json_perfstat))->ignore_case();
272 passes_opt->add_flag(
"--show-symtab",
274 fmt::format(
"Write symbol table to stdout ({})", show_symtab))->ignore_case();
276 auto codegen_opt = app.add_subcommand(
"codegen",
"Code generation options")->ignore_case();
277 codegen_opt->add_option(
"--datatype",
279 "Data type for floating point variables")->capture_default_str()->ignore_case()->check(CLI::IsMember({
"float",
"double"}));
280 codegen_opt->add_flag(
"--force",
282 "Force code generation even if there is any incompatibility");
283 codegen_opt->add_flag(
"--only-check-compatibility",
284 only_check_compatibility,
285 "Check compatibility and return without generating code");
286 codegen_opt->add_flag(
"--opt-ionvar-copy",
287 optimize_ionvar_copies_codegen,
288 fmt::format(
"Optimize copies of ion variables ({})", optimize_ionvar_copies_codegen))->ignore_case();
289 codegen_opt->add_flag(
"--cvode",
291 fmt::format(
"Print code for CVODE ({})", codegen_cvode))->ignore_case();
294 auto blame_opt = app.add_subcommand(
"blame",
"Blame NMODL code that generated some code.");
295 blame_opt->add_option(
"--line", blame_line,
"Justify why this line was generated.");
296 blame_opt->add_flag(
"--detailed", detailed_blame,
"Justify by printing full backtraces.");
303 std::string simulator_name = neuron_code ?
"neuron" :
"coreneuron";
304 verbatim_rename = neuron_code ? false : verbatim_rename;
306 fs::create_directories(output_dir);
307 fs::create_directories(scratch_dir);
309 logger->set_level(spdlog::level::from_str(verbose));
312 const auto ast_to_nmodl = [nmodl_ast](
ast::Program& ast,
const std::string& filepath) {
315 logger->info(
"AST to NMODL transformation written to {}", filepath);
319 for (
const auto& file: mod_files) {
320 logger->info(
"Processing {}", file.string());
322 const auto modfile = file.stem().string();
325 auto filepath = [scratch_dir, modfile](
const std::string&
suffix) {
326 static int count = 0;
328 auto filename = fmt::format(
"{}.{:02d}.{}.mod", modfile, count++,
suffix);
329 return (std::filesystem::path(scratch_dir) / filename).string();
340 bool update_symtab =
false;
343 logger->info(
"Running argument renaming visitor");
349 logger->info(
"Running INITIAL block merge visitor");
351 ast_to_nmodl(*ast, filepath(
"merge_initial_block"));
356 logger->info(
"Running symtab visitor");
362 logger->info(
"Running semantic analysis visitor");
370 logger->info(
"Running CVode to cnexp visitor");
372 ast_to_nmodl(*ast, filepath(
"after_cvode_to_cnexp"));
376 if (nmodl_global_to_range) {
382 logger->info(
"Running GlobalToRange visitor");
385 ast_to_nmodl(*ast, filepath(
"global_to_range"));
389 if (nmodl_local_to_range) {
390 logger->info(
"Running LOCAL to ASSIGNED visitor");
394 ast_to_nmodl(*ast, filepath(
"local_to_assigned"));
399 logger->info(
"Running code compatibility checker");
403 auto compatibility_visitor = CodegenCompatibilityVisitor(simulator_name);
405 if (only_check_compatibility) {
406 return compatibility_visitor.find_unhandled_ast_nodes(*ast);
410 if (compatibility_visitor.find_unhandled_ast_nodes(*ast) && !force_codegen) {
416 logger->info(
"Printing symbol table");
418 symtab->
print(std::cout);
421 ast_to_nmodl(*ast, filepath(
"ast"));
424 std::filesystem::path file{scratch_dir};
425 file /= modfile +
".ast.json";
426 logger->info(
"Writing AST into {}", file.string());
430 if (verbatim_rename) {
431 logger->info(
"Running verbatim rename visitor");
433 ast_to_nmodl(*ast, filepath(
"verbatim_rename"));
436 if (nmodl_const_folding) {
437 logger->info(
"Running nmodl constant folding visitor");
439 ast_to_nmodl(*ast, filepath(
"constfold"));
443 logger->info(
"Running nmodl loop unroll visitor");
446 ast_to_nmodl(*ast, filepath(
"unroll"));
452 ast_to_nmodl(*ast, filepath(
"londifus"));
461 logger->info(
"Running KINETIC block visitor");
463 kineticBlockVisitor.visit_program(*ast);
465 const auto filename = filepath(
"kinetic");
466 ast_to_nmodl(*ast, filename);
467 if (nmodl_ast && kineticBlockVisitor.get_conserve_statement_count()) {
469 fmt::format(
"{} presents non-standard CONSERVE statements in DERIVATIVE "
470 "blocks. Use it only for debugging/developing",
476 logger->info(
"Running STEADYSTATE visitor");
479 ast_to_nmodl(*ast, filepath(
"steadystate"));
484 logger->info(
"Parsing Units");
491 update_symtab =
true;
494 logger->info(
"Running nmodl inline visitor");
497 ast_to_nmodl(*ast, filepath(
"inline"));
501 logger->info(
"Running local variable rename visitor");
504 ast_to_nmodl(*ast, filepath(
"local_rename"));
507 if (nmodl_localize) {
509 logger->info(
"Running localize visitor");
513 ast_to_nmodl(*ast, filepath(
"localize"));
519 if (!sympy_analytic) {
520 auto enable_sympy = [&sympy_analytic](
bool enable,
const std::string& reason) {
525 if (!sympy_analytic) {
526 logger->info(
"Automatically enabling sympy_analytic.");
527 logger->info(
"Required by: {}.", reason);
530 sympy_analytic =
true;
533 enable_sympy(
solver_exists(*ast,
"derivimplicit"),
"'SOLVE ... METHOD derivimplicit'");
536 "'DERIVATIVE' block");
538 "'NONLINEAR' block");
539 enable_sympy(
solver_exists(*ast,
"sparse"),
"'SOLVE ... METHOD sparse'");
543 if (sympy_conductance || sympy_analytic) {
548 if (neuron_code && codegen_cvode) {
549 logger->info(
"Running CVODE visitor");
552 ast_to_nmodl(*ast, filepath(
"cvode"));
555 if (sympy_conductance) {
556 logger->info(
"Running sympy conductance visitor");
559 ast_to_nmodl(*ast, filepath(
"sympy_conductance"));
562 if (sympy_analytic) {
563 logger->info(
"Running sympy solve visitor");
566 ast_to_nmodl(*ast, filepath(
"sympy_solve"));
574 logger->info(
"Running cnexp visitor");
576 ast_to_nmodl(*ast, filepath(
"cnexp"));
582 ast_to_nmodl(*ast, filepath(
"solveblock"));
586 std::string file{scratch_dir};
588 file.append(modfile);
589 file.append(
".perf.json");
590 logger->info(
"Writing performance statistics to {}", file);
607 ast_to_nmodl(*ast, filepath(
"TransformVisitor"));
613 ast_to_nmodl(*ast, filepath(
"FunctionCallpathVisitor"));
618 auto output_stream = std::ofstream(std::filesystem::path(output_dir) /
622 if (coreneuron_code && oacc_backend) {
623 logger->info(
"Running OpenACC backend code generator for CoreNEURON");
624 CodegenAccVisitor visitor(modfile,
627 optimize_ionvar_copies_codegen,
629 visitor.visit_program(*ast);
632 else if (coreneuron_code && !neuron_code && cpp_backend) {
633 logger->info(
"Running C++ backend code generator for CoreNEURON");
634 CodegenCoreneuronCppVisitor visitor(modfile,
637 optimize_ionvar_copies_codegen,
639 visitor.visit_program(*ast);
642 else if (neuron_code && cpp_backend) {
643 logger->info(
"Running C++ backend code generator for NEURON");
644 CodegenNeuronCppVisitor visitor(modfile,
647 optimize_ionvar_copies_codegen,
650 visitor.visit_program(*ast);
654 throw std::runtime_error(
655 "Non valid code generation configuration. Code generation with NMODL is "
656 "supported for NEURON with C++ backend or CoreNEURON with C++/OpenACC "
667 }
catch (
const std::runtime_error& e) {
668 std::cerr <<
"[FATAL] NMODL encountered an unhandled exception.\n";
669 std::cerr <<
" cwd = " << std::filesystem::current_path() <<
"\n";
671 for (
int i = 0;
i <
argc; ++
i) {
672 std::cerr <<
argv[
i] <<
" ";
674 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 all INITIAL 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 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 which merges all INITIAL blocks into one.
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.