OpenASIP 2.2
Loading...
Searching...
No Matches
OperationDAGConverter.cc
Go to the documentation of this file.
1/*
2 Copyright (c) 2002-2009 Tampere University.
3
4 This file is part of TTA-Based Codesign Environment (TCE).
5
6 Permission is hereby granted, free of charge, to any person obtaining a
7 copy of this software and associated documentation files (the "Software"),
8 to deal in the Software without restriction, including without limitation
9 the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 and/or sell copies of the Software, and to permit persons to whom the
11 Software is furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 DEALINGS IN THE SOFTWARE.
23 */
24/**
25 * @file OperationDAGConverter.cc
26 *
27 * Implementation of OperationDAGConverter class.
28 *
29 * @author Mikael Lepistö 2007 (tmlepist-no.spam-cs.tut.fi)
30 * @note rating: red
31 */
32
36
37#include "CompilerWarnings.hh"
38IGNORE_CLANG_WARNING("-Wunused-local-typedef")
39#include <boost/algorithm/string.hpp>
40#include <boost/algorithm/string/trim.hpp>
42
43#include "OperationPool.hh"
44#include "OperationNode.hh"
45#include "OperationDAGEdge.hh"
46#include "TerminalNode.hh"
47#include "ConstantNode.hh"
48#include "Operation.hh"
49#include "TCEString.hh"
50#include "OperationDAG.hh"
51#include "OperationPimpl.hh"
52
53/**
54 * Creates OperationDAG out of OSAL DAG language source code.
55 *
56 * @param sourceCode OSAL DAG Language source code.
57 * @return Dynamically allocated OperationDAG instance.
58 * @exception IllegalParameters There was an error during parsing.
59 */
61OperationDAGConverter::createDAG(const OperationPimpl& operation, std::string sourceCode) {
63 skip_grammar skip;
64
65 // lets add semicolon to end of file to make boost 1.34 happy
66 sourceCode += "\n;";
67
68 const char *orig = sourceCode.c_str();
69
70 //std::cerr << "source code" << std::endl << sourceCode << std::endl;
71
72 parse_info<const char*> result =
73 parse(orig, g, skip);
74
75 if (result.full) {
77 OperationDAG* retVal = new OperationDAG(operation);
78 OperationDAGBuilder builder(operation, *retVal, *root);
79
80 // std::cerr << g.tokenData_.tokenTree()->toStr() << std::endl;
81
82 builder.parse();
83 return retVal;
84
85 } else {
86 for (unsigned int i = 0; i < skip.strippedParts.size(); i++) {
87 skip.strippedParts[i].second -=
88 reinterpret_cast<long int>(skip.strippedParts[i].first);
89
90 skip.strippedParts[i].first -= reinterpret_cast<long int>(orig);
91
92 //std::cerr << "Skipped " << int(skip.strippedParts[i].second)
93 //<< " chars at pos " << int(skip.strippedParts[i].first)
94 //<< " line: \""
95 //<< sourceCode.substr(int(skip.strippedParts[i].first),
96 //int(skip.strippedParts[i].second)) << "\"" << std::endl;
97 }
98
99 // find character count in original code
100 unsigned int charsToPos = 0;
101 for (unsigned int i = 0; i < result.length ; i++) {
102
103 for (unsigned int j = 0; j < skip.strippedParts.size(); j++) {
104 if ((unsigned long int)skip.strippedParts[j].first
105 == charsToPos) {
106 charsToPos +=
107 reinterpret_cast<long int>(skip.strippedParts[j].second);
108
109 //std::cerr << std::endl << "Added at position: "
110 //<< int(skip.strippedParts[j].first)
111 //<< " to char length: "
112 //<< int(skip.strippedParts[j].second);
113 }
114 }
115
116 charsToPos++;
117 }
118
119 // find line number
120 int lineNumber = 1;
121 for (unsigned int i = 0; i < charsToPos; i++) {
122 if (sourceCode.at(i) == '\n') {
123 lineNumber++;
124 }
125 }
126
127 // get logical line
128 int lineStart = sourceCode.rfind(';', charsToPos);
129 int lineEnd = sourceCode.find(';', charsToPos);
130
131 std::string errorLine =
132 boost::algorithm::trim_copy(
133 sourceCode.substr(lineStart + 1, lineEnd - lineStart));
134
135 // std::cerr << "result.stop: " << result.stop << std::endl
136 // << "result.hit: " << result.hit << std::endl
137 // << "result.full: " << result.full << std::endl
138 // << "result.length: " << result.length << std::endl;
139
140 std::string message = "Parsing failed at the position " +
141 Conversion::toString(result.length) + " in the code.\n" +
142 "line " + Conversion::toString(lineNumber) + ": " + errorLine;
143
144 //std::cerr << message << std::endl;
145
146 throw IllegalParameters(__FILE__, __LINE__, __func__, message);
147 }
148 return NULL;
149}
150
151
152/**
153 * Writes node and all the nodes connected as osal code.
154 *
155 * @param retVal String where to code is written.
156 * @param dag Dag that contains the nodes.
157 * @param node Currently written node.
158 * @param varBindings Names of variables that are bound for TerminalNodes and
159 * OperandNode outputs.
160 * @param alreadyHandled If node is included to this ser if it is
161 * already fully written and all its operands are handled.
162 * @param tempVarCount Number of variables created.
163 * @param currentlyHandling Node is in this set if it's currently handled in
164 * some recursion level.
165 * @param opReplace Map of operation names and C operators that can simulate them.
166 * @return false If node is already in middle of handling.
167 */
168#define DEBUG_CODE(x)
169
170bool
172 std::string& retVal,
173 const OperationDAG& dag,
174 const OperationDAGNode& node,
175 std::map<VariableKey, std::string>& varBindings,
176 std::set<const OperationDAGNode*>& alreadyHandled,
177 int& tempVarCount,
178 std::set<const OperationDAGNode*>& currentlyHandling,
179 std::map<std::string, std::string>* opReplace,
180 std::vector<std::string>* varReplacements) {
181
182 static int tmpCounter = 0;
183
184 DEBUG_CODE(static std::string recursion_level = ""; recursion_level += "----";);
185
186 int currentStepsToRoot = dag.stepsToRoot(node);
187
188 if (currentlyHandling.find(&node) != currentlyHandling.end()) {
189 DEBUG_CODE(recursion_level = recursion_level.erase(0,4););
190 return false;
191 }
192
193 if (alreadyHandled.find(&node) == alreadyHandled.end()) {
194
195 currentlyHandling.insert(&node);
196
197 const TerminalNode* termNode =
198 dynamic_cast<const TerminalNode*>(&node);
199 const ConstantNode* constNode =
200 dynamic_cast<const ConstantNode*>(&node);
201
202 if (constNode != NULL) {
203 currentlyHandling.erase(constNode);
204 alreadyHandled.insert(constNode);
205 VariableKey termKey(constNode, 0);
206 varBindings[termKey] = constNode->toString();
207 return true;
208 } else if (termNode != NULL) {
209 // Node is input or output terminal or a const. Either set
210 // variablename e.g. IO(1) to node to varBindings or
211 // write variable
212 // value to output node e.g. "IO(4) = tmp12;\n"
213
214 std::string ioName = "";
215 if (!varReplacements ||
216 (termNode->operandIndex() - 1) > (int)varReplacements->size()) {
217 ioName = "IO(" +
218 Conversion::toString(termNode->operandIndex()) + ")";
219 } else {
220 ioName = varReplacements->at(termNode->operandIndex() - 1);
221 }
222
223 VariableKey termKey(termNode, termNode->operandIndex());
224
225 DEBUG_CODE(std::cerr << recursion_level
226 << "**** Started handling term node " << long(&node)
227 << ":" << currentStepsToRoot << ":" + ioName + "\n";);
228
229 const bool inputTerminalNode = dag.inDegree(*termNode) == 0;
230 if (inputTerminalNode) {
231 // TODO: should only do extension here if different bit widths
232 TCEString tmpName = "inTmp_"; tmpName << tmpCounter++;
233 retVal += "SimValue " + tmpName + ";\n";
234 retVal += tmpName + " = " +
235 castedVar(ioName, dag.operation().operand(
236 termNode->operandIndex()).type()) + ";\n";
237
238 // set veriable binding for reading (the extended) terminal value
239 varBindings[termKey] = tmpName;
240 DEBUG_CODE(std::cerr << recursion_level
241 << "Added input terminal: " + ioName + "\n";);
242
243 } else {
244
245 assert(dag.outDegree(*termNode) == 0);
246
247 OperationDAGEdge& srcEdge = dag.inEdge(*termNode, 0);
248 OperationDAGNode& tail = dag.tailNode(srcEdge);
249
250 DEBUG_CODE(std::cerr << recursion_level
251 << "** Read input of terminal " << ioName << "\n";);
252
253 writeNode(
254 retVal, dag, tail, varBindings, alreadyHandled,
255 tempVarCount, currentlyHandling, opReplace, varReplacements);
256 DEBUG_CODE(std::cerr << recursion_level
257 << "** Ready reading input of terminal " << ioName << "\n";);
258 VariableKey srcKey(&tail, srcEdge.srcOperand());
259
260 // write result to output terminal
261 retVal += ioName + " = " + varBindings[srcKey] + ";\n";
262 }
263
264 DEBUG_CODE(std::cerr << recursion_level
265 << "added terminal " << long (&node)
266 << "to already handled nodes\n";);
267
268 currentlyHandling.erase(termNode);
269 alreadyHandled.insert(termNode);
270
271 } else {
272 // Node is operation node. Read inputs and create destination
273 // variables for outputs. In the end write EXEC_OPERATION
274 // line to code.
275
276 const OperationNode* opNode =
277 dynamic_cast<const OperationNode*>(&node);
278
279 if (opNode == NULL) {
281 (boost::format(
282 "Must be either OperationNode or Terminal Node. "
283 "Got: %s.") % node.toString()).str());
284 }
285
286 Operation &refOp = opNode->referencedOperation();
287
288 DEBUG_CODE(std::cerr << recursion_level
289 << "Started handling opnode " << long(&node) << ":"
290 << currentStepsToRoot << ": " + refOp.name() + "\n";);
291
292 // go through in edges, copy bindings and create input string for
293 // EXEC_OPERATION
294
295 std::vector<std::string> operandVec(
296 refOp.numberOfInputs() + refOp.numberOfOutputs());
297
298 // kludge: this vector has 'true' in the operand's
299 // position only if the operand is a constant and
300 // thus needs not to be casted to IntWord with
301 // castedVar later when generating the operation
302 // parameters
303 std::vector<bool> isConstOperand(
304 refOp.numberOfInputs() + refOp.numberOfOutputs());
305
306 // input parameters
307 for (int i = 0; i < dag.inDegree(node); i++) {
308 OperationDAGEdge& edge = dag.inEdge(node, i);
309 OperationDAGNode& tail = dag.tailNode(edge);
310
311 const TerminalNode* termTail =
312 dynamic_cast<const TerminalNode*>(&tail);
313
314 int srcOperand;
315 if (termTail != 0) {
316 srcOperand = termTail->operandIndex();
317 } else {
318 srcOperand = edge.srcOperand();
319 }
320
321 VariableKey sourceKey;
322 if (dynamic_cast<const ConstantNode*>(&tail) != NULL) {
323 sourceKey = VariableKey(&tail, 0);
324 isConstOperand[edge.dstOperand()-1] = true;
325 } else {
326 sourceKey = VariableKey(&tail, srcOperand);
327 isConstOperand[edge.dstOperand()-1] = false;
328 }
329
330 // if input not already handled, treat it first.
331 DEBUG_CODE(std::cerr << recursion_level
332 << "** Read input op: " << refOp.name() << ":" << i << "\n";);
333
334 if (!writeNode(retVal, dag, tail, varBindings,
335 alreadyHandled, tempVarCount, currentlyHandling, opReplace, varReplacements)) {
336
337 DEBUG_CODE(std::cerr << recursion_level
338 << "Input can't be read yet: " << refOp.name() << ":" << i << "\n";);
339
340 currentlyHandling.erase(&node);
341
342 DEBUG_CODE(recursion_level = recursion_level.erase(0,4););
343 return false;
344 }
345
346 DEBUG_CODE(std::cerr << recursion_level
347 << "** Ready reading input of op: " << refOp.name() << ":" << i << "\n";);
348
349 // add input to correct place in EXEC_OPERATION parameter list
350 if (varBindings[sourceKey].empty()) {
351 throw IllegalParameters(
352 __FILE__, __LINE__, __func__,
353 std::string(
354 "DAG cannot be written to OSAL code, because illegal output edge. "
355 "Edge srcOperand: ") + Conversion::toString(srcOperand));
356 }
357
358 operandVec[edge.dstOperand()-1] = varBindings[sourceKey];
359
360 DEBUG_CODE(std::cerr << recursion_level
361 << "Added operand: " << varBindings[sourceKey]
362 << " key: (" << sourceKey.first << ":" << sourceKey.second << ")"
363 << " to " << refOp.name() << " paramer vector index: "
364 << edge.dstOperand() - 1 << std::endl;);
365 }
366
367 // and outputs
368 for (int i = 0; i < refOp.numberOfOutputs(); i++) {
369 int opNumber = i + refOp.numberOfInputs() + 1;
370 VariableKey operandKey(&node, opNumber);
371 tempVarCount++;
372 std::string tempVarName = "tmp" +
373 Conversion::toString(tempVarCount);
374 retVal = "SimValue " + tempVarName + ";\n" + retVal;
375 varBindings[operandKey] = tempVarName;
376
377 DEBUG_CODE(std::cerr << recursion_level
378 << "Created variable: " << tempVarName
379 << " key: (" << operandKey.first << ":" << operandKey.second << ")"
380 << " for " << refOp.name() << ":" << opNumber << std::endl;);
381
382 // add output to EXEC_OPERATION parameter list
383 operandVec[opNumber-1] = varBindings[operandKey];
384 DEBUG_CODE(std::cerr << recursion_level
385 << "Added operand: " << varBindings[operandKey]
386 << " to " << refOp.name() << " paramer vector index: "
387 << opNumber -1 << std::endl;);
388 }
389
390 // this node is processed.
391 DEBUG_CODE(std::cerr << recursion_level << "added node "
392 << long (&node) << "to already handled nodes\n" ;);
393
394 currentlyHandling.erase(&node);
395 alreadyHandled.insert(&node);
396
397 // Write the execute operation code
398 if (opReplace != NULL &&
399 opReplace->find(refOp.name()) != opReplace->end()) {
400 std::string simOp = (*opReplace)[refOp.name()];
401
402 std::string rightSide = "";
403 if (isConstOperand[0]) {
404 // the consts need not to be casted
405 rightSide = operandVec[0];
406 } else {
407 rightSide = castedVar(operandVec[0], refOp.operand(1).type());
408 }
409
410 // if unary operator
411 if (refOp.numberOfInputs() == 1) {
412 rightSide = simOp + rightSide;
413 } else {
414 for (int i = 1; i < refOp.numberOfInputs(); i++) {
415 if (isConstOperand[i]) {
416 rightSide +=
417 " " + simOp + " " + operandVec[i];
418 } else {
419 rightSide +=
420 " " + simOp + " " +
421 castedVar(
422 operandVec[i],
423 refOp.operand(i+1).type());
424 }
425 }
426 }
427
428 assert(refOp.numberOfOutputs() == 1 &&
429 "Cant write simulation replacement code only"
430 "for singleoutput operations.");
431
432 // assing right side to first outputoperand.
433 retVal += operandVec[refOp.numberOfInputs()] +
434 " = " + rightSide + ";\n";
435
436 } else {
437 retVal += "{ EXEC_OPERATION(" + std::string(refOp.name());
438 for (unsigned int i = 0; i < operandVec.size(); i++) {
439 retVal += ", " + operandVec[i];
440 }
441 retVal += "); } \n";
442
443 DEBUG_CODE(std::cerr << recursion_level
444 << "Added EXEC: " + refOp.name() << std::endl;);
445 }
446
447 // write outgoing nodes
448 for (int i = 0; i < dag.outDegree(node); i++) {
449 OperationDAGNode& headNode =
450 dag.headNode(dag.outEdge(node,i));
451
452 // handle only those nodes, which are one step away from
453 // curren node.
454 if (dag.stepsToRoot(headNode) == currentStepsToRoot + 1) {
455 DEBUG_CODE(std::cerr << recursion_level
456 << "** Goto output op: " << refOp.name()
457 << ":" << i << "\n";);
458
459 writeNode(retVal, dag, headNode, varBindings,
460 alreadyHandled, tempVarCount, currentlyHandling, opReplace, varReplacements);
461
462 DEBUG_CODE(std::cerr << recursion_level
463 << recursion_level
464 << "** Ready going output op: " << refOp.name()
465 << ":" << i << "\n";);
466 }
467 }
468 }
469 }
470
471 DEBUG_CODE(recursion_level = recursion_level.erase(0,4););
472 return true;
473}
474
475/**
476 * Cast variable to be correct type.
477 */
478std::string
480 std::string var, Operand::OperandType type) {
481
482 switch (type) {
484 return var + ".sIntWordValue()";
486 return var + ".uIntWordValue()";
488 return var + ".floatWordValue()";
490 return var + ".halfFloatWordValue()";
492 return var + ".doubleWordValue()";
494 return var + ".uLongWordValue()";
496 return var + ".sLongWordValue()";
497 default:
498 return var;
499 }
500}
501
502
503/**
504 * Write OSAL DAG code for graph.
505 *
506 * @param dag Graph to write as OSAL code (basically C subset)
507 */
508std::string
510 std::string retVal;
511 std::map<VariableKey, std::string> varBindings;
512 std::set<const OperationDAGNode*> alreadyHandled;
513 std::set<const OperationDAGNode*> currentlyHandling;
514 int tempVarCount = 0;
515
516 if (dag.nodeCount() == 0) {
517 return retVal;
518 }
519
520 // Writes recursively all the DAG from node(0)
521 writeNode(retVal, dag, dag.node(0), varBindings,
522 alreadyHandled, tempVarCount, currentlyHandling);
523
524 return retVal;
525}
526
527/**
528 * Optimizations, when compiled simulation code is created from DAG.
529 *
530 * OSAL implementations of operantions will not be called for calculating
531 * basic C operations.
532 */
533std::string
535 const OperationDAG& dag,
536 std::vector<std::string>* varReplacements) {
537 std::string retVal;
538 std::map<VariableKey, std::string> varBindings;
539 std::set<const OperationDAGNode*> alreadyHandled;
540 std::set<const OperationDAGNode*> currentlyHandling;
541 int tempVarCount = 0;
542 std::map<std::string, std::string> opReplacements;
543
544 opReplacements["ADD"] = "+";
545 opReplacements["SUB"] = "-";
546 opReplacements["MUL"] = "*";
547 opReplacements["DIV"] = "/";
548 opReplacements["DIVU"] = "/";
549 opReplacements["EQ"] = "==";
550 opReplacements["GT"] = ">";
551 opReplacements["GTU"] = ">";
552 opReplacements["SHL"] = "<<";
553 opReplacements["SHR"] = ">>";
554 opReplacements["SHRU"] = ">>";
555 opReplacements["AND"] = "&";
556 opReplacements["IOR"] = "|";
557 opReplacements["XOR"] = "^";
558 opReplacements["NEG"] = "-";
559 opReplacements["NEGF"] = "-";
560 opReplacements["ADDF"] = "+";
561 opReplacements["SUBF"] = "-";
562 opReplacements["MULF"] = "*";
563 opReplacements["DIVF"] = "/";
564 opReplacements["EQF"] = "==";
565 opReplacements["GTF"] = ">";
566 opReplacements["CFI"] = "(UIntWord)";
567 opReplacements["CIF"] = "(FloatWord)";
568 opReplacements["CFD"] = "(DoubleWord)";
569 opReplacements["CDF"] = "(FloatWord)";
570 opReplacements["MOD"] = "%";
571 opReplacements["MODU"] = "%";
572 opReplacements["MULH"] = "*";
573
574 opReplacements["ADD64"] = "+";
575 opReplacements["SUB64"] = "-";
576 opReplacements["MUL64"] = "*";
577 opReplacements["DIV64"] = "/";
578 opReplacements["DIVU64"] = "/";
579 opReplacements["EQ64"] = "==";
580 opReplacements["GT64"] = ">";
581 opReplacements["GTU64"] = ">";
582 opReplacements["SHL64"] = "<<";
583 opReplacements["SHR64"] = ">>";
584 opReplacements["SHRU64"] = ">>";
585 opReplacements["AND64"] = "&";
586 opReplacements["IOR64"] = "|";
587 opReplacements["XOR64"] = "^";
588 opReplacements["NEG64"] = "-";
589 opReplacements["NEGF64"] = "-";
590 opReplacements["ADDF64"] = "+";
591 opReplacements["SUBF64"] = "-";
592 opReplacements["MULF64"] = "*";
593 opReplacements["DIVF64"] = "/";
594 opReplacements["EQF64"] = "==";
595 opReplacements["GTF64"] = ">";
596 opReplacements["CFI64"] = "(UIntWord)";
597 opReplacements["CIF64"] = "(FloatWord)";
598 opReplacements["CFD64"] = "(DoubleWord)";
599 opReplacements["CDF64"] = "(FloatWord)";
600 opReplacements["MOD64"] = "%";
601 opReplacements["MODU64"] = "%";
602
603 // Writes recursively all the DAG from node(0)
604 // But this recursion does not work so added the loop
605 // to make sure all nodes get processed.
606 OperationDAGNode* node;
607 for (int i = 0; i < dag.nodeCount(); i++) {
608 node = &dag.node(i);
609 if (alreadyHandled.find(node) == alreadyHandled.end()) {
610 writeNode(retVal, dag, *node, varBindings,
611 alreadyHandled, tempVarCount, currentlyHandling,
612 &opReplacements, varReplacements);
613 }
614 }
615 return retVal;
616}
#define __func__
#define abortWithError(message)
#define assert(condition)
#define POP_CLANG_DIAGS
#define IGNORE_CLANG_WARNING(X)
#define DEBUG_CODE(x)
virtual Node & headNode(const Edge &edge) const
int nodeCount() const
virtual Edge & outEdge(const Node &node, const int index) const
virtual Edge & inEdge(const Node &node, const int index) const
Node & node(const int index) const
virtual int inDegree(const Node &node) const
virtual int outDegree(const Node &node) const
virtual Node & tailNode(const Edge &edge) const
virtual std::string toString() const
static std::string toString(const T &source)
virtual std::string toString() const
Definition GraphNode.cc:61
virtual OperandType type() const
Definition Operand.cc:165
OperandType
Definition Operand.hh:58
@ SLONG_WORD
Definition Operand.hh:66
@ FLOAT_WORD
Definition Operand.hh:61
@ ULONG_WORD
Definition Operand.hh:67
@ SINT_WORD
Definition Operand.hh:59
@ DOUBLE_WORD
Definition Operand.hh:62
@ UINT_WORD
Definition Operand.hh:60
@ HALF_FLOAT_WORD
Definition Operand.hh:63
static bool writeNode(std::string &retVal, const OperationDAG &dag, const OperationDAGNode &node, std::map< VariableKey, std::string > &varBindings, std::set< const OperationDAGNode * > &alreadyHandled, int &tempVarCount, std::set< const OperationDAGNode * > &currentlyHandling, std::map< std::string, std::string > *opReplace=NULL, std::vector< std::string > *varReplacements=NULL)
std::pair< const OperationDAGNode *, int > VariableKey
static std::string createOsalCode(const OperationDAG &dag)
static OperationDAG * createDAG(const OperationPimpl &operation, std::string sourceCode)
static std::string castedVar(std::string var, Operand::OperandType type)
static std::string createSimulationCode(const OperationDAG &dag, std::vector< std::string > *varReplacements=NULL)
const class OperationPimpl & operation() const
int stepsToRoot(const OperationDAGNode &node) const
Operation & referencedOperation() const
Operand & operand(int id) const
virtual TCEString name() const
Definition Operation.cc:93
virtual int numberOfInputs() const
Definition Operation.cc:192
virtual int numberOfOutputs() const
Definition Operation.cc:202
virtual Operand & operand(int id) const
Definition Operation.cc:541
virtual int operandIndex() const
const TokenTreeNode * tokenTree() const
std::vector< std::pair< const char *, const char * > > strippedParts