11 #include <CLI/CLI.hpp>
61 namespace fs = std::filesystem;
62 using namespace nmodl;
63 using namespace codegen;
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();
292 #if NMODL_ENABLE_BACKWARD
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.");
300 CLI11_PARSE(app, argc, argv);
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) {
313 NmodlPrintVisitor(filepath).visit_program(ast);
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");
343 RenameFunctionArgumentsVisitor().visit_program(*ast);
348 logger->info(
"Running symtab visitor");
354 logger->info(
"Running semantic analysis visitor");
355 if (SemanticAnalysisVisitor(oacc_backend).check(*ast)) {
362 logger->info(
"Running CVode to cnexp visitor");
363 AfterCVodeToCnexpVisitor().visit_program(*ast);
364 ast_to_nmodl(*ast, filepath(
"after_cvode_to_cnexp"));
368 if (nmodl_global_to_range) {
371 PerfVisitor().visit_program(*ast);
374 logger->info(
"Running GlobalToRange visitor");
375 GlobalToRangeVisitor(*ast).visit_program(*ast);
377 ast_to_nmodl(*ast, filepath(
"global_to_range"));
381 if (nmodl_local_to_range) {
382 logger->info(
"Running LOCAL to ASSIGNED visitor");
383 PerfVisitor().visit_program(*ast);
384 LocalToAssignedVisitor().visit_program(*ast);
386 ast_to_nmodl(*ast, filepath(
"local_to_assigned"));
391 logger->info(
"Running code compatibility checker");
393 PerfVisitor().visit_program(*ast);
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());
419 JSONVisitor(file.string()).write(*ast);
422 if (verbatim_rename) {
423 logger->info(
"Running verbatim rename visitor");
424 VerbatimVarRenameVisitor().visit_program(*ast);
425 ast_to_nmodl(*ast, filepath(
"verbatim_rename"));
428 if (nmodl_const_folding) {
429 logger->info(
"Running nmodl constant folding visitor");
430 ConstantFolderVisitor().visit_program(*ast);
431 ast_to_nmodl(*ast, filepath(
"constfold"));
435 logger->info(
"Running nmodl loop unroll visitor");
436 LoopUnrollVisitor().visit_program(*ast);
437 ConstantFolderVisitor().visit_program(*ast);
438 ast_to_nmodl(*ast, filepath(
"unroll"));
443 CreateLongitudinalDiffusionBlocks().visit_program(*ast);
444 ast_to_nmodl(*ast, filepath(
"londifus"));
453 logger->info(
"Running KINETIC block visitor");
454 auto kineticBlockVisitor = KineticBlockVisitor();
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");
469 SteadystateVisitor().visit_program(*ast);
471 ast_to_nmodl(*ast, filepath(
"steadystate"));
476 logger->info(
"Parsing Units");
477 UnitsVisitor(units_dir).visit_program(*ast);
483 update_symtab =
true;
486 logger->info(
"Running nmodl inline visitor");
487 InlineVisitor().visit_program(*ast);
489 ast_to_nmodl(*ast, filepath(
"inline"));
493 logger->info(
"Running local variable rename visitor");
494 LocalVarRenameVisitor().visit_program(*ast);
496 ast_to_nmodl(*ast, filepath(
"local_rename"));
499 if (nmodl_localize) {
501 logger->info(
"Running localize visitor");
502 LocalizeVisitor(localize_verbatim).visit_program(*ast);
503 LocalVarRenameVisitor().visit_program(*ast);
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");
542 CvodeVisitor().visit_program(*ast);
544 ast_to_nmodl(*ast, filepath(
"cvode"));
547 if (sympy_conductance) {
548 logger->info(
"Running sympy conductance visitor");
549 SympyConductanceVisitor().visit_program(*ast);
551 ast_to_nmodl(*ast, filepath(
"sympy_conductance"));
554 if (sympy_analytic) {
555 logger->info(
"Running sympy solve visitor");
556 SympySolverVisitor(sympy_pade, sympy_cse).visit_program(*ast);
558 ast_to_nmodl(*ast, filepath(
"sympy_solve"));
566 logger->info(
"Running cnexp visitor");
567 NeuronSolveVisitor().visit_program(*ast);
568 ast_to_nmodl(*ast, filepath(
"cnexp"));
572 SolveBlockVisitor().visit_program(*ast);
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);
583 PerfVisitor(file).visit_program(*ast);
588 ImplicitArgumentVisitor{simulator_name}.visit_program(*ast);
594 PerfVisitor().visit_program(*ast);
599 ast_to_nmodl(*ast, filepath(
"TransformVisitor"));
604 FunctionCallpathVisitor{}.visit_program(*ast);
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");
619 optimize_ionvar_copies_codegen,
624 else if (coreneuron_code && !neuron_code && cpp_backend) {
625 logger->info(
"Running C++ backend code generator for CoreNEURON");
629 optimize_ionvar_copies_codegen,
634 else if (neuron_code && cpp_backend) {
635 logger->info(
"Running C++ backend code generator for NEURON");
639 optimize_ionvar_copies_codegen,
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 "
656 int main(
int argc,
const char* argv[]) {
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;