1//
2// Copyright (c) 2002-2014 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6
7//
8// Definition of the in-memory high-level intermediate representation
9// of shaders. This is a tree that parser creates.
10//
11// Nodes in the tree are defined as a hierarchy of classes derived from
12// TIntermNode. Each is a node in a tree. There is no preset branching factor;
13// each node can have it's own type of list of children.
14//
15
16#ifndef COMPILER_TRANSLATOR_INTERMNODE_H_
17#define COMPILER_TRANSLATOR_INTERMNODE_H_
18
19#include "GLSLANG/ShaderLang.h"
20
21#include <algorithm>
22#include <queue>
23
24#include "common/angleutils.h"
25#include "compiler/translator/Common.h"
26#include "compiler/translator/ConstantUnion.h"
27#include "compiler/translator/Operator.h"
28#include "compiler/translator/Types.h"
29
30class TIntermTraverser;
31class TIntermAggregate;
32class TIntermBinary;
33class TIntermUnary;
34class TIntermConstantUnion;
35class TIntermSelection;
36class TIntermSwitch;
37class TIntermCase;
38class TIntermTyped;
39class TIntermSymbol;
40class TIntermLoop;
41class TInfoSink;
42class TInfoSinkBase;
43class TIntermRaw;
44
45//
46// Base class for the tree nodes
47//
48class TIntermNode
49{
50 public:
51 POOL_ALLOCATOR_NEW_DELETE();
52 TIntermNode()
53 {
54 // TODO: Move this to TSourceLoc constructor
55 // after getting rid of TPublicType.
56 mLine.first_file = mLine.last_file = 0;
57 mLine.first_line = mLine.last_line = 0;
58 }
59 virtual ~TIntermNode() { }
60
61 const TSourceLoc &getLine() const { return mLine; }
62 void setLine(const TSourceLoc &l) { mLine = l; }
63
64 virtual void traverse(TIntermTraverser *) = 0;
65 virtual TIntermTyped *getAsTyped() { return 0; }
66 virtual TIntermConstantUnion *getAsConstantUnion() { return 0; }
67 virtual TIntermAggregate *getAsAggregate() { return 0; }
68 virtual TIntermBinary *getAsBinaryNode() { return 0; }
69 virtual TIntermUnary *getAsUnaryNode() { return 0; }
70 virtual TIntermSelection *getAsSelectionNode() { return 0; }
71 virtual TIntermSwitch *getAsSwitchNode() { return 0; }
72 virtual TIntermCase *getAsCaseNode() { return 0; }
73 virtual TIntermSymbol *getAsSymbolNode() { return 0; }
74 virtual TIntermLoop *getAsLoopNode() { return 0; }
75 virtual TIntermRaw *getAsRawNode() { return 0; }
76
77 // Replace a child node. Return true if |original| is a child
78 // node and it is replaced; otherwise, return false.
79 virtual bool replaceChildNode(
80 TIntermNode *original, TIntermNode *replacement) = 0;
81
82 protected:
83 TSourceLoc mLine;
84};
85
86//
87// This is just to help yacc.
88//
89struct TIntermNodePair
90{
91 TIntermNode *node1;
92 TIntermNode *node2;
93};
94
95//
96// Intermediate class for nodes that have a type.
97//
98class TIntermTyped : public TIntermNode
99{
100 public:
101 TIntermTyped(const TType &t) : mType(t) { }
102 virtual TIntermTyped *getAsTyped() { return this; }
103
104 virtual bool hasSideEffects() const = 0;
105
106 void setType(const TType &t) { mType = t; }
107 void setTypePreservePrecision(const TType &t);
108 const TType &getType() const { return mType; }
109 TType *getTypePointer() { return &mType; }
110
111 TBasicType getBasicType() const { return mType.getBasicType(); }
112 TQualifier getQualifier() const { return mType.getQualifier(); }
113 TPrecision getPrecision() const { return mType.getPrecision(); }
114 int getCols() const { return mType.getCols(); }
115 int getRows() const { return mType.getRows(); }
116 int getNominalSize() const { return mType.getNominalSize(); }
117 int getSecondarySize() const { return mType.getSecondarySize(); }
118
119 bool isInterfaceBlock() const { return mType.isInterfaceBlock(); }
120 bool isMatrix() const { return mType.isMatrix(); }
121 bool isArray() const { return mType.isArray(); }
122 bool isVector() const { return mType.isVector(); }
123 bool isScalar() const { return mType.isScalar(); }
124 bool isScalarInt() const { return mType.isScalarInt(); }
125 const char *getBasicString() const { return mType.getBasicString(); }
126 const char *getQualifierString() const { return mType.getQualifierString(); }
127 TString getCompleteString() const { return mType.getCompleteString(); }
128
129 int getArraySize() const { return mType.getArraySize(); }
130
131 protected:
132 TType mType;
133};
134
135//
136// Handle for, do-while, and while loops.
137//
138enum TLoopType
139{
140 ELoopFor,
141 ELoopWhile,
142 ELoopDoWhile
143};
144
145class TIntermLoop : public TIntermNode
146{
147 public:
148 TIntermLoop(TLoopType type,
149 TIntermNode *init, TIntermTyped *cond, TIntermTyped *expr,
150 TIntermNode *body)
151 : mType(type),
152 mInit(init),
153 mCond(cond),
154 mExpr(expr),
155 mBody(body),
156 mUnrollFlag(false) { }
157
158 virtual TIntermLoop *getAsLoopNode() { return this; }
159 virtual void traverse(TIntermTraverser *);
160 virtual bool replaceChildNode(
161 TIntermNode *original, TIntermNode *replacement);
162
163 TLoopType getType() const { return mType; }
164 TIntermNode *getInit() { return mInit; }
165 TIntermTyped *getCondition() { return mCond; }
166 TIntermTyped *getExpression() { return mExpr; }
167 TIntermNode *getBody() { return mBody; }
168
169 void setUnrollFlag(bool flag) { mUnrollFlag = flag; }
170 bool getUnrollFlag() const { return mUnrollFlag; }
171
172 protected:
173 TLoopType mType;
174 TIntermNode *mInit; // for-loop initialization
175 TIntermTyped *mCond; // loop exit condition
176 TIntermTyped *mExpr; // for-loop expression
177 TIntermNode *mBody; // loop body
178
179 bool mUnrollFlag; // Whether the loop should be unrolled or not.
180};
181
182//
183// Handle break, continue, return, and kill.
184//
185class TIntermBranch : public TIntermNode
186{
187 public:
188 TIntermBranch(TOperator op, TIntermTyped *e)
189 : mFlowOp(op),
190 mExpression(e) { }
191
192 virtual void traverse(TIntermTraverser *);
193 virtual bool replaceChildNode(
194 TIntermNode *original, TIntermNode *replacement);
195
196 TOperator getFlowOp() { return mFlowOp; }
197 TIntermTyped* getExpression() { return mExpression; }
198
199protected:
200 TOperator mFlowOp;
201 TIntermTyped *mExpression; // non-zero except for "return exp;" statements
202};
203
204//
205// Nodes that correspond to symbols or constants in the source code.
206//
207class TIntermSymbol : public TIntermTyped
208{
209 public:
210 // if symbol is initialized as symbol(sym), the memory comes from the poolallocator of sym.
211 // If sym comes from per process globalpoolallocator, then it causes increased memory usage
212 // per compile it is essential to use "symbol = sym" to assign to symbol
213 TIntermSymbol(int id, const TString &symbol, const TType &type)
214 : TIntermTyped(type),
215 mId(id),
216 mInternal(false)
217 {
218 mSymbol = symbol;
219 }
220
221 virtual bool hasSideEffects() const { return false; }
222
223 int getId() const { return mId; }
224 const TString &getSymbol() const { return mSymbol; }
225
226 void setId(int newId) { mId = newId; }
227
228 bool isInternal() const { return mInternal; }
229 void setInternal(bool isInternal) { mInternal = isInternal; }
230
231 virtual void traverse(TIntermTraverser *);
232 virtual TIntermSymbol *getAsSymbolNode() { return this; }
233 virtual bool replaceChildNode(TIntermNode *, TIntermNode *) { return false; }
234
235 protected:
236 int mId;
237 bool mInternal;
238 TString mSymbol;
239};
240
241// A Raw node stores raw code, that the translator will insert verbatim
242// into the output stream. Useful for transformation operations that make
243// complex code that might not fit naturally into the GLSL model.
244class TIntermRaw : public TIntermTyped
245{
246 public:
247 TIntermRaw(const TType &type, const TString &rawText)
248 : TIntermTyped(type),
249 mRawText(rawText) { }
250
251 virtual bool hasSideEffects() const { return false; }
252
253 TString getRawText() const { return mRawText; }
254
255 virtual void traverse(TIntermTraverser *);
256
257 virtual TIntermRaw *getAsRawNode() { return this; }
258 virtual bool replaceChildNode(TIntermNode *, TIntermNode *) { return false; }
259
260 protected:
261 TString mRawText;
262};
263
264class TIntermConstantUnion : public TIntermTyped
265{
266 public:
267 TIntermConstantUnion(TConstantUnion *unionPointer, const TType &type)
268 : TIntermTyped(type),
269 mUnionArrayPointer(unionPointer) { }
270
271 virtual bool hasSideEffects() const { return false; }
272
273 const TConstantUnion *getUnionArrayPointer() const { return mUnionArrayPointer; }
274 TConstantUnion *getUnionArrayPointer() { return mUnionArrayPointer; }
275
276 int getIConst(size_t index) const
277 {
278 return mUnionArrayPointer ? mUnionArrayPointer[index].getIConst() : 0;
279 }
280 unsigned int getUConst(size_t index) const
281 {
282 return mUnionArrayPointer ? mUnionArrayPointer[index].getUConst() : 0;
283 }
284 float getFConst(size_t index) const
285 {
286 return mUnionArrayPointer ? mUnionArrayPointer[index].getFConst() : 0.0f;
287 }
288 bool getBConst(size_t index) const
289 {
290 return mUnionArrayPointer ? mUnionArrayPointer[index].getBConst() : false;
291 }
292
293 void replaceConstantUnion(TConstantUnion *safeConstantUnion)
294 {
295 // Previous union pointer freed on pool deallocation.
296 mUnionArrayPointer = safeConstantUnion;
297 }
298
299 virtual TIntermConstantUnion *getAsConstantUnion() { return this; }
300 virtual void traverse(TIntermTraverser *);
301 virtual bool replaceChildNode(TIntermNode *, TIntermNode *) { return false; }
302
303 TIntermTyped *fold(TOperator op, TIntermConstantUnion *rightNode, TInfoSink &infoSink);
304
305 protected:
306 TConstantUnion *mUnionArrayPointer;
307
308 private:
309 typedef float(*FloatTypeUnaryFunc) (float);
310 bool foldFloatTypeUnary(const TConstantUnion &parameter, FloatTypeUnaryFunc builtinFunc, TInfoSink &infoSink, TConstantUnion *result) const;
311};
312
313//
314// Intermediate class for node types that hold operators.
315//
316class TIntermOperator : public TIntermTyped
317{
318 public:
319 TOperator getOp() const { return mOp; }
320 void setOp(TOperator op) { mOp = op; }
321
322 bool isAssignment() const;
323 bool isConstructor() const;
324
325 virtual bool hasSideEffects() const { return isAssignment(); }
326
327 protected:
328 TIntermOperator(TOperator op)
329 : TIntermTyped(TType(EbtFloat, EbpUndefined)),
330 mOp(op) {}
331 TIntermOperator(TOperator op, const TType &type)
332 : TIntermTyped(type),
333 mOp(op) {}
334
335 TOperator mOp;
336};
337
338//
339// Nodes for all the basic binary math operators.
340//
341class TIntermBinary : public TIntermOperator
342{
343 public:
344 TIntermBinary(TOperator op)
345 : TIntermOperator(op),
346 mAddIndexClamp(false) {}
347
348 virtual TIntermBinary *getAsBinaryNode() { return this; }
349 virtual void traverse(TIntermTraverser *);
350 virtual bool replaceChildNode(
351 TIntermNode *original, TIntermNode *replacement);
352
353 virtual bool hasSideEffects() const
354 {
355 return isAssignment() || mLeft->hasSideEffects() || mRight->hasSideEffects();
356 }
357
358 void setLeft(TIntermTyped *node) { mLeft = node; }
359 void setRight(TIntermTyped *node) { mRight = node; }
360 TIntermTyped *getLeft() const { return mLeft; }
361 TIntermTyped *getRight() const { return mRight; }
362 bool promote(TInfoSink &);
363
364 void setAddIndexClamp() { mAddIndexClamp = true; }
365 bool getAddIndexClamp() { return mAddIndexClamp; }
366
367 protected:
368 TIntermTyped* mLeft;
369 TIntermTyped* mRight;
370
371 // If set to true, wrap any EOpIndexIndirect with a clamp to bounds.
372 bool mAddIndexClamp;
373};
374
375//
376// Nodes for unary math operators.
377//
378class TIntermUnary : public TIntermOperator
379{
380 public:
381 TIntermUnary(TOperator op, const TType &type)
382 : TIntermOperator(op, type),
383 mOperand(NULL),
384 mUseEmulatedFunction(false) {}
385 TIntermUnary(TOperator op)
386 : TIntermOperator(op),
387 mOperand(NULL),
388 mUseEmulatedFunction(false) {}
389
390 virtual void traverse(TIntermTraverser *);
391 virtual TIntermUnary *getAsUnaryNode() { return this; }
392 virtual bool replaceChildNode(
393 TIntermNode *original, TIntermNode *replacement);
394
395 virtual bool hasSideEffects() const
396 {
397 return isAssignment() || mOperand->hasSideEffects();
398 }
399
400 void setOperand(TIntermTyped *operand) { mOperand = operand; }
401 TIntermTyped *getOperand() { return mOperand; }
402 void promote(const TType *funcReturnType);
403
404 void setUseEmulatedFunction() { mUseEmulatedFunction = true; }
405 bool getUseEmulatedFunction() { return mUseEmulatedFunction; }
406
407 protected:
408 TIntermTyped *mOperand;
409
410 // If set to true, replace the built-in function call with an emulated one
411 // to work around driver bugs.
412 bool mUseEmulatedFunction;
413};
414
415typedef TVector<TIntermNode *> TIntermSequence;
416typedef TVector<int> TQualifierList;
417
418//
419// Nodes that operate on an arbitrary sized set of children.
420//
421class TIntermAggregate : public TIntermOperator
422{
423 public:
424 TIntermAggregate()
425 : TIntermOperator(EOpNull),
426 mUserDefined(false),
427 mUseEmulatedFunction(false) { }
428 TIntermAggregate(TOperator op)
429 : TIntermOperator(op),
430 mUseEmulatedFunction(false) { }
431 ~TIntermAggregate() { }
432
433 virtual TIntermAggregate *getAsAggregate() { return this; }
434 virtual void traverse(TIntermTraverser *);
435 virtual bool replaceChildNode(
436 TIntermNode *original, TIntermNode *replacement);
437 bool replaceChildNodeWithMultiple(TIntermNode *original, TIntermSequence replacements);
438 // Conservatively assume function calls and other aggregate operators have side-effects
439 virtual bool hasSideEffects() const { return true; }
440
441 TIntermSequence *getSequence() { return &mSequence; }
442
443 void setName(const TString &name) { mName = name; }
444 const TString &getName() const { return mName; }
445
446 void setUserDefined() { mUserDefined = true; }
447 bool isUserDefined() const { return mUserDefined; }
448
449 void setOptimize(bool optimize) { mOptimize = optimize; }
450 bool getOptimize() const { return mOptimize; }
451 void setDebug(bool debug) { mDebug = debug; }
452 bool getDebug() const { return mDebug; }
453
454 void setFunctionId(int functionId) { mFunctionId = functionId; }
455 int getFunctionId() const { return mFunctionId; }
456
457 void setUseEmulatedFunction() { mUseEmulatedFunction = true; }
458 bool getUseEmulatedFunction() { return mUseEmulatedFunction; }
459
460 void setPrecisionFromChildren();
461 void setBuiltInFunctionPrecision();
462
463 protected:
464 TIntermAggregate(const TIntermAggregate &); // disallow copy constructor
465 TIntermAggregate &operator=(const TIntermAggregate &); // disallow assignment operator
466 TIntermSequence mSequence;
467 TString mName;
468 bool mUserDefined; // used for user defined function names
469 int mFunctionId;
470
471 bool mOptimize;
472 bool mDebug;
473
474 // If set to true, replace the built-in function call with an emulated one
475 // to work around driver bugs.
476 bool mUseEmulatedFunction;
477};
478
479//
480// For if tests.
481//
482class TIntermSelection : public TIntermTyped
483{
484 public:
485 TIntermSelection(TIntermTyped *cond, TIntermNode *trueB, TIntermNode *falseB)
486 : TIntermTyped(TType(EbtVoid, EbpUndefined)),
487 mCondition(cond),
488 mTrueBlock(trueB),
489 mFalseBlock(falseB) {}
490 TIntermSelection(TIntermTyped *cond, TIntermNode *trueB, TIntermNode *falseB,
491 const TType &type)
492 : TIntermTyped(type),
493 mCondition(cond),
494 mTrueBlock(trueB),
495 mFalseBlock(falseB) {}
496
497 virtual void traverse(TIntermTraverser *);
498 virtual bool replaceChildNode(
499 TIntermNode *original, TIntermNode *replacement);
500
501 // Conservatively assume selections have side-effects
502 virtual bool hasSideEffects() const { return true; }
503
504 bool usesTernaryOperator() const { return getBasicType() != EbtVoid; }
505 TIntermNode *getCondition() const { return mCondition; }
506 TIntermNode *getTrueBlock() const { return mTrueBlock; }
507 TIntermNode *getFalseBlock() const { return mFalseBlock; }
508 TIntermSelection *getAsSelectionNode() { return this; }
509
510protected:
511 TIntermTyped *mCondition;
512 TIntermNode *mTrueBlock;
513 TIntermNode *mFalseBlock;
514};
515
516//
517// Switch statement.
518//
519class TIntermSwitch : public TIntermNode
520{
521 public:
522 TIntermSwitch(TIntermTyped *init, TIntermAggregate *statementList)
523 : TIntermNode(),
524 mInit(init),
525 mStatementList(statementList)
526 {
527 }
528
529 void traverse(TIntermTraverser *it) override;
530 bool replaceChildNode(
531 TIntermNode *original, TIntermNode *replacement) override;
532
533 TIntermSwitch *getAsSwitchNode() override { return this; }
534
535 TIntermAggregate *getStatementList() { return mStatementList; }
536 void setStatementList(TIntermAggregate *statementList) { mStatementList = statementList; }
537
538 protected:
539 TIntermTyped *mInit;
540 TIntermAggregate *mStatementList;
541};
542
543//
544// Case label.
545//
546class TIntermCase : public TIntermNode
547{
548 public:
549 TIntermCase(TIntermTyped *condition)
550 : TIntermNode(),
551 mCondition(condition)
552 {
553 }
554
555 void traverse(TIntermTraverser *it) override;
556 bool replaceChildNode(
557 TIntermNode *original, TIntermNode *replacement) override;
558
559 TIntermCase *getAsCaseNode() override { return this; }
560
561 bool hasCondition() const { return mCondition != nullptr; }
562 TIntermTyped *getCondition() const { return mCondition; }
563
564 protected:
565 TIntermTyped *mCondition;
566};
567
568enum Visit
569{
570 PreVisit,
571 InVisit,
572 PostVisit
573};
574
575//
576// For traversing the tree. User should derive from this,
577// put their traversal specific data in it, and then pass
578// it to a Traverse method.
579//
580// When using this, just fill in the methods for nodes you want visited.
581// Return false from a pre-visit to skip visiting that node's subtree.
582//
583class TIntermTraverser : angle::NonCopyable
584{
585 public:
586 POOL_ALLOCATOR_NEW_DELETE();
587 // TODO(zmo): remove default values.
588 TIntermTraverser(bool preVisit = true, bool inVisit = false, bool postVisit = false,
589 bool rightToLeft = false)
590 : preVisit(preVisit),
591 inVisit(inVisit),
592 postVisit(postVisit),
593 rightToLeft(rightToLeft),
594 mDepth(0),
595 mMaxDepth(0) {}
596 virtual ~TIntermTraverser() {}
597
598 virtual void visitSymbol(TIntermSymbol *) {}
599 virtual void visitRaw(TIntermRaw *) {}
600 virtual void visitConstantUnion(TIntermConstantUnion *) {}
601 virtual bool visitBinary(Visit, TIntermBinary *) { return true; }
602 virtual bool visitUnary(Visit, TIntermUnary *) { return true; }
603 virtual bool visitSelection(Visit, TIntermSelection *) { return true; }
604 virtual bool visitSwitch(Visit, TIntermSwitch *) { return true; }
605 virtual bool visitCase(Visit, TIntermCase *) { return true; }
606 virtual bool visitAggregate(Visit, TIntermAggregate *) { return true; }
607 virtual bool visitLoop(Visit, TIntermLoop *) { return true; }
608 virtual bool visitBranch(Visit, TIntermBranch *) { return true; }
609
610 int getMaxDepth() const { return mMaxDepth; }
611
612 void incrementDepth(TIntermNode *current)
613 {
614 mDepth++;
615 mMaxDepth = std::max(mMaxDepth, mDepth);
616 mPath.push_back(current);
617 }
618
619 void decrementDepth()
620 {
621 mDepth--;
622 mPath.pop_back();
623 }
624
625 TIntermNode *getParentNode()
626 {
627 return mPath.size() == 0 ? NULL : mPath.back();
628 }
629
630 // Return the original name if hash function pointer is NULL;
631 // otherwise return the hashed name.
632 static TString hash(const TString& name, ShHashFunction64 hashFunction);
633
634 const bool preVisit;
635 const bool inVisit;
636 const bool postVisit;
637 const bool rightToLeft;
638
639 // If traversers need to replace nodes, they can add the replacements in
640 // mReplacements/mMultiReplacements during traversal and the user of the traverser should call
641 // this function after traversal to perform them.
642 void updateTree();
643
644 protected:
645 int mDepth;
646 int mMaxDepth;
647
648 // All the nodes from root to the current node's parent during traversing.
649 TVector<TIntermNode *> mPath;
650
651 // To replace a single node with another on the parent node
652 struct NodeUpdateEntry
653 {
654 NodeUpdateEntry(TIntermNode *_parent,
655 TIntermNode *_original,
656 TIntermNode *_replacement,
657 bool _originalBecomesChildOfReplacement)
658 : parent(_parent),
659 original(_original),
660 replacement(_replacement),
661 originalBecomesChildOfReplacement(_originalBecomesChildOfReplacement) {}
662
663 TIntermNode *parent;
664 TIntermNode *original;
665 TIntermNode *replacement;
666 bool originalBecomesChildOfReplacement;
667 };
668
669 // To replace a single node with multiple nodes on the parent aggregate node
670 struct NodeReplaceWithMultipleEntry
671 {
672 NodeReplaceWithMultipleEntry(TIntermAggregate *_parent, TIntermNode *_original, TIntermSequence _replacements)
673 : parent(_parent),
674 original(_original),
675 replacements(_replacements)
676 {
677 }
678
679 TIntermAggregate *parent;
680 TIntermNode *original;
681 TIntermSequence replacements;
682 };
683
684 // During traversing, save all the changes that need to happen into
685 // mReplacements/mMultiReplacements, then do them by calling updateTree().
686 // Multi replacements are processed after single replacements.
687 std::vector<NodeUpdateEntry> mReplacements;
688 std::vector<NodeReplaceWithMultipleEntry> mMultiReplacements;
689};
690
691//
692// For traversing the tree, and computing max depth.
693// Takes a maximum depth limit to prevent stack overflow.
694//
695class TMaxDepthTraverser : public TIntermTraverser
696{
697 public:
698 POOL_ALLOCATOR_NEW_DELETE();
699 TMaxDepthTraverser(int depthLimit)
700 : TIntermTraverser(true, true, false, false),
701 mDepthLimit(depthLimit) { }
702
703 virtual bool visitBinary(Visit, TIntermBinary *) { return depthCheck(); }
704 virtual bool visitUnary(Visit, TIntermUnary *) { return depthCheck(); }
705 virtual bool visitSelection(Visit, TIntermSelection *) { return depthCheck(); }
706 virtual bool visitAggregate(Visit, TIntermAggregate *) { return depthCheck(); }
707 virtual bool visitLoop(Visit, TIntermLoop *) { return depthCheck(); }
708 virtual bool visitBranch(Visit, TIntermBranch *) { return depthCheck(); }
709
710 protected:
711 bool depthCheck() const { return mMaxDepth < mDepthLimit; }
712
713 int mDepthLimit;
714};
715
716#endif // COMPILER_TRANSLATOR_INTERMNODE_H_
717