CGRA-ME
OpGraph.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  * The software programs comprising "CGRA-ME" and the documentation provided
3  * with them are copyright by its authors and the University of Toronto. Only
4  * non-commercial, not-for-profit use of this software is permitted without ex-
5  * plicit permission. This software is provided "as is" with no warranties or
6  * guarantees of support. See the LICENCE for more details. You should have re-
7  * ceived a copy of the full licence along with this software. If not, see
8  * <http://cgra-me.ece.utoronto.ca/license/>.
9  ******************************************************************************/
10 #include <CGRA/OpGraph.h>
11 
12 #include <CGRA/Exception.h>
13 #include <CGRA/GraphAlgorithms.h>
15 #include <CGRA/MRRGProcedures.h>
16 #include <CGRA/dotparse.h>
17 
18 #include <iostream>
19 #include <sstream>
20 #include <cmath>
21 #include <algorithm>
22 #include <limits>
23 #include <fstream>
24 
25 
26 namespace {
27 
32 const auto opcode_names = []{ // immediately invoked
33  std::vector<std::pair<std::string, OpGraphOpCode>> result {
34  {"nop", OpCode::NOP},
35  {"input", OpCode::INPUT},
36  {"input_pred", OpCode::INPUT_PRED},
37  {"output_pred", OpCode::OUTPUT_PRED},
38  {"output", OpCode::OUTPUT},
39  {"const", OpCode::CONST},
40  {"trunc", OpCode::TRUNC},
41  {"sext", OpCode::SEXT},
42  {"zext", OpCode::ZEXT},
43  {"phi", OpCode::PHI},
44  {"add", OpCode::ADD},
45  {"sub", OpCode::SUB},
46  {"mul", OpCode::MUL},
47  {"mult", OpCode::MUL},
48  {"div", OpCode::DIV},
49  {"and", OpCode::AND},
50  {"or", OpCode::OR},
51  {"xor", OpCode::XOR},
52  {"shl", OpCode::SHL},
53  {"ashr", OpCode::ASHR},
54  {"lshr", OpCode::LSHR},
55  {"gep", OpCode::GEP},
56  {"icmp", OpCode::ICMP},
57  {"load", OpCode::LOAD},
58  {"store", OpCode::STORE},
59  {"br", OpCode::BR},
60  {"sqrt", OpCode::SQRT},
61  {"fadd", OpCode::FADD},
62  {"fmul", OpCode::FMUL},
63  {"fdiv", OpCode::FDIV},
64  {"fp2int", OpCode::FP2INT},
65  {"mulu_full_lo", OpCode::MULU_FULL_LO},
66  {"mulu_half_lo", OpCode::MULU_HALF_LO},
67  {"mulu_quart_lo", OpCode::MULU_QUART_LO},
68  {"mulu_full_hi", OpCode::MULU_FULL_HI},
69  {"mulu_half_hi", OpCode::MULU_HALF_HI},
70  {"mulu_quart_hi", OpCode::MULU_QUART_HI},
71  {"muls_full_lo", OpCode::MULS_FULL_LO},
72  {"muls_half_lo", OpCode::MULS_HALF_LO},
73  {"muls_quart_lo", OpCode::MULS_QUART_LO},
74  {"muls_full_hi", OpCode::MULS_FULL_HI},
75  {"muls_half_hi", OpCode::MULS_HALF_HI},
76  {"muls_quart_hi", OpCode::MULS_QUART_HI},
77  {"add_full", OpCode::ADD_FULL},
78  {"add_half", OpCode::ADD_HALF},
79  {"add_quart", OpCode::ADD_QUART},
80  {"select", OpCode::SELECT},
81  {"int2fp", OpCode::INT2FP},
82  };
83  std::sort(result.begin(), result.end());
84  return result;
85 }();
86 
90 const auto opcode_to_name = []{ // immediately invoked
91  std::map<OpGraphOpCode, std::string> result;
92  for (const auto& name_and_code : opcode_names) {
93  result.emplace(name_and_code.second, name_and_code.first);
94  }
95  return result;
96 }();
97 
98 } // end anon namespace
99 
100 std::ostream& operator<<(std::ostream &os, const OpGraphOpCode &opcode) {
101  const auto lookup_result = opcode_to_name.find(opcode);
102  if (lookup_result != opcode_to_name.end()) {
103  return os << lookup_result->second;
104  }
105 
106  throw make_from_stream<cgrame_error>([&,fname=__func__](auto&& s) {
107  s << fname << "can't print opcode with value " << +opcode;
108  });
109 }
110 
111 const std::string& to_string(const OpGraphOpCode& opcode) { return opcode_to_name.at(opcode); }
112 
113 OpGraphOpCode opcode_from_string(const std::string& str) {
114  const auto name_comp_less = [](auto&& lhs_elem, auto& rhs_val) {
115  const auto comp = [](auto&& lhs, auto& rhs) { return std::tolower(lhs) < std::tolower(rhs); };
116  auto first1 = lhs_elem.first.begin();
117  auto first2 = rhs_val.begin();
118  const auto last1 = lhs_elem.first.end();
119  const auto last2 = rhs_val.end();
120 
121  // same as C++20's lexicographical compare, from https://en.cppreference.com/w/cpp/algorithm/lexicographical_compare
122  for ( ; (first1 != last1) && (first2 != last2); ++first1, (void) ++first2 ) {
123  if (comp(*first1, *first2)) return true;
124  if (comp(*first2, *first1)) return false;
125  }
126  return (first1 == last1) && (first2 != last2);
127  };
128 
129  const auto search_result = std::lower_bound(opcode_names.begin(), opcode_names.end(), str, name_comp_less);
130 
131  if (search_result == opcode_names.end() || name_comp_less(std::make_pair(str,0), search_result->first)) {
132  throw make_from_stream<std::logic_error>([&,fname=__func__](auto&& s) {
133  s << fname << " can't convert '" << str << "' into an opcode";
134  });
135  } else {
136  return search_result->second;
137  }
138 }
139 
140 std::istream& operator>>(std::istream &is, OpGraphOpCode &opcode) {
141  std::string str;
142  if (is >> str) {
143  opcode = opcode_from_string(str);
144  }
145  return is;
146 }
147 
149 {
150 }
151 
152 
153 
154 OpGraphOp::OpGraphOp(std::string name, int bitwidth, OpGraphOpCode code)
155  : OpGraphNode(name)
156  , bitwidth(bitwidth)
157  , opcode(code)
158  , input()
159  , output(nullptr) {
160  if (code == OpCode::CONST) {
161  make_and_throw<std::logic_error>([&](auto& s) {
162  s << "Trying to create a op graph const without initializing a value";
163  });
164  }
165  if (code == OpCode::CMP) {
166  make_and_throw<std::logic_error>([&](auto& s) {
167  s << "Trying to create a op graph cmp without initializing a mode";
168  });
169  }
170 }
171 
172 OpGraphOp::OpGraphOp(std::string _name, int _bitwidth, OpGraphOpCode _code, BitConfig* _bitConfig)
173  : OpGraphNode(_name)
174  , bitwidth(_bitwidth)
175  , opcode(_code)
176  , bitConfig(_bitConfig)
177  , input()
178  , output(nullptr) {
179  if (_code == OpCode::CONST) {
180  make_and_throw<std::logic_error>([&](auto& s) {
181  s << "Trying to create a op graph const without initializing a value";
182  });
183  }
184  if (_code == OpCode::CMP) {
185  make_and_throw<std::logic_error>([&](auto& s) {
186  s << "Trying to create a op graph cmp without initializing a mode";
187  });
188  }
189 }
190 
191 OpGraphOp::OpGraphOp(std::string name, int bitwidth, OpGraphOpCode code, std::int64_t value)
192  : OpGraphNode(name)
193  , bitwidth(bitwidth)
194  , opcode(code)
195  , input()
196  , output(nullptr)
197  , constVal(value) {
198  if (code != OpCode::CONST) {
199  make_and_throw<std::logic_error>([&](auto& s) {
200  s << "Trying to assign a value to an op that is not const";
201  });
202  }
203 }
204 
205 OpGraphOp::OpGraphOp(std::string name, int bitwidth, OpGraphOpCode code, std::string value, bool cmp)
206  : OpGraphNode(name)
207  , bitwidth(bitwidth)
208  , opcode(code)
209  , input()
210  , output(nullptr) {
211  if (cmp) {
212  cmpMode = value;
213  } else {
214  memName = value;
215  }
216  if (code != OpCode::CMP && code != OpCode::LOAD && code != OpCode::STORE) {
217  make_and_throw<std::logic_error>([&](auto& s) {
218  s << "Trying to assign a value to an op that is not cmp or memory access";
219  });
220  }
221 }
222 
223 OpGraphOp OpGraphOp::propertyClone(std::string new_name) const {
224  if (opcode == OpCode::CONST) {
225  return {std::move(new_name), bitwidth, opcode, constVal};
226  } else if (opcode == OpCode::CMP) {
227  return {std::move(new_name), bitwidth, opcode, cmpMode, true};
228  } else if (opcode == OpCode::LOAD || opcode == OpCode::STORE) {
229  return {std::move(new_name), bitwidth, opcode, memName, false};
230  } else {
231  return {std::move(new_name), bitwidth, opcode};
232  }
233 }
234 
235 bool OpGraphOp::operator==(const OpGraphOp& rhs) const {
236  if (*(const OpGraphNode*)this != rhs) { return false; }
237  if (this->opcode != rhs.opcode) { return false; }
238  if (this->bitwidth != rhs.bitwidth) { return false; }
239  if (opcode == OpCode::CONST) {
240  return this->constVal == rhs.constVal;
241  } else if (opcode == OpCode::CMP) {
242  return this->cmpMode.compare(rhs.cmpMode) == 0;
243  } else if (opcode == OpCode::LOAD || opcode == OpCode::STORE) {
244  return this->memName.compare(rhs.memName) == 0;
245  } else {
246  return true;
247  }
248 }
249 
251 
252 OpGraphVal::OpGraphVal(std::string name, int bitwidth, int dist, EdgeKind ek)
253  : OpGraphNode(name)
254  , bitwidth(bitwidth)
255  , input(nullptr)
256  , output()
257  , output_operand()
258  , dist(dist)
259  , kind(ek) { }
260 
261 OpGraphVal::OpGraphVal(std::string name, int bitwidth, OpGraphOp* input_op, int dist, EdgeKind ek)
262  : OpGraphVal(name, bitwidth, dist, ek) {
263  this->input = input_op;
264  if (ek == EdgeKind::kDataFlow)
265  input_op->output = this;
266 }
267 
269 
271  const auto ival_output_pos = std::find(output.begin(), output.end(), op);
272  const auto dist = std::distance(output.begin(), ival_output_pos);
273  return output_operand[dist];
274 }
275 
277  const auto ival_output_pos = std::find(output.begin(), output.end(), op);
278  const auto dist = std::distance(output.begin(), ival_output_pos);
279  return output_predicate[dist];
280 }
281 
283  : op_nodes{}
284  , val_nodes{}
285  , ops_by_name{}
286  , vals_by_name{}
287 {
288 }
289 
290 const std::vector<OpGraphOp*> OpGraph::empty_op_vector = {};
291 const std::vector<OpGraphVal*> OpGraph::empty_val_vector = {};
292 const std::vector<OpGraph::EdgeDescriptor> OpGraph::empty_edge_vector = {};
293 
295 
297  for (auto& op : op_nodes) {
298  delete op;
299  }
300  for (auto& val : val_nodes) {
301  delete val;
302  }
303  op_nodes.clear();
304  val_nodes.clear();
305  alias_nodes.clear();
306  ops_by_name.clear();
307  vals_by_name.clear();
308 }
309 
311  : op_nodes{}
312  , val_nodes{}
313  , alias_nodes {}
314  , ops_by_name{}
315  , vals_by_name{} {
316  *this = src;
317 }
318 
320  clear();
321  std::map<OpDescriptor, OpDescriptor> src_to_this;
322  for (const auto& src_op : src.opNodes()) {
323  src_to_this.insert({src_op, insert(src.getNodeRef(src_op).propertyClone())});
324  }
325  for (const auto& src_op : src.opNodes()) {
326  const auto src_val = src.outputVal(src_op);
327  int output_index = -1;
328  for (const auto& sink_op : src.outputOps(src_val)) {
329  output_index += 1;
330  link(src_to_this.at(src_op), src_to_this.at(sink_op),
331  src.getNodeRef(src_val).output_operand.at(output_index),
332  src.getNodeRef(src_val).bitwidth, src.getNodeRef(src_val).dist, src.getNodeRef(src_val).kind);
333  }
334  }
335  return *this;
336 }
337 
339  OpGraphOp* op = new OpGraphOp(std::move(op_to_move_in));
340 
341  if (!ops_by_name.emplace(op->name, op).second) {
342  make_and_throw<std::logic_error>([&](auto& s) {
343  s << "Ops should have unique names. An op with the name " << op->getName() << " already exists";
344  });
345  }
346 
347  op_nodes.push_back(op);
348 
349  return op;
350 }
351 
353  OpGraphOp fanout_to_move_in,
354  std::string operand_group,
355  int bitwidth,
356  int dist,
357  EdgeKind kind) {
358  auto fanout_op = insert(std::move(fanout_to_move_in));
359  auto val = link(driver, fanout_op, operand_group, bitwidth, dist, kind);
360 
361  return { val, fanout_op };
362 }
363 
365  OpDescriptor fanout,
366  std::string operand_group,
367  int bitwidth,
368  int dist,
369  EdgeKind kind,
370  bool predicate) {
371  // create val if it doesn't exist
373  std::string val_name = getNodeRef(driver).name + "_out" + (kind == EdgeKind::kAlias ? "_alias" : "");
374  if (vals_by_name.find(val_name) != vals_by_name.end()) {
375  val = vals_by_name[val_name];
376  } else if (!getNodeRef(driver).output || kind == EdgeKind::kAlias) {
377  val = new OpGraphVal(
378  val_name,
379  bitwidth,
380  &getNodeRef(driver),
381  dist,
382  kind);
383 
384  if (!vals_by_name.emplace(val->name, val).second) {
385  make_and_throw<std::logic_error>([&](auto& s) {
386  s << "Vals should have unique names. A val with the name " << val->getName() << " already exists";
387  });
388  }
389  if (kind == EdgeKind::kAlias) {
390  alias_nodes.push_back(&getNodeRef(val));
391  } else {
392  val_nodes.push_back(&getNodeRef(val));
393  }
394  } else {
395  val = outputVal(driver);
396  }
397  auto& driver_val_ref = getNodeRef(val);
398  driver_val_ref.output.emplace_back(&getNodeRef(fanout));
399  driver_val_ref.output_operand.emplace_back(operand_group);
400  driver_val_ref.output_predicate.emplace_back(predicate);
401 
402  if (kind != EdgeKind::kAlias)
403  getNodeRef(fanout).input.emplace_back(&getNodeRef(val));
404 
405  return val;
406 }
407 
408 OpGraph::ValDescriptor OpGraph::link(ValDescriptor val, OpDescriptor fanout, std::string operand_group) {
409  // setup the link
410  auto& driver_val_ref = getNodeRef(val);
411  driver_val_ref.output.emplace_back(&getNodeRef(fanout));
412  driver_val_ref.output_operand.emplace_back(operand_group);
413  getNodeRef(fanout).input.emplace_back(&getNodeRef(val));
414 
415  return val;
416 }
417 
419  return link(driver, fanout, getOperandTag(base), getBitwidth(base), getDist(base), getKind(base));
420 }
421 
422 void OpGraph::unLink(ValDescriptor driver_val, OpDescriptor fanout) {
423  auto& ival = getNodeRef(driver_val);
424  auto& oval = getNodeRef(fanout);
425  const auto ival_output_pos = std::find(ival.output.begin(), ival.output.end(), fanout);
426  if (ival_output_pos == ival.output.end()) {
427  throw make_from_stream<std::logic_error>([&](auto&& s) {
428  s << "trying to unlink " << driver_val << " -> "
429  << fanout << " but the latter op is not a fanout of the former val";
430  });
431  }
432 
433  // remove the connecting val from fanout's input list
434  auto& fo_ins = oval.input;
435  const auto oval_input_pos = std::find(fo_ins.begin(), fo_ins.end(), driver_val);
436 
437  fo_ins.erase(oval_input_pos);
438 
439  // remove fanout from the connecting val
440  const auto dist = std::distance(ival.output.begin(), ival_output_pos);
441  ival.output.erase(ival_output_pos);
442  ival.output_operand.erase(std::next(ival.output_operand.begin(), dist));
443 
444  if (ival.output.empty()) {
445  defunct_nodes.emplace_back(&ival);
446  const auto driver = ival.input;
447  getNodeRef(driver).output = nullptr;
448  vals_by_name.erase(ival.getName());
449  val_nodes.erase(std::find(val_nodes.begin(), val_nodes.end(), driver_val));
450  }
451 }
452 
454  const auto pos_in_op_nodes = std::find(op_nodes.begin(), op_nodes.end(), op);
455  if (pos_in_op_nodes == op_nodes.end()) {
456  throw make_from_stream<std::logic_error>([&](auto&& s) {
457  s << "trying to erase op " << op << " which is not part of the OpGraph " << this;
458  });
459  }
460 
461  const auto input_vals_copy = inputVals(op); // this list will be modified in unLink
462  for (auto& ival : input_vals_copy) {
463  unLink(ival, op);
464  }
465 
466  const auto output_ops_copy = outputOps(op); // this list will be modified in unLink
467  for (auto& oop : output_ops_copy) {
468  unLink(outputVal(op), oop);
469  }
470 
471  op_nodes.erase(pos_in_op_nodes);
472  ops_by_name.erase(getNodeRef(op).getName());
473  defunct_nodes.emplace_back(&getNodeRef(op));
474 }
475 
477  if (not op_desc) { return null_val; }
478  auto& op = getNodeRef(op_desc);
479  if (op.output) { return op.output; }
480  else { return null_val; }
481 }
482 
483 std::vector<OpGraph::ValDescriptor> OpGraph::outputVals(OpDescriptor op_desc) const {
484  if (not op_desc) { return {}; }
485  return { outputVal(op_desc) };
486 }
487 
488 const std::vector<OpGraphOp*>& OpGraph::outputOps(ValDescriptor val) const {
489  if (not val) { return empty_op_vector; }
490  return getNodeRef(val).output;
491 }
492 
493 const std::vector<OpGraphOp*>& OpGraph::outputOps(OpDescriptor op) const {
494  return outputOps(outputVal(op));
495 }
496 
497 auto OpGraph::inputOps(OpDescriptor op) const -> std::vector<OpDescriptor> {
498  std::vector<OpDescriptor> result;
499  const auto& ivals = inputVals(op);
500  std::transform(ivals.begin(), ivals.end(), std::back_inserter(result), [this](const auto& ival) { return this->inputOp(ival); });
501  return result;
502 }
503 
505  if (not val) { return null_op; }
506  return getNodeRef(val).input;
507 }
508 
510  return getNodeRef(edge.val).output_operand.at(edge.output_index);
511 }
512 
513 const int OpGraph::getDist(EdgeDescriptor edge) const {
514  return getNodeRef(edge.val).dist;
515 }
516 
517 const int OpGraph::getBitwidth(EdgeDescriptor edge) const {
518  return getNodeRef(edge.val).bitwidth;
519 }
520 
522  return getNodeRef(edge.val).kind;
523 }
524 
526  if (not ed) { return null_op; }
527  return getNodeRef(ed.val).output.at(ed.output_index);
528 }
529 
530 auto OpGraph::outEdges(const OpDescriptor& op) const -> std::vector<EdgeDescriptor> {
531  std::vector<EdgeDescriptor> result;
532  for (const auto& out_val : outputVals(op)) {
533  for (int i = 0; i < (std::ptrdiff_t)outputOps(out_val).size(); ++i) {
534  result.push_back({out_val, i});
535  }
536  }
537  return result;
538 }
539 
540 auto OpGraph::inEdges(const OpDescriptor& op) const -> std::vector<EdgeDescriptor> {
541  std::vector<EdgeDescriptor> result;
542  for (const auto& target_ival : inputVals(op)) {
543  for (const auto& edge_to_target : outEdges(inputOp(target_ival))) {
544  if (targetOfEdge(edge_to_target) != op) { continue; }
545  result.push_back(edge_to_target);
546  }
547  }
548  return result;
549 }
550 
551 const std::vector<OpGraphVal*>& OpGraph::inputVals(OpDescriptor op_desc) const {
552  if (!op_desc) {
553  return empty_val_vector;
554  }
555  auto& op = getNodeRef(op_desc);
556  return op.input;
557 }
558 
559 std::ostream& operator<<(std::ostream& output, const OpGraphOp& op)
560 {
561  output << op.name << "(" << op.opcode <<")";
562  return output; // for multiple << operators.
563 }
564 
565 std::ostream& operator<<(std::ostream& output, const OpGraphVal& val)
566 {
567  output << val.name;
568  return output; // for multiple << operators.
569 }
570 
571 //Change opnode with select to be scheduled first
573 {
574  OpSchedule schedule;
575  // create a copy of the list of all op nodes
576  auto V = op_graph.opNodes();
577 
578  unsigned int num_scheduled = 0;
579  // initialize all nodes
580  for (auto& v : V)
581  {
582  if(v->input.size() == 0)
583  {
584  schedule.cycle[v] = 0;
585  v = nullptr; // invalidate from list
586  num_scheduled++;
587  }
588  else
589  {
590  schedule.cycle[v] = -1;
591  }
592  }
593 
594  int max_latency = 0;
595 
596  while(num_scheduled < V.size())
597  {
598  for (auto& v : V)
599  {
600  int max = 0;
601  bool pred_sched = v;
602  for(int i = 0; v && i < (std::ptrdiff_t)v->input.size(); i++) // for all node inputs
603  {
604  OpGraphVal* in = v->input[i];
605  std::cout << "Path latency (" << *(in->input) << ", " << *v << "): " << /* output_latency.at(in).at(i) << */ "\n";
606  max = std::max(max, schedule.cycle.at(in->input) /* + op_latency.at(in->input) + output_latency.at(in).at(i) */); // TODO: verify: latency needs to be the path between predecessor and current node
607  if(schedule.cycle.at(in->input) == -1)
608  {
609  pred_sched = false;
610  break;
611  }
612  }
613 
614  if(pred_sched) // all predecessors are scheduled
615  {
616  schedule.cycle.at(v) = max; // TODO: should I factor in current op latency here?
617  v = nullptr; // invalidate from list
618  num_scheduled++;
619  max_latency = std::max(max, max_latency);
620  }
621  }
622  }
623 
624  schedule.latency = max_latency;
625 
626  return schedule;
627 }
628 
629 OpSchedule computeALAP(const OpGraph& op_graph, unsigned int max_cycles)
630 {
631  OpSchedule schedule;
632  // create a copy of the list of all op nodes
633  auto V = op_graph.opNodes();
634 
635  unsigned int num_scheduled = 0;
636 
637  // initialize all nodes
638  for (auto& v : V)
639  {
640  if(v->output == nullptr) // TODO: this is probably wrong - this needs to be finding all the output nodes
641  {
642  schedule.cycle[v] = max_cycles;
643  v = nullptr; // invalidate from list
644  num_scheduled++;
645  }
646  else
647  {
648  schedule.cycle[v] = -1;
649  }
650  }
651 
652  while(num_scheduled < V.size())
653  {
654  for (auto& v : V)
655  {
656  int min = 0;
657  bool succ_sched = v;
658  for(int i = 0; v && i < (std::ptrdiff_t)v->output->output.size(); i++) // for all op outputs
659  {
660  OpGraphVal* out = v->output;
661  OpGraphOp* succ = out->output[i];
662 
663  std::cout << "Path latency (" << *v << ", " << *succ << "): " << /* output_latency.at(out).at(i) << */ "\n";
664  min = std::min(min, schedule.cycle.at(succ) /* - op_latency.at(v) + output_latency.at(out).at(i) */ );
665 
666  if(schedule.cycle.at(succ) == -1)
667  {
668  succ_sched = false;
669  break;
670  }
671  }
672 
673  if(succ_sched) // all sucessors are scheduled
674  {
675  schedule.cycle.at(v) = min; // TODO: should I factor in current op latency here?
676  v = nullptr; // invalidate from list
677  num_scheduled++;
678  }
679  }
680  }
681  return schedule;
682 }
683 
684 auto OpGraph::verify() const -> std::vector<VerifyMessage>
685 {
686  std::vector<VerifyMessage> result;
687  const auto& add_result = [&](VerifyMessage::Type type, auto&& f) {
688  result.emplace_back(VerifyMessage{ type, string_from_stream(std::forward<decltype(f)>(f)) });
689  };
690  const auto& add_error = [&](auto&& f) { add_result(VerifyMessage::Type::Error, std::forward<decltype(f)>(f)); };
691  const auto& add_warning = [&](auto&& f) { add_result(VerifyMessage::Type::Warning, std::forward<decltype(f)>(f)); };
692  const auto& add_info = [&](auto&& f) { add_result(VerifyMessage::Type::Info, std::forward<decltype(f)>(f)); };
693  (void)add_warning; (void)add_info; // Remove this line when these are used (it silences a warning)
694 
695  //Check for null pointer
696  for(auto op : this->opNodes()) {
697  if (not op) {
698  add_error([&](auto&& s) { s << "found null op\n"; });
699  }
700  }
701  for(auto& val : this->valNodes()){
702  if (not val) {
703  add_error([&](auto&& s) { s << "found null val\n"; });
704  }
705  const auto expected_size = val->output.size();
706  if (val->output_operand.size() != expected_size) {
707  add_error([&](auto&& s) { s << "operand index list size does not match number of outputs\n"; });
708  }
709  }
710 
711  // Check to make sure that all double link is set-up correctly from the input side
712  for(auto temp_val : this->valNodes())
713  {
714  if (not temp_val) { continue; }
715 
716  auto& output = temp_val->output;
717  for(auto temp_op : output)
718  {
719  if (not temp_op) { continue; }
720 
721  auto temp_it = std::find(temp_op->input.begin(), temp_op->input.end(), temp_val);
722  if (temp_it == temp_op->input.end()) {
723  add_error([&](auto&& s) { s << "expected to find " << *temp_val << " in input list of " << *temp_op; });
724  }
725  }
726  }
727  // Check to make sure that all double link is set-up correctly from the output side
728  for(auto temp_op : this->opNodes())
729  {
730  if (not temp_op) { continue; }
731 
732  auto& input = temp_op->input;
733  for(auto temp_val : input)
734  {
735  if (not temp_val) { continue; }
736 
737  auto temp_it = std::find(temp_val->output.begin(), temp_val->output.end(), temp_op);
738  if (temp_it == temp_val->output.end()) {
739  add_error([&](auto&& s) { s << "expected to find " << *temp_op << " in output list of " << *temp_val; });
740  }
741  }
742  }
743 
744  return result;
745 }
746 
748  auto it = find(op_nodes.begin(), op_nodes.end(), op);
749  if (it != op_nodes.end()) {
750  return it - op_nodes.begin();
751  } else {
752  throw cgrame_error ("Operation not found within the opgraph");
753  }
754 }
755 
757  auto it = find(val_nodes.begin(), val_nodes.end(), val);
758  if (it != val_nodes.end()) {
759  return it - val_nodes.begin();
760  } else {
761  throw cgrame_error ("Value not found within the opgraph " + val->getName());
762  }
763 }
764 
766  auto it = find(alias_nodes.begin(), alias_nodes.end(), val);
767  if (it != alias_nodes.end()) {
768  return it - alias_nodes.begin();
769  } else {
770  throw cgrame_error ("Alias not found within the opgraph " + val->getName());
771  }
772 }
773 
774 const OpGraphOp* OpGraph::getOpByIndex (int index) const {
775  if (index >= op_nodes.size()) {
776  throw cgrame_error ("Index is larger than the number of operations");
777  } else {
778  return op_nodes[index];
779  }
780 }
781 
782 const OpGraphVal* OpGraph::getValByIndex (int index) const {
783  if (index >= val_nodes.size()) {
784  throw cgrame_error ("Index is larger than the number of operations");
785  } else {
786  return val_nodes[index];
787  }
788 }
789 
790 bool verifyAndPrintReport(const OpGraph& opgraph, std::ostream& os, bool silent_on_no_errors, bool throw_if_errors) {
791  const auto verify_output = opgraph.verify();
792  const auto found_errors = analyzeOpgraphVerifyResults(os, verify_output, silent_on_no_errors);
793  if (throw_if_errors && found_errors) {
794  make_and_throw<cgrame_error>([&](auto&& s) { s << "OpGraph verify check failed! check stdout for results"; });
795  } else {
796  return not found_errors;
797  }
798 }
799 
800 bool analyzeOpgraphVerifyResults(std::ostream& os, const std::vector<OpGraph::VerifyMessage>& messages, const bool silent_on_no_errors) {
801  using Type = OpGraph::VerifyMessage::Type;
802 
803  const auto contains_error = std::any_of(begin(messages), end(messages), [](auto&& msg) { return msg.type == Type::Error; });
804  const auto has_messages = !messages.empty();
805  const auto print_all_messages = contains_error || not silent_on_no_errors;
806 
807  if (print_all_messages) {
808  if (contains_error) {
809  os << "OpGraph verify FAILED";
810  } else {
811  os << "OpGraph verify passed";
812  }
813 
814  if (has_messages) {
815  os << ". Begin report:\n";
816 
817  for (auto& msg : messages) {
818  os << msg.type << ": " << msg.message << '\n';
819  }
820 
821  os << "End OpGraph verify report\n";
822  } else {
823  os << ", and nothing to report.\n";
824  }
825 
826  }
827 
828  return contains_error;
829 }
830 
831 // This function uses a DFS to find the largest cycle in the graph starting with the input nodes
832 // It assumes the graph is not disjoint.
833 // If the graph is acyclic, it returns 0.
834 static void dfs_visit(int time , int & longest_cycle, std::map<OpGraphOp*, int> & dfs_colour, std::map<OpGraphOp*, int> & dfs_timestamp, OpGraphOp* op)
835 {
836  // colour grey
837  dfs_colour[op] = 1;
838 
839  dfs_timestamp[op] = time;
840  time++;
841 
842  //if leaf node, we are done
843  if(op->opcode == OpCode::OUTPUT || op->opcode == OpCode::STORE)
844  {
845  dfs_colour[op] = 2;
846  return;
847  }
848 
849  for(auto & n : op->output->output)
850  {
851  if(dfs_colour[n] == 0)
852  {
853  dfs_visit(time, longest_cycle, dfs_colour, dfs_timestamp, n);
854  }
855  else if(dfs_colour[n] == 1)
856  {
857  int size = dfs_timestamp[op] - dfs_timestamp[n] + 1;
858  //cout << "found cycle at " << *op << " to " << *n << " size: " << size << "\n";
859  if(size > longest_cycle)
860  longest_cycle = size;
861  }
862  }
863  dfs_colour[op] = 2;
864  return;
865 }
866 
868 {
869  int result = 0;
870 
871  std::map<OpGraphOp*, int> dfs_colour;
872  std::map<OpGraphOp*, int> dfs_timestamp;
873 
874  // Following Corman et al. page540, 2nd ed.
875  // 0 - white
876  // 1 - grey
877  // 2 - black
878  for(auto & n : op_nodes)
879  dfs_colour[n] = 0;
880 
881  // Loop through all op nodes
882  for(auto & op : op_nodes)
883  {
884  if(dfs_colour[op] == 0)
885  {
886  //cout << "Visiting input: " << *input_op << "\n";
887  dfs_visit(0, result, dfs_colour, dfs_timestamp, op);
888  }
889  }
890  return result;
891 }
892 
893 namespace {
894  OpGraph::Walk reverse_and_canonicalize(OpGraph::Walk&& cyclic_trail, const OpGraph& opgraph) {
895  std::reverse(cyclic_trail.begin(), cyclic_trail.end());
896  auto new_begin = std::min_element(cyclic_trail.begin(), cyclic_trail.end(), [&](const auto& lhs, const auto& rhs) {
897  return opgraph.getNodeRef(lhs.val).getName() < opgraph.getNodeRef(rhs.val).getName();
898  });
899  std::rotate(cyclic_trail.begin(), new_begin, cyclic_trail.end());
900  return cyclic_trail;
901  }
902 }
903 
904 // code is based off of computeTrailsToBalance
905 // TODO: annotate this info directly on the graph, and remove this code
906 auto OpGraph::edgeLatencies() const -> std::map<EdgeDescriptor,int> {
907  // return value. initialize to all zeros
908  std::map<EdgeDescriptor,int> result;
909  for (const auto& op : this->opNodes()) {
910  for (const auto& e : this->outEdges(op)) {
911  result[e] = 0;
912  }
913  }
914 
915  std::set<std::vector<EdgeDescriptor>> cycles_found;
916 
917  // ops that have been visited by previous iterations
918  std::set<OpDescriptor> ops_visited;
919 
920  // non-tree and re-convergence edges that have been visited by previous iterations
921  // note: re-convergence edges are symmetric with non-tree ones, so we treat them the same here
922  std::set<EdgeDescriptor> nte_and_convergence_edges_found;
923 
924  // use (ordered) sets -- no hash defined
925  // also, we'll be treating hyper-edge output pins as vertices; as-if it were a multi-graph
926  const auto galgos = GraphAlgorithms<EdgeDescriptor,
928  >{};
929 
930  for (OpDescriptor op : this->opNodes()) {
931  if (ops_visited.find(op) != ops_visited.end()) { continue; }
932 
933  // start as far up as possible, to make for fewer iterations.
934  // note: for an unvisited Op, all Ops reachable by fanin edges must have also not been visited.
935  {std::set<OpDescriptor> ancestors;
936  while (true) {
937  const auto& fanin = this->inputVals(op);
938  if (fanin.empty() || not ancestors.insert(op).second) { break; }
939  op = this->inputOp(*fanin.begin());
940  }}
941 
942  struct FindOpNonTreeEdgesVisitor : DoNothingGraphVisitor<EdgeDescriptor> {
943  const OpGraph* opgraph = nullptr;
944  std::set<OpDescriptor> examined_op_targets = {};
945  std::set<EdgeDescriptor> non_tree_edges = {};
946 
947  void onExamine(const EdgeDescriptor& e) {
948  // if this hyper-edge's target was already visited, save it
949  if (not examined_op_targets.insert(opgraph->targetOfEdge(e)).second) {
950  non_tree_edges.insert(e);
951  }
952  }
953  } visitor;
954  visitor.opgraph = this;
955  visitor.examined_op_targets.insert(op); // handle edges that loop around to op
956 
957  // a search with with the hyper-edge output pins as vertices
958  // returns a fanin graph representing the search, where fanin.front() defines a spanning tree
959  auto search_tree = galgos.wavedBreadthFirstVisit(*this, this->outEdges(op), visitAllVertices(), visitor);
960 
961  // each non-tree edge represents one constraint.
962  // examine the spanning tree around the target and driver to determine type and members
963  for (const auto nte : visitor.non_tree_edges) {
964  // only one constraint per nte or convergence edge!
965  if (not nte_and_convergence_edges_found.insert(nte).second) { continue; }
966 
967  // if there is any tree or non-tree edge that is driven by the target of nte, then it's a cycle
968  EdgeDescriptor cycle_edge;
969  const auto reverse_search_tree = galgos.wavedBreadthFirstVisit(
970  makeReversedGraphFromFaninLists<EdgeDescriptor>(&search_tree), {nte},
971  // input_is(this->targetOfEdge(nte))
972  [&](auto& e) {
973  const bool found = this->targetOfEdge(nte) == this->inputOp(e.val);
974  if (found) { cycle_edge = e; }
975  return found;
976  }
977  );
978  if (cycle_edge) {
979  auto traceback = galgos.singleTraceback(singleItemSet(nte), cycle_edge, reverse_search_tree);
980  if (not traceback.success) { throw make_from_stream<cgrame_error>([fname=__func__](auto&& s) {
981  s << fname << " couldn't find original non-tree edge when searching a reverse search tree";
982  });}
983  if (nte == cycle_edge) {
984  traceback.path.pop_back(); // remove duplicate
985  }
986  // is it a new cycle?
987  if (cycles_found.insert(reverse_and_canonicalize(std::move(traceback.path), *this)).second) {
988  result[nte] = 1;
989  }
990  }
991  }
992 
993  // synchronize the global visited set with the one for this iteration
994  std::copy(visitor.examined_op_targets.begin(), visitor.examined_op_targets.end(), std::inserter(ops_visited, ops_visited.end()));
995  }
996 
997  return result;
998 }
999 
1000 void OpGraph::printDOTwithOps(std::ostream &s) const
1001 {
1002  using std::to_string;
1003  unsigned int counter = 0;
1004  s << "digraph G {\n";
1005 
1006  // print op_nodes
1007  // op_node map
1008  std::map<OpGraphOp*, std::string> opnode_map;
1009  counter = 0;
1010  for(auto it = this->opNodes().begin(); it != this->opNodes().end(); it++)
1011  {
1012  opnode_map[(*it)] = to_string((*it)->opcode) + to_string(counter++);
1013  s << opnode_map[(*it)] << "[opcode=" << (*it)->opcode;
1014  if ((*it)->opcode == OpCode::CONST) {
1015  s << ", constVal=" << (*it)->constVal;
1016  } else if ((*it)->opcode == OpCode::CMP) {
1017  s << ", cmpMode=" << (*it)->cmpMode;
1018  } else if ((*it)->opcode == OpCode::LOAD || (*it)->opcode == OpCode::STORE){
1019  s << ", memName=" << (*it)->memName;
1020  }
1021 
1022  s << "];\n";
1023  }
1024 
1025  // use val nodes to create all edges
1026  for(auto it = this->valNodes().begin(); it != this->valNodes().end(); it++)
1027  {
1028  std::string inputnode = opnode_map[(*it)->input];
1029  for(unsigned int o = 0; o < (*it)->output.size(); o++)
1030  {
1031  OpGraphOp* op = (*it)->output.at(o);
1032  std::string operand = (*it)->output_operand.at(o);
1033  s << inputnode << "->" << opnode_map[op] << "[operand=" << operand << "]; ";
1034  s << "//" << (*it)->input->name << "->" << op->name << "\n";
1035  }
1036  }
1037  s << "}\n";
1038 }
1039 
1040 void OpGraph::print_dot(std::ostream &s) const
1041 {
1042  unsigned int counter = 0;
1043  s << "digraph G {\n";
1044 
1045  // print op_nodes
1046  // op_node map
1047  std::map<OpGraphOp*, std::string> opnode_map;
1048  counter = 0;
1049  for(auto it = this->opNodes().begin(); it != this->opNodes().end(); it++)
1050  {
1051  opnode_map[(*it)] = "node" + std::to_string(counter++);
1052  s << opnode_map[(*it)] << "[opcode=" << (*it)->opcode << "];\n";
1053  }
1054 
1055  // use val nodes to create all edges
1056  for(auto it = this->valNodes().begin(); it != this->valNodes().end(); it++)
1057  {
1058  std::string inputnode = opnode_map[(*it)->input];
1059  for(unsigned int o = 0; o < (*it)->output.size(); o++)
1060  {
1061  OpGraphOp* op = (*it)->output.at(o);
1062  std::string operand = (*it)->output_operand.at(o);
1063  s << inputnode << "->" << opnode_map[op] << "[operand=" << operand << "]; ";
1064  s << "//" << (*it)->input->name << "->" << op->name << "\n";
1065  }
1066  }
1067  s << "}\n";
1068 }
1069 
1070 void OpGraph::serialize(std::ostream& s) const {
1071  // std::vector<OpDescriptor> input_nodes;
1072  // const auto& on = opNodes();
1073  // std::copy_if(on.begin(), on.end(), std::back_inserter(input_nodes), [&](const auto& op) {
1074  // return fanin(op).empty();
1075  // });
1076 
1077  // struct V : DoNothingGraphVisitor<OpDescriptor> {
1078  // std::map<OpGraph::OpDescriptor, int> op_print_ranking;
1079  // onExamine(const auto& op) { op_print_ranking.insert(op, static_cast<int>(op_print_ranking.size())); }
1080  // shouldIgnore(const auto& op) const { return op_print_ranking.count(op) != 0; }
1081  // };
1082 
1083  // for (const auto& seed_op : input_nodes) {
1084  // wavedBreadthFirstVisit();
1085  // }
1086 
1087  // return serialize(s, op_print_ranking);
1088  return serialize(s, {});
1089 }
1090 
1091 void OpGraph::serialize(std::ostream& s, const std::map<OpDescriptor,int>& op_print_ranking) const {
1092 
1093  // print ordering. If no value specified, is tied for last
1094  const auto get_ranking = [&](OpDescriptor op) {
1095  const auto lookup = op_print_ranking.find(op);
1096  if (lookup == op_print_ranking.end()) {
1097  return std::numeric_limits<int>::max();
1098  } else {
1099  return lookup->second;
1100  }
1101  };
1102 
1103  // print in order of given ranking. Stable, so ties go to memory order
1104  auto sorted_op_nodes = opNodes();
1105  std::stable_sort(sorted_op_nodes.begin(), sorted_op_nodes.end(), [&,this](const auto& lhs, const auto& rhs) {
1106  return get_ranking(lhs) < get_ranking(rhs);
1107  });
1108 
1109  s << "digraph G {\n";
1110 
1111  for (const auto& op_desc : sorted_op_nodes) {
1112  s << " "; printDotID(s, getNodeRef(op_desc).getName()) << " [opcode=" << getNodeRef(op_desc).opcode;
1113  if (getNodeRef(op_desc).opcode == OpCode::CONST) {
1114  s << ", constVal="; printDotID(s, std::to_string(getNodeRef(op_desc).constVal));
1115  } else if (getNodeRef(op_desc).opcode == OpCode::CMP) {
1116  s << ", cmpMode="; printDotID(s, getNodeRef(op_desc).cmpMode);
1117  } else if (getNodeRef(op_desc).opcode == OpCode::STORE || getNodeRef(op_desc).opcode == OpCode::LOAD ) {
1118  s << ", memName="; printDotID(s, getNodeRef(op_desc).memName);
1119  }
1120  s << "];\n";
1121  }
1122 
1123  for (const auto& src_op_desc : sorted_op_nodes) {
1124  const auto& src_op = getNodeRef(src_op_desc);
1125 
1126  // sort out edges by op rank, with ties broken by output_index. Stable, so ties go to memory order
1127  auto sorted_out_edges = outEdges(src_op_desc);
1128  std::stable_sort(sorted_out_edges.begin(), sorted_out_edges.end(), [&,this](const auto& lhs, const auto& rhs) {
1129  const auto& lhs_rank = get_ranking(this->targetOfEdge(lhs));
1130  const auto& rhs_rank = get_ranking(this->targetOfEdge(rhs));
1131  if (lhs_rank == rhs_rank) {
1132  return lhs.output_index < rhs.output_index;
1133  } else {
1134  return lhs_rank < rhs_rank;
1135  }
1136  });
1137 
1138  for (const auto& out_edge : sorted_out_edges) {
1139  const auto& sink_op = getNodeRef(targetOfEdge(out_edge));
1140  const auto& val = getNodeRef(out_edge.val);
1141  const auto& operand_tag = val.output_operand.at(out_edge.output_index);
1142  const bool has_operand_tag = not operand_tag.empty();
1143  const bool has_attributes = has_operand_tag; // && anything new
1144  s << " " ; printDotID(s, src_op.getName()) << " -> " ; printDotID(s, sink_op.getName());
1145  if (has_attributes) {
1146  s << " [";
1147  if (has_operand_tag) {
1148  s << "operand="; printDotID(s, operand_tag);
1149  }
1150  s << "]";
1151  }
1152  s << ";\n";
1153  }
1154  }
1155 
1156  s << "}\n";
1157 }
1158 
1159 bool operator==(const OpGraph& lhs, const OpGraph& rhs) {
1160  if (&lhs == &rhs) { return true; }
1161 
1162  // check ops same
1163  if (lhs.ops_by_name.size() != rhs.ops_by_name.size()) { return false; }
1164  for (const auto& lhs_name_and_opdesc : lhs.ops_by_name) {
1165  // find the node with the same name
1166  const auto rhs_search_result = rhs.ops_by_name.find(lhs_name_and_opdesc.first);
1167  if (rhs_search_result == rhs.ops_by_name.end()) { return false; }
1168 
1169  // check if properties are the same
1170  const auto& lhs_node = lhs.getNodeRef(lhs_name_and_opdesc.second);
1171  const auto& rhs_node = rhs.getNodeRef(rhs_search_result->second);
1172  if (not (lhs_node == rhs_node)) { return false; }
1173 
1174  // check if fanout is the same
1175  const auto& lhs_out_edges = lhs.outEdges(lhs_name_and_opdesc.second);
1176  const auto& rhs_out_edges = rhs.outEdges(rhs_search_result->second);
1177  const auto& mismatch_result = std::mismatch(
1178  lhs_out_edges.begin(), lhs_out_edges.end(),
1179  rhs_out_edges.begin(), rhs_out_edges.end(),
1180  [&](const auto& lhs_edge, const auto& rhs_edge) {
1181  // shouldn't be any nulls in the first place... but check anyway
1182  if (not (bool)lhs_edge || not (bool)rhs_edge) { return false; }
1183 
1184  // get, then compare, the val with the same name (if it exists)
1185  // and check if the operand index is the same
1186  const auto& lhs_val = lhs.getNodeRef(lhs_edge.val);
1187  const auto& rhs_val_lookup = rhs.vals_by_name.find(lhs_val.name);
1188  if (rhs_val_lookup == rhs.vals_by_name.end()) { return false; }
1189  const auto& rhs_val = rhs.getNodeRef(rhs_edge.val);
1190  return lhs_val == rhs_val && (
1191  lhs_val.output_operand.at(lhs_edge.output_index)
1192  == rhs_val.output_operand.at(rhs_edge.output_index)
1193  );
1194  }
1195  );
1196  if (mismatch_result != std::make_pair(lhs_out_edges.end(), rhs_out_edges.end())) { return false; }
1197  }
1198 
1199  // check vals same
1200  if (lhs.vals_by_name.size() != rhs.vals_by_name.size()) { return false; }
1201  for (const auto& lhs_name_and_valdesc : lhs.vals_by_name) {
1202  const auto rhs_search_result = rhs.vals_by_name.find(lhs_name_and_valdesc.first);
1203  if (rhs_search_result == rhs.vals_by_name.end()) { return false; }
1204  const auto& lhs_node = lhs.getNodeRef(lhs_name_and_valdesc.second);
1205  const auto& rhs_node = rhs.getNodeRef(rhs_search_result->second);
1206  if (not (lhs_node == rhs_node)) { return false; }
1207  }
1208 
1209  return true;
1210 }
1211 
1213  const OpGraph& src,
1214  const std::set<OpGraph::OpDescriptor>& allowed_ops
1215 ) {
1216  OpGraphTransformResult result;
1217 
1218  // add all the ops
1219  for (const auto& src_op : src.opNodes()) {
1220  if (allowed_ops.end() == allowed_ops.find(src_op)) { continue; }
1221  const auto& dest_op = result.transform_result.insert(src.getNodeRef(src_op).propertyClone());
1222  result.forward_mappings.emplace(src_op, dest_op);
1223  result.reverse_mappings.emplace(dest_op, src_op);
1224  }
1225 
1226  // add all the vals
1227  for (const auto& src_op : src.opNodes()) {
1228  if (allowed_ops.end() == allowed_ops.find(src_op)) { continue; }
1229  const auto& dest_op = result.transform_result.asOp(result.forward_mappings.at(src_op));
1230  const auto& src_val = src.getNodeRef(src.outputVal(src_op));
1231  int output_idex = 0;
1232  for (const auto& src_oop : src.outputOps(src_op)) {
1233  if (allowed_ops.end() == allowed_ops.find(src_oop)) { continue; }
1234  const auto& dest_oop = result.transform_result.asOp(result.forward_mappings.at(src_oop));
1235  const auto& dest_oval = result.transform_result.link(dest_op, dest_oop,
1236  src_val.output_operand.at(output_idex), src_val.bitwidth, src_val.dist, src_val.kind);
1237  result.forward_mappings.emplace(src.outputVal(src_op), dest_oval); // subsequent iterations will do nothing
1238  result.reverse_mappings.emplace(dest_oval, src.outputVal(src_op)); // subsequent iterations will do nothing
1239  output_idex += 1;
1240  }
1241  }
1242 
1243  return result;
1244 }
1245 
1246 std::set<OpGraph::OpDescriptor> findNDownstreamOps(
1247  const OpGraph& opgraph,
1248  const std::vector<OpGraphOp*>& starting_points,
1249  const std::ptrdiff_t n_ops
1250 ) {
1251  using ODesc = OpGraph::OpDescriptor;
1252  const auto g_algos = GraphAlgorithms<ODesc>{};
1253 
1254  struct Visitor : DoNothingGraphVisitor<ODesc> {
1255  std::ptrdiff_t n_ops = -1; // better than uninitialized?
1256  std::set<ODesc> ops = {};
1257  bool have_enough = false;
1258 
1259  void onExamine(const ODesc& op) { if (!have_enough) { ops.insert(op); } }
1260  void onWaveEnd() { have_enough = (std::ptrdiff_t)ops.size() > n_ops; }
1261  bool shouldStop() { return have_enough; }
1262  } v;
1263  v.n_ops = n_ops; // no aggregate init here, until c++17
1264 
1265  std::set<OpGraph::OpDescriptor> starting_points_as_set(starting_points.begin(), starting_points.end());
1266  g_algos.wavedBreadthFirstVisit(opgraph, starting_points_as_set, visitAllVertices(), v);
1267 
1268  return std::move(v.ops);
1269 }
1270 
1271 // in the cpp file, as LLVM disables RTTI.
1272 // Somehow, the linker figures out how to make the code work together?
1273 // The RTTI data is still present (would get a link error otherwise)...
1274 // Probably crash if called from LLVM plugin code
1275 auto OpGraph::asOp(NodeDescriptor ndesc) const -> OpDescriptor { return dynamic_cast<OpDescriptor>(ndesc); }
1276 auto OpGraph::asVal(NodeDescriptor ndesc) const -> ValDescriptor { return dynamic_cast<ValDescriptor>(ndesc); }
1277 
1278 std::ostream& operator<<(std::ostream& os, const OpGraph::EdgeDescriptor& ed) {
1279  if (ed) { os << ed.val->name; } else { os << "nullptr"; }
1280  return os << "-" << ed.output_index;
1281 }
1282 std::ostream& operator<<(std::ostream& os, const OpGraph::VerifyMessage::Type& vm_type) {
1283  using Type = OpGraph::VerifyMessage::Type;
1284  switch (vm_type) {
1285  case Type::Info: os << "Info"; break;
1286  case Type::Warning: os << "Warning"; break;
1287  case Type::Error: os << "Error"; break;
1288  default: os << "OpGraphVMTNotImplementedByPrinter" << +vm_type; break;
1289  }
1290  return os;
1291 }
OpCode::BR
@ BR
OpGraph::OpOpInsertResult
Definition: OpGraph.h:270
OpGraph::empty_val_vector
static const std::vector< OpGraphVal * > empty_val_vector
Definition: OpGraph.h:470
StandardMapMaker
Definition: Collections.h:148
OpGraphVal::output
std::vector< OpGraphOp * > output
Definition: OpGraph.h:196
OpCode::ADD_HALF
@ ADD_HALF
BitConfig
Definition: BitSetting.h:58
OpCode::DIV
@ DIV
OpCode::MULS_HALF_LO
@ MULS_HALF_LO
OpGraph::EdgeDescriptor::val
OpGraph::ValDescriptor val
Definition: OpGraph.h:225
OpGraphOp::output
OpGraphVal * output
Definition: OpGraph.h:166
ConfigGraph.h
OpCode::MULS_FULL_LO
@ MULS_FULL_LO
OpGraph::clear
void clear()
Definition: OpGraph.cpp:296
OpGraphNode::~OpGraphNode
virtual ~OpGraphNode()
Definition: OpGraph.cpp:148
OperandTag
std::string OperandTag
Definition: OpGraph.h:81
OpGraphTransformResult::transform_result
OpGraph transform_result
Definition: OpGraph.h:503
OpGraph.h
OpCode::SEXT
@ SEXT
OpCode::CONST
@ CONST
OpGraphTransformResult
Definition: OpGraph.h:502
GraphAlgorithms
Definition: GraphAlgorithms.h:72
dotparse.h
OpCode::PHI
@ PHI
OpGraph::outputVals
std::vector< ValDescriptor > outputVals(OpDescriptor op_desc) const
Definition: OpGraph.cpp:483
GraphAlgorithms.h
OpCode::ADD_QUART
@ ADD_QUART
OpGraph::getDist
const int getDist(EdgeDescriptor edge) const
Definition: OpGraph.cpp:513
OpGraph::getNodeRef
OpGraphOp & getNodeRef(OpDescriptor ndesc)
Definition: OpGraph.h:376
OpGraph::getOpIndex
int getOpIndex(OpGraphOp *op) const
Definition: OpGraph.cpp:747
OpCode::ASHR
@ ASHR
OpGraph::targetOfEdge
OpDescriptor targetOfEdge(EdgeDescriptor ed) const
Definition: OpGraph.cpp:525
OpCode::ZEXT
@ ZEXT
OpSchedule::latency
int latency
Definition: OpGraph.h:496
OpCode::OR
@ OR
OpGraphVal::dist
int dist
Definition: OpGraph.h:199
begin
auto begin(const SingleItemImmutableSet< VertexID > &siis)
Definition: Collections.h:137
link
static void link(MRRGNode *driver, MRRGNode *fanout)
Definition: MRRG.cpp:822
printDotID
auto printDotID(std::ostream &os, const std::string &id) -> std::ostream &
Definition: ConfigGraph.h:26
OpGraph::asOp
OpDescriptor asOp(NodeDescriptor ndesc) const
Definition: OpGraph.cpp:1275
OpGraph::inputVals
const std::vector< OpGraphVal * > & inputVals(OpDescriptor op) const
Definition: OpGraph.cpp:551
OpGraph::VerifyMessage::Type
Type
Definition: OpGraph.h:448
DoNothingGraphVisitor::onWaveEnd
void onWaveEnd()
Definition: GraphAlgorithmHelpers.h:93
OpGraph::getBitwidth
const int getBitwidth(EdgeDescriptor edge) const
Definition: OpGraph.cpp:517
OpCode::SELECT
@ SELECT
OpCode::STORE
@ STORE
OpGraph::valNodes
auto & valNodes() const
Definition: OpGraph.h:314
visitAllVertices
auto visitAllVertices()
Definition: GraphAlgorithmHelpers.h:38
OpGraphVal::OpGraphVal
OpGraphVal(std::string name, int bitwidth, int dist=0, EdgeKind ek=EdgeKind::kDataFlow)
Definition: OpGraph.cpp:252
computeASAP
OpSchedule computeASAP(const OpGraph &op_graph)
Definition: OpGraph.cpp:572
OpSchedule::cycle
std::unordered_map< const OpGraphOp *, int > cycle
Definition: OpGraph.h:495
OpGraph::getAliasIndex
int getAliasIndex(OpGraphVal *op) const
Definition: OpGraph.cpp:765
OpGraph::inputOps
std::vector< OpDescriptor > inputOps(OpDescriptor op) const
Definition: OpGraph.cpp:497
OpGraph::null_val
static constexpr ValDescriptor null_val
Definition: OpGraph.h:473
OpGraph::getOperandTag
const OperandTag & getOperandTag(EdgeDescriptor edge) const
Definition: OpGraph.cpp:509
OpCode::ADD
@ ADD
OpCode::CMP
@ CMP
OpGraphVal::output_predicate
std::vector< bool > output_predicate
Definition: OpGraph.h:201
OpCode::INT2FP
@ INT2FP
OpGraph::edgeLatencies
std::map< EdgeDescriptor, int > edgeLatencies() const
Definition: OpGraph.cpp:906
OpGraph::print_dot
void print_dot(std::ostream &s) const
Definition: OpGraph.cpp:1040
OpGraph::serialize
void serialize(std::ostream &s) const
Definition: OpGraph.cpp:1070
OpGraph::link_like
ValDescriptor link_like(OpDescriptor driver, OpDescriptor fanout, EdgeDescriptor base)
Definition: OpGraph.cpp:418
OpGraphOp::constVal
std::int64_t constVal
Definition: OpGraph.h:160
OpGraph::inputOp
OpDescriptor inputOp(ValDescriptor val) const
Definition: OpGraph.cpp:504
DoNothingGraphVisitor::onExamine
void onExamine(const VertexID &)
Definition: GraphAlgorithmHelpers.h:55
OpCode::MULU_QUART_LO
@ MULU_QUART_LO
to_string
const std::string & to_string(const OpGraphOpCode &opcode)
Definition: OpGraph.cpp:111
OpGraph::verify
std::vector< VerifyMessage > verify() const
Definition: OpGraph.cpp:684
OpGraphOpCode
signed char OpCode OpGraphOpCode
Definition: OpGraph.h:15
OpGraph::ops_by_name
std::unordered_map< std::string, OpDescriptor > ops_by_name
Definition: OpGraph.h:465
OpCode::LOAD
@ LOAD
OpGraphOp::input
std::vector< OpGraphVal * > input
Definition: OpGraph.h:164
OpCode::MULS_QUART_HI
@ MULS_QUART_HI
OpGraphVal::~OpGraphVal
~OpGraphVal()
Definition: OpGraph.cpp:268
OpCode::MULU_FULL_LO
@ MULU_FULL_LO
OpCode::LSHR
@ LSHR
OpGraph::VerifyMessage::Type::Info
@ Info
OpCode::MULU_HALF_HI
@ MULU_HALF_HI
OpSchedule
Definition: OpGraph.h:494
OpCode::NOP
@ NOP
OpGraph::inEdges
std::vector< EdgeDescriptor > inEdges(const OpDescriptor &op) const
Definition: OpGraph.cpp:540
Exception.h
analyzeOpgraphVerifyResults
bool analyzeOpgraphVerifyResults(std::ostream &os, const std::vector< OpGraph::VerifyMessage > &messages, const bool silent_on_no_errors)
Definition: OpGraph.cpp:800
OpGraphOp::operator==
bool operator==(const OpGraphOp &rhs) const
Definition: OpGraph.cpp:235
OpCode::XOR
@ XOR
OpGraph::getValByIndex
const OpGraphVal * getValByIndex(int index) const
Definition: OpGraph.cpp:782
OpGraphTransformResult::reverse_mappings
std::unordered_map< OpGraph::NodeDescriptor, OpGraph::NodeDescriptor > reverse_mappings
Definition: OpGraph.h:505
operator==
bool operator==(const OpGraph &lhs, const OpGraph &rhs)
Definition: OpGraph.cpp:1159
OpCode::GEP
@ GEP
OpGraph::fanin
decltype(auto) fanin(const EdgeDescriptor &ed) const
Definition: OpGraph.h:369
findNDownstreamOps
std::set< OpGraph::OpDescriptor > findNDownstreamOps(const OpGraph &opgraph, const std::vector< OpGraphOp * > &starting_points, const std::ptrdiff_t n_ops)
Definition: OpGraph.cpp:1246
OpGraph::EdgeDescriptor::output_index
int output_index
Definition: OpGraph.h:225
OpGraph::outputOps
const std::vector< OpGraphOp * > & outputOps(ValDescriptor op) const
Definition: OpGraph.cpp:488
OpGraphNode::name
std::string name
Definition: OpGraph.h:123
OpGraph::link
ValDescriptor link(OpDescriptor driver, OpDescriptor fanout, std::string operand_group, int bitwidth=32, int dist=0, EdgeKind kind=EdgeKind::kDataFlow, bool predicate=false)
Definition: OpGraph.cpp:364
OpCode::INPUT
@ INPUT
OpCode::MULU_HALF_LO
@ MULU_HALF_LO
OpGraph::opNodes
auto & opNodes() const
Definition: OpGraph.h:313
OpGraphVal::output_operand
std::vector< std::string > output_operand
Definition: OpGraph.h:198
OpGraphOp::OpGraphOp
OpGraphOp(std::string name, int bitwidth)
OpGraph::fanout
const std::vector< OpGraphOp * > & fanout(OpDescriptor op) const
Definition: OpGraph.h:334
OpGraph::outputVal
ValDescriptor outputVal(OpDescriptor op) const
Definition: OpGraph.cpp:476
OpCode::INPUT_PRED
@ INPUT_PRED
OpGraph::VerifyMessage::Type::Warning
@ Warning
OpGraphNode
Definition: OpGraph.h:113
OpCode::MULU_FULL_HI
@ MULU_FULL_HI
OpCode::FMUL
@ FMUL
OpGraphOp::propertyClone
OpGraphOp propertyClone() const
Definition: OpGraph.h:149
string_from_stream
std::string string_from_stream(F &&f)
Definition: Exception.h:95
OpGraphOp::bitwidth
int bitwidth
Definition: OpGraph.h:159
OpCode::SHL
@ SHL
OpGraph::empty_op_vector
static const std::vector< OpGraphOp * > empty_op_vector
Definition: OpGraph.h:469
OpGraph::getOpByIndex
const OpGraphOp * getOpByIndex(int index) const
Definition: OpGraph.cpp:774
OpGraph::defunct_nodes
std::vector< std::unique_ptr< OpGraphNode > > defunct_nodes
Definition: OpGraph.h:478
OpGraph::~OpGraph
~OpGraph()
Definition: OpGraph.cpp:294
OpCode::FDIV
@ FDIV
OpCode::TRUNC
@ TRUNC
OpGraphTransformResult::forward_mappings
std::unordered_map< OpGraph::NodeDescriptor, OpGraph::NodeDescriptor > forward_mappings
Definition: OpGraph.h:504
OpGraph::val_nodes
std::vector< OpGraphVal * > val_nodes
Definition: OpGraph.h:461
OpGraphVal
Definition: OpGraph.h:178
DoNothingGraphVisitor
Definition: GraphAlgorithmHelpers.h:49
end
auto end(const SingleItemImmutableSet< VertexID > &siis)
Definition: Collections.h:138
EdgeKind
EdgeKind
Definition: OpGraph.h:99
OpCode::SQRT
@ SQRT
OpGraph::operator=
OpGraph & operator=(const OpGraph &)
Definition: OpGraph.cpp:319
OpGraph::getKind
const EdgeKind getKind(EdgeDescriptor edge) const
Definition: OpGraph.cpp:521
OpGraph::asVal
ValDescriptor asVal(NodeDescriptor ndesc) const
Definition: OpGraph.cpp:1276
OpGraphOp
Definition: OpGraph.h:131
EdgeKind::kAlias
@ kAlias
DoNothingGraphVisitor::shouldStop
bool shouldStop()
Definition: GraphAlgorithmHelpers.h:101
OpGraph::VerifyMessage
Definition: OpGraph.h:447
verifyAndPrintReport
bool verifyAndPrintReport(const OpGraph &opgraph, std::ostream &os, bool silent_on_no_errors, bool throw_if_errors)
Definition: OpGraph.cpp:790
OpGraph::empty_edge_vector
static const std::vector< EdgeDescriptor > empty_edge_vector
Definition: OpGraph.h:471
OpCode::MULS_FULL_HI
@ MULS_FULL_HI
EdgeKind::kDataFlow
@ kDataFlow
OpGraphOp::cmpMode
std::string cmpMode
Definition: OpGraph.h:161
OpCode::OUTPUT
@ OUTPUT
OpCode::ADD_FULL
@ ADD_FULL
OpGraph::VerifyMessage::Type::Error
@ Error
OpGraph::EdgeDescriptor
Definition: OpGraph.h:224
OpCode::AND
@ AND
OpCode::OUTPUT_PRED
@ OUTPUT_PRED
OpGraph::insert
OpDescriptor insert(OpGraphOp op)
Definition: OpGraph.cpp:338
OpCode::MULS_QUART_LO
@ MULS_QUART_LO
OpCode::SUB
@ SUB
OpCode::MUL
@ MUL
OpGraph::alias_nodes
std::vector< OpGraphVal * > alias_nodes
Definition: OpGraph.h:462
OpGraphVal::getOperandForOutput
std::string getOperandForOutput(const OpGraphOp *)
Definition: OpGraph.cpp:270
OpGraphVal::input
OpGraphOp * input
Definition: OpGraph.h:190
StandardSetMaker
Definition: Collections.h:156
MRRGProcedures.h
if
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0.0) set(CMAKE_CXX_FLAGS "$
Definition: CMakeLists.txt:7
OpCode::ICMP
@ ICMP
singleItemSet
SingleItemImmutableSet< VertexID > singleItemSet(VertexID v)
Definition: Collections.h:143
dfs_visit
static void dfs_visit(int time, int &longest_cycle, std::map< OpGraphOp *, int > &dfs_colour, std::map< OpGraphOp *, int > &dfs_timestamp, OpGraphOp *op)
Definition: OpGraph.cpp:834
OpGraph::getMaxCycle
int getMaxCycle()
Definition: OpGraph.cpp:867
OpGraph::printDOTwithOps
void printDOTwithOps(std::ostream &s) const
Definition: OpGraph.cpp:1000
OpGraph::op_nodes
std::vector< OpGraphOp * > op_nodes
Definition: OpGraph.h:460
opcode_from_string
OpGraphOpCode opcode_from_string(const std::string &str)
Definition: OpGraph.cpp:113
OpGraphOp::~OpGraphOp
~OpGraphOp()
Definition: OpGraph.cpp:250
OpGraph::vals_by_name
std::unordered_map< std::string, ValDescriptor > vals_by_name
Definition: OpGraph.h:466
OpGraph::outEdges
std::vector< EdgeDescriptor > outEdges(const OpDescriptor &op) const
Definition: OpGraph.cpp:530
OpGraph::unLink
void unLink(ValDescriptor driver_val, OpDescriptor fanout)
Definition: OpGraph.cpp:422
OpCode::MULS_HALF_HI
@ MULS_HALF_HI
OpGraphOp::memName
std::string memName
Definition: OpGraph.h:162
filter
OpGraphTransformResult filter(const OpGraph &src, const std::set< OpGraph::OpDescriptor > &allowed_ops)
Definition: OpGraph.cpp:1212
operator<<
std::ostream & operator<<(std::ostream &os, const OpGraphOpCode &opcode)
Definition: OpGraph.cpp:100
OpGraph::erase
void erase(OpDescriptor op)
Definition: OpGraph.cpp:453
OpCode::FP2INT
@ FP2INT
OpGraph::Walk
std::vector< EdgeDescriptor > Walk
Definition: OpGraph.h:231
OpGraph::OpDescriptor
const OpGraphOp * OpDescriptor
Definition: OpGraph.h:219
OpCode::MULU_QUART_HI
@ MULU_QUART_HI
OpCode::FADD
@ FADD
computeALAP
OpSchedule computeALAP(const OpGraph &op_graph, unsigned int max_cycles)
Definition: OpGraph.cpp:629
OpGraph
Definition: OpGraph.h:215
OpGraphOp::opcode
OpGraphOpCode opcode
Definition: OpGraph.h:158
OpGraphVal::getPredicateForOutput
bool getPredicateForOutput(const OpGraphOp *)
Definition: OpGraph.cpp:276
OpGraph::getValIndex
int getValIndex(OpGraphVal *op) const
Definition: OpGraph.cpp:756
cgrame_error
Definition: Exception.h:20
operator>>
std::istream & operator>>(std::istream &is, OpGraphOpCode &opcode)
Definition: OpGraph.cpp:140
OpGraph::OpGraph
OpGraph()
Definition: OpGraph.cpp:282
OpGraphNode::getName
const std::string & getName() const
Definition: OpGraph.h:121