source: icGREP/icgrep-devel/icgrep/pablo/optimizers/pablo_simplifier.cpp @ 5782

Last change on this file since 5782 was 5782, checked in by nmedfort, 16 months ago

Initial check-in of LookAhead? support; modified LineBreakKernel? to compute CR+LF using LookAhead?(1) + misc. fixes.

File size: 24.1 KB
Line 
1#include <pablo/optimizers/pablo_simplifier.hpp>
2#include <pablo/pablo_kernel.h>
3#include <pablo/codegenstate.h>
4#include <pablo/expression_map.hpp>
5#include <pablo/boolean.h>
6#include <pablo/pe_zeroes.h>
7#include <pablo/pe_ones.h>
8#include <pablo/arithmetic.h>
9#include <pablo/branch.h>
10#include <pablo/ps_assign.h>
11#include <pablo/pe_advance.h>
12#include <pablo/pe_lookahead.h>
13#include <pablo/pe_scanthru.h>
14#include <pablo/pe_matchstar.h>
15#include <pablo/pe_var.h>
16#ifndef NDEBUG
17#include <pablo/analysis/pabloverifier.hpp>
18#endif
19#include <boost/container/flat_set.hpp>
20#include <llvm/IR/Type.h>
21
22using namespace boost;
23using namespace boost::container;
24using namespace llvm;
25
26namespace pablo {
27
28using TypeId = PabloAST::ClassTypeId;
29
30using ScopeMap = flat_map<PabloBlock *, unsigned>;
31
32/** ------------------------------------------------------------------------------------------------------------- *
33 * @brief VariableTable
34 ** ------------------------------------------------------------------------------------------------------------- */
35struct VariableTable {
36
37    VariableTable(VariableTable * predecessor = nullptr)
38    : mPredecessor(predecessor) {
39
40    }
41
42    PabloAST * get(PabloAST * const var) const {
43        const auto f = mMap.find(var);
44        if (f == mMap.end()) {
45            return (mPredecessor) ? mPredecessor->get(var) : nullptr;
46        }
47        return f->second;
48    }
49
50    void put(PabloAST * const var, PabloAST * value) {
51        const auto f = mMap.find(var);
52        if (LLVM_LIKELY(f == mMap.end())) {
53            mMap.emplace(var, value);
54        } else {
55            f->second = value;
56        }
57        assert (get(var) == value);
58    }
59
60private:
61    VariableTable * const mPredecessor;
62    flat_map<PabloAST *, PabloAST *> mMap;
63};
64
65struct PassContainer {
66
67/** ------------------------------------------------------------------------------------------------------------- *
68 * @brief run
69 ** ------------------------------------------------------------------------------------------------------------- */
70void run(PabloKernel * const kernel) {
71    redundancyElimination(kernel->getEntryBlock(), nullptr, nullptr);
72    strengthReduction(kernel->getEntryBlock());
73    deadCodeElimination(kernel->getEntryBlock());
74}
75
76protected:
77
78/** ------------------------------------------------------------------------------------------------------------- *
79 * @brief redundancyElimination
80 *
81 * Note: Do not recursively delete statements in this function. The ExpressionTable could use deleted statements
82 * as replacements. Let the DCE remove the unnecessary statements with the finalized Def-Use information.
83 ** ------------------------------------------------------------------------------------------------------------- */
84void redundancyElimination(PabloBlock * const block, ExpressionTable * const et, VariableTable * const vt) {
85    ExpressionTable expressions(et);
86    VariableTable variables(vt);
87
88    if (Branch * br = block->getBranch()) {
89        assert ("block has a branch but the expression and variable tables were not supplied" && et && vt);
90        for (Var * var : br->getEscaped()) {
91            variables.put(var, var);
92        }
93    }
94
95    mInScope.push_back(block);
96
97    const auto baseNonZeroEntries = mNonZero.size();
98    Statement * stmt = block->front();
99    while (stmt) {
100
101        if (LLVM_UNLIKELY(isa<Assign>(stmt))) {
102            Assign * const assign = cast<Assign>(stmt);
103            PabloAST * const var = assign->getVariable();
104            PabloAST * value = assign->getValue();
105            if (LLVM_UNLIKELY(var == value)) {
106                stmt = stmt->eraseFromParent();
107                continue;
108            }
109            while (LLVM_UNLIKELY(isa<Var>(value))) {
110                PabloAST * next = variables.get(cast<Var>(value));
111                if (LLVM_LIKELY(next == nullptr || next == value)) {
112                    break;
113                }
114                value = next;
115                assign->setValue(value);
116            }
117            if (LLVM_UNLIKELY(variables.get(var) == value)) {
118                stmt = stmt->eraseFromParent();
119                continue;
120            }
121            variables.put(var, value);
122
123        } else if (LLVM_UNLIKELY(isa<Branch>(stmt))) {
124
125            Branch * const br = cast<Branch>(stmt);
126            PabloAST * cond = br->getCondition();
127            if (isa<Var>(cond)) {
128                PabloAST * const value = variables.get(cast<Var>(cond));
129                if (value) {
130                    cond = value;
131                    if (isa<If>(br)) {
132                        br->setCondition(cond);
133                    }
134                }
135            }
136
137            // Test whether we can ever take this branch
138            if (LLVM_UNLIKELY(isa<Zeroes>(cond))) {
139                stmt = stmt->eraseFromParent();
140                continue;
141            }
142
143            // If we're guaranteed to take this branch, flatten it.
144            if (LLVM_LIKELY(isa<If>(br)) && LLVM_UNLIKELY(isNonZero(cond))) {
145                stmt = flatten(br);
146                continue;
147            }
148
149            // Mark the cond as non-zero prior to processing the inner scope.
150            mNonZero.push_back(cond);
151            // Process the Branch body
152            redundancyElimination(br->getBody(), &expressions, &variables);
153            assert (mNonZero.back() == cond);
154            mNonZero.pop_back();
155
156            if (LLVM_LIKELY(isa<If>(br))) {
157                // Check whether the cost of testing the condition and taking the branch with
158                // 100% correct prediction rate exceeds the cost of the body itself
159                if (LLVM_UNLIKELY(isTrivial(br->getBody()))) {
160                    stmt = flatten(br);
161                    continue;
162                }
163            }
164
165        } else {
166
167            // demote any uses of any Var whose value is in scope
168            for (unsigned i = 0; i < stmt->getNumOperands(); ++i) {
169                PabloAST * op = stmt->getOperand(i);
170                if (LLVM_UNLIKELY(isa<Var>(op))) {
171                    PabloAST * const value = variables.get(cast<Var>(op));
172                    if (value && value != op) {
173                        stmt->setOperand(i, value);
174                    }
175                }
176            }
177
178            PabloAST * const folded = triviallyFold(stmt, block);
179            if (folded) {
180                Statement * const prior = stmt->getPrevNode();
181                stmt->replaceWith(folded);
182                stmt = prior ? prior->getNextNode() : block->front();
183                continue;
184            }
185
186            // By recording which statements have already been seen, we can detect the redundant statements
187            // as any having the same type and operands. If so, we can replace its users with the prior statement.
188            // and erase this statement from the AST
189            const auto f = expressions.findOrAdd(stmt);
190            if (!f.second) {
191                stmt = stmt->replaceWith(f.first);
192                continue;
193            }
194
195            // Attempt to extend our set of trivially non-zero statements.
196            if (isa<Or>(stmt)) {
197                for (unsigned i = 0; i < stmt->getNumOperands(); ++i) {
198                    if (LLVM_UNLIKELY(isNonZero(stmt->getOperand(i)))) {
199                        mNonZero.push_back(stmt);
200                        break;
201                    }
202                }
203            } else if (isa<Advance>(stmt)) {
204                const Advance * const adv = cast<Advance>(stmt);
205                if (LLVM_LIKELY(adv->getAmount() < (adv->getType()->getPrimitiveSizeInBits() / 2))) {
206                    if (LLVM_UNLIKELY(isNonZero(adv->getExpression()))) {
207                        mNonZero.push_back(adv);
208                    }
209                }
210            }
211        }
212
213        stmt = stmt->getNextNode();
214    }
215
216    // Erase any local non-zero entries that were discovered while processing this scope
217    mNonZero.erase(mNonZero.begin() + baseNonZeroEntries, mNonZero.end());
218
219    assert (mInScope.back() == block);
220    mInScope.pop_back();
221
222    // If this block has a branch statement leading into it, we can verify whether an escaped value
223    // was updated within this block and update the preceeding block's variable state appropriately.
224
225    Branch * const br = block->getBranch();
226    if (LLVM_LIKELY(br != nullptr)) {
227
228        // When removing identical escaped values, we have to consider that the identical Vars could
229        // be assigned new differing values later in the outer body. Thus instead of replacing them
230        // directly, we map future uses of the duplicate Var to the initial one. The DCE pass will
231        // later mark any Assign statement as dead if the Var is never read.
232
233        const auto escaped = br->getEscaped();
234        const auto n = escaped.size();
235        PabloAST * variable[n];
236        PabloAST * incoming[n];
237        PabloAST * outgoing[n];
238        for (unsigned i = 0; i < n; ++i) {
239            variable[i] = escaped[i];
240            incoming[i] = vt->get(variable[i]);
241            outgoing[i] = variables.get(variable[i]);
242            if (LLVM_UNLIKELY(incoming[i] == outgoing[i])) {
243                variable[i] = incoming[i];
244            } else {
245                for (unsigned j = 0; j < i; ++j) {
246                    if (LLVM_UNLIKELY((outgoing[j] == outgoing[i]) && (incoming[j] == incoming[i]))) {
247                        variable[i] = variable[j];
248                        break;
249                    }
250                }
251            }
252            vt->put(escaped[i], variable[i]);
253        }
254
255    }
256
257}
258
259
260/** ------------------------------------------------------------------------------------------------------------- *
261 * @brief fold
262 ** ------------------------------------------------------------------------------------------------------------- */
263static PabloAST * triviallyFold(Statement * stmt, PabloBlock * const block) {
264    if (isa<Not>(stmt)) {
265        PabloAST * value = stmt->getOperand(0);
266        if (LLVM_UNLIKELY(isa<Not>(value))) {
267            return cast<Not>(value)->getOperand(0); // ¬¬A ⇔ A
268        } else if (LLVM_UNLIKELY(isa<Zeroes>(value))) {
269            return block->createOnes(stmt->getType()); // ¬0 ⇔ 1
270        }  else if (LLVM_UNLIKELY(isa<Ones>(value))) {
271            return block->createZeroes(stmt->getType()); // ¬1 ⇔ 0
272        }
273    } else if (isa<Variadic>(stmt)) {
274        std::sort(cast<Variadic>(stmt)->begin(), cast<Variadic>(stmt)->end());
275        for (unsigned i = 1; i < stmt->getNumOperands(); ) {
276            if (LLVM_UNLIKELY(stmt->getOperand(i - 1) == stmt->getOperand(i))) {
277                if (LLVM_UNLIKELY(isa<Xor>(stmt))) {
278                    if (LLVM_LIKELY(stmt->getNumOperands() == 2)) {
279                        return block->createZeroes(stmt->getType());
280                    } else {
281                        cast<Variadic>(stmt)->removeOperand(i);
282                        cast<Variadic>(stmt)->removeOperand(i - 1);
283                        continue;
284                    }
285                } else {
286                    if (LLVM_LIKELY(stmt->getNumOperands() == 2)) {
287                        return stmt->getOperand(1 - i);
288                    } else {
289                        cast<Variadic>(stmt)->removeOperand(i);
290                        continue;
291                    }
292                }
293            }
294            ++i;
295        }
296        if (LLVM_UNLIKELY(stmt->getNumOperands() < 2)) {
297            if (LLVM_LIKELY(stmt->getNumOperands() == 1)) {
298                return stmt->getOperand(0);
299            } else {
300                return block->createZeroes(stmt->getType());
301            }
302        }
303        if (LLVM_UNLIKELY(isa<Xor>(stmt))) {
304            bool negated = false;
305            PabloAST * expr = nullptr;
306            for (unsigned i = 0; i < stmt->getNumOperands(); ) {
307                const PabloAST * const op = stmt->getOperand(i);
308                if (isa<Not>(op)) {
309                    negated ^= true;
310                    stmt->setOperand(i, cast<Not>(op)->getExpr());
311                } else if (LLVM_UNLIKELY(isa<Zeroes>(op) || isa<Ones>(op))) {
312                    negated ^= isa<Ones>(op);
313                    if (LLVM_LIKELY(stmt->getNumOperands() == 2)) {
314                        expr = stmt->getOperand(1 - i);
315                        break;
316                    } else {
317                        cast<Variadic>(stmt)->removeOperand(i);
318                        continue;
319                    }
320                }
321                ++i;
322            }
323            if (LLVM_UNLIKELY(negated)) {
324                block->setInsertPoint(stmt);
325                expr = triviallyFold(block->createNot(expr ? expr : stmt), block);
326            }
327            return expr;
328        } else { // if (isa<And>(stmt) || isa<Or>(stmt))
329            for (unsigned i = 0; i < stmt->getNumOperands(); ++i) {
330                const PabloAST * const op = stmt->getOperand(i);
331                if (LLVM_UNLIKELY(isa<Zeroes>(op) || isa<Ones>(op))) {
332                    if (isa<And>(stmt) ^ isa<Zeroes>(op)) {
333                        if (LLVM_LIKELY(stmt->getNumOperands() == 2)) {
334                            return stmt->getOperand(1 - i);
335                        } else {
336                            cast<Variadic>(stmt)->removeOperand(i);
337                            continue;
338                        }
339                    } else {
340                        return stmt->getOperand(i);
341                    }
342                }
343                ++i;
344            }
345        }
346    } else if (isa<Advance>(stmt)) {
347        Advance * const adv = cast<Advance>(stmt);
348        if (LLVM_UNLIKELY(isa<Zeroes>(adv->getExpression()) || adv->getAmount() == 0)) {
349            return adv->getExpression();
350        }
351    } else if (isa<ScanThru>(stmt)) {
352        ScanThru * const st = cast<ScanThru>(stmt);
353        if (LLVM_UNLIKELY(isa<Zeroes>(st->getScanFrom()) || isa<Zeroes>(st->getScanThru()))) {
354            return st->getScanFrom();
355        } else if (LLVM_UNLIKELY(isa<Ones>(st->getScanThru()))) {
356            block->setInsertPoint(stmt->getPrevNode());
357            return block->createZeroes(stmt->getType());
358        }
359    } else if (isa<MatchStar>(stmt)) {
360        MatchStar * const mstar = cast<MatchStar>(stmt);
361        if (LLVM_UNLIKELY(isa<Zeroes>(mstar->getMarker()) || isa<Zeroes>(mstar->getCharClass()))) {
362            return mstar->getMarker();
363        } else if (LLVM_UNLIKELY(isa<Ones>(mstar->getMarker()))) {
364            block->setInsertPoint(stmt->getPrevNode());
365            return block->createOnes(stmt->getType());
366        }
367    } else if (isa<Lookahead>(stmt)) {
368        Lookahead * const la = cast<Lookahead>(stmt);
369        if (LLVM_UNLIKELY(isa<Zeroes>(la->getExpression()) || la->getAmount() == 0)) {
370            return la->getExpression();
371        }
372    } else if (LLVM_UNLIKELY(isa<Sel>(stmt))) {
373        Sel * const sel = cast<Sel>(stmt);
374        if (LLVM_UNLIKELY(isa<Zeroes>(sel->getCondition()))) {
375            return sel->getFalseExpr();
376        }
377        if (LLVM_UNLIKELY(isa<Ones>(sel->getCondition()))) {
378            return sel->getTrueExpr();
379        }
380        if (LLVM_UNLIKELY(isa<Zeroes>(sel->getTrueExpr()))) {
381            block->setInsertPoint(stmt->getPrevNode());
382            PabloAST * const negCond = triviallyFold(block->createNot(sel->getCondition()), block);
383            return triviallyFold(block->createAnd(sel->getFalseExpr(), negCond), block);
384        }
385        if (LLVM_UNLIKELY(isa<Ones>(sel->getTrueExpr()))) {
386            block->setInsertPoint(stmt->getPrevNode());
387            return triviallyFold(block->createOr(sel->getCondition(), sel->getFalseExpr()), block);
388        }
389        if (LLVM_UNLIKELY(isa<Zeroes>(sel->getFalseExpr()))) {
390            block->setInsertPoint(stmt->getPrevNode());
391            return triviallyFold(block->createAnd(sel->getCondition(), sel->getTrueExpr()), block);
392        }
393        if (LLVM_UNLIKELY(isa<Ones>(sel->getFalseExpr()))) {
394            block->setInsertPoint(stmt->getPrevNode());
395            PabloAST * const negCond = triviallyFold(block->createNot(sel->getCondition()), block);
396            return triviallyFold(block->createOr(sel->getTrueExpr(), negCond), block);
397        }
398    } else if (LLVM_UNLIKELY(isa<Add>(stmt) || isa<Subtract>(stmt))) {
399       if (LLVM_UNLIKELY(isa<Integer>(stmt->getOperand(0)) && isa<Integer>(stmt->getOperand(1)))) {
400           const Integer * const int0 = cast<Integer>(stmt->getOperand(0));
401           const Integer * const int1 = cast<Integer>(stmt->getOperand(1));
402           Integer::IntTy result = 0;
403           if (isa<Add>(stmt)) {
404               result = int0->value() + int1->value();
405           } else {
406               result = int0->value() - int1->value();
407           }
408           return block->getInteger(result);
409       }
410    }
411    return nullptr;
412}
413
414/** ------------------------------------------------------------------------------------------------------------- *
415 * @brief isTrivial
416 *
417 * If this inner block is composed of only Boolean logic and Assign statements and there are fewer than 3
418 * statements, just add the statements in the inner block to the current block
419 ** ------------------------------------------------------------------------------------------------------------- */
420static bool isTrivial(const PabloBlock * const block) {
421    unsigned computations = 0;
422    for (const Statement * stmt : *block) {
423        switch (stmt->getClassTypeId()) {
424            case TypeId::And:
425            case TypeId::Or:
426            case TypeId::Xor:
427                if (++computations > 3) {
428                    return false;
429                }
430            case TypeId::Not:
431            case TypeId::Assign:
432                break;
433            default:
434                return false;
435        }
436    }
437    return true;
438}
439
440/** ------------------------------------------------------------------------------------------------------------- *
441 * @brief flatten
442 ** ------------------------------------------------------------------------------------------------------------- */
443static Statement * flatten(Branch * const br) {
444    Statement * stmt = br;
445    Statement * nested = br->getBody()->front();
446    while (nested) {
447        Statement * next = nested->removeFromParent();
448        nested->insertAfter(stmt);
449        stmt = nested;
450        nested = next;
451    }
452    return br->eraseFromParent();
453}
454
455/** ------------------------------------------------------------------------------------------------------------- *
456 * @brief isNonZero
457 ** ------------------------------------------------------------------------------------------------------------- */
458bool isNonZero(const PabloAST * const expr) const {
459    return isa<Ones>(expr) || std::find(mNonZero.begin(), mNonZero.end(), expr) != mNonZero.end();
460}
461
462/** ------------------------------------------------------------------------------------------------------------- *
463 * @brief strengthReduction
464 *
465 * Find and replace any Pablo operations with a less expensive equivalent operation whenever possible.
466 ** ------------------------------------------------------------------------------------------------------------- */
467void strengthReduction(PabloBlock * const block) {
468
469    Statement * stmt = block->front();
470    while (stmt) {
471        if (isa<Branch>(stmt)) {
472            strengthReduction(cast<Branch>(stmt)->getBody());
473        } else if (isa<Advance>(stmt)) {
474            Advance * adv = cast<Advance>(stmt);
475            if (LLVM_UNLIKELY(isa<Advance>(adv->getOperand(0)))) {
476                // Replace an Advance(Advance(x, n), m) with an Advance(x,n + m)
477                // Test whether this will generate a long advance and abort?
478                Advance * op = cast<Advance>(stmt->getOperand(0));
479                if (LLVM_UNLIKELY(op->getNumUses() == 1)) {
480                    adv->setOperand(0, op->getOperand(0));
481                    adv->setOperand(1, block->getInteger(adv->getAmount() + op->getAmount()));
482                    op->eraseFromParent(false);
483                }
484            }
485        } else if (LLVM_UNLIKELY(isa<ScanThru>(stmt))) {           
486            ScanThru * const outer = cast<ScanThru>(stmt);
487            if (LLVM_UNLIKELY(isa<Advance>(outer->getScanFrom()))) {
488                // Replace ScanThru(Advance(x,n),y) with ScanThru(Advance(x, n - 1), Advance(x, n - 1) | y), where Advance(x, 0) = x               
489                Advance * const inner = cast<Advance>(outer->getScanFrom());
490                if (LLVM_UNLIKELY(inner->getNumUses() == 1)) {
491                    PabloAST * stream = inner->getExpression();
492                    block->setInsertPoint(stmt);
493                    if (LLVM_UNLIKELY(inner->getAmount() != 1)) {
494                        stream = block->createAdvance(stream, block->getInteger(inner->getAmount() - 1));
495                    }
496                    stmt = outer->replaceWith(block->createAdvanceThenScanThru(stream, outer->getScanThru()));
497                    inner->eraseFromParent(false);
498                    continue;
499                }
500            } else if (LLVM_UNLIKELY(isa<ScanThru>(outer->getScanFrom()))) {
501                // Replace ScanThru(ScanThru(x, y), z) with ScanThru(x, y | z)
502                ScanThru * const inner = cast<ScanThru>(outer->getScanFrom());
503                block->setInsertPoint(stmt);
504                ScanThru * const scanThru = block->createScanThru(inner->getScanFrom(), block->createOr(inner->getScanThru(), outer->getScanThru()));
505                stmt->replaceWith(scanThru);
506                stmt = scanThru;
507                continue;
508            } else if (LLVM_UNLIKELY(isa<And>(outer->getScanFrom()))) {
509                // Suppose B is an arbitrary bitstream and A = Advance(B, 1). ScanThru(B ∧ ¬A, B) will leave a marker on the position
510                // following the end of any run of 1-bits in B. But this is equivalent to computing A ∧ ¬B since A will have exactly
511                // one 1-bit past the end of any run of 1-bits in B.
512
513
514
515
516
517            }
518        } else if (LLVM_UNLIKELY(isa<ScanTo>(stmt))) {
519            ScanTo * scanTo = cast<ScanTo>(stmt);
520            if (LLVM_UNLIKELY(isa<Advance>(scanTo->getScanFrom()))) {
521                // Replace a ScanTo(Advance(x,n),y) with an ScanTo(Advance(x, n - 1), Advance(x, n - 1) | y), where Advance(x, 0) = x
522                Advance * adv = cast<Advance>(scanTo->getScanFrom());
523                if (LLVM_UNLIKELY(adv->getNumUses() == 1)) {
524                    PabloAST * stream = adv->getExpression();
525                    block->setInsertPoint(stmt);
526                    if (LLVM_UNLIKELY(adv->getAmount() != 1)) {
527                        stream = block->createAdvance(stream, block->getInteger(adv->getAmount() - 1));
528                    }
529                    stmt = scanTo->replaceWith(block->createAdvanceThenScanTo(stream, scanTo->getScanTo()));
530                    adv->eraseFromParent(false);
531                    continue;
532                }
533            }
534        }
535        stmt = stmt->getNextNode();
536    }
537}
538
539/** ------------------------------------------------------------------------------------------------------------- *
540 * @brief deadCodeElimination
541 ** ------------------------------------------------------------------------------------------------------------- */
542void deadCodeElimination(PabloBlock * const block) {
543
544    flat_set<PabloAST *> written;
545
546    for (Statement * stmt = block->back(), * prior; stmt; stmt = prior) {
547        prior = stmt->getPrevNode();
548        if (LLVM_UNLIKELY(stmt->getNumUses() == 0)) {
549            if (LLVM_UNLIKELY(isa<Branch>(stmt))) {
550                written.clear();
551                deadCodeElimination(cast<Branch>(stmt)->getBody());
552            } else if (LLVM_UNLIKELY(isa<Assign>(stmt))) {
553                // An Assign statement is locally dead whenever its variable is not read
554                // before being reassigned a value.
555                PabloAST * var = cast<Assign>(stmt)->getVariable();
556                if (LLVM_UNLIKELY(!written.insert(var).second)) {
557                    stmt->eraseFromParent();
558                }
559            } else {
560                stmt->eraseFromParent();
561            }
562        }
563    }
564}
565
566std::vector<const PabloAST *>       mNonZero;
567std::vector<const PabloBlock *>     mInScope;
568
569};
570
571/** ------------------------------------------------------------------------------------------------------------- *
572 * @brief optimize
573 ** ------------------------------------------------------------------------------------------------------------- */
574bool Simplifier::optimize(PabloKernel * kernel) {
575    PassContainer pc;
576    pc.run(kernel);
577    #ifndef NDEBUG
578    PabloVerifier::verify(kernel, "post-simplification");
579    #endif
580    return true;
581}
582
583}
Note: See TracBrowser for help on using the repository browser.