diff --git a/.github/workflows/buildAndTest.yml b/.github/workflows/buildAndTest.yml index cbe58063262..573c5b81ef0 100644 --- a/.github/workflows/buildAndTest.yml +++ b/.github/workflows/buildAndTest.yml @@ -8,19 +8,19 @@ jobs: # Build Phism and run its tests. build-phism: name: Build and Test Phism - runs-on: self-hosted + runs-on: ubuntu-latest steps: - # - name: Configure Environment - # run: echo "${GITHUB_WORKSPACE}/llvm/install/bin" >> $GITHUB_PATH - # Disabled for self-hosted - # - name: Get dependences - # run: | - # sudo apt-get update -y - # sudo apt-get install -y build-essential libtool autoconf pkg-config flex bison libgmp-dev clang-9 libclang-9-dev texinfo python3 - # - name: Update the LLVM/Clang version to 9 - # run: | - # sudo update-alternatives --install /usr/bin/llvm-config llvm-config /usr/bin/llvm-config-9 100 - # sudo update-alternatives --install /usr/bin/FileCheck FileCheck /usr/bin/FileCheck-9 100 + - name: Configure Environment + run: echo "${GITHUB_WORKSPACE}/llvm/install/bin" >> $GITHUB_PATH + + - name: Get dependences + run: | + sudo apt-get update -y + sudo apt-get install -y build-essential libtool autoconf pkg-config flex bison libgmp-dev clang-9 libclang-9-dev texinfo python3 + - name: Update the LLVM/Clang version to 9 + run: | + sudo update-alternatives --install /usr/bin/llvm-config llvm-config /usr/bin/llvm-config-9 100 + sudo update-alternatives --install /usr/bin/FileCheck FileCheck /usr/bin/FileCheck-9 100 # Clone the Phism repo and its submodules. Do shallow clone to save clone @@ -29,6 +29,11 @@ jobs: uses: actions/checkout@v2 with: submodules: "true" + + - name: Sync Polygeist submodule + run: | + cd polygeist + git submodule update --init --recursive # -------- # Restore LLVM from cache and build if it's not in there. @@ -36,14 +41,14 @@ jobs: # Extract the LLVM submodule hash for use in the cache key. - name: Get LLVM Hash id: get-llvm-hash - run: echo "::set-output name=hash::$(git rev-parse @:./llvm)" + run: echo "::set-output name=hash::$(git rev-parse @:./polygeist/llvm-project)" shell: bash # Try to fetch LLVM from the cache. - name: Cache LLVM id: cache-llvm uses: actions/cache@v2 with: - path: llvm/build + path: ./polygeist/llvm-project/build key: ${{ runner.os }}-llvm-${{ steps.get-llvm-hash.outputs.hash }} # Build LLVM if we didn't hit in the cache. Even though we build it in # the previous job, there is a low chance that it'll have been evicted by @@ -52,8 +57,24 @@ jobs: - name: Rebuild and Install LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' run: | - ./scripts/build-llvm.sh ci + ./scripts/build-llvm.sh rm -rf ./llvm/build/test + + + # -------- + # Build and test Polygeist. + # -------- + - name: Build and test Polygeist + run: | + ./scripts/build-polygeist.sh + + # -------- + # Build and test Polymer. + # -------- + - name: Build and test Polymer + run: | + ./scripts/build-polymer.sh + # -------- # Build and test Phism in both debug and release mode. # -------- @@ -71,19 +92,19 @@ jobs: pytest pyphism/ # Build and test Phism with pb-flow. - - name: Build and Test Phism (pb-flow) - run: | - python3 -m venv env - source env/bin/activate - which python3 - python3 -m pip install -r requirements.txt - python3 ./scripts/pb-flow.py ./example/polybench --dataset MINI --sanity-check - python3 ./scripts/pb-flow.py ./example/polybench --dataset MINI --polymer --sanity-check - python3 ./scripts/pb-flow.py ./example/polybench --dataset MINI --polymer --loop-transforms --sanity-check - python3 ./scripts/pb-flow.py ./example/polybench --dataset SMALL --skip-vitis - python3 ./scripts/pb-flow.py ./example/polybench --dataset SMALL --polymer --skip-vitis - python3 ./scripts/pb-flow.py ./example/polybench --dataset SMALL --polymer --loop-transforms --skip-vitis - python3 ./scripts/pb-flow.py ./example/polybench --dataset SMALL --polymer --loop-transforms --array-partition --skip-vitis +# - name: Build and Test Phism (pb-flow) +# run: | +# python3 -m venv env +# source env/bin/activate +# which python3 +# python3 -m pip install -r requirements.txt +# python3 ./scripts/pb-flow.py ./example/polybench --dataset MINI --sanity-check +# python3 ./scripts/pb-flow.py ./example/polybench --dataset MINI --polymer --sanity-check +# python3 ./scripts/pb-flow.py ./example/polybench --dataset MINI --polymer --loop-transforms --sanity-check +# python3 ./scripts/pb-flow.py ./example/polybench --dataset SMALL --skip-vitis +# python3 ./scripts/pb-flow.py ./example/polybench --dataset SMALL --polymer --skip-vitis +# python3 ./scripts/pb-flow.py ./example/polybench --dataset SMALL --polymer --loop-transforms --skip-vitis +# python3 ./scripts/pb-flow.py ./example/polybench --dataset SMALL --polymer --loop-transforms --array-partition --skip-vitis diff --git a/.gitmodules b/.gitmodules index 0bf63474ae9..5c86da0a841 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,9 @@ -[submodule "llvm"] - path = llvm - url = https://github.com/kumasento/Polygeist [submodule "example/polybench"] path = example/polybench url = git@github.com:MatthiasJReisinger/PolyBenchC-4.2.1.git +[submodule "polygeist"] + path = polygeist + url = https://github.com/wsmoses/Polygeist +[submodule "polymer"] + path = polymer + url = https://github.com/kumasento/polymer diff --git a/README.md b/README.md index 001031d7a1f..83b1b342e11 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # Phism: Polyhedral High-Level Synthesis in MLIR [![Build and Test](https://github.com/kumasento/phism/actions/workflows/buildAndTest.yml/badge.svg)](https://github.com/kumasento/phism/actions/workflows/buildAndTest.yml) +![GitHub release (latest by date)](https://img.shields.io/github/v/release/kumasento/phism) +![GitHub](https://img.shields.io/github/license/kumasento/phism) +![GitHub issues](https://img.shields.io/github/issues/kumasento/phism) +![GitHub pull requests](https://img.shields.io/github/issues-pr/kumasento/phism) + ## What is Phism? @@ -18,20 +23,40 @@ Please find how to setup the prerequisites [here](docs/PREREQUISITES.md). ### Build LLVM -After that, the first thing you need to do is building the [Polygeist](wsmoses/Polygeist) submodule (with name `llvm`). Make sure you have it cloned: +Phism uses [Polygeist](https://github.com/wsmoses/Polygeist) to process C/C++ code into MLIR. But before we built Polygeist, we need to build the LLVM package within Polygeist (`polygeist/llvm-project`). This LLVM package will be shared by Polygeist, Polymer (later), and Phism. + +First of all, make sure you've initialized all the submodules. ```sh -git submodule update --init --update +git submodule update --init --recursive ``` You may see many submodules being synced -- don't worry, they are simply required by Pluto, the polyhedral optimizer that Phism uses. -To build Polygeist, Just run the following script. It should take care of everything you need. +To build LLVM, Just run the following script. It should take care of everything you need. ```sh ./scripts/build-llvm.sh ``` +### Build Polygeist + +It is another one-liner: + +```sh +./script/build-polygeist.sh +``` + +### Build Polymer + +[Polymer](https://github.com/kumasento/polymer) provides the functionality to interact MLIR code with polyhedral scheduler. + +There is also a script for you - + +```sh +./script/build-polymer.sh +``` + ### Build Phism Finally, you're all prepared to build Phism! Just type in the following commands: @@ -44,7 +69,6 @@ It should run the Phism regression test in the end. And if all the tests passed, ## Usage - ### Using Docker This [doc](docs/DOCKER.md) gives an introduction on how to run Phism with docker. diff --git a/include/phism/mlir/Transforms/PhismTransforms.h b/include/phism/mlir/Transforms/PhismTransforms.h index efd22aeb9b0..288513e46ae 100644 --- a/include/phism/mlir/Transforms/PhismTransforms.h +++ b/include/phism/mlir/Transforms/PhismTransforms.h @@ -11,8 +11,9 @@ namespace phism { void registerExtractTopFuncPass(); void registerLoopTransformPasses(); void registerArrayPartitionPasses(); +void registerFoldIfPasses(); void registerAllPhismPasses(); -void registerDependenceAnalysisPasses(); +// void registerDependenceAnalysisPasses(); } // namespace phism #endif diff --git a/include/phism/mlir/Transforms/Utils.h b/include/phism/mlir/Transforms/Utils.h index f1d09726276..11106a03bfe 100644 --- a/include/phism/mlir/Transforms/Utils.h +++ b/include/phism/mlir/Transforms/Utils.h @@ -1,10 +1,16 @@ //===- Utils.h - Utility functions ------------------ C++-===// +#include "mlir/Dialect/Affine/IR/AffineOps.h" +#include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/OperationSupport.h" namespace phism { /// Get the top function for the hardware design. mlir::FuncOp getTopFunction(mlir::ModuleOp m); +mlir::Value expandAffineExpr(mlir::OpBuilder &builder, mlir::Location loc, + mlir::AffineExpr expr, mlir::ValueRange dimValues, + mlir::ValueRange symbolValues); } // namespace phism diff --git a/lib/llvm/Transforms/VhlsLLVMRewriter.cc b/lib/llvm/Transforms/VhlsLLVMRewriter.cc index 4922b531735..1910eab9618 100644 --- a/lib/llvm/Transforms/VhlsLLVMRewriter.cc +++ b/lib/llvm/Transforms/VhlsLLVMRewriter.cc @@ -1776,9 +1776,9 @@ struct ConfigMemoryInterfacePass : public ModulePass { if (isPointerToArray(arg->getType())) { // Set ap_memory interface to array arguments. auto arrayName = arg->getName().str(); - attributeList = attributeList.addAttribute(F->getContext(), i + 1, - "fpga.address.interface", - "ap_memory." + arrayName); + attributeList = attributeList.addAttributeAtIndex( + F->getContext(), i + 1, "fpga.address.interface", + "ap_memory." + arrayName); // Set bram configuration to function metadata. auto &C = F->getContext(); diff --git a/lib/mlir/Transforms/ArrayPartition.cc b/lib/mlir/Transforms/ArrayPartition.cc index 35e0f9e8d47..be4b6f8a4be 100644 --- a/lib/mlir/Transforms/ArrayPartition.cc +++ b/lib/mlir/Transforms/ArrayPartition.cc @@ -26,6 +26,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SetVector.h" +#include "llvm/ADT/StringExtras.h" #include #include @@ -970,7 +971,8 @@ static void renameTiledFunctions(ModuleOp m, OpBuilder &b) { m.walk([&](mlir::CallOp caller) { if (newNames.count(caller.getCallee())) caller->setAttr("callee", - b.getSymbolRefAttr(newNames[caller.getCallee()])); + SymbolRefAttr::get(caller.getContext(), + newNames[caller.getCallee()])); }); } diff --git a/lib/mlir/Transforms/CMakeLists.txt b/lib/mlir/Transforms/CMakeLists.txt index b4d22142df2..93f0f0d31ec 100644 --- a/lib/mlir/Transforms/CMakeLists.txt +++ b/lib/mlir/Transforms/CMakeLists.txt @@ -3,7 +3,7 @@ add_mlir_library(PhismTransforms LoopTransforms.cc PhismTransforms.cc ArrayPartition.cc - DependenceAnalysis.cc + FoldIf.cc Utils.cc ADDITIONAL_HEADER_DIRS diff --git a/lib/mlir/Transforms/DependenceAnalysis.cc b/lib/mlir/Transforms/DependenceAnalysis.cc deleted file mode 100644 index b0b8851c09b..00000000000 --- a/lib/mlir/Transforms/DependenceAnalysis.cc +++ /dev/null @@ -1,427 +0,0 @@ -//===- DependenceAnalysis.cc - Dependence analysis ----------------- C++-===// - -#include "phism/mlir/Transforms/PhismTransforms.h" - -#include "mlir/Analysis/AffineAnalysis.h" -#include "mlir/Analysis/AffineStructures.h" -#include "mlir/Analysis/SliceAnalysis.h" -#include "mlir/Analysis/Utils.h" -#include "mlir/Dialect/Affine/IR/AffineOps.h" -#include "mlir/Dialect/Affine/IR/AffineValueMap.h" -#include "mlir/Dialect/MemRef/IR/MemRef.h" -#include "mlir/IR/BlockAndValueMapping.h" -#include "mlir/IR/Builders.h" -#include "mlir/IR/Dominance.h" -#include "mlir/IR/OpImplementation.h" -#include "mlir/IR/PatternMatch.h" -#include "mlir/IR/Types.h" -#include "mlir/IR/Value.h" -#include "mlir/Pass/Pass.h" -#include "mlir/Pass/PassManager.h" -#include "mlir/Transforms/DialectConversion.h" -#include "mlir/Transforms/Passes.h" -#include "mlir/Transforms/RegionUtils.h" -#include "mlir/Transforms/Utils.h" - -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/SetVector.h" - -#include -#include - -#define DEBUG_TYPE "dependence-analysis" - -using namespace mlir; -using namespace llvm; -using namespace phism; - -/// -------------------------- Dependence analysis --------------------------- - -static bool hasPeCaller(FuncOp f) { - bool ret = false; - f.walk([&](CallOp caller) { - if (caller->hasAttr("scop.pe")) - ret = true; - }); - return ret; -} - -static FuncOp getTopFunction(ModuleOp m) { - FuncOp top = nullptr; - m.walk([&](FuncOp f) { - if (hasPeCaller(f)) { - assert(!top && "There should be only one top function."); - top = f; - } - }); - return top; -} - -static FlatAffineConstraints getOpIndexSet(Operation *op) { - FlatAffineConstraints cst; - SmallVector ops; - getEnclosingAffineForAndIfOps(*op, &ops); - getIndexSet(ops, &cst); - return cst; -} - -/// Get the access domain from all the provided operations. -static FlatAffineConstraints getDomain(ArrayRef ops) { - FlatAffineConstraints cst; - for (Operation *op : ops) { - MemRefAccess access(op); - - AffineValueMap accessMap; - access.getAccessMap(&accessMap); - - SmallSetVector ivs; // used to access memref. - for (Value operand : accessMap.getOperands()) - ivs.insert(operand); - - FlatAffineConstraints domain = getOpIndexSet(op); - - // project out those IDs that are not in the accessMap. - SmallVector values; - domain.getIdValues(0, domain.getNumDimIds(), &values); - - SmallVector toProject; - for (Value value : values) - if (!ivs.count(value)) - toProject.push_back(value); - - for (Value id : toProject) { - unsigned pos; - domain.findId(id, &pos); - domain.projectOut(pos); - } - - cst.mergeAndAlignIdsWithOther(0, &domain); - cst.append(domain); - } - - cst.removeTrivialRedundancy(); - return cst; -} - -namespace { - -/// Memory access. -struct PeMemAccess { - Value memref; - CallOp caller; - FlatAffineConstraints read; - FlatAffineConstraints write; - - PeMemAccess() {} - PeMemAccess(Value memref, CallOp caller, FlatAffineConstraints read, - FlatAffineConstraints write) - : memref(memref), caller(caller), read(read), write(write) {} - - // Predicates. - bool hasWrite() const { return write.getNumConstraints() > 0; } - bool hasRead() const { return read.getNumConstraints() > 0; } - bool noWrite() const { return write.getNumConstraints() == 0; } - bool noRead() const { return read.getNumConstraints() == 0; } - bool isReadOnly() const { return noWrite() && hasRead(); } - bool isWriteOnly() const { return hasWrite() && noRead(); } - bool isEmpty() const { return noWrite() && noRead(); } - bool isReadWrite() const { return hasWrite() && hasRead(); } - - unsigned getNumDims() const { - return isReadOnly() ? read.getNumDimIds() : write.getNumDimIds(); - } -}; -} // namespace - -static PeMemAccess getPeMemAccess(mlir::CallOp caller, unsigned argId, - ModuleOp m) { - FuncOp callee = cast(m.lookupSymbol(caller.getCallee())); - - Value arg = callee.getArgument(argId); - // Get all the read/write accesses. - SmallVector loadOps, storeOps; - copy_if(arg.getUsers(), std::back_inserter(loadOps), - [](Operation *op) { return isa(op); }); - copy_if(arg.getUsers(), std::back_inserter(storeOps), - [](Operation *op) { return isa(op); }); - - // Union all the read constraints from all the load operations. - FlatAffineConstraints read = getDomain(loadOps); - FlatAffineConstraints write = getDomain(storeOps); - - Value memref = caller.getOperand(argId + 1); - return {memref, caller, read, write}; -} - -namespace { - -/// A single PE instance. -struct PeInst { - CallOp caller; - SmallVector forOps; - SmallDenseMap memAccesses; - - PeInst() {} - PeInst(CallOp caller) : caller(caller) { getLoopIVs(*caller, &forOps); } - - void initMemAccesses(ModuleOp m) { - for (auto arg : enumerate(caller.getArgOperands())) - if (arg.value().getType().isa()) - memAccesses[arg.value()] = getPeMemAccess(caller, arg.index(), m); - } -}; - -} // namespace - -/// Get the list of memrefs being accessed by both the srcInst and the dstInst. -/// One of the access should be write. -static SmallVector getCommonMemRefs(PeInst &srcInst, PeInst &dstInst) { - SmallVector memrefs; - for (auto &it : srcInst.memAccesses) - // if it.first (memref) exists in both the src and the dst. - // The access should be at RAW, WAR, or WAW. - if (dstInst.memAccesses.count(it.first) && - (dstInst.memAccesses[it.first].hasWrite() || it.second.hasWrite())) - memrefs.push_back(it.first); - return memrefs; -} - -namespace { -struct PeMemDependence { - enum Type { RAW, WAR, WAW }; - - Type type; - unsigned depth; - FlatAffineConstraints src, dst; - Operation *srcOp, *dstOp; - - PeMemDependence(Type type, unsigned depth, FlatAffineConstraints src, - FlatAffineConstraints dst, Operation *srcOp, Operation *dstOp) - : type(type), depth(depth), src(src), dst(dst), srcOp(srcOp), - dstOp(dstOp) {} -}; -} // namespace - -// Returns the number of outer loop common to 'src/dstDomain'. -// Loops common to 'src/dst' domains are added to 'commonLoops' if non-null. -static unsigned getNumCommonLoops(const FlatAffineConstraints &srcDomain, - const FlatAffineConstraints &dstDomain) { - // Find the number of common loops shared by src and dst accesses. - unsigned minNumLoops = - std::min(srcDomain.getNumDimIds(), dstDomain.getNumDimIds()); - unsigned numCommonLoops = 0; - for (unsigned i = 0; i < minNumLoops; ++i) { - if (!isForInductionVar(srcDomain.getIdValue(i)) || - !isForInductionVar(dstDomain.getIdValue(i)) || - srcDomain.getIdValue(i) != dstDomain.getIdValue(i)) - break; - ++numCommonLoops; - } - return numCommonLoops; -} - -static Block *getCommonBlock(Operation *src, Operation *dst, - const FlatAffineConstraints &srcDomain, - unsigned numCommonLoops) { - // Get the chain of ancestor blocks to the given `MemRefAccess` instance. The - // search terminates when either an op with the `AffineScope` trait or - // `endBlock` is reached. - auto getChainOfAncestorBlocks = [&](Operation *op, - SmallVector &ancestorBlocks, - Block *endBlock = nullptr) { - Block *currBlock = op->getBlock(); - // Loop terminates when the currBlock is nullptr or equals to the endBlock, - // or its parent operation holds an affine scope. - while (currBlock && currBlock != endBlock && - !currBlock->getParentOp()->hasTrait()) { - ancestorBlocks.push_back(currBlock); - currBlock = currBlock->getParentOp()->getBlock(); - } - }; - - if (numCommonLoops == 0) { - Block *block = src->getBlock(); - while (!llvm::isa(block->getParentOp())) { - block = block->getParentOp()->getBlock(); - } - return block; - } - Value commonForIV = srcDomain.getIdValue(numCommonLoops - 1); - AffineForOp forOp = getForInductionVarOwner(commonForIV); - assert(forOp && "commonForValue was not an induction variable"); - - // Find the closest common block including those in AffineIf. - SmallVector srcAncestorBlocks, dstAncestorBlocks; - getChainOfAncestorBlocks(src, srcAncestorBlocks, forOp.getBody()); - getChainOfAncestorBlocks(dst, dstAncestorBlocks, forOp.getBody()); - - Block *commonBlock = forOp.getBody(); - for (int i = srcAncestorBlocks.size() - 1, j = dstAncestorBlocks.size() - 1; - i >= 0 && j >= 0 && srcAncestorBlocks[i] == dstAncestorBlocks[j]; - i--, j--) - commonBlock = srcAncestorBlocks[i]; - - return commonBlock; -} - -static bool -srcAppearsBeforeDstInAncestralBlock(Operation *src, Operation *dst, - const FlatAffineConstraints &srcDomain, - unsigned numCommonLoops) { - // Get Block common to 'srcAccess.opInst' and 'dstAccess.opInst'. - auto *commonBlock = getCommonBlock(src, dst, srcDomain, numCommonLoops); - // Check the dominance relationship between the respective ancestors of the - // src and dst in the Block of the innermost among the common loops. - auto *srcInst = commonBlock->findAncestorOpInBlock(*src); - assert(srcInst != nullptr); - auto *dstInst = commonBlock->findAncestorOpInBlock(*dst); - assert(dstInst != nullptr); - - // Determine whether dstInst comes after srcInst. - return srcInst->isBeforeInBlock(dstInst); -} - -/// TODO: skip some of the dependencies if they are not valid. -/// We need to make sure srcOp should be the ancestor of dstOp, -static void addOrSkipPeMemDependence(PeMemDependence::Type type, unsigned depth, - FlatAffineConstraints src, - FlatAffineConstraints dst, - Operation *srcOp, Operation *dstOp, - SmallVectorImpl &deps) { - FlatAffineConstraints srcDomain = getOpIndexSet(srcOp), - dstDomain = getOpIndexSet(dstOp); - - unsigned numCommonLoops = getNumCommonLoops(srcDomain, dstDomain); - // If we are looking at the dependence at a deeper level, we should make sure - // srcOp properly dominates dstOp. - if (numCommonLoops < depth && !srcAppearsBeforeDstInAncestralBlock( - srcOp, dstOp, srcDomain, numCommonLoops)) - return; - - deps.push_back({type, depth, src, dst, srcOp, dstOp}); -} - -static auto getPeMemDependenceMap(ArrayRef commonMemRefs, unsigned depth, - PeInst &srcInst, PeInst &dstInst) { - SmallDenseMap> deps; - for (Value memref : commonMemRefs) { - if (srcInst.memAccesses[memref].hasWrite() && - dstInst.memAccesses[memref].hasRead()) - addOrSkipPeMemDependence(PeMemDependence::Type::RAW, depth, - srcInst.memAccesses[memref].write, - dstInst.memAccesses[memref].read, srcInst.caller, - dstInst.caller, deps[memref]); - if (srcInst.memAccesses[memref].hasRead() && - dstInst.memAccesses[memref].hasWrite()) - addOrSkipPeMemDependence(PeMemDependence::Type::WAR, depth, - srcInst.memAccesses[memref].read, - dstInst.memAccesses[memref].write, - srcInst.caller, dstInst.caller, deps[memref]); - if (srcInst.memAccesses[memref].hasWrite() && - dstInst.memAccesses[memref].hasWrite()) - addOrSkipPeMemDependence(PeMemDependence::Type::WAW, depth, - srcInst.memAccesses[memref].write, - dstInst.memAccesses[memref].write, - srcInst.caller, dstInst.caller, deps[memref]); - - LLVM_DEBUG({ - memref.dump(); - for (auto &dep : deps[memref]) - llvm::errs() << "Dependence type: " << dep.type << '\n'; - }); - - if (deps[memref].empty()) - deps.erase(deps.find(memref)); - } - - return deps; -} - -static DependenceResult checkPeInstDependence(PeInst &srcInst, PeInst &dstInst, - unsigned depth) { - LLVM_DEBUG(llvm::errs() << "=========\nDependence analysis:\n";); - LLVM_DEBUG(srcInst.caller.dump();); - LLVM_DEBUG(dstInst.caller.dump();); - - SmallVector commonMemRefs = getCommonMemRefs(srcInst, dstInst); - - LLVM_DEBUG({ - llvm::errs() << "Common memrefs:\n"; - for (Value memref : commonMemRefs) - memref.dump(); - llvm::errs() << "--------------------------\n"; - }); - - // If there is no common memrefs, no dependence. - if (commonMemRefs.empty()) - return DependenceResult::NoDependence; - - // Get all kinds of dependencies for each memref between the src and dst. - auto deps = getPeMemDependenceMap(commonMemRefs, depth, srcInst, dstInst); - if (deps.empty()) { - LLVM_DEBUG(llvm::errs() << "No dependences for every common memref.\n";); - return DependenceResult::NoDependence; - } - - // We know that the loops wrapping srcInst and dstInst have constant bounds. - // So they can be fully unrolled. For each dependence in the map, we can - // realise them by the actual outer loop induction variable values. - - return DependenceResult::NoDependence; -} - -namespace { - -struct DependenceAnalysisPass - : public mlir::PassWrapper> { - void runOnOperation() override { - ModuleOp m = getOperation(); - OpBuilder b(m.getContext()); - - FuncOp top = getTopFunction(m); - - SmallVector peInsts; - top.walk([&](mlir::CallOp caller) { - if (caller->hasAttr("scop.pe")) - peInsts.push_back({caller}); - }); - - // initialize memory accesses. - for (PeInst &inst : peInsts) - inst.initMemAccesses(m); - - // calculate dependencies - unsigned maxLoopDepth = 3; - for (unsigned depth = 1; depth <= maxLoopDepth; ++depth) - for (PeInst &srcInst : peInsts) - for (PeInst &dstInst : peInsts) - checkPeInstDependence(srcInst, dstInst, depth); - - // Debugging. - llvm::errs() << "PE callers:\n"; - for (PeInst &inst : peInsts) { - inst.caller.dump(); - - llvm::errs() << "Memory accesses:\n"; - for (auto &it : inst.memAccesses) { - llvm::errs() << "Mem:\n"; - it.first.dump(); - - llvm::errs() << "Read:\n"; - it.second.read.dump(); - llvm::errs() << "Write:\n"; - it.second.write.dump(); - - llvm::errs() << "-------------------------\n"; - } - } - } -}; - -} // namespace - -void phism::registerDependenceAnalysisPasses() { - PassRegistration("dependence-analysis", - "Analyse PE dependencies"); -} diff --git a/lib/mlir/Transforms/ExtractTopFunc.cc b/lib/mlir/Transforms/ExtractTopFunc.cc index 13f047ba0e3..d5e0a9b7baa 100644 --- a/lib/mlir/Transforms/ExtractTopFunc.cc +++ b/lib/mlir/Transforms/ExtractTopFunc.cc @@ -28,12 +28,23 @@ #include "llvm/ADT/SetVector.h" +#include + using namespace mlir; using namespace llvm; using namespace phism; static constexpr const char *SCOP_CONSTANT_VALUE = "scop.constant_value"; +namespace { +struct PipelineOptions : public mlir::PassPipelineOptions { + Option topFuncName{*this, "name", + llvm::cl::desc("Top function name")}; + Option keepAll{*this, "keepall", + llvm::cl::desc("Keep all the functions.")}; +}; +} // namespace + /// Union the given function and all the others it calls into 'keep'. static void unionCallee(FuncOp f, ModuleOp m, SmallPtrSetImpl &keep) { @@ -93,14 +104,13 @@ namespace { struct ExtractTopFuncPass : public PassWrapper> { + std::string topFuncName; + bool keepAll = false; + ExtractTopFuncPass() = default; ExtractTopFuncPass(const ExtractTopFuncPass &pass) {} - - Option topFuncName{ - *this, "name", - llvm::cl::desc("Name of the top function to be extracted.")}; - Option keepAll{*this, "keepall", - llvm::cl::desc("Keep all the functions.")}; + ExtractTopFuncPass(const PipelineOptions &options) + : topFuncName{options.topFuncName}, keepAll{keepAll} {} void runOnOperation() override { ModuleOp m = getOperation(); @@ -127,12 +137,6 @@ struct ExtractTopFuncPass } // namespace -struct ConstArgsPipelineOptions - : public mlir::PassPipelineOptions { - Option topFuncName{*this, "name", - llvm::cl::desc("Top function name")}; -}; - /// --------------------------- ReplaceConstantArguments ---------------------- namespace { @@ -208,7 +212,7 @@ struct PropagateConstantsPass PropagateConstantsPass() = default; PropagateConstantsPass(const PropagateConstantsPass &pass) {} - PropagateConstantsPass(const ConstArgsPipelineOptions &options) + PropagateConstantsPass(const PipelineOptions &options) : topName(options.topFuncName) {} void runOnOperation() override { @@ -258,11 +262,14 @@ struct PropagateConstantsPass } // namespace void phism::registerExtractTopFuncPass() { - PassRegistration( - "extract-top-func", "Extract the top function out of its module."); - PassPipelineRegistration( + PassPipelineRegistration( + "extract-top-func", "Extract the top function out of its module.", + [](OpPassManager &pm, const PipelineOptions &options) { + pm.addPass(std::make_unique(options)); + }); + PassPipelineRegistration( "replace-constant-arguments", "Replace the annotated constant arguments.", - [](OpPassManager &pm, const ConstArgsPipelineOptions &options) { + [](OpPassManager &pm, const PipelineOptions &options) { pm.addPass(std::make_unique()); pm.addPass(createCanonicalizerPass()); pm.addPass(std::make_unique()); diff --git a/lib/mlir/Transforms/FoldIf.cc b/lib/mlir/Transforms/FoldIf.cc new file mode 100644 index 00000000000..c68f5b77b8c --- /dev/null +++ b/lib/mlir/Transforms/FoldIf.cc @@ -0,0 +1,157 @@ +//===- FoldIf.cc - Fold affine.if into select ---------------- C++-===// +#include "phism/mlir/Transforms/PhismTransforms.h" +#include "phism/mlir/Transforms/Utils.h" + +#include "mlir/Analysis/AffineAnalysis.h" +#include "mlir/Analysis/AffineStructures.h" +#include "mlir/Analysis/SliceAnalysis.h" +#include "mlir/Analysis/Utils.h" +#include "mlir/Dialect/Affine/IR/AffineOps.h" +#include "mlir/Dialect/Affine/IR/AffineValueMap.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" +#include "mlir/IR/BlockAndValueMapping.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/Dominance.h" +#include "mlir/IR/IntegerSet.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/PatternMatch.h" +#include "mlir/IR/Types.h" +#include "mlir/IR/Value.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Pass/PassManager.h" +#include "mlir/Transforms/DialectConversion.h" +#include "mlir/Transforms/Passes.h" +#include "mlir/Transforms/RegionUtils.h" +#include "mlir/Transforms/Utils.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SetVector.h" + +#include +#include +#include + +#define DEBUG_TYPE "fold-if" + +using namespace mlir; +using namespace llvm; +using namespace phism; + +static void addEligibleAffineIfOps(FuncOp f, + SmallVector &ifOps) { + f.walk([&](mlir::AffineIfOp ifOp) { + LLVM_DEBUG(dbgs() << "Num regions: " << ifOp.getNumRegions() + << ", num blocks: " + << ifOp.getBodyRegion().getBlocks().size() << '\n'); + // There should be a single region with a single block. + if (ifOp.hasElse() || ifOp.getBodyRegion().getBlocks().size() > 1) + return; + // TODO: other conditions + ifOps.push_back(ifOp); + }); +} + +static Value createAffineIfCond(mlir::AffineIfOp ifOp, OpBuilder &b) { + Location loc = ifOp.getLoc(); + + IntegerSet integerSet = ifOp.getIntegerSet(); + Value zeroConstant = b.create(loc, 0); + SmallVector operands(ifOp.getOperands()); + auto operandsRef = llvm::makeArrayRef(operands); + + Value cond = nullptr; + + for (unsigned i = 0, e = integerSet.getNumConstraints(); i < e; ++i) { + AffineExpr constraintExpr = integerSet.getConstraint(i); + bool isEquality = integerSet.isEq(i); + + auto numDims = integerSet.getNumDims(); + Value affResult = expandAffineExpr(b, loc, constraintExpr, + operandsRef.take_front(numDims), + operandsRef.drop_front(numDims)); + if (!affResult) + return nullptr; + + auto pred = isEquality ? CmpIPredicate::eq : CmpIPredicate::sge; + Value cmpVal = b.create(loc, pred, affResult, zeroConstant); + cond = cond ? b.create(loc, cond, cmpVal).getResult() : cmpVal; + } + + return cond ? cond : b.create(loc, /*value=*/1, /*width=*/1); +} + +/// The value to be store is either the original value to be stored, or the +/// current value at this given address. +static LogicalResult process(mlir::AffineStoreOp storeOp, Value cond, + BlockAndValueMapping &vmap, OpBuilder &b) { + Location loc = storeOp.getLoc(); + + Value memref = storeOp.getMemRef(); + AffineMap affMap = storeOp.getAffineMap(); + SmallVector mapOperands(storeOp.getMapOperands()); + + Value orig = b.create(loc, memref, affMap, mapOperands); + Value toStore = b.create( + loc, cond, vmap.lookup(storeOp.getValueToStore()), orig); + + b.create(loc, toStore, memref, affMap, mapOperands); + + return success(); +} + +/// TODO: filter invalid operations. +/// TODO: affine.load might load from invalid address. +static LogicalResult process(mlir::AffineIfOp ifOp, OpBuilder &b) { + OpBuilder::InsertionGuard guard(b); + b.setInsertionPointAfter(ifOp); + + // Turn the if-condition evaluation result to a single value. + // This implementation is inspired by AffineIfLowering from AffineToStandard. + Value cond = createAffineIfCond(ifOp, b); + if (!cond) + return failure(); + + BlockAndValueMapping vmap; + for (Operation &op : ifOp.getBody()->getOperations()) { + if (isa(op)) + continue; + + if (auto storeOp = dyn_cast(op)) { + if (failed(process(storeOp, cond, vmap, b))) + return failure(); + } else { + b.clone(op, vmap); + } + } + + ifOp.erase(); + + return success(); +} + +namespace { +struct FoldIfPass : PassWrapper> { + void runOnOperation() override { + ModuleOp m = getOperation(); + OpBuilder b(m.getContext()); + + SmallVector ifOps; + m.walk([&](FuncOp f) { addEligibleAffineIfOps(f, ifOps); }); + + for (mlir::AffineIfOp ifOp : ifOps) + if (failed(process(ifOp, b))) + LLVM_DEBUG(dbgs() << "Failed to process: " << ifOp << '\n'); + } +}; +} // namespace + +namespace phism { +void registerFoldIfPasses() { + + PassPipelineRegistration<>("fold-if", "Fold affine.if", + [&](OpPassManager &pm) { + pm.addPass(std::make_unique()); + // pm.addPass(createCanonicalizerPass()); + }); +} +} // namespace phism diff --git a/lib/mlir/Transforms/LoopTransforms.cc b/lib/mlir/Transforms/LoopTransforms.cc index d6b587c07e8..06b8991a707 100644 --- a/lib/mlir/Transforms/LoopTransforms.cc +++ b/lib/mlir/Transforms/LoopTransforms.cc @@ -113,7 +113,7 @@ struct InsertScratchpadPass BlockAndValueMapping vmap; vmap.map(parent.getInductionVar(), nextFor.getInductionVar()); - SetVector shouldClone; + llvm::SetVector shouldClone; b.setInsertionPointToStart(nextFor.getBody()); for (Operation &op : nextBlock->getOperations()) @@ -206,10 +206,10 @@ static bool isPointLoop(mlir::AffineForOp forOp) { return forOp->hasAttr("scop.point_loop"); } -static void getArgs(Operation *parentOp, SetVector &args) { +static void getArgs(Operation *parentOp, llvm::SetVector &args) { args.clear(); - SetVector internalOps; + llvm::SetVector internalOps; internalOps.insert(parentOp); parentOp->walk([&](Operation *op) { internalOps.insert(op); }); @@ -250,7 +250,7 @@ createPointLoopsCallee(mlir::AffineForOp forOp, int id, FuncOp f, b.setInsertionPointToStart(entry); // Grab arguments from the top forOp. - SetVector args; + llvm::SetVector args; getArgs(forOp, args); // Argument mapping for cloning. Also intialize arguments to the entry block. @@ -283,7 +283,7 @@ static CallOp createPointLoopsCaller(AffineForOp startForOp, FuncOp callee, return caller; } -using LoopTree = llvm::DenseMap>; +using LoopTree = llvm::DenseMap>; /// Returns true if itself or any of the descendants has been extracted. static bool greedyLoopExtraction(Operation *op, const int maxSpan, int &startId, @@ -345,7 +345,7 @@ static int extractPointLoops(FuncOp f, int startId, int maxSpan, OpBuilder &b) { b.setInsertionPoint(m.getBody(), std::prev(m.getBody()->end())); // Those point loops that has been visited and extracted. - SetVector extracted; + llvm::SetVector extracted; // Map from a point loop to its children. LoopTree loopTree; @@ -481,7 +481,8 @@ struct AnnotatePointLoopsPass /// --------------------- Redistribute statements --------------------------- -static void getAllScopStmts(FuncOp func, SetVector &stmts, ModuleOp m) { +static void getAllScopStmts(FuncOp func, llvm::SetVector &stmts, + ModuleOp m) { func.walk([&](mlir::CallOp caller) { FuncOp callee = dyn_cast(m.lookupSymbol(caller.getCallee())); if (!callee) @@ -494,7 +495,7 @@ static void getAllScopStmts(FuncOp func, SetVector &stmts, ModuleOp m) { } static void detectScopPeWithMultipleStmts(ModuleOp m, - SetVector &pes) { + llvm::SetVector &pes) { FuncOp top = getTopFunction(m); if (!top) return; @@ -507,7 +508,7 @@ static void detectScopPeWithMultipleStmts(ModuleOp m, if (!callee) return; - SetVector stmts; + llvm::SetVector stmts; getAllScopStmts(callee, stmts, m); if (stmts.size() >= 2) @@ -553,9 +554,9 @@ static bool hasOnlyReadByScopStmts(FuncOp f, ModuleOp m, Value memref) { /// Also assuming each scop.stmt will have its accessed memrefs once in its /// interface. static bool areScopStmtsSeparable(FuncOp f, ModuleOp m) { - SetVector visited; // memrefs visited. - SetVector conflicted; - SetVector visitedStmts; + llvm::SetVector visited; // memrefs visited. + llvm::SetVector conflicted; + llvm::SetVector visitedStmts; f.walk([&](mlir::CallOp caller) { FuncOp callee = dyn_cast(m.lookupSymbol(caller.getCallee())); if (!callee || !callee->hasAttr("scop.stmt")) @@ -641,7 +642,7 @@ distributeScopStmt(FuncOp stmt, FuncOp f, ModuleOp m, OpBuilder &b) { static LogicalResult distributeScopStmts( FuncOp f, SmallVectorImpl>> &dist, ModuleOp m, OpBuilder &b) { - SetVector stmts; + llvm::SetVector stmts; getAllScopStmts(f, stmts, m); // Need to duplicate the whole function for each statement. And within each @@ -670,7 +671,7 @@ struct RedistributeScopStatementsPass // ------------------------------------------------------------------- // Step 1: detect the scop.pe callee that has more than one scop.stmt. - SetVector pes; + llvm::SetVector pes; detectScopPeWithMultipleStmts(m, pes); if (pes.empty()) @@ -690,7 +691,7 @@ struct RedistributeScopStatementsPass // The condition is basically each caller refers to different memref. /// TODO: carry out alias analysis (not an issue for polybench) /// TODO: detailed dependence analysis to cover more cases. - SetVector pesToProc; + llvm::SetVector pesToProc; for (FuncOp pe : pes) { if (!areScopStmtsSeparable(pe, m)) { LLVM_DEBUG({ @@ -750,7 +751,7 @@ struct RedistributeScopStatementsPass /// --------------------- Loop merge pass --------------------------- static LogicalResult loopMergeOnScopStmt(FuncOp f, ModuleOp m, OpBuilder &b) { - SetVector stmts; + llvm::SetVector stmts; getAllScopStmts(f, stmts, m); if (!llvm::hasSingleElement(stmts)) { @@ -777,13 +778,13 @@ static LogicalResult loopMergeOnScopStmt(FuncOp f, ModuleOp m, OpBuilder &b) { // ---------------------------------------------------------------------- // Step 1: make sure there are no empty sets in loop domains. - SetVector erased; + llvm::SetVector erased; for (mlir::CallOp caller : callers) { SmallVector ops; getEnclosingAffineForAndIfOps(*caller.getOperation(), &ops); - FlatAffineConstraints cst; - getIndexSet(ops, &cst); + FlatAffineValueConstraints cst; + assert(succeeded(getIndexSet(ops, &cst))); if (!cst.findIntegerSample().hasValue()) { LLVM_DEBUG({ @@ -871,9 +872,9 @@ static LogicalResult loopMergeOnScopStmt(FuncOp f, ModuleOp m, OpBuilder &b) { // ---------------------------------------------------------------------- // Step 3: Affine analysis // Check if the innermost loops have no intersection. - SmallVector csts; + SmallVector csts; transform(innermosts, std::back_inserter(csts), [&](mlir::AffineForOp forOp) { - FlatAffineConstraints cst; + FlatAffineValueConstraints cst; cst.addInductionVarOrTerminalSymbol(forOp.getInductionVar()); LLVM_DEBUG(cst.dump()); @@ -883,7 +884,7 @@ static LogicalResult loopMergeOnScopStmt(FuncOp f, ModuleOp m, OpBuilder &b) { // Make every constraint has the same induction variable. for (unsigned i = 1; i < csts.size(); ++i) - csts[i].setIdValue(0, csts[0].getIdValue(0)); + csts[i].setValue(0, csts[0].getValue(0)); // Check if all the constraints share the same number of columns. for (unsigned i = 1; i < csts.size(); ++i) { @@ -897,7 +898,7 @@ static LogicalResult loopMergeOnScopStmt(FuncOp f, ModuleOp m, OpBuilder &b) { // Check if two loops have intersection. for (unsigned i = 0; i < csts.size(); ++i) for (unsigned j = i + 1; j < csts.size(); ++j) { - FlatAffineConstraints tmp{csts[i]}; + FlatAffineValueConstraints tmp{csts[i]}; tmp.append(csts[j]); if (tmp.findIntegerSample().hasValue()) { @@ -947,7 +948,7 @@ static LogicalResult loopMergeOnScopStmt(FuncOp f, ModuleOp m, OpBuilder &b) { loopToErase = loop2; // Set the new upper bound; - SetVector results; + llvm::SetVector results; for (AffineExpr expr : ubMap.getResults()) if (expr != ub) results.insert(expr); @@ -1092,15 +1093,19 @@ struct ScopStmtInlinePass } // namespace void phism::registerLoopTransformPasses() { - PassRegistration( - "annotate-point-loops", "Annotate loops with point/tile info."); - PassRegistration( - "extract-point-loops", "Extract point loop bands into functions"); - PassRegistration( + // PassRegistration( + // "annotate-point-loops", "Annotate loops with point/tile info."); + // PassRegistration( + // "extract-point-loops", "Extract point loop bands into functions"); + PassPipelineRegistration<>( "redis-scop-stmts", - "Redistribute scop statements across extracted point loops."); - PassRegistration("loop-merge", - "Merge loops by affine analysis."); + "Redistribute scop statements across extracted point loops.", + [](OpPassManager &pm) { + pm.addPass(std::make_unique()); + }); + PassPipelineRegistration<>( + "loop-merge", "Merge loops by affine analysis.", + [](OpPassManager &pm) { pm.addPass(std::make_unique()); }); PassPipelineRegistration<>( "improve-pipelining", "Improve the pipelining performance", diff --git a/lib/mlir/Transforms/PhismTransforms.cc b/lib/mlir/Transforms/PhismTransforms.cc index 33613c4b5f6..5723604e78a 100644 --- a/lib/mlir/Transforms/PhismTransforms.cc +++ b/lib/mlir/Transforms/PhismTransforms.cc @@ -12,7 +12,8 @@ void registerAllPhismPasses() { registerLoopTransformPasses(); registerExtractTopFuncPass(); registerArrayPartitionPasses(); - registerDependenceAnalysisPasses(); + // registerDependenceAnalysisPasses(); + registerFoldIfPasses(); } } // namespace phism diff --git a/lib/mlir/Transforms/Utils.cc b/lib/mlir/Transforms/Utils.cc index 2d00c90cb05..f1711b6dcb0 100644 --- a/lib/mlir/Transforms/Utils.cc +++ b/lib/mlir/Transforms/Utils.cc @@ -2,12 +2,212 @@ #include "phism/mlir/Transforms/Utils.h" +#include "mlir/Dialect/Affine/IR/AffineOps.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" +#include "mlir/Dialect/SCF/SCF.h" #include "mlir/Dialect/StandardOps/IR/Ops.h" +#include "mlir/Dialect/Vector/VectorOps.h" +#include "mlir/IR/AffineExprVisitor.h" +#include "mlir/IR/BlockAndValueMapping.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/IntegerSet.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" +#include "mlir/Transforms/Passes.h" using namespace mlir; using namespace llvm; using namespace phism; +/// Copied from AffineToStandard.cpp +namespace { +/// Visit affine expressions recursively and build the sequence of operations +/// that correspond to it. Visitation functions return an Value of the +/// expression subtree they visited or `nullptr` on error. +class AffineApplyExpander + : public AffineExprVisitor { +public: + /// This internal class expects arguments to be non-null, checks must be + /// performed at the call site. + AffineApplyExpander(OpBuilder &builder, ValueRange dimValues, + ValueRange symbolValues, Location loc) + : builder(builder), dimValues(dimValues), symbolValues(symbolValues), + loc(loc) {} + + template Value buildBinaryExpr(AffineBinaryOpExpr expr) { + auto lhs = visit(expr.getLHS()); + auto rhs = visit(expr.getRHS()); + if (!lhs || !rhs) + return nullptr; + auto op = builder.create(loc, lhs, rhs); + return op.getResult(); + } + + Value visitAddExpr(AffineBinaryOpExpr expr) { + return buildBinaryExpr(expr); + } + + Value visitMulExpr(AffineBinaryOpExpr expr) { + return buildBinaryExpr(expr); + } + + /// Euclidean modulo operation: negative RHS is not allowed. + /// Remainder of the euclidean integer division is always non-negative. + /// + /// Implemented as + /// + /// a mod b = + /// let remainder = srem a, b; + /// negative = a < 0 in + /// select negative, remainder + b, remainder. + Value visitModExpr(AffineBinaryOpExpr expr) { + auto rhsConst = expr.getRHS().dyn_cast(); + if (!rhsConst) { + emitError( + loc, + "semi-affine expressions (modulo by non-const) are not supported"); + return nullptr; + } + if (rhsConst.getValue() <= 0) { + emitError(loc, "modulo by non-positive value is not supported"); + return nullptr; + } + + auto lhs = visit(expr.getLHS()); + auto rhs = visit(expr.getRHS()); + assert(lhs && rhs && "unexpected affine expr lowering failure"); + + Value remainder = builder.create(loc, lhs, rhs); + Value zeroCst = builder.create(loc, 0); + Value isRemainderNegative = + builder.create(loc, CmpIPredicate::slt, remainder, zeroCst); + Value correctedRemainder = builder.create(loc, remainder, rhs); + Value result = builder.create(loc, isRemainderNegative, + correctedRemainder, remainder); + return result; + } + + /// Floor division operation (rounds towards negative infinity). + /// + /// For positive divisors, it can be implemented without branching and with a + /// single division operation as + /// + /// a floordiv b = + /// let negative = a < 0 in + /// let absolute = negative ? -a - 1 : a in + /// let quotient = absolute / b in + /// negative ? -quotient - 1 : quotient + Value visitFloorDivExpr(AffineBinaryOpExpr expr) { + auto rhsConst = expr.getRHS().dyn_cast(); + if (!rhsConst) { + emitError( + loc, + "semi-affine expressions (division by non-const) are not supported"); + return nullptr; + } + if (rhsConst.getValue() <= 0) { + emitError(loc, "division by non-positive value is not supported"); + return nullptr; + } + + auto lhs = visit(expr.getLHS()); + auto rhs = visit(expr.getRHS()); + assert(lhs && rhs && "unexpected affine expr lowering failure"); + + Value zeroCst = builder.create(loc, 0); + Value noneCst = builder.create(loc, -1); + Value negative = + builder.create(loc, CmpIPredicate::slt, lhs, zeroCst); + Value negatedDecremented = builder.create(loc, noneCst, lhs); + Value dividend = + builder.create(loc, negative, negatedDecremented, lhs); + Value quotient = builder.create(loc, dividend, rhs); + Value correctedQuotient = builder.create(loc, noneCst, quotient); + Value result = + builder.create(loc, negative, correctedQuotient, quotient); + return result; + } + + /// Ceiling division operation (rounds towards positive infinity). + /// + /// For positive divisors, it can be implemented without branching and with a + /// single division operation as + /// + /// a ceildiv b = + /// let negative = a <= 0 in + /// let absolute = negative ? -a : a - 1 in + /// let quotient = absolute / b in + /// negative ? -quotient : quotient + 1 + Value visitCeilDivExpr(AffineBinaryOpExpr expr) { + auto rhsConst = expr.getRHS().dyn_cast(); + if (!rhsConst) { + emitError(loc) << "semi-affine expressions (division by non-const) are " + "not supported"; + return nullptr; + } + if (rhsConst.getValue() <= 0) { + emitError(loc, "division by non-positive value is not supported"); + return nullptr; + } + auto lhs = visit(expr.getLHS()); + auto rhs = visit(expr.getRHS()); + assert(lhs && rhs && "unexpected affine expr lowering failure"); + + Value zeroCst = builder.create(loc, 0); + Value oneCst = builder.create(loc, 1); + Value nonPositive = + builder.create(loc, CmpIPredicate::sle, lhs, zeroCst); + Value negated = builder.create(loc, zeroCst, lhs); + Value decremented = builder.create(loc, lhs, oneCst); + Value dividend = + builder.create(loc, nonPositive, negated, decremented); + Value quotient = builder.create(loc, dividend, rhs); + Value negatedQuotient = builder.create(loc, zeroCst, quotient); + Value incrementedQuotient = builder.create(loc, quotient, oneCst); + Value result = builder.create(loc, nonPositive, negatedQuotient, + incrementedQuotient); + return result; + } + + Value visitConstantExpr(AffineConstantExpr expr) { + auto valueAttr = + builder.getIntegerAttr(builder.getIndexType(), expr.getValue()); + auto op = + builder.create(loc, builder.getIndexType(), valueAttr); + return op.getResult(); + } + + Value visitDimExpr(AffineDimExpr expr) { + assert(expr.getPosition() < dimValues.size() && + "affine dim position out of range"); + return dimValues[expr.getPosition()]; + } + + Value visitSymbolExpr(AffineSymbolExpr expr) { + assert(expr.getPosition() < symbolValues.size() && + "symbol dim position out of range"); + return symbolValues[expr.getPosition()]; + } + +private: + OpBuilder &builder; + ValueRange dimValues; + ValueRange symbolValues; + + Location loc; +}; +} // namespace + +/// Create a sequence of operations that implement the `expr` applied to the +/// given dimension and symbol values. +namespace phism { +mlir::Value expandAffineExpr(OpBuilder &builder, Location loc, AffineExpr expr, + ValueRange dimValues, ValueRange symbolValues) { + return AffineApplyExpander(builder, dimValues, symbolValues, loc).visit(expr); +} +} // namespace phism + static bool hasPeCaller(FuncOp f) { bool ret = false; f.walk([&](CallOp caller) { diff --git a/llvm b/llvm deleted file mode 160000 index 18e089d1c7e..00000000000 --- a/llvm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 18e089d1c7ea2d22e5589d838592017c6d525532 diff --git a/notebooks/search-codegen.ipynb b/notebooks/search-codegen.ipynb new file mode 100644 index 00000000000..a1c4164aeda --- /dev/null +++ b/notebooks/search-codegen.ipynb @@ -0,0 +1,271 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from pyphism.polybench import pb_flow" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "plt.rcParams['axes.facecolor'] = 'white'\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nameclooglcloogfmodule_nameloop_namepipeline_ii
0fdtd-2d-11kernel_fdtd_2d_PE0_1Loop 1.1.1.138
1fdtd-2d-11kernel_fdtd_2d_PE0_1Loop 1.1.1.238
2fdtd-2d-11kernel_fdtd_2d_PE0_1Loop 1.1.1.3.1.146
3fdtd-2d-11kernel_fdtd_2d_PE0_1Loop 1.1.1.46
4fdtd-2d-11kernel_fdtd_2d_PE0_1Loop 1.1.1.56
.....................
1jacobi-2d55kernel_jacobi_2d_PE0_1Loop 1.1.23
2jacobi-2d55kernel_jacobi_2d_PE0_1Loop 1.1.33
3jacobi-2d55kernel_jacobi_2d_PE0_1Loop 1.1.4.150
4jacobi-2d55kernel_jacobi_2d_PE0_1Loop 1.1.53
5jacobi-2d55kernel_jacobi_2d_PE0_1Loop 1.1.63
\n", + "

1140 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " name cloogl cloogf module_name loop_name \\\n", + "0 fdtd-2d -1 1 kernel_fdtd_2d_PE0_1 Loop 1.1.1.1 \n", + "1 fdtd-2d -1 1 kernel_fdtd_2d_PE0_1 Loop 1.1.1.2 \n", + "2 fdtd-2d -1 1 kernel_fdtd_2d_PE0_1 Loop 1.1.1.3.1.1 \n", + "3 fdtd-2d -1 1 kernel_fdtd_2d_PE0_1 Loop 1.1.1.4 \n", + "4 fdtd-2d -1 1 kernel_fdtd_2d_PE0_1 Loop 1.1.1.5 \n", + ".. ... ... ... ... ... \n", + "1 jacobi-2d 5 5 kernel_jacobi_2d_PE0_1 Loop 1.1.2 \n", + "2 jacobi-2d 5 5 kernel_jacobi_2d_PE0_1 Loop 1.1.3 \n", + "3 jacobi-2d 5 5 kernel_jacobi_2d_PE0_1 Loop 1.1.4.1 \n", + "4 jacobi-2d 5 5 kernel_jacobi_2d_PE0_1 Loop 1.1.5 \n", + "5 jacobi-2d 5 5 kernel_jacobi_2d_PE0_1 Loop 1.1.6 \n", + "\n", + " pipeline_ii \n", + "0 38 \n", + "1 38 \n", + "2 46 \n", + "3 6 \n", + "4 6 \n", + ".. ... \n", + "1 3 \n", + "2 3 \n", + "3 50 \n", + "4 3 \n", + "5 3 \n", + "\n", + "[1140 rows x 6 columns]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.read_csv('./tmp/phism/search-codegen.small/results.csv', index_col=0)\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABWsAAANKCAYAAAD88BxAAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAABxtUlEQVR4nOz9e7htd1Uf/r8HOUEQsOFyjJGAoYAXWiXoEeFBrdxarJdEq6k3GiU2+v2KhXop0a8X+BVbrHKpl6pBhKgooIhJoaAxgNSq4CFEEkALchNIyEFJCWqBwPj9sWfCzs4+56x99lx7ffY6r9fzrGevNedcY445P2vPPdbYc81V3R0AAAAAAFbrdqtOAAAAAAAAzVoAAAAAgCFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi3AJlX1OVV1VVXdWFX/bsu8b6+qP9phvK6q+82U247XDwAAq1RV76yqR606j+NRawOj0KwFuLX/kORV3X2X7v6ZYy2420ZsVT2kqi6vqr+tqiNV9VtVdcaJxgMAgJPBIo3VqnpAVR2uqg9Otz+oqgfsVY4AJ0qzFuDWPivJm/ZoXXdNcnGSs6b13pjkuXu0bgAAWGfvS/INSe6W5B5JLkvygpVmBLAAzVqASVW9MsnDk/xcVX24qr6kqi6rqg9V1euS3HfTsq+Z7v75tOy/nqb/YFVdW1Xvq6rHHWt93f3y7v6t7v5Qd/99kp9L8rBN67j70dYPAAD7yNlV9caq+j9V9cKqukOSVNVXT5cgu6Gq/riqvuDmJ1TVRVX1V9Plyd5cVV83Tf+8JL+Y5KFTHX7Ddivs7hu6+53d3UkqyceT3G9TfLU2MKQDq04AYBTd/YiqenWSX+/uX66qFyT5v0nOSHKfJL+X5B3Tsl9eVZ3kgd39tiSpqsck+YEkj5yWe/YOU/jy3Pqs3p8/2voBAGAfOS/JY7JR2/6vJN9eVa9N8itJvibJ4STfluSyqvqc7v5Ikr9K8mVJrkvyjUl+varu191vqarvTvKd3f2lx1vx1My9czZOVvuxTbPU2sCQnFkLsI2qOiXJv0ryY939d919TZJLjvO085I8t7uv6e6/S/LkHazvC7JRPP7gLtYPAAAj+pnufl93/22S/57k7CQXJvml7n5td3+8uy9J8pEkD0mS6RNo7+vuT3T3C5O8NcmDd7ri7j4tyT9K8vgkb0jU2sDYNGsBtncwG58++OtN0951nOd85tGWr6p7Tx/T+nBVfXjzk6YvKXt5kid09//cxfoBAGBE1226//fZONP1s5J8/3QJhBumM2DvlY2aOlX1bzZdIuGGJP80G9eevY1j1dpJMp1I8YtJfrWqPj1qbWBgmrUA2zuS5KZsFIw3u/dxnnPt0Zbv7nd3951vvt08vao+K8kfJPmP3f1ru1w/AADsF3+d5Ce6+7RNt0/t7t+cauRnZ+Ns2LtPZ8dek41rzyZJbw50tFp7i9sl+dQk94xaGxiYZi3ANrr740l+J8mTq+pTq+oBSc7fstj7k/zjTY9flI3rbz2gqj41yY8fax1Vdc8kr0zyc939iyewfgAA2K+eneS7py/1raq6U1V9VVXdJcmdstGQPZIkVfUd2Tiz9mbvT3JmVd3+aMGr6tFV9aCqOqWqPi3JM5J8MMlb1NrAyDRrAY7u8dn4iNZ1SZ6X5Llb5j85ySXTR7PO6+6XJ3lWNhqwb5t+Hst3ZqPZ++SjfGzreOsHAIB9qbsPJ/m3SX4uG03UtyX59mnem5M8PcmfZKMx+/nZ+GKym70yG1/Me11VfeAoqzgtyW8m+T/Z+LKy+yZ5THf/32m+WhsYUnX38ZcCAAAAAGCpnFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAAeWGbyq3pnkxiQfT3JTdx+qqrsleWGSs5K8M8l53f3BY8W5xz3u0WedddYyUwUAYIvXv/71H+jug6vOg+VRZwMA7L1j1dlLbdZOHt7dH9j0+KIkV3T306rqounxk44V4Kyzzsrhw4eXmSMAAFtU1btWnQPLpc4GANh7x6qzV3EZhHOSXDLdvyTJuSvIAQAAAABgKMtu1naS36+q11fVhdO007v72un+dUlO3+6JVXVhVR2uqsNHjhxZcpoAAAAAAKu17MsgfGl3v7eqPj3J5VX1F5tndndXVW/3xO6+OMnFSXLo0KFtlwEAAAAAWBdLPbO2u987/bw+yUuSPDjJ+6vqjCSZfl6/zBwAAAAAAPaDpTVrq+pOVXWXm+8n+edJrklyWZLzp8XOT3LpsnIAAAAAANgvlnkZhNOTvKSqbl7Pb3T3K6rqz5K8qKouSPKuJOctMQcAAAAAgH1hac3a7n57kgduM/1vkjxyWesFAAAAANiPlnrNWgAAAAAAFqNZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAADqw6AdbTWRe97Jjz3/m0r9p1jEXizBFjHY20b+d4raybkfat36HtjbJvjc/2Rtq3xoj9aJRj3Ei5rNv2jJSL7dn/uazb9oyUi+3Z/7ms2/aMlMu6bc9mzqwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAARxYdQIn4qyLXnbcZd75tK/adZw5YiwSZ6+2Zy9zWTcj7ds5XrfrZqR963doe6PsW+OzvZH2rWPcbdm3AABw8nBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAMA+VFWnVdVvV9VfVNVbquqhVXW3qrq8qt46/bzrqvMEAGBxmrUAALA//dckr+juz03ywCRvSXJRkiu6+/5JrpgeAwCwT2jWAgDAPlNV/yjJlyd5TpJ090e7+4Yk5yS5ZFrskiTnriI/AABOjGYtAADsP/dJciTJc6vqDVX1y1V1pySnd/e10zLXJTl96xOr6sKqOlxVh48cObKHKQMAcDyatQAAsP8cSPKFSX6hux+U5O+y5ZIH3d1JeusTu/vi7j7U3YcOHjy4J8kCALAYzVoAANh/3pPkPd392unxb2ejefv+qjojSaaf168oPwAAToBmLQAA7DPdfV2Sv66qz5kmPTLJm5NcluT8adr5SS5dQXoAAJygA6tOAAAAOCHfm+T5VXX7JG9P8h3ZOBnjRVV1QZJ3JTlvhfkBALBDmrUAALAPdfdVSQ5tM+uRe5wKAAAzcRkEAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADCApTdrq+qUqnpDVb10enyfqnptVb2tql5YVbdfdg4AAAAAAKPbizNrn5DkLZse/2SSZ3b3/ZJ8MMkFe5ADAAAAAMDQltqsraozk3xVkl+eHleSRyT57WmRS5Kcu8wcAAAAAAD2g2WfWfusJP8hySemx3dPckN33zQ9fk+Se273xKq6sKoOV9XhI0eOLDlNAAAAAIDVWlqztqq+Osn13f36E3l+d1/c3Ye6+9DBgwdnzg4AAAAAYCwHlhj7YUm+tqr+ZZI7JPm0JP81yWlVdWA6u/bMJO9dYg4AAAAAAPvC0s6s7e4f6u4zu/usJN+U5JXd/a1JXpXkG6bFzk9y6bJyAAAAAADYL5Z9zdrtPCnJ91XV27JxDdvnrCAHAAAAAIChLPMyCLfo7lcnefV0/+1JHrwX6wUAAAAA2C9WcWYtAAAAAABbaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADCAA6tOAAAA2LmqemeSG5N8PMlN3X2oqu6W5IVJzkryziTndfcHV5UjAAA748xaAADYvx7e3Wd396Hp8UVJruju+ye5YnoMAMA+oVkLAADr45wkl0z3L0ly7upSAQBgpzRrAQBgf+okv19Vr6+qC6dpp3f3tdP965KcvvVJVXVhVR2uqsNHjhzZq1wBAFiAa9YCAMD+9KXd/d6q+vQkl1fVX2ye2d1dVb31Sd19cZKLk+TQoUO3mQ8AwOo4sxYAAPah7n7v9PP6JC9J8uAk76+qM5Jk+nn96jIEAGCnNGsBAGCfqao7VdVdbr6f5J8nuSbJZUnOnxY7P8mlq8kQAIAT4TIIAACw/5ye5CVVlWzU9L/R3a+oqj9L8qKquiDJu5Kct8IcAQDYIc1aAADYZ7r77UkeuM30v0nyyL3PCACAObgMAgAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABrC0Zm1V3aGqXldVf15Vb6qqp0zT71NVr62qt1XVC6vq9svKAQAAAABgv1jmmbUfSfKI7n5gkrOTPKaqHpLkJ5M8s7vvl+SDSS5YYg4AAAAAAPvC0pq1veHD08NTp1sneUSS356mX5Lk3GXlAAAAAACwXyz1mrVVdUpVXZXk+iSXJ/mrJDd0903TIu9Jcs+jPPfCqjpcVYePHDmyzDQBAAAAAFZuqc3a7v54d5+d5MwkD07yuTt47sXdfai7Dx08eHBZKQIAAAAADGGpzdqbdfcNSV6V5KFJTquqA9OsM5O8dy9yAAAAAAAY2dKatVV1sKpOm+7fMcmjk7wlG03bb5gWOz/JpcvKAQAA1tV0ybE3VNVLp8f3qarXVtXbquqFVXX7VecIAMDOLPPM2jOSvKqq3pjkz5Jc3t0vTfKkJN9XVW9Lcvckz1liDgAAsK6ekI2TIW72k0me2d33S/LBJBesJCsAAE7YgeMvcmK6+41JHrTN9Ldn4/q1AADACaiqM5N8VZKfyMaJEJXkEUm+ZVrkkiRPTvILK0kQAIATsifXrAUAAGb1rCT/Icknpsd3T3JDd980PX5PknuuIC8AAHZBsxYAAPaRqvrqJNd39+tP8PkXVtXhqjp85MiRmbMDAGA3NGsBAGB/eViSr62qdyZ5QTYuf/Bfk5xWVTdf5uzMJO/d7sndfXF3H+ruQwcPHtyLfAEAWJBmLQAA7CPd/UPdfWZ3n5Xkm5K8sru/NcmrknzDtNj5SS5dUYoAAJwgzVoAAFgPT8rGl429LRvXsH3OivMBAGCHDhxtRlV94bGe2N1Xzp8OAACsv7lq7e5+dZJXT/ffnuTBu80NAIDVOWqzNsnTjzGvs3FtLAAAYOfU2gAA3MZRm7Xd/fC9TAQAAE4Wam0AALZzrMsgfP2xntjdvzN/OgAAsP7U2gAAbOdYl0H4mmPM6yQKSAAAODFqbQAAbuNYl0H4jr1MBAAAThZqbQAAtnO7VScAAAAAAIBmLQAAAADAEDRrAQAAAAAGcNxmbVV9alX9aFU9e3p8/6r66uWnBgAA602tDQDAZoucWfvcJB9J8tDp8XuTPHVpGQEAwMlDrQ0AwC0Wadbet7v/S5KPJUl3/32SWmpWAABwclBrAwBwi0WatR+tqjsm6SSpqvtm47//AADA7qi1AQC4xYEFlvnxJK9Icq+qen6ShyX59mUmBQAAJwm1NgAAtzhus7a7L6+qK5M8JBsfyXpCd39g6ZkBAMCaU2sDALDZImfWJskdknxwWv4BVZXufs3y0gIAgJOGWhsAgCQLNGur6ieT/Oskb0ryiWlyJ1FAAgDALqi1AQDYbJEza89N8jnd7YsOAABgXudGrQ0AwOR2Cyzz9iSnLjsRAAA4Cam1AQC4xSJn1v59kquq6ookt/zHv7v/3dKyAgCAk4NaGwCAWyzSrL1sugEAAPNSawMAcIvjNmu7+5K9SAQAAE42am0AADY7arO2ql7U3edV1dXZ+EbaW+nuL1hqZgAAsKbU2gAAbOdYZ9Y+Yfr51XuRCAAAnETU2gAA3MZRm7Xdfe308117lw4AAKw/tTYAANs51mUQbswnP5JV08+e7nd3f9qScwMAgLWk1gYAYDvHOrP2LnuZCAAAnCzU2gAAbOd2iyxUVV9aVd8x3b9HVd1nuWkBAMDJQa0NAMDNjtusraofT/KkJD80Tbp9kl9fZlIAAHAyUGsDALDZImfWfl2Sr03yd0nS3e9L4mNbAACwe2ptAABusUiz9qPd3Zm+AKGq7rTclAAA4KSh1gYA4BaLNGtfVFW/lOS0qvq3Sf4gybOXmxYAAJwU1NoAANziwPEW6O6frqpHJ/lQks9O8mPdffnSMwMAgDWn1gYAYLPjNmsnVye5YzY+nnX18tIBAICTjlobAIAkC1wGoaq+M8nrknx9km9I8qdV9bhlJwYAAOtOrQ0AwGaLnFn7g0ke1N1/kyRVdfckf5zkV5aZGAAAnATU2gAA3GKRLxj7myQ3bnp84zQNAADYHbU2AAC3WOTM2rcleW1VXZqN62idk+SNVfV9SdLdz1hifgAAsM7U2gAA3GKRZu1fTbebXTr9vMv86QAAwElFrQ0AwC2O26zt7qfsRSIAAHCyUWsDALDZUZu1VfWs7n5iVf33bHwk61a6+2uXmhkAAKwptTYAANs51pm1vzb9/Om9SAQAAE4iam0AAG7jWM3aN1XVE5PcL8nVSZ7T3TftSVYAALDedlVrV9Udkrwmyadko6b/7e7+8aq6T5IXJLl7ktcneWx3f3Tu5AEAWI7bHWPeJUkOZaN4/MokT9+TjAAAYP3tttb+SJJHdPcDk5yd5DFV9ZAkP5nkmd19vyQfTHLBbBkDALB0xzqz9gHd/flJUlXPSfK6vUkJAADW3q5q7e7uJB+eHp463TrJI5J8yzT9kiRPTvILM+QLAMAeONaZtR+7+Y7LHwAAwKx2XWtX1SlVdVWS65NcnuSvktywKd57ktxzl3kCALCHjnVm7QOr6kPT/Upyx+lxZeOf+Z+29OwAAGA97brW7u6PJzm7qk5L8pIkn7vIiqvqwiQXJsm9733vE0gdAIBlOWqztrtP2ctEAADgZDFnrd3dN1TVq5I8NMlpVXVgOrv2zCTv3Wb5i5NcnCSHDh3qufIAAGD3jnUZBAAAYEBVdXA6ozZVdcckj07yliSvSvIN02LnJ7l0JQkCAHBCjnUZBAAAYExnJLmkqk7JxgkYL+rul1bVm5O8oKqemuQNSZ6zyiQBANgZzVoAANhnuvuNSR60zfS3J3nw3mcEAMAcXAYBAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAAJbWrK2qe1XVq6rqzVX1pqp6wjT9blV1eVW9dfp512XlAAAAAACwXyzzzNqbknx/dz8gyUOSfE9VPSDJRUmu6O77J7liegwAAAAAcFJbWrO2u6/t7iun+zcmeUuSeyY5J8kl02KXJDl3WTkAAAAAAOwXe3LN2qo6K8mDkrw2yendfe0067okpx/lORdW1eGqOnzkyJG9SBMAAAAAYGWW3qytqjsneXGSJ3b3hzbP6+5O0ts9r7sv7u5D3X3o4MGDy04TAAAAAGClltqsrapTs9GofX53/840+f1VdcY0/4wk1y8zBwAAAACA/WBpzdqqqiTPSfKW7n7GplmXJTl/un9+kkuXlQMAAAAAwH5xYImxH5bksUmurqqrpmk/nORpSV5UVRckeVeS85aYAwAAAADAvrC0Zm13/1GSOsrsRy5rvQAAAAAA+9HSv2AMAAAAAIDj06wFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAA+0xV3auqXlVVb66qN1XVE6bpd6uqy6vqrdPPu646VwAAFqdZCwAA+89NSb6/ux+Q5CFJvqeqHpDkoiRXdPf9k1wxPQYAYJ/QrAUAgH2mu6/t7iun+zcmeUuSeyY5J8kl02KXJDl3JQkCAHBCNGsBAGAfq6qzkjwoyWuTnN7d106zrkty+jbLX1hVh6vq8JEjR/YuUQAAjkuzFgAA9qmqunOSFyd5Ynd/aPO87u4kvfU53X1xdx/q7kMHDx7co0wBAFiEZi0AAOxDVXVqNhq1z+/u35kmv7+qzpjmn5Hk+lXlBwDAzmnWAgDAPlNVleQ5Sd7S3c/YNOuyJOdP989Pcule5wYAwIk7sOoEAACAHXtYkscmubqqrpqm/XCSpyV5UVVdkORdSc5bTXoAAJwIzVoAANhnuvuPktRRZj9yL3MBAGA+LoMAAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGMDSmrVV9StVdX1VXbNp2t2q6vKqeuv0867LWj8AAAAAwH6yzDNrn5fkMVumXZTkiu6+f5IrpscAAAAAACe9pTVru/s1Sf52y+Rzklwy3b8kybnLWj8AAAAAwH6y19esPb27r53uX5fk9D1ePwAAAADAkFb2BWPd3Un6aPOr6sKqOlxVh48cObKHmQEAAAAA7L29bta+v6rOSJLp5/VHW7C7L+7uQ9196ODBg3uWIAAAAADAKux1s/ayJOdP989Pcukerx8AAAAAYEhLa9ZW1W8m+ZMkn1NV76mqC5I8Lcmjq+qtSR41PQYAAAAAOOkdWFbg7v7mo8x65LLWCQAAAACwX63sC8YAAAAAAPgkzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAMA+U1W/UlXXV9U1m6bdraour6q3Tj/vusocAQDYOc1aAADYf56X5DFbpl2U5Iruvn+SK6bHAADsI5q1AACwz3T3a5L87ZbJ5yS5ZLp/SZJz9zInAAB2T7MWAADWw+ndfe10/7okp2+3UFVdWFWHq+rwkSNH9i47AACOS7MWAADWTHd3kj7KvIu7+1B3Hzp48OAeZwYAwLFo1gIAwHp4f1WdkSTTz+tXnA8AADukWQsAAOvhsiTnT/fPT3LpCnMBAOAEaNYCAMA+U1W/meRPknxOVb2nqi5I8rQkj66qtyZ51PQYAIB95MCqEwAAAHamu7/5KLMeuaeJAAAwK2fWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxgJc3aqnpMVf1lVb2tqi5aRQ4AALCO1NoAAPvXnjdrq+qUJD+f5CuTPCDJN1fVA/Y6DwAAWDdqbQCA/W0VZ9Y+OMnbuvvt3f3RJC9Ics4K8gAAgHWj1gYA2Mequ/d2hVXfkOQx3f2d0+PHJvmS7n78luUuTHLh9PBzkvzlcULfI8kHdpneKDHksrwYclleDLksL8ZIuazb9oyUy7ptz0i5rNv27GUun9XdB3e5HvbQIrX2iursueKMEmOkXNZte0bKZd22Z6Rc1m17Rspl3bZnpFxsj1zmjHHUOvvALle+NN19cZKLF12+qg5396HdrHOUGHJZXgy5LC+GXJYXY6Rc1m17Rspl3bZnpFzWbXtGy4X9ZxV19lxxRokxUi7rtj0j5bJu2zNSLuu2PSPlsm7bM1IutkcuexVjFZdBeG+Se216fOY0DQAA2B21NgDAPraKZu2fJbl/Vd2nqm6f5JuSXLaCPAAAYN2otQEA9rE9vwxCd99UVY9P8ntJTknyK939phlCL/xRrn0QY64465bLum3PXHFGiTFXnHXLxfYsL8665bJu2zNXnFFizBVnHXNhIEuqtdftdWt7lhdn3XJZt+2ZK84oMeaKs265rNv2zBVnlBhzxRklxlxx1i2XXcXY8y8YAwAAAADgtlZxGQQAAAAAALbQrAUAAAAAGIBmLQAAAADAAPb8C8bWUVV9epKHJfnMJP+Q5Jokh7v7E3sZAxiD3+exGR+A/WOuY7ZjP6wHv8vjM0awe/v2C8aq6lCSL8utDwCXd/cH9ypOVT08yUVJ7pbkDUmuT3KHJJ+d5L5JfjvJ07v7Q8uMMdf2zBWjqh6a5NumGGdsivGyJL/e3f9nr+JU1ZlJvmmb7XlZkpcv+gdjjjgz7pc5xniOfTvX9syxb4cYn7l+nwd73d4hyVdvF2PRbxefI8Yc2zPn8Xag1+1c+3aOcZ7ldTvFWvnfIcc4Tnarriln/Js657F/5e8/puc7xi13m1Y+PnPFGaym3NX2qOOWG2em7RnmvdCMr/21eS+0KY5j3BLizF1r77tmbVV9R5LvTfKOJK/PrQ8AD8vGzvjR7n73suNU1U8l+dntlqmqA9n4hTylu1+8zBgzbs8cMV6e5H1JLk1yeEuMhyf5miTP6O7LjrM9u45TVc9Ncs8kLz1KjC9KclF3v+Y4uew6zkzbM9drf45c5hrnOfbtEOMzxZnjmDDS6/YpU86vzm1fcw+f7n9/d79xmTFm3J65jrejvG7n2rdzjPNcr9sh/g45xnEyG6imnOuYPcff5pHefzjGLWmbRhmfGbdnpJpyju1Rxy0pzoyvlSHeC824Pev2XsgxbklxllJrd/e+uiX5niR3PMb8s5M8cq/ijHKbY3tminGPBXLdk2WS/NPjzL99kvstsJ5dx5lpe+Z67c+Ry1xjOMe+HWJ85roN9rr9quPM//Qkh5YdY879MsoYjTI+M47zXK/bIf4OOca5ncy3mX4P1dnL27eOccvbL0OMz4zbM1JNOczfoVFet3PUX3PFmeu1sk7jM+O+Hea9kGPcUvft7Me4fXdm7UiqqpJ8Y5LOxin9j0hyTpK/SPKLfYLXZKmqV3b3I2ZLlNlV1Rd295WrzoPtjTg+VfUd3f3cVecxgqr69O6+ftV5bGZ8bm3EMeKTRjzGwdyWVWdPsdXaA3OMG99oY6SOu7UR6zhj9Ekjjg+3NsIx7narXPncqurHdrj8v6iqC6rqrC3TH7dgiJ9Pcl6Sxyb5tSTfneTPknx5kmcumMMbt9yuTvKwmx/vYFvuseXxt1XVz1TVhVOxuys73bdHiXH1Dpa9V1W9oKr+Z1X9cFWdumne7y4Y43Or6uVV9bKqum9VPa+qbqiq11XV5+0gly/ccvuiJJdV1YOq6gsXjPG4TffPrKorplz+uKo+e9FcjhF/1+MzxVlojOYYn2nZXY/RfhifyVN2G2D6eMWiy35aVT2tqn6tqr5ly7z/tmCMz6iqX6iqn6+qu1fVk6vq6qp6UVWdsWCMu2253T3J66rqrlV1tx1sz2M23T+tqp4zHSd/o6pOXzTOMex6fJLFx2iU8Zni7HqM9mB89vTvkGMcbG8nv4c1QJ09rW/XtXYtuc6eYjrGneAxboozfK296PhMy3ovtDPquMHruOzhe6E5xmda9qR5L+QYN94xbq3OrK2qd3f3vRdc9j9n47ocV2bj+hHP6u6fneZd2d3HHZSqurq7P396YV2X5Izu/mhtXI/lyu7+ggViXJbkQ0memo0LEFeS/5nkS5Oku9+14PbcknNV/Ug2Lmr8G9m4Psp7uvvfLxLnGPEX2rdV9fVHm5WNsyAOLri+y5O8OMmfJrkgG9cJ+Zru/puqekN3P2iBGK9J8lNJ7pzkaUmelOSF2dgnT+zuRy6YyyemPD6yafJDpmm9yJkZW8bnRUn+IMkvZ+MMkccvmssx4u/ktb/rMZpjfKY4ux6jkcanjv6mr5J8dnd/ygIxjnbsqSQv7e5FC4MXJ3lrNvbD45J8LMm3dPdHdnCMe0U2Loh+pyTfkuT52TiunJvkUd19zgIxPpFk63HszCTvycb4/OMFt2fzGP1yNo65z07y9Un+WXefu0CMXY/PFGfXYzTK+Exxdj1Gc4zPAuvYs79DjnGwvR38Hg5RZ09xdl1rL7vOnuI6xn3Sjo5xU5wham3vhY4aY9fjo447apxh6rhR3gvNMT5TnLV6L3Sc+I5xo9XavYNrJoxwy0axtd3txiQ37SDO1UkOTPdPS/I/kjxzevyGBWO8YdP9V2yZd9UOcvm6JK9J8rXT47efwH7ZnMuVSe403T81ydV7tW+zcSB8XpLnbnO7cQfbc9WWx9+W5E3Z+BbJK09gn7xty7yFYkzL/qskf5jkKzdNe8cOx+fKTfe3btuir7e5Xvu7HqM5xmeuMRplfKZl35+Na/181pbbWUnet2CMjyd5ZZJXbXP7hx3ksnU7/r8k/yvJ3U/wd+jdx4p/jBjfn+QVST7/RMdngTFaNJddj89cYzTK+Mw1RnOMz7TsEH+HttkGxzi3k+Y20+/hMHX2tPyuau3MUGfPuG8d47aPM0StPcf4zDVGc4zPXGM00/io47aPM1IdN8R7oTnGZ64xmmN85hqjOMYdLc4Qx7ittwPZf25I8sXd/f6tM6rqr3cQ50B335Qk3X1DVX1Nkour6reycQHhRVxXVXfu7g939+bT0j8jyUcXTaS7X1JVv5/kP1bVBTtY/2Z3rKoHZePSFqd0999NsT9WVR9fMMYN2f2+fWOSn+7ua7aJ8agFYyTJqVV1h+7+v0nS3b9eVdcl+b1s/GdrEadsuv+MLfMW3sfd/eKq+r1sjM/jsnHA7UWfPzmzqn4mG/9xOlhVp3b3x6Z5px7jeZvdkHle+3OM0Rzjk8wwRgONT7LxDZJ37u6rts6oqlcvGOMtSb6ru9+6TYydjPOnVNXterqmX3f/RFW9NxtvVu+8YIzbbbr/q8eYd1Td/fSqemGSZ075/3h2Pj5J8ulV9X3ZGKNPq6rq6a/forlknvFJ5hmjIcZnWvccYzTH+CTj/B1yjONkdkN2/3s4TJ095bDbWnuOOjtxjLuNmY5xyTi1tvdC25tjfNRx2xisjhvlvdAc45Os33uhG+IYdxsDHeNuZT82a381G/+duc0LLBunpC/qr6rqn3X3HyZJd388yQVV9dRsdNaPq7u/8iizbszGqdcLm4q+76uqByZ56E6eO7k2n3yB/m1VndHd19bGNVFuWjDGHPv2idn478x2vm7BGMnGKeNfko3/cCRJuvsPquobk/yXBWP8/KYi/5Zr01TV/bJxWvrCuvvDSf79VKhfkp0d5JPkBzfdPzw9/4PTG47LFowx12v/idn9GM0xPslMYzTI+KS7LzjGvG852rwtnpyj/8H93kVzSfLfs/FlLLfsx+5+3vRH8GcXjHHppvH5kZsnTuPzvxdNpLvfk+Qbq+prk1ye5FMXfe4mz05yl+n+JUnukeTINEZXLZjHHOOTzDNGw4zPtO7djtGux2cyyt8hxzhOZnP8Hg5VZ0/xdlNrz1FnJ45x25rhGJeMU2s/Md4LbWfX46OOO7pR6riB3gvNMT7Jmr0XimPcUY1wjNtqra5ZuxNVdcck6e5/2GbePbv7vXuf1fyq6pQkn9Ldf7/qXNZFVVWSu3T30Q5SrNBo41NVF3b3xavOYxTTsfe+2/03dhWMz22NNkbc2mjHONiOOpsT5Rg3vpHGSB13W6PVccbo1kYbH25rlGPcTk5pXyvd/Q/bFZDTvLUoIJONMxl2U0BW1ZNnTGct9IaVFyeJ8dnOSOMz+e5VJzCS6dg7UnFifLYYbYwc525twGMc3IY6e3GOcbc22jHO+NzWYGOkjttitDouxuhWRhsfx7jbGuUYd9I2a1nY1646AY7J+IyvVp0Ax2R8xuc4B6wzx7ixGZ+xqePGZ4zG5hg3KM1ajsfBdWzGZ3xfs+oEOCbjMz7HOWCdOcaNzfiMTR03PmM0Nse4QWnWLkFVvWW6PX6VMWbyRXMEqapzqupLRoizZrnMMj4z5bJu+3aWGNMF5Xetqg5V1WeOEGeUGHPEmWt85shlpBiD5TLM3yHHOFi9uWpktfZyYqxhLsOMz1xx1ikXddz4uYz0Xmjd9u1McRzjBs3lwG5WPJKqest09+e7++dWGae7P6+q7pGNb7c7IXPESHa/Pd39id2sf5MvSfL5VXXgGN/uu1dx1iaXGcdn17nMGGOkXObanjl8b5IvqKr/3d3/esVxRokxZ5w5jLJfRtq3u44x2N8hxzhOWnPUyKPU2XPFmWl7HOMGzmWw8ZkrzjrmMoch6p6ZYoyWyxxG2p61ycUxbtxcqrtPcJ3jubng6u6XjRBnFOu2PXAyq6q7dPeNI8QZJcacceYwyn4Zad+OND7AiZujply3unTdtgdOdqPUPeq47Y20PeuWC2NZq8sgdPcH5iiU5ohTVVfvNo85YiTz7ZfdqqpHjxJnHXPZ4To/raruu830L9jLGCPlMtf2HCX2jj6eUlWfUVWfMd0/WFVfX1X/JEl28kd4jjijxJgzzjZx5/go03+6+f6J5jJKjNFy2a2qus/0WvncVcYYKZe5toeTzxw15Sh19lxxRqmzk7Hq0nXLZYfrW6saebRctnn+juu4UerbkWrkZdXZU7xd1doj1aXrlsturVuNPFIu63Zm7dXd/fl7Faeqvv5os5L8Yncf3IsYC6xjV/ulqs5Jcl13v3aXeby7u++9mxhzxVmnXHY6PlV1XpJnJbk+yalJvr27/2yad2V3f+FexBgpl7m25xjxL0nyBUmO+/GUqvquJBdl4xjwk0m+Pck1Sb40yX/p7ucsuM5dxxklxpxxjhJ74fGZlv+ZrZOSPDbJryZJd/+7/RJjtFyOEX/h41xV/W53n7vpec9K8uokD0vyn7r7eXsRY6Rc5toemKPW3ss6e844x4g/xz4ZptZepxp5rjg7/Bu0VjXyaLkcJfZO67gh6tuRauRl1tlT/J28FxqmLl23XI4Re0/r7LnirGMum+27a9Yep+D6jD2O88Ikz0+yXcf7DnsYY7b9chQLX2ejqi47Rh53X3SFc8RZx1yOYqfXQfnhJF/U3ddW1YOT/FpV/VB3v2TKZxFzxBgpl7m2Z1vdfX6SVNVdFlj88Un+SZI7JnlXkvt193VVddckr0qyaLE0R5xRYswZ5zZ2OD5J8nVJ/jDJ7+eTr49vSvL6Hax2lBij5XI0OznOfdam+09K8ojufkdtfFT5iiTPW2B9c8QYKZe5toeTwBw15UB19ixxllxnJ3tca69jjTxQrb1uNfJoudzGCdRxo9S3I9XIS6uzkx2P0Uh16brlcjR7XWfPFWcdc7nFvmvWZqDCLckbk/x0d1+zdUZVPWoPYyTz7Zfb6O4f3sHiX5bk25J8eMv0SvLgPY6zjrncxg7HJ0lO6e5rp+e+rqoenuSlVXWvbP/6WVaMkXKZa3tS08eHpgLnYDbG/i+7+0292MdTPtbdf5/k76vqr7r7uineB6tqJ7nMEWeUGHPGuZWq+k83/w4tOD5J8oAk/zHJY5L8QHe/r6p+vLsv2cGqR4kxWi7b2uFxbvPr4UB3v2OK8YGqWvRLFOaIMVIuc20PJ4dRTkaYq0aeI87S6uxkJbX2OtbIo9Ta61YjD5XLDHV2Mk59O1KNvJQ6OzmhWnukunTdctnWCursueKsYy6fDHIiT1qxkQq3Jyb50FHmfd0exkjm2y/bqqpHd/flCyz6p0n+vrv/cJsYf7mDVc4RZ+1yqapPS3Kwu/9qy/Qv6O43Lhjmxqq6780xeuM/3F+R5Hez8R/VvYoxUi6zbE9t+ghRVW3+CNF/rqpFP0LUVXVqd38syVdtin2H7Ow643PEGSXGLHHqKB8fqqo7J4t/fGgqNJ9YVV+U5PlV9bJFcxgtxmi5bFVV90nyoCRv7u6/WPBpD6yqD2VjfD+lqs6Yfqdvn+SUPYwxUi5zbQ8nh1FORnhi5qmR54iz1Dp7irOXtfba1chzxZmh1l63GnmYXGaqs5Nx6tthauS5cpmj1h6pLl23XLZaYZ09V5x1zOWTuntf3bLx37N7H2Xeob2OM8pt2duT5N2r3saT/ZbkvCTvS3JVkjcl+eJN867cQZwHJrn/NtNPTfKtexVjpFxm3J6rk3xqNj5u9+EknzFNv2uSqxaMce9s/Ddu6/R7JnnUDnLZdZxRYsyYy18n+fUk/ybJ+dPtyM33F81lS8xK8j1Jfv1Enj9SjBFySfK7m+6fk+QdSZ6b5H9n4/p2u9m205I8dNUxRsplru1xW69bZqgp54gx0m0vtidq7ZXfMkOtPWNNOVJ9O0QumaHOnpYfor6dI8aAucxaa0eNPGucDF5nzxVnXXJZqy8YG0HN82VEu45xgus91rWeHtHdd1ogRvVxXlR7tcy65VJVVyX5yv7ktZ5+NckPdfdLquoN3f2gY8WfOZd127dz5XrL725V/Xl3P3DTvIXGyL5dai53ycbHhz49n/z40Nu7+x8fK+6SchkixoC53PJ7UlV/nI03cLdc72nz79Qyc1m3fTtXrrBqc9XIJ3utvW7HuBlzuSq7rLUH2561ymWOOnvGXIaIMWAuu661B9uedctliDp7rmXWMZfNdv0RxRFU1ZUDxdnVBdJnjHEi2/NlSX4pydO3uW29/tPRvKqqvreqbvWNq1V1+6p6RG18E+T5exRn3XK51bWekjw8yY9U1b/Lzq47Ncr2jJTLXNvTVXXqdP9EP0Jk3y4pTnff2N1PzMYx7flV9QM5sb+Do+yXYfbtjLlsPpbd6npPSRa93tNI2zNKLnNtDyepmqFGniNGZqqR54hzgtszSq29bse4ueLMUWuPtD3rlsscdfZcuYwSY6hcZqq1h9meNcxllDp7rjjrmMsn9QmezjvSLckbRomT5KkjxDiR7Uny8iQPP8q81ywY4w5J/t8k/ysbHyN6c5K3Z+NbJZ+d5EF7FWfdcknyx0nuu2XaXbLx7YIf2cE4D7E9I+Uy4/bM8TEk+3aJcTbF281H9bfL5R0z7Jc9jzFgLh/PxrUlb0zy0SRnTNNvn+SN++m1P1Iuc//+uJ18t8xTI88RY64aeY56fcfbk0Fq7XU7xs2Yy65r7cG2Z61yyXwf1R9le9ZqfLaJeaIf1R+pLl23XIaos0d73Y6Uy+ZbTYH3tap6anf/yF7HqRrzdOlNz5tlv5yo6T+f90jyD919wyrjrEMuVfXAbHxxwlu3iXdedz9/r3KZO8ZIuewmxty/z/btvHGWcbwdZb+set8uI5ctMU9L8nnd/Sd7ncu67dtljA/rb46achV19pzLbPOcldbZUw7DHBPWIZe5a+1Vb8+65aKOGz+XEd8Lrcu+XUacTfFOy4rq7LnirGUu+61ZO1LhVlWvTvLiJJd297s3Tb99ki/NxmnOr+ru5y0zxozbs7TGMbtnfMY31+8zy2F8xuc4B6s3Sk05Y4286zgjvf9geYzP2NRx4zNGY3OM21/24zVrR7ouxWOycSr5b1bV+6rqzVX1jiRvTfLNSZ61wIFojhhzbY9r2o3N+Ixvu9/nt2fnv88sx1zHW5bHcQ5Wb5Sacq5j9hxxRnr/wfIYn7Gps8en1h6bY9w+sh/PrL1Dkscl+dYk90lyQ5I7ZqPx/PtJ/lt3v2Gv4myKt+qPHOx6e44S4w5JTlk0BstjfPaXOY4JLI/xGZPjHKzeEmvKldbZu4mz5PcfjnGDMD77hzpufMZoPI5x+8u+a9ZuturCbVSrbhyzfMYHWHeOc7B6asrb8v7j5GB8gHXmGDe+fd2sBQAAAABYF/vxmrUAAAAAAGtHsxYAAAAAYACatcBJqaqeXFU/MHPMr6iql+5g+VdX1aGZczhYVa+tqjdU1ZfNGRsAABah1gY4cQdWnQAAs3pkkqu7+ztXnQgAAKwZtTawdM6sBU4KVfVvquqNVfXnVfVrW+adXVV/Os1/SVXd9TjTv3iadlVV/VRVXXOcdZ9SVT9dVddMz/vebZb55qq6elrmJxeYfkFV/e+qel1VPbuqfq6qzk7yX5KcM+V2x13tNAAAWIBaG2A+mrXA2quqf5LkR5I8orsfmOQJWxb51SRP6u4vSHJ1kh8/zvTnJvmu7j47yccXSOHCJGclOXuK9fwt+X1mkp9M8ogkZyf54qo69zjTfzTJQ5I8LMnnJkl3X5Xkx5K8sLvP7u5/WCA3AAA4YWptgHlp1gIng0ck+a3u/kCSdPff3jyjqv5RktO6+w+nSZck+fJjTD8tyV26+0+m6b+xwPofleSXuvumreuffHGSV3f3kWmZ5yf58mNMf3CSP+zuv+3ujyX5rYX3BAAAzEutDTAjzVoAAAAAgAFo1gIng1cm+caqunuSVNXdbp7R3f8nyQc3fZvrY7Pxn/SjTb8hyY1V9SXT9G/aboVV9eCq+tXp4eVJvquqDmxd/+R1Sf5ZVd2jqk5J8s1J/vAY0/9smn7XKea/OoF9AgAAc1BrA8zowKoTAFi27n5TVf1Ekj+sqo8neUOSd25a5Pwkv1hVn5rk7Um+4zjTL0jy7Kr6RDYKuv+zzWrvneTm61j9cpLPTvLGqvpYkmcn+blN+V1bVRcleVWSSvKy7r40SY4x/T9lo8D82yR/cZQcAABgqdTaAPOq7l51DgD7SlXdubs/PN2/KMkZ3f2ELcv8VJJf6+43LjOH6b/9L0nyK939kmWsCwAA9opaGzjZadYC7FBV/eskP5SNTye8K8m3d/eRPc7hp7PxZQp3SPL7SZ7QDugAAOxzam3gZKdZCwAAAAAwAF8wBpyUqupNVfUVS4r9FVX1nmPM/8Wq+tEZ19dVdb+54gEAwG6otQFOnC8YA05K3f1PVrju7z7W/Kp6fJJvT/L5SX6zu799D9ICAIBZjFprV9WnJPlv2bjEwd2S/FWSH+rul+9RegDHpVkLMJ73JXlqkn+R5I4rzgUAANbFgSR/neSfJXl3kn+Z5EVV9fnd/c5VJgZwM5dBAE5KVfXOqnpUVT24qv6kqm6oqmur6ueq6vablvsnVXV5Vf1tVb2/qn54mv4pVfWsqnrfdHvW9J/6zev44ar6wLSub900/XlV9dSj5dbdv9Pdv5vkb46S+w9Oub6vqh63230BAABzGrXW7u6/6+4nd/c7u/sT3f3SJO9I8kWbnq/WBlZKsxY42X08yb9Pco8kD03yyCT/b5JU1V2S/EGSVyT5zCT3S3LF9Lz/L8lDkpyd5IFJHpzkRzbF/Ywp5j2TnJ/k4qr6nN0mW1WPSfIDSR6d5P7Z+AgXAACMaOhau6pOT/LZSd40PVZrAyunWQuc1Lr79d39p9190/TRp1/KxseikuSrk1zX3U/v7v/b3Td292uned+a5P/X3dd395EkT0ny2C3hf7S7P9Ldf5jkZUnOmyHl85I8t7uv6e6/S/LkGWICAMDsRq61q+rUJM9Pckl3/8U0Wa0NrJxmLXBSq6rPrqqXVtV1VfWhJP8pG/+lT5J7ZeNLB7bzmUnetenxu6ZpN/vgVOAdbf7N6395VX14un3r1vlHWe9fb4kLAADDGbXWrqrbJfm1JB9N8vgt61VrAyulWQuc7H4hyV8kuX93f1qSH05S07y/TvKPj/K89yX5rE2P7z1Nu9ldq+pOx5ifJOnur+zuO0+35y+Q77XZKGw3xwUAgBENV2tXVSV5TpLTk/yr7v7YpqeotYGV06wFTnZ3SfKhJB+uqs9N8v9smvfSJGdU1ROnLzm4S1V9yTTvN5P8SFUdrKp7JPmxJL++JfZTqur2VfVl2fiY128tklBVHaiqOyQ5JckpVXWHqjowzX5Rkm+vqgdU1acm+fET2GYAANgLw9Xa2Wggf16Sr+nuf9gyT60NrJxmLXCy+4Ek35LkxiTPTvLCm2d0943Z+HKBr0lyXZK3Jnn4NPupSQ4neWOSq5NcOU272XVJPpiN//A/P8l3b7oW1vH8SJJ/SHJRkm+b7v/IlNPLkzwrySuTvG36CQAAIxqq1q6qz0ryXdn44rLrtl4iQa0NjKC6e9U5AOy5qnp3km/r7tesOhcAAFgnam2AE+fMWuCkU1UHkxxM8s4VpwIAAGtFrQ2wO5q1wEmlqr44Gx+x+tnufveq8wEAgHWh1gbYPZdBAAAAAAAYgDNrAQAAAAAGcGDVCSziHve4R5911lmrTgMA4KTy+te//gPdfXDVebA86mwAgL13rDp7XzRrzzrrrBw+fHjVaQAAnFSq6l2rzoHlUmcDAOy9Y9XZLoMAAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYAAHVp0AAACwc1X1ziQ3Jvl4kpu6+1BV3S3JC5OcleSdSc7r7g+uKkcAAHbGmbUAALB/Pby7z+7uQ9Pji5Jc0d33T3LF9BgAgH1CsxYAANbHOUkume5fkuTc1aUCAMBOuQwC7JGzLnrZcZd559O+ag8y4WiON0bGZ7X8Do3N+IzPMW4tdZLfr6pO8kvdfXGS07v72mn+dUlO3/qkqrowyYVJcu973/s2Qed4rcx1TBgll3XbnpFysT37P5d1256RcrE9+z+XdduekXJZt+3ZTLMWAAD2py/t7vdW1acnubyq/mLzzO7uqZGbLdMvTnJxkhw6dOg28wEAWB2XQQAAgH2ou987/bw+yUuSPDjJ+6vqjCSZfl6/ugwBANgpzVoAANhnqupOVXWXm+8n+edJrklyWZLzp8XOT3LpajIEAOBEuAwCAADsP6cneUlVJRs1/W909yuq6s+SvKiqLkjyriTnrTBHAAB2SLMWAAD2me5+e5IHbjP9b5I8cu8zAgBgDi6DAAAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADCApTVrq+oOVfW6qvrzqnpTVT1lmv68qnpHVV013c5eVg4AAAAAAPvFgSXG/kiSR3T3h6vq1CR/VFUvn+b9YHf/9hLXDQAAAACwryytWdvdneTD08NTp1sva30AAAAAAPvZMs+sTVWdkuT1Se6X5Oe7+7VV9f8k+Ymq+rEkVyS5qLs/ss1zL0xyYZLc+973vtW8sy562XHX/c6nfdVxlzlenDliLBJnr7ZnpFzWbXsWjTOHk23frtv4JCfnvp0jlzmMtD376bWyaJw52LfLywUAABjfUr9grLs/3t1nJzkzyYOr6p8m+aEkn5vki5PcLcmTjvLci7v7UHcfOnjw4DLTBAAAAABYuaU2a2/W3TckeVWSx3T3tb3hI0mem+TBe5EDAAAAAMDIltasraqDVXXadP+OSR6d5C+q6oxpWiU5N8k1y8oBAAAAAGC/WOY1a89Icsl03drbJXlRd7+0ql5ZVQeTVJKrknz3EnMAAAAAANgXltas7e43JnnQNtMfsax1AgAAAADsV3tyzVoAAAAAAI5NsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAA2Ieq6pSqekNVvXR6fJ+qem1Vva2qXlhVt191jgAA7IxmLQAA7E9PSPKWTY9/Mskzu/t+ST6Y5IKVZAUAwAnTrAUAgH2mqs5M8lVJfnl6XEkekeS3p0UuSXLuSpIDAOCELa1ZW1V3qKrXVdWfV9Wbquop03QfzwIAgN15VpL/kOQT0+O7J7mhu2+aHr8nyT23e2JVXVhVh6vq8JEjR5aeKAAAi1vmmbUfSfKI7n5gkrOTPKaqHhIfzwIAgBNWVV+d5Prufv2JPL+7L+7uQ9196ODBgzNnBwDAbiytWdsbPjw9PHW6dXw8CwAAduNhSb62qt6Z5AXZqK//a5LTqurAtMyZSd67mvQAADhRS71m7fQNtVcluT7J5Un+Kj6eBQAAJ6y7f6i7z+zus5J8U5JXdve3JnlVkm+YFjs/yaUrShEAgBO01GZtd3+8u8/Oxn/2H5zkc3fwXB/PAgCAxT0pyfdV1duycQ3b56w4HwAAdujA8RfZve6+oapeleShmT6eNZ1d6+NZAABwgrr71UlePd1/ezZOkAAAYJ9a2pm1VXWwqk6b7t8xyaOTvCU+ngUAAAAAcBvLPLP2jCSXVNUp2WgKv6i7X1pVb07ygqp6apI3xMezAAAAAACW16zt7jcmedA20308CwAAAABgi6V+wRgAAAAAAIvRrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABjA0pq1VXWvqnpVVb25qt5UVU+Ypj+5qt5bVVdNt3+5rBwAAAAAAPaLA0uMfVOS7+/uK6vqLkleX1WXT/Oe2d0/vcR1AwAAAADsK0tr1nb3tUmune7fWFVvSXLPZa0PAAAAAGA/25Nr1lbVWUkelOS106THV9Ubq+pXquque5EDAAAAAMDIlt6srao7J3lxkid294eS/EKS+yY5Oxtn3j79KM+7sKoOV9XhI0eOLDtNAAAAAICVWmqztqpOzUaj9vnd/TtJ0t3v7+6Pd/cnkjw7yYO3e253X9zdh7r70MGDB5eZJgAAAADAyi2tWVtVleQ5Sd7S3c/YNP2MTYt9XZJrlpUDAAAAAMB+sbQvGEvysCSPTXJ1VV01TfvhJN9cVWcn6STvTPJdS8wBAAAAAGBfWFqztrv/KEltM+t/LGudAAAAAAD71dK/YAwAAAAAgOPTrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAYJ+pqjtU1euq6s+r6k1V9ZRp+n2q6rVV9baqemFV3X7VuQIAsDjNWgAA2H8+kuQR3f3AJGcneUxVPSTJTyZ5ZnffL8kHk1ywuhQBANgpzVoAANhnesOHp4enTrdO8ogkvz1NvyTJuXufHQAAJ0qzFgAA9qGqOqWqrkpyfZLLk/xVkhu6+6Zpkfckuec2z7uwqg5X1eEjR47sWb4AAByfZi0AAOxD3f3x7j47yZlJHpzkcxd83sXdfai7Dx08eHCZKQIAsEMHjjajqr7wWE/s7ivnTwcAANbfnLV2d99QVa9K8tAkp1XVgens2jOTvHd3mQIAsJeO2qxN8vRjzLv5elgAAMDO7arWrqqDST42NWrvmOTR2fhysVcl+YYkL0hyfpJL50kXAIC9cNRmbXc/fC8TAQCAk8UMtfYZSS6pqlOycWmzF3X3S6vqzUleUFVPTfKGJM/Z5XoAANhDx7oMwtcf64nd/TvzpwMAAOtvt7V2d78xyYO2mf72bFy/FgCAfehYl0H4mmPM6ySatQAAcGLU2gAA3MaxLoPwHXuZCAAAnCzU2gAAbOd2q04AAAAAAADNWgAAAACAIWjWAgAAAAAM4LjN2qr61Kr60ap69vT4/lX11ctPDQAA1ptaGwCAzRY5s/a5ST6S5KHT4/cmeerSMgIAgJOHWhsAgFss0qy9b3f/lyQfS5Lu/vsktdSsAADg5KDWBgDgFos0az9aVXdM0klSVffNxn//AQCA3VFrAwBwiwMLLPPjSV6R5F5V9fwkD0vy7ctMCgAAThJqbQAAbnHcZm13X15VVyZ5SDY+kvWE7v7A0jMDAIA1p9YGAGCzRc6sTZI7JPngtPwDqird/ZrlpQUAACcNtTYAAEkWaNZW1U8m+ddJ3pTkE9PkTqKABACAXVBrAwCw2SJn1p6b5HO62xcdAADAvM6NWhsAgMntFljm7UlOXXYiAABwElJrAwBwi0XOrP37JFdV1RVJbvmPf3f/u2M9qaruleRXk5yejY9yXdzd/7Wq7pbkhUnOSvLOJOd19wdPKHsAANjfTqjWBgBgPS3SrL1suu3UTUm+v7uvrKq7JHl9VV2e5NuTXNHdT6uqi5JclORJJxAfAAD2uxOttQEAWEPHbdZ29yUnEri7r01y7XT/xqp6S5J7JjknyVdMi12S5NXRrAUA4CR0orU2AADr6ajN2qp6UXefV1VXZ+MyBrfS3V+w6Eqq6qwkD0ry2iSnT43cJLkuG5dJ2O45Fya5MEnufe97L7oqAAAY3py1NgAA6+NYZ9Y+Yfr51btZQVXdOcmLkzyxuz9UVbfM6+6uqtsUp9O8i5NcnCSHDh3adhkAANinZqm1AQBYL0dt1t589mt3v+tEg1fVqdlo1D6/u39nmvz+qjqju6+tqjOSXH+i8QEAYD+ao9YGAGD9HOsyCDfmkx/Juvl02J7ud3d/2rEC18YptM9J8pbufsamWZclOT/J06afl55Y6gAAsD/tttYGAGA9HevM2rvsMvbDkjw2ydVVddU07Yez0aR9UVVdkORdSc7b5XoAAGBfmaHWBgBgDR3rmrW3qKovTXL/7n5uVd0jyV26+x3Hek53/1E+eZbAVo/cWZoAALCeTqTWBgBgPd3ueAtU1Y8neVKSH5om3T7Jry8zKQAAOBmotQEA2Oy4zdokX5fka5P8XZJ09/uS+NgWAADsnlobAIBbLNKs/Wh3d6YvQKiqOy03JQAAOGmotQEAuMUizdoXVdUvJTmtqv5tkj9I8uzlpgUAACcFtTYAALc47heMdfdPV9Wjk3woyWcn+bHuvnzpmQEAwJpTawMAsNlxm7WTq5PcMRsfz7p6eekAAMBJR60NAECSBS6DUFXfmeR1Sb4+yTck+dOqetyyEwMAgHWn1gYAYLNFzqz9wSQP6u6/SZKqunuSP07yK8tMDAAATgJqbQAAbrHIF4z9TZIbNz2+cZoGAADsjlobAIBbLHJm7duSvLaqLs3GdbTOSfLGqvq+JOnuZywxPwAAWGdqbQAAbrFIs/avptvNLp1+3mX+dAAA4KSi1gYA4BbHbdZ291P2IhEAADjZqLUBANjsqM3aqnpWdz+xqv57Nj6SdSvd/bVLzQwAANaUWhsAgO0c68zaX5t+/vReJAIAACcRtTYAALdxrGbtm6rqiUnul+TqJM/p7pv2JCsAAFhvam0AAG7jdseYd0mSQ9koHr8yydP3JCMAAFh/am0AAG7jWGfWPqC7Pz9Jquo5SV63NykBAMDaU2sDAHAbxzqz9mM33/GRLAAAmJVaGwCA2zjWmbUPrKoPTfcryR2nx5Wku/vTlp4dAACsJ7U2AAC3cdRmbXefspeJAADAyUKtDQDAdo51GQQAAAAAAPaIZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAA2Geq6l5V9aqqenNVvamqnjBNv1tVXV5Vb51+3nXVuQIAsDjNWgAA2H9uSvL93f2AJA9J8j1V9YAkFyW5orvvn+SK6TEAAPvE0pq1VfUrVXV9VV2zadqTq+q9VXXVdPuXy1o/AACsq+6+truvnO7fmOQtSe6Z5Jwkl0yLXZLk3JUkCADACVnmmbXPS/KYbaY/s7vPnm7/Y4nrBwCAtVdVZyV5UJLXJjm9u6+dZl2X5PRV5QUAwM4trVnb3a9J8rfLig8AACe7qrpzkhcneWJ3f2jzvO7uJL3Ncy6sqsNVdfjIkSN7lCkAAItYxTVrH19Vb5wuk3DULzxQRAIAwNFV1anZaNQ+v7t/Z5r8/qo6Y5p/RpLrtz6vuy/u7kPdfejgwYN7lzAAAMe1183aX0hy3yRnJ7k2ydOPtqAiEgAAtldVleQ5Sd7S3c/YNOuyJOdP989Pcule5wYAwIk7sJcr6+7333y/qp6d5KV7uX4AAFgTD0vy2CRXV9VV07QfTvK0JC+qqguSvCvJeatJDwCAE7GnzdqqOmPTFx58XZJr9nL9AACwDrr7j5LUUWY/ci9zAQBgPktr1lbVbyb5iiT3qKr3JPnxJF9RVWdn44sO3pnku5a1fgAAAACA/WRpzdru/uZtJj9nWesDAAAAANjP9voLxgAAAAAA2IZmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABrC0Zm1V/UpVXV9V12yadrequryq3jr9vOuy1g8AAAAAsJ8s88za5yV5zJZpFyW5orvvn+SK6TEAAAAAwElvac3a7n5Nkr/dMvmcJJdM9y9Jcu6y1g8AAAAAsJ/s9TVrT+/ua6f71yU5/WgLVtWFVXW4qg4fOXJkb7IDAAAAAFiRlX3BWHd3kj7G/Iu7+1B3Hzp48OAeZgYAAAAAsPf2uln7/qo6I0mmn9fv8foBAAAAAIa0183ay5KcP90/P8mle7x+AAAAAIAhLa1ZW1W/meRPknxOVb2nqi5I8rQkj66qtyZ51PQYAAAAAOCkd2BZgbv7m48y65HLWicAAAAAwH61si8YAwAAAADgkzRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAADYZ6rqV6rq+qq6ZtO0u1XV5VX11unnXVeZIwAAO6dZCwAA+8/zkjxmy7SLklzR3fdPcsX0GACAfUSzFgAA9pnufk2Sv90y+Zwkl0z3L0ly7l7mBADA7mnWAgDAeji9u6+d7l+X5PRVJgMAwM5p1gIAwJrp7k7S282rqgur6nBVHT5y5MgeZwYAwLFo1gIAwHp4f1WdkSTTz+u3W6i7L+7uQ9196ODBg3uaIAAAx6ZZCwAA6+GyJOdP989PcukKcwEA4ARo1gIAwD5TVb+Z5E+SfE5VvaeqLkjytCSPrqq3JnnU9BgAgH3kwKoTAAAAdqa7v/kosx65p4kAADArZ9YCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGcGAVK62qdya5McnHk9zU3YdWkQcAAAAAwChW0qydPLy7P7DC9QMAAAAADMNlEAAAAAAABrCqZm0n+f2qen1VXbjdAlV1YVUdrqrDR44c2eP0AAAAAAD21qqatV/a3V+Y5CuTfE9VffnWBbr74u4+1N2HDh48uPcZAgAAAADsoZU0a7v7vdPP65O8JMmDV5EHAAAAAMAo9rxZW1V3qqq73Hw/yT9Pcs1e5wEAAAAAMJIDK1jn6UleUlU3r/83uvsVK8gDAAAAAGAYe96s7e63J3ngXq8XAAAAAGBkq/qCMQAAAAAANtGsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGIBmLQAAAADAADRrAQAAAAAGoFkLAAAAADAAzVoAAAAAgAFo1gIAAAAADECzFgAAAABgAJq1AAAAAAAD0KwFAAAAABiAZi0AAAAAwAA0awEAAAAABqBZCwAAAAAwAM1aAAAAAIABaNYCAAAAAAxAsxYAAAAAYACatQAAAAAAA9CsBQAAAAAYgGYtAAAAAMAANGsBAAAAAAagWQsAAAAAMADNWgAAAACAAWjWAgAAAAAMQLMWAAAAAGAAmrUAAAAAAAPQrAUAAAAAGMBKmrVV9Ziq+suqeltVXbSKHAAAYB2ptQEA9q89b9ZW1SlJfj7JVyZ5QJJvrqoH7HUeAACwbtTaAAD72yrOrH1wkrd199u7+6NJXpDknBXkAQAA60atDQCwj1V37+0Kq74hyWO6+zunx49N8iXd/fgty12Y5MLp4eck+cvjhL5Hkg/sMr1RYshleTHksrwYcllejJFyWbftGSmXdduekXJZt+3Zy1w+q7sP7nI97KFFau0V1dlzxRklxki5rNv2jJTLum3PSLms2/aMlMu6bc9IudgeucwZ46h19oFdrnxpuvviJBcvunxVHe7uQ7tZ5ygx5LK8GHJZXgy5LC/GSLms2/aMlMu6bc9Iuazb9oyWC/vPKursueKMEmOkXNZte0bKZd22Z6Rc1m17Rspl3bZnpFxsj1z2KsYqLoPw3iT32vT4zGkaAACwO2ptAIB9bBXN2j9Lcv+quk9V3T7JNyW5bAV5AADAulFrAwDsY3t+GYTuvqmqHp/k95KckuRXuvtNM4Re+KNc+yDGXHHWLZd125654owSY64465aL7VlenHXLZd22Z644o8SYK8465sJAllRrr9vr1vYsL8665bJu2zNXnFFizBVn3XJZt+2ZK84oMeaKM0qMueKsWy67irHnXzAGAAAAAMBtreIyCAAAAAAAbKFZCwAAAAAwAM1aAAAAAIAB7PkXjK2jqvr0JA9L8plJ/iHJNUkOd/cn9jIGMAa/z2MzPgD7x1zHbMd+WA9+l8dnjGD39u0XjFXVoSRfllsfAC7v7g/uVZyqeniSi5LcLckbklyf5A5JPjvJfZP8dpKnd/eHlhljru2ZK0ZVPTTJt00xztgU42VJfr27/89examqM5N80zbb87IkL1/0D8YccWbcL3OM8Rz7dq7tmWPfDjE+c/0+D/a6vUOSr94uxqLfLj5HjDm2Z87j7UCv27n27RzjPMvrdoq18r9DjnGc7FZdU874N3XOY//K339Mz3eMW+42rXx85oozWE25q+1Rxy03zkzbM8x7oRlf+2vzXmhTHMe4JcSZu9bed83aqvqOJN+b5B1JXp9bHwAelo2d8aPd/e5lx6mqn0rys9stU1UHsvELeUp3v3iZMWbcnjlivDzJ+5JcmuTwlhgPT/I1SZ7R3ZcdZ3t2HaeqnpvknkleepQYX5Tkou5+zXFy2XWcmbZnrtf+HLnMNc5z7NshxmeKM8cxYaTX7VOmnF+d277mHj7d//7ufuMyY8y4PXMdb0d53c61b+cY57let0P8HXKM42Q2UE051zF7jr/NI73/cIxb0jaNMj4zbs9INeUc26OOW1KcGV8rQ7wXmnF71u29kGPckuIspdbu7n11S/I9Se54jPlnJ3nkXsUZ5TbH9swU4x4L5LonyyT5p8eZf/sk91tgPbuOM9P2zPXanyOXucZwjn07xPjMdRvsdftVx5n/6UkOLTvGnPtllDEaZXxmHOe5XrdD/B1yjHM7mW8z/R6qs5e3bx3jlrdfhhifGbdnpJpymL9Do7xu56i/5ooz12tlncZnxn07zHshx7il7tvZj3H77szakVRVJfnGJJ2NU/ofkeScJH+R5Bf7BK/JUlWv7O5HzJYos6uqL+zuK1edB9sbcXyq6ju6+7mrzmMEVfXp3X39qvPYzPjc2ohjxCeNeIyDuS2rzp5iq7UH5hg3vtHGSB13ayPWccbok0YcH25thGPc7Va58rlV1Y/tcPl/UVUXVNVZW6Y/bsEQP5/kvCSPTfJrSb47yZ8l+fIkz1wwhzduuV2d5GE3P97Bttxjy+Nvq6qfqaoLp2J3V3a6b48S4+odLHuvqnpBVf3Pqvrhqjp107zfXTDG51bVy6vqZVV136p6XlXdUFWvq6rP20EuX7jl9kVJLquqB1XVFy4Y43Gb7p9ZVVdMufxxVX32orkcI/6ux2eKs9AYzTE+07K7HqP9MD6Tp+w2wPTxikWX/bSqelpV/VpVfcuWef9twRifUVW/UFU/X1V3r6onV9XVVfWiqjpjwRh323K7e5LXVdVdq+puO9iex2y6f1pVPWc6Tv5GVZ2+aJxj2PX4JIuP0SjjM8XZ9Rjtwfjs6d8hxzjY3k5+D2uAOnta365r7VpynT3FdIw7wWPcFGf4WnvR8ZmW9V5oZ9Rxg9dx2cP3QnOMz7TsSfNeyDFuvGPcWp1ZW1Xv7u57L7jsf87GdTmuzMb1I57V3T87zbuyu487KFV1dXd//vTCui7JGd390dq4HsuV3f0FC8S4LMmHkjw1GxcgriT/M8mXJkl3v2vB7bkl56r6kWxc1Pg3snF9lPd0979fJM4x4i+0b6vq6482KxtnQRxccH2XJ3lxkj9NckE2rhPyNd39N1X1hu5+0AIxXpPkp5LcOcnTkjwpyQuzsU+e2N2PXDCXT0x5fGTT5IdM03qRMzO2jM+LkvxBkl/Oxhkij180l2PE38lrf9djNMf4THF2PUYjjU8d/U1fJfns7v6UBWIc7dhTSV7a3YsWBi9O8tZs7IfHJflYkm/p7o/s4Bj3imxcEP1OSb4lyfOzcVw5N8mjuvucBWJ8IsnW49iZSd6TjfH5xwtuz+Yx+uVsHHOfneTrk/yz7j53gRi7Hp8pzq7HaJTxmeLseozmGJ8F1rFnf4cc42B7O/g9HKLOnuLsutZedp09xXWM+6QdHeOmOEPU2t4LHTXGrsdHHXfUOMPUcaO8F5pjfKY4a/Ve6DjxHeNGq7V7B9dMGOGWjWJru9uNSW7aQZyrkxyY7p+W5H8keeb0+A0LxnjDpvuv2DLvqh3k8nVJXpPka6fHbz+B/bI5lyuT3Gm6f2qSq/dq32bjQPi8JM/d5nbjDrbnqi2Pvy3Jm7LxLZJXnsA+eduWeQvFmJb9V0n+MMlXbpr2jh2Oz5Wb7m/dtkVfb3O99nc9RnOMz1xjNMr4TMu+PxvX+vmsLbezkrxvwRgfT/LKJK/a5vYPO8hl63b8f0n+V5K7n+Dv0LuPFf8YMb4/ySuSfP6Jjs8CY7RoLrsen7nGaJTxmWuM5hifadkh/g5tsw2OcW4nzW2m38Nh6uxp+V3V2pmhzp5x3zrGbR9niFp7jvGZa4zmGJ+5xmim8VHHbR9npDpuiPdCc4zPXGM0x/jMNUZxjDtanCGOcVtvB7L/3JDki7v7/VtnVNVf7yDOge6+KUm6+4aq+pokF1fVb2XjAsKLuK6q7tzdH+7uzaelf0aSjy6aSHe/pKp+P8l/rKoLdrD+ze5YVQ/KxqUtTunuv5tif6yqPr5gjBuy+337xiQ/3d3XbBPjUQvGSJJTq+oO3f1/k6S7f72qrkvye9n4z9YiTtl0/xlb5i28j7v7xVX1e9kYn8dl44Dbiz5/cmZV/Uw2/uN0sKpO7e6PTfNOPcbzNrsh87z25xijOcYnmWGMBhqfZOMbJO/c3VdtnVFVr14wxluSfFd3v3WbGDsZ50+pqtv1dE2/7v6JqnpvNt6s3nnBGLf7/7d3/7GS1Wcdx9+f7EIX7CJYaKCNKFJNKym7yC+blrasNGljLKUGWqwtKKYYLWVtMIUGhUTEQGkkSptW1MLS1ZDGALXEBFRYE/uDKrvdBdrSwFpUIEKAdglEKX38Y87uzt47c/fMzDMzzz3380omuffMzGeec753vveZM3PO9P28aYnrhoqIT0m6FfjTpv4rGH18AF4t6WP0xugQSYrmv1/bWsgZH8gZoxLj0zx2xhhljA/U+T/kOc5WsueY/HlYps9uapi0187os8Fz3CJJcxzU6bX9WmiwjPFxHzdAsT6uymuhjPGB7r0Weg7PcYsUmuP2sRx31m6i9+7Moj8weh9Jb+sRSW+LiC0AEfEycIGkq+jtWd+viHjXkKt20fvodWtN0/cxSeuAN41y38YT7P0DfUbSURHxhHrnRPlhy4yMbbuR3rszg5zVMgN6Hxk/ld47HABExD9KOhu4tmXGp/ua/D3nppH0OnofS28tIp4Hfq9p1G9mtEke4Pf7fv635v7PNi84vtQyI+tvfyOTj1HG+EDSGBUZHyLigiWu+7Vh1y1wJcP/4V7Uthbg7+l9Gcue7RgRNzX/BP+8ZcYdfeNz+e6Fzfg83LaQiPgv4GxJ7wbuBg5ue98+NwJrm59vBg4HnmrGaFvLOjLGB3LGqMz4NI896RhNPD6NKv+HPMfZSpbxPCzVZzd5k/TaGX02eI4bKGGOgzq99kb8WmiQicfHfdxwVfq4Qq+FMsYHOvZaCM9xQ1WY4xbq1DlrRyHpIICIeHHAda+NiP+efVX5JK0CXhERL8y7lq6QJGBtRAybpGyOqo2PpA9HxF/Mu44qmrn32EHvxs6Dx2examNk+6o2x5kN4j7bxuU5rr5KY+Q+brFqfZzHaF/VxscWqzLHjfKR9k6JiBcHNZDNdZ1oIKH3SYZJGkhJVyaW0wnRM/fmBDw+g1Qan8Zvz7uASpq5t1Jz4vFZoNoYeZ7bV8E5zmwR99nteY7bV7U5zuOzWLExch+3QLU+Do/RPqqNj+e4xarMcSt2Z6219u55F2BL8vjUp3kXYEvy+NTnec7MusxzXG0en9rcx9XnMarNc1xR3llr++PJtTaPT32/Mu8CbEken/o8z5lZl3mOq83jU5v7uPo8RrV5jivKO2unQNK3mstH5pmR5MSMEElnSjq1Qk7HakkZn6RaurZtUzKaE8pPTNJJkl5TIadKRkZO1vhk1FIpo1gtZf4PeY4zm7+sHtm99nQyOlhLmfHJyulSLe7j6tdS6bVQ17ZtUo7nuKK1rJ7kgSuR9K3mx09HxA3zzImIN0g6nN63240lIwMmX5+I+NEkj9/nVOCNklYv8e2+s8rpTC2J4zNxLYkZlWrJWp8MFwHHS3o4It4355wqGZk5Gapsl0rbduKMYv+HPMfZipXRI1fps7NyktbHc1zhWoqNT1ZOF2vJUKLvScqoVkuGSuvTmVo8x9WtRREx5mPWs7vhiog7K+RU0bX1MVvJJK2NiF0VcqpkZOZkqLJdKm3bSuNjZuPL6Cm71pd2bX3MVroqfY/7uMEqrU/XarFaOnUahIh4OqNRysiRtGPSOjIyIG+7TErSO6rkdLGWER/zEEnHDlh+/CwzKtWStT5Dskc6PEXSkZKObH4+QtJ7JR0HMMo/4YycKhmZOQNyMw5lunr3z+PWUiWjWi2TknRM87fy+nlmVKola31s5cnoKav02Vk5VfpsqNWXdq2WER+vUz1ytVoG3H/kPq5Kf1upR55Wn93kTdRrV+pLu1bLpLrWI1eqpWufrN0REW+cVY6k9w67CvhsRBwxi4wWjzHRdpF0JvBkRHx9wjoei4ijJ8nIyulSLaOOj6RzgOuB/wEOAM6PiG80190fEb8wi4xKtWStzxL5NwPHA/s9PEXShcCl9OaAa4DzgQeAtwDXRsRftXzMiXOqZGTmDMluPT7N7f9s4SLgg8AmgIj46HLJqFbLEvmt5zlJt0fEe/rudz1wL/Bm4OqIuGkWGZVqyVofs4xee5Z9dmbOEvkZ26RMr92lHjkrZ8T/QZ3qkavVMiR71D6uRH9bqUeeZp/d5I/yWqhMX9q1WpbInmmfnZXTxVr6Lbtz1u6n4Tpyxjm3ApuBQXu818wwI227DNH6PBuSvrREHa9q+4AZOV2sZYhRz4PyCeDEiHhC0inALZIui4jbmnrayMioVEvW+gwUEecBSFrb4uYfAY4DDgK+B7wuIp6UdBhwD9C2WcrIqZKRmbPIiOMDcBawBbiLvX8f7wf+fYSHrZJRrZZhRpnnfqrv548DGyJip3qHKv8TcFOLx8vIqFRL1vrYCpDRUxbqs1Nyptxnw4x77S72yIV67a71yNVqWWSMPq5Kf1upR55anw0jj1GlvrRrtQwz6z47K6eLteyx7HbWUqhxA7YD10XEAwuvkHTGDDMgb7ssEhGfGOHmpwG/Djy/YLmAU2ac08VaFhlxfABWRcQTzX3vk3Q68GVJP8ngv59pZVSqJWt9UHP4UNPgHEFv7L8TEQ9Gu8NTXoqIF4AXJD0SEU82ec9KGqWWjJwqGZk5+5B09e7nUMvxAfh54I+AdwKXRMTjkq6IiJtHeOgqGdVqGWjEea7/72F1ROxsMp6W1PZLFDIyKtWStT62MlT5MEJWj5yRM7U+G+bSa3exR67Sa3etRy5VS0KfDXX620o98lT6bBir167Ul3atloHm0Gdn5XSxlr0h49xpzio1bhuBHwy57qwZZkDedhlI0jsi4u4WN/0a8EJEbBmQ8Z0RHjIjp3O1SDoEOCIiHlmw/PiI2N4yZpekY3dnRO8d7rcDt9N7R3VWGZVqSVkf9R1CJKn/EKI/kdT2EKKQdEBEvAT8cl/2GkY7z3hGTpWMlBwNOXxI0iuh/eFDTaO5UdKJwGZJd7atoVpGtVoWknQMcALwUER8u+Xd1kn6Ab3xfYWko5rn9IHAqhlmVKola31sZajyYYSN5PTIGTlT7bObnFn22p3rkbNyEnrtrvXIZWpJ6rOhTn9bpkfOqiWj167Ul3atloXm2Gdn5XSxlr0iYlld6L17dvSQ606adU6Vy7TXB3hs3uu40i/AOcDjwDbgQeDkvuvuHyFnHfCzA5YfAHxgVhmVaklcnx3AwfQOt3seOLJZfhiwrWXG0fTejVu4/LXAGSPUMnFOlYzEWv4T+ALwIeC85vLU7p/b1rIgU8DvAl8Y5/6VMirUAtze9/OZwE7g88DD9M5vN8m6HQq8ad4ZlWrJWh9funUhoafMyKh0mcX64F577hcSeu3EnrJSf1uiFhL67Ob2JfrbjIyCtaT22rhHTs2heJ+dldOVWjr1BWMVKOfLiCbOGPNxlzrX04aI+LEWGYr9/FHN6jZdq0XSNuBdsfdcT5uAyyLiNklbI+KEpfKTa+nats2qdc9zV9I3I2Jd33Wtxsjbdqq1rKV3+NCr2Xv40KMR8TNL5U6plhIZBWvZ8zyR9BV6L+D2nO+p/zk1zVq6tm2zajWbt6weeaX32l2b4xJr2caEvXax9elULRl9dmItJTIK1jJxr11sfbpWS4k+O+s2Xayl38SHKFYg6f5COROdID0xY5z1OQ34HPCpAZeF538a5h5JF0na5xtXJR0oaYN63wR53oxyulbLPud6Ak4HLpf0UUY771SV9alUS9b6hKQDmp/HPYTI23ZKORGxKyI20pvTNku6hPH+D1bZLmW2bWIt/XPZPud7Atqe76nS+lSpJWt9bIVSQo+ckUFSj5yRM+b6VOm1uzbHZeVk9NqV1qdrtWT02Vm1VMkoVUtSr11mfTpYS5U+Oyuni7XsFWN+nLfSBdhaJQe4qkLGOOsD/ANw+pDr/qVlxhrgd4B/pXcY0UPAo/S+VfJG4IRZ5XStFuArwLELlq2l9+2C/zvCOJdYn0q1JK5PxmFI3rZTzOnLm+RQ/UG17EzYLjPPKFjLy/TOLbkL+D/gqGb5gcD25fS3X6mW7OePLyvvQk6PnJGR1SNn9Osjrw9Feu2uzXGJtUzcaxdbn07VQt6h+lXWp1PjMyBz3EP1K/WlXaulRJ9d7e+2Ui39FzXBy5qkqyLi8lnnSDU/Lt13v5TtMq7mnc/DgRcj4rl55nShFknr6H1xwncH5J0TEZtnVUt2RqVaJsnIfj572+bmTGO+rbJd5r1tp1HLgsxDgTdExFdnXUvXtu00xse6L6OnnEefnXmbAfeZa5/d1FBmTuhCLdm99rzXp2u1uI+rX0vF10Jd2bbTyOnLO5Q59dlZOZ2sZbntrK3UuEm6F/g74I6IeKxv+YHAW+h9zPmeiLhpmhmJ6zO1Hcc2OY9PfVnPZ5sOj099nufM5q9KT5nYI0+cU+n1h02Px6c293H1eYxq8xy3vCzHc9ZWOi/FO+l9lPxvJT0u6SFJO4HvAucC17eYiDIystbH57SrzeNT36Dn86OM/ny26ciab216PM+ZzV+VnjJrzs7IqfT6w6bH41Ob++z63GvX5jluGVmOn6xdA/wm8AHgGOA54CB6O57vAj4TEVtnldOXN+9DDiZenyEZa4BVbTNsejw+y0vGnGDT4/GpyfOc2fxNsaeca589Sc6UX394jivC47N8uI+rz2NUj+e45WXZ7aztN+/Grap57zi26fP4mFnXeZ4zmz/3lIv59cfK4PExsy7zHFffst5Za2ZmZmZmZmZmZtYVy/GctWZmZmZmZmZmZmad4521ZmZmZmZmZmZmZgV4Z62ZrUiSrpR0SXLm2yV9eYTb3yvppOQajpD0dUlbJZ2WmW1mZmZm1oZ7bTOz8a2edwFmZpbql4AdEfFb8y7EzMzMzKxj3Gub2dT5k7VmtiJI+pCk7ZK+KemWBdetl/S15vrbJB22n+UnN8u2SfqkpAf289irJF0n6YHmfhcNuM25knY0t7mmxfILJD0s6T5JN0q6QdJ64FrgzKa2gybaaGZmZmZmLbjXNjPL4521ZtZ5ko4DLgc2RMQ64OIFN9kEfDwijgd2AFfsZ/nngQsjYj3wcosSPgz8NLC+ydq8oL7XANcAG4D1wMmS3rOf5X8A/CLwZuD1ABGxDfhD4NaIWB8RL7aozczMzMxsbO61zcxyeWetma0EG4AvRsTTABHxzO4rJP04cGhEbGkW3Qy8dYnlhwJrI+KrzfK/afH4ZwCfi4gfLnz8xsnAvRHxVHObzcBbl1h+CrAlIp6JiJeAL7beEmZmZmZmudxrm5kl8s5aMzMzMzMzMzMzswK8s9bMVoJ/Bs6W9CoAST+x+4qI+D7wbN+3uX6Q3jvpw5Y/B+ySdGqz/P2DHlDSKZI2Nb/eDVwoafXCx2/cB7xN0uGSVgHnAluWWP6NZvlhTeavjrFNzMzMzMwyuNc2M0u0et4FmJlNW0Q8KOmPgS2SXga2Av/Rd5PzgM9KOhh4FPiN/Sy/ALhR0o/oNXTfH/CwRwO7z2P1l8DPAdslvQTcCNzQV98Tki4F7gEE3BkRdwAssfxqeg3mM8C3h9RgZmZmZjZV7rXNzHIpIuZdg5nZsiLplRHxfPPzpcBREXHxgtt8ErglIrZPs4bm3f7bgL+OiNum8VhmZmZmZrPiXtvMVjrvrDUzG5Gk9wGX0Ts64XvA+RHx1IxruI7elymsAe4CLg5P6GZmZma2zLnXNrOVzjtrzczMzMzMzMzMzArwF4yZmZmZmZmZmZmZFeCdtWZmZmZmZmZmZmYFeGetmZmZmZmZmZmZWQHeWWtmZmZmZmZmZmZWgHfWmpmZmZmZmZmZmRXw/9iu25Yo6yKcAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "names = df['name'].unique()\n", + "\n", + "N = 2\n", + "fig, axes = plt.subplots(len(names) // N, N, figsize=(N * 12, 7 * len(names) // N))\n", + "for i, name in enumerate(names):\n", + " ax = axes[i // N, i % N]\n", + " df_ = df[df['name'] == name]\n", + " df_.groupby(['cloogl', 'cloogf'])['pipeline_ii'].max().plot(ax=ax, kind='bar')\n", + "\n", + " ax.set_title(name)\n", + " ax.set_ylabel('Pipeline II')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/polygeist b/polygeist new file mode 160000 index 00000000000..7bf134c1c5e --- /dev/null +++ b/polygeist @@ -0,0 +1 @@ +Subproject commit 7bf134c1c5e3e8e37a35d00635a96238172a053b diff --git a/polymer b/polymer new file mode 160000 index 00000000000..5d0198adcc9 --- /dev/null +++ b/polymer @@ -0,0 +1 @@ +Subproject commit 5d0198adcc940a4d6c8256f200c6a762c6a8ada5 diff --git a/pyphism/polybench/pb_flow.py b/pyphism/polybench/pb_flow.py index 464fb5ea6c9..627eaaf7914 100644 --- a/pyphism/polybench/pb_flow.py +++ b/pyphism/polybench/pb_flow.py @@ -11,11 +11,11 @@ import subprocess import traceback import xml.etree.ElementTree as ET -from collections import OrderedDict, namedtuple +from collections import OrderedDict, defaultdict, namedtuple from dataclasses import dataclass from multiprocessing import Pool from timeit import default_timer as timer -from typing import List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import pandas as pd @@ -79,6 +79,10 @@ class PbFlowOptions: pb_dir: str job: int = 1 polymer: bool = False + # CLooG options + cloogf: int = -1 + cloogl: int = -1 + dataset: str = "MINI" cleanup: bool = False debug: bool = False @@ -105,6 +109,11 @@ def __post_init__(self): self.skip_csim = True +def filter_init_args(args: Dict[str, Any]) -> Dict[str, Any]: + opt = PbFlowOptions(pb_dir="") + return {k: v for k, v in args.items() if hasattr(opt, k)} + + # ----------------------- Utility functions ------------------------------------ @@ -278,6 +287,45 @@ def parse_cosim_log(fp): ) +def fetch_pipeline_info(d: str, proj_name: str = "tb") -> Dict[str, List[Any]]: + """Find the pipeline II result from the provided project directory.""" + syn_report_dir = os.path.join(d, proj_name, "solution1", "syn", "report") + if not os.path.isdir(syn_report_dir): + return None + + syn_report = os.path.join(syn_report_dir, "csynth.xml") + if not os.path.isfile(syn_report): + return None + + # Parse the XML report and find every resource usage (tags given by RESOURCE_FIELDS) + data = defaultdict(list) + root = ET.parse(syn_report).getroot() + + def process(el: Optional[ET.Element], module_name: str): + if el is None: + return + + pipeline_ii = el.findtext("PipelineII") + if pipeline_ii is not None: + name = el.findtext("Name") + + data["module_name"].append(module_name) + data["loop_name"].append(name) + data["pipeline_ii"].append(pipeline_ii) + + return + + for child in el.getchildren(): + process(child, module_name) + + for el in root.findall("ModuleInformation/Module"): + module_name = el.findtext("Name") + loops = el.find("PerformanceEstimates/SummaryOfLoopLatency") + process(loops, module_name) + + return data + + def process_directory(d): """Process the result data within the given directory. Return a dictionary of all available data entries.""" example_name = os.path.basename(d) @@ -620,6 +668,21 @@ def toggle_cosim_setup(file: str): f.write("\n".join(lines)) +def comment_out_cosim(file: str): + with open(file, "r") as f: + lines = f.readlines() + assert lines + + lines = [l.strip() for l in lines] + pos = next(i for i, l in enumerate(lines) if "cosim_design" in l) + assert pos >= 0 and pos < len(lines) + + lines[pos] = f"# {lines[pos]}" + + with open(file, "w") as f: + f.write("\n".join(lines)) + + # ----------------------- Benchmark runners --------------------------- @@ -642,24 +705,18 @@ def get_phism_env(): root_dir = get_project_root() phism_env = os.environ.copy() - phism_env["PATH"] = "{}:{}:{}".format( - os.path.join(root_dir, "llvm", "build", "bin"), - os.path.join(root_dir, "build", "bin"), - phism_env["PATH"], + phism_env["PATH"] = ":".join( + [ + os.path.join(root_dir, "polygeist", "llvm-project", "build", "bin"), + os.path.join(root_dir, "polygeist", "build", "mlir-clang"), + os.path.join(root_dir, "polymer", "build", "bin"), + os.path.join(root_dir, "build", "bin"), + phism_env["PATH"], + ] ) phism_env["LD_LIBRARY_PATH"] = "{}:{}:{}:{}".format( - os.path.join(root_dir, "llvm", "build", "lib"), - os.path.join( - root_dir, - "llvm", - "build", - "tools", - "mlir", - "tools", - "polymer", - "pluto", - "lib", - ), + os.path.join(root_dir, "polygeist", "llvm-project", "build", "lib"), + os.path.join(root_dir, "polymer", "build", "pluto", "lib"), os.path.join(root_dir, "build", "lib"), phism_env["LD_LIBRARY_PATH"], ) @@ -827,7 +884,7 @@ def run(self, src_file): .vitis_opt() .write_tb_tcl_by_llvm() # .run_vitis_on_phism() - .run_tbgen_csim() + .run_vitis() # .backup_csim_results() # .copy_design_from_phism_to_tb() # .run_cosim() @@ -887,30 +944,34 @@ def dump_test_data(self): out_file = self.get_golden_out_file() exe_file = self.cur_file.replace(".c", ".exe") self.run_command( - cmd_list=[ - self.get_program_abspath("clang"), - "-D", - f"{self.options.dataset}_DATASET", - "-D", - "POLYBENCH_DUMP_ARRAYS", - "-I", - os.path.join(self.work_dir, "utilities"), - "-I", - os.path.join( - self.root_dir, - "llvm", - "build", - "lib", - "clang", - "13.0.0", - "include", - ), - "-lm", - self.cur_file, - os.path.join(self.work_dir, "utilities", "polybench.c"), - "-o", - exe_file, - ], + cmd=" ".join( + [ + self.get_program_abspath("clang"), + "-D", + f"{self.options.dataset}_DATASET", + "-D", + "POLYBENCH_DUMP_ARRAYS", + "-I", + os.path.join(self.work_dir, "utilities"), + "-I", + os.path.join( + self.root_dir, + "polygeist", + "llvm-project", + "build", + "lib", + "clang", + "14.0.0", + "include", + ), + "-lm", + self.cur_file, + os.path.join(self.work_dir, "utilities", "polybench.c"), + "-o", + exe_file, + ] + ), + shell=True, env=self.env, ) self.run_command( @@ -943,28 +1004,34 @@ def compile_c(self): self.run_command(cmd=f'sed -i "s/static//g" {src_file}', shell=True) self.run_command( - cmd_list=[ - self.get_program_abspath("mlir-clang"), - src_file, - "-memref-fullrank", - "-D", - f"{self.options.dataset}_DATASET", - "-D", - "POLYBENCH_DUMP_ARRAYS", - "-I={}".format( + cmd=" ".join( + [ + self.get_program_abspath("mlir-clang"), + src_file, + "-memref-fullrank", + "-S", + "-O0", + "-D", + f"{self.options.dataset}_DATASET", + "-D", + "POLYBENCH_DUMP_ARRAYS", + "-I", os.path.join( self.root_dir, - "llvm", + "polygeist", + "llvm-project", "build", "lib", "clang", - "13.0.0", + "14.0.0", "include", - ) - ), - "-I={}".format(os.path.join(self.work_dir, "utilities")), - ], + ), + "-I", + os.path.join(self.work_dir, "utilities"), + ] + ), stdout=open(self.cur_file, "w"), + shell=True, env=self.env, ) return self @@ -983,6 +1050,7 @@ def sanity_check(self): self.get_program_abspath("mlir-opt"), "-lower-affine", "-convert-scf-to-std", + "-convert-memref-to-llvm", "-convert-std-to-llvm", self.cur_file, "|", @@ -1101,12 +1169,12 @@ def polymer_opt(self): ] passes += [ "-extract-scop-stmt", - "-pluto-opt", + f'-pluto-opt="cloogf={self.options.cloogf} cloogl={self.options.cloogl} diamond-tiling"', "-debug", ] self.run_command( - cmd_list=( + cmd=" ".join( [ self.get_program_abspath("polymer-opt"), src_file, @@ -1115,6 +1183,7 @@ def polymer_opt(self): ), stderr=open(log_file, "w"), stdout=open(self.cur_file, "w"), + shell=True, env=self.env, ) @@ -1165,6 +1234,7 @@ def loop_transforms(self): src_file, f'-loop-transforms="max-span={self.options.max_span}"', "-loop-redis-and-merge", + "-fold-if", "-debug-only=loop-transforms", ] @@ -1252,6 +1322,7 @@ def lower_llvm(self): src_file, "-lower-affine", "-convert-scf-to-std", + "-convert-memref-to-llvm", "-canonicalize", convert_std_to_llvm, f"| {self.get_program_abspath('mlir-translate')} -mlir-to-llvmir", @@ -1277,7 +1348,9 @@ def vitis_opt(self): log_file = self.cur_file.replace(".llvm", ".log") xln_names = get_top_func_param_names( - self.c_source, self.work_dir, llvm_dir=os.path.join(self.root_dir, "llvm") + self.c_source, + self.work_dir, + llvm_dir=os.path.join(self.root_dir, "polygeist", "llvm-project"), ) # Whether array partition has been successful. @@ -1286,7 +1359,9 @@ def vitis_opt(self): ) args = [ - os.path.join(self.root_dir, "llvm", "build", "bin", "opt"), + os.path.join( + self.root_dir, "polygeist", "llvm-project", "build", "bin", "opt" + ), src_file, "-S", "-enable-new-pm=0", @@ -1335,7 +1410,7 @@ def write_tb_tcl_by_llvm(self): # Write the TCL for TBGEN. args = [ - os.path.join(self.root_dir, "llvm", "build", "bin", "opt"), + os.path.join(self.root_dir, "polygeist", "llvm-project", "build", "bin", "opt"), src_file, "-S", "-enable-new-pm=0", @@ -1362,9 +1437,13 @@ def write_tb_tcl_by_llvm(self): def run_vitis_on_phism(self): """Just run vitis_hls on the LLVM generated from Phism.""" + # DEPRECATED if self.options.skip_vitis: self.logger.warn("Vitis won't run since --skip-vitis has been set.") return self + if self.options.cosim: + self.logger.warn("Vitis won't run since --cosim has been set.") + return self src_file = self.cur_file base_dir = os.path.dirname(src_file) @@ -1411,12 +1490,8 @@ def run_vitis_on_phism(self): return self - def run_tbgen_csim(self, force_skip=False): + def run_vitis(self, force_skip=False): """Run the tbgen.tcl file. Assuming the Tcl file has been written.""" - if not self.options.cosim: - self.logger.warn("Cosim won't run due to the input setting.") - return self - src_file = self.cur_file base_dir = os.path.dirname(src_file) @@ -1429,6 +1504,10 @@ def run_tbgen_csim(self, force_skip=False): self.logger.debug("Toggled -setup to cosim_design.") toggle_cosim_setup(tbgen_vitis_tcl) + if not self.options.cosim: + self.logger.warn("Cosim won't run due to the input setting.") + comment_out_cosim(tbgen_vitis_tcl) + if self.options.dry_run: return self @@ -1599,7 +1678,7 @@ def pb_flow_dump_report(options: PbFlowOptions): df.to_csv(os.path.join(options.work_dir, f"pb-flow.report.{get_timestamp()}.csv")) -def pb_flow_runner(options: PbFlowOptions): +def pb_flow_runner(options: PbFlowOptions, dump_report: bool = True): """Run pb-flow with the provided arguments.""" assert os.path.isdir(options.pb_dir) @@ -1631,7 +1710,7 @@ def pb_flow_runner(options: PbFlowOptions): print("Elapsed time: {:.6f} sec".format(end - start)) # Will only dump report if Vitis has been run. - if not options.skip_vitis: + if dump_report and not options.skip_vitis: print(">>> Dumping report ... ") pb_flow_dump_report(options) diff --git a/scripts/build-llvm.sh b/scripts/build-llvm.sh index 66a1760b534..09ce375f9bc 100755 --- a/scripts/build-llvm.sh +++ b/scripts/build-llvm.sh @@ -9,8 +9,6 @@ echo "" echo ">>> Install LLVM for Phism" echo "" -TARGET="${1:-"local"}" - # If ninja is available, use it. CMAKE_GENERATOR="Unix Makefiles" if which ninja &>/dev/null; then @@ -20,16 +18,12 @@ fi # The absolute path to the directory of this script. DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -if [ "${TARGET}" == "local" ]; then - "${DIR}/check-vitis.sh" || { echo "Xilinx Vitis check failed."; exit 1; } -fi - # Make sure llvm submodule is up-to-date. git submodule sync git submodule update --init --recursive # Go to the llvm directory and carry out installation. -LLVM_DIR="${DIR}/../llvm" +LLVM_DIR="${DIR}/../polygeist/llvm-project" cd "${LLVM_DIR}" mkdir -p build @@ -37,10 +31,8 @@ cd build # Configure CMake if [ ! -f "CMakeCache.txt" ]; then - export CC=gcc - export CXX=g++ cmake ../llvm \ - -DLLVM_ENABLE_PROJECTS="mlir;llvm;clang" \ + -DLLVM_ENABLE_PROJECTS="mlir;clang" \ -DCMAKE_BUILD_TYPE=RELEASE \ -DLLVM_BUILD_EXAMPLES=OFF \ -DLLVM_TARGETS_TO_BUILD="host" \ @@ -49,14 +41,16 @@ if [ ! -f "CMakeCache.txt" ]; then -DLLVM_ENABLE_BINDINGS=OFF \ -DLLVM_INSTALL_UTILS=ON \ -DLLVM_ENABLE_ASSERTIONS=ON \ - -DBUILD_POLYMER=ON \ - -DPLUTO_LIBCLANG_PREFIX="$(llvm-config --prefix)" \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DLLVM_USE_LINKER=lld \ -G "${CMAKE_GENERATOR}" fi # Run building if [ "${CMAKE_GENERATOR}" == "Ninja" ]; then ninja + ninja check-mlir else make -j "$(nproc)" fi diff --git a/scripts/build-phism.sh b/scripts/build-phism.sh index bd6630b330d..8dab7fb8857 100755 --- a/scripts/build-phism.sh +++ b/scripts/build-phism.sh @@ -15,17 +15,10 @@ echo "" # ------------------------- Environment -------------------------- -# TARGET can be local or ci. ci build won't use the Xilinx environment -TARGET="${1:-"local"}" - # The absolute path to the directory of this script. DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" PHISM_DIR="${DIR}/.." -LLVM_DIR="${PHISM_DIR}/llvm" - -if [ "${TARGET}" == "local" ]; then - "${DIR}/check-vitis.sh" || { echo "Xilinx Vitis check failed."; exit 1; } -fi +LLVM_DIR="${PHISM_DIR}/polygeist/llvm-project/" # ------------------------- CMake Configure --------------------- diff --git a/scripts/build-polygeist.sh b/scripts/build-polygeist.sh new file mode 100755 index 00000000000..a9a05844641 --- /dev/null +++ b/scripts/build-polygeist.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# This script installs the Polygeist repository. + +set -o errexit +set -o pipefail +set -o nounset + +echo "" +echo ">>> Install Polygeist for Phism" +echo "" + +TARGET="${1:-"local"}" + +# If ninja is available, use it. +CMAKE_GENERATOR="Unix Makefiles" +if which ninja &>/dev/null; then + CMAKE_GENERATOR="Ninja" +fi + +# The absolute path to the directory of this script. +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +# Make sure Polygeist submodule is up-to-date. +git submodule sync + +if [ ! -d "${DIR}/../polygeist/llvm-project" ]; then + git submodule update --init --recursive +fi + +# Go to the polygeist directory and carry out installation. +POLYGEIST_DIR="${DIR}/../polygeist" + +cd "${POLYGEIST_DIR}" +mkdir -p build +cd build + +ls -al . + +echo "" +echo ">>> Compiler info:" +echo "" + +clang --version +clang++ --version + +# Configure CMake +if [ ! -f "CMakeCache.txt" ]; then + cmake -G "${CMAKE_GENERATOR}" \ + .. \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DLLVM_USE_LINKER=lld \ + -DMLIR_DIR="${POLYGEIST_DIR}/llvm-project/build/lib/cmake/mlir" \ + -DCLANG_DIR="${POLYGEIST_DIR}/llvm-project/build/lib/cmake/clang" \ + -DLLVM_TARGETS_TO_BUILD="host" \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCMAKE_BUILD_TYPE=DEBUG +fi + +# Run building +if [ "${CMAKE_GENERATOR}" == "Ninja" ]; then + ninja + ninja check-mlir-clang +else + make -j "$(nproc)" +fi diff --git a/scripts/build-polymer.sh b/scripts/build-polymer.sh new file mode 100755 index 00000000000..ad433a977b5 --- /dev/null +++ b/scripts/build-polymer.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# This script installs the Polymer repository. + +set -o errexit +set -o pipefail +set -o nounset + +echo "" +echo ">>> Install Polymer for Phism" +echo "" + +# If ninja is available, use it. +CMAKE_GENERATOR="Unix Makefiles" +if which ninja &>/dev/null; then + CMAKE_GENERATOR="Ninja" +fi + +# The absolute path to the directory of this script. +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +# Make sure Polymer submodule is up-to-date. +git submodule sync +git submodule update --init --recursive + +LLVM_DIR="${DIR}/../polygeist/llvm-project" +# Go to the polymer directory and carry out installation. +POLYMER_DIR="${DIR}/../polymer" + +cd "${POLYMER_DIR}" +mkdir -p build +cd build + +ls -al . + +echo "" +echo ">>> Compiler info:" +echo "" + +clang --version +clang++ --version + +# Configure CMake +if [ ! -f "CMakeCache.txt" ]; then + cmake -G "${CMAKE_GENERATOR}" \ + .. \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DLLVM_USE_LINKER=lld \ + -DMLIR_DIR="${LLVM_DIR}/build/lib/cmake/mlir" \ + -DLLVM_DIR="${LLVM_DIR}/build/lib/cmake/llvm" \ + -DLLVM_EXTERNAL_LIT="${LLVM_DIR}/build/bin/llvm-lit" \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCMAKE_BUILD_TYPE=DEBUG +fi + +# Run building +if [ "${CMAKE_GENERATOR}" == "Ninja" ]; then + ninja + ninja check-polymer +else + make -j "$(nproc)" +fi diff --git a/scripts/pb-flow.py b/scripts/pb-flow.py index d7622d23fc1..0fcc69ae3f5 100755 --- a/scripts/pb-flow.py +++ b/scripts/pb-flow.py @@ -6,9 +6,6 @@ # && python3 scripts/pb-flow.py -c example/polybench import argparse -import os -import subprocess -import sys from pyphism.polybench import pb_flow @@ -71,6 +68,11 @@ def main(): "--skip-csim", action="store_true", help="Don't run tbgen (csim)." ) parser.add_argument("--sanity-check", action="store_true", help="Run sanity check.") + parser.add_argument("--cloogl", type=int, default=-1, help="-cloogl option") + parser.add_argument("--cloogf", type=int, default=-1, help="-cloogf option") + parser.add_argument( + "--diamond-tiling", action="store_true", help="Use diamond tiling" + ) args = parser.parse_args() options = pb_flow.PbFlowOptions(**vars(args)) diff --git a/scripts/search-codegen.py b/scripts/search-codegen.py new file mode 100755 index 00000000000..4a69fe05ed8 --- /dev/null +++ b/scripts/search-codegen.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +""" +Search for different Polyhedral codegen configurations. +""" + +import argparse +import copy +import os +import shutil + +import pandas as pd + +from pyphism.polybench import pb_flow + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("pb_dir", type=str, help="Polybench directory") + parser.add_argument("--work-dir", type=str, help="The temporary work directory.") + parser.add_argument( + "-e", "--examples", nargs="+", default=[], help="Polybench examples to run." + ) + parser.add_argument("--cloogl-start", type=int, help="Start value for -cloogl") + parser.add_argument("--cloogf-start", type=int, help="Start value for -cloogf") + parser.add_argument("--cloogl-end", type=int, help="End value for -cloogl") + parser.add_argument("--cloogf-end", type=int, help="End value for -cloogf") + parser.add_argument( + "-j", "--job", type=int, default=1, help="Number of parallel jobs (default: 1)" + ) + parser.add_argument( + "--dataset", + choices=pb_flow.POLYBENCH_DATASETS, + default="MINI", + help="Polybench dataset size. ", + ) + parser.add_argument("--skip-run", action="store_true", help="Skip running pbflow.") + args = parser.parse_args() + + options = pb_flow.PbFlowOptions(**pb_flow.filter_init_args(vars(args))) + options.polymer = True + options.cosim = False + options.loop_transforms = True + options.array_partition = True + options.cleanup = False + + results = None + for cloogl in [-1] + list(range(args.cloogl_start, args.cloogl_end + 1)): + for cloogf in [-1] + list(range(args.cloogf_start, args.cloogf_end + 1)): + work_dir = os.path.join(args.work_dir, f"cloogl-{cloogl}-f-{cloogf}") + + options_ = copy.deepcopy(options) + options_.cloogf = cloogf + options_.cloogl = cloogl + options_.work_dir = work_dir + + if not args.skip_run: + if os.path.isdir(work_dir): + shutil.rmtree(work_dir) + pb_flow.pb_flow_runner(options_, dump_report=False) + + for d in pb_flow.discover_examples(options_.work_dir, options_.examples): + info = pb_flow.fetch_pipeline_info(d) + name = os.path.basename(d).lower() + + df = pd.DataFrame(info) + df.insert(0, "name", name) + df.insert(1, "cloogl", cloogl) + df.insert(2, "cloogf", cloogf) + + if results is None: + results = df + else: + results = pd.concat((results, df)) + + if results is not None: + results.to_csv(os.path.join(args.work_dir, "results.csv")) + print(results) + + +if __name__ == "__main__": + main() diff --git a/test/llvm/Transforms/VhlsLLVMRewriter/matmul.mlir b/test/llvm/Transforms/VhlsLLVMRewriter/matmul.mlir index 9adbffe6db4..f28b5792551 100644 --- a/test/llvm/Transforms/VhlsLLVMRewriter/matmul.mlir +++ b/test/llvm/Transforms/VhlsLLVMRewriter/matmul.mlir @@ -1,4 +1,4 @@ -// RUN: mlir-opt -lower-affine -convert-scf-to-std -convert-std-to-llvm='use-bare-ptr-memref-call-conv=1' %s | mlir-translate -mlir-to-llvmir | opt -enable-new-pm=0 -load ${PHISM_LIBS_DIR}/VhlsLLVMRewriter.so -mem2arr -instcombine -strip-debug -S | FileCheck %s +// RUN: mlir-opt -lower-affine -convert-scf-to-std -convert-memref-to-llvm -convert-std-to-llvm='use-bare-ptr-memref-call-conv=1' %s | mlir-translate -mlir-to-llvmir | opt -enable-new-pm=0 -load ${PHISM_LIBS_DIR}/VhlsLLVMRewriter.so -mem2arr -instcombine -strip-debug -S | FileCheck %s // CHECK: noinline // CHECK: define void @matmul([200 x [300 x float]]* %[[A:.*]], [300 x [400 x float]]* %[[B:.*]], [200 x [400 x float]]* %[[C:.*]]) #[[ATTR:.*]] diff --git a/test/mlir/Transforms/FoldIfPass/fold-if.mlir b/test/mlir/Transforms/FoldIfPass/fold-if.mlir new file mode 100644 index 00000000000..bb4bafbe9be --- /dev/null +++ b/test/mlir/Transforms/FoldIfPass/fold-if.mlir @@ -0,0 +1,25 @@ +// RUN: phism-opt -fold-if %s | FileCheck %s + +#set = affine_set<(d0) : (d0 - 5 >= 0)> + +// CHECK-LABEL: func @square_last_half +func @square_last_half(%A: memref<10xf32>) { + affine.for %i = 0 to 10 { + affine.if #set(%i) { + %0 = affine.load %A[%i] : memref<10xf32> + %1 = mulf %0, %0 : f32 + affine.store %1, %A[%i] : memref<10xf32> + } + } + return +} + +// CHECK: %[[C0:.*]] = constant 0 : index +// CHECK: %[[C5:.*]] = constant -5 : index +// CHECK: %[[VAL0:.*]] = addi %{{.*}}, %[[C5]] : index +// CHECK: %[[VAL1:.*]] = cmpi sge, %[[VAL0]], %[[C0]] : index +// CHECK: %[[VAL2:.*]] = affine.load +// CHECK: %[[VAL3:.*]] = mulf %[[VAL2]], %[[VAL2]] : f32 +// CHECK: %[[VAL4:.*]] = affine.load %[[ARG0:.*]][%[[ARG1:.*]]] : memref<10xf32> +// CHECK: %[[VAL5:.*]] = select %[[VAL1]], %[[VAL3]], %[[VAL4]] : f32 +// CHECK: affine.store %[[VAL5]], %[[ARG0]][%[[ARG1]]] : memref<10xf32> diff --git a/tools/phism-opt/CMakeLists.txt b/tools/phism-opt/CMakeLists.txt index 5f0f1444675..9d2c7876189 100644 --- a/tools/phism-opt/CMakeLists.txt +++ b/tools/phism-opt/CMakeLists.txt @@ -14,7 +14,6 @@ target_link_libraries(phism-opt MLIRLoopAnalysis MLIRAnalysis MLIRDialect - MLIREDSC MLIROptLib MLIRParser MLIRPass