NEURON
defuse_analyze_visitor.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 
9 
10 #include <algorithm>
11 #include <utility>
12 
13 #include "ast/all.hpp"
14 #include "utils/logger.hpp"
15 
16 namespace nmodl {
17 namespace visitor {
18 
19 using printer::JSONPrinter;
21 
22 /// DUState to string conversion for pretty-printing
23 std::string to_string(DUState state) {
24  switch (state) {
25  case DUState::U:
26  return "U";
27  case DUState::D:
28  return "D";
29  case DUState::CD:
30  return "CD";
31  case DUState::LU:
32  return "LU";
33  case DUState::LD:
34  return "LD";
36  return "CONDITIONAL_BLOCK";
37  case DUState::IF:
38  return "IF";
39  case DUState::ELSEIF:
40  return "ELSEIF";
41  case DUState::ELSE:
42  return "ELSE";
43  case DUState::UNKNOWN:
44  return "UNKNOWN";
45  case DUState::NONE:
46  return "NONE";
47  default:
48  throw std::runtime_error("Unhandled DUState?");
49  }
50 }
51 
52 std::ostream& operator<<(std::ostream& os, DUState state) {
53  return os << to_string(state);
54 }
55 
56 /// DUInstance to JSON string
57 void DUInstance::print(JSONPrinter& printer) const {
58  if (children.empty()) {
59  printer.add_node(to_string(state));
60  } else {
61  printer.push_block(to_string(state));
62  for (const auto& inst: children) {
63  inst.print(printer);
64  }
65  printer.pop_block();
66  }
67 }
68 
69 /// DUChain to JSON string
70 std::string DUChain::to_string(bool compact) const {
71  std::ostringstream stream;
72  JSONPrinter printer(stream);
73  printer.compact_json(compact);
74 
75  printer.push_block(name);
76  for (const auto& instance: chain) {
77  instance.print(printer);
78  }
79  printer.pop_block();
80 
81  printer.flush();
82  return stream.str();
83 }
84 
85 /** Evaluate sub-blocks like if, elseif and else
86  * As these are innermost blocks, we have to just check first use
87  * of variable in this block and that's the result of this block.
88  */
91  for (const auto& chain: children) {
92  const auto& child_state = chain.eval(variable_type);
93  if ((variable_type == DUVariableType::Global &&
94  (child_state == DUState::U || child_state == DUState::D)) ||
95  (variable_type == DUVariableType::Local &&
96  (child_state == DUState::LU || child_state == DUState::LD))) {
97  result = child_state;
98  break;
99  }
100  if (child_state == DUState::CD) {
102  }
103  }
104  return result;
105 }
106 
107 /**
108  * Evaluate conditional block containing sub-blocks like if, elseif and else
109  *
110  * Note that sub-blocks are already evaluated by sub_block_eval() and has only
111  * leaf nodes. In order to find effective defuse, following rules are used:
112  *
113  * - If variable is "used" in any of the sub-block then it's effectively
114  * "U". This is because any branch can be taken at runtime.
115  *
116  * - If variable is "defined" in all sub-blocks doesn't mean that it's
117  * effectively "D". This is because if we can have just "if-elseif"
118  * which could never be taken. Same for empty "if". In order to decide
119  * if it is "D", we make sure there is no empty block and there must
120  * be "else" block with "D". Note that "U" definitions are already
121  * covered in 1) and hence this rule is safe.
122  *
123  * - If there is an "if" with "D" or empty "if" followed by "D" in "else"
124  * block, we can't say it's definition. In this case we return "CD".
125  *
126  * - If there is empty "if" followed by "U" in "else" block, we can say
127  * it's "use". This is because we don't want to "localize" such variables.
128  *
129  * - If we reach else block with either D or CD and if there is no empty
130  * block encountered, this means every block has either "D" or "CD". In
131  * this case we can say that entire block effectively has "D".
132  */
134  DUVariableType variable_type = DUVariableType::Global) const {
136  bool block_with_none = false;
137 
138  for (const auto& chain: children) {
139  auto child_state = chain.eval(variable_type);
140  if ((variable_type == DUVariableType::Global && child_state == DUState::U) ||
141  (variable_type == DUVariableType::Local && child_state == DUState::LU)) {
142  result = child_state;
143  break;
144  }
145  if (child_state == DUState::NONE) {
146  block_with_none = true;
147  }
148  if ((variable_type == DUVariableType::Global && child_state == DUState::D) ||
149  (variable_type == DUVariableType::Local && child_state == DUState::LD) ||
150  child_state == DUState::CD) {
152  if (chain.state == DUState::ELSE && !block_with_none) {
153  result = child_state;
154  break;
155  }
156  }
157  }
158  return result;
159 }
160 
161 /** Find "effective" usage of variable from def-use chain.
162  * Note that we are interested in "global" variable usage
163  * and hence we consider only [U,D] states and not [LU, LD]
164  */
166  auto result = state;
168  result = sub_block_eval(variable_type);
169  } else if (state == DUState::CONDITIONAL_BLOCK) {
170  result = conditional_block_eval(variable_type);
171  }
172  return result;
173 }
174 
175 /// first usage of a variable in a block decides whether it's definition
176 /// or usage. Note that if-else blocks already evaluated.
178  auto result = DUState::NONE;
179  for (auto& inst: chain) {
180  auto re = inst.eval(variable_type);
181  if ((variable_type == DUVariableType::Global && (re == DUState::U || re == DUState::D)) ||
182  (variable_type == DUVariableType::Local && (re == DUState::LU || re == DUState::LD))) {
183  result = re;
184  break;
185  }
186  if (re == DUState::CD) {
187  result = re;
188  }
189  }
190  return result;
191 }
192 
194  unsupported_node = true;
195  node.visit_children(*this);
196  unsupported_node = false;
197 }
198 
199 /** Nothing to do if called function is not defined or it's external
200  * but if there is a function call for internal function that means
201  * there is no inlining happened. In this case we mark the call as
202  * unsupported.
203  */
205  const auto& function_name = node.get_node_name();
206  const auto& symbol = global_symtab->lookup_in_scope(function_name);
207  if (symbol == nullptr || symbol->is_external_variable()) {
208  node.visit_children(*this);
209  } else {
211  }
212 }
213 
215  const auto& symtab = node.get_symbol_table();
216  if (symtab != nullptr) {
217  current_symtab = symtab;
218  }
219 
221  node.visit_children(*this);
222  symtab_stack.pop();
224 }
225 
226 /** Nmodl grammar doesn't allow assignment operator on rhs (e.g. a = b + (b=c)
227  * and hence not necessary to keep track of assignment operator using stack.
228  */
230  // only the outermost binary expression is important
231  const bool is_outer_binary_expression = !current_binary_expression;
232  if (is_outer_binary_expression) {
233  current_binary_expression = std::static_pointer_cast<const ast::BinaryExpression>(
234  node.get_shared_ptr());
235  }
236 
237  node.get_rhs()->visit_children(*this);
238  if (node.get_op().get_value() == ast::BOP_ASSIGN) {
239  visiting_lhs = true;
240  }
241  node.get_lhs()->visit_children(*this);
242  visiting_lhs = false;
243 
244  if (is_outer_binary_expression) {
245  current_binary_expression = nullptr;
246  }
247 }
248 
250  /// store previous chain
251  auto previous_chain = current_chain;
252 
253  /// starting new if block
255  current_chain = &(previous_chain->back().children);
256 
257  /// visiting if sub-block
258  auto last_chain = current_chain;
260  node.get_condition()->accept(*this);
261  const auto& block = node.get_statement_block();
262  if (block) {
263  block->accept(*this);
264  }
265  current_chain = last_chain;
266 
267  /// visiting else if sub-blocks
268  for (const auto& item: node.get_elseifs()) {
270  }
271 
272  /// visiting else sub-block
273  if (node.get_elses()) {
274  visit_with_new_chain(*node.get_elses(), DUState::ELSE);
275  }
276 
277  /// restore to previous chain
278  current_chain = previous_chain;
279 }
280 
281 /** We are not analyzing verbatim blocks yet and hence if there is
282  * a verbatim block we assume there is variable usage.
283  *
284  * \todo One simple way would be to look for p_name in the string
285  * of verbatim block to find the variable usage.
286  */
288  if (!ignore_verbatim) {
290  }
291 }
292 
293 
294 /// unsupported statements : we aren't sure how to handle this "yet" and
295 /// hence variables used in any of the below statements are handled separately
296 
299 }
300 
303 }
304 
307 }
308 
311 }
312 
315 }
316 
318  const std::string& variable = to_nmodl(node);
319  process_variable(variable);
320 }
321 
323  const std::string& variable = to_nmodl(node);
324  process_variable(variable);
325 }
326 
328  const auto& name = node.get_node_name();
329  const auto& length = node.get_length();
330 
331  /// index should be an integer (e.g. after constant folding)
332  /// if this is not the case and then we can't determine exact
333  /// def-use chain
334  if (!length->is_integer()) {
335  /// check if variable name without index part is same
336  auto variable_name_prefix = variable_name.substr(0, variable_name.find('['));
337  if (name == variable_name_prefix) {
338  update_defuse_chain(variable_name_prefix);
339  const std::string& text = to_nmodl(node);
340  nmodl::logger->info("index used to access variable is not known : {} ", text);
341  }
342  return;
343  }
344  auto index = std::dynamic_pointer_cast<ast::Integer>(length);
345  process_variable(name, index->eval());
346 }
347 
348 /// statements / nodes that should not be used for def-use chain analysis
349 
351 
353 
355 
356 
357 /**
358  * Update the Def-Use chain for given variable
359  *
360  * @param name Name of the variable excluding index or dimension
361  *
362  * Update def-use chain if we encounter a variable that we are looking for.
363  * If we encounter non-supported construct then we mark that variable as "use"
364  * because we haven't completely analyzed the usage. Marking that variable "U"
365  * make sures that won't get optimized. Then we distinguish between local and
366  * non-local variables. All variables that appear on lhs are marked as "definitions"
367  * whereas the one on rhs are marked as "usages".
368  */
370  const auto& symbol = current_symtab->lookup_in_scope(name);
371  assert(symbol != nullptr);
372  // variable properties that make variable local
373  const auto properties = NmodlType::local_var | NmodlType::argument;
374  const auto is_local = symbol->has_any_property(properties);
375 
376  if (unsupported_node) {
378  } else if (visiting_lhs) {
379  if (is_local) {
381  } else {
383  }
384  } else {
385  if (is_local) {
387  } else {
389  }
390  }
391 }
392 
394  if (name == variable_name) {
396  }
397 }
398 
399 void DefUseAnalyzeVisitor::process_variable(const std::string& name, int index) {
400  std::ostringstream fullname;
401  fullname << name << '[' << std::to_string(index) + ']';
402  if (fullname.str() == variable_name) {
404  }
405 }
406 
408  const auto last_chain = current_chain;
409  start_new_chain(state);
410  node.visit_children(*this);
411  current_chain = last_chain;
412 }
413 
416  current_chain = &current_chain->back().children;
417 }
418 
420  /// re-initialize state
422  visiting_lhs = false;
424  unsupported_node = false;
425 
426  // variable types that we try to localise: RANGE, GLOBAL and ASSIGNED
427  auto global_symbol_properties = NmodlType::global_var | NmodlType::range_var |
428  NmodlType::assigned_definition;
429  auto global_symbol = global_symtab->lookup_in_scope(variable_name);
430  // If global_symbol exists in the global_symtab then search for a global variable. Otherwise the
431  // variable can only be local if it exists
432  if (global_symbol != nullptr && global_symbol->has_any_property(global_symbol_properties)) {
434  } else {
436  }
437 
438  /// new chain
439  DUChain usage(node.get_node_type_name(), variable_type);
440  current_chain = &usage.chain;
441 
442  /// analyze given node
444  node.visit_children(*this);
445  symtab_stack.pop();
446 
447  return usage;
448 }
449 
450 } // namespace visitor
451 } // namespace nmodl
Auto generated AST classes declaration.
Represents an argument to functions and procedures.
Definition: argument.hpp:48
Represents binary expression in the NMODL.
Represents CONDUCTANCE statement in NMODL.
Represent CONSERVE statement in NMODL.
Definition: conserve.hpp:38
Represents specific element of an array variable.
One equation in a system of equations tha collectively form a LINEAR block.
Represents a name.
Definition: name.hpp:44
Base class for all AST node.
Definition: node.hpp:40
One equation in a system of equations that collectively make a NONLINEAR block.
Represents block encapsulating list of statements.
Represents a variable.
Definition: var_name.hpp:43
Represents a C code block.
Definition: verbatim.hpp:38
Helper class for printing AST in JSON form.
void flush()
Dump json object to stream (typically at the end) nspaces is number of spaces used for indentation.
void add_node(std::string value, const std::string &key="name")
Add node to json (typically basic type)
void compact_json(bool flag)
print json in compact mode
void push_block(const std::string &value, const std::string &key="name")
Add new json object (typically start of new block) name here is type of new block encountered.
void pop_block()
We finished processing a block, add processed block to it's parent block.
std::shared_ptr< Symbol > lookup_in_scope(const std::string &name) const
check if symbol with given name exist in the current table (including all parents)
Def-Use chain for an AST node.
std::string to_string(bool compact=true) const
return json representation
std::vector< DUInstance > chain
def-use chain for a variable
DUVariableType variable_type
type of variable
DUState eval() const
return "effective" usage of a variable
std::string name
name of the node
Represent use of a variable in the statement.
DUState eval(DUVariableType variable_type) const
analyze all children and return "effective" usage
DUState state
state of the usage
DUState conditional_block_eval(DUVariableType variable_type) const
evaluate global usage i.e. with [D,U] states of children
void print(printer::JSONPrinter &printer) const
DUInstance to JSON string.
std::vector< DUInstance > children
usage of variable in case of if like statements
DUState sub_block_eval(DUVariableType variable_type) const
if, elseif and else evaluation
DUVariableType variable_type
variable type (Local or Global)
bool unsupported_node
indicate that there is unsupported construct encountered
void visit_name(const ast::Name &node) override
visit node of type ast::Name
symtab::SymbolTable * current_symtab
symbol table for current statement block (or of parent block if doesn't have) should be initialized b...
symtab::SymbolTable * global_symtab
symbol table containing global variables
void visit_indexed_name(const ast::IndexedName &node) override
visit node of type ast::IndexedName
void visit_binary_expression(const ast::BinaryExpression &node) override
Nmodl grammar doesn't allow assignment operator on rhs (e.g.
DUChain analyze(const ast::Ast &node, const std::string &name)
compute def-use chain for a variable within the node
void visit_non_lin_equation(const ast::NonLinEquation &node) override
visit node of type ast::NonLinEquation
void visit_conductance_hint(const ast::ConductanceHint &node) override
statements / nodes that should not be used for def-use chain analysis
void visit_from_statement(const ast::FromStatement &node) override
visit node of type ast::FromStatement
void visit_function_call(const ast::FunctionCall &node) override
Nothing to do if called function is not defined or it's external but if there is a function call for ...
bool visiting_lhs
starting visiting lhs of assignment statement
void visit_conserve(const ast::Conserve &node) override
visit node of type ast::Conserve
void visit_with_new_chain(const ast::Node &node, DUState state)
void visit_local_list_statement(const ast::LocalListStatement &node) override
visit node of type ast::LocalListStatement
std::vector< DUInstance > * current_chain
def-use chain currently being constructed
void visit_verbatim(const ast::Verbatim &node) override
We are not analyzing verbatim blocks yet and hence if there is a verbatim block we assume there is va...
void process_variable(const std::string &name)
void visit_reaction_statement(const ast::ReactionStatement &node) override
unsupported statements : we aren't sure how to handle this "yet" and hence variables used in any of t...
void visit_if_statement(const ast::IfStatement &node) override
visit node of type ast::IfStatement
void visit_statement_block(const ast::StatementBlock &node) override
visit node of type ast::StatementBlock
std::stack< symtab::SymbolTable * > symtab_stack
symbol tables in call hierarchy
void visit_var_name(const ast::VarName &node) override
visit node of type ast::VarName
void update_defuse_chain(const std::string &name)
Update the Def-Use chain for given variable.
void visit_unsupported_node(const ast::Node &node)
std::shared_ptr< const ast::BinaryExpression > current_binary_expression
void visit_argument(const ast::Argument &node) override
visit node of type ast::Argument
std::string variable_name
variable for which to construct def-use chain
void visit_lin_equation(const ast::LinEquation &node) override
visit node of type ast::LinEquation
Visitor to return Def-Use chain for a given variable in the block/node
#define assert(ex)
Definition: hocassrt.h:24
const char * name
Definition: init.cpp:16
NmodlType
NMODL variable properties.
std::string to_string(DUState state)
DUState to string conversion for pretty-printing.
DUState
Represent a state in Def-Use chain.
@ ELSEIF
elseif sub-block
@ CONDITIONAL_BLOCK
conditional block
@ CD
global or local variable is conditionally defined
@ U
global variable is used
@ UNKNOWN
state not known
@ ELSE
else sub-block
@ LU
local variable is used
@ LD
local variable is defined
@ NONE
variable is not used
@ D
global variable is defined
DUVariableType
Variable type processed by DefUseAnalyzeVisitor.
std::ostream & operator<<(std::ostream &os, DUState state)
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
std::string to_nmodl(const ast::Ast &node, const std::set< ast::AstNodeType > &exclude_types)
Given AST node, return the NMODL string representation.
logger_type logger
Definition: logger.cpp:34
static Node * node(Object *)
Definition: netcvode.cpp:291
short index
Definition: cabvars.h:11
#define text
Definition: plot.cpp:60
Base class for all Abstract Syntax Tree node types.
Definition: ast.hpp:52