User Guide
inline.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2023 Blue Brain Project, EPFL.
3  * See the top-level LICENSE file for details.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <catch2/catch_test_macros.hpp>
9 #include <catch2/matchers/catch_matchers_string.hpp>
10 
11 #include "ast/program.hpp"
12 #include "parser/nmodl_driver.hpp"
18 
19 
20 using namespace nmodl;
21 using namespace visitor;
22 using namespace test;
23 using namespace test_utils;
24 
25 using Catch::Matchers::Equals;
27 
28 //=============================================================================
29 // Procedure/Function inlining tests
30 //=============================================================================
31 
32 std::string run_inline_visitor(const std::string& text) {
34  const auto& ast = driver.parse_string(text);
35 
36  SymtabVisitor().visit_program(*ast);
37  InlineVisitor().visit_program(*ast);
38  std::stringstream stream;
39  NmodlPrintVisitor(stream).visit_program(*ast);
40 
41 
42  // check that, after visitor rearrangement, parents are still up-to-date
43  CheckParentVisitor().check_ast(*ast);
44 
45  return stream.str();
46 }
47 
48 SCENARIO("Inlining of external procedure calls", "[visitor][inline]") {
49  GIVEN("Procedures with external procedure call") {
50  std::string nmodl_text = R"(
51  PROCEDURE rates_1() {
52  hello()
53  }
54 
55  PROCEDURE rates_2() {
56  bye()
57  }
58  )";
59 
60  THEN("nothing gets inlined") {
61  std::string input = reindent_text(nmodl_text);
62  auto result = run_inline_visitor(input);
63  REQUIRE(result == input);
64  }
65  }
66 }
67 
68 SCENARIO("Inlining of function call as argument in external function", "[visitor][inline]") {
69  GIVEN("An external function calling a function") {
70  std::string input_nmodl = R"(
71  FUNCTION rates_1() {
72  rates_1 = 1
73  }
74 
75  FUNCTION rates_2() {
76  net_send(rates_1(), 0)
77  }
78  )";
79 
80  std::string output_nmodl = R"(
81  FUNCTION rates_1() {
82  rates_1 = 1
83  }
84 
85  FUNCTION rates_2() {
86  LOCAL rates_1_in_0
87  {
88  rates_1_in_0 = 1
89  }
90  net_send(rates_1_in_0, 0)
91  }
92  )";
93  THEN("External function doesn't get inlined") {
94  std::string input = reindent_text(input_nmodl);
95  auto expected_result = reindent_text(output_nmodl);
96  auto result = run_inline_visitor(input);
97  REQUIRE(result == expected_result);
98  }
99  }
100 }
101 
102 SCENARIO("Inlining of simple, one level procedure call", "[visitor][inline]") {
103  GIVEN("A procedure calling another procedure") {
104  std::string input_nmodl = R"(
105  PROCEDURE rates_1() {
106  LOCAL x
107  rates_2(23.1)
108  }
109 
110  PROCEDURE rates_2(y) {
111  LOCAL x
112  x = 21.1*v+y
113  }
114  )";
115 
116  std::string output_nmodl = R"(
117  PROCEDURE rates_1() {
118  LOCAL x
119  {
120  LOCAL x, y_in_0
121  y_in_0 = 23.1
122  x = 21.1*v+y_in_0
123  }
124  }
125 
126  PROCEDURE rates_2(y) {
127  LOCAL x
128  x = 21.1*v+y
129  }
130  )";
131  THEN("Procedure body gets inlined") {
132  std::string input = reindent_text(input_nmodl);
133  auto expected_result = reindent_text(output_nmodl);
134  auto result = run_inline_visitor(input);
135  REQUIRE(result == expected_result);
136  }
137  }
138 }
139 
140 SCENARIO("Inlining of nested procedure call", "[visitor][inline]") {
141  GIVEN("A procedure with nested call chain and arguments") {
142  std::string input_nmodl = R"(
143  PROCEDURE rates_1() {
144  LOCAL x, y
145  rates_2()
146  rates_3(x, y)
147  }
148 
149  PROCEDURE rates_2() {
150  LOCAL x
151  x = 21.1*v + rates_3(x, x+1.1)
152  }
153 
154  PROCEDURE rates_3(a, b) {
155  LOCAL c
156  c = 21.1*v+a*b
157  }
158  )";
159 
160  std::string output_nmodl = R"(
161  PROCEDURE rates_1() {
162  LOCAL x, y
163  {
164  LOCAL x, rates_3_in_0
165  {
166  LOCAL c, a_in_0, b_in_0
167  a_in_0 = x
168  b_in_0 = x+1.1
169  c = 21.1*v+a_in_0*b_in_0
170  rates_3_in_0 = 0
171  }
172  x = 21.1*v+rates_3_in_0
173  }
174  {
175  LOCAL c, a_in_1, b_in_1
176  a_in_1 = x
177  b_in_1 = y
178  c = 21.1*v+a_in_1*b_in_1
179  }
180  }
181 
182  PROCEDURE rates_2() {
183  LOCAL x, rates_3_in_0
184  {
185  LOCAL c, a_in_0, b_in_0
186  a_in_0 = x
187  b_in_0 = x+1.1
188  c = 21.1*v+a_in_0*b_in_0
189  rates_3_in_0 = 0
190  }
191  x = 21.1*v+rates_3_in_0
192  }
193 
194  PROCEDURE rates_3(a, b) {
195  LOCAL c
196  c = 21.1*v+a*b
197  }
198  )";
199  THEN("Nested procedure gets inlined with variables renaming") {
200  std::string input = reindent_text(input_nmodl);
201  auto expected_result = reindent_text(output_nmodl);
202  auto result = run_inline_visitor(input);
203  REQUIRE(result == expected_result);
204  }
205  }
206 }
207 
208 SCENARIO("Inline function call in procedure", "[visitor][inline]") {
209  GIVEN("A procedure with function call") {
210  std::string input_nmodl = R"(
211  PROCEDURE rates_1() {
212  LOCAL x
213  x = 12.1+rates_2()
214  }
215 
216  FUNCTION rates_2() {
217  LOCAL x
218  x = 21.1*12.1+11
219  rates_2 = x
220  }
221  )";
222 
223  std::string output_nmodl = R"(
224  PROCEDURE rates_1() {
225  LOCAL x, rates_2_in_0
226  {
227  LOCAL x
228  x = 21.1*12.1+11
229  rates_2_in_0 = x
230  }
231  x = 12.1+rates_2_in_0
232  }
233 
234  FUNCTION rates_2() {
235  LOCAL x
236  x = 21.1*12.1+11
237  rates_2 = x
238  }
239  )";
240  THEN("Procedure body gets inlined") {
241  std::string input = reindent_text(input_nmodl);
242  auto expected_result = reindent_text(output_nmodl);
243  auto result = run_inline_visitor(input);
244  REQUIRE(result == expected_result);
245  }
246  }
247 }
248 
249 SCENARIO("Inling function call within conditional statement", "[visitor][inline]") {
250  GIVEN("A procedure with function call in if statement") {
251  std::string input_nmodl = R"(
252  FUNCTION rates_1() {
253  IF (rates_2()) {
254  rates_1 = 10
255  } ELSE {
256  rates_1 = 20
257  }
258  }
259 
260  FUNCTION rates_2() {
261  rates_2 = 10
262  }
263  )";
264 
265  std::string output_nmodl = R"(
266  FUNCTION rates_1() {
267  LOCAL rates_2_in_0
268  {
269  rates_2_in_0 = 10
270  }
271  IF (rates_2_in_0) {
272  rates_1 = 10
273  } ELSE {
274  rates_1 = 20
275  }
276  }
277 
278  FUNCTION rates_2() {
279  rates_2 = 10
280  }
281  )";
282 
283  THEN("Procedure body gets inlined and return value is used in if condition") {
284  std::string input = reindent_text(input_nmodl);
285  auto expected_result = reindent_text(output_nmodl);
286  auto result = run_inline_visitor(input);
287  REQUIRE(result == expected_result);
288  }
289  }
290 }
291 
292 SCENARIO("Inline multiple function calls in same statement", "[visitor][inline]") {
293  GIVEN("A procedure with two function calls in binary expression") {
294  std::string input_nmodl = R"(
295  FUNCTION rates_1() {
296  IF (rates_2()-rates_2()) {
297  rates_1 = 20
298  }
299  }
300 
301  FUNCTION rates_2() {
302  rates_2 = 10
303  }
304  )";
305 
306  std::string output_nmodl = R"(
307  FUNCTION rates_1() {
308  LOCAL rates_2_in_0, rates_2_in_1
309  {
310  rates_2_in_0 = 10
311  }
312  {
313  rates_2_in_1 = 10
314  }
315  IF (rates_2_in_0-rates_2_in_1) {
316  rates_1 = 20
317  }
318  }
319 
320  FUNCTION rates_2() {
321  rates_2 = 10
322  }
323  )";
324 
325  THEN("Procedure body gets inlined") {
326  std::string input = reindent_text(input_nmodl);
327  auto expected_result = reindent_text(output_nmodl);
328  auto result = run_inline_visitor(input);
329  REQUIRE(result == expected_result);
330  }
331  }
332 
333  GIVEN("A procedure with multiple function calls in an expression") {
334  std::string input_nmodl = R"(
335  FUNCTION rates_1() {
336  LOCAL x
337  x = (rates_2()+(rates_2()/rates_2()))
338  }
339 
340  FUNCTION rates_2() {
341  rates_2 = 10
342  }
343  )";
344 
345  std::string output_nmodl = R"(
346  FUNCTION rates_1() {
347  LOCAL x, rates_2_in_0, rates_2_in_1, rates_2_in_2
348  {
349  rates_2_in_0 = 10
350  }
351  {
352  rates_2_in_1 = 10
353  }
354  {
355  rates_2_in_2 = 10
356  }
357  x = (rates_2_in_0+(rates_2_in_1/rates_2_in_2))
358  }
359 
360  FUNCTION rates_2() {
361  rates_2 = 10
362  }
363  )";
364 
365  THEN("Procedure body gets inlined and return values are used in an expression") {
366  std::string input = reindent_text(input_nmodl);
367  auto expected_result = reindent_text(output_nmodl);
368  auto result = run_inline_visitor(input);
369  REQUIRE(result == expected_result);
370  }
371  }
372 }
373 
374 SCENARIO("Inline nested function calls withing arguments", "[visitor][inline]") {
375  GIVEN("A procedure with function call") {
376  std::string input_nmodl = R"(
377  FUNCTION rates_2() {
378  IF (rates_3(11,21)) {
379  rates_2 = 10.1
380  }
381  rates_2 = rates_3(12,22)
382  }
383 
384  FUNCTION rates_1() {
385  rates_1 = 12.1+rates_2()+exp(12.1)
386  }
387 
388  FUNCTION rates_3(x, y) {
389  rates_3 = x+y
390  }
391  )";
392 
393  std::string output_nmodl = R"(
394  FUNCTION rates_2() {
395  LOCAL rates_3_in_0, rates_3_in_1
396  {
397  LOCAL x_in_0, y_in_0
398  x_in_0 = 11
399  y_in_0 = 21
400  rates_3_in_0 = x_in_0+y_in_0
401  }
402  IF (rates_3_in_0) {
403  rates_2 = 10.1
404  }
405  {
406  LOCAL x_in_1, y_in_1
407  x_in_1 = 12
408  y_in_1 = 22
409  rates_3_in_1 = x_in_1+y_in_1
410  }
411  rates_2 = rates_3_in_1
412  }
413 
414  FUNCTION rates_1() {
415  LOCAL rates_2_in_0
416  {
417  LOCAL rates_3_in_0, rates_3_in_1
418  {
419  LOCAL x_in_0, y_in_0
420  x_in_0 = 11
421  y_in_0 = 21
422  rates_3_in_0 = x_in_0+y_in_0
423  }
424  IF (rates_3_in_0) {
425  rates_2_in_0 = 10.1
426  }
427  {
428  LOCAL x_in_1, y_in_1
429  x_in_1 = 12
430  y_in_1 = 22
431  rates_3_in_1 = x_in_1+y_in_1
432  }
433  rates_2_in_0 = rates_3_in_1
434  }
435  rates_1 = 12.1+rates_2_in_0+exp(12.1)
436  }
437 
438  FUNCTION rates_3(x, y) {
439  rates_3 = x+y
440  }
441  )";
442 
443  THEN("Procedure body gets inlined") {
444  std::string input = reindent_text(input_nmodl);
445  auto expected_result = reindent_text(output_nmodl);
446  auto result = run_inline_visitor(input);
447  REQUIRE(result == expected_result);
448  }
449  }
450 }
451 
452 SCENARIO("Inline function call in non-binary expression", "[visitor][inline]") {
453  GIVEN("A function call in unary expression") {
454  std::string input_nmodl = R"(
455  PROCEDURE rates_1() {
456  LOCAL x
457  x = (-rates_2(23.1))
458  }
459 
460  FUNCTION rates_2(y) {
461  rates_2 = 21.1*v+y
462  }
463  )";
464 
465  std::string output_nmodl = R"(
466  PROCEDURE rates_1() {
467  LOCAL x, rates_2_in_0
468  {
469  LOCAL y_in_0
470  y_in_0 = 23.1
471  rates_2_in_0 = 21.1*v+y_in_0
472  }
473  x = (-rates_2_in_0)
474  }
475 
476  FUNCTION rates_2(y) {
477  rates_2 = 21.1*v+y
478  }
479  )";
480  THEN("Function gets inlined in the unary expression") {
481  std::string input = reindent_text(input_nmodl);
482  auto expected_result = reindent_text(output_nmodl);
483  auto result = run_inline_visitor(input);
484  REQUIRE(result == expected_result);
485  }
486  }
487 
488  GIVEN("A function call as part of function argument itself") {
489  std::string input_nmodl = R"(
490  FUNCTION rates_1() {
491  rates_1 = 10 + rates_2( rates_2(11) )
492  }
493 
494  FUNCTION rates_2(x) {
495  rates_2 = 10+x
496  }
497  )";
498 
499  std::string output_nmodl = R"(
500  FUNCTION rates_1() {
501  LOCAL rates_2_in_0, rates_2_in_1
502  {
503  LOCAL x_in_0
504  x_in_0 = 11
505  rates_2_in_0 = 10+x_in_0
506  }
507  {
508  LOCAL x_in_1
509  x_in_1 = rates_2_in_0
510  rates_2_in_1 = 10+x_in_1
511  }
512  rates_1 = 10+rates_2_in_1
513  }
514 
515  FUNCTION rates_2(x) {
516  rates_2 = 10+x
517  }
518  )";
519  THEN("Function and it's arguments gets inlined recursively") {
520  std::string input = reindent_text(input_nmodl);
521  auto expected_result = reindent_text(output_nmodl);
522  auto result = run_inline_visitor(input);
523  REQUIRE(result == expected_result);
524  }
525  }
526 }
527 
528 
529 SCENARIO("Inline function call as standalone expression", "[visitor][inline]") {
530  GIVEN("Function call as a statement") {
531  std::string input_nmodl = R"(
532  PROCEDURE rates_1() {
533  LOCAL x
534  rates_2(23.1)
535  }
536 
537  FUNCTION rates_2(y) {
538  rates_2 = 21.1*v+y
539  }
540  )";
541 
542  std::string output_nmodl = R"(
543  PROCEDURE rates_1() {
544  LOCAL x, rates_2_in_0
545  {
546  LOCAL y_in_0
547  y_in_0 = 23.1
548  rates_2_in_0 = 21.1*v+y_in_0
549  }
550  }
551 
552  FUNCTION rates_2(y) {
553  rates_2 = 21.1*v+y
554  }
555  )";
556  THEN("Function gets inlined but it's value is not used") {
557  std::string input = reindent_text(input_nmodl);
558  auto expected_result = reindent_text(output_nmodl);
559  auto result = run_inline_visitor(input);
560  REQUIRE(result == expected_result);
561  }
562  }
563 }
564 
565 SCENARIO("Inline procedure call as standalone statement as well as part of expression",
566  "[visitor][inline]") {
567  GIVEN("A procedure call in expression and statement") {
568  std::string input_nmodl = R"(
569  PROCEDURE rates_1() {
570  LOCAL x
571  x = 10 + rates_2()
572  rates_2()
573  }
574 
575  PROCEDURE rates_2() {
576  }
577  )";
578 
579  std::string output_nmodl = R"(
580  PROCEDURE rates_1() {
581  LOCAL x, rates_2_in_0
582  {
583  rates_2_in_0 = 0
584  }
585  x = 10+rates_2_in_0
586  {
587  }
588  }
589 
590  PROCEDURE rates_2() {
591  }
592  )";
593  THEN("Return statement from procedure (with zero value) is used") {
594  std::string input = reindent_text(input_nmodl);
595  auto expected_result = reindent_text(output_nmodl);
596  auto result = run_inline_visitor(input);
597  REQUIRE(result == expected_result);
598  }
599  }
600 }
601 
602 SCENARIO("Inlining pass handles local-global name conflict", "[visitor][inline]") {
603  GIVEN("A procedure with local variable that exist in global scope") {
604  /// note that x in rates_2 should still update global x after inlining
605  std::string input_nmodl = R"(
606  NEURON {
607  RANGE x
608  }
609 
610  PROCEDURE rates_1() {
611  LOCAL x
612  x = 12
613  rates_2(x)
614  x = 11
615  }
616 
617  PROCEDURE rates_2(y) {
618  x = 10+y
619  }
620  )";
621 
622  std::string output_nmodl = R"(
623  NEURON {
624  RANGE x
625  }
626 
627  PROCEDURE rates_1() {
628  LOCAL x_r_0
629  x_r_0 = 12
630  {
631  LOCAL y_in_0
632  y_in_0 = x_r_0
633  x = 10+y_in_0
634  }
635  x_r_0 = 11
636  }
637 
638  PROCEDURE rates_2(y) {
639  x = 10+y
640  }
641  )";
642 
643  THEN("Caller variables get renamed first and then inlining is done") {
644  std::string input = reindent_text(input_nmodl);
645  auto expected_result = reindent_text(output_nmodl);
646  auto result = run_inline_visitor(input);
647  REQUIRE(result == expected_result);
648  }
649  }
650 }
651 
652 SCENARIO("Trying to inline a function with VERBATIM block") {
653  GIVEN("A VERBATIM block without a return inside") {
654  std::string input_nmodl = R"(
655  PROCEDURE verb_1() {
656  VERBATIM
657  pow(1,2);
658  ENDVERBATIM
659  }
660 
661  PROCEDURE verb_2() {
662  verb_1()
663  }
664  )";
665 
666  std::string output_nmodl = R"(
667  PROCEDURE verb_1() {
668  VERBATIM
669  pow(1,2);
670  ENDVERBATIM
671  }
672 
673  PROCEDURE verb_2() {
674  {
675  VERBATIM
676  pow(1,2);
677  ENDVERBATIM
678  }
679  }
680  )";
681  THEN("It gets inlined") {
682  std::string input = reindent_text(input_nmodl);
683  auto expected_result = reindent_text(output_nmodl);
684  auto result = run_inline_visitor(input);
685  REQUIRE(expected_result == result);
686  }
687  }
688  GIVEN("A VERBATIM block with a return value") {
689  std::string nmodl_text = R"(
690  PROCEDURE verb_1() {
691  VERBATIM
692  return pow(1,2);
693  ENDVERBATIM
694  }
695 
696  PROCEDURE verb_2() {
697  verb_1()
698  }
699  )";
700 
701  THEN("It is not inlined") {
702  std::string input = reindent_text(nmodl_text);
703  auto result = run_inline_visitor(input);
704  REQUIRE(result == input);
705  }
706  }
707 }
test_utils.hpp
nmodl::parser::NmodlDriver
Class that binds all pieces together for parsing nmodl file.
Definition: nmodl_driver.hpp:67
run_inline_visitor
std::string run_inline_visitor(const std::string &text)
Definition: inline.cpp:32
nmodl::test_utils::reindent_text
std::string reindent_text(const std::string &text, int indent_level)
Reindent nmodl text for text-to-text comparison.
Definition: test_utils.cpp:53
nmodl
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
program.hpp
Auto generated AST classes declaration.
driver
nmodl::parser::UnitDriver driver
Definition: parser.cpp:28
nmodl::parser::UnitDriver::parse_string
bool parse_string(const std::string &input)
parser Units provided as string (used for testing)
Definition: unit_driver.cpp:40
SCENARIO
SCENARIO("Inlining of external procedure calls", "[visitor][inline]")
Definition: inline.cpp:48
checkparent_visitor.hpp
Visitor for checking parents of ast nodes
inline_visitor.hpp
Visitor to inline local procedure and function calls
nmodl_driver.hpp
symtab_visitor.hpp
THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED.
nmodl_visitor.hpp
THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED.