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

Last change on this file since 5836 was 5836, checked in by nmedfort, 13 months ago

Added PabloBlock/Builder? createScope() methods + minor code changes.

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