Ignore:
Timestamp:
Aug 3, 2016, 4:49:22 PM (3 years ago)
Author:
nmedfort
Message:

Implemented topological sort using Z3.

Location:
icGREP/icgrep-devel/icgrep/pablo/optimizers
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • icGREP/icgrep-devel/icgrep/pablo/optimizers/pablo_automultiplexing.cpp

    r5112 r5113  
    7979bool MultiplexingPass::optimize(PabloFunction & function) {
    8080
    81     if (LLVM_UNLIKELY(Samples < 1)) {
    82         return false;
    83     }
    84 
     81    #ifndef NDEBUG
    8582    PabloVerifier::verify(function, "pre-multiplexing");
     83    #endif
    8684
    8785    Z3_config cfg = Z3_mk_config();
     
    9391    MultiplexingPass mp(function, Seed, ctx, solver);
    9492
    95     mp.characterize(function);
     93    mp.optimize();
    9694
    9795    Z3_solver_dec_ref(ctx, solver);
    9896    Z3_del_context(ctx);
    9997
     98    #ifndef NDEBUG
    10099    PabloVerifier::verify(function, "post-multiplexing");
     100    #endif
     101
     102    Simplifier::optimize(function);
    101103
    102104    return true;
     
    107109 * @param function the function to optimize
    108110 ** ------------------------------------------------------------------------------------------------------------- */
    109 void MultiplexingPass::characterize(PabloFunction & function) {
     111void MultiplexingPass::optimize() {
    110112    // Map the constants and input variables
    111     Z3_sort boolTy = Z3_mk_bool_sort(mContext);
    112 
    113     Z3_ast F = Z3_mk_const(mContext, Z3_mk_int_symbol(mContext, 0), boolTy);
    114     Z3_inc_ref(mContext, F);
    115     add(PabloBlock::createZeroes(), F);
    116 
    117     Z3_ast T = Z3_mk_const(mContext, Z3_mk_int_symbol(mContext, 1), boolTy);
    118     Z3_inc_ref(mContext, T);
    119     add(PabloBlock::createOnes(), T);
    120 
    121     for (unsigned i = 0; i < function.getNumOfParameters(); ++i) {
    122         make(function.getParameter(i));
    123     }
    124 
    125     characterize(function.getEntryBlock());
     113
     114    add(PabloBlock::createZeroes(), Z3_mk_false(mContext));
     115    add(PabloBlock::createOnes(), Z3_mk_true(mContext));
     116    for (unsigned i = 0; i < mFunction.getNumOfParameters(); ++i) {
     117        make(mFunction.getParameter(i));
     118    }
     119
     120    optimize(mFunction.getEntryBlock());
    126121}
    127122
     
    129124 * @brief characterize
    130125 ** ------------------------------------------------------------------------------------------------------------- */
    131 void MultiplexingPass::characterize(PabloBlock * const block) {
    132     Statement * end = initialize(block->front());
     126void MultiplexingPass::optimize(PabloBlock * const block) {
     127    Statement * begin = block->front();
     128    Statement * end = initialize(begin);
    133129    for (Statement * stmt : *block) {
    134130        if (LLVM_UNLIKELY(stmt == end)) {
    135131            Statement * const next = stmt->getNextNode();
    136             multiplex(block);
     132            multiplex(block, begin, stmt);
    137133            if (isa<If>(stmt)) {
    138                 characterize(cast<If>(stmt)->getBody());
     134                optimize(cast<If>(stmt)->getBody());
    139135            } else if (isa<While>(stmt)) {
    140136                for (const Next * var : cast<While>(stmt)->getVariants()) {
    141137                    Z3_inc_ref(mContext, get(var->getInitial()));
    142138                }
    143                 characterize(cast<While>(stmt)->getBody());
     139                optimize(cast<While>(stmt)->getBody());
    144140                // since we cannot be certain that we'll always execute at least one iteration of a loop, we must
    145                 // assume that the variants could either be their initial value or their resulting value.
     141                // assume that the variants could either be their initial or resulting value.
    146142                for (const Next * var : cast<While>(stmt)->getVariants()) {
    147143                    Z3_ast v0 = get(var->getInitial());
     
    156152                }
    157153            }
    158             end = initialize(next);
     154            end = initialize(begin = next);
    159155        } else {
    160156            characterize(stmt);
    161157        }
    162158    }
    163     multiplex(block);
     159    multiplex(block, begin, nullptr);
    164160}
    165161
     
    167163 * @brief multiplex
    168164 ** ------------------------------------------------------------------------------------------------------------- */
    169 void MultiplexingPass::multiplex(PabloBlock * const block) {
     165void MultiplexingPass::multiplex(PabloBlock * const block, Statement * const begin, Statement * const end) {
    170166    if (generateCandidateSets()) {
    171167        selectMultiplexSetsGreedy();
    172168        eliminateSubsetConstraints();
    173         multiplexSelectedSets(block);
     169        multiplexSelectedSets(block, begin, end);
    174170    }
    175171}
     
    382378    for (auto i = mCharacterization.begin(); i != mCharacterization.end(); ) {
    383379        const CharacterizationRef & r = std::get<1>(*i);
     380        const auto e = i++;
    384381        if (LLVM_UNLIKELY(std::get<1>(r) == 0)) {
    385382            Z3_dec_ref(mContext, std::get<0>(r));
    386             auto j = i++;
    387             mCharacterization.erase(j);
    388         } else {
    389             ++i;
     383            mCharacterization.erase(e);
    390384        }
    391385    }
     
    807801}
    808802
     803///** ------------------------------------------------------------------------------------------------------------- *
     804// * Fu & Malik procedure for MaxSAT. This procedure is based on unsat core extraction and the at-most-one constraint.
     805// ** ------------------------------------------------------------------------------------------------------------- */
     806//inline bool fu_malik_maxsat_step(Z3_context ctx, Z3_solver s, const std::vector<Z3_ast> & soft)
     807//{
     808//    // create assumptions
     809//    const auto n = soft.size();
     810//    Z3_ast assumptions[n];
     811//    for (size_t i = 0; i < n; ++i) {
     812//        assumptions[i] = Z3_mk_not(ctx, soft[i]);
     813//    }
     814
     815//    if (Z3_solver_check_assumptions(ctx, s, n, assumptions) != Z3_L_FALSE) {
     816//        return true; // done
     817//    }
     818
     819//    const auto core = Z3_solver_get_unsat_core(ctx, s);
     820//    const auto m = Z3_ast_vector_size(ctx, core);
     821//    Z3_ast block_vars[m];
     822//    unsigned k = 0;
     823//    // update soft-constraints and aux_vars
     824//    for (unsigned i = 0; i < num_soft_cnstrs; i++) {
     825//        // check whether assumption[i] is in the core or not
     826//        for (unsigned j = 0; j < m; j++) {
     827//            if (assumptions[i] == Z3_ast_vector_get(ctx, core, j)) {
     828//                // assumption[i] is in the unsat core... so soft_cnstrs[i] is in the unsat core
     829//                Z3_ast block_var   = Z3_mk_fresh_const(ctx, nullptr, Z3_mk_bool_sort(ctx));
     830//                Z3_ast new_aux_var = Z3_mk_fresh_const(ctx, nullptr, Z3_mk_bool_sort(ctx));
     831//                soft_cnstrs[i]     = mk_binary_or(ctx, soft_cnstrs[i], block_var);
     832//                aux_vars[i]        = new_aux_var;
     833//                block_vars[k]      = block_var;
     834//                k++;
     835//                // Add new constraint containing the block variable.
     836//                // Note that we are using the new auxiliary variable to be able to use it as an assumption.
     837//                Z3_solver_assert(ctx, s, mk_binary_or(ctx, soft_cnstrs[i], new_aux_var));
     838//                break;
     839//            }
     840//        }
     841//    }
     842
     843
     844//    assert_at_most_one(ctx, s, k, block_vars);
     845//    return 0; // not done.
     846
     847//}
     848
     849///** ------------------------------------------------------------------------------------------------------------- *
     850// * Fu & Malik procedure for MaxSAT. This procedure is based on unsat core extraction and the at-most-one constraint.
     851// ** ------------------------------------------------------------------------------------------------------------- */
     852//inline bool fu_malik_maxsat(Z3_context ctx, Z3_solver s, const std::vector<Z3_ast> & soft) {
     853//    assert(Z3_solver_check(ctx, s) != Z3_L_FALSE);
     854//    for (size_t k = 0; k < soft.size(); ++k) {
     855//        if (fu_malik_maxsat_step(ctx, s, soft)) {
     856//            return true;
     857//        }
     858//    }
     859//    return false;
     860//}
     861
     862
     863/** ------------------------------------------------------------------------------------------------------------- *
     864 * @brief addWithHardConstraints
     865 ** ------------------------------------------------------------------------------------------------------------- */
     866Z3_ast addWithHardConstraints(Z3_context ctx, Z3_solver solver, PabloBlock * const block, Statement * stmt, flat_map<Statement *, Z3_ast> & M) {
     867    assert (M.count(stmt) == 0 && stmt->getParent() == block);
     868    // compute the hard dependency constraints
     869    Z3_symbol symbol = Z3_mk_string_symbol(ctx, stmt->getName()->value().data()); assert (symbol);
     870    Z3_ast node = Z3_mk_const(ctx, symbol, Z3_mk_int_sort(ctx)); assert (node);
     871    for (unsigned i = 0; i != stmt->getNumOperands(); ++i) {
     872        PabloAST * const op = stmt->getOperand(i);
     873        if (isa<Statement>(op) && cast<Statement>(op)->getParent() == block) {
     874            const auto f = M.find(cast<Statement>(op));
     875            if (f != M.end()) {
     876                Z3_ast depedency = Z3_mk_lt(ctx, f->second, node);
     877                Z3_solver_assert(ctx, solver, depedency);
     878            }
     879        }
     880    }
     881    M.emplace(stmt, node);
     882    return node;
     883
     884}
     885
    809886/** ------------------------------------------------------------------------------------------------------------- *
    810887 * @brief dominates
     
    813890 ** ------------------------------------------------------------------------------------------------------------- */
    814891bool dominates(const Statement * const a, const Statement * const b) {
    815 
     892    assert (a);
    816893    if (LLVM_UNLIKELY(b == nullptr)) {
    817         return true;
    818     } else if (LLVM_UNLIKELY(a == nullptr)) {
    819894        return false;
    820895    }
    821 
    822     assert (a->getParent());
    823     assert (b->getParent());
    824 
    825     const PabloBlock * const parent = a->getParent();
    826     if (LLVM_LIKELY(parent == b->getParent())) {
    827         for (const Statement * t : *parent) {
    828             if (t == a) {
    829                 return true;
    830             } else if (t == b) {
    831                 break;
    832             }
    833         }
    834         return false;
    835     } else {
    836         const PabloBlock * block = b->getParent();
    837         for (;;) {
    838             Statement * br = block->getBranch();
    839             if (br == nullptr) {
    840                 return dominates(parent->getBranch(), b);
    841             }
    842             block = br->getParent();
    843             if (block == parent) {
    844                 return dominates(a, br);
    845             }
    846         }
    847     }
     896    assert (a->getParent() == b->getParent());
     897    for (const Statement * t : *a->getParent()) {
     898        if (t == a) {
     899            return true;
     900        } else if (t == b) {
     901            return false;
     902        }
     903    }
     904    llvm_unreachable("Neither a nor b are in their reported block!");
     905    return false;
     906}
     907
     908/** ------------------------------------------------------------------------------------------------------------- *
     909 * @brief addWithHardConstraints
     910 ** ------------------------------------------------------------------------------------------------------------- */
     911Z3_ast addWithHardConstraints(Z3_context ctx, Z3_solver solver, PabloBlock * const block, PabloAST * expr, flat_map<Statement *, Z3_ast> & M, Statement * const ip) {
     912    if (isa<Statement>(expr)) {
     913        Statement * const stmt = cast<Statement>(expr);
     914        if (stmt->getParent() == block) {
     915            const auto f = M.find(stmt);
     916            if (LLVM_UNLIKELY(f != M.end())) {
     917                return f->second;
     918            } else if (!dominates(stmt, ip)) {
     919                for (unsigned i = 0; i != stmt->getNumOperands(); ++i) {
     920                    addWithHardConstraints(ctx, solver, block, stmt->getOperand(i), M, ip);
     921                }
     922                return addWithHardConstraints(ctx, solver, block, stmt, M);
     923            }
     924        }
     925    }
     926    return nullptr;
    848927}
    849928
     
    851930 * @brief multiplexSelectedSets
    852931 ** ------------------------------------------------------------------------------------------------------------- */
    853 inline void MultiplexingPass::multiplexSelectedSets(PabloBlock * const block) {
    854 
    855 
    856 //    Z3_config cfg = Z3_mk_config();
    857 //    Z3_context ctx = Z3_mk_context_rc(cfg);
    858 //    Z3_del_config(cfg);
    859 //    Z3_solver solver = Z3_mk_solver(ctx);
    860 //    Z3_solver_inc_ref(ctx, solver);
    861 
     932inline void MultiplexingPass::multiplexSelectedSets(PabloBlock * const block, Statement * const begin, Statement * const end) {
     933
     934    assert ("begin cannot be null!" && begin);
     935    assert (begin->getParent() == block);
     936    assert (!end || end->getParent() == block);
     937    assert (!end || isa<If>(end) || isa<While>(end));
     938
     939    Statement * const ip = begin->getPrevNode(); // save our insertion point prior to modifying the AST
     940
     941    Z3_config cfg = Z3_mk_config();
     942    // Z3_set_param_value(cfg, "MODEL", "true");
     943    Z3_context ctx = Z3_mk_context(cfg);
     944    Z3_del_config(cfg);
     945    Z3_solver solver = Z3_mk_solver(ctx);
    862946
    863947    const auto first_set = num_vertices(mConstraintGraph);
    864948    const auto last_set = num_vertices(mCandidateGraph);
     949
     950    // Compute the hard and soft constraints for any part of the AST that we are not intending to modify.
     951    flat_map<Statement *, Z3_ast> M;
     952
     953//    Z3_ast prior = nullptr;
     954
     955//    Z3_ast one = Z3_mk_int(ctx, 1, Z3_mk_int_sort(ctx));
     956
     957//    std::vector<Z3_ast> soft; // call check_with_assumptions!!!
     958
     959    for (Statement * stmt = begin; stmt != end; stmt = stmt->getNextNode()) {
     960        Z3_ast node = addWithHardConstraints(ctx, solver, block, stmt, M);
     961//        // add in the soft ordering constraints
     962//        if (prior) {
     963//            Z3_ast constraint[2];
     964//            if (gap) {
     965//                constraint[0] = Z3_mk_lt(ctx, prior, node);
     966//                gap = false;
     967//            } else {
     968//                Z3_ast prior_plus_one[2] = { prior, one };
     969//                Z3_ast num = Z3_mk_add(ctx, 2, prior_plus_one);
     970//                constraint[0] = Z3_mk_eq(ctx, node, num);
     971//            }
     972//            Z3_ast ordering = Z3_mk_fresh_const(ctx, nullptr, Z3_mk_bool_sort(ctx));
     973//            constraint[1] = ordering;
     974//            Z3_solver_assert(ctx, solver, Z3_mk_or(ctx, 2, constraint));
     975//            soft.push_back(ordering);
     976//        }
     977//        prior = node;
     978    }
     979
     980
     981    block->setInsertPoint(block->back());
     982
    865983    for (auto idx = first_set; idx != last_set; ++idx) {
    866984        const size_t n = degree(idx, mCandidateGraph);
    867         assert (n == 0 || n > 2);
    868985        if (n) {
    869             const size_t m = log2_plus_one(n);
     986            const size_t m = log2_plus_one(n); assert (n > 2 && m < n);
    870987            Advance * input[n];
    871988            PabloAST * muxed[m];
     
    873990            // The multiplex set graph is a DAG with edges denoting the set relationships of our independent sets.
    874991            unsigned i = 0;
    875             for (const auto u : make_iterator_range(adjacent_vertices(idx, mCandidateGraph))) { // orderMultiplexSet(idx)) {
    876                 input[i++] = mConstraintGraph[u];
    877             }
    878             Advance * const adv = input[0];
    879             assert (block == adv->getParent());
     992            for (const auto u : make_iterator_range(adjacent_vertices(idx, mCandidateGraph))) {
     993                input[i] = mConstraintGraph[u];
     994                assert ("Algorithm failure! not all inputs are in the same block!" && (input[i]->getParent() == block));
     995                assert ("Algorithm failure! not all inputs advance by the same amount!" && (input[i]->getOperand(1) == input[0]->getOperand(1)));
     996                ++i;
     997            }
    880998
    881999            circular_buffer<PabloAST *> Q(n);
    8821000
    883             PabloBuilder builder(block);
    884             block->setInsertPoint(nullptr);
    885             /// Perform n-to-m Multiplexing           
     1001            /// Perform n-to-m Multiplexing
    8861002            for (size_t j = 0; j != m; ++j) {               
    8871003                std::ostringstream prefix;
     
    8961012                    PabloAST * a = Q.front(); Q.pop_front();
    8971013                    PabloAST * b = Q.front(); Q.pop_front();
    898                     Q.push_back(builder.createOr(a, b));
    899                 }
    900                 PabloAST * const muxing =  Q.front(); Q.clear();
    901                 muxed[j] = builder.createAdvance(muxing, adv->getOperand(1), prefix.str());
    902                 muxed_n[j] = builder.createNot(muxed[j]);
    903             }
     1014                    PabloAST * expr = block->createOr(a, b);
     1015                    addWithHardConstraints(ctx, solver, block, expr, M, ip);
     1016                    Q.push_back(expr);
     1017                }
     1018                PabloAST * const muxing = Q.front(); Q.clear();
     1019                muxed[j] = block->createAdvance(muxing, input[0]->getOperand(1), prefix.str());
     1020                addWithHardConstraints(ctx, solver, block, muxed[j], M, ip);
     1021                muxed_n[j] = block->createNot(muxed[j]);
     1022                addWithHardConstraints(ctx, solver, block, muxed_n[j], M, ip);
     1023            }
     1024
    9041025            /// Perform m-to-n Demultiplexing
    905             block->setInsertPoint(block->back());
    9061026            for (size_t i = 0; i != n; ++i) {
    9071027                // Construct the demuxed values and replaces all the users of the original advances with them.
     
    9101030                    Q.push_back((((i + 1) & (1UL << j)) != 0) ? muxed[j] : muxed_n[j]);
    9111031                }
     1032                Z3_ast replacement = nullptr;
    9121033                while (Q.size() > 1) {
    9131034                    PabloAST * const a = Q.front(); Q.pop_front();
    9141035                    PabloAST * const b = Q.front(); Q.pop_front();
    915                     Q.push_back(builder.createAnd(a, b));
    916                 }
    917                 PabloAST * const demuxed =  Q.front(); Q.clear();
    918                 input[i]->replaceWith(demuxed, true, true);
    919             }
    920         }
    921     }
    922 
    923     flat_set<PabloAST *> encountered;
    924     for (Statement * stmt = block->front(); stmt; ) {
    925 
    926         assert (stmt->getParent() == block);
    927         Statement * const next = stmt->getNextNode();
    928 
    929         bool unmoved = true;
    930         for (unsigned i = 0; i != stmt->getNumOperands(); ++i) {
    931             PabloAST * const op = stmt->getOperand(i);
    932             if (isa<Statement>(op)) {
    933                 Statement * ip = cast<Statement>(op);
    934                 if (ip->getParent() != block) {
    935                     // If we haven't already encountered the Assign or Next node, it must come from a If or
    936                     // While node that we haven't processed yet. Scan ahead and try to locate it.
    937                     if (isa<Assign>(op)) {
    938                         for (PabloAST * user : cast<Assign>(op)->users()) {
    939                             if (isa<If>(user) && cast<If>(user)->getParent() == block) {
    940                                 const auto & defs = cast<If>(user)->getDefined();
    941                                 if (LLVM_LIKELY(std::find(defs.begin(), defs.end(), op) != defs.end())) {
    942                                     ip = cast<If>(user);
    943                                     break;
    944                                 }
    945                             }
    946                         }
    947                     } else if (isa<Next>(op)) {
    948                         for (PabloAST * user : cast<Next>(op)->users()) {
    949                             if (isa<While>(user) && cast<While>(user)->getParent() == block) {
    950                                 const auto & vars = cast<While>(user)->getVariants();
    951                                 if (LLVM_LIKELY(std::find(vars.begin(), vars.end(), op) != vars.end())) {
    952                                     ip = cast<While>(user);
    953                                     break;
    954                                 }
    955                             }
    956                         }
    957                     }
    958                 }
    959                 if (encountered.count(ip) == 0) {
    960                     if (dominates(ip, stmt)) {
    961                         encountered.insert(ip);
    962                     } else {
    963                         assert (ip->getParent() == block);
    964                         stmt->insertAfter(ip);
    965                         unmoved = false;
    966                         break;
    967                     }
    968                 }
    969             }
    970         }
    971         if (unmoved) {
    972             encountered.insert(stmt);
    973         }
    974         stmt = next;
    975     }
    976 
    977 //    Z3_solver_dec_ref(ctx, solver);
    978 //    Z3_del_context(ctx);
     1036                    PabloAST * expr = block->createAnd(a, b);
     1037                    replacement = addWithHardConstraints(ctx, solver, block, expr, M, ip);
     1038                    Q.push_back(expr);
     1039                }
     1040                assert (replacement);
     1041                PabloAST * const demuxed = Q.front(); Q.clear();
     1042
     1043                const auto f = M.find(input[i]);
     1044                assert (f != M.end());
     1045                Z3_solver_assert(ctx, solver, Z3_mk_eq(ctx, f->second, replacement));
     1046                M.erase(f);
     1047
     1048                input[i]->replaceWith(demuxed);
     1049                assert (M.count(input[i]) == 0);
     1050            }
     1051        }
     1052    }
     1053
     1054    assert (M.count(ip) == 0);
     1055
     1056    if (LLVM_UNLIKELY(Z3_solver_check(ctx, solver) == Z3_L_FALSE)) {
     1057        throw std::runtime_error("Unexpected Z3 failure when attempting to topologically sort the AST!");
     1058    }
     1059
     1060    Z3_model m = Z3_solver_get_model(ctx, solver);
     1061    Z3_model_inc_ref(ctx, m);
     1062
     1063    std::vector<std::pair<long long int, Statement *>> Q;
     1064
     1065    for (const auto i : M) {
     1066        Z3_ast value;
     1067        if (Z3_model_eval(ctx, m, std::get<1>(i), Z3_L_TRUE, &value) != Z3_L_TRUE) {
     1068            throw std::runtime_error("Unexpected Z3 error when attempting to obtain value from model!");
     1069        }
     1070        long long int line;
     1071        if (Z3_get_numeral_int64(ctx, value, &line) != Z3_L_TRUE) {
     1072            throw std::runtime_error("Unexpected Z3 error when attempting to convert model value to integer!");
     1073        }
     1074        Q.emplace_back(line, std::get<0>(i));
     1075    }
     1076
     1077    Z3_model_dec_ref(ctx, m);
     1078    Z3_del_context(ctx);
     1079
     1080    std::sort(Q.begin(), Q.end());
     1081
     1082    block->setInsertPoint(ip);
     1083    for (auto i : Q) {
     1084        block->insert(std::get<1>(i));
     1085    }
    9791086
    9801087
     
    10341141    assert (expr);
    10351142    Z3_sort ty = Z3_mk_bool_sort(mContext);
    1036     Z3_symbol s = Z3_mk_string_symbol(mContext, nullptr); // expr->getName()->to_string().c_str()
     1143    const String * name = nullptr;
     1144    if (LLVM_LIKELY(isa<Statement>(expr))) {
     1145        name = cast<Statement>(expr)->getName();
     1146    } else if (LLVM_UNLIKELY(isa<Var>(expr))) {
     1147        name = cast<Var>(expr)->getName();
     1148    }
     1149    assert (name);
     1150    Z3_symbol s = Z3_mk_string_symbol(mContext, name->value().data());
    10371151    Z3_ast node = Z3_mk_const(mContext, s, ty);
    10381152    Z3_inc_ref(mContext, node);
  • icGREP/icgrep-devel/icgrep/pablo/optimizers/pablo_automultiplexing.hpp

    r5112 r5113  
    6060    void reset();
    6161
    62     void characterize(PabloFunction & function);
    63     void characterize(PabloBlock * const block);
     62    void optimize();
     63    void optimize(PabloBlock * const block);
    6464    Z3_ast characterize(Statement * const stmt);
    6565    Z3_ast characterize(Advance * const adv, Z3_ast Ik);
    66     void multiplex(PabloBlock * const block);
     66    void multiplex(PabloBlock * const block, Statement * const begin, Statement * const end);
    6767
    6868    bool generateCandidateSets();
     
    7777    void doTransitiveReductionOfSubsetGraph();
    7878
    79     void multiplexSelectedSets(PabloBlock * const block);
     79    void multiplexSelectedSets(PabloBlock * const block, Statement * const begin, Statement * const end);
    8080
    8181
Note: See TracChangeset for help on using the changeset viewer.