diff --git a/experimental/include/experimental/Transforms/HandshakeRewriteTerms.h b/experimental/include/experimental/Transforms/HandshakeRewriteTerms.h new file mode 100644 index 0000000000..d62e60582b --- /dev/null +++ b/experimental/include/experimental/Transforms/HandshakeRewriteTerms.h @@ -0,0 +1,35 @@ +//===- HandshakeRewriteTerms.h - Rewrite Terms in Handshake Operation Sequences +//-----*- C++ -*-===// +// +// Dynamatic is under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares the --handshake-rewrite-terms pass. +// +//===----------------------------------------------------------------------===// + +#ifndef EXPERIMENTAL_TRANSFORMS_HANDSHAKEREWRITETERMS_H +#define EXPERIMENTAL_TRANSFORMS_HANDSHAKEREWRITETERMS_H + +#include "dynamatic/Support/DynamaticPass.h" +#include "dynamatic/Support/LLVM.h" +#include "mlir/Dialect/Arith/IR/Arith.h" +#include "mlir/IR/DialectRegistry.h" +#include "mlir/Pass/Pass.h" + +namespace dynamatic { +namespace experimental { + +#define GEN_PASS_DECL_HANDSHAKEREWRITETERMS +#define GEN_PASS_DEF_HANDSHAKEREWRITETERMS +#include "experimental/Transforms/Passes.h.inc" + +std::unique_ptr rewriteHandshakeTerms(); + +} // namespace experimental +} // namespace dynamatic + +#endif // EXPERIMENTAL_TRANSFORMS_HANDSHAKEREWRITETERMS_H diff --git a/experimental/include/experimental/Transforms/Passes.h b/experimental/include/experimental/Transforms/Passes.h index 61afa7416d..b2d89180d9 100644 --- a/experimental/include/experimental/Transforms/Passes.h +++ b/experimental/include/experimental/Transforms/Passes.h @@ -18,6 +18,8 @@ #include "dynamatic/Support/LLVM.h" #include "mlir/Pass/Pass.h" +#include "experimental/Transforms/HandshakeRewriteTerms.h" + namespace dynamatic { namespace experimental { /// Generate the code for registering passes. diff --git a/experimental/include/experimental/Transforms/Passes.td b/experimental/include/experimental/Transforms/Passes.td index 1563a23144..c0dab1821f 100644 --- a/experimental/include/experimental/Transforms/Passes.td +++ b/experimental/include/experimental/Transforms/Passes.td @@ -16,6 +16,18 @@ include "dynamatic/Support/Passes.td" include "mlir/Pass/PassBase.td" +def HandshakeRewriteTerms : DynamaticPass< "handshake-rewrite-terms"> { + let summary = "Rewrite terms in Handshake operation sequences."; + let description = [{ + Removes redundant operations and simplifies the IR by applying a series + of optimizations. The pass uses a greedy pattern rewriter to apply the + optimizations, which are based on the specific semantics of Handshake + operations. The pass is conservative and does not perform any aggressive + transformations that could potentially change the behavior of the circuit. + }]; + let constructor = "dynamatic::experimental::rewriteHandshakeTerms()"; +} + def HandshakeCombineSteeringLogic : DynamaticPass< "handshake-combine-steering-logic"> { let summary = "Combine common steering logic between different handshake operations."; let description = [{ diff --git a/experimental/lib/Transforms/CMakeLists.txt b/experimental/lib/Transforms/CMakeLists.txt index 50cf1c085b..ba89d8a528 100644 --- a/experimental/lib/Transforms/CMakeLists.txt +++ b/experimental/lib/Transforms/CMakeLists.txt @@ -1,6 +1,7 @@ add_dynamatic_library(DynamaticExperimentalTransforms HandshakePlaceBuffersCustom.cpp HandshakeCombineSteeringLogic.cpp + HandshakeRewriteTerms.cpp HandshakeStraightToQueue.cpp DEPENDS diff --git a/experimental/lib/Transforms/HandshakeRewriteTerms.cpp b/experimental/lib/Transforms/HandshakeRewriteTerms.cpp new file mode 100644 index 0000000000..a34f4120b1 --- /dev/null +++ b/experimental/lib/Transforms/HandshakeRewriteTerms.cpp @@ -0,0 +1,2270 @@ +//===-HandshakeRewriteTerms.cpp - Rewrite Terms in Handshake Operation Sequences +//----*- C++ -*-===// +// +// Dynamatic is under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Implements rewrite patterns for the Handshake rewrite terms pass, which are +// greedily applied on the IR. The pass looks for certain sequences of handshake +// operations and simplifies them. The pass preserves the behaviour of the +// circuit. +//===----------------------------------------------------------------------===// + +#include "experimental/Transforms/HandshakeRewriteTerms.h" +#include "dynamatic/Dialect/Handshake/HandshakeCanonicalize.h" +#include "dynamatic/Dialect/Handshake/HandshakeOps.h" +#include "dynamatic/Support/CFG.h" +#include "dynamatic/Support/LLVM.h" +#include "mlir/IR/AsmState.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/Operation.h" +#include "mlir/IR/PatternMatch.h" +#include "mlir/IR/Value.h" +#include "mlir/IR/ValueRange.h" +#include "mlir/Support/LLVM.h" +#include "mlir/Support/LogicalResult.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Casting.h" +#include +#include +#include +#include + +using namespace mlir; +using namespace dynamatic; + +namespace { + +#define OPTIM_DISTR \ + false // associate it with a disable of DistributeSuppresses, + // DistributeMergeRepeats,DistributeMuxRepeats +#define OPTIM_BRANCH_TO_SUPP \ + false // associate it with a disable of ConstructSuppresses, + // FixBranchesToSuppresses + +// Helper functions +bool isFunctionStartArgument(Value value) { + auto blockArg = dyn_cast(value); + if (!blockArg || !isa(value.getType())) + return false; + + auto funcOp = dyn_cast(blockArg.getOwner()->getParentOp()); + return funcOp && blockArg.getArgNumber() == funcOp.getNumArguments() - 1; +} + +bool isDerivedFromFunctionStart(Value value, DenseSet &visited) { + if (!visited.insert(value).second) + return false; + if (isFunctionStartArgument(value)) + return true; + + Operation *defOp = value.getDefiningOp(); + if (!defOp) + return false; + + if (auto condBranchOp = dyn_cast(defOp)) { + if (condBranchOp.getTrueResult() == value || + condBranchOp.getFalseResult() == value) + return isDerivedFromFunctionStart(condBranchOp.getDataOperand(), visited); + } + + if (auto muxOp = dyn_cast(defOp)) { + if (muxOp.getResult() == value) { + for (Value operand : muxOp.getDataOperands()) + if (isDerivedFromFunctionStart(operand, visited)) + return true; + } + } + if (isa(defOp) && + defOp->getResult(0) == value) { + for (Value operand : defOp->getOperands()) + if (isDerivedFromFunctionStart(operand, visited)) + return true; + } + + // Follow simple forwarding operations. + if (defOp->getNumOperands() == 1 && defOp->getNumResults() == 1 && + defOp->getResult(0) == value && + defOp->getOperand(0).getType() == value.getType()) + return isDerivedFromFunctionStart(defOp->getOperand(0), visited); + + return false; +} + +bool isDerivedFromFunctionStart(Value value) { + DenseSet visited; + return isDerivedFromFunctionStart(value, visited); +} + +bool mayReachMemoryInterface(Value value, DenseSet &visited) { + if (!isa(value.getType())) + return false; + if (!visited.insert(value).second) + return false; + + for (Operation *user : value.getUsers()) { + if (isa(user)) + return true; + + // Follow control values through routing ops so we can catch paths that + // eventually drive an LSQ or memory-controller control input. + if (isa( + user)) { + for (Value result : user->getResults()) { + if (mayReachMemoryInterface(result, visited)) + return true; + } + } + } + + return false; +} + +bool mayReachMemoryInterface(Value value) { + DenseSet visited; + return mayReachMemoryInterface(value, visited); +} + +bool wouldReplaceMemoryControlWithStartDerived(Value oldValue, Value newValue) { + return mayReachMemoryInterface(oldValue) && + isDerivedFromFunctionStart(newValue); +} + +bool isSuppress(handshake::ConditionalBranchOp condBranchOp) { + return (condBranchOp.getTrueResult().getUsers().empty() && + !condBranchOp.getFalseResult().getUsers().empty()); +} + +int returnTotalCondBranchUsers(handshake::ConditionalBranchOp condBranchOp) { + return std::distance(condBranchOp.getTrueResult().getUsers().begin(), + condBranchOp.getTrueResult().getUsers().end()) + + std::distance(condBranchOp.getFalseResult().getUsers().begin(), + condBranchOp.getFalseResult().getUsers().end()); +} + +/// Returns Operation holding a Branch, if it exists, and the its index (by +/// reference) in the operands of the Mux. Returns a nullptr and -1 (by +/// reference) otherwise +/// Takes a generic Operation, but returns if it is not a MuxOp or MergeOp +Operation *returnBranchFormingCycle(Operation *muxOrMergeOp, int &cycleInputIdx, + bool &isCycleBranchTrueSucc) { + cycleInputIdx = -1; + isCycleBranchTrueSucc = false; + bool isMux = false; + if (isa_and_nonnull(muxOrMergeOp)) + isMux = true; + else if (!isa_and_nonnull(muxOrMergeOp) && + !isa_and_nonnull(muxOrMergeOp)) + return nullptr; + + DenseSet branches; + for (auto *user : muxOrMergeOp->getResults().getUsers()) { + if (isa_and_nonnull(user)) { + auto br = cast(user); + branches.insert(br); + } + } + + // One of the conditional branches that were found should feed the + // muxOrMergeOp forming a cycle + int operIdx = 0; + handshake::ConditionalBranchOp cycleBranchOp = nullptr; + auto muxOrMergeOperands = muxOrMergeOp->getOperands(); + if (isMux) + muxOrMergeOperands = cast(muxOrMergeOp).getDataOperands(); + for (auto operand : muxOrMergeOperands) { + auto *op = operand.getDefiningOp(); + if (isa_and_nonnull(op)) { + auto br = cast(op); + if (branches.contains(br)) { + cycleInputIdx = operIdx; + cycleBranchOp = br; + break; + } + } + operIdx++; + } + + if (cycleBranchOp != nullptr) { + Value muxOrMergeInnerOperand = muxOrMergeOperands[cycleInputIdx]; + Value branchTrueResult = cycleBranchOp.getTrueResult(); + Value branchFalseResult = cycleBranchOp.getFalseResult(); + if (branchTrueResult == muxOrMergeInnerOperand) + isCycleBranchTrueSucc = true; + else if (branchFalseResult == muxOrMergeInnerOperand) { + isCycleBranchTrueSucc = false; + } + } + + return cycleBranchOp; +} + +Operation *returnBranchExitingCycle(Operation *muxOrMergeOp) { + int cycleInputIdx; + bool isCycleBranchTrueSucc; + Operation *potentialBranchOp = returnBranchFormingCycle( + muxOrMergeOp, cycleInputIdx, isCycleBranchTrueSucc); + if (potentialBranchOp == nullptr) + return nullptr; + + assert(isa_and_nonnull(potentialBranchOp)); + handshake::ConditionalBranchOp cyclicBranchOp = + cast(potentialBranchOp); + + Value loopCond = cyclicBranchOp.getConditionOperand(); + Value origLoopCond; + bool isNegatedCyclicBr = false; + int countOfInverters = 0; + while (isa_and_nonnull(loopCond.getDefiningOp())) { + loopCond = loopCond.getDefiningOp()->getOperand(0); + countOfInverters++; + } + if (countOfInverters % 2 != 0) + isNegatedCyclicBr = true; + origLoopCond = loopCond.getDefiningOp()->getOperand(0); + + handshake::ConditionalBranchOp exitingBranch = nullptr; + for (auto *user : muxOrMergeOp->getResults().getUsers()) { + if (isa_and_nonnull(user) && + user != cyclicBranchOp) { + auto br = cast(user); + + Value cond = br.getConditionOperand(); + Value origCond; + bool isNegated = false; + countOfInverters = 0; + while (isa_and_nonnull(cond.getDefiningOp())) { + cond = cond.getDefiningOp()->getOperand(0); + countOfInverters++; + } + if (countOfInverters % 2 != 0) + isNegated = true; + origCond = cond.getDefiningOp()->getOperand(0); + + if (origCond == origLoopCond && isNegated != isNegatedCyclicBr) { + exitingBranch = br; + break; + } + } + } + + return exitingBranch; +} + +Operation *isConditionInverted(Value condition) { + handshake::NotIOp existingNotOp = nullptr; + for (auto condUser : condition.getUsers()) { + if (isa_and_nonnull(condUser)) { + existingNotOp = cast(condUser); + break; + } + } + return existingNotOp; +} + +Operation *isConditionFeedingInit(Value condition) { + handshake::MergeOp existingInit = nullptr; + for (auto iterCondRes : condition.getUsers()) { + if (isa_and_nonnull(iterCondRes)) { + existingInit = cast(iterCondRes); + break; + } + } + + return existingInit; +} + +// Rules E +/// Erases unconditional branches (which would eventually lower to simple +/// wires). +struct EraseUnconditionalBranches + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::BranchOp brOp, + PatternRewriter &rewriter) const override { + rewriter.replaceOp(brOp, brOp.getOperand()); + // llvm::errs() << "\t***Rules E: Removing unconditional Branch!!***\n"; + return success(); + } +}; + +// Rules E +/// Erases merges with a single data operand. +struct EraseSingleInputMerges : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::MergeOp mergeOp, + PatternRewriter &rewriter) const override { + if (mergeOp->getNumOperands() != 1) + return failure(); + + rewriter.replaceOp(mergeOp, mergeOp.getOperand(0)); + return success(); + } +}; + +// Rules E +/// Erases muxes with a single data operand. Inserts a sink operation to consume +/// the select operand of erased muxes. +struct EraseSingleInputMuxes : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::MuxOp muxOp, + PatternRewriter &rewriter) const override { + ValueRange dataOperands = muxOp.getDataOperands(); + if (dataOperands.size() != 1) + return failure(); + + // Insert a sink to consume the mux's select token + rewriter.setInsertionPoint(muxOp); + Value select = muxOp.getSelectOperand(); + rewriter.create(muxOp->getLoc(), select); + + rewriter.replaceOp(muxOp, dataOperands.front()); + return success(); + } +}; + +// Rules E +/// Erases control merges with a single data operand. If necessary, inserts a +/// sourced 0 constant to replace any real uses of the index result of erased +/// control merges. +struct EraseSingleInputControlMerges + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::ControlMergeOp cmergeOp, + PatternRewriter &rewriter) const override { + if (cmergeOp->getNumOperands() != 1) + return failure(); + + Value dataRes = cmergeOp.getOperand(0); + Value indexRes = cmergeOp.getIndex(); + if (hasRealUses(indexRes)) { + // If the index result has uses, then replace it with a sourced constant + // with value 0 (the index of the cmerge's single input) + rewriter.setInsertionPoint(cmergeOp); + + // Create a source operation for the constant + handshake::SourceOp srcOp = rewriter.create( + cmergeOp->getLoc(), rewriter.getNoneType()); + inheritBB(cmergeOp, srcOp); + + /// NOTE: Sourcing this value may cause problems with very exotic uses of + /// control merges. Ideally, we would check whether the value is sourcable + /// first; if not we would connect the constant to the control network + /// instead. + + // Build the attribute for the constant + Type indexResType = indexRes.getType(); + handshake::ConstantOp cstOp = rewriter.create( + cmergeOp.getLoc(), indexResType, + rewriter.getIntegerAttr(indexResType, 0), srcOp.getResult()); + inheritBB(cmergeOp, cstOp); + + // Replace the cmerge's index result with a constant 0 + rewriter.replaceOp(cmergeOp, {dataRes, cstOp.getResult()}); + return success(); + } + + // Replace the cmerge's data result with its unique operand, erase any sinks + // consuming the index result, and finally delete the cmerge + rewriter.replaceAllUsesWith(cmergeOp.getResult(), dataRes); + eraseSinkUsers(indexRes, rewriter); + rewriter.eraseOp(cmergeOp); + return success(); + } +}; + +// Rules E +/// Downgrades control merges whose index result has no real uses to simpler +/// yet equivalent merges. +struct DowngradeIndexlessControlMerge + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::ControlMergeOp cmergeOp, + PatternRewriter &rewriter) const override { + Value indexRes = cmergeOp.getIndex(); + if (hasRealUses(indexRes)) + return failure(); + + // Create a merge operation to replace the cmerge + rewriter.setInsertionPoint(cmergeOp); + handshake::MergeOp mergeOp = rewriter.create( + cmergeOp.getLoc(), cmergeOp->getOperands()); + inheritBB(cmergeOp, mergeOp); + + // Replace the cmerge's data result with the merge's result, erase any + // sinks consuming the index result, and finally delete the cmerge + rewriter.replaceAllUsesWith(cmergeOp.getResult(), mergeOp.getResult()); + eraseSinkUsers(indexRes, rewriter); + rewriter.eraseOp(cmergeOp); + return success(); + } +}; + +// Rules E +/// Remove Conditional Branches that have no successors +struct RemoveDoubleSinkBranches + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::ConditionalBranchOp condBranchOp, + PatternRewriter &rewriter) const override { + Value branchTrueResult = condBranchOp.getTrueResult(); + Value branchFalseResult = condBranchOp.getFalseResult(); + + // Pattern match fails if the Branch has a true or false successor + if (!branchTrueResult.getUsers().empty() || + !branchFalseResult.getUsers().empty()) + return failure(); + + rewriter.eraseOp(condBranchOp); + // llvm::errs() << "\t***Rules E: remove-double-sink-branch!***\n"; + + return success(); + } +}; + +// Rules E +/// Remove floating cycles that can have a Mux or a Merge at the cycle header +template +struct RemoveFloatingLoop : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(MuxOrMergeOp muxOrMergeOp, + PatternRewriter &rewriter) const override { + bool isMux = false; + if (isa_and_nonnull(muxOrMergeOp)) + isMux = true; + else if (!isa_and_nonnull(muxOrMergeOp)) + return failure(); + + if (muxOrMergeOp->getNumOperands() < 2) + return failure(); + + auto users = (muxOrMergeOp->getResults()[0]).getUsers(); + // Pattern match fails if the muxOrMergeOp has more than 1 user or no users + // at all + if (users.empty() || std::distance(users.begin(), users.end()) != 1) + return failure(); + + int cycleInputIdx; + bool isCycleBranchTrueSucc; + Operation *potentialBranchOp = returnBranchFormingCycle( + muxOrMergeOp, cycleInputIdx, isCycleBranchTrueSucc); + if (potentialBranchOp == nullptr) + return failure(); + + assert(isa_and_nonnull(potentialBranchOp)); + handshake::ConditionalBranchOp condBranchOp = + cast(potentialBranchOp); + + int outsideInputIdx = 1 - cycleInputIdx; + + // Pattern match fails if the Branch has more than 1 user + if ((returnTotalCondBranchUsers(condBranchOp) != 1)) + return failure(); + + auto muxOrMergeOperands = muxOrMergeOp->getOperands(); + if (isMux) + muxOrMergeOperands = + cast(muxOrMergeOp).getDataOperands(); + + // Safety step to be able to delete the cycle, we first replace all uses of + // condBranchOp with muxOrMerge, then erase the latter then erase the former + rewriter.replaceAllUsesWith(condBranchOp.getDataOperand(), + muxOrMergeOperands[outsideInputIdx]); + rewriter.eraseOp(muxOrMergeOp); + rewriter.eraseOp(condBranchOp); + + llvm::errs() << "\t***Rules E: remove-GENERIC-floating-loop!***\n"; + + return success(); + } +}; + +// TODO the if-then-else rewrites +// Rules A +// Remove redundant if-then-else structures +struct RemoveBranchMergeIfThenElse + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::MergeOp mergeOp, + PatternRewriter &rewriter) const override { + + if (mergeOp->getNumOperands() != 2) + return failure(); + + // The two operands of the merge should be conditional Branches; otherwise, + // the pattern match fails + Operation *firstOperand = mergeOp.getOperands()[0].getDefiningOp(); + Operation *secondOperand = mergeOp.getOperands()[1].getDefiningOp(); + if (!isa_and_nonnull(firstOperand) || + !isa_and_nonnull(secondOperand)) + return failure(); + + handshake::ConditionalBranchOp firstBranchOperand = + cast(firstOperand); + handshake::ConditionalBranchOp secondBranchOperand = + cast(secondOperand); + + if (!OPTIM_BRANCH_TO_SUPP) { + // New conditions: to ensure we only conside suppresses + // If the first branch is not a suppress, the pattern match fails + if ((!firstBranchOperand.getTrueResult().getUsers().empty()) || + (firstBranchOperand.getTrueResult().getUsers().empty() && + firstBranchOperand.getFalseResult().getUsers().empty())) + return failure(); + // If the second branch is not a suppress, the pattern match fails + if ((!secondBranchOperand.getTrueResult().getUsers().empty()) || + (secondBranchOperand.getTrueResult().getUsers().empty() && + secondBranchOperand.getFalseResult().getUsers().empty())) + return failure(); + } + + if (!OPTIM_DISTR) { + // Kill Distrib. for Optim.: Another new condition (to make a meaningful + // use of the suppress->distribute rule): the two suppresses should have + // only a single usage; otherwise the pattern match fails + if (std::distance(firstBranchOperand.getFalseResult().getUsers().begin(), + firstBranchOperand.getFalseResult().getUsers().end()) != + 1) + return failure(); + if (std::distance( + secondBranchOperand.getFalseResult().getUsers().begin(), + secondBranchOperand.getFalseResult().getUsers().end()) != 1) + return failure(); + } + Value firstBranchCondition = firstBranchOperand.getConditionOperand(); + Value secondBranchCondition = secondBranchOperand.getConditionOperand(); + + // If the two original conditions are not equivalent, the pattern match + // fails + if (firstBranchCondition != secondBranchCondition) + return failure(); + + // If the data input of the two Branches is not the same, the pattern match + // fails + Value firstBranchData = firstBranchOperand.getDataOperand(); + Value secondBranchData = secondBranchOperand.getDataOperand(); + if (firstBranchData != secondBranchData) + return failure(); + + Value mergeOutput = mergeOp.getResult(); + + // Replace all uses of the merge output with the input of the Branches + rewriter.replaceAllUsesWith(mergeOutput, firstBranchData); + // Delete the merge + rewriter.eraseOp(mergeOp); + + // Delegated the deletiong to such Branches through a separate function that + // deletes Branches ffedng sinks on both sides + // If the only user of the two Branches is the merge, delete them + // if ((std::distance(firstBranchOperand.getTrueResult().getUsers().begin(), + // firstBranchOperand.getTrueResult().getUsers().end()) + + // std::distance(firstBranchOperand.getFalseResult().getUsers().begin(), + // firstBranchOperand.getFalseResult().getUsers().end())) + // == + // 1) + // rewriter.eraseOp(firstBranchOperand); + + // if + // ((std::distance(secondBranchOperand.getTrueResult().getUsers().begin(), + // secondBranchOperand.getTrueResult().getUsers().end()) + // + + // std::distance( + // secondBranchOperand.getFalseResult().getUsers().begin(), + // secondBranchOperand.getFalseResult().getUsers().end())) == 1) + // rewriter.eraseOp(secondBranchOperand); + + llvm::errs() << "\t***Rules A: remove-branch-merge-if-then-else!***\n"; + return success(); + } +}; + +// Rules A Removes Conditional Branch and Mux +// operation pairs if both the inputs of the Mux are outputs of the Conditional +// Branch. The results of the MeMuxrge are replaced with the data operand. +struct RemoveBranchMuxIfThenElse : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::MuxOp muxOp, + PatternRewriter &rewriter) const override { + + if (muxOp->getNumOperands() != 3) + return failure(); + + // The two operands of the mux should be conditional Branches; otherwise, + // the pattern match fails + Operation *firstOperand = muxOp.getDataOperands()[0].getDefiningOp(); + Operation *secondOperand = muxOp.getDataOperands()[1].getDefiningOp(); + if (!isa_and_nonnull(firstOperand) || + !isa_and_nonnull(secondOperand)) + return failure(); + + handshake::ConditionalBranchOp firstBranchOperand = + cast(firstOperand); + handshake::ConditionalBranchOp secondBranchOperand = + cast(secondOperand); + + if (!OPTIM_BRANCH_TO_SUPP) { + // New conditions: to ensure we only conside suppresses + // If the first branch is not a suppress, the pattern match fails + if ((!firstBranchOperand.getTrueResult().getUsers().empty()) || + (firstBranchOperand.getTrueResult().getUsers().empty() && + firstBranchOperand.getFalseResult().getUsers().empty())) + return failure(); + // If the second branch is not a suppress, the pattern match fails + if ((!secondBranchOperand.getTrueResult().getUsers().empty()) || + (secondBranchOperand.getTrueResult().getUsers().empty() && + secondBranchOperand.getFalseResult().getUsers().empty())) + return failure(); + } + + if (!OPTIM_DISTR) { + // Kill Distrib. for Optim.: Another new condition (to make a meaningful + // use of the suppress->distribute rule): the two suppresses should have + // only a single usage; otherwise the pattern match fails + if (std::distance(firstBranchOperand.getFalseResult().getUsers().begin(), + firstBranchOperand.getFalseResult().getUsers().end()) != + 1) + return failure(); + if (std::distance( + secondBranchOperand.getFalseResult().getUsers().begin(), + secondBranchOperand.getFalseResult().getUsers().end()) != 1) + return failure(); + } + + Value firstBranchCondition = firstBranchOperand.getConditionOperand(); + Value firstOriginalBranchCondition = firstBranchCondition; + if (isa_and_nonnull( + firstBranchCondition.getDefiningOp())) + firstOriginalBranchCondition = + firstBranchCondition.getDefiningOp()->getOperand(0); + + Value secondBranchCondition = secondBranchOperand.getConditionOperand(); + Value secondOriginalBranchCondition = secondBranchCondition; + if (isa_and_nonnull( + secondBranchCondition.getDefiningOp())) + secondOriginalBranchCondition = + secondBranchCondition.getDefiningOp()->getOperand(0); + + // If the two original conditions are not equivalent, the pattern match + // fails + if (firstOriginalBranchCondition != secondOriginalBranchCondition) + return failure(); + + // If the data input of the two Branches is not the same, the pattern match + // fails + Value firstBranchData = firstBranchOperand.getDataOperand(); + Value secondBranchData = secondBranchOperand.getDataOperand(); + if (firstBranchData != secondBranchData) + return failure(); + + Value muxOutput = muxOp.getResult(); + + // Replace all uses of the mux output with the input of the Branches + rewriter.replaceAllUsesWith(muxOutput, firstBranchData); + // Delete the mux + rewriter.eraseOp(muxOp); + + // Delegated the deletiong to such Branches through a separate function that + // deletes Branches ffedng sinks on both sides + // If the only user of the two + // Branches is the merge, delete them if + // ((std::distance(firstBranchOperand.getTrueResult().getUsers().begin(), + // firstBranchOperand.getTrueResult().getUsers().end()) + + // std::distance(firstBranchOperand.getFalseResult().getUsers().begin(), + // firstBranchOperand.getFalseResult().getUsers().end())) + // == + // 1) + // rewriter.eraseOp(firstBranchOperand); + + // if + // ((std::distance(secondBranchOperand.getTrueResult().getUsers().begin(), + // secondBranchOperand.getTrueResult().getUsers().end()) + // + + // std::distance( + // secondBranchOperand.getFalseResult().getUsers().begin(), + // secondBranchOperand.getFalseResult().getUsers().end())) == 1) + // rewriter.eraseOp(secondBranchOperand); + + llvm::errs() << "\t***Rules A: remove-branch-mux-if-then-else!***\n"; + return success(); + } +}; + +// Rules A +// Removes redundant loops that are guarded by two suppresses +template +struct EliminateRedundantLoop : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(MuxOrMergeOp muxOrMergeOp, + PatternRewriter &rewriter) const override { + bool isMux = false; + if (isa_and_nonnull(muxOrMergeOp)) + isMux = true; + else if (!isa_and_nonnull(muxOrMergeOp)) + return failure(); + + if (muxOrMergeOp->getNumOperands() < 2) + return failure(); + + auto users = (muxOrMergeOp->getResults()[0]).getUsers(); + + // Pattern match fails if the muxOrMergeOp does not have exactly 2 users (2 + // suppresses: one feeding the cycle and the other going outside of the + // loop) + if (users.empty() || std::distance(users.begin(), users.end()) != 2) + return failure(); + + int cycleInputIdx; + bool isCycleBranchTrueSucc; + Operation *potentialBranchOp = returnBranchFormingCycle( + muxOrMergeOp, cycleInputIdx, isCycleBranchTrueSucc); + if (potentialBranchOp == nullptr) + return failure(); + + assert(isa_and_nonnull(potentialBranchOp)); + handshake::ConditionalBranchOp condBranchOp = + cast(potentialBranchOp); + + if (!isSuppress(condBranchOp)) + return failure(); + + int outsideInputIdx = 1 - cycleInputIdx; + + Operation *potentialExitingBranchOp = + returnBranchExitingCycle(muxOrMergeOp); + if (potentialExitingBranchOp == nullptr) + return failure(); + + assert(isa_and_nonnull( + potentialExitingBranchOp)); + handshake::ConditionalBranchOp exitingCondBranchOp = + cast(potentialExitingBranchOp); + + if (!isSuppress(exitingCondBranchOp)) + return failure(); + + auto muxOrMergeOperands = muxOrMergeOp->getOperands(); + if (isMux) + muxOrMergeOperands = + cast(muxOrMergeOp).getDataOperands(); + + if (wouldReplaceMemoryControlWithStartDerived( + exitingCondBranchOp.getFalseResult(), + muxOrMergeOperands[outsideInputIdx])) + return failure(); + rewriter.replaceAllUsesWith(exitingCondBranchOp.getFalseResult(), + muxOrMergeOperands[outsideInputIdx]); + + rewriter.eraseOp(exitingCondBranchOp); + llvm::errs() << "\t***Rules A: eliminate-GENERIC-redundant-loop!***\n"; + + return success(); + } +}; + +// Rules B +// Extract the index result of the Control Merge in a loop structure. +struct ExtractLoopCondition + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::ControlMergeOp cmergeOp, + PatternRewriter &rewriter) const override { + if (cmergeOp->getNumOperands() != 2) + return failure(); + + auto cmergeUsers = (cmergeOp.getResults()).getUsers(); + if (cmergeUsers.empty()) + return failure(); + + int cycleInputIdx; + bool isCycleBranchTrueSucc; + Operation *potentialBranchOp = returnBranchFormingCycle( + cmergeOp, cycleInputIdx, isCycleBranchTrueSucc); + if (potentialBranchOp == nullptr) + return failure(); + + assert(isa_and_nonnull(potentialBranchOp)); + handshake::ConditionalBranchOp condBranchOp = + cast(potentialBranchOp); + + int outsideInputIdx = 1 - cycleInputIdx; + + if (!isSuppress(condBranchOp)) + return failure(); + + // Retrieve the loop condition + Value condition = condBranchOp.getConditionOperand(); + bool needNot = ((isCycleBranchTrueSucc && cycleInputIdx == 0) || + (!isCycleBranchTrueSucc && cycleInputIdx == 1)); + bool foundNot = false; + handshake::NotIOp existingNotOp; + Operation *potentialNotOp = isConditionInverted(condition); + if (potentialNotOp != nullptr) { + foundNot = true; + existingNotOp = cast(potentialNotOp); + } + + // Identify the value of the Init token + int constVal = outsideInputIdx; + + // Obtain the Start signal from the last argument of any block + Block *cmergeBlock = cmergeOp->getBlock(); + MutableArrayRef l = cmergeBlock->getArguments(); + if (l.empty()) + return failure(); + mlir::Value start = l.back(); + if (!isa(start.getType())) + return failure(); + + if (needNot) { + if (foundNot) + condition = existingNotOp.getResult(); + else { + rewriter.setInsertionPoint(condBranchOp); + handshake::NotIOp notIOp = rewriter.create( + condBranchOp->getLoc(), condition); + inheritBB(condBranchOp, notIOp); + condition = notIOp.getResult(); + } + } + + // Check if there is an already existing Init, i.e., a Merge fed from the + // iterCond + bool foundInit = false; + handshake::MergeOp existingInitOp; + Operation *potentialInitOp = isConditionFeedingInit(condition); + if (potentialInitOp != nullptr) { + foundInit = true; + existingInitOp = cast(potentialInitOp); + } + + Value muxSel; + if (foundInit) { + muxSel = existingInitOp.getResult(); + } else { + // Create a new ConstantOp in the same block as that of the branch + // forming the cycle + Type constantType = rewriter.getIntegerType(1); + Value valueOfConstant = rewriter.create( + condBranchOp->getLoc(), + rewriter.getIntegerAttr(constantType, constVal), start); + + // Create a new Init + ValueRange operands = {condition, valueOfConstant}; + rewriter.setInsertionPoint(cmergeOp); + handshake::MergeOp mergeOp = + rewriter.create(cmergeOp.getLoc(), operands); + muxSel = mergeOp.getResult(); + inheritBB(cmergeOp, mergeOp); + } + + Value index = cmergeOp.getIndex(); + rewriter.replaceAllUsesWith(index, muxSel); + + llvm::errs() << "\t***Rules B: extract-loop-mux-condition!***\n"; + return success(); + } +}; + +// Rules B +// Extract the index result of the Control Merge in an if-then-else structure. +struct ExtractIfThenElseCondition + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::ControlMergeOp cmergeOp, + PatternRewriter &rewriter) const override { + + // Pattern match fails if the cntrlMerge does not have exactly two inputs + if (cmergeOp->getNumOperands() != 2) + return failure(); + + // The two operands of the Cmerge should be conditional Branches; otherwise, + // the pattern match fails + Operation *firstOperand = cmergeOp.getOperands()[0].getDefiningOp(); + Operation *secondOperand = cmergeOp.getOperands()[1].getDefiningOp(); + if (!isa_and_nonnull(firstOperand) || + !isa_and_nonnull(secondOperand)) + return failure(); + + handshake::ConditionalBranchOp firstBranchOperand = + cast(firstOperand); + handshake::ConditionalBranchOp secondBranchOperand = + cast(secondOperand); + + if (!OPTIM_BRANCH_TO_SUPP) { + // New condition: The firstBranchOperand has to be a suppress; otherwise, + // the pattern match fails + if (!firstBranchOperand.getTrueResult().getUsers().empty() || + firstBranchOperand.getFalseResult().getUsers().empty()) + return failure(); + // The secondBranchOperand has to be a suppress; otherwise, + // the pattern match fails + if (!secondBranchOperand.getTrueResult().getUsers().empty() || + secondBranchOperand.getFalseResult().getUsers().empty()) + return failure(); + } + + Value firstBranchCondition = firstBranchOperand.getConditionOperand(); + Value firstOriginalBranchCondition = firstBranchCondition; + if (isa_and_nonnull( + firstBranchCondition.getDefiningOp())) + firstOriginalBranchCondition = + firstBranchCondition.getDefiningOp()->getOperand(0); + + Value secondBranchCondition = secondBranchOperand.getConditionOperand(); + Value secondOriginalBranchCondition = secondBranchCondition; + if (isa_and_nonnull( + secondBranchCondition.getDefiningOp())) + secondOriginalBranchCondition = + secondBranchCondition.getDefiningOp()->getOperand(0); + + // If the two original conditions are not equivalent, the pattern match + // fails + if (firstOriginalBranchCondition != secondOriginalBranchCondition) + return failure(); + + Value index = cmergeOp.getIndex(); + + // Check if we need to negate the condition before feeding it to the index + // output of the cmerge + // (1) Should negate if the in0 receives the true succ of the Branch and the + // condition of the Branch is not negated OR if it receives the false succ + // and the condition of the Branch is negated + bool reversedFirstInput = + (firstBranchOperand.getTrueResult() == cmergeOp.getOperands()[0] && + firstBranchCondition == firstOriginalBranchCondition) || + (firstBranchOperand.getFalseResult() == cmergeOp.getOperands()[0] && + firstBranchCondition != firstOriginalBranchCondition); + // (1) Should negate if the in0 receives the true succ of the Branch and the + // condition of the Branch is not negated OR if it receives the false succ + // and the condition of the Branch is negated + bool reversedSecondInput = + (secondBranchOperand.getFalseResult() == cmergeOp.getOperands()[1] && + secondBranchCondition == secondOriginalBranchCondition) || + (secondBranchOperand.getTrueResult() == cmergeOp.getOperands()[1] && + secondBranchCondition != secondOriginalBranchCondition); + + bool needNot = reversedFirstInput && reversedSecondInput; + Value cond; + if (needNot) { + + // Check if the condition already feeds a NOT, no need to create a new one + bool foundNot = false; + handshake::NotIOp existingNotOp; + for (auto condRes : firstOriginalBranchCondition.getUsers()) { + if (isa_and_nonnull(condRes)) { + foundNot = true; + existingNotOp = cast(condRes); + break; + } + } + + if (foundNot) { + cond = existingNotOp.getResult(); + } else { + rewriter.setInsertionPoint(cmergeOp); + handshake::NotIOp notIOp = rewriter.create( + cmergeOp->getLoc(), firstOriginalBranchCondition); + inheritBB(cmergeOp, notIOp); + cond = notIOp.getResult(); + } + + } else { + cond = firstOriginalBranchCondition; + } + + // Replace the Cmerge index output with the branch condition + rewriter.replaceAllUsesWith(index, cond); + + llvm::errs() << "\t***Rules B: extract-if-then-else-mux-condition!***\n"; + return success(); + } +}; + +// Rules C +// Replaces a pair of consecutive Suppress operations with a +// a single suppress operation with a mux at its condition input. +struct ShortenSuppressPairs + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult + matchAndRewrite(handshake::ConditionalBranchOp firstCondBranchOp, + PatternRewriter &rewriter) const override { + // Consider only Branches that either have trueSuccs or falseSuccs but not + // both + Value firstTrueResult = firstCondBranchOp.getTrueResult(); + Value firstFalseResult = firstCondBranchOp.getFalseResult(); + bool firstTrueSuccOnlyFlag = (!firstTrueResult.getUsers().empty() && + firstFalseResult.getUsers().empty()); + bool firstFalseSuccOnlyFlag = (firstTrueResult.getUsers().empty() && + !firstFalseResult.getUsers().empty()); + if (!firstTrueSuccOnlyFlag && !firstFalseSuccOnlyFlag) + return failure(); + + // There must be only 1 successor; otherwise, we cannot optimize + if (std::distance(firstTrueResult.getUsers().begin(), + firstTrueResult.getUsers().end()) > 1 || + std::distance(firstFalseResult.getUsers().begin(), + firstFalseResult.getUsers().end()) > 1) + return failure(); + + Operation *succ = nullptr; + Value succVal; + if (firstTrueSuccOnlyFlag) { + succ = *firstTrueResult.getUsers().begin(); + succVal = firstTrueResult; + } else { + succ = *firstFalseResult.getUsers().begin(); + succVal = firstFalseResult; + } + + // This succ must be a conditional branch; otherwise, the pattern match + // fails + if (!isa_and_nonnull(succ)) + return failure(); + + handshake::ConditionalBranchOp secondCondBranchOp = + cast(succ); + + // The pattern match should fail if this Branch has succs both in the true + // and false sides + Value secondTrueResult = secondCondBranchOp.getTrueResult(); + Value secondFalseResult = secondCondBranchOp.getFalseResult(); + bool secondTrueSuccOnlyFlag = (!secondTrueResult.getUsers().empty() && + secondFalseResult.getUsers().empty()); + bool secondFalseSuccOnlyFlag = (secondTrueResult.getUsers().empty() && + !secondFalseResult.getUsers().empty()); + if (!secondTrueSuccOnlyFlag && !secondFalseSuccOnlyFlag) + return failure(); + + // For the shortening to work, the two branches should have their + // successor in the same direction (either true or false); otherwise, we + // need to enforce it by negating.. When they are not consistent, we will + // force both to have their succs in the false side and sink in true side + // (like a typical suppress) + Value condBr1 = firstCondBranchOp.getConditionOperand(); + Value condBr2 = secondCondBranchOp.getConditionOperand(); + if (firstTrueSuccOnlyFlag && secondFalseSuccOnlyFlag) { + + // Check if the condition already feeds a NOT, no need to create a new one + bool foundNot = false; + handshake::NotIOp existingNotOp; + for (auto condRes : condBr1.getUsers()) { + if (isa_and_nonnull(condRes)) { + foundNot = true; + existingNotOp = cast(condRes); + break; + } + } + Value newCond; + if (foundNot) { + newCond = existingNotOp.getResult(); + } else { + // Insert a NOT at the condition input of the first Branch + rewriter.setInsertionPoint(firstCondBranchOp); + handshake::NotIOp notIOp = rewriter.create( + firstCondBranchOp->getLoc(), condBr1); + inheritBB(firstCondBranchOp, notIOp); + + newCond = notIOp.getResult(); + } + + rewriter.replaceAllUsesWith(condBr1, newCond); + + // Replace all uses coming from the true side of the first Branch with + // the false side of it + rewriter.replaceAllUsesWith(firstTrueResult, firstFalseResult); + // Adjust the firstTrueSuccOnlyFlag and firstFalseSuccOnlyFlag + firstTrueSuccOnlyFlag = false; + firstFalseSuccOnlyFlag = true; + + // Retrieve the new value of the condition, in case it is not updated + condBr1 = firstCondBranchOp.getConditionOperand(); + } else { + // llvm::errs() << firstFalseSuccOnlyFlag << ", " << + // secondTrueSuccOnlyFlag + // << ", " << firstTrueSuccOnlyFlag << ", " + // << secondFalseSuccOnlyFlag << "\n"; + // assert(firstFalseSuccOnlyFlag && secondTrueSuccOnlyFlag); + + if (firstFalseSuccOnlyFlag && secondTrueSuccOnlyFlag) { + // Check if the condition already feeds a NOT, no need to create a new + // one + bool foundNot = false; + handshake::NotIOp existingNotOp; + for (auto condRes : condBr2.getUsers()) { + if (isa_and_nonnull(condRes)) { + foundNot = true; + existingNotOp = cast(condRes); + break; + } + } + Value newCond; + if (foundNot) { + newCond = existingNotOp.getResult(); + } else { + // Insert a NOT at the condition input of the second Branch + rewriter.setInsertionPoint(secondCondBranchOp); + handshake::NotIOp notIOp = rewriter.create( + secondCondBranchOp->getLoc(), condBr2); + inheritBB(secondCondBranchOp, notIOp); + + newCond = notIOp.getResult(); + } + + rewriter.replaceAllUsesWith(condBr2, newCond); + + // Replace all uses coming from the true side of the first Branch with + // the false side of it + rewriter.replaceAllUsesWith(secondTrueResult, secondFalseResult); + // Adjust the secondTrueSuccOnlyFlag and firstFalseSuccOnlyFlag + secondTrueSuccOnlyFlag = false; + secondFalseSuccOnlyFlag = true; + + // Retrieve the new value of the condition, in case it is not updated + condBr2 = secondCondBranchOp.getConditionOperand(); + } + } + + // The goal now is to replace the two Branches with a single Branch, we do + // so by deleting the first branch and adjusting the inputs of the second + // branch + // The new condition is a Mux, calculate its inputs: One input of the + // Mux will be a constant that should take the value of the condition that + // feeds a sink (for suppressing) and should be triggered from Source + int64_t constantValue; + if (firstTrueSuccOnlyFlag) { + assert(secondTrueSuccOnlyFlag); + // this means suppress when the condition is false + constantValue = 0; + } else { + assert(firstFalseSuccOnlyFlag && secondFalseSuccOnlyFlag); + // this means suppress when the condition is true + constantValue = 1; + } + rewriter.setInsertionPoint(secondCondBranchOp); + Value source = + rewriter.create(secondCondBranchOp->getLoc()); + + Type constantType = rewriter.getIntegerType(1); + Value constantVal = rewriter.create( + secondCondBranchOp->getLoc(), constantType, + rewriter.getIntegerAttr(constantType, constantValue), source); + + // Create a new Mux and assign its operands + ValueRange muxOperands; + if (firstTrueSuccOnlyFlag) { + assert(secondTrueSuccOnlyFlag); + // This means suppress when the condition is false, so put the constVal + // at in0 and the additional condition at in1 + muxOperands = {constantVal, condBr2}; + } else { + assert(firstFalseSuccOnlyFlag && secondFalseSuccOnlyFlag); + // This means suppress when the condition is true, so put the constVal + // at in1 and the additional condition at in0 + muxOperands = {condBr2, constantVal}; + } + rewriter.setInsertionPoint(secondCondBranchOp); + handshake::MuxOp mux = rewriter.create( + secondCondBranchOp->getLoc(), muxOperands[0].getType(), condBr1, + muxOperands); // Zeinab comment: adding muxOperands[0].getType() to + // create + inheritBB(secondCondBranchOp, mux); + + // Correct the inputs of the second Branch + Value muxResult = mux.getResult(); + Value dataOperand = firstCondBranchOp.getDataOperand(); + ValueRange branchOperands = {muxResult, dataOperand}; + secondCondBranchOp->setOperands(branchOperands); + + // Erase the first Branch + rewriter.eraseOp(firstCondBranchOp); + + llvm::errs() << "\t***Rules C: shorten-suppress-pairs!***\n"; + + return success(); + } +}; + +// Rules C +// Replaces a pair of consecutive Repeats with a +// a single Repeat with a mux at its condition input. +struct ShortenMuxRepeatPairs : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::MuxOp firstMuxOp, + PatternRewriter &rewriter) const override { + // Search for a Repeat structure + // (1) Get the users of the Mux. If they are not exactly two, the pattern + // match fails + auto firstMuxUsers = (firstMuxOp.getResult()).getUsers(); + if (std::distance(firstMuxUsers.begin(), firstMuxUsers.end()) != 2) + return failure(); + + // If the mux is not driven by a Merge (i.e., INIT), the pattern match + // fails + if (!isa_and_nonnull( + firstMuxOp.getSelectOperand().getDefiningOp())) + return failure(); + + // One user must be a Branch; otherwise, the pattern match fails + bool firstFoundCondBranch = false; + handshake::ConditionalBranchOp firstCondBranchOp; + // Also, One user must be another Mux belonging to a second Repeat; + // otherwise, the pattern match fails + bool foundSecondMux = false; + handshake::MuxOp secondMuxOp; + for (auto muxUser : firstMuxUsers) { + if (isa_and_nonnull(muxUser)) { + firstFoundCondBranch = true; + firstCondBranchOp = cast(muxUser); + } else if (isa_and_nonnull(muxUser)) { + foundSecondMux = true; + secondMuxOp = cast(muxUser); + } + } + if (!firstFoundCondBranch && !foundSecondMux) + return failure(); + + // The firstCondBranchOp must be also be an operand + // forming a cycle with the firstMuxOp; otherwise, the pattern match fails + bool firstFoundCycle = false; + int operIdx = 0; + int firstMuxCycleInputIdx = 0; + for (auto muxOperand : firstMuxOp->getOperands()) { + if (isa_and_nonnull( + muxOperand.getDefiningOp())) + if (cast(muxOperand.getDefiningOp()) == + firstCondBranchOp) { + firstFoundCycle = true; + firstMuxCycleInputIdx = operIdx; + break; + } + operIdx++; + } + if (!firstFoundCycle) + return failure(); + int firstMuxOuterInputIdx = (firstMuxCycleInputIdx == 0) ? 1 : 0; + + // The firstCondBranchOp should not have any more successors; otherwise, + // it is not a Repeat structure + if (std::distance(firstCondBranchOp->getResults().getUsers().begin(), + firstCondBranchOp->getResults().getUsers().end()) != 1) + return failure(); + + // At this point we have firstMuxOp and firstCondBranchOp which constitute + // the first Repeat sturcture. It should feed a second Repeat structure + // otherwise the pattern match fails + // Check if secondMuxOp also has a Branch forming a cycle + auto secondMuxUsers = (secondMuxOp.getResult()).getUsers(); + if (secondMuxUsers.empty()) + return failure(); + + // If the mux is not driven by a Merge (i.e., INIT), the pattern match + // fails + if (!isa_and_nonnull( + secondMuxOp.getSelectOperand().getDefiningOp())) + return failure(); + + // One user must be a Branch; otherwise, the pattern match fails + bool secondFoundCondBranch = false; + // This second Repeat could be feeding many users including maybe another + // non-loop Branch + DenseSet branches; + for (auto muxUser : secondMuxUsers) { + if (isa_and_nonnull(muxUser)) { + secondFoundCondBranch = true; + branches.insert(cast(muxUser)); + } + } + if (!secondFoundCondBranch) + return failure(); + + // One of the branches in the set of Branches must be also be an operand + // forming a cycle with the mux; otherwise, the pattern match fails + bool secondFoundCycle = false; + operIdx = 0; + int secondMuxCycleInputIdx = 0; + handshake::ConditionalBranchOp secondCondBranchOp; + for (auto muxOperand : secondMuxOp->getOperands()) { + if (isa_and_nonnull( + muxOperand.getDefiningOp())) + if (branches.contains(cast( + muxOperand.getDefiningOp()))) { + secondFoundCycle = true; + secondMuxCycleInputIdx = operIdx; + secondCondBranchOp = + cast(muxOperand.getDefiningOp()); + break; + } + operIdx++; + } + if (!secondFoundCycle) + return failure(); + int secondMuxOuterInputIdx = (secondMuxCycleInputIdx == 0) ? 1 : 0; + + // The secondCondBranchOp should not have any more successors; otherwise, + // it is not a Repeat structure + if (std::distance(secondCondBranchOp->getResults().getUsers().begin(), + secondCondBranchOp->getResults().getUsers().end()) != 1) + return failure(); + + // Now, we are sure we have two consecutive Repeats, check the signs of + // loop conditions. Retrieve the values at the Muxes inputs Retrieve the + // values at the mux inputs + OperandRange firstMuxDataOperands = firstMuxOp.getDataOperands(); + Value firstMuxOuterOperand = firstMuxDataOperands[firstMuxOuterInputIdx]; + Value firstMuxInnerOperand = firstMuxDataOperands[firstMuxCycleInputIdx]; + OperandRange secondMuxDataOperands = secondMuxOp.getDataOperands(); + Value secondMuxOuterOperand = secondMuxDataOperands[secondMuxOuterInputIdx]; + Value secondMuxInnerOperand = secondMuxDataOperands[secondMuxCycleInputIdx]; + + // Identify which output of the two Branches feeds the muxInnerOperand + Value firstBranchTrueResult = firstCondBranchOp.getTrueResult(); + Value firstBranchFalseResult = firstCondBranchOp.getFalseResult(); + bool firstTrueIterFlag = (firstBranchTrueResult == firstMuxInnerOperand); + Value secondBranchTrueResult = secondCondBranchOp.getTrueResult(); + Value secondBranchFalseResult = secondCondBranchOp.getFalseResult(); + bool secondTrueIterFlag = (secondBranchTrueResult == secondMuxInnerOperand); + + Value condBr1 = firstCondBranchOp.getConditionOperand(); + Value condBr2 = secondCondBranchOp.getConditionOperand(); + if (firstTrueIterFlag && !secondTrueIterFlag) { + + // Check if the condition already feeds a NOT, no need to create a new one + bool foundNot = false; + handshake::NotIOp existingNotOp; + for (auto condRes : condBr2.getUsers()) { + if (isa_and_nonnull(condRes)) { + foundNot = true; + existingNotOp = cast(condRes); + break; + } + } + Value newCond; + if (foundNot) { + newCond = existingNotOp.getResult(); + } else { + rewriter.setInsertionPoint(secondCondBranchOp); + // Insert a NOT at the condition input of the second Branch + handshake::NotIOp notIOp = rewriter.create( + secondCondBranchOp->getLoc(), condBr2); + inheritBB(secondCondBranchOp, notIOp); + + newCond = notIOp.getResult(); + } + + rewriter.replaceAllUsesWith(condBr2, newCond); + + // Replace all uses coming from the false side of the second Branch with + // the true side of it + rewriter.replaceAllUsesWith(secondBranchFalseResult, + secondBranchTrueResult); + // Adjust the secondTrueIterFlag + secondTrueIterFlag = true; + + // Retrieve the new value of the condition, in case it is not updated + condBr2 = secondCondBranchOp.getConditionOperand(); + + } else if (!firstTrueIterFlag && secondTrueIterFlag) { + + // Check if the condition already feeds a NOT, no need to create a new one + bool foundNot = false; + handshake::NotIOp existingNotOp; + for (auto condRes : condBr1.getUsers()) { + if (isa_and_nonnull(condRes)) { + foundNot = true; + existingNotOp = cast(condRes); + break; + } + } + Value newCond; + if (foundNot) { + newCond = existingNotOp.getResult(); + } else { + rewriter.setInsertionPoint(firstCondBranchOp); + // Insert a NOT at the condition input of the first Branch + handshake::NotIOp notIOp = rewriter.create( + firstCondBranchOp->getLoc(), condBr1); + inheritBB(firstCondBranchOp, notIOp); + + newCond = notIOp.getResult(); + } + + rewriter.replaceAllUsesWith(condBr1, newCond); + + // Replace all uses coming from the false side of the second Branch with + // the true side of it + rewriter.replaceAllUsesWith(firstBranchFalseResult, + firstBranchTrueResult); + // Adjust the secondTrueIterFlag + firstTrueIterFlag = true; + + // Retrieve the new value of the condition, in case it is not updated + condBr1 = firstCondBranchOp.getConditionOperand(); + } + + // The goal now is to replace the two Repeats with a single Repeat, we do + // so by deleting the first Mux and Branch and adjusting the inputs of the + // second Mux The new condition is a Mux, calculate its inputs: One input + // of the Mux will be a constant that should take the value of the + // condition that feeds a sink (for suppressing) and should be triggered + // from Source + int64_t constantValue; + if (firstTrueIterFlag) { + assert(secondTrueIterFlag); + // this means repeat when the condition is true + constantValue = 1; + } else { + assert(!firstTrueIterFlag && !secondTrueIterFlag); + // this means repeat when the condition is false + constantValue = 0; + } + Value source = + rewriter.create(secondCondBranchOp->getLoc()); + Type constantType = rewriter.getIntegerType(1); + Value constantVal = rewriter.create( + secondCondBranchOp->getLoc(), constantType, + rewriter.getIntegerAttr(constantType, constantValue), source); + + // Create a new Mux and assign its operands + ValueRange muxOperands; + if (firstTrueIterFlag) { + assert(firstTrueIterFlag); + // This means repeat when the condition is true, so put the constVal at + // in1 and the additional condition (i.e., condition of the first + // Repeat) at in0 + muxOperands = {condBr1, constantVal}; + } else { + assert(!firstTrueIterFlag && !firstTrueIterFlag); + // This means repeat when the condition is false, so put the constVal at + // in0 and the additional condition (i.e., the condition of the first + // Repeat) at in1 + muxOperands = {constantVal, condBr1}; + } + rewriter.setInsertionPoint(secondCondBranchOp); + handshake::MuxOp mux = rewriter.create( + secondCondBranchOp->getLoc(), muxOperands[0].getType(), condBr2, + muxOperands); + inheritBB(secondCondBranchOp, mux); + + Value muxResult = mux.getResult(); + + // Correct the select of the second Mux; at this point, we are sure it + // comes from a Merge (INIT), so retrieve it + assert(isa_and_nonnull( + secondMuxOp.getSelectOperand().getDefiningOp())); + handshake::MergeOp initOp = cast( + secondMuxOp.getSelectOperand().getDefiningOp()); + // The convention used in the ExtractLoopMuxCondition rewrite puts the + // loop condition at in0 of the Merge + rewriter.replaceAllUsesWith(initOp.getDataOperands()[0], muxResult); + + // Correct the condition of the second Branch + rewriter.replaceAllUsesWith(condBr2, muxResult); + + // Correct the external input of the second Mux + rewriter.replaceAllUsesWith(secondMuxOuterOperand, firstMuxOuterOperand); + + // Erase the first Branch and first Mux + rewriter.replaceAllUsesWith(firstCondBranchOp.getDataOperand(), + firstMuxOuterOperand); + rewriter.eraseOp(firstMuxOp); + rewriter.eraseOp(firstCondBranchOp); + + // TODO: Erase the first INIT as well!!!!! + + llvm::errs() << "\t***Rules C: shorten-mux-repeat-pairs!***\n"; + + return success(); + } +}; + +// Rules C +// Replaces a pair of consecutive Repeats with a +// a single Repeat with a merge at its condition input. +struct ShortenMergeRepeatPairs : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::MergeOp firstMergeOp, + PatternRewriter &rewriter) const override { + // Search for a Repeat structure that has a single user other than the Supp + // (1) Get the users of the Merge. If they are not exactly two, the + // pattern match fails + auto firstMergeUsers = (firstMergeOp.getResult()).getUsers(); + if (std::distance(firstMergeUsers.begin(), firstMergeUsers.end()) != 2) + return failure(); + + // One user must be a Branch; otherwise, the pattern match fails + bool firstFoundCondBranch = false; + handshake::ConditionalBranchOp firstCondBranchOp; + // One user must be another Merge belonging to a second Repeat; otherwise, + // the pattern match fails + bool foundSecondMerge = false; + handshake::MergeOp secondMergeOp; + for (auto mergeUser : firstMergeUsers) { + if (isa_and_nonnull(mergeUser)) { + firstFoundCondBranch = true; + firstCondBranchOp = cast(mergeUser); + } else if (isa_and_nonnull(mergeUser)) { + foundSecondMerge = true; + secondMergeOp = cast(mergeUser); + } + } + if (!firstFoundCondBranch && !foundSecondMerge) + return failure(); + + // The firstCondBranchOp must be also be an operand + // forming a cycle with the firstMergeOp; otherwise, the pattern match + // fails + bool firstFoundCycle = false; + int operIdx = 0; + int firstMergeCycleInputIdx = 0; + for (auto mergeOperand : firstMergeOp->getOperands()) { + if (isa_and_nonnull( + mergeOperand.getDefiningOp())) + if (cast( + mergeOperand.getDefiningOp()) == firstCondBranchOp) { + firstFoundCycle = true; + firstMergeCycleInputIdx = operIdx; + break; + } + operIdx++; + } + if (!firstFoundCycle) + return failure(); + int firstMergeOuterInputIdx = (firstMergeCycleInputIdx == 0) ? 1 : 0; + + // The firstCondBranchOp should not have any more successors; otherwise, + // it is not a Repeat structure + if (std::distance(firstCondBranchOp->getResults().getUsers().begin(), + firstCondBranchOp->getResults().getUsers().end()) != 1) + return failure(); + + // At this point we have firstMergeOp and firstCondBranchOp which + // constitute the first Repeat sturcture. It should feed a second Repeat + // structure otherwise the pattern match fails Check if secondMergeOp also + // has a Branch forming a cycle + auto secondMergeUsers = (secondMergeOp.getResult()).getUsers(); + if (secondMergeUsers.empty()) + return failure(); + + // One user must be a Branch; otherwise, the pattern match fails + bool secondFoundCondBranch = false; + // This second Repeat could be feeding many users including maybe another + // non-loop Branch + DenseSet branches; + for (auto mergeUser : secondMergeUsers) { + if (isa_and_nonnull(mergeUser)) { + secondFoundCondBranch = true; + branches.insert(cast(mergeUser)); + } + } + if (!secondFoundCondBranch) + return failure(); + + // One of the branches in the set of Branches must be also be an operand + // forming a cycle with the merge; otherwise, the pattern match fails + bool secondFoundCycle = false; + operIdx = 0; + int secondMergeCycleInputIdx = 0; + handshake::ConditionalBranchOp secondCondBranchOp; + for (auto mergeOperand : secondMergeOp->getOperands()) { + if (isa_and_nonnull( + mergeOperand.getDefiningOp())) + if (branches.contains(cast( + mergeOperand.getDefiningOp()))) { + secondFoundCycle = true; + secondMergeCycleInputIdx = operIdx; + secondCondBranchOp = cast( + mergeOperand.getDefiningOp()); + break; + } + operIdx++; + } + if (!secondFoundCycle) + return failure(); + int secondMergeOuterInputIdx = (secondMergeCycleInputIdx == 0) ? 1 : 0; + + // The secondCondBranchOp should not have any more successors; otherwise, + // it is not a Repeat structure + if (std::distance(secondCondBranchOp->getResults().getUsers().begin(), + secondCondBranchOp->getResults().getUsers().end()) != 1) + return failure(); + + // Now, we are sure we have two consecutive Repeats, check the signs of + // loop conditions. Retrieve the values at the Merges inputs Retrieve the + // values at the merge inputs + OperandRange firstMergeDataOperands = firstMergeOp.getDataOperands(); + Value firstMergeOuterOperand = + firstMergeDataOperands[firstMergeOuterInputIdx]; + Value firstMergeInnerOperand = + firstMergeDataOperands[firstMergeCycleInputIdx]; + OperandRange secondMergeDataOperands = secondMergeOp.getDataOperands(); + Value secondMergeOuterOperand = + secondMergeDataOperands[secondMergeOuterInputIdx]; + Value secondMergeInnerOperand = + secondMergeDataOperands[secondMergeCycleInputIdx]; + + // Identify which output of the two Branches feeds the mergeInnerOperand + Value firstBranchTrueResult = firstCondBranchOp.getTrueResult(); + Value firstBranchFalseResult = firstCondBranchOp.getFalseResult(); + bool firstTrueIterFlag = (firstBranchTrueResult == firstMergeInnerOperand); + Value secondBranchTrueResult = secondCondBranchOp.getTrueResult(); + Value secondBranchFalseResult = secondCondBranchOp.getFalseResult(); + bool secondTrueIterFlag = + (secondBranchTrueResult == secondMergeInnerOperand); + + Value condBr1 = firstCondBranchOp.getConditionOperand(); + Value condBr2 = secondCondBranchOp.getConditionOperand(); + if (firstTrueIterFlag && !secondTrueIterFlag) { + + // Check if the condition already feeds a NOT, no need to create a new one + bool foundNot = false; + handshake::NotIOp existingNotOp; + for (auto condRes : condBr2.getUsers()) { + if (isa_and_nonnull(condRes)) { + foundNot = true; + existingNotOp = cast(condRes); + break; + } + } + + Value newCond; + if (foundNot) { + newCond = existingNotOp.getResult(); + } else { + rewriter.setInsertionPoint(secondCondBranchOp); + // Insert a NOT at the condition input of the second Branch + handshake::NotIOp notIOp = rewriter.create( + secondCondBranchOp->getLoc(), condBr2); + inheritBB(secondCondBranchOp, notIOp); + + newCond = notIOp.getResult(); + } + + rewriter.replaceAllUsesWith(condBr2, newCond); + + // Replace all uses coming from the false side of the second Branch with + // the true side of it + rewriter.replaceAllUsesWith(secondBranchFalseResult, + secondBranchTrueResult); + // Adjust the secondTrueIterFlag + secondTrueIterFlag = true; + + // Retrieve the new value of the condition, in case it is not updated + condBr2 = secondCondBranchOp.getConditionOperand(); + + } else if (!firstTrueIterFlag && secondTrueIterFlag) { + + // Check if the condition already feeds a NOT, no need to create a new one + bool foundNot = false; + handshake::NotIOp existingNotOp; + for (auto condRes : condBr1.getUsers()) { + if (isa_and_nonnull(condRes)) { + foundNot = true; + existingNotOp = cast(condRes); + break; + } + } + + Value newCond; + if (foundNot) { + newCond = existingNotOp.getResult(); + } else { + rewriter.setInsertionPoint(firstCondBranchOp); + // Insert a NOT at the condition input of the first Branch + handshake::NotIOp notIOp = rewriter.create( + firstCondBranchOp->getLoc(), condBr1); + inheritBB(firstCondBranchOp, notIOp); + + newCond = notIOp.getResult(); + } + + rewriter.replaceAllUsesWith(condBr1, newCond); + + // Replace all uses coming from the false side of the second Branch with + // the true side of it + rewriter.replaceAllUsesWith(firstBranchFalseResult, + firstBranchTrueResult); + // Adjust the secondTrueIterFlag + firstTrueIterFlag = true; + + // Retrieve the new value of the condition, in case it is not updated + condBr1 = firstCondBranchOp.getConditionOperand(); + } + + // The goal now is to replace the two Repeats with a single Repeat, we do + // so by deleting the first Merge and Branch and adjusting the inputs of + // the second Merge + // The new condition is a Merge, calculate its inputs: + // One input of the Merge will be a constant that should take the value of + // the condition that feeds a sink (for suppressing) and should be + // triggered from Source + int64_t constantValue; + if (firstTrueIterFlag) { + assert(secondTrueIterFlag); + // this means repeat when the condition is true + constantValue = 1; + } else { + assert(!firstTrueIterFlag && !secondTrueIterFlag); + // this means repeat when the condition is false + constantValue = 0; + } + Value source = + rewriter.create(secondCondBranchOp->getLoc()); + Type constantType = rewriter.getIntegerType(1); + Value constantVal = rewriter.create( + secondCondBranchOp->getLoc(), constantType, + rewriter.getIntegerAttr(constantType, constantValue), source); + + // Create a new Mux and assign its operands + ValueRange muxOperands; + if (firstTrueIterFlag) { + assert(firstTrueIterFlag); + // This means repeat when the condition is true, so put the constVal at + // in1 and the additional condition (i.e., condition of the first + // Repeat) at in0 + muxOperands = {condBr1, constantVal}; + } else { + assert(!firstTrueIterFlag && !firstTrueIterFlag); + // This means repeat when the condition is false, so put the constVal at + // in0 and the additional condition (i.e., the condition of the first + // Repeat) at in1 + muxOperands = {constantVal, condBr1}; + } + rewriter.setInsertionPoint(secondCondBranchOp); + handshake::MuxOp mux = rewriter.create( + secondCondBranchOp->getLoc(), muxOperands[0].getType(), condBr2, + muxOperands); + inheritBB(secondCondBranchOp, mux); + + //////////////////////////////////////// + + Value muxResult = mux.getResult(); + + // Correct the condition of the second Branch + rewriter.replaceAllUsesWith(condBr2, muxResult); + + // Correct the external input of the second Merge + rewriter.replaceAllUsesWith(secondMergeOuterOperand, + firstMergeOuterOperand); + + // Erase the first Branch and first Merge + rewriter.replaceAllUsesWith(firstCondBranchOp.getDataOperand(), + firstMergeOuterOperand); + rewriter.eraseOp(firstMergeOp); + rewriter.eraseOp(firstCondBranchOp); + + llvm::errs() << "\t***Rules C: shorten-merge-repeat-pairs!***\n"; + + return success(); + } +}; + +// Rules D +// Breaks a Branch that has both true and false successors into two +// Suppresses. +struct ConstructSuppresses + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::ConditionalBranchOp condBranchOp, + PatternRewriter &rewriter) const override { + if (OPTIM_BRANCH_TO_SUPP) + return failure(); + + if (isSuppress(condBranchOp) || + condBranchOp.getFalseResult().getUsers().empty()) + return failure(); + + // Create a new Branch and let its true side replace the true side of the + // old Branch + Value dataOperand = condBranchOp.getDataOperand(); + Value condOperand = condBranchOp.getConditionOperand(); + ValueRange branchOperands = {condOperand, dataOperand}; + rewriter.setInsertionPoint(condBranchOp); + handshake::ConditionalBranchOp newBranch = + rewriter.create(condBranchOp->getLoc(), + branchOperands); + inheritBB(condBranchOp, newBranch); + + Value branchFalseResult = condBranchOp.getFalseResult(); + Value newBranchFalseResult = newBranch.getFalseResult(); + rewriter.replaceAllUsesWith(branchFalseResult, newBranchFalseResult); + + // llvm::errs() << "\t***Rules D: break-branches!***\n"; + + return success(); + } +}; + +// Rules D +// If a Branch has one successor in the true side, reverse it to be really a +// Suppress +struct FixSuppresses : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::ConditionalBranchOp condBranchOp, + PatternRewriter &rewriter) const override { + if (OPTIM_BRANCH_TO_SUPP) + return failure(); + + if (isSuppress(condBranchOp)) + return failure(); + + // Construct a new Branch that should feed the true side of the old Branch + // with its false side after inverting the condition + Value dataOperand = condBranchOp.getDataOperand(); + Value condOperand = condBranchOp.getConditionOperand(); + + bool foundNot = false; + handshake::NotIOp existingNotOp; + Operation *potentialNotOp = isConditionInverted(condOperand); + if (potentialNotOp != nullptr) { + foundNot = true; + existingNotOp = cast(potentialNotOp); + } + + if (foundNot) { + condOperand = existingNotOp.getResult(); + } else { + rewriter.setInsertionPoint(condBranchOp); + handshake::NotIOp notIOp = rewriter.create( + condBranchOp->getLoc(), condOperand); + inheritBB(condBranchOp, notIOp); + condOperand = notIOp.getResult(); + } + + ValueRange branchOperands = {condOperand, dataOperand}; + rewriter.setInsertionPoint(condBranchOp); + handshake::ConditionalBranchOp newBranch = + rewriter.create(condBranchOp->getLoc(), + branchOperands); + inheritBB(condBranchOp, newBranch); + + Value branchTrueResult = condBranchOp.getTrueResult(); + Value newBranchFalseResult = newBranch.getFalseResult(); + rewriter.replaceAllUsesWith(branchTrueResult, newBranchFalseResult); + + // llvm::errs() << "\t***Rules D: fix-branches-for-suppresses!***\n"; + + return success(); + } +}; + +// Rules D +// If a Suppress has two or more successors, feed each successor by a separate +// Suppress +struct DistributeSuppresses + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::ConditionalBranchOp condBranchOp, + PatternRewriter &rewriter) const override { + if (OPTIM_DISTR) + return failure(); + // All rewrites should operate on suppresses + if (!isSuppress(condBranchOp)) + return failure(); + + // Nothing to distribute if the branch has only 1 user + int numOfUsers = returnTotalCondBranchUsers(condBranchOp); + if (numOfUsers == 1) + return failure(); + + Value dataOperand = condBranchOp.getDataOperand(); + Value condOperand = condBranchOp.getConditionOperand(); + int i = 0; + handshake::ConditionalBranchOp oldBranch = condBranchOp; + while (i < numOfUsers - 1) { + ValueRange branchOperands = {condOperand, dataOperand}; + rewriter.setInsertionPoint(condBranchOp); + handshake::ConditionalBranchOp newBranch = + rewriter.create( + condBranchOp->getLoc(), branchOperands); + inheritBB(condBranchOp, newBranch); + + Value newBranchFalseResult = newBranch.getFalseResult(); + Value branchOldFalseResult = oldBranch.getFalseResult(); + // Direct all users of the old branch to the users of the new branch + // except 1 user + rewriter.replaceAllUsesExcept( + branchOldFalseResult, newBranchFalseResult, + *oldBranch.getFalseResult().getUsers().begin()); + oldBranch = newBranch; + i++; + } + + // llvm::errs() << "\t***Rules D: distribute-suppresses!***\n"; + return success(); + } +}; + +// Rules D +// If a Repeat has two or more successors, feed each successor by a separate +// Repeat +template +struct DistributeRepeats : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(MuxOrMergeOp muxOrMergeOp, + PatternRewriter &rewriter) const override { + if (OPTIM_DISTR) + return failure(); + + bool isMux = false; + if (isa_and_nonnull(muxOrMergeOp)) + isMux = true; + else if (!isa_and_nonnull(muxOrMergeOp)) + return failure(); + + // muxOrMergeOp should have at least 3 users for the distribute pattern to + // apply: loop Branch and 2 other users + auto users = muxOrMergeOp->getResults()[0].getUsers(); + if (std::distance(users.begin(), users.end()) < 3) + return failure(); + + int cycleInputIdx; + bool isCycleBranchTrueSucc; + Operation *potentialBranchOp = returnBranchFormingCycle( + muxOrMergeOp, cycleInputIdx, isCycleBranchTrueSucc); + if (potentialBranchOp == nullptr) + return failure(); + + assert(isa_and_nonnull(potentialBranchOp)); + handshake::ConditionalBranchOp condBranchOp = + cast(potentialBranchOp); + + int outsideInputIdx = 1 - cycleInputIdx; + + if (!isSuppress(condBranchOp)) + return failure(); + + int numOfUsers = std::distance(users.begin(), users.end()); + + // Implement the distribution by replicating muxOrMergeOp and Branch for + // each user + int i = 0; + handshake::ConditionalBranchOp oldBranchOp = condBranchOp; + Value branchCond = condBranchOp.getConditionOperand(); + + Operation *oldMuxOrMergeOp = muxOrMergeOp; + Value muxOrMergeOuterInput; + Value muxSel; + if (isMux) { + muxOrMergeOuterInput = cast(oldMuxOrMergeOp) + .getDataOperands()[outsideInputIdx]; + muxSel = cast(oldMuxOrMergeOp).getSelectOperand(); + } else + muxOrMergeOuterInput = oldMuxOrMergeOp->getOperands()[outsideInputIdx]; + + // Loop over the users of muxOrMergeOp excluding 1 user (branch) + while (i < numOfUsers - 2) { + // Temporarily, feed the data of the new Branch from the output of + // oldMuxOrMergeOp until we create a new muxOrMergeOp + ValueRange newBranchOperands = {branchCond, + oldMuxOrMergeOp->getResults()[0]}; + rewriter.setInsertionPoint(oldBranchOp); + handshake::ConditionalBranchOp newBranch = + rewriter.create(oldBranchOp->getLoc(), + newBranchOperands); + inheritBB(oldBranchOp, newBranch); + + ValueRange newMergeOrMuxOperands; + if (outsideInputIdx == 0) + newMergeOrMuxOperands = {muxOrMergeOuterInput, + newBranch.getFalseResult()}; + else + newMergeOrMuxOperands = {newBranch.getFalseResult(), + muxOrMergeOuterInput}; + rewriter.setInsertionPoint(oldMuxOrMergeOp); + + Operation *newMuxOrMergeOp; + if (isMux) + newMuxOrMergeOp = rewriter.create( + oldMuxOrMergeOp->getLoc(), newMergeOrMuxOperands[0].getType(), + muxSel, newMergeOrMuxOperands); + else + newMuxOrMergeOp = rewriter.create( + oldMuxOrMergeOp->getLoc(), newMergeOrMuxOperands); + + inheritBB(oldMuxOrMergeOp, newMuxOrMergeOp); + + // Update the data input of the newBranch to come from the newMuxOrMergeOp + newBranch->setOperand(1, newMuxOrMergeOp->getResults()[0]); + + Value oldMuxOrMergeResult = oldMuxOrMergeOp->getResults()[0]; + Value newMuxOrMergeResult = newMuxOrMergeOp->getResults()[0]; + rewriter.replaceAllUsesExcept(oldMuxOrMergeResult, newMuxOrMergeResult, + oldBranchOp); + + // We removed all users of the oldMuxOrMergeResult except the oldBranchOp, + // but now we want to return to it exactly one user to make this Repeat + // meaningful For this, we simply choose the first user of the + // newMuxOrMergeResult that is not equal to newBranch + Operation *oneUser; + for (auto newMergeUser : newMuxOrMergeOp->getResults()[0].getUsers()) { + if (newMergeUser != newBranch) { + oneUser = newMergeUser; + break; + } + } + int idxInUserOperands = 0; + for (auto oneUserOperand : oneUser->getOperands()) { + if (oneUserOperand == newMuxOrMergeResult) + break; + idxInUserOperands++; + } + oneUser->setOperand(idxInUserOperands, oldMuxOrMergeResult); + + oldMuxOrMergeOp = newMuxOrMergeOp; + oldBranchOp = newBranch; + i++; + } + + llvm::errs() << "\t***Rules D: distribute-GENERIC-repeats!***\n"; + + return success(); + } +}; + +// This is not entirely true because in the simple buffer placement, I skip +// Merges to avoid placing a buffer after an Init, but we might need it if there +// is a Merge at the loop header +/* + Not clean but is temporary anyways: + Eventually, a subset of the network of CMerges will be extracted solely for + triggering constants, it will contain Merges. There is no risk of disordering + here, since both sides of the Merge circulate Start. Yet, this function is + needed if the RTL of merge cannot accommodate having two active inputs. +*/ +struct ConvertLoopMergeToMux : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(handshake::MergeOp mergeOp, + PatternRewriter &rewriter) const override { + // Doublecheck that the Merge has 2 inputs + if (mergeOp->getNumOperands() != 2) + return failure(); + + // Get the users of the Merge + auto mergeUsers = (mergeOp.getResult()).getUsers(); + if (mergeUsers.empty()) + return failure(); + + // One user must be a Branch; otherwise, the pattern match fails + bool foundCondBranch = false; + DenseSet branches; + for (auto mergeUser : mergeUsers) { + if (isa_and_nonnull(mergeUser)) { + foundCondBranch = true; + branches.insert(cast(mergeUser)); + } + } + if (!foundCondBranch) + return failure(); + + // This condBranchOp must also be an operand forming a cycle with the + // merge; otherwise, the pattern match fails + bool foundCycle = false; + int operIdx = 0; + int mergeOuterInputIdx = 0; + int mergeCycleInputIdx = 0; + handshake::ConditionalBranchOp condBranchOp; + for (auto mergeOperand : mergeOp->getOperands()) { + if (isa_and_nonnull( + mergeOperand.getDefiningOp())) + if (branches.contains(cast( + mergeOperand.getDefiningOp()))) { + foundCycle = true; + mergeCycleInputIdx = operIdx; + condBranchOp = cast( + mergeOperand.getDefiningOp()); + break; + } + operIdx++; + } + if (!foundCycle) + return failure(); + + if (!OPTIM_BRANCH_TO_SUPP) { + // New condition: The condBranchOp has to be a suppress; otherwise, + // the pattern match fails + if (!condBranchOp.getTrueResult().getUsers().empty() || + condBranchOp.getFalseResult().getUsers().empty()) + return failure(); + } + + mergeOuterInputIdx = (mergeCycleInputIdx == 0) ? 1 : 0; + + // Retrieve the values at the merge inputs + OperandRange mergeDataOperands = mergeOp.getDataOperands(); + Value mergeInnerOperand = mergeDataOperands[mergeCycleInputIdx]; + + // Identify the output of the Branch going outside of the loop (even if it + // has no users) + bool isTrueOutputOuter = false; + Value branchTrueResult = condBranchOp.getTrueResult(); + Value branchFalseResult = condBranchOp.getFalseResult(); + Value branchOuterResult; + if (branchTrueResult == mergeInnerOperand) + branchOuterResult = branchFalseResult; + else if (branchFalseResult == mergeInnerOperand) { + branchOuterResult = branchTrueResult; + isTrueOutputOuter = true; + } else + return failure(); + + // 1st) Identify whether the loop condition will be connected directly or + // through a NOT + // Note: This strategy is correct, but might result in the insertion of + // double NOT + Value condition = condBranchOp.getConditionOperand(); + bool needNot = ((isTrueOutputOuter && mergeCycleInputIdx == 1) || + (!isTrueOutputOuter && mergeCycleInputIdx == 0)); + Value iterCond; + if (needNot) { + + // Check if the condition already feeds a NOT, no need to create a new one + bool foundNot = false; + handshake::NotIOp existingNotOp; + for (auto condRes : condition.getUsers()) { + if (isa_and_nonnull(condRes)) { + foundNot = true; + existingNotOp = cast(condRes); + break; + } + } + if (foundNot) { + iterCond = existingNotOp.getResult(); + } else { + rewriter.setInsertionPoint(condBranchOp); + handshake::NotIOp notIOp = rewriter.create( + condBranchOp->getLoc(), condition); + inheritBB(condBranchOp, notIOp); + iterCond = notIOp.getResult(); + } + + } else { + iterCond = condition; + } + + // 2nd) Identify the value of the constant that will be triggered from Start + // and add it + // The value of the constant should be the mergeOuterInputIdx + int constVal = mergeOuterInputIdx; + // Obtain the start signal from the last argument of any block + Block *mergeBlock = mergeOp->getBlock(); + MutableArrayRef l = mergeBlock->getArguments(); + if (l.empty()) + return failure(); + mlir::Value start = l.back(); + if (!isa(start.getType())) + return failure(); + + // Check if there is an already existing INIT, i.e., a Merge fed from the + // iterCond + bool foundInit = false; + handshake::MergeOp existingInit; + for (auto iterCondRes : iterCond.getUsers()) { + if (isa_and_nonnull(iterCondRes)) { + foundInit = true; + existingInit = cast(iterCondRes); + break; + } + } + + Value muxSel; + if (foundInit) { + muxSel = existingInit.getResult(); + } else { + // Create a new ConstantOp in the same block as that of the branch + // forming the cycle + Type constantType = rewriter.getIntegerType(1); + rewriter.setInsertionPoint(mergeOp); + Value valueOfConstant = rewriter.create( + mergeOp->getLoc(), rewriter.getIntegerAttr(constantType, constVal), + start); + + // 3rd) Add a new Merge operation to serve as the INIT + ValueRange operands = {iterCond, valueOfConstant}; + rewriter.setInsertionPoint(mergeOp); + handshake::MergeOp initMergeOp = + rewriter.create(mergeOp.getLoc(), operands); + inheritBB(mergeOp, initMergeOp); + + muxSel = initMergeOp.getResult(); + } + + // Create a new muxOp and make it replace the mergeOp + rewriter.setInsertionPoint(mergeOp); + handshake::MuxOp newMuxOp = rewriter.create( + mergeOp.getLoc(), mergeOp->getOperands()[0].getType(), muxSel, + mergeOp->getOperands()); + rewriter.replaceOp(mergeOp, newMuxOp); + inheritBB(mergeOp, newMuxOp); + + llvm::errs() << "\t***Converted Merge to Mux!***\n"; + + return success(); + } +}; + +/// Simple driver for the Handshake Rewrite Terms pass, based on a greedy +/// pattern rewriter. +struct HandshakeRewriteTermsPass + : public dynamatic::experimental::impl::HandshakeRewriteTermsBase< + HandshakeRewriteTermsPass> { + + void runDynamaticPass() override { + MLIRContext *ctx = &getContext(); + ModuleOp mod = getOperation(); + + GreedyRewriteConfig config; + config.useTopDownTraversal = true; + config.enableRegionSimplification = false; + config.maxIterations = 100; + RewritePatternSet patterns(ctx); + patterns.add, + RemoveFloatingLoop, + ConstructSuppresses, FixSuppresses, DistributeSuppresses, + DistributeRepeats, + DistributeRepeats, + ExtractIfThenElseCondition, ExtractLoopCondition, + RemoveBranchMergeIfThenElse, RemoveBranchMuxIfThenElse, + EliminateRedundantLoop, + EliminateRedundantLoop, ConvertLoopMergeToMux/*, + ShortenSuppressPairs + , ShortenMuxRepeatPairs*/>(ctx); + + if (failed(applyPatternsAndFoldGreedily(mod, std::move(patterns), config))) + return signalPassFailure(); + }; +}; +}; // namespace + +std::unique_ptr +dynamatic::experimental::rewriteHandshakeTerms() { + return std::make_unique(); +} diff --git a/experimental/test/Transforms/handshake-rewrite-terms.mlir b/experimental/test/Transforms/handshake-rewrite-terms.mlir new file mode 100644 index 0000000000..bc78755c16 --- /dev/null +++ b/experimental/test/Transforms/handshake-rewrite-terms.mlir @@ -0,0 +1,444 @@ +// RUN: dynamatic-opt %s --split-input-file -o /dev/null + +handshake.func @removeBranchMUXPair_simple(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %arg1_one, %arg1_two = fork [2] %arg1 : + %true, %false = cond_br %arg1_one, %arg0 : , + %result_mux= mux %arg1_two [%true, %false] : , [, ] to + end %result_mux : +} + +// ----- + +handshake.func @removeBranchCMergePair_simple(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel) { + %true, %false = cond_br %arg1, %arg0 : , + %result_cmerge, %index= control_merge [%true, %false] : [, ] to , + end %result_cmerge, %index : , +} + +// ----- + +handshake.func @removeBranchMUXPair_doNot(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %arg1_one, %arg1_two = fork [2] %arg1 : + %true, %false = cond_br %arg1_one, %arg0 : , + %num_1 = constant %start {value = 1 : i32} : <>, + %num_2 = constant %start {value = 2 : i32} : <>, + %add = addi %false, %num_1 : + %mul = muli %true, %num_2 : + %result_mux= mux %arg1_two [%mul, %add] : , [, ] to + end %result_mux : +} + +// ----- + +handshake.func @removeBranchCMergePair_doNot(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel) { + %true, %false = cond_br %arg1, %arg0 : , + %num_1 = constant %start {value = 1 : i32} : <>, + %num_2 = constant %start {value = 2 : i32} : <>, + %add = addi %false, %num_1 : + %mul = muli %true, %num_2 : + %result_cmerge, %index= control_merge [%mul, %add] : [, ] to , + end %result_cmerge, %index : , +} + +// ----- + +handshake.func @removeBranchMUXPair(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %arg1_one, %arg1_two = fork [2] %arg1 : + %arg2_one, %arg2_two = fork [2] %arg2 : + %true, %false = cond_br %arg1_one, %arg0 : , + %result_mux= mux %arg1_two [%true, %false] : , [, ] to + %true_2, %false_2 = cond_br %arg2_one, %result_mux : , + %num_1 = constant %start {value = 1 : i32} : <>, + %num_2 = constant %start {value = 2 : i32} : <>, + %add = addi %false_2, %num_1 : + %mul = muli %true_2, %num_2 : + %result_mux_2= mux %arg2_two [%mul, %add] : , [, ] to + end %result_mux_2 : +} + +// ----- + +handshake.func @removeBranchCMergePair(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %true, %false = cond_br %arg1, %arg0 : , + %result_cmerge, %index= control_merge [%true, %false] : [, ] to , + %true_2, %false_2 = cond_br %arg2, %result_cmerge : , + %num_1 = constant %start {value = 1 : i32} : <>, + %num_2 = constant %start {value = 2 : i32} : <>, + %add = addi %false_2, %num_1 : + %mul = muli %true_2, %num_2 : + %result_cmerge_2, %index_2= control_merge [%mul, %add] : [, ] to , + end %result_cmerge_2 : +} + +// ----- + +handshake.func @removeBranchCMergePair_multiple(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %true, %false = cond_br %arg1, %arg0 : , + %true_2, %false_2 = cond_br %arg2, %true : , + %result_cmerge, %index= control_merge [%true_2, %false_2] : [, ] to , + %result_cmerge_2, %index_2= control_merge [%result_cmerge, %false] : [, ] to , + end %result_cmerge_2 : +} + +// ----- + +handshake.func @removeBranchMUXPair_multiple(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %arg1_one, %arg1_two = fork [2] %arg1 : + %arg2_one, %arg2_two = fork [2] %arg2 : + %true, %false = cond_br %arg1_one, %arg0 : , + %true_2, %false_2 = cond_br %arg2_one, %true : , + %result_mux= mux %arg2_two [%true_2, %false_2] : , [, ] to + %result_mux_2= mux %arg1_two [%result_mux, %false] : , [, ] to + end %result_mux_2 : +} + +// ----- + +handshake.func @removeBranchMUXPairloop_simple_case1(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux= mux %arg1 [%true, %arg0] : , [, ] to + %true, %false = cond_br %arg2, %result_mux : , + end %false : +} + +// ----- + +handshake.func @removeBranchMUXPairloop_simple_case2(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux= mux %arg1 [%arg0, %false] : , [, ] to + %true, %false = cond_br %arg2, %result_mux : , + end %true : +} + +// ----- + +handshake.func @removeBranchMUXPairloop_simple_case3(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux= mux %arg1 [%arg0, %true] : , [, ] to + %true, %false = cond_br %arg2, %result_mux : , + end %false : +} + +// ----- + +handshake.func @removeBranchMUXPairloop_simple_case4(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux= mux %arg1 [%false, %arg0] : , [, ] to + %true, %false = cond_br %arg2, %result_mux : , + end %true : +} + +// ----- + +handshake.func @removeBranchMergePairloop_simple_case1(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_merge= merge %true, %arg0 : + %true, %false = cond_br %arg1, %result_merge : , + end %false : +} + +// ----- + +handshake.func @removeBranchMergePairloop_simple_case2(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_merge= merge %arg0, %false : + %true, %false = cond_br %arg1, %result_merge : , + end %true : +} + +// ----- + +handshake.func @removeBranchMergePairloop_simple_case3(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_merge= merge %arg0, %true : + %true, %false = cond_br %arg1, %result_merge : , + end %false : +} + +// ----- + +handshake.func @removeBranchMergePairloop_simple_case4(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_merge= merge %false, %arg0 : + %true, %false = cond_br %arg1, %result_merge : , + end %true : +} + +// ----- + +handshake.func @removeBranchMUXPairloop_doNot_case1(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux= mux %arg1 [%true, %arg0] : , [, ] to + %num_1 = constant %start {value = 1 : i32} : <>, + %data_in= addi %result_mux, %num_1 : + %true, %false = cond_br %arg2, %data_in : , + end %false : +} + +// ----- + +handshake.func @removeBranchMUXPairloop_doNot_case2(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux= mux %arg1 [%data_in, %arg0] : , [, ] to + %true, %false = cond_br %arg2, %result_mux : , + %num_2 = constant %start {value = 2 : i32} : <>, + %data_in= muli %true, %num_2 : + end %false : +} + +// ----- + +handshake.func @removeBranchMergePairloop_doNot_case1(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_merge= merge %true, %arg0 : + %num_1 = constant %start {value = 1 : i32} : <>, + %data_in= addi %result_merge, %num_1 : + %true, %false = cond_br %arg2, %data_in : , + end %false : +} + +// ----- + +handshake.func @removeBranchMergePairloop_doNot_case2(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_merge= merge %arg0, %data_in : + %true, %false = cond_br %arg1, %result_merge : , + %num_2 = constant %start {value = 2 : i32} : <>, + %data_in= muli %true, %num_2 : + end %false : +} + +// ----- + +handshake.func @removeBranchMUXPairloop_multiple(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %arg3: !handshake.channel, %arg4: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux_1= mux %arg1 [%true_2, %arg0] : , [, ] to + %result_mux_2= mux %arg2 [%true_1, %result_mux_1] : , [, ] to + %true_1, %false_1 = cond_br %arg3, %result_mux_2 : , + %true_2, %false_2 = cond_br %arg4, %false_1 : , + end %false_2 : +} + +// ----- + +handshake.func @removeBranchMergePairloop_multiple(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %arg3: !handshake.channel, %arg4: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_merge_1= merge %true_2, %arg0 : + %result_merge_2= merge %true_1, %result_merge_1 : + %true_1, %false_1 = cond_br %arg3, %result_merge_2 : , + %true_2, %false_2 = cond_br %arg4, %false_1 : , + end %false_2 : +} + +// ----- + +handshake.func @removeBranchMUXPairloop_multiple_doNot(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %arg3: !handshake.channel, %arg4: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux_1= mux %arg1 [%true_2, %arg0] : , [, ] to + %result_mux_2= mux %arg2 [%true_1, %result_mux_1] : , [, ] to + %true_1, %false_1 = cond_br %arg3, %result_mux_2 : , + %num_1 = constant %start {value = 1 : i32} : <>, + %data_in = addi %num_1, %false_1 : + %true_2, %false_2 = cond_br %arg4, %data_in : , + end %false_2 : +} + +// ----- + +handshake.func @removeBranchMergePairloop_multiple_doNot(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %arg3: !handshake.channel, %arg4: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_merge_1= merge %true_2, %arg0 : + %result_merge_2= merge %true_1, %result_merge_1 : + %true_1, %false_1 = cond_br %arg3, %result_merge_2 : , + %num_1 = constant %start {value = 1 : i32} : <>, + %data_in = addi %num_1, %false_1 : + %true_2, %false_2 = cond_br %arg4, %data_in : , + end %false_2 : +} + +// ----- + +handshake.func @removeBoth(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %arg3: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux= mux %arg1 [%true_2, %arg0] : , [, ] to + %result_merge= merge %true_1, %result_mux : + %true_1, %false_1 = cond_br %arg2, %result_merge : , + %true_2, %false_2 = cond_br %arg3, %false_1 : , + end %false_2 : +} + +// ----- + +handshake.func @removeBoth_doNot(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %arg3: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux= mux %arg1 [%true_2, %arg0] : , [, ] to + %result_merge= merge %true_1, %result_mux : + %true_1, %false_1 = cond_br %arg2, %result_merge : , + %num_2 = constant %start {value = 2 : i32} : <>, + %data_in= muli %false_1, %num_2 : + %true_2, %false_2 = cond_br %arg3, %data_in : , + end %false_2 : +} + +// ----- + +handshake.func @exampleBranchMUXPairloop(%x: !handshake.channel, %j: !handshake.channel, %c1: !handshake.channel, %c2: !handshake.channel) -> !handshake.channel { + %c1_one, %c1_two = fork [2] %c1 : + %c2_one, %c2_two = fork [2] %c2 : + %result_mux_1= mux %c1_one [%x, %false] : , [, ] to + %true, %false = cond_br %c1_two, %result_mux_1 : , + %result_mux_2= mux %c2_one [%true, %false_2] : , [, ] to + %result_mux_2one, %result_mux_2two = fork [2] %result_mux_2 : + %true_2, %false_2 = cond_br %c2_two, %result_mux_2two : , + sink %true_2 : + %sta = addi %j, %result_mux_2one : + end %sta : +} + +// ----- + +handshake.func @removeMUXBranchloop_fork_doNot(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux = mux %arg1 [%arg0, %false] : , [, ] to + %c1, %c2 = fork [2] %result_mux : + %num_2 = constant %start {value = 2 : i32} : <>, + %answer = muli %num_2, %c1 : + %true, %false = cond_br %arg2, %c2 : , + sink %true : + end %false : +} + +// ----- + +handshake.func @removeMUXBranchloop_fork_doOne(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %arg3: !handshake.channel, %arg4: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %result_mux_1 = mux %arg1 [%arg0, %false_2] : , [, ] to + %c1, %c2 = fork [2] %result_mux_1 : + %num_2 = constant %start {value = 2 : i32} : <>, + %answer = muli %num_2, %c1 : + %result_mux_2 = mux %arg2 [%c2, %false_1] : , [, ] to + %true_1, %false_1 = cond_br %arg3, %result_mux_2 : , + %true_2, %false_2 = cond_br %arg4, %true_1 : , + sink %true_2 : + end %false_2 : +} + +// ----- + +handshake.func @removeBranchCMergePairloop_multiple_doNot(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel) { + %result_cmerge_1, %index1= control_merge [%true_2, %arg0] : [, ] to , + %result_cmerge_2, %index2= control_merge [%false_1, %result_cmerge_1] : [, ] to , + %true_1, %false_1 = cond_br %arg1, %result_cmerge_2 : , + %num_1 = constant %start {value = 1 : i32} : <>, + %data_in = addi %num_1, %true_1 : + %true_2, %false_2 = cond_br %arg2, %data_in : , + end %false_2,%index2 : , +} + +// ----- + +handshake.func @removeBothCMerge(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %arg3: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel, !handshake.channel) { + %result_cmerge_1, %index1= control_merge [%true_2, %arg0] : [, ] to , + %result_cmerge_2, %index2= control_merge [%false_1, %result_cmerge_1] : [, ] to , + %true_1, %false_1 = cond_br %arg2, %result_cmerge_2 : , + %true_2, %false_2 = cond_br %arg3, %true_1 : , + end %false_2, %index1, %index2 : , , +} + +// ----- + +handshake.func @removeSupressFork(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel){ + %true, %false= cond_br %arg0, %arg1 : , + sink %true : + %one, %two = fork [2] %false : + end %one, %two : , +} + +// ----- + +handshake.func @removeSupressFork2(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel){ + %true, %false= cond_br %arg1, %arg0 : , + %num1 = constant %start {value = 1 : i32} : <>, + %answer= addi %false, %num1 : + %one, %two = fork [2] %answer : + end %one, %two : , +} + +// ----- + +handshake.func @removeSupressSupressPairs(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel{ + %true, %false= cond_br %arg1, %arg0 : , + %true2, %false2= cond_br %arg2, %false : , + end %false2 : +} + +// ----- + +handshake.func @BranchtoForkSupressPairs(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel){ + %true, %false= cond_br %arg1, %arg0 : , + end %true, %false : , +} + +// ----- + +handshake.func @BranchtoForkSupressPairs_3(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel){ + %val1, %val2, %val3= fork [3] %arg1 : + %true, %false= cond_br %val1, %arg0 : , + %true_1, %false_1 = cond_br %val2, %true : , + %true_2, %false_2 = cond_br %val3, %false : , + end %true_1, %false_1, %true_2, %false_2 : , , , +} + +// ----- + +handshake.func @removeForkForkPair(%arg0: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel, !handshake.channel) { + %one, %two = fork [2] %arg0 : + %two1, %two2 = fork [2] %two : + end %one, %two1, %two2 : , , +} + +// ----- + +handshake.func @removeForkForkPairMultiple(%arg0: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel) { + %one, %two, %three = fork [3] %arg0 : + %two1, %two2 = fork [2] %two : + %three1, %three2, %three3, %three4 = fork [4] %three : + end %one, %two1, %two2, %three1, %three2, %three3, %three4 : , , , , , , +} + +// ----- + +handshake.func @removeForkForkPairdonot(%arg0: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel) { + %one, %two, %three = fork [3] %arg0 : + %num1 = constant %start {value = 1 : i32} : <>, + %ans = addi %two, %num1 : + %two1, %two2 = fork [2] %ans : + %three1, %three2, %three3, %three4 = fork [4] %three : + end %one, %two1, %two2, %three1, %three2, %three3, %three4 : , , , , , , +} + +// ----- + +handshake.func @removeForkForkPairNested(%arg0: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel) { + %one, %two= fork [2] %arg0 : + %two1, %two2 = fork [2] %two : + %three1, %three2, %three3, %three4 = fork [4] %two1 : + end %one, %two2, %three1, %three2, %three3, %three4 : , , , , , +} + +// ----- + +handshake.func @removeForkForkPairNested_only1(%arg0: !handshake.channel, %start: !handshake.control<>) -> (!handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel, !handshake.channel) { + %one, %two= fork [2] %arg0 : + %two1, %two2 = fork [2] %two : + %num1 = constant %start {value = 1 : i32} : <>, + %ans = addi %two1, %num1 : + %three1, %three2, %three3, %three4 = fork [4] %ans : + end %one, %three1, %three2, %three3, %three4 : , , , , +} + +// ----- + +handshake.func @removeForkSuppressMUX(%arg0: !handshake.channel, %arg1: !handshake.channel, %start: !handshake.control<>) -> !handshake.channel { + %one, %two= fork [2] %arg0 : + %one1, %two1, %three1 = fork [3] %arg1 : + %true, %false= cond_br %one1, %one : , + %ans = noti %two1 : + %true_2, %false_2= cond_br %ans, %two : , + %c = mux %three1 [%false_2, %false] : , [, ] to + end %c : +} + +// ----- + +handshake.func @removeBranchMUXPair_simplee(%arg0: !handshake.channel, %arg1: !handshake.channel, %arg2: !handshake.control<>, ...) -> !handshake.channel attributes {argNames = ["arg0", "arg1", "start"], resNames = ["out0"]} { + %0:3 = fork [3] %arg1 : + %1:2 = fork [2] %arg0 : + %trueResult, %falseResult = cond_br %0#0, %1#0 : , + sink %trueResult : + %2 = noti %0#1 : + %trueResult_0, %falseResult_1 = cond_br %2, %1#1 : , + sink %trueResult_0 : + %3 = mux %0#2 [%falseResult, %falseResult_1] : , [, ] to + end %3 : +} diff --git a/include/dynamatic/Support/CFG.h b/include/dynamatic/Support/CFG.h index 0ef76046c2..c275f31860 100644 --- a/include/dynamatic/Support/CFG.h +++ b/include/dynamatic/Support/CFG.h @@ -98,12 +98,12 @@ bool getBBEndpoints(Value val, Operation *user, BBEndpoints &endpoints); bool getBBEndpoints(Value val, BBEndpoints &endpoints); /// Determines whether the value is a backedge i.e., whether the channel -/// corresponding to the value is located between a branch-like operation and a -/// merge-like operation, where the merge-like operation happens semantically -/// "before" the branch-like operation. This function can only correctly -/// identify backedges if the circuit's branches and merges are associated to -/// basic blocks (otherwise it will always return false). `user` must be one of -/// `val`'s users. +/// corresponding to the value is located between a loop-feedback source +/// (branch-like or compare-like within a block) and a merge-like operation, +/// where the merge-like operation happens semantically "before" that source. +/// This function can only correctly identify backedges if the circuit's +/// branches, compares, and merges are associated to basic blocks (otherwise it +/// will always return false). `user` must be one of `val`'s users. bool isBackedge(Value val, Operation *user, BBEndpoints *endpoints = nullptr); /// Determines whether the value is a backedge. The value must have a single diff --git a/include/dynamatic/Transforms/BufferPlacement/Utils/CFDFC.h b/include/dynamatic/Transforms/BufferPlacement/Utils/CFDFC.h index 186c3aaab3..8d9e15ab8e 100644 --- a/include/dynamatic/Transforms/BufferPlacement/Utils/CFDFC.h +++ b/include/dynamatic/Transforms/BufferPlacement/Utils/CFDFC.h @@ -24,6 +24,7 @@ #include "dynamatic/Support/ConstraintProgramming/ConstraintProgramming.h" #include "dynamatic/Support/LLVM.h" #include "experimental/Support/StdProfiler.h" +#include "mlir/Support/LogicalResult.h" namespace dynamatic { namespace buffer { @@ -59,13 +60,20 @@ struct CFDFC { /// Constructs a CFDFC from a set of selected archs and basic blocks in the /// function. Assumes that every value in the function is used exactly once. - CFDFC(handshake::FuncOp funcOp, ArchSet &archs, unsigned numExec); + CFDFC(handshake::FuncOp funcOp, ArchSet &archs, unsigned numExec, + DenseSet backwardChannels); // Determines whether the channel is a "CFDFC backedge" i.e., the first // channel along a sequence of backedges from a source block to a destination // block. The distinction is important for the buffer placement MILP, which // uses backedges to determine where to insert "tokens" in the circuit. static bool isCFDFCBackedge(Value val); + + // Determines whether the channel has a corresponding CFG edge. + bool isCFGCompliant(unsigned srcBB, unsigned dstBB); + + // AYA: Added this from Jiahui to write the CFDFCs in DOT for debugging + // void writeDot(const std::string &fileName); }; /// Represents a union of CFDFCs. Its blocks, units, channels, and backedges are diff --git a/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp b/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp index 72bd146601..dafbd1bc21 100644 --- a/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp +++ b/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp @@ -22,7 +22,6 @@ #include "dynamatic/Support/CFG.h" #include "dynamatic/Transforms/BufferPlacement/CostAwareBuffers.h" #include "dynamatic/Transforms/BufferPlacement/FPGA20Buffers.h" -#include "dynamatic/Transforms/BufferPlacement/FPGA24Buffers.h" #include "dynamatic/Transforms/BufferPlacement/FPL22Buffers.h" #include "dynamatic/Transforms/BufferPlacement/MAPBUFBuffers.h" #include "dynamatic/Transforms/BufferPlacement/Utils/BufferingSupport.h" @@ -36,8 +35,14 @@ #include "llvm/Support/Casting.h" #include "llvm/Support/Path.h" #include +#include #include +#include "mlir/Support/LLVM.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/StringSet.h" + using namespace mlir; using namespace dynamatic; using namespace dynamatic::handshake; @@ -48,7 +53,7 @@ using namespace dynamatic::experimental; static constexpr llvm::StringLiteral ON_MERGES("on-merges"); /// Algorithms that do require solving an MILP. static constexpr llvm::StringLiteral FPGA20("fpga20"), FPL22("fpl22"), - COST_AWARE("costaware"), MAPBUF("mapbuf"), FPGA24("fpga24"); + COST_AWARE("costaware"), MAPBUF("mapbuf"); // [START Boilerplate code for the MLIR pass] #include "dynamatic/Transforms/Passes.h" // IWYU pragma: keep @@ -173,8 +178,7 @@ void HandshakePlaceBuffersPass::runOnOperation() { } else if ( // clang-format off algorithm == FPGA20 || - algorithm == FPL22 || - algorithm == FPGA24 || + algorithm == FPL22 || algorithm == COST_AWARE || algorithm == MAPBUF // clang-format on @@ -208,6 +212,8 @@ LogicalResult HandshakePlaceBuffersPass::placeUsingMILP() { } ModuleOp modOp = llvm::dyn_cast(getOperation()); + + // AYA: TODO: This is where I should add the timing of the OOE units // // Read the operations' timing models from disk TimingDatabase timingDB; @@ -231,9 +237,10 @@ LogicalResult HandshakePlaceBuffersPass::placeUsingMILP() { << "Failed to read profiling information from CSV"; } + // AYA commented this out // Check IR invariants and parse basic block archs from disk - if (failed(checkFuncInvariants(info))) - return failure(); + // if (failed(checkFuncInvariants(info))) + // return failure(); // Get CFDFCs from the function unless the functions has no archs (i.e., // it has a single block) in which case there are no CFDFCs @@ -380,11 +387,208 @@ static void logFuncInfo(FuncInfo &info) { os << "- Number of channels: " << cf->channels.size() << "\n"; os << "- Number of backedges: " << cf->backedges.size() << "\n\n"; os.unindent(); + + // AYA: Added this from Jiahui to write the CFDFCs in DOT for debugging + // cf->writeDot("AYAA-cfdfc_" + std::to_string(idx) + ".dot"); } os.flush(); } +////////////////////////////////////////////////////////////////////////////////////////// +// AYA: The following functions are for identifying backward edges in the +// circuit graph +static void +printBackwardChannels(const llvm::DenseSet &backwardChannels) { + llvm::errs() << "=== Backward Channels (with src/dst ops) ===\n"; + int idx = 0; + + for (Value v : backwardChannels) { + llvm::errs() << "Channel " << idx++ << ":\n"; + + // Source operation + if (Operation *srcOp = v.getDefiningOp()) { + llvm::errs() << " Source: "; + srcOp->print(llvm::errs()); + llvm::errs() << "\n"; + } else { + llvm::errs() << " Source: \n"; + } + + // Destination operations + for (auto &use : v.getUses()) { + Operation *dstOp = use.getOwner(); + llvm::errs() << " Destination: "; + dstOp->print(llvm::errs()); + llvm::errs() << "\n"; + } + llvm::errs() << "\n"; + } + + if (backwardChannels.empty()) + llvm::errs() << "(empty)\n"; +} + +// Aya: The following captivates the necessary logic for extracting back edges +// in cycles +namespace { + +struct CircuitEdge { + Operation *src; + Operation *dst; + Value channel; +}; + +static bool isBackedgeSourceLike(Operation *op) { + do { + if (!op) + return false; + if (isa(op)) + return true; + if (isa(op)) + op = op->getOperand(0).getDefiningOp(); + else + return false; + } while (true); +} + +static bool isBackedgeDestinationLike(Operation *op) { + if (isa(op)) + return true; + + auto notOp = dyn_cast(op); + if (!notOp) + return false; + + return llvm::any_of(notOp.getResult().getUsers(), [](Operation *user) { + return isa(user); + }); +} + +/// Finds all loop-feedback-source -> merge-like backward channels per cyclic +/// SCC in the handshake graph. Grouping by SCC remains more stable than trying +/// to assign channels to every simple cycle when cycles overlap. +static mlir::DenseSet +findBackwardChannelPerCyclicRegion(handshake::FuncOp funcOp) { + SmallVector ops; + SmallVector edges; + llvm::DenseMap> succs; + + for (Operation &op : funcOp.getOps()) { + ops.push_back(&op); + succs[&op] = {}; + } + + for (Operation *src : ops) { + for (Value result : src->getResults()) { + for (OpOperand &use : result.getUses()) { + Operation *dst = use.getOwner(); + + if (isa(dst)) + continue; + + edges.push_back({src, dst, result}); + succs[src].push_back(dst); + } + } + } + + llvm::DenseMap index, lowlink; + llvm::DenseSet onStack; + SmallVector stack; + SmallVector> sccs; + unsigned nextIndex = 0; + + std::function strongConnect = [&](Operation *op) { + index[op] = nextIndex; + lowlink[op] = nextIndex; + ++nextIndex; + stack.push_back(op); + onStack.insert(op); + + for (Operation *succ : succs[op]) { + if (!index.contains(succ)) { + strongConnect(succ); + lowlink[op] = std::min(lowlink[op], lowlink[succ]); + } else if (onStack.contains(succ)) { + lowlink[op] = std::min(lowlink[op], index[succ]); + } + } + + if (lowlink[op] != index[op]) + return; + + SmallVector scc; + while (true) { + Operation *top = stack.pop_back_val(); + onStack.erase(top); + scc.push_back(top); + if (top == op) + break; + } + sccs.push_back(std::move(scc)); + }; + + for (Operation *op : ops) { + if (!index.contains(op)) + strongConnect(op); + } + + mlir::DenseSet backwardChannels; + for (const auto &scc : sccs) { + llvm::DenseSet sccNodes(scc.begin(), scc.end()); + + bool isCyclic = scc.size() > 1; + if (!isCyclic) { + Operation *only = scc.front(); + isCyclic = llvm::any_of(edges, [&](const CircuitEdge &edge) { + return edge.src == only && edge.dst == only; + }); + } + if (!isCyclic) + continue; + + for (const CircuitEdge &edge : edges) { + if (!sccNodes.contains(edge.src) || !sccNodes.contains(edge.dst)) + continue; + if (!isBackedge(edge.channel)) + continue; + if (!isBackedgeSourceLike(edge.src)) + continue; + if (!isBackedgeDestinationLike(edge.dst)) + continue; + backwardChannels.insert(edge.channel); + } + } + + return backwardChannels; +} + +} // namespace + +// useful in debugging +// static void printCycles(const CycleList &cycles) { +// llvm::errs() << "=== Circuit Cycles ===\n"; +// int cycleIdx = 0; + +// for (const auto &cycle : cycles) { +// llvm::errs() << "Cycle " << cycleIdx++ << " (" << cycle.size() +// << " ops):\n"; + +// for (Operation *op : cycle) { +// llvm::errs() << " - "; +// op->print(llvm::errs()); +// llvm::errs() << "\n"; +// } + +// llvm::errs() << "\n"; +// } +// } + +//////////////////////////////////////////////////////////////////////////////////// + LogicalResult HandshakePlaceBuffersPass::getCFDFCs(FuncInfo &info, std::vector &cfdfcs) { SmallVector archsCopy(info.archs); @@ -403,6 +607,25 @@ LogicalResult HandshakePlaceBuffersPass::getCFDFCs(FuncInfo &info, bbs.insert(arch.dstBB); } + //////////// AYA added the following to identify all graph cycles in the + /// circuit graph + // Identify the cycles and the backward channels in your circuit + llvm::errs() << "\nBefore findALlCycles\n"; + + // CycleList circuitCycles = + // findAllCycles(info.funcOp); + // // llvm::errs() << "\nAfter findALlCycles\n"; + + // AYA TO AYA: The goal is to send mlir::DenseSet backwardChannels + // structure to the constructor of CFDFC below. + // GraphForJohnson johnsonGraph(info.funcOp); + // CycleList circuitCycles = johnsonGraph.findAllCycles(); + + // printCycles(circuitCycles); + mlir::DenseSet backwardChannels = + findBackwardChannelPerCyclicRegion(info.funcOp); + printBackwardChannels(backwardChannels); + // Set of selected archs ArchSet selectedArchs; // Number of executions @@ -427,7 +650,7 @@ LogicalResult HandshakePlaceBuffersPass::getCFDFCs(FuncInfo &info, break; // Create the CFDFC from the set of selected archs and BBs - cfdfcs.emplace_back(info.funcOp, selectedArchs, numExecs); + cfdfcs.emplace_back(info.funcOp, selectedArchs, numExecs, backwardChannels); } while (!firstCFDFC); return success(); @@ -538,12 +761,6 @@ LogicalResult HandshakePlaceBuffersPass::solveBufferPlacementMILP( writeTo); return milp.solve(placement, calculatePathDelays); } - - if (algorithm == FPGA24) { - fpga24::FPGA24Buffers solver(solverKind, timeout, info, timingDB, targetCP); - return solver.solve(placement); - } - if (algorithm == COST_AWARE) { if (dumpMILPModels) { writeTo = dumpDir + sep + funcName + "-cost-aware"; diff --git a/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp b/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp index 2ff2514048..8c6068a008 100644 --- a/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp +++ b/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp @@ -11,7 +11,9 @@ //===----------------------------------------------------------------------===// #include "dynamatic/Transforms/BufferPlacement/Utils/CFDFC.h" +#include "dynamatic/Analysis/NameAnalysis.h" #include "dynamatic/Dialect/Handshake/HandshakeOps.h" +#include "dynamatic/Dialect/Handshake/HandshakeTypes.h" #include "dynamatic/Support/CFG.h" #include "dynamatic/Support/ConstraintProgramming/ConstraintProgramming.h" #include "mlir/Dialect/Arith/IR/Arith.h" @@ -20,9 +22,13 @@ #include "mlir/IR/OperationSupport.h" #include "mlir/Support/IndentedOstream.h" #include "mlir/Support/LogicalResult.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/SetOperations.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/Casting.h" #include using namespace mlir; @@ -31,6 +37,9 @@ using namespace dynamatic::handshake; using namespace dynamatic::buffer; using namespace dynamatic::experimental; +// #include "graphviz/cgraph.h" +// #include "graphviz/gvc.h" + namespace { /// Helper data structure to hold mappings between each arch/basic block and the /// Gurobi variable that corresponds to it. @@ -44,6 +53,17 @@ struct MILPVars { }; } // namespace +// AYA: added the following function +bool CFDFC::isCFGCompliant(unsigned srcBB, unsigned dstBB) { + for (size_t i = 0; i < cycle.size(); ++i) { + unsigned nextBB = i == cycle.size() - 1 ? 0 : i + 1; + if (srcBB == cycle[i] && dstBB == cycle[nextBB]) + return true; + } + + return false; +} + /// Initializes all variables in the MILP, one per arch and per basic block. /// Fills in the last argument with mappings between archs/BBs and their /// associated Gurobi variable. @@ -155,87 +175,175 @@ static void setBBConstraints(std::unique_ptr &model, MILPVars &vars) { } } -CFDFC::CFDFC(handshake::FuncOp funcOp, ArchSet &archs, unsigned numExec) +CFDFC::CFDFC(handshake::FuncOp funcOp, ArchSet &archs, unsigned numExec, + DenseSet backwardChannels) : numExecs(numExec) { - // Identify the block that starts the CFDFC; it's the only one that is both - // the source of an arch and the destination of another - std::optional startBB; - llvm::SmallSet uniqueBlocks; - for (ArchBB *arch : archs) { - if (auto [_, inserted] = uniqueBlocks.insert(arch->srcBB); !inserted) { - startBB = arch->srcBB; - break; + bool ayaWay = true; + if (ayaWay) { + llvm::errs() << "\n\tUsing Aya's CFDFC extraction method\n"; + // Identify the block that starts the CFDFC; it's the only one that is both + // the source of an arch and the destination of another + std::optional startBB; + llvm::SmallSet uniqueBlocks; + for (ArchBB *arch : archs) { + if (auto [_, inserted] = uniqueBlocks.insert(arch->srcBB); !inserted) { + startBB = arch->srcBB; + break; + } + if (auto [_, inserted] = uniqueBlocks.insert(arch->dstBB); !inserted) { + startBB = arch->dstBB; + break; + } } - if (auto [_, inserted] = uniqueBlocks.insert(arch->dstBB); !inserted) { - startBB = arch->dstBB; - break; + assert(startBB.has_value() && "failed to identify start of CFDFC"); + + // Form the CFG cycle by stupidly iterating over the archs + cycle.insert(*startBB); + unsigned currentBB = *startBB; + for (size_t i = 0, e = archs.size() - 1; i < e; ++i) { + for (ArchBB *arch : archs) { + if (arch->srcBB == currentBB) { + currentBB = arch->dstBB; + cycle.insert(currentBB); + break; + } + } } - } - assert(startBB.has_value() && "failed to identify start of CFDFC"); + assert(cycle.size() == archs.size() && "failed to construct cycle"); + + // AYA: note to self: the above is common with the old approach + + for (Operation &op : funcOp.getOps()) { + // Get operation's basic block + unsigned srcBB; + if (auto optBB = getLogicBB(&op); !optBB.has_value()) + continue; + else + srcBB = *optBB; + + // The basic block the operation belongs to must be selected in the CFG + // cycle currently under study + if (!cycle.contains(srcBB)) + continue; - // Form the cycle by stupidly iterating over the archs - cycle.insert(*startBB); - unsigned currentBB = *startBB; - for (size_t i = 0, e = archs.size() - 1; i < e; ++i) { + // Add the unit and valid outgoing channels to the CFDFC + units.insert(&op); + for (OpResult res : op.getResults()) { + assert(std::distance(res.getUsers().begin(), res.getUsers().end()) == + 1 && + "value must have unique user"); + + // Get the value's unique user and its basic block + Operation *user = *res.getUsers().begin(); + unsigned dstBB; + if (std::optional optBB = getLogicBB(user); + !optBB.has_value()) + continue; + else + dstBB = *optBB; + + if (!cycle.contains(dstBB)) + continue; + + // AYA: the following if-else is really the core of my logic. First, + // check if it is not a backward edge and the src and dst BBs are part + // of the cycle, consider the channel. Otherwise, if it is a backward + // edge, insert it only if compliant with the CFG + if (!backwardChannels.contains(res)) + channels.insert(res); + else { + // insert backedges only if they are compliant with the CFG + backedges.insert(res); + if (isCFGCompliant(srcBB, dstBB)) + channels.insert(res); + } + } + } + } else { + llvm::errs() << "\n\tUsing the old CFDFC extraction method\n"; + + // Identify the block that starts the CFDFC; it's the only one that is both + // the source of an arch and the destination of another + std::optional startBB; + llvm::SmallSet uniqueBlocks; for (ArchBB *arch : archs) { - if (arch->srcBB == currentBB) { - currentBB = arch->dstBB; - cycle.insert(currentBB); + if (auto [_, inserted] = uniqueBlocks.insert(arch->srcBB); !inserted) { + startBB = arch->srcBB; + break; + } + if (auto [_, inserted] = uniqueBlocks.insert(arch->dstBB); !inserted) { + startBB = arch->dstBB; break; } } - } - assert(cycle.size() == archs.size() && "failed to construct cycle"); - - for (Operation &op : funcOp.getOps()) { - // Get operation's basic block - unsigned srcBB; - if (auto optBB = getLogicBB(&op); !optBB.has_value()) - continue; - else - srcBB = *optBB; - - // The basic block the operation belongs to must be selected - if (!cycle.contains(srcBB)) - continue; - - // Add the unit and valid outgoing channels to the CFDFC - units.insert(&op); - for (OpResult res : op.getResults()) { - assert(std::distance(res.getUsers().begin(), res.getUsers().end()) == 1 && - "value must have unique user"); - - // Get the value's unique user and its basic block - Operation *user = *res.getUsers().begin(); - unsigned dstBB; - if (std::optional optBB = getLogicBB(user); !optBB.has_value()) + assert(startBB.has_value() && "failed to identify start of CFDFC"); + + // Form the cycle by stupidly iterating over the archs + cycle.insert(*startBB); + unsigned currentBB = *startBB; + for (size_t i = 0, e = archs.size() - 1; i < e; ++i) { + for (ArchBB *arch : archs) { + if (arch->srcBB == currentBB) { + currentBB = arch->dstBB; + cycle.insert(currentBB); + break; + } + } + } + assert(cycle.size() == archs.size() && "failed to construct cycle"); + + for (Operation &op : funcOp.getOps()) { + // Get operation's basic block + unsigned srcBB; + if (auto optBB = getLogicBB(&op); !optBB.has_value()) continue; else - dstBB = *optBB; - - if (srcBB != dstBB) { - // The channel is in the CFDFC if it belongs belong to a selected arch - // between two basic blocks - for (size_t i = 0; i < cycle.size(); ++i) { - unsigned nextBB = i == cycle.size() - 1 ? 0 : i + 1; - if (srcBB == cycle[i] && dstBB == cycle[nextBB]) { - channels.insert(res); - if (isCFDFCBackedge(res)) - backedges.insert(res); - break; + srcBB = *optBB; + + // The basic block the operation belongs to must be selected + if (!cycle.contains(srcBB)) + continue; + + // Add the unit and valid outgoing channels to the CFDFC + units.insert(&op); + for (OpResult res : op.getResults()) { + assert(std::distance(res.getUsers().begin(), res.getUsers().end()) == + 1 && + "value must have unique user"); + + // Get the value's unique user and its basic block + Operation *user = *res.getUsers().begin(); + unsigned dstBB; + if (std::optional optBB = getLogicBB(user); + !optBB.has_value()) + continue; + else + dstBB = *optBB; + + if (srcBB != dstBB) { + // The channel is in the CFDFC if it belongs belong to a selected arch + // between two basic blocks + for (size_t i = 0; i < cycle.size(); ++i) { + unsigned nextBB = i == cycle.size() - 1 ? 0 : i + 1; + if (srcBB == cycle[i] && dstBB == cycle[nextBB]) { + channels.insert(res); + if (isCFDFCBackedge(res)) + backedges.insert(res); + break; + } } + } else if (cycle.size() == 1) { + // The channel is in the CFDFC if its producer/consumer belong to the + // same basic block and the CFDFC is just a block looping to itself + channels.insert(res); + if (isCFDFCBackedge(res)) + backedges.insert(res); + } else if (!isBackedge(res)) { + // The channel is in the CFDFC if its producer/consumer belong to the + // same basic block and the channel is not a backedge + channels.insert(res); } - } else if (cycle.size() == 1) { - // The channel is in the CFDFC if its producer/consumer belong to the - // same basic block and the CFDFC is just a block looping to itself - channels.insert(res); - if (isCFDFCBackedge(res)) - backedges.insert(res); - } else if (!isBackedge(res)) { - // The channel is in the CFDFC if its producer/consumer belong to the - // same basic block and the channel is not a backedge - channels.insert(res); } } } @@ -355,6 +463,35 @@ void dynamatic::buffer::getDisjointBlockUnions( } } +// AYA: Added this from Jiahui to write the CFDFCs in DOT for debugging +// void CFDFC::writeDot(const std::string &fileName) { +// Agraph_t *gv = agopen(const_cast("cfdfc"), Agdirected, nullptr); +// for (auto value : channels) { +// auto *pred = value.getDefiningOp(); +// auto succ = value.getUsers().begin(); +// std::string predName = +// pred->getAttrOfType(NameAnalysis::ATTR_NAME).str(); +// std::string succName = +// succ->getAttrOfType(NameAnalysis::ATTR_NAME).str(); + +// auto *predNode = agnode(gv, const_cast(predName.c_str()), +// /* create if not exist */ 1); + +// auto *succNode = agnode(gv, const_cast(succName.c_str()), +// /* create if not exist */ 1); + +// agedge(gv, predNode, succNode, nullptr, 1); +// } +// // Write to DOT file +// FILE *fs = fopen(fileName.c_str(), "w"); + +// if (!fs) +// llvm::errs() << "Failed to write file\n"; + +// agwrite(gv, fs); +// fclose(fs); +// } + LogicalResult dynamatic::buffer::extractCFDFC( handshake::FuncOp funcOp, ArchSet &archs, BBSet &bbs, ArchSet &selectedArchs, unsigned &numExecs, CPSolver::SolverKind solverKind, diff --git a/lib/Transforms/HandshakeInferBasicBlocks.cpp b/lib/Transforms/HandshakeInferBasicBlocks.cpp index c3104cf394..0c0e2e8009 100644 --- a/lib/Transforms/HandshakeInferBasicBlocks.cpp +++ b/lib/Transforms/HandshakeInferBasicBlocks.cpp @@ -21,6 +21,7 @@ #include "dynamatic/Support/CFG.h" #include "mlir/Support/LogicalResult.h" #include "mlir/Transforms/DialectConversion.h" +#include "llvm/Support/Casting.h" using namespace mlir; using namespace dynamatic; @@ -81,7 +82,9 @@ static LogicalResult inferLogicBB(Operation *op, unsigned &logicBB) { Operation *defOp = opr.getDefiningOp(); std::optional oprBB = defOp ? getLogicBB(defOp) : ENTRY_BB; if (failed(mergeInferredBB(oprBB))) { - return failure(); + // return failure(); + // Aya: commented the above and added instead break; + break; } } @@ -89,6 +92,16 @@ static LogicalResult inferLogicBB(Operation *op, unsigned &logicBB) { logicBB = *infBB; return success(); } + + // Aya: Added an additional way for inferring a basic block which is important + // for components added by the Term Rewrite Pass + if (llvm::isa_and_nonnull(op)) { + handshake::ConditionalBranchOp br = + cast(op); + logicBB = *getLogicBB(br.getConditionOperand().getDefiningOp()); + return success(); + } + return failure(); } diff --git a/tools/dynamatic/dynamatic.cpp b/tools/dynamatic/dynamatic.cpp index 8a8ba017db..6e3b641119 100644 --- a/tools/dynamatic/dynamatic.cpp +++ b/tools/dynamatic/dynamatic.cpp @@ -301,6 +301,8 @@ class Compile : public Command { static constexpr llvm::StringLiteral K_INDUCTION = "k-induction"; static constexpr llvm::StringLiteral DISABLE_LSQ = "disable-lsq"; static constexpr llvm::StringLiteral STRAIGHT_TO_QUEUE = "straight-to-queue"; + static constexpr llvm::StringLiteral OPTIMIZE_STEERING_REWRITES = + "optimize-steering-rewrites"; static constexpr llvm::StringLiteral ENABLE_SHORT_CIRCUIT = "enable-short-circuit"; static constexpr llvm::StringLiteral SPECULATION = "speculation"; @@ -338,6 +340,8 @@ class Compile : public Command { "accesses, use with caution!"}); addFlag({STRAIGHT_TO_QUEUE, "Use straight to queue to connect the circuit to the LSQ"}); + addFlag( + {OPTIMIZE_STEERING_REWRITES, "Use handshake steering-term rewrites"}); addFlag({ENABLE_SHORT_CIRCUIT, "Enable short-circuit evaluation of && and ||, " "to match C specification"}); @@ -761,6 +765,8 @@ CommandResult Compile::execute(CommandArguments &args) { args.flags.contains(FAST_TOKEN_DELIVERY) ? "1" : "0"; std::string straightToQueue = args.flags.contains(STRAIGHT_TO_QUEUE) ? "1" : "0"; + std::string optimizeSteeringRewrite = + args.flags.contains(OPTIMIZE_STEERING_REWRITES) ? "1" : "0"; if (auto it = args.options.find(BUFFER_ALGORITHM); it != args.options.end()) { if (it->second == "on-merges" || it->second == "fpga20" || @@ -798,12 +804,12 @@ CommandResult Compile::execute(CommandArguments &args) { std::string calculatePathDelays = args.flags.contains(CALCULATE_PATH_DELAYS) ? "1" : "0"; - return execCmd(script, state.dynamaticPath, state.getKernelDir(), - state.getOutputDir(), state.getKernelName(), buffers, - floatToString(state.targetCP, 3), sharing, - state.fpUnitsGenerator, rigidification, kInduction, disableLSQ, - fastTokenDelivery, milpSolver, straightToQueue, speculation, - enableShortCircuit, enableDuplication, calculatePathDelays); + return execCmd( + script, state.dynamaticPath, state.getKernelDir(), state.getOutputDir(), + state.getKernelName(), buffers, floatToString(state.targetCP, 3), sharing, + state.fpUnitsGenerator, rigidification, kInduction, disableLSQ, + fastTokenDelivery, milpSolver, straightToQueue, optimizeSteeringRewrite, + speculation, enableShortCircuit, enableDuplication, calculatePathDelays); } CommandResult WriteHDL::execute(CommandArguments &args) { diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index 55e790bcbc..94931fc9c1 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -21,10 +21,11 @@ DISABLE_LSQ=${11} FAST_TOKEN_DELIVERY=${12} MILP_SOLVER=${13} STRAIGHT_TO_QUEUE=${14} -SPECULATION=${15} -ENABLE_SHORT_CIRCUIT=${16} -ENABLE_DUPLICATION=${17:-0} -CALCULATE_PATH_DELAYS=${18} +OPTIMIZE_STEERING_REWRITES=${15} +SPECULATION=${16} +ENABLE_SHORT_CIRCUIT=${17} +ENABLE_DUPLICATION=${18:-0} +CALCULATE_PATH_DELAYS=${19} LLVM=$DYNAMATIC_DIR/llvm-project DYNAMATIC_BINS=$DYNAMATIC_DIR/bin @@ -63,6 +64,8 @@ F_HANDSHAKE_BUFFERED="$COMP_DIR/handshake_buffered.mlir" F_HANDSHAKE_EXPORT="$COMP_DIR/handshake_export.mlir" F_HANDSHAKE_RIGIDIFIED="$COMP_DIR/handshake_rigidified.mlir" F_HANDSHAKE_SQ="$COMP_DIR/handshake_sq.mlir" +F_HANDSHAKE_PRE_MATERIALIZE="$COMP_DIR/handshake_pre_materialize.mlir" +F_HANDSHAKE_REWRITTEN="$COMP_DIR/handshake_rewritten.mlir" F_HW="$COMP_DIR/hw.mlir" F_FREQUENCIES="$COMP_DIR/frequencies.csv" @@ -308,28 +311,41 @@ if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then F_HANDSHAKE=$F_HANDSHAKE_SQ - # handshake transformations "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ --handshake-remove-unused-memrefs \ --handshake-optimize-bitwidths \ - --handshake-materialize --handshake-infer-basic-blocks \ - > "$F_HANDSHAKE_TRANSFORMED" - exit_on_fail "Failed to apply transformations to handshake" \ - "Applied transformations to handshake" - + > "$F_HANDSHAKE_PRE_MATERIALIZE" + exit_on_fail "Failed to apply pre-materialization transformations to handshake" \ + "Applied pre-materialization transformations to handshake" else - - # handshake transformations "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ --handshake-deactivate-mem-dependencies --handshake-replace-memory-interfaces \ --handshake-remove-unused-memrefs \ --handshake-optimize-bitwidths \ - --handshake-materialize --handshake-infer-basic-blocks \ - > "$F_HANDSHAKE_TRANSFORMED" - exit_on_fail "Failed to apply transformations to handshake" \ - "Applied transformations to handshake" + > "$F_HANDSHAKE_PRE_MATERIALIZE" + exit_on_fail "Failed to apply pre-materialization transformations to handshake" \ + "Applied pre-materialization transformations to handshake" +fi + +F_HANDSHAKE_TO_MATERIALIZE="$F_HANDSHAKE_PRE_MATERIALIZE" +if [[ $OPTIMIZE_STEERING_REWRITES -ne 0 ]]; then + echo_info "Applying steering-term rewrites" + "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE_TO_MATERIALIZE" \ + --handshake-rewrite-terms \ + --handshake-combine-steering-logic \ + > "$F_HANDSHAKE_REWRITTEN" + exit_on_fail "Failed to apply steering-term rewrites" \ + "Applied steering-term rewrites" + F_HANDSHAKE_TO_MATERIALIZE="$F_HANDSHAKE_REWRITTEN" fi +# Final materialization before speculation and buffer placement. +"$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE_TO_MATERIALIZE" \ + --handshake-materialize --handshake-infer-basic-blocks \ + > "$F_HANDSHAKE_TRANSFORMED" +exit_on_fail "Failed to apply transformations to handshake" \ + "Applied transformations to handshake" + # Speculation (pre-buffer): place speculative units and then materialize. if [[ "$SPECULATION" == "1" ]]; then "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE_TRANSFORMED" \