From b499cd269d867f0c64a32a9fb04f629f3a6e4d64 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Sat, 18 Apr 2026 20:08:51 +0200 Subject: [PATCH 01/15] adding pass, some examples fail --- .../Transforms/HandshakeRewriteTerms.h | 33 + include/dynamatic/Transforms/Passes.h | 2 + include/dynamatic/Transforms/Passes.td | 13 + lib/Transforms/CMakeLists.txt | 1 + lib/Transforms/HandshakeInferBasicBlocks.cpp | 15 +- lib/Transforms/HandshakeRewriteTerms.cpp | 2177 +++++++++++++++++ test/Transforms/handshake-rewrite-terms.mlir | 353 +++ tools/dynamatic/scripts/compile.sh | 2 + 8 files changed, 2595 insertions(+), 1 deletion(-) create mode 100644 include/dynamatic/Transforms/HandshakeRewriteTerms.h create mode 100644 lib/Transforms/HandshakeRewriteTerms.cpp create mode 100644 test/Transforms/handshake-rewrite-terms.mlir diff --git a/include/dynamatic/Transforms/HandshakeRewriteTerms.h b/include/dynamatic/Transforms/HandshakeRewriteTerms.h new file mode 100644 index 0000000000..f83c4cad5e --- /dev/null +++ b/include/dynamatic/Transforms/HandshakeRewriteTerms.h @@ -0,0 +1,33 @@ +//===- 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 DYNAMATIC_TRANSFORMS_HANDSHAKEREWRITETERMS_H +#define DYNAMATIC_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 { + +#define GEN_PASS_DECL_HANDSHAKEREWRITETERMS +#define GEN_PASS_DEF_HANDSHAKEREWRITETERMS +#include "dynamatic/Transforms/Passes.h.inc" + +std::unique_ptr rewriteHandshakeTerms(); + +} // namespace dynamatic + +#endif // DYNAMATIC_TRANSFORMS_HANDSHAKEREWRITETERMS_H diff --git a/include/dynamatic/Transforms/Passes.h b/include/dynamatic/Transforms/Passes.h index 5d9b61b931..201a2c713a 100644 --- a/include/dynamatic/Transforms/Passes.h +++ b/include/dynamatic/Transforms/Passes.h @@ -20,6 +20,8 @@ #include "mlir/IR/DialectRegistry.h" #include "mlir/Pass/Pass.h" +#include "dynamatic/Transforms/HandshakeRewriteTerms.h" + namespace dynamatic { /// Generate the code for registering passes. diff --git a/include/dynamatic/Transforms/Passes.td b/include/dynamatic/Transforms/Passes.td index c70840c65a..4cf7489044 100644 --- a/include/dynamatic/Transforms/Passes.td +++ b/include/dynamatic/Transforms/Passes.td @@ -104,6 +104,19 @@ def FuncSetArgNames : DynamaticPass<"func-set-arg-names"> { // Handshake passes //===----------------------------------------------------------------------===// + +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::rewriteHandshakeTerms()"; +} + def HandshakeAnalyzeLSQUsage : DynamaticPass<"handshake-analyze-lsq-usage"> { let summary = "Analyzes memory accesses to LSQs to find unnecessary ones"; let description = [{ diff --git a/lib/Transforms/CMakeLists.txt b/lib/Transforms/CMakeLists.txt index 929d84786d..ceae87cdc0 100644 --- a/lib/Transforms/CMakeLists.txt +++ b/lib/Transforms/CMakeLists.txt @@ -8,6 +8,7 @@ add_dynamatic_library(DynamaticTransforms HandshakeAnalyzeLSQUsage.cpp HandshakeCanonicalize.cpp HandshakeHoistExtInstances.cpp + HandshakeRewriteTerms.cpp HandshakeMaterialize.cpp HandshakeMinimizeCstWidth.cpp HandshakeOptimizeBitwidths.cpp 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/lib/Transforms/HandshakeRewriteTerms.cpp b/lib/Transforms/HandshakeRewriteTerms.cpp new file mode 100644 index 0000000000..ed79c2cdf2 --- /dev/null +++ b/lib/Transforms/HandshakeRewriteTerms.cpp @@ -0,0 +1,2177 @@ +//===-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 "dynamatic/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 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 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 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(); + + 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); + } + if (needNot) { + if (foundNot) + condition = existingNotOp.getResult(); + else { + rewriter.setInsertionPoint(condBranchOp); + handshake::NotIOp notIOp = rewriter.create( + condBranchOp->getLoc(), condition); + inheritBB(notIOp, condBranchOp); + condition = notIOp.getResult(); + } + } + + // 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(); + + // 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(), constantType, + 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(), constantType, + 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::impl::HandshakeRewriteTermsBase< + HandshakeRewriteTermsPass> { + + void runDynamaticPass() override { + MLIRContext *ctx = &getContext(); + ModuleOp mod = getOperation(); + + GreedyRewriteConfig config; + config.useTopDownTraversal = true; + config.enableRegionSimplification = false; + 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::rewriteHandshakeTerms() { + return std::make_unique(); +} \ No newline at end of file diff --git a/test/Transforms/handshake-rewrite-terms.mlir b/test/Transforms/handshake-rewrite-terms.mlir new file mode 100644 index 0000000000..e9baba3ee2 --- /dev/null +++ b/test/Transforms/handshake-rewrite-terms.mlir @@ -0,0 +1,353 @@ +handshake.func @removeBranchMUXPair_simple(%arg0: i32, %arg1: i1, %start: none) -> i32 { + %arg1_one, %arg1_two = fork [2] %arg1: i1 + %true, %false = cond_br %arg1_one , %arg0 : i32 + %result_mux= mux %arg1_two [%true, %false]: i1, i32 + end %result_mux : i32 +} +handshake.func @removeBranchCMergePair_simple(%arg0: i32, %arg1: i1, %start: none) -> (i32, index) { + %true, %false = cond_br %arg1, %arg0 : i32 + %result_cmerge, %index= control_merge %true, %false : i32, index + end %result_cmerge, %index : i32, index +} +handshake.func @removeBranchMUXPair_doNot(%arg0: i32, %arg1: i1, %start: none) -> i32 { + %arg1_one, %arg1_two = fork [2] %arg1: i1 + %true, %false = cond_br %arg1_one, %arg0: i32 + %num_1 = arith.constant 1 : i32 + %num_2 = arith.constant 2 : i32 + %add = arith.addi %false, %num_1 : i32 + %mul = arith.muli %true, %num_2 : i32 + %result_mux= mux %arg1_two [%mul, %add] : i1, i32 + end %result_mux : i32 +} +handshake.func @removeBranchCMergePair_doNot(%arg0: i32, %arg1: i1, %start: none) -> (i32, index) { + %true, %false = cond_br %arg1, %arg0: i32 + %num_1 = arith.constant 1 : i32 + %num_2 = arith.constant 2 : i32 + %add = arith.addi %false, %num_1 : i32 + %mul = arith.muli %true, %num_2 : i32 + %result_cmerge, %index= control_merge %mul, %add : i32, index + end %result_cmerge, %index : i32, index +} +handshake.func @removeBranchMUXPair(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %arg1_one, %arg1_two = fork [2] %arg1: i1 + %arg2_one, %arg2_two = fork [2] %arg2: i1 + %true, %false = cond_br %arg1_one, %arg0 : i32 + %result_mux= mux %arg1_two [%true, %false]: i1, i32 + %true_2, %false_2 = cond_br %arg2_one, %result_mux : i32 + %num_1 = arith.constant 1 : i32 + %num_2 = arith.constant 2 : i32 + %add = arith.addi %false_2, %num_1 : i32 + %mul = arith.muli %true_2, %num_2 : i32 + %result_mux_2= mux %arg2_two [%mul, %add] : i1, i32 + end %result_mux_2 : i32 +} +handshake.func @removeBranchCMergePair(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %true, %false = cond_br %arg1, %arg0 : i32 + %result_cmerge, %index= control_merge %true, %false : i32, index + %true_2, %false_2 = cond_br %arg2, %result_cmerge : i32 + %num_1 = arith.constant 1 : i32 + %num_2 = arith.constant 2 : i32 + %add = arith.addi %false_2, %num_1 : i32 + %mul = arith.muli %true_2, %num_2 : i32 + %result_cmerge_2, %index_2= control_merge %mul, %add : i32, index + end %result_cmerge_2 : i32 +} +handshake.func @removeBranchCMergePair_multiple(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %true, %false = cond_br %arg1, %arg0 : i32 + %true_2, %false_2 = cond_br %arg2, %true : i32 + %result_cmerge, %index= control_merge %true_2, %false_2 : i32, index + %result_cmerge_2, %index_2= control_merge %result_cmerge, %false : i32, index + end %result_cmerge_2 : i32 +} +handshake.func @removeBranchMUXPair_multiple(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %arg1_one, %arg1_two = fork [2] %arg1: i1 + %arg2_one, %arg2_two = fork [2] %arg2: i1 + %true, %false = cond_br %arg1_one, %arg0 : i32 + %true_2, %false_2 = cond_br %arg2_one, %true : i32 + %result_mux= mux %arg2_two [%true_2, %false_2]: i1, i32 + %result_mux_2= mux %arg1_two [%result_mux, %false]: i1, i32 + end %result_mux_2 : i32 +} + +handshake.func @removeBranchMUXPairloop_simple_case1(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %result_mux= mux %arg1 [%true, %arg0]: i1, i32 + %true, %false = cond_br %arg2, %result_mux : i32 + end %false : i32 +} + +handshake.func @removeBranchMUXPairloop_simple_case2(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %result_mux= mux %arg1 [%arg0, %false]: i1, i32 + %true, %false = cond_br %arg2, %result_mux : i32 + end %true : i32 +} + +handshake.func @removeBranchMUXPairloop_simple_case3(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %result_mux= mux %arg1 [%arg0, %true]: i1, i32 + %true, %false = cond_br %arg2, %result_mux : i32 + end %false : i32 +} + +handshake.func @removeBranchMUXPairloop_simple_case4(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %result_mux= mux %arg1 [%false, %arg0]: i1, i32 + %true, %false = cond_br %arg2, %result_mux : i32 + end %true : i32 +} + +handshake.func @removeBranchMergePairloop_simple_case1(%arg0: i32, %arg1: i1, %start: none) -> i32 { + %result_merge= merge %true, %arg0: i32 + %true, %false = cond_br %arg1, %result_merge : i32 + end %false : i32 +} + +handshake.func @removeBranchMergePairloop_simple_case2(%arg0: i32, %arg1: i1, %start: none) -> i32 { + %result_merge= merge %arg0, %false: i32 + %true, %false = cond_br %arg1, %result_merge : i32 + end %true : i32 +} + +handshake.func @removeBranchMergePairloop_simple_case3(%arg0: i32, %arg1: i1, %start: none) -> i32 { + %result_merge= merge %arg0, %true: i32 + %true, %false = cond_br %arg1, %result_merge : i32 + end %false : i32 +} + +handshake.func @removeBranchMergePairloop_simple_case4(%arg0: i32, %arg1: i1, %start: none) -> i32 { + %result_merge= merge %false, %arg0: i32 + %true, %false = cond_br %arg1, %result_merge : i32 + end %true : i32 +} + +handshake.func @removeBranchMUXPairloop_doNot_case1(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %result_mux= mux %arg1 [%true, %arg0]: i1, i32 + %num_1 = arith.constant 1: i32 + %data_in= arith.addi %result_mux, %num_1: i32 + %true, %false = cond_br %arg2, %data_in : i32 + end %false : i32 +} + +handshake.func @removeBranchMUXPairloop_doNot_case2(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %result_mux= mux %arg1 [%data_in, %arg0]: i1, i32 + %true, %false = cond_br %arg2, %result_mux : i32 + %num_2 = arith.constant 2: i32 + %data_in= arith.muli %true, %num_2: i32 + end %false : i32 +} + +handshake.func @removeBranchMergePairloop_doNot_case1(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %result_merge= merge %true, %arg0: i32 + %num_1 = arith.constant 1: i32 + %data_in= arith.addi %result_merge, %num_1: i32 + %true, %false = cond_br %arg2, %data_in : i32 + end %false : i32 +} + +handshake.func @removeBranchMergePairloop_doNot_case2(%arg0: i32, %arg1: i1, %start: none) -> i32 { + %result_merge= merge %arg0, %data_in: i32 + %true, %false = cond_br %arg1, %result_merge : i32 + %num_2 = arith.constant 2: i32 + %data_in= arith.muli %true, %num_2: i32 + end %false : i32 +} + +handshake.func @removeBranchMUXPairloop_multiple(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1, %start: none) -> i32 { + %result_mux_1= mux %arg1 [%true_2, %arg0]: i1, i32 + %result_mux_2= mux %arg2 [%true_1, %result_mux_1]: i1, i32 + %true_1, %false_1 = cond_br %arg3, %result_mux_2 : i32 + %true_2, %false_2 = cond_br %arg4, %false_1 : i32 + end %false_2 : i32 +} + +handshake.func @removeBranchMergePairloop_multiple(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1, %start: none) -> i32 { + %result_merge_1= merge %true_2, %arg0: i32 + %result_merge_2= merge %true_1, %result_merge_1: i32 + %true_1, %false_1 = cond_br %arg3, %result_merge_2 : i32 + %true_2, %false_2 = cond_br %arg4, %false_1 : i32 + end %false_2 : i32 +} + +handshake.func @removeBranchMUXPairloop_multiple_doNot(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1, %start: none) -> i32 { + %result_mux_1= mux %arg1 [%true_2, %arg0]: i1, i32 + %result_mux_2= mux %arg2 [%true_1, %result_mux_1]: i1, i32 + %true_1, %false_1 = cond_br %arg3, %result_mux_2 : i32 + %num_1 = arith.constant 1 : i32 + %data_in = arith.addi %num_1, %false_1 : i32 + %true_2, %false_2 = cond_br %arg4, %data_in : i32 + end %false_2 : i32 +} + +handshake.func @removeBranchMergePairloop_multiple_doNot(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1, %start: none) -> i32 { + %result_merge_1= merge %true_2, %arg0: i32 + %result_merge_2= merge %true_1, %result_merge_1: i32 + %true_1, %false_1 = cond_br %arg3, %result_merge_2 : i32 + %num_1 = arith.constant 1 : i32 + %data_in = arith.addi %num_1, %false_1 : i32 + %true_2, %false_2 = cond_br %arg4, %data_in : i32 + end %false_2 : i32 +} + +handshake.func @removeBoth(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %start: none) -> i32 { + %result_mux= mux %arg1 [%true_2, %arg0]: i1, i32 + %result_merge= merge %true_1, %result_mux: i32 + %true_1, %false_1 = cond_br %arg2, %result_merge : i32 + %true_2, %false_2 = cond_br %arg3, %false_1 : i32 + end %false_2 : i32 +} + +handshake.func @removeBoth_doNot(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %start: none) -> i32 { + %result_mux= mux %arg1 [%true_2, %arg0]: i1, i32 + %result_merge= merge %true_1, %result_mux: i32 + %true_1, %false_1 = cond_br %arg2, %result_merge : i32 + %num_2 = arith.constant 2: i32 + %data_in= arith.muli %false_1, %num_2: i32 + %true_2, %false_2 = cond_br %arg3, %data_in : i32 + end %false_2 : i32 +} + +handshake.func @exampleBranchMUXPairloop(%x: i32, %j: i32, %c1: i1, %c2: i1) -> i32 { + %c1_one, %c1_two = fork [2] %c1 : i1 + %c2_one, %c2_two = fork [2] %c2 : i1 + %result_mux_1= mux %c1_one [%x, %false]: i1, i32 + %true, %false = cond_br %c1_two, %result_mux_1 : i32 + %result_mux_2= mux %c2_one [%true, %false_2]: i1, i32 + %result_mux_2one, %result_mux_2two = fork [2] %result_mux_2 : i32 + %true_2, %false_2 = cond_br %c2_two, %result_mux_2two : i32 + sink %true_2 : i32 + %sta = arith.addi %j, %result_mux_2one: i32 + end %sta : i32 +} + + +handshake.func @removeMUXBranchloop_fork_doNot(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { + %result_mux = mux %arg1 [%arg0, %false] : i1, i32 + %c1, %c2 = fork [2] %result_mux : i32 + %num_2 = arith.constant 2: i32 + %answer = arith.muli %num_2, %c1 : i32 + %true, %false = cond_br %arg2, %c2 : i32 + sink %true: i32 + end %false: i32 +} + +handshake.func @removeMUXBranchloop_fork_doOne(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1, %start: none) -> i32 { + %result_mux_1 = mux %arg1 [%arg0, %false_2] : i1, i32 + %c1, %c2 = fork [2] %result_mux_1 : i32 + %num_2 = arith.constant 2: i32 + %answer = arith.muli %num_2, %c1 : i32 + %result_mux_2 = mux %arg2 [%c2, %false_1] : i1, i32 + %true_1, %false_1 = cond_br %arg3, %result_mux_2 : i32 + %true_2, %false_2 = cond_br %arg4, %true_1 : i32 + sink %true_2: i32 + end %false_2: i32 +} + +handshake.func @removeBranchCMergePairloop_multiple_doNot(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> (i32, index) { + %result_cmerge_1, %index1= control_merge %true_2, %arg0: i32, index + %result_cmerge_2, %index2= control_merge %false_1, %result_cmerge_1: i32, index + %true_1, %false_1 = cond_br %arg1, %result_cmerge_2 : i32 + %num_1 = arith.constant 1 : i32 + %data_in = arith.addi %num_1, %true_1 : i32 + %true_2, %false_2 = cond_br %arg2, %data_in : i32 + end %false_2,%index2 : i32, index +} + +handshake.func @removeBothCMerge(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %start: none) -> (i32, index, index) { + %result_cmerge_1, %index1= control_merge %true_2, %arg0: i32, index + %result_cmerge_2, %index2= control_merge %false_1, %result_cmerge_1: i32, index + %true_1, %false_1 = cond_br %arg2, %result_cmerge_2 : i32 + %true_2, %false_2 = cond_br %arg3, %true_1 : i32 + end %false_2, %index1, %index2 : i32, index, index +} + + +handshake.func @removeSupressFork (%arg0: i1, %arg1: i32, %start: none) -> (i32, i32){ + %true, %false= cond_br %arg0, %arg1: i32 + sink %true: i32 + %one, %two = fork [2] %false : i32 + end %one, %two: i32, i32 +} + +handshake.func @removeSupressFork2 (%arg0: i32, %arg1: i1, %start: none) -> (i32, i32){ + %true, %false= cond_br %arg1, %arg0: i32 + %num1 = arith.constant 1: i32 + %answer= arith.addi %false, %num1 : i32 + %one, %two = fork [2] %answer: i32 + end %one, %two: i32, i32 +} + +handshake.func @removeSupressSupressPairs (%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32{ + %true, %false= cond_br %arg1, %arg0: i32 + %true2, %false2= cond_br %arg2, %false : i32 + end %false2: i32 +} + +handshake.func @BranchtoForkSupressPairs (%arg0: i32, %arg1: i1, %start: none) -> (i32, i32){ + %true, %false= cond_br %arg1, %arg0: i32 + end %true, %false: i32, i32 +} + +handshake.func @BranchtoForkSupressPairs_3 (%arg0: i32, %arg1: i1, %start: none) -> (i32, i32, i32, i32){ + %val1, %val2, %val3= fork [3] %arg1: i1 + %true, %false= cond_br %val1, %arg0: i32 + %true_1, %false_1 = cond_br %val2, %true: i32 + %true_2, %false_2 = cond_br %val3, %false: i32 + end %true_1, %false_1, %true_2, %false_2: i32, i32, i32, i32 +} + +handshake.func @removeForkForkPair(%arg0: i32, %start: none) -> (i32, i32, i32) { + %one, %two = fork [2] %arg0: i32 + %two1, %two2 = fork [2] %two: i32 + end %one, %two1, %two2: i32, i32, i32 +} + +handshake.func @removeForkForkPairMultiple(%arg0: i32, %start: none) -> (i32, i32, i32, i32, i32, i32, i32) { + %one, %two, %three = fork [3] %arg0: i32 + %two1, %two2 = fork [2] %two: i32 + %three1, %three2, %three3, %three4 = fork [4] %three: i32 + end %one, %two1, %two2, %three1, %three2, %three3, %three4 : i32, i32, i32, i32, i32, i32, i32 +} + +handshake.func @removeForkForkPairdonot(%arg0: i32, %start: none) -> (i32, i32, i32, i32, i32, i32) { + %one, %two, %three = fork [3] %arg0: i32 + %num1= arith.constant 1: i32 + %ans = arith.addi %two, %num1 : i32 + %two1, %two2 = fork [2] %ans: i32 + %three1, %three2, %three3, %three4 = fork [4] %three: i32 + end %one, %two1, %two2, %three1, %three2, %three3, %three4 : i32, i32, i32, i32, i32, i32, i32 +} + +handshake.func @removeForkForkPairNested(%arg0: i32, %start: none) -> (i32, i32, i32, i32, i32, i32) { + %one, %two= fork [2] %arg0: i32 + %two1, %two2 = fork [2] %two: i32 + %three1, %three2, %three3, %three4 = fork [4] %two1: i32 + end %one, %two2, %three1, %three2, %three3, %three4 : i32, i32, i32, i32, i32, i32 +} + +handshake.func @removeForkForkPairNested_only1(%arg0: i32, %start: none) -> (i32, i32, i32, i32, i32) { + %one, %two= fork [2] %arg0: i32 + %two1, %two2 = fork [2] %two: i32 + %num1= arith.constant 1: i32 + %ans = arith.addi %two1, %num1 : i32 + %three1, %three2, %three3, %three4 = fork [4] %ans: i32 + end %one, %three1, %three2, %three3, %three4 : i32, i32, i32, i32, i32 +} + +handshake.func @removeForkSuppressMUX(%arg0: i32, %arg1: i1, %start: none) -> i32 { + %one, %two= fork [2] %arg0: i32 + %one1, %two1, %three1 = fork [3] %arg1: i1 + %true, %false= cond_br %one1, %one: i32 + %ans = not %two1 : i1 + %true_2, %false_2= cond_br %ans, %two: i32 + %c = mux %three1 [%false_2, %false]:i1, i32 + end %c: i32 +} + +handshake.func @removeBranchMUXPair_simplee(%arg0: i32, %arg1: i1, %arg2: none, ...) -> i32 attributes {argNames = ["arg0", "arg1", "start"], resNames = ["out0"]} { + %0:3 = fork [3] %arg1 : i1 + %1:2 = fork [2] %arg0 : i32 + %trueResult, %falseResult = cond_br %0#0, %1#0 : i32 + sink %trueResult : i32 + %2 = not %0#1 : i1 + %trueResult_0, %falseResult_1 = cond_br %2, %1#1 : i32 + sink %trueResult_0 : i32 + %3 = mux %0#2 [%falseResult, %falseResult_1] : i1, i32 + end %3 : i32 +} \ No newline at end of file diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index 52e8520579..3073491876 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -268,6 +268,7 @@ if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ --handshake-remove-unused-memrefs \ --handshake-minimize-cst-width --handshake-optimize-bitwidths \ + --handshake-rewrite-terms \ --handshake-materialize="replicate-constant=true" --handshake-infer-basic-blocks \ > "$F_HANDSHAKE_TRANSFORMED" exit_on_fail "Failed to apply transformations to handshake" \ @@ -280,6 +281,7 @@ else --handshake-analyze-lsq-usage --handshake-replace-memory-interfaces \ --handshake-remove-unused-memrefs \ --handshake-minimize-cst-width --handshake-optimize-bitwidths \ + --handshake-rewrite-terms \ --handshake-materialize --handshake-infer-basic-blocks \ > "$F_HANDSHAKE_TRANSFORMED" exit_on_fail "Failed to apply transformations to handshake" \ From 53274bfe6c8da4fbcd780fb6a33dd5989a638b87 Mon Sep 17 00:00:00 2001 From: ayaelakhras Date: Tue, 28 Apr 2026 23:48:54 +0200 Subject: [PATCH 02/15] Changed buffer placement + term rewrite pass --- .../HandshakeCombineSteeringLogic.cpp | 562 ++++++++++++++-- include/dynamatic/Support/CFG.h | 14 +- .../Transforms/BufferPlacement/CFDFC.h | 12 +- lib/Transforms/BufferPlacement/CFDFC.cpp | 275 ++++++-- .../BufferPlacement/HandshakePlaceBuffers.cpp | 245 ++++++- lib/Transforms/HandshakeRewriteTerms.cpp | 605 +++++++++++------- tools/dynamatic/scripts/compile.sh | 6 +- 7 files changed, 1340 insertions(+), 379 deletions(-) diff --git a/experimental/lib/Transforms/HandshakeCombineSteeringLogic.cpp b/experimental/lib/Transforms/HandshakeCombineSteeringLogic.cpp index 7fbbc54dd4..782ab282da 100644 --- a/experimental/lib/Transforms/HandshakeCombineSteeringLogic.cpp +++ b/experimental/lib/Transforms/HandshakeCombineSteeringLogic.cpp @@ -22,7 +22,9 @@ #include "mlir/Support/LogicalResult.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" #include +#include // [START Boilerplate code for the MLIR pass] #include "experimental/Transforms/Passes.h" // IWYU pragma: keep @@ -37,8 +39,95 @@ namespace experimental { using namespace mlir; using namespace dynamatic; +static void logLine(const char *msg) { + std::ofstream f("/home/yuqin/dynamatic-scripts/TempOutputs/" + "HandshakeCombineSteeringLogic.txt", + std::ios::app); + f << msg << "\n"; +} + +static void inheritBB(Operation *from, Operation *to) { + if (auto bbAttr = from->getAttr("handshake.bb")) + to->setAttr("handshake.bb", bbAttr); +} + +static Location getConditionLocOrFallback(Value condition, + Operation *fallback) { + if (Operation *defOp = condition.getDefiningOp()) + return defOp->getLoc(); + return fallback->getLoc(); +} + +static void inheritConditionBBOrFallback(Value condition, Operation *fallback, + Operation *to) { + if (Operation *defOp = condition.getDefiningOp()) { + if (auto bbAttr = defOp->getAttr("handshake.bb")) { + to->setAttr("handshake.bb", bbAttr); + return; + } + } + inheritBB(fallback, to); +} + +static void +refreshBranchAttrsFromCondition(handshake::ConditionalBranchOp branchOp, + Operation *fallback) { + Value condition = branchOp.getConditionOperand(); + branchOp->setLoc(getConditionLocOrFallback(condition, fallback)); + inheritConditionBBOrFallback(condition, fallback, branchOp); +} + namespace { +/// Check if two values are functionally equivalent: +/// - Same SSA value, OR +/// - Both are ConstantOps with the same attribute value, OR +/// - Both are NotIOps whose inputs are themselves equivalent (recursive) +static bool areEquivalentValues(Value a, Value b) { + if (a == b) + return true; + + if (a.getType() != b.getType()) + return false; + + Operation *defA = a.getDefiningOp(); + Operation *defB = b.getDefiningOp(); + if (!defA || !defB) + return false; + + if (auto constA = dyn_cast(defA)) { + if (auto constB = dyn_cast(defB)) + return constA.getValueAttr() == constB.getValueAttr(); + return false; + } + + if (auto notA = dyn_cast(defA)) { + if (auto notB = dyn_cast(defB)) + return areEquivalentValues(notA.getOperand(), notB.getOperand()); + return false; + } + + return false; +} + +static FailureOr +getSingleConstantOperandIndex(handshake::MergeOp mergeOp) { + int constIdx = -1; + for (int i = 0; i < 2; i++) { + if (!isa_and_nonnull( + mergeOp.getDataOperands()[i].getDefiningOp())) + continue; + + if (constIdx != -1) + return failure(); + constIdx = i; + } + + if (constIdx == -1) + return failure(); + return constIdx; +} + /// Combine redundant init merges. These merges have one constant input and a /// condition input. If two merges are identical, then one of them can be /// removed @@ -51,33 +140,41 @@ struct CombineInits : public OpRewritePattern { if (mergeOp->getNumOperands() != 2) return failure(); - // One of the inputs of the merge must be a constants - int constIdx = -1; - for (int i = 0; i < 2; i++) { - if (isa_and_nonnull( - mergeOp.getDataOperands()[i].getDefiningOp())) - constIdx = i; - } - - if (constIdx == -1) + // Exactly one of the inputs of the merge must be a constant. + FailureOr maybeConstIdx = getSingleConstantOperandIndex(mergeOp); + if (failed(maybeConstIdx)) return failure(); + int constIdx = *maybeConstIdx; // Get the index of the other input int loopIdx = 1 - constIdx; - // If there are other merges fed from the same input at the loopIdx - DenseSet redundantInits; - for (auto *user : mergeOp.getDataOperands()[loopIdx].getUsers()) - if (isa_and_nonnull(user) && user != mergeOp) { - handshake::MergeOp mergeUser = cast(user); - if (isa_and_nonnull( - mergeUser.getDataOperands()[constIdx].getDefiningOp())) - redundantInits.insert(mergeUser); - } + SmallVector redundantInits; + mergeOp->getParentRegion()->walk([&](handshake::MergeOp mergeUser) { + if (mergeUser == mergeOp) + return; + if (mergeUser->getNumOperands() != 2) + return; + + FailureOr maybeUserConstIdx = + getSingleConstantOperandIndex(mergeUser); + if (failed(maybeUserConstIdx) || *maybeUserConstIdx != constIdx) + return; + + if (!areEquivalentValues(mergeUser.getDataOperands()[constIdx], + mergeOp.getDataOperands()[constIdx])) + return; + if (!areEquivalentValues(mergeUser.getDataOperands()[loopIdx], + mergeOp.getDataOperands()[loopIdx])) + return; + + redundantInits.push_back(mergeUser); + }); if (redundantInits.empty()) return failure(); + logLine("[HandshakeCombineSteeringLogic] CombineInits applied"); for (auto init : redundantInits) { rewriter.replaceAllUsesWith(init.getResult(), mergeOp.getResult()); rewriter.eraseOp(init); @@ -87,6 +184,34 @@ struct CombineInits : public OpRewritePattern { } }; +/// Combine NotIOps that have functionally identical inputs. +struct CombineEquivalentNotIOps : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(handshake::NotIOp notOp, + PatternRewriter &rewriter) const override { + SmallVector redundant; + + notOp->getParentRegion()->walk([&](handshake::NotIOp otherNot) { + if (otherNot == notOp) + return; + if (!areEquivalentValues(otherNot.getOperand(), notOp.getOperand())) + return; + redundant.push_back(otherNot); + }); + + if (redundant.empty()) + return failure(); + + logLine("[HandshakeCombineSteeringLogic] CombineEquivalentNotIOps applied"); + for (auto notUser : redundant) { + rewriter.replaceAllUsesWith(notUser.getResult(), notOp.getResult()); + rewriter.eraseOp(notUser); + } + + return success(); + } +}; + /// Returns true if the loop under analysis has a self regenerating mux. One /// input of the mux comes from the mux itself, while the other input comes from /// somewhere else. @@ -213,11 +338,12 @@ struct CombineMuxes : public OpRewritePattern { if (redundantMuxes.empty()) return failure(); + logLine("[HandshakeCombineSteeringLogic] CombineMuxes applied"); // Loop over redundantMuxes and replace the users of them with the output of // muxOp Note that the users of all redundantMuxes include the Branches // forming cycles with each of them, but as we erase the redundantMuxes, // these Branches will have their two outputs feeding nothing and will be - // erased using the RemoveDoubleSinkBranches + // erased using the RemoveUnusedOp for (auto mux : redundantMuxes) { rewriter.replaceAllUsesWith(mux.getResult(), muxOp.getResult()); rewriter.eraseOp(mux); @@ -227,39 +353,138 @@ struct CombineMuxes : public OpRewritePattern { } }; -/// Remove muxes that have no successors -struct RemoveSinkMuxes : public OpRewritePattern { +/// Combine MuxOps that have functionally identical inputs. +struct CombineEquivalentMuxes : public OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(handshake::MuxOp muxOp, PatternRewriter &rewriter) const override { - // The pattern fails if the Mux has any successors - if (!muxOp.getResult().getUsers().empty()) + if (muxOp.getNumOperands() != 3) + return failure(); + + SmallVector redundant; + + muxOp->getParentRegion()->walk([&](handshake::MuxOp otherMux) { + if (otherMux == muxOp) + return; + if (otherMux.getNumOperands() != 3) + return; + if (!areEquivalentValues(otherMux.getSelectOperand(), + muxOp.getSelectOperand())) + return; + if (!areEquivalentValues(otherMux.getDataOperands()[0], + muxOp.getDataOperands()[0])) + return; + if (!areEquivalentValues(otherMux.getDataOperands()[1], + muxOp.getDataOperands()[1])) + return; + redundant.push_back(otherMux); + }); + + if (redundant.empty()) return failure(); - rewriter.eraseOp(muxOp); + logLine("[HandshakeCombineSteeringLogic] CombineEquivalentMuxes applied\n"); + for (auto mux : redundant) { + rewriter.replaceAllUsesWith(mux.getResult(), muxOp.getResult()); + rewriter.eraseOp(mux); + } + return success(); } }; -/// Remove conditional branches that have no successors -struct RemoveDoubleSinkBranches +/// Combine ConditionalBranchOps that have functionally identical inputs. +struct CombineEquivalentBranches : public OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(handshake::ConditionalBranchOp condBranchOp, PatternRewriter &rewriter) const override { - Value branchTrueResult = condBranchOp.getTrueResult(); - Value branchFalseResult = condBranchOp.getFalseResult(); + SmallVector redundant; + + condBranchOp->getParentRegion()->walk( + [&](handshake::ConditionalBranchOp otherBr) { + if (otherBr == condBranchOp) + return; + if (!areEquivalentValues(otherBr.getConditionOperand(), + condBranchOp.getConditionOperand())) + return; + if (!areEquivalentValues(otherBr.getDataOperand(), + condBranchOp.getDataOperand())) + return; + redundant.push_back(otherBr); + }); + + if (redundant.empty()) + return failure(); + + logLine( + "[HandshakeCombineSteeringLogic] CombineEquivalentBranches applied\n"); + for (auto br : redundant) { + rewriter.replaceAllUsesWith(br.getTrueResult(), + condBranchOp.getTrueResult()); + rewriter.replaceAllUsesWith(br.getFalseResult(), + condBranchOp.getFalseResult()); + rewriter.eraseOp(br); + } - // The pattern fails if the branch has either true or false successors - if (!branchTrueResult.getUsers().empty()) + return success(); + } +}; + +/// Remove a lazy fork that only forwards its input into another lazy fork. +/// This matches the S2Q shape where output #1 was meant for the LSQ but ended +/// up unused, while output #0 only feeds a successor lazy fork. +struct BypassRedundantLazyFork + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(handshake::LazyForkOp forkOp, + PatternRewriter &rewriter) const override { + + if (forkOp->getNumResults() != 2) return failure(); - if (!branchFalseResult.getUsers().empty()) + Value forwarded = forkOp->getResult(0); + Value lsqOutput = forkOp->getResult(1); + + if (!lsqOutput.use_empty()) return failure(); - rewriter.eraseOp(condBranchOp); + if (!forwarded.hasOneUse()) + return failure(); + + auto *user = *forwarded.getUsers().begin(); + auto succFork = dyn_cast(user); + if (!succFork) + return failure(); + + if (succFork.getOperand() != forwarded) + return failure(); + + logLine("[HandshakeCombineSteeringLogic] BypassRedundantLazyFork applied"); + succFork->setOperand(0, forkOp.getOperand()); + rewriter.eraseOp(forkOp); + return success(); + } +}; + +/// Remove any op of type OpTy whose results are all unused. +template +struct RemoveUnusedOp : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(OpTy op, + PatternRewriter &rewriter) const override { + // The pattern fails if the Op has any successors + for (auto result : op->getResults()) { + if (!result.use_empty()) + return failure(); + } + + logLine(("[HandshakeCombineSteeringLogic] RemoveUnusedOp<" + + std::string(OpTy::getOperationName()) + "> applied") + .c_str()); + rewriter.eraseOp(op); return success(); } }; @@ -317,6 +542,8 @@ struct CombineBranchesOppositeSign if (redundantBranches.empty()) return failure(); + logLine("[HandshakeCombineSteeringLogic] CombineBranchesOppositeSign " + "applied\n"); // Erase the redundant branch for (auto br : redundantBranches) { rewriter.replaceAllUsesWith(br.getFalseResult(), @@ -356,37 +583,268 @@ struct RemoveNotCondition rewriter.replaceAllUsesWith(condBranchOp.getFalseResult(), newBranch.getTrueResult()); - newBranch->setAttr("handshake.bb", condBranchOp->getAttr("handshake.bb")); + refreshBranchAttrsFromCondition(newBranch, condBranchOp); + rewriter.eraseOp(condBranchOp); + logLine("[HandshakeCombineSteeringLogic] RemoveNotCondition applied\n"); return success(); } }; -/// Remove branches with same data operands and same conditional operand -struct CombineBranchesSameSign +/// When a ConditionalBranch has cond == data (or they differ only by a NOT), +/// each output carries a known boolean. Replace the condition operand of any +/// downstream branch that uses these outputs as condition with a constant +/// 0 or 1, disconnecting the upstream branch's use. +struct SimplifyKnownConditionBranch : public OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(handshake::ConditionalBranchOp condBranchOp, PatternRewriter &rewriter) const override { + Value condOperand = condBranchOp.getConditionOperand(); Value dataOperand = condBranchOp.getDataOperand(); + + // Match three cases: + // 1) cond == data (direct) + // 2) cond == not(data) (inverted) + // 3) data == not(cond) (inverted) + bool inverted = false; + if (condOperand == dataOperand) { + inverted = false; + } else { + Operation *condDef = condOperand.getDefiningOp(); + Operation *dataDef = dataOperand.getDefiningOp(); + if (isa_and_nonnull(condDef) && + condDef->getOperand(0) == dataOperand) { + inverted = true; + } else if (isa_and_nonnull(dataDef) && + dataDef->getOperand(0) == condOperand) { + inverted = true; + } else { + return failure(); + } + } + + bool changed = false; + + // For a given output of the upstream branch, replace the condition + // of all downstream branches that use it as condition with a constant. + auto replaceDownstreamCond = [&](Value branchOutput, bool outputIsTrue) { + // Runtime boolean value carried by branchOutput: + // direct: true output -> 1, false output -> 0 + // inverted: true output -> 0, false output -> 1 + bool knownCondTrue = inverted ? !outputIsTrue : outputIsTrue; + + // Collect downstream branches using branchOutput as condition + SmallVector toSimplify; + for (auto *user : branchOutput.getUsers()) { + if (auto br = dyn_cast(user)) { + if (br.getConditionOperand() == branchOutput) + toSimplify.push_back(br); + } + } + + for (auto br : toSimplify) { + rewriter.setInsertionPoint(br); + + // Create source as trigger + auto sourceOp = rewriter.create(br.getLoc()); + if (auto bbAttr = br->getAttr("handshake.bb")) + sourceOp->setAttr("handshake.bb", bbAttr); + + // Build the i1 attribute + auto i1Type = rewriter.getIntegerType(1); + auto cstAttr = rewriter.getIntegerAttr(i1Type, knownCondTrue ? 1 : 0); + + // Check if the condition operand is channelified + Type condType = branchOutput.getType(); + handshake::ConstantOp constOp; + + if (auto channelType = dyn_cast(condType)) { + // Channelified: use 4-arg constructor (loc, resultType, attr, ctrl) + // matching the pattern from the existing codebase + constOp = rewriter.create( + br.getLoc(), channelType, cstAttr, sourceOp.getResult()); + } else { + // Raw i1: use 3-arg constructor (loc, attr, ctrl) + constOp = rewriter.create( + br.getLoc(), cstAttr, sourceOp.getResult()); + } + + if (auto bbAttr = br->getAttr("handshake.bb")) + constOp->setAttr("handshake.bb", bbAttr); + + // Replace condition operand of downstream branch + br->setOperand(0, constOp.getResult()); + refreshBranchAttrsFromCondition(br, br); + + changed = true; + } + }; + + replaceDownstreamCond(condBranchOp.getTrueResult(), /*outputIsTrue=*/true); + replaceDownstreamCond(condBranchOp.getFalseResult(), + /*outputIsTrue=*/false); + + if (changed) + logLine("[HandshakeCombineSteeringLogic] SimplifyKnownConditionBranch " + "applied\n"); + return changed ? success() : failure(); + } +}; + +/// Eliminate a ConditionalBranch whose condition is a constant. +/// Short-circuit: the always-taken output is replaced with the data operand, +/// and the branch (along with its feeding constant + source) is erased. +struct EliminateConstantCondBranch + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(handshake::ConditionalBranchOp condBranchOp, + PatternRewriter &rewriter) const override { + Value condOperand = condBranchOp.getConditionOperand(); + auto constOp = + dyn_cast_or_null(condOperand.getDefiningOp()); + if (!constOp) + return failure(); - auto redundantBranches = - findRedundantBranches(condOperand, dataOperand, condBranchOp); + auto constAttr = dyn_cast(constOp.getValueAttr()); + if (!constAttr) + return failure(); - // Nothing to erase + bool condIsTrue = constAttr.getValue().getBoolValue(); + + Value takenResult = condIsTrue ? condBranchOp.getTrueResult() + : condBranchOp.getFalseResult(); + Value notTakenResult = condIsTrue ? condBranchOp.getFalseResult() + : condBranchOp.getTrueResult(); + + // Only proceed when the never-taken side has no users + if (!notTakenResult.use_empty()) + return failure(); + + logLine("[HandshakeCombineSteeringLogic] EliminateConstantCondBranch " + "applied\n"); + // Short-circuit the always-taken side + rewriter.replaceAllUsesWith(takenResult, condBranchOp.getDataOperand()); + + // Erase the branch + rewriter.eraseOp(condBranchOp); + + // Clean up the constant + source if they have no other users + if (constOp.getResult().use_empty()) { + Value trigger = constOp.getCtrl(); + rewriter.eraseOp(constOp); + if (auto sourceOp = + dyn_cast_or_null(trigger.getDefiningOp())) { + if (sourceOp.getResult().use_empty()) + rewriter.eraseOp(sourceOp); + } + } + return success(); + } +}; + +/// Match: +/// br_mux : cond_br (mux %c [d0, d1]), %data +/// br_base : cond_br %c, %data +/// +/// Rewrite br_mux into: +/// br_outer : cond_br %c, %data +/// br_inner : cond_br %x, +/// +/// The newly created outer branch is intentionally left structurally identical +/// to br_base so that CombineEquivalentBranches can fold them afterwards. +/// This rewrite is only valid when: +/// - exactly one mux input is a constant +/// - branch output emptiness matches the constant value: +/// const 1 => true unused, false used +/// const 0 => false unused, true used +/// - if the constant is mux input 0, innerBranch is fed from outer.false +/// - if the constant is mux input 1, innerBranch is fed from outer.true +struct SplitBranchWithMuxCondition + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(handshake::ConditionalBranchOp condBranchOp, + PatternRewriter &rewriter) const override { + + auto muxOp = dyn_cast_or_null( + condBranchOp.getConditionOperand().getDefiningOp()); + if (!muxOp || muxOp.getNumOperands() != 3) + return failure(); + + int constIdx = -1; + int nonConstIdx = -1; + handshake::ConstantOp constOp; + for (int idx = 0; idx < 2; ++idx) { + if (auto candidate = dyn_cast_or_null( + muxOp.getDataOperands()[idx].getDefiningOp())) { + // Checking that we have not found constants before + if (constIdx != -1) + return failure(); + constIdx = idx; + constOp = candidate; + } else { + if (nonConstIdx != -1) + return failure(); + nonConstIdx = idx; + } + } + + if (constIdx == -1 || nonConstIdx == -1) + return failure(); + + auto constAttr = dyn_cast(constOp.getValueAttr()); + if (!constAttr) + return failure(); + bool constValue = constAttr.getValue().getBoolValue(); + + Value baseCond = muxOp.getSelectOperand(); + Value dataOperand = condBranchOp.getDataOperand(); + + // Keep the rewrite profitable: the outer branch should be mergeable with an + // already existing branch on the same data and condition. + auto redundantBranches = + findRedundantBranches(baseCond, dataOperand, condBranchOp); if (redundantBranches.empty()) return failure(); - // Erase the redundant branch - for (auto br : redundantBranches) { - rewriter.replaceAllUsesWith(br.getTrueResult(), - condBranchOp.getTrueResult()); - rewriter.replaceAllUsesWith(br.getFalseResult(), - condBranchOp.getFalseResult()); - rewriter.eraseOp(br); + bool trueEmpty = condBranchOp.getTrueResult().use_empty(); + bool falseEmpty = condBranchOp.getFalseResult().use_empty(); + // Exactly one of the two outputs of the branch must be empty + if (trueEmpty == falseEmpty) + return failure(); + + // The empty output should be consistent with the value of the constant + if (constValue) { + if (!trueEmpty || falseEmpty) + return failure(); + } else { + if (trueEmpty || !falseEmpty) + return failure(); } + + Value nestedCond = muxOp.getDataOperands()[nonConstIdx]; + + rewriter.setInsertionPoint(condBranchOp); + + auto outerBranch = rewriter.create( + getConditionLocOrFallback(baseCond, condBranchOp), baseCond, + dataOperand); + inheritConditionBBOrFallback(baseCond, condBranchOp, outerBranch); + + Value outerToInner = nonConstIdx == 0 ? outerBranch.getFalseResult() + : outerBranch.getTrueResult(); + + auto innerBranch = rewriter.create( + getConditionLocOrFallback(nestedCond, condBranchOp), nestedCond, + outerToInner); + inheritConditionBBOrFallback(nestedCond, condBranchOp, innerBranch); + + logLine("[HandshakeCombineSteeringLogic] SplitBranchWithMuxCondition " + "applied\n"); + rewriter.replaceOp(condBranchOp, {innerBranch.getTrueResult(), + innerBranch.getFalseResult()}); return success(); } }; @@ -403,11 +861,17 @@ struct HandshakeCombineSteeringLogicPass config.useTopDownTraversal = true; config.enableRegionSimplification = false; RewritePatternSet patterns(ctx); - patterns.add(ctx); + patterns.add, + RemoveUnusedOp, + RemoveUnusedOp, + RemoveUnusedOp, + RemoveUnusedOp, SplitBranchWithMuxCondition, + CombineBranchesOppositeSign, CombineEquivalentNotIOps, + CombineInits, CombineMuxes, RemoveNotCondition, + SimplifyKnownConditionBranch, EliminateConstantCondBranch, + CombineEquivalentMuxes, CombineEquivalentBranches>(ctx); if (failed(applyPatternsAndFoldGreedily(mod, std::move(patterns), config))) return signalPassFailure(); }; }; -} // namespace +} // namespace \ No newline at end of file diff --git a/include/dynamatic/Support/CFG.h b/include/dynamatic/Support/CFG.h index fa61ce04d9..62f9716e18 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 @@ -195,4 +195,4 @@ bool isChannelOnCycle(mlir::Value channel); } // namespace dynamatic -#endif // DYNAMATIC_SUPPORT_CFG_H +#endif // DYNAMATIC_SUPPORT_CFG_H \ No newline at end of file diff --git a/include/dynamatic/Transforms/BufferPlacement/CFDFC.h b/include/dynamatic/Transforms/BufferPlacement/CFDFC.h index cb53985eeb..010d0e1691 100644 --- a/include/dynamatic/Transforms/BufferPlacement/CFDFC.h +++ b/include/dynamatic/Transforms/BufferPlacement/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 { @@ -58,13 +59,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 @@ -114,4 +122,4 @@ LogicalResult extractCFDFC(handshake::FuncOp funcOp, ArchSet &archs, BBSet &bbs, } // namespace buffer } // namespace dynamatic -#endif // DYNAMATIC_TRANSFORMS_BUFFERPLACEMENT_CFDFC_H +#endif // DYNAMATIC_TRANSFORMS_BUFFERPLACEMENT_CFDFC_H \ No newline at end of file diff --git a/lib/Transforms/BufferPlacement/CFDFC.cpp b/lib/Transforms/BufferPlacement/CFDFC.cpp index c5e9d1499c..7bd384e8d3 100644 --- a/lib/Transforms/BufferPlacement/CFDFC.cpp +++ b/lib/Transforms/BufferPlacement/CFDFC.cpp @@ -11,7 +11,9 @@ //===----------------------------------------------------------------------===// #include "dynamatic/Transforms/BufferPlacement/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; - // 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) { + // The basic block the operation belongs to must be selected in the CFG + // cycle currently under study + 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 (!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, @@ -417,4 +554,4 @@ LogicalResult dynamatic::buffer::extractCFDFC( } return success(); -} +} \ No newline at end of file diff --git a/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp b/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp index 91f4f1de6d..179d9ebd95 100644 --- a/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp +++ b/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp @@ -23,7 +23,6 @@ #include "dynamatic/Transforms/BufferPlacement/CFDFC.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/HandshakeMaterialize.h" @@ -35,8 +34,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; @@ -47,7 +52,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 @@ -172,8 +177,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 @@ -207,6 +211,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; @@ -227,9 +233,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 @@ -376,11 +383,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); @@ -399,6 +603,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 @@ -423,7 +646,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(); @@ -532,12 +755,6 @@ LogicalResult HandshakePlaceBuffersPass::solveBufferPlacementMILP( return solveMILP( placement, solverKind, timeout, info, timingDB, targetCP, writeTo); } - - 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"; @@ -780,4 +997,4 @@ void HandshakePlaceBuffersPass::instantiateBuffers(BufferPlacement &placement, cfdfc.channelOccupancy[channel] = 0.0; } } -} +} \ No newline at end of file diff --git a/lib/Transforms/HandshakeRewriteTerms.cpp b/lib/Transforms/HandshakeRewriteTerms.cpp index ed79c2cdf2..503b0082e0 100644 --- a/lib/Transforms/HandshakeRewriteTerms.cpp +++ b/lib/Transforms/HandshakeRewriteTerms.cpp @@ -53,10 +53,26 @@ bool isSuppress(handshake::ConditionalBranchOp condBranchOp) { } 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()); + DenseSet users; + for (Operation *user : condBranchOp.getTrueResult().getUsers()) + users.insert(user); + for (Operation *user : condBranchOp.getFalseResult().getUsers()) + users.insert(user); + return users.size(); +} + +Value stripForks(Value value) { + Operation *defOp = value.getDefiningOp(); + while (defOp != nullptr) { + if (auto forkOp = dyn_cast(defOp)) + value = forkOp.getOperand(); + else if (auto lazyForkOp = dyn_cast(defOp)) + value = lazyForkOp.getOperand(); + else + break; + defOp = value.getDefiningOp(); + } + return value; } /// Returns Operation holding a Branch, if it exists, and the its index (by @@ -265,7 +281,7 @@ struct EraseSingleInputControlMerges // Create a source operation for the constant handshake::SourceOp srcOp = rewriter.create( - cmergeOp->getLoc(), rewriter.getNoneType()); + cmergeOp->getLoc(), handshake::ControlType::get(getContext())); inheritBB(cmergeOp, srcOp); /// NOTE: Sourcing this value may cause problems with very exotic uses of @@ -274,10 +290,11 @@ struct EraseSingleInputControlMerges /// instead. // Build the attribute for the constant - Type indexResType = indexRes.getType(); + Type indexResType = + cast(indexRes.getType()).getDataType(); handshake::ConstantOp cstOp = rewriter.create( - cmergeOp.getLoc(), indexResType, - rewriter.getIntegerAttr(indexResType, 0), srcOp.getResult()); + cmergeOp.getLoc(), rewriter.getIntegerAttr(indexResType, 0), + srcOp.getResult()); inheritBB(cmergeOp, cstOp); // Replace the cmerge's index result with a constant 0 @@ -403,6 +420,174 @@ struct RemoveFloatingLoop : public OpRewritePattern { } }; +struct IfThenElsePairMatch { + unsigned firstIdx; + unsigned secondIdx; + Value firstInput; + Value secondInput; + Value foldedData; + Value condition; +}; + +bool getIfThenElsePairMatch(Value firstInput, Value secondInput, + unsigned firstIdx, unsigned secondIdx, + Operation *insertionPoint, + PatternRewriter &rewriter, + IfThenElsePairMatch &match, + bool requireSameData) { + Value strippedFirstInput = stripForks(firstInput); + Value strippedSecondInput = stripForks(secondInput); + + Operation *firstOperand = strippedFirstInput.getDefiningOp(); + Operation *secondOperand = strippedSecondInput.getDefiningOp(); + if (!isa_and_nonnull(firstOperand) || + !isa_and_nonnull(secondOperand)) + return false; + + handshake::ConditionalBranchOp firstBranchOperand = + cast(firstOperand); + handshake::ConditionalBranchOp secondBranchOperand = + cast(secondOperand); + + bool sameBranchComplement = + firstBranchOperand == secondBranchOperand && + ((firstBranchOperand.getTrueResult() == strippedFirstInput && + firstBranchOperand.getFalseResult() == strippedSecondInput) || + (firstBranchOperand.getFalseResult() == strippedFirstInput && + firstBranchOperand.getTrueResult() == strippedSecondInput)); + + if (!OPTIM_BRANCH_TO_SUPP && !sameBranchComplement) { + if ((!firstBranchOperand.getTrueResult().getUsers().empty()) || + (firstBranchOperand.getTrueResult().getUsers().empty() && + firstBranchOperand.getFalseResult().getUsers().empty())) + return false; + if ((!secondBranchOperand.getTrueResult().getUsers().empty()) || + (secondBranchOperand.getTrueResult().getUsers().empty() && + secondBranchOperand.getFalseResult().getUsers().empty())) + return false; + } + + if (!OPTIM_DISTR && !sameBranchComplement) { + if (std::distance(firstBranchOperand.getFalseResult().getUsers().begin(), + firstBranchOperand.getFalseResult().getUsers().end()) != + 1) + return false; + if (std::distance(secondBranchOperand.getFalseResult().getUsers().begin(), + secondBranchOperand.getFalseResult().getUsers().end()) != + 1) + return false; + } + + 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 (firstOriginalBranchCondition != secondOriginalBranchCondition) + return false; + + Value firstBranchData = firstBranchOperand.getDataOperand(); + Value secondBranchData = secondBranchOperand.getDataOperand(); + if (requireSameData && firstBranchData != secondBranchData) + return false; + + bool reversedFirstInput = + (firstBranchOperand.getTrueResult() == strippedFirstInput && + firstBranchCondition == firstOriginalBranchCondition) || + (firstBranchOperand.getFalseResult() == strippedFirstInput && + firstBranchCondition != firstOriginalBranchCondition); + bool reversedSecondInput = + (secondBranchOperand.getFalseResult() == strippedSecondInput && + secondBranchCondition == secondOriginalBranchCondition) || + (secondBranchOperand.getTrueResult() == strippedSecondInput && + secondBranchCondition != secondOriginalBranchCondition); + + bool needNot = reversedFirstInput && reversedSecondInput; + Value condition; + if (needNot) { + bool foundNot = false; + handshake::NotIOp existingNotOp; + for (Operation *condUser : firstOriginalBranchCondition.getUsers()) { + if (isa_and_nonnull(condUser)) { + foundNot = true; + existingNotOp = cast(condUser); + break; + } + } + + if (foundNot) { + condition = existingNotOp.getResult(); + } else { + rewriter.setInsertionPoint(insertionPoint); + handshake::NotIOp notIOp = rewriter.create( + insertionPoint->getLoc(), firstOriginalBranchCondition); + inheritBB(insertionPoint, notIOp); + condition = notIOp.getResult(); + } + } else { + condition = firstOriginalBranchCondition; + } + + match.firstIdx = firstIdx; + match.secondIdx = secondIdx; + match.firstInput = firstInput; + match.secondInput = secondInput; + match.foldedData = firstBranchData; + match.condition = condition; + return true; +} + +template +bool findIfThenElsePair(OperandRange operands, Operation *insertionPoint, + PatternRewriter &rewriter, + IfThenElsePairMatch &match, + bool requireSameData) { + for (unsigned firstIdx = 0, numOperands = operands.size(); + firstIdx < numOperands; ++firstIdx) { + for (unsigned secondIdx = firstIdx + 1; secondIdx < numOperands; + ++secondIdx) { + if (getIfThenElsePairMatch(operands[firstIdx], operands[secondIdx], + firstIdx, secondIdx, insertionPoint, rewriter, + match, requireSameData)) + return true; + } + } + return false; +} + +Value createPairSelected(Value select, unsigned untouchedIdx, + Operation *insertionPoint, + PatternRewriter &rewriter) { + rewriter.setInsertionPoint(insertionPoint); + handshake::SourceOp sourceOp = + rewriter.create(insertionPoint->getLoc()); + inheritBB(insertionPoint, sourceOp); + + Type selectDataType = cast(select.getType()).getDataType(); + handshake::ConstantOp constOp = rewriter.create( + insertionPoint->getLoc(), rewriter.getIntegerAttr(selectDataType, + untouchedIdx), + sourceOp.getResult()); + inheritBB(insertionPoint, constOp); + + handshake::CmpIOp pairSelected = rewriter.create( + insertionPoint->getLoc(), handshake::CmpIPredicate::ne, select, + constOp.getResult()); + inheritBB(insertionPoint, pairSelected); + return pairSelected.getResult(); +} + +bool sameValueIgnoringForks(Value lhs, Value rhs) { + return stripForks(lhs) == stripForks(rhs); +} + // TODO the if-then-else rewrites // Rules A // Remove redundant if-then-else structures @@ -413,80 +598,30 @@ struct RemoveBranchMergeIfThenElse LogicalResult matchAndRewrite(handshake::MergeOp mergeOp, PatternRewriter &rewriter) const override { - if (mergeOp->getNumOperands() != 2) + if (mergeOp->getNumOperands() != 2 && mergeOp->getNumOperands() != 3) 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)) + IfThenElsePairMatch match; + if (!findIfThenElsePair(mergeOp.getOperands(), mergeOp, rewriter, match, + /*requireSameData=*/true)) 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(); + if (mergeOp->getNumOperands() == 2) { + rewriter.replaceAllUsesWith(mergeOp.getResult(), + match.foldedData); + rewriter.eraseOp(mergeOp); + llvm::errs() << "\t***Rules A: remove-branch-merge-if-then-else!***\n"; + return success(); } - 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 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); + unsigned untouchedIdx = 3 - match.firstIdx - match.secondIdx; + SmallVector newMergeOperands = { + mergeOp.getOperands()[untouchedIdx], match.foldedData}; + rewriter.setInsertionPoint(mergeOp); + handshake::MergeOp newMergeOp = + rewriter.create(mergeOp.getLoc(), newMergeOperands); + inheritBB(mergeOp, newMergeOp); + rewriter.replaceOp(mergeOp, newMergeOp.getResult()); // Delegated the deletiong to such Branches through a separate function that // deletes Branches ffedng sinks on both sides @@ -522,81 +657,34 @@ struct RemoveBranchMuxIfThenElse : public OpRewritePattern { LogicalResult matchAndRewrite(handshake::MuxOp muxOp, PatternRewriter &rewriter) const override { - if (muxOp->getNumOperands() != 3) + if (muxOp.getDataOperands().size() != 2 && + muxOp.getDataOperands().size() != 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)) + IfThenElsePairMatch match; + if (!findIfThenElsePair(muxOp.getDataOperands(), muxOp, rewriter, match, + /*requireSameData=*/true)) 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(); + if (muxOp.getDataOperands().size() == 2) { + rewriter.replaceAllUsesWith(muxOp.getResult(), match.foldedData); + rewriter.eraseOp(muxOp); + llvm::errs() << "\t***Rules A: remove-branch-mux-if-then-else!***\n"; + return success(); } - 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); + unsigned untouchedIdx = 3 - match.firstIdx - match.secondIdx; + Value pairSelected = + createPairSelected(muxOp.getSelectOperand(), untouchedIdx, muxOp, + rewriter); + SmallVector newMuxOperands = { + muxOp.getDataOperands()[untouchedIdx], match.foldedData}; + rewriter.setInsertionPoint(muxOp); + handshake::MuxOp newMuxOp = rewriter.create( + muxOp.getLoc(), muxOp.getResult().getType(), pairSelected, + newMuxOperands); + inheritBB(muxOp, newMuxOp); + rewriter.replaceOp(muxOp, newMuxOp.getResult()); // Delegated the deletiong to such Branches through a separate function that // deletes Branches ffedng sinks on both sides @@ -756,7 +844,7 @@ struct ExtractLoopCondition if (l.empty()) return failure(); mlir::Value start = l.back(); - if (!isa(start.getType())) + if (!isa(start.getType())) return failure(); // Check if there is an already existing Init, i.e., a Merge fed from the @@ -777,7 +865,7 @@ struct ExtractLoopCondition // forming the cycle Type constantType = rewriter.getIntegerType(1); Value valueOfConstant = rewriter.create( - condBranchOp->getLoc(), constantType, + condBranchOp->getLoc(), rewriter.getIntegerAttr(constantType, constVal), start); // Create a new Init @@ -807,105 +895,149 @@ struct ExtractIfThenElseCondition PatternRewriter &rewriter) const override { // Pattern match fails if the cntrlMerge does not have exactly two inputs - if (cmergeOp->getNumOperands() != 2) + if (cmergeOp->getNumOperands() != 2 && cmergeOp->getNumOperands() != 3) 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)) + Value index = cmergeOp.getIndex(); + if (!hasRealUses(index)) return failure(); - handshake::ConditionalBranchOp firstBranchOperand = - cast(firstOperand); - handshake::ConditionalBranchOp secondBranchOperand = - cast(secondOperand); + IfThenElsePairMatch match; + if (findIfThenElsePair(cmergeOp.getOperands(), cmergeOp, rewriter, match, + /*requireSameData=*/false) && + cmergeOp->getNumOperands() == 2) { + // Replace the Cmerge index output with the branch condition + rewriter.replaceAllUsesWith(index, match.condition); - 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(); + llvm::errs() + << "\t***Rules B: extract-if-then-else-mux-condition!***\n"; + return success(); } - 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); + SmallVector logicalOperands; + SmallVector, 3> logicalParentOperands; + handshake::MergeOp nestedMergeOp; + unsigned nestedMergeOperandIdx = 0; + IfThenElsePairMatch nestedMergeMatch; + bool hasNestedMerge = false; + + if (cmergeOp->getNumOperands() == 2) { + for (unsigned operandIdx = 0; operandIdx < 2; ++operandIdx) { + auto mergeOperand = dyn_cast_or_null( + cmergeOp.getOperands()[operandIdx].getDefiningOp()); + if (!mergeOperand || mergeOperand->getNumOperands() != 2) + continue; + if (hasNestedMerge) + return failure(); + + nestedMergeOp = mergeOperand; + nestedMergeOperandIdx = operandIdx; + hasNestedMerge = true; + } - // If the two original conditions are not equivalent, the pattern match - // fails - if (firstOriginalBranchCondition != secondOriginalBranchCondition) - return failure(); + if (!hasNestedMerge) + return failure(); - Value index = cmergeOp.getIndex(); + unsigned directOperandIdx = 1 - nestedMergeOperandIdx; + logicalOperands.push_back(cmergeOp.getOperands()[directOperandIdx]); + logicalParentOperands.push_back(directOperandIdx); + logicalOperands.push_back(nestedMergeOp.getOperands()[0]); + logicalParentOperands.push_back(std::nullopt); + logicalOperands.push_back(nestedMergeOp.getOperands()[1]); + logicalParentOperands.push_back(std::nullopt); + + if (!findIfThenElsePair(nestedMergeOp.getOperands(), nestedMergeOp, + rewriter, nestedMergeMatch, + /*requireSameData=*/false)) + return failure(); + } else { + for (Value operand : cmergeOp.getOperands()) { + logicalOperands.push_back(operand); + logicalParentOperands.push_back(logicalParentOperands.size()); + } + } - // 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) { + if (!findIfThenElsePair(logicalOperands, cmergeOp, rewriter, match, + /*requireSameData=*/false)) + return failure(); + if (hasNestedMerge && match.firstIdx != 0 && match.secondIdx != 0) + return failure(); - // 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; - } - } + SmallVector indexMuxUsers; + SmallVector, 4> logicalMuxOperands; + for (Operation *user : llvm::make_early_inc_range(index.getUsers())) { + auto muxUser = dyn_cast(user); + if (!muxUser || muxUser.getSelectOperand() != index) + return failure(); - if (foundNot) { - cond = existingNotOp.getResult(); + SmallVector muxLogicalOperands; + if (!hasNestedMerge) { + if (muxUser.getDataOperands().size() != 3) + return failure(); + for (Value operand : muxUser.getDataOperands()) + muxLogicalOperands.push_back(operand); } else { - rewriter.setInsertionPoint(cmergeOp); - handshake::NotIOp notIOp = rewriter.create( - cmergeOp->getLoc(), firstOriginalBranchCondition); - inheritBB(cmergeOp, notIOp); - cond = notIOp.getResult(); + if (muxUser.getDataOperands().size() != 2) + return failure(); + + unsigned directOperandIdx = *logicalParentOperands.front(); + Value nestedData = muxUser.getDataOperands()[nestedMergeOperandIdx]; + auto nestedMux = dyn_cast_or_null( + nestedData.getDefiningOp()); + if (!nestedMux || nestedMux.getDataOperands().size() != 2 || + !sameValueIgnoringForks(nestedMux.getSelectOperand(), + nestedMergeMatch.condition)) + return failure(); + + muxLogicalOperands.push_back(muxUser.getDataOperands()[directOperandIdx]); + muxLogicalOperands.push_back( + nestedMux.getDataOperands()[nestedMergeMatch.firstIdx]); + muxLogicalOperands.push_back( + nestedMux.getDataOperands()[nestedMergeMatch.secondIdx]); } - } else { - cond = firstOriginalBranchCondition; + indexMuxUsers.push_back(muxUser); + logicalMuxOperands.push_back(muxLogicalOperands); } - // Replace the Cmerge index output with the branch condition - rewriter.replaceAllUsesWith(index, cond); + unsigned untouchedIdx = 3 - match.firstIdx - match.secondIdx; + SmallVector pairMergeOperands = {logicalOperands[match.firstIdx], + logicalOperands[match.secondIdx]}; + rewriter.setInsertionPoint(cmergeOp); + handshake::MergeOp pairMergeOp = rewriter.create( + cmergeOp.getLoc(), pairMergeOperands); + inheritBB(cmergeOp, pairMergeOp); + SmallVector outerCmergeOperands = {logicalOperands[untouchedIdx], + pairMergeOp.getResult()}; + handshake::ControlMergeOp outerCmergeOp = + rewriter.create(cmergeOp.getLoc(), + outerCmergeOperands); + inheritBB(cmergeOp, outerCmergeOp); + + for (auto [muxUser, muxOperands] : + llvm::zip_equal(indexMuxUsers, logicalMuxOperands)) { + SmallVector innerMuxOperands = { + muxOperands[match.firstIdx], muxOperands[match.secondIdx]}; + rewriter.setInsertionPoint(muxUser); + handshake::MuxOp innerMuxOp = rewriter.create( + muxUser.getLoc(), muxUser.getResult().getType(), match.condition, + innerMuxOperands); + inheritBB(muxUser, innerMuxOp); + + SmallVector outerMuxOperands = { + muxOperands[untouchedIdx], innerMuxOp.getResult()}; + handshake::MuxOp outerMuxOp = rewriter.create( + muxUser.getLoc(), muxUser.getResult().getType(), + outerCmergeOp.getIndex(), outerMuxOperands); + inheritBB(muxUser, outerMuxOp); + rewriter.replaceOp(muxUser, outerMuxOp.getResult()); + } + + rewriter.replaceAllUsesWith(cmergeOp.getResult(), + outerCmergeOp.getResult()); + rewriter.eraseOp(cmergeOp); + if (hasNestedMerge && nestedMergeOp->use_empty()) + rewriter.eraseOp(nestedMergeOp); llvm::errs() << "\t***Rules B: extract-if-then-else-mux-condition!***\n"; return success(); @@ -1080,7 +1212,7 @@ struct ShortenSuppressPairs Type constantType = rewriter.getIntegerType(1); Value constantVal = rewriter.create( - secondCondBranchOp->getLoc(), constantType, + secondCondBranchOp->getLoc(), rewriter.getIntegerAttr(constantType, constantValue), source); // Create a new Mux and assign its operands @@ -1354,7 +1486,7 @@ struct ShortenMuxRepeatPairs : public OpRewritePattern { rewriter.create(secondCondBranchOp->getLoc()); Type constantType = rewriter.getIntegerType(1); Value constantVal = rewriter.create( - secondCondBranchOp->getLoc(), constantType, + secondCondBranchOp->getLoc(), rewriter.getIntegerAttr(constantType, constantValue), source); // Create a new Mux and assign its operands @@ -1644,7 +1776,7 @@ struct ShortenMergeRepeatPairs : public OpRewritePattern { rewriter.create(secondCondBranchOp->getLoc()); Type constantType = rewriter.getIntegerType(1); Value constantVal = rewriter.create( - secondCondBranchOp->getLoc(), constantType, + secondCondBranchOp->getLoc(), rewriter.getIntegerAttr(constantType, constantValue), source); // Create a new Mux and assign its operands @@ -2087,7 +2219,7 @@ struct ConvertLoopMergeToMux : public OpRewritePattern { if (l.empty()) return failure(); mlir::Value start = l.back(); - if (!isa(start.getType())) + if (!isa(start.getType())) return failure(); // Check if there is an already existing INIT, i.e., a Merge fed from the @@ -2111,8 +2243,8 @@ struct ConvertLoopMergeToMux : public OpRewritePattern { Type constantType = rewriter.getIntegerType(1); rewriter.setInsertionPoint(mergeOp); Value valueOfConstant = rewriter.create( - mergeOp->getLoc(), constantType, - rewriter.getIntegerAttr(constantType, constVal), start); + mergeOp->getLoc(), rewriter.getIntegerAttr(constantType, constVal), + start); // 3rd) Add a new Merge operation to serve as the INIT ValueRange operands = {iterCond, valueOfConstant}; @@ -2150,6 +2282,7 @@ struct HandshakeRewriteTermsPass GreedyRewriteConfig config; config.useTopDownTraversal = true; config.enableRegionSimplification = false; + config.maxIterations = 100; RewritePatternSet patterns(ctx); patterns.add dynamatic::rewriteHandshakeTerms() { return std::make_unique(); -} \ No newline at end of file +} diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index 3073491876..45bd022d61 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -268,8 +268,7 @@ if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ --handshake-remove-unused-memrefs \ --handshake-minimize-cst-width --handshake-optimize-bitwidths \ - --handshake-rewrite-terms \ - --handshake-materialize="replicate-constant=true" --handshake-infer-basic-blocks \ + --handshake-materialize --handshake-infer-basic-blocks \ > "$F_HANDSHAKE_TRANSFORMED" exit_on_fail "Failed to apply transformations to handshake" \ "Applied transformations to handshake" @@ -282,6 +281,9 @@ else --handshake-remove-unused-memrefs \ --handshake-minimize-cst-width --handshake-optimize-bitwidths \ --handshake-rewrite-terms \ + --handshake-combine-steering-logic \ + --handshake-materialize --handshake-infer-basic-blocks \ + --handshake-rewrite-terms \ --handshake-materialize --handshake-infer-basic-blocks \ > "$F_HANDSHAKE_TRANSFORMED" exit_on_fail "Failed to apply transformations to handshake" \ From 9b4c55596df76166cbf27ff57149d0899b9858cc Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Fri, 1 May 2026 12:50:22 +0200 Subject: [PATCH 03/15] adding optimize-steering-rewrites flag --- tools/dynamatic/dynamatic.cpp | 7 ++++++- tools/dynamatic/scripts/compile.sh | 31 ++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/tools/dynamatic/dynamatic.cpp b/tools/dynamatic/dynamatic.cpp index 63cedd7892..0b50827eab 100644 --- a/tools/dynamatic/dynamatic.cpp +++ b/tools/dynamatic/dynamatic.cpp @@ -291,6 +291,7 @@ class Compile : public Command { static constexpr llvm::StringLiteral RIGIDIFICATION = "rigidification"; 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"; Compile(FrontendState &state) : Command("compile", @@ -319,6 +320,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"}); } CommandResult execute(CommandArguments &args) override; @@ -711,6 +714,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" || @@ -741,7 +746,7 @@ CommandResult Compile::execute(CommandArguments &args) { state.getOutputDir(), state.getKernelName(), buffers, floatToString(state.targetCP, 3), sharing, state.fpUnitsGenerator, rigidification, disableLSQ, - fastTokenDelivery, milpSolver, straightToQueue); + fastTokenDelivery, milpSolver, straightToQueue, optimizeSteeringRewrite); } CommandResult WriteHDL::execute(CommandArguments &args) { diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index 45bd022d61..0eeb542ec0 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -20,6 +20,7 @@ DISABLE_LSQ=${10} FAST_TOKEN_DELIVERY=${11} MILP_SOLVER=${12} STRAIGHT_TO_QUEUE=${13} +OPTIMIZE_STEERING_REWRITES=${14} LLVM=$DYNAMATIC_DIR/llvm-project DYNAMATIC_BINS=$DYNAMATIC_DIR/bin @@ -55,6 +56,7 @@ 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_MEM="$COMP_DIR/handshake_mem.mlir" F_HW="$COMP_DIR/hw.mlir" F_FREQUENCIES="$COMP_DIR/frequencies.csv" @@ -263,21 +265,22 @@ if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then exit_on_fail "Failed to apply Straight to the Queue" "Applied Straight to the Queue" F_HANDSHAKE=$F_HANDSHAKE_SQ - - # handshake transformations - "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ - --handshake-remove-unused-memrefs \ - --handshake-minimize-cst-width --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_MEM=$F_HANDSHAKE_SQ + else # handshake transformations "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ --handshake-analyze-lsq-usage --handshake-replace-memory-interfaces \ + > "$F_HANDSHAKE_MEM" + exit_on_fail "Failed to apply LSQ transformations" "Applied LSQ transformations" +fi + +if [[ $OPTIMIZE_STEERING_REWRITES -ne 0 ]]; then + + echo_info "Optimize steering rewrites enabled" + + "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE_MEM" \ --handshake-remove-unused-memrefs \ --handshake-minimize-cst-width --handshake-optimize-bitwidths \ --handshake-rewrite-terms \ @@ -288,6 +291,14 @@ else > "$F_HANDSHAKE_TRANSFORMED" exit_on_fail "Failed to apply transformations to handshake" \ "Applied transformations to handshake" +else # --handshake-combine-steering-logic ???????TODO + "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE_MEM" \ + --handshake-remove-unused-memrefs \ + --handshake-minimize-cst-width --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" fi # Credit-based sharing From b2f5d60d302f5677a1574a5b1ba63419efd0c385 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Wed, 3 Jun 2026 16:05:08 +0200 Subject: [PATCH 04/15] Fix on handshake rewrite --- lib/Transforms/HandshakeRewriteTerms.cpp | 631 +++++++++-------------- 1 file changed, 249 insertions(+), 382 deletions(-) diff --git a/lib/Transforms/HandshakeRewriteTerms.cpp b/lib/Transforms/HandshakeRewriteTerms.cpp index 503b0082e0..bc222a65ad 100644 --- a/lib/Transforms/HandshakeRewriteTerms.cpp +++ b/lib/Transforms/HandshakeRewriteTerms.cpp @@ -53,26 +53,10 @@ bool isSuppress(handshake::ConditionalBranchOp condBranchOp) { } int returnTotalCondBranchUsers(handshake::ConditionalBranchOp condBranchOp) { - DenseSet users; - for (Operation *user : condBranchOp.getTrueResult().getUsers()) - users.insert(user); - for (Operation *user : condBranchOp.getFalseResult().getUsers()) - users.insert(user); - return users.size(); -} - -Value stripForks(Value value) { - Operation *defOp = value.getDefiningOp(); - while (defOp != nullptr) { - if (auto forkOp = dyn_cast(defOp)) - value = forkOp.getOperand(); - else if (auto lazyForkOp = dyn_cast(defOp)) - value = lazyForkOp.getOperand(); - else - break; - defOp = value.getDefiningOp(); - } - return value; + 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 @@ -281,7 +265,7 @@ struct EraseSingleInputControlMerges // Create a source operation for the constant handshake::SourceOp srcOp = rewriter.create( - cmergeOp->getLoc(), handshake::ControlType::get(getContext())); + cmergeOp->getLoc(), rewriter.getNoneType()); inheritBB(cmergeOp, srcOp); /// NOTE: Sourcing this value may cause problems with very exotic uses of @@ -290,11 +274,10 @@ struct EraseSingleInputControlMerges /// instead. // Build the attribute for the constant - Type indexResType = - cast(indexRes.getType()).getDataType(); + Type indexResType = indexRes.getType(); handshake::ConstantOp cstOp = rewriter.create( - cmergeOp.getLoc(), rewriter.getIntegerAttr(indexResType, 0), - srcOp.getResult()); + cmergeOp.getLoc(), indexResType, + rewriter.getIntegerAttr(indexResType, 0), srcOp.getResult()); inheritBB(cmergeOp, cstOp); // Replace the cmerge's index result with a constant 0 @@ -420,174 +403,6 @@ struct RemoveFloatingLoop : public OpRewritePattern { } }; -struct IfThenElsePairMatch { - unsigned firstIdx; - unsigned secondIdx; - Value firstInput; - Value secondInput; - Value foldedData; - Value condition; -}; - -bool getIfThenElsePairMatch(Value firstInput, Value secondInput, - unsigned firstIdx, unsigned secondIdx, - Operation *insertionPoint, - PatternRewriter &rewriter, - IfThenElsePairMatch &match, - bool requireSameData) { - Value strippedFirstInput = stripForks(firstInput); - Value strippedSecondInput = stripForks(secondInput); - - Operation *firstOperand = strippedFirstInput.getDefiningOp(); - Operation *secondOperand = strippedSecondInput.getDefiningOp(); - if (!isa_and_nonnull(firstOperand) || - !isa_and_nonnull(secondOperand)) - return false; - - handshake::ConditionalBranchOp firstBranchOperand = - cast(firstOperand); - handshake::ConditionalBranchOp secondBranchOperand = - cast(secondOperand); - - bool sameBranchComplement = - firstBranchOperand == secondBranchOperand && - ((firstBranchOperand.getTrueResult() == strippedFirstInput && - firstBranchOperand.getFalseResult() == strippedSecondInput) || - (firstBranchOperand.getFalseResult() == strippedFirstInput && - firstBranchOperand.getTrueResult() == strippedSecondInput)); - - if (!OPTIM_BRANCH_TO_SUPP && !sameBranchComplement) { - if ((!firstBranchOperand.getTrueResult().getUsers().empty()) || - (firstBranchOperand.getTrueResult().getUsers().empty() && - firstBranchOperand.getFalseResult().getUsers().empty())) - return false; - if ((!secondBranchOperand.getTrueResult().getUsers().empty()) || - (secondBranchOperand.getTrueResult().getUsers().empty() && - secondBranchOperand.getFalseResult().getUsers().empty())) - return false; - } - - if (!OPTIM_DISTR && !sameBranchComplement) { - if (std::distance(firstBranchOperand.getFalseResult().getUsers().begin(), - firstBranchOperand.getFalseResult().getUsers().end()) != - 1) - return false; - if (std::distance(secondBranchOperand.getFalseResult().getUsers().begin(), - secondBranchOperand.getFalseResult().getUsers().end()) != - 1) - return false; - } - - 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 (firstOriginalBranchCondition != secondOriginalBranchCondition) - return false; - - Value firstBranchData = firstBranchOperand.getDataOperand(); - Value secondBranchData = secondBranchOperand.getDataOperand(); - if (requireSameData && firstBranchData != secondBranchData) - return false; - - bool reversedFirstInput = - (firstBranchOperand.getTrueResult() == strippedFirstInput && - firstBranchCondition == firstOriginalBranchCondition) || - (firstBranchOperand.getFalseResult() == strippedFirstInput && - firstBranchCondition != firstOriginalBranchCondition); - bool reversedSecondInput = - (secondBranchOperand.getFalseResult() == strippedSecondInput && - secondBranchCondition == secondOriginalBranchCondition) || - (secondBranchOperand.getTrueResult() == strippedSecondInput && - secondBranchCondition != secondOriginalBranchCondition); - - bool needNot = reversedFirstInput && reversedSecondInput; - Value condition; - if (needNot) { - bool foundNot = false; - handshake::NotIOp existingNotOp; - for (Operation *condUser : firstOriginalBranchCondition.getUsers()) { - if (isa_and_nonnull(condUser)) { - foundNot = true; - existingNotOp = cast(condUser); - break; - } - } - - if (foundNot) { - condition = existingNotOp.getResult(); - } else { - rewriter.setInsertionPoint(insertionPoint); - handshake::NotIOp notIOp = rewriter.create( - insertionPoint->getLoc(), firstOriginalBranchCondition); - inheritBB(insertionPoint, notIOp); - condition = notIOp.getResult(); - } - } else { - condition = firstOriginalBranchCondition; - } - - match.firstIdx = firstIdx; - match.secondIdx = secondIdx; - match.firstInput = firstInput; - match.secondInput = secondInput; - match.foldedData = firstBranchData; - match.condition = condition; - return true; -} - -template -bool findIfThenElsePair(OperandRange operands, Operation *insertionPoint, - PatternRewriter &rewriter, - IfThenElsePairMatch &match, - bool requireSameData) { - for (unsigned firstIdx = 0, numOperands = operands.size(); - firstIdx < numOperands; ++firstIdx) { - for (unsigned secondIdx = firstIdx + 1; secondIdx < numOperands; - ++secondIdx) { - if (getIfThenElsePairMatch(operands[firstIdx], operands[secondIdx], - firstIdx, secondIdx, insertionPoint, rewriter, - match, requireSameData)) - return true; - } - } - return false; -} - -Value createPairSelected(Value select, unsigned untouchedIdx, - Operation *insertionPoint, - PatternRewriter &rewriter) { - rewriter.setInsertionPoint(insertionPoint); - handshake::SourceOp sourceOp = - rewriter.create(insertionPoint->getLoc()); - inheritBB(insertionPoint, sourceOp); - - Type selectDataType = cast(select.getType()).getDataType(); - handshake::ConstantOp constOp = rewriter.create( - insertionPoint->getLoc(), rewriter.getIntegerAttr(selectDataType, - untouchedIdx), - sourceOp.getResult()); - inheritBB(insertionPoint, constOp); - - handshake::CmpIOp pairSelected = rewriter.create( - insertionPoint->getLoc(), handshake::CmpIPredicate::ne, select, - constOp.getResult()); - inheritBB(insertionPoint, pairSelected); - return pairSelected.getResult(); -} - -bool sameValueIgnoringForks(Value lhs, Value rhs) { - return stripForks(lhs) == stripForks(rhs); -} - // TODO the if-then-else rewrites // Rules A // Remove redundant if-then-else structures @@ -598,30 +413,70 @@ struct RemoveBranchMergeIfThenElse LogicalResult matchAndRewrite(handshake::MergeOp mergeOp, PatternRewriter &rewriter) const override { - if (mergeOp->getNumOperands() != 2 && mergeOp->getNumOperands() != 3) + if (mergeOp->getNumOperands() != 2) return failure(); - IfThenElsePairMatch match; - if (!findIfThenElsePair(mergeOp.getOperands(), mergeOp, rewriter, match, - /*requireSameData=*/true)) + // 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(); - if (mergeOp->getNumOperands() == 2) { - rewriter.replaceAllUsesWith(mergeOp.getResult(), - match.foldedData); - rewriter.eraseOp(mergeOp); - llvm::errs() << "\t***Rules A: remove-branch-merge-if-then-else!***\n"; - return success(); + 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(); } - unsigned untouchedIdx = 3 - match.firstIdx - match.secondIdx; - SmallVector newMergeOperands = { - mergeOp.getOperands()[untouchedIdx], match.foldedData}; - rewriter.setInsertionPoint(mergeOp); - handshake::MergeOp newMergeOp = - rewriter.create(mergeOp.getLoc(), newMergeOperands); - inheritBB(mergeOp, newMergeOp); - rewriter.replaceOp(mergeOp, newMergeOp.getResult()); + 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 @@ -657,34 +512,82 @@ struct RemoveBranchMuxIfThenElse : public OpRewritePattern { LogicalResult matchAndRewrite(handshake::MuxOp muxOp, PatternRewriter &rewriter) const override { - if (muxOp.getDataOperands().size() != 2 && - muxOp.getDataOperands().size() != 3) + if (muxOp->getNumOperands() != 3) return failure(); - IfThenElsePairMatch match; - if (!findIfThenElsePair(muxOp.getDataOperands(), muxOp, rewriter, match, - /*requireSameData=*/true)) + // 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(); - if (muxOp.getDataOperands().size() == 2) { - rewriter.replaceAllUsesWith(muxOp.getResult(), match.foldedData); - rewriter.eraseOp(muxOp); - llvm::errs() << "\t***Rules A: remove-branch-mux-if-then-else!***\n"; - return success(); + 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(); } - unsigned untouchedIdx = 3 - match.firstIdx - match.secondIdx; - Value pairSelected = - createPairSelected(muxOp.getSelectOperand(), untouchedIdx, muxOp, - rewriter); - SmallVector newMuxOperands = { - muxOp.getDataOperands()[untouchedIdx], match.foldedData}; - rewriter.setInsertionPoint(muxOp); - handshake::MuxOp newMuxOp = rewriter.create( - muxOp.getLoc(), muxOp.getResult().getType(), pairSelected, - newMuxOperands); - inheritBB(muxOp, newMuxOp); - rewriter.replaceOp(muxOp, newMuxOp.getResult()); + 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 @@ -823,17 +726,6 @@ struct ExtractLoopCondition foundNot = true; existingNotOp = cast(potentialNotOp); } - if (needNot) { - if (foundNot) - condition = existingNotOp.getResult(); - else { - rewriter.setInsertionPoint(condBranchOp); - handshake::NotIOp notIOp = rewriter.create( - condBranchOp->getLoc(), condition); - inheritBB(notIOp, condBranchOp); - condition = notIOp.getResult(); - } - } // Identify the value of the Init token int constVal = outsideInputIdx; @@ -844,9 +736,21 @@ struct ExtractLoopCondition if (l.empty()) return failure(); mlir::Value start = l.back(); - if (!isa(start.getType())) + 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; @@ -895,149 +799,106 @@ struct ExtractIfThenElseCondition PatternRewriter &rewriter) const override { // Pattern match fails if the cntrlMerge does not have exactly two inputs - if (cmergeOp->getNumOperands() != 2 && cmergeOp->getNumOperands() != 3) + if (cmergeOp->getNumOperands() != 2) return failure(); - Value index = cmergeOp.getIndex(); - if (!hasRealUses(index)) + // 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(); - IfThenElsePairMatch match; - if (findIfThenElsePair(cmergeOp.getOperands(), cmergeOp, rewriter, match, - /*requireSameData=*/false) && - cmergeOp->getNumOperands() == 2) { - // Replace the Cmerge index output with the branch condition - rewriter.replaceAllUsesWith(index, match.condition); - - llvm::errs() - << "\t***Rules B: extract-if-then-else-mux-condition!***\n"; - return success(); - } - - SmallVector logicalOperands; - SmallVector, 3> logicalParentOperands; - handshake::MergeOp nestedMergeOp; - unsigned nestedMergeOperandIdx = 0; - IfThenElsePairMatch nestedMergeMatch; - bool hasNestedMerge = false; - - if (cmergeOp->getNumOperands() == 2) { - for (unsigned operandIdx = 0; operandIdx < 2; ++operandIdx) { - auto mergeOperand = dyn_cast_or_null( - cmergeOp.getOperands()[operandIdx].getDefiningOp()); - if (!mergeOperand || mergeOperand->getNumOperands() != 2) - continue; - if (hasNestedMerge) - return failure(); - - nestedMergeOp = mergeOperand; - nestedMergeOperandIdx = operandIdx; - hasNestedMerge = true; - } + handshake::ConditionalBranchOp firstBranchOperand = + cast(firstOperand); + handshake::ConditionalBranchOp secondBranchOperand = + cast(secondOperand); - if (!hasNestedMerge) + 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(); - - unsigned directOperandIdx = 1 - nestedMergeOperandIdx; - logicalOperands.push_back(cmergeOp.getOperands()[directOperandIdx]); - logicalParentOperands.push_back(directOperandIdx); - logicalOperands.push_back(nestedMergeOp.getOperands()[0]); - logicalParentOperands.push_back(std::nullopt); - logicalOperands.push_back(nestedMergeOp.getOperands()[1]); - logicalParentOperands.push_back(std::nullopt); - - if (!findIfThenElsePair(nestedMergeOp.getOperands(), nestedMergeOp, - rewriter, nestedMergeMatch, - /*requireSameData=*/false)) + // The secondBranchOperand has to be a suppress; otherwise, + // the pattern match fails + if (!secondBranchOperand.getTrueResult().getUsers().empty() || + secondBranchOperand.getFalseResult().getUsers().empty()) return failure(); - } else { - for (Value operand : cmergeOp.getOperands()) { - logicalOperands.push_back(operand); - logicalParentOperands.push_back(logicalParentOperands.size()); - } } - if (!findIfThenElsePair(logicalOperands, cmergeOp, rewriter, match, - /*requireSameData=*/false)) - return failure(); - if (hasNestedMerge && match.firstIdx != 0 && match.secondIdx != 0) + 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(); - SmallVector indexMuxUsers; - SmallVector, 4> logicalMuxOperands; - for (Operation *user : llvm::make_early_inc_range(index.getUsers())) { - auto muxUser = dyn_cast(user); - if (!muxUser || muxUser.getSelectOperand() != index) - return failure(); + Value index = cmergeOp.getIndex(); - SmallVector muxLogicalOperands; - if (!hasNestedMerge) { - if (muxUser.getDataOperands().size() != 3) - return failure(); - for (Value operand : muxUser.getDataOperands()) - muxLogicalOperands.push_back(operand); - } else { - if (muxUser.getDataOperands().size() != 2) - return failure(); - - unsigned directOperandIdx = *logicalParentOperands.front(); - Value nestedData = muxUser.getDataOperands()[nestedMergeOperandIdx]; - auto nestedMux = dyn_cast_or_null( - nestedData.getDefiningOp()); - if (!nestedMux || nestedMux.getDataOperands().size() != 2 || - !sameValueIgnoringForks(nestedMux.getSelectOperand(), - nestedMergeMatch.condition)) - return failure(); - - muxLogicalOperands.push_back(muxUser.getDataOperands()[directOperandIdx]); - muxLogicalOperands.push_back( - nestedMux.getDataOperands()[nestedMergeMatch.firstIdx]); - muxLogicalOperands.push_back( - nestedMux.getDataOperands()[nestedMergeMatch.secondIdx]); + // 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; + } } - indexMuxUsers.push_back(muxUser); - logicalMuxOperands.push_back(muxLogicalOperands); - } + if (foundNot) { + cond = existingNotOp.getResult(); + } else { + rewriter.setInsertionPoint(cmergeOp); + handshake::NotIOp notIOp = rewriter.create( + cmergeOp->getLoc(), firstOriginalBranchCondition); + inheritBB(cmergeOp, notIOp); + cond = notIOp.getResult(); + } - unsigned untouchedIdx = 3 - match.firstIdx - match.secondIdx; - SmallVector pairMergeOperands = {logicalOperands[match.firstIdx], - logicalOperands[match.secondIdx]}; - rewriter.setInsertionPoint(cmergeOp); - handshake::MergeOp pairMergeOp = rewriter.create( - cmergeOp.getLoc(), pairMergeOperands); - inheritBB(cmergeOp, pairMergeOp); - SmallVector outerCmergeOperands = {logicalOperands[untouchedIdx], - pairMergeOp.getResult()}; - handshake::ControlMergeOp outerCmergeOp = - rewriter.create(cmergeOp.getLoc(), - outerCmergeOperands); - inheritBB(cmergeOp, outerCmergeOp); - - for (auto [muxUser, muxOperands] : - llvm::zip_equal(indexMuxUsers, logicalMuxOperands)) { - SmallVector innerMuxOperands = { - muxOperands[match.firstIdx], muxOperands[match.secondIdx]}; - rewriter.setInsertionPoint(muxUser); - handshake::MuxOp innerMuxOp = rewriter.create( - muxUser.getLoc(), muxUser.getResult().getType(), match.condition, - innerMuxOperands); - inheritBB(muxUser, innerMuxOp); - - SmallVector outerMuxOperands = { - muxOperands[untouchedIdx], innerMuxOp.getResult()}; - handshake::MuxOp outerMuxOp = rewriter.create( - muxUser.getLoc(), muxUser.getResult().getType(), - outerCmergeOp.getIndex(), outerMuxOperands); - inheritBB(muxUser, outerMuxOp); - rewriter.replaceOp(muxUser, outerMuxOp.getResult()); + } else { + cond = firstOriginalBranchCondition; } - rewriter.replaceAllUsesWith(cmergeOp.getResult(), - outerCmergeOp.getResult()); - rewriter.eraseOp(cmergeOp); - if (hasNestedMerge && nestedMergeOp->use_empty()) - rewriter.eraseOp(nestedMergeOp); + // 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(); @@ -1212,7 +1073,7 @@ struct ShortenSuppressPairs Type constantType = rewriter.getIntegerType(1); Value constantVal = rewriter.create( - secondCondBranchOp->getLoc(), + secondCondBranchOp->getLoc(), constantType, rewriter.getIntegerAttr(constantType, constantValue), source); // Create a new Mux and assign its operands @@ -1230,7 +1091,9 @@ struct ShortenSuppressPairs } rewriter.setInsertionPoint(secondCondBranchOp); handshake::MuxOp mux = rewriter.create( - secondCondBranchOp->getLoc(), muxOperands[0].getType(), condBr1, muxOperands); //Zeinab comment: adding muxOperands[0].getType() to 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 @@ -1486,7 +1349,7 @@ struct ShortenMuxRepeatPairs : public OpRewritePattern { rewriter.create(secondCondBranchOp->getLoc()); Type constantType = rewriter.getIntegerType(1); Value constantVal = rewriter.create( - secondCondBranchOp->getLoc(), + secondCondBranchOp->getLoc(), constantType, rewriter.getIntegerAttr(constantType, constantValue), source); // Create a new Mux and assign its operands @@ -1506,7 +1369,8 @@ struct ShortenMuxRepeatPairs : public OpRewritePattern { } rewriter.setInsertionPoint(secondCondBranchOp); handshake::MuxOp mux = rewriter.create( - secondCondBranchOp->getLoc(), muxOperands[0].getType(), condBr2, muxOperands); + secondCondBranchOp->getLoc(), muxOperands[0].getType(), condBr2, + muxOperands); inheritBB(secondCondBranchOp, mux); Value muxResult = mux.getResult(); @@ -1776,7 +1640,7 @@ struct ShortenMergeRepeatPairs : public OpRewritePattern { rewriter.create(secondCondBranchOp->getLoc()); Type constantType = rewriter.getIntegerType(1); Value constantVal = rewriter.create( - secondCondBranchOp->getLoc(), + secondCondBranchOp->getLoc(), constantType, rewriter.getIntegerAttr(constantType, constantValue), source); // Create a new Mux and assign its operands @@ -1796,7 +1660,8 @@ struct ShortenMergeRepeatPairs : public OpRewritePattern { } rewriter.setInsertionPoint(secondCondBranchOp); handshake::MuxOp mux = rewriter.create( - secondCondBranchOp->getLoc(), muxOperands[0].getType(), condBr2, muxOperands); + secondCondBranchOp->getLoc(), muxOperands[0].getType(), condBr2, + muxOperands); inheritBB(secondCondBranchOp, mux); //////////////////////////////////////// @@ -2043,7 +1908,8 @@ struct DistributeRepeats : public OpRewritePattern { Operation *newMuxOrMergeOp; if (isMux) newMuxOrMergeOp = rewriter.create( - oldMuxOrMergeOp->getLoc(), newMergeOrMuxOperands[0].getType(), muxSel, newMergeOrMuxOperands); + oldMuxOrMergeOp->getLoc(), newMergeOrMuxOperands[0].getType(), + muxSel, newMergeOrMuxOperands); else newMuxOrMergeOp = rewriter.create( oldMuxOrMergeOp->getLoc(), newMergeOrMuxOperands); @@ -2219,7 +2085,7 @@ struct ConvertLoopMergeToMux : public OpRewritePattern { if (l.empty()) return failure(); mlir::Value start = l.back(); - if (!isa(start.getType())) + if (!isa(start.getType())) return failure(); // Check if there is an already existing INIT, i.e., a Merge fed from the @@ -2259,7 +2125,8 @@ struct ConvertLoopMergeToMux : public OpRewritePattern { // 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()); + mergeOp.getLoc(), mergeOp->getOperands()[0].getType(), muxSel, + mergeOp->getOperands()); rewriter.replaceOp(mergeOp, newMuxOp); inheritBB(mergeOp, newMuxOp); @@ -2295,8 +2162,8 @@ struct HandshakeRewriteTermsPass ExtractIfThenElseCondition, ExtractLoopCondition, RemoveBranchMergeIfThenElse, RemoveBranchMuxIfThenElse, EliminateRedundantLoop, - EliminateRedundantLoop, ConvertLoopMergeToMux - /*ShortenSuppressPairs, + EliminateRedundantLoop, ConvertLoopMergeToMux/*, + ShortenSuppressPairs , ShortenMuxRepeatPairs*/>(ctx); if (failed(applyPatternsAndFoldGreedily(mod, std::move(patterns), config))) From b62113ddc3c5b974072b93702d7b89a294eb47df Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Tue, 16 Jun 2026 15:28:34 +0200 Subject: [PATCH 05/15] default compile.sh --- tools/dynamatic/scripts/compile.sh | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index acdbe32de9..ab7fcc6f83 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -284,34 +284,21 @@ if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then exit_on_fail "Failed to apply Straight to the Queue" "Applied Straight to the Queue" F_HANDSHAKE=$F_HANDSHAKE_SQ - F_HANDSHAKE_MEM=$F_HANDSHAKE_SQ - -else # handshake transformations "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ - --handshake-analyze-lsq-usage --handshake-replace-memory-interfaces \ - > "$F_HANDSHAKE_MEM" - exit_on_fail "Failed to apply LSQ transformations" "Applied LSQ transformations" -fi - -if [[ $OPTIMIZE_STEERING_REWRITES -ne 0 ]]; then - - echo_info "Optimize steering rewrites enabled" - - "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE_MEM" \ --handshake-remove-unused-memrefs \ - --handshake-minimize-cst-width --handshake-optimize-bitwidths \ - --handshake-rewrite-terms \ - --handshake-combine-steering-logic \ - --handshake-materialize --handshake-infer-basic-blocks \ - --handshake-rewrite-terms \ - --handshake-materialize --handshake-infer-basic-blocks \ + --handshake-optimize-bitwidths \ + --handshake-materialize="replicate-constant=true" --handshake-infer-basic-blocks \ > "$F_HANDSHAKE_TRANSFORMED" exit_on_fail "Failed to apply transformations to handshake" \ "Applied transformations to handshake" -else # --handshake-combine-steering-logic ???????TODO - "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE_MEM" \ + +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 \ From 65e66ef767c4491442cd3dda82efb98523e1fcd7 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Tue, 16 Jun 2026 15:29:06 +0200 Subject: [PATCH 06/15] unconditional -combine-steering-logic --- tools/dynamatic/scripts/compile.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index ab7fcc6f83..39a382ad7b 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -301,6 +301,10 @@ else --handshake-deactivate-mem-dependencies --handshake-replace-memory-interfaces \ --handshake-remove-unused-memrefs \ --handshake-optimize-bitwidths \ + --handshake-rewrite-terms \ + --handshake-combine-steering-logic \ + --handshake-materialize --handshake-infer-basic-blocks \ + --handshake-rewrite-terms \ --handshake-materialize --handshake-infer-basic-blocks \ > "$F_HANDSHAKE_TRANSFORMED" exit_on_fail "Failed to apply transformations to handshake" \ From 24cd3f7d9080ae3d022c191dbc2190cfe20c2bb0 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Tue, 16 Jun 2026 17:50:59 +0200 Subject: [PATCH 07/15] Move rewrite terms pass to experimental --- .../Transforms/HandshakeRewriteTerms.h | 10 ++++++---- .../include/experimental/Transforms/Passes.h | 2 ++ .../include/experimental/Transforms/Passes.td | 12 ++++++++++++ experimental/lib/Transforms/CMakeLists.txt | 1 + .../lib}/Transforms/HandshakeRewriteTerms.cpp | 7 ++++--- .../test}/Transforms/handshake-rewrite-terms.mlir | 0 include/dynamatic/Transforms/Passes.h | 2 -- include/dynamatic/Transforms/Passes.td | 13 ------------- lib/Transforms/CMakeLists.txt | 1 - tools/dynamatic/scripts/compile.sh | 1 - 10 files changed, 25 insertions(+), 24 deletions(-) rename {include/dynamatic => experimental/include/experimental}/Transforms/HandshakeRewriteTerms.h (77%) rename {lib => experimental/lib}/Transforms/HandshakeRewriteTerms.cpp (99%) rename {test => experimental/test}/Transforms/handshake-rewrite-terms.mlir (100%) diff --git a/include/dynamatic/Transforms/HandshakeRewriteTerms.h b/experimental/include/experimental/Transforms/HandshakeRewriteTerms.h similarity index 77% rename from include/dynamatic/Transforms/HandshakeRewriteTerms.h rename to experimental/include/experimental/Transforms/HandshakeRewriteTerms.h index f83c4cad5e..d62e60582b 100644 --- a/include/dynamatic/Transforms/HandshakeRewriteTerms.h +++ b/experimental/include/experimental/Transforms/HandshakeRewriteTerms.h @@ -11,8 +11,8 @@ // //===----------------------------------------------------------------------===// -#ifndef DYNAMATIC_TRANSFORMS_HANDSHAKEREWRITETERMS_H -#define DYNAMATIC_TRANSFORMS_HANDSHAKEREWRITETERMS_H +#ifndef EXPERIMENTAL_TRANSFORMS_HANDSHAKEREWRITETERMS_H +#define EXPERIMENTAL_TRANSFORMS_HANDSHAKEREWRITETERMS_H #include "dynamatic/Support/DynamaticPass.h" #include "dynamatic/Support/LLVM.h" @@ -21,13 +21,15 @@ #include "mlir/Pass/Pass.h" namespace dynamatic { +namespace experimental { #define GEN_PASS_DECL_HANDSHAKEREWRITETERMS #define GEN_PASS_DEF_HANDSHAKEREWRITETERMS -#include "dynamatic/Transforms/Passes.h.inc" +#include "experimental/Transforms/Passes.h.inc" std::unique_ptr rewriteHandshakeTerms(); +} // namespace experimental } // namespace dynamatic -#endif // DYNAMATIC_TRANSFORMS_HANDSHAKEREWRITETERMS_H +#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 fcb5603220..779404f657 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 11e51e911f..72014343ee 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/lib/Transforms/HandshakeRewriteTerms.cpp b/experimental/lib/Transforms/HandshakeRewriteTerms.cpp similarity index 99% rename from lib/Transforms/HandshakeRewriteTerms.cpp rename to experimental/lib/Transforms/HandshakeRewriteTerms.cpp index bc222a65ad..5bfd969097 100644 --- a/lib/Transforms/HandshakeRewriteTerms.cpp +++ b/experimental/lib/Transforms/HandshakeRewriteTerms.cpp @@ -13,11 +13,11 @@ // circuit. //===----------------------------------------------------------------------===// -#include "dynamatic/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 "experimental/Transforms/HandshakeRewriteTerms.h" #include "mlir/IR/AsmState.h" #include "mlir/IR/Diagnostics.h" #include "mlir/IR/Operation.h" @@ -2139,7 +2139,7 @@ struct ConvertLoopMergeToMux : public OpRewritePattern { /// Simple driver for the Handshake Rewrite Terms pass, based on a greedy /// pattern rewriter. struct HandshakeRewriteTermsPass - : public dynamatic::impl::HandshakeRewriteTermsBase< + : public dynamatic::experimental::impl::HandshakeRewriteTermsBase< HandshakeRewriteTermsPass> { void runDynamaticPass() override { @@ -2172,6 +2172,7 @@ struct HandshakeRewriteTermsPass }; }; // namespace -std::unique_ptr dynamatic::rewriteHandshakeTerms() { +std::unique_ptr +dynamatic::experimental::rewriteHandshakeTerms() { return std::make_unique(); } diff --git a/test/Transforms/handshake-rewrite-terms.mlir b/experimental/test/Transforms/handshake-rewrite-terms.mlir similarity index 100% rename from test/Transforms/handshake-rewrite-terms.mlir rename to experimental/test/Transforms/handshake-rewrite-terms.mlir diff --git a/include/dynamatic/Transforms/Passes.h b/include/dynamatic/Transforms/Passes.h index 201a2c713a..5d9b61b931 100644 --- a/include/dynamatic/Transforms/Passes.h +++ b/include/dynamatic/Transforms/Passes.h @@ -20,8 +20,6 @@ #include "mlir/IR/DialectRegistry.h" #include "mlir/Pass/Pass.h" -#include "dynamatic/Transforms/HandshakeRewriteTerms.h" - namespace dynamatic { /// Generate the code for registering passes. diff --git a/include/dynamatic/Transforms/Passes.td b/include/dynamatic/Transforms/Passes.td index daeccf4f38..9da4ca4229 100644 --- a/include/dynamatic/Transforms/Passes.td +++ b/include/dynamatic/Transforms/Passes.td @@ -105,19 +105,6 @@ def FuncSetArgNames : DynamaticPass<"func-set-arg-names"> { //===----------------------------------------------------------------------===// - -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::rewriteHandshakeTerms()"; -} - def HandshakeDeactivateMemDependencies : Pass<"handshake-deactivate-mem-dependencies", "mlir::ModuleOp"> { let summary = "Deactivates memory dependences that do not need to be enforced"; let description = [{ diff --git a/lib/Transforms/CMakeLists.txt b/lib/Transforms/CMakeLists.txt index 9d2417d09b..ce2b46dadb 100644 --- a/lib/Transforms/CMakeLists.txt +++ b/lib/Transforms/CMakeLists.txt @@ -9,7 +9,6 @@ add_dynamatic_library(DynamaticTransforms HandshakeCanonicalize.cpp HandshakeDeactivateMemDependencies.cpp HandshakeHoistExtInstances.cpp - HandshakeRewriteTerms.cpp HandshakeMaterialize.cpp HandshakeOptimizeBitwidths.cpp HandshakeInferBasicBlocks.cpp diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index 39a382ad7b..82998aec93 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -62,7 +62,6 @@ 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_MEM="$COMP_DIR/handshake_mem.mlir" F_HW="$COMP_DIR/hw.mlir" F_FREQUENCIES="$COMP_DIR/frequencies.csv" From 1313ec8f83de6867424804b9763c4e447f03b384 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Wed, 17 Jun 2026 17:16:10 +0200 Subject: [PATCH 08/15] Add steering rewrite flag to compile flow --- tools/dynamatic/scripts/compile.sh | 50 ++++++++++++++++-------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index 82998aec93..b4af7eb860 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -270,6 +270,9 @@ else exit_on_fail "Failed to compile cf to handshake" "Compiled cf to handshake" fi +MEMORY_INTERFACE_ARGS=() +MATERIALIZE_ARGS=(--handshake-materialize) + if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then echo_info "Using FPGA'23 for LSQ connection" @@ -283,33 +286,34 @@ if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then exit_on_fail "Failed to apply Straight to the Queue" "Applied Straight to the Queue" F_HANDSHAKE=$F_HANDSHAKE_SQ - - # handshake transformations - "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ - --handshake-remove-unused-memrefs \ - --handshake-optimize-bitwidths \ - --handshake-materialize="replicate-constant=true" --handshake-infer-basic-blocks \ - > "$F_HANDSHAKE_TRANSFORMED" - exit_on_fail "Failed to apply transformations to handshake" \ - "Applied transformations to handshake" - + MATERIALIZE_ARGS=("--handshake-materialize=replicate-constant=true") else + MEMORY_INTERFACE_ARGS=( + --handshake-deactivate-mem-dependencies + --handshake-replace-memory-interfaces + ) +fi - # handshake transformations - "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ - --handshake-deactivate-mem-dependencies --handshake-replace-memory-interfaces \ - --handshake-remove-unused-memrefs \ - --handshake-optimize-bitwidths \ - --handshake-rewrite-terms \ - --handshake-combine-steering-logic \ - --handshake-materialize --handshake-infer-basic-blocks \ - --handshake-rewrite-terms \ - --handshake-materialize --handshake-infer-basic-blocks \ - > "$F_HANDSHAKE_TRANSFORMED" - exit_on_fail "Failed to apply transformations to handshake" \ - "Applied transformations to handshake" +STEERING_REWRITE_ARGS=() +if [[ $OPTIMIZE_STEERING_REWRITES -ne 0 ]]; then + echo_info "Applying steering-term rewrites" + STEERING_REWRITE_ARGS=( + --handshake-rewrite-terms + --handshake-combine-steering-logic + ) fi +# handshake transformations +"$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ + "${MEMORY_INTERFACE_ARGS[@]}" \ + --handshake-remove-unused-memrefs \ + --handshake-optimize-bitwidths \ + "${STEERING_REWRITE_ARGS[@]}" \ + "${MATERIALIZE_ARGS[@]}" --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" \ From 13ff669e85639601902ab12cb314ffee6e49ea12 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Wed, 24 Jun 2026 22:56:05 +0200 Subject: [PATCH 09/15] Modify steering flag --- tools/dynamatic/scripts/compile.sh | 49 ++++++++++++++++++------------ 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index b4af7eb860..b4062b7ee3 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -62,6 +62,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" @@ -270,9 +272,7 @@ else exit_on_fail "Failed to compile cf to handshake" "Compiled cf to handshake" fi -MEMORY_INTERFACE_ARGS=() -MATERIALIZE_ARGS=(--handshake-materialize) - +MATERIALIZE_PASS="--handshake-materialize" if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then echo_info "Using FPGA'23 for LSQ connection" @@ -286,30 +286,39 @@ if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then exit_on_fail "Failed to apply Straight to the Queue" "Applied Straight to the Queue" F_HANDSHAKE=$F_HANDSHAKE_SQ - MATERIALIZE_ARGS=("--handshake-materialize=replicate-constant=true") + MATERIALIZE_PASS="--handshake-materialize=replicate-constant=true" + + "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ + --handshake-remove-unused-memrefs \ + --handshake-optimize-bitwidths \ + > "$F_HANDSHAKE_PRE_MATERIALIZE" + exit_on_fail "Failed to apply pre-materialization transformations to handshake" \ + "Applied pre-materialization transformations to handshake" else - MEMORY_INTERFACE_ARGS=( - --handshake-deactivate-mem-dependencies - --handshake-replace-memory-interfaces - ) + "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ + --handshake-deactivate-mem-dependencies --handshake-replace-memory-interfaces \ + --handshake-remove-unused-memrefs \ + --handshake-optimize-bitwidths \ + > "$F_HANDSHAKE_PRE_MATERIALIZE" + exit_on_fail "Failed to apply pre-materialization transformations to handshake" \ + "Applied pre-materialization transformations to handshake" fi -STEERING_REWRITE_ARGS=() +F_HANDSHAKE_TO_MATERIALIZE="$F_HANDSHAKE_PRE_MATERIALIZE" if [[ $OPTIMIZE_STEERING_REWRITES -ne 0 ]]; then echo_info "Applying steering-term rewrites" - STEERING_REWRITE_ARGS=( - --handshake-rewrite-terms - --handshake-combine-steering-logic - ) + "$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 -# handshake transformations -"$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ - "${MEMORY_INTERFACE_ARGS[@]}" \ - --handshake-remove-unused-memrefs \ - --handshake-optimize-bitwidths \ - "${STEERING_REWRITE_ARGS[@]}" \ - "${MATERIALIZE_ARGS[@]}" --handshake-infer-basic-blocks \ +# Final materialization before speculation and buffer placement. +"$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE_TO_MATERIALIZE" \ + "$MATERIALIZE_PASS" --handshake-infer-basic-blocks \ > "$F_HANDSHAKE_TRANSFORMED" exit_on_fail "Failed to apply transformations to handshake" \ "Applied transformations to handshake" From 4f881be5761f2fd159a9eefe637c60419e4ee522 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Fri, 3 Jul 2026 19:58:38 +0200 Subject: [PATCH 10/15] remove logLines --- .../lib/Transforms/HandshakeCombineSteeringLogic.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/experimental/lib/Transforms/HandshakeCombineSteeringLogic.cpp b/experimental/lib/Transforms/HandshakeCombineSteeringLogic.cpp index dd7c09cf15..bfb42437c7 100644 --- a/experimental/lib/Transforms/HandshakeCombineSteeringLogic.cpp +++ b/experimental/lib/Transforms/HandshakeCombineSteeringLogic.cpp @@ -217,7 +217,6 @@ struct CombineInits : public OpRewritePattern { if (redundantInits.empty()) return failure(); - logLine("[HandshakeCombineSteeringLogic] CombineInits applied"); for (auto init : redundantInits) { rewriter.replaceAllUsesWith(init.getResult(), mergeOp.getResult()); rewriter.eraseOp(init); @@ -393,7 +392,6 @@ struct CombineMuxes : public OpRewritePattern { if (redundantMuxes.empty()) return failure(); - logLine("[HandshakeCombineSteeringLogic] CombineMuxes applied"); // Loop over redundantMuxes and replace the users of them with the output of // muxOp Note that the users of all redundantMuxes include the Branches // forming cycles with each of them, but as we erase the redundantMuxes, @@ -555,8 +553,6 @@ struct CombineBranchesOppositeSign if (redundantBranches.empty()) return failure(); - logLine("[HandshakeCombineSteeringLogic] CombineBranchesOppositeSign " - "applied\n"); // Erase the redundant branch for (auto br : redundantBranches) { rewriter.replaceAllUsesWith(br.getFalseResult(), @@ -601,7 +597,6 @@ struct RemoveNotCondition refreshBranchAttrsFromCondition(newBranch, condBranchOp); rewriter.eraseOp(condBranchOp); - logLine("[HandshakeCombineSteeringLogic] RemoveNotCondition applied\n"); return success(); } }; @@ -904,4 +899,4 @@ struct HandshakeCombineSteeringLogicPass return signalPassFailure(); }; }; -} // namespace \ No newline at end of file +} // namespace From 394931274fdc8e978e5ab1e8bd184c1c2f5b7a92 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Mon, 20 Jul 2026 14:28:04 +0200 Subject: [PATCH 11/15] Avoid start-derived memory control rewrites --- .../lib/Transforms/HandshakeRewriteTerms.cpp | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/experimental/lib/Transforms/HandshakeRewriteTerms.cpp b/experimental/lib/Transforms/HandshakeRewriteTerms.cpp index 5bfd969097..bff6a043e3 100644 --- a/experimental/lib/Transforms/HandshakeRewriteTerms.cpp +++ b/experimental/lib/Transforms/HandshakeRewriteTerms.cpp @@ -47,6 +47,93 @@ namespace { // 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()); @@ -674,6 +761,10 @@ struct EliminateRedundantLoop : public OpRewritePattern { muxOrMergeOperands = cast(muxOrMergeOp).getDataOperands(); + if (wouldReplaceMemoryControlWithStartDerived( + exitingCondBranchOp.getFalseResult(), + muxOrMergeOperands[outsideInputIdx])) + return failure(); rewriter.replaceAllUsesWith(exitingCondBranchOp.getFalseResult(), muxOrMergeOperands[outsideInputIdx]); From 284e3f93f6830437cc990158561fcf3897a267d6 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Mon, 20 Jul 2026 16:02:50 +0200 Subject: [PATCH 12/15] Disable constant replication in STQ materialization --- tools/dynamatic/scripts/compile.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index c7009a3256..94931fc9c1 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -297,7 +297,6 @@ else exit_on_fail "Failed to compile cf to handshake" "Compiled cf to handshake" fi -MATERIALIZE_PASS="--handshake-materialize" if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then echo_info "Using FPGA'23 for LSQ connection" @@ -311,7 +310,6 @@ if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then exit_on_fail "Failed to apply Straight to the Queue" "Applied Straight to the Queue" F_HANDSHAKE=$F_HANDSHAKE_SQ - MATERIALIZE_PASS="--handshake-materialize=replicate-constant=true" "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ --handshake-remove-unused-memrefs \ @@ -343,7 +341,7 @@ fi # Final materialization before speculation and buffer placement. "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE_TO_MATERIALIZE" \ - "$MATERIALIZE_PASS" --handshake-infer-basic-blocks \ + --handshake-materialize --handshake-infer-basic-blocks \ > "$F_HANDSHAKE_TRANSFORMED" exit_on_fail "Failed to apply transformations to handshake" \ "Applied transformations to handshake" From c1fa98e95a96f5f1fabeda7b062c847330d07080 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Mon, 20 Jul 2026 20:48:15 +0200 Subject: [PATCH 13/15] Fix CFDFC build without Graphviz headers --- lib/Transforms/BufferPlacement/Utils/CFDFC.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp b/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp index 382298eaa4..05b8de1768 100644 --- a/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp +++ b/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp @@ -38,8 +38,8 @@ using namespace dynamatic::handshake; using namespace dynamatic::buffer; using namespace dynamatic::experimental; -#include "graphviz/cgraph.h" -#include "graphviz/gvc.h" +// #include "graphviz/cgraph.h" +// #include "graphviz/gvc.h" namespace { /// Helper data structure to hold mappings between each arch/basic block and the From d4dcc80c1c1c722eb673066b343f014fda611112 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Mon, 20 Jul 2026 20:56:12 +0200 Subject: [PATCH 14/15] CI check-format --- .../lib/Transforms/HandshakeRewriteTerms.cpp | 9 +++++---- include/dynamatic/Support/CFG.h | 2 +- .../Transforms/BufferPlacement/Utils/CFDFC.h | 2 +- .../BufferPlacement/HandshakePlaceBuffers.cpp | 2 +- .../BufferPlacement/Utils/CFDFC.cpp | 3 +-- tools/dynamatic/dynamatic.cpp | 20 +++++++++---------- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/experimental/lib/Transforms/HandshakeRewriteTerms.cpp b/experimental/lib/Transforms/HandshakeRewriteTerms.cpp index bff6a043e3..a34f4120b1 100644 --- a/experimental/lib/Transforms/HandshakeRewriteTerms.cpp +++ b/experimental/lib/Transforms/HandshakeRewriteTerms.cpp @@ -13,11 +13,11 @@ // 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 "experimental/Transforms/HandshakeRewriteTerms.h" #include "mlir/IR/AsmState.h" #include "mlir/IR/Diagnostics.h" #include "mlir/IR/Operation.h" @@ -113,8 +113,8 @@ bool mayReachMemoryInterface(Value value, DenseSet &visited) { // Follow control values through routing ops so we can catch paths that // eventually drive an LSQ or memory-controller control input. if (isa(user)) { + handshake::MergeOp, handshake::MuxOp, handshake::ControlMergeOp>( + user)) { for (Value result : user->getResults()) { if (mayReachMemoryInterface(result, visited)) return true; @@ -131,7 +131,8 @@ bool mayReachMemoryInterface(Value value) { } bool wouldReplaceMemoryControlWithStartDerived(Value oldValue, Value newValue) { - return mayReachMemoryInterface(oldValue) && isDerivedFromFunctionStart(newValue); + return mayReachMemoryInterface(oldValue) && + isDerivedFromFunctionStart(newValue); } bool isSuppress(handshake::ConditionalBranchOp condBranchOp) { diff --git a/include/dynamatic/Support/CFG.h b/include/dynamatic/Support/CFG.h index 184b34ffab..c275f31860 100644 --- a/include/dynamatic/Support/CFG.h +++ b/include/dynamatic/Support/CFG.h @@ -199,4 +199,4 @@ bool isChannelOnCycle(mlir::Value channel); } // namespace dynamatic -#endif // DYNAMATIC_SUPPORT_CFG_H \ No newline at end of file +#endif // DYNAMATIC_SUPPORT_CFG_H diff --git a/include/dynamatic/Transforms/BufferPlacement/Utils/CFDFC.h b/include/dynamatic/Transforms/BufferPlacement/Utils/CFDFC.h index a87a9f9ac0..8d9e15ab8e 100644 --- a/include/dynamatic/Transforms/BufferPlacement/Utils/CFDFC.h +++ b/include/dynamatic/Transforms/BufferPlacement/Utils/CFDFC.h @@ -123,4 +123,4 @@ LogicalResult extractCFDFC(handshake::FuncOp funcOp, ArchSet &archs, BBSet &bbs, } // namespace buffer } // namespace dynamatic -#endif // DYNAMATIC_TRANSFORMS_BUFFERPLACEMENT_CFDFC_H \ No newline at end of file +#endif // DYNAMATIC_TRANSFORMS_BUFFERPLACEMENT_CFDFC_H diff --git a/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp b/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp index 56efc25c4d..dafbd1bc21 100644 --- a/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp +++ b/lib/Transforms/BufferPlacement/HandshakePlaceBuffers.cpp @@ -1018,4 +1018,4 @@ void HandshakePlaceBuffersPass::instantiateBuffers(BufferPlacement &placement, cfdfc.channelOccupancy[channel] = 0.0; } } -} \ No newline at end of file +} diff --git a/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp b/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp index 05b8de1768..8c6068a008 100644 --- a/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp +++ b/lib/Transforms/BufferPlacement/Utils/CFDFC.cpp @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// - #include "dynamatic/Transforms/BufferPlacement/Utils/CFDFC.h" #include "dynamatic/Analysis/NameAnalysis.h" #include "dynamatic/Dialect/Handshake/HandshakeOps.h" @@ -555,4 +554,4 @@ LogicalResult dynamatic::buffer::extractCFDFC( } return success(); -} \ No newline at end of file +} diff --git a/tools/dynamatic/dynamatic.cpp b/tools/dynamatic/dynamatic.cpp index a86baa2278..6e3b641119 100644 --- a/tools/dynamatic/dynamatic.cpp +++ b/tools/dynamatic/dynamatic.cpp @@ -301,7 +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 OPTIMIZE_STEERING_REWRITES = + "optimize-steering-rewrites"; static constexpr llvm::StringLiteral ENABLE_SHORT_CIRCUIT = "enable-short-circuit"; static constexpr llvm::StringLiteral SPECULATION = "speculation"; @@ -310,7 +311,6 @@ class Compile : public Command { static constexpr llvm::StringLiteral CALCULATE_PATH_DELAYS = "calculate-path-delays"; - Compile(FrontendState &state) : Command("compile", "Compiles the source kernel into a dataflow circuit; " @@ -340,8 +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( + {OPTIMIZE_STEERING_REWRITES, "Use handshake steering-term rewrites"}); addFlag({ENABLE_SHORT_CIRCUIT, "Enable short-circuit evaluation of && and ||, " "to match C specification"}); @@ -804,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, optimizeSteeringRewrite, 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) { From 83757ecb2bad0ee811b5451643fa1d197e816024 Mon Sep 17 00:00:00 2001 From: Zeinab Pourgheisari Date: Wed, 22 Jul 2026 16:22:23 +0200 Subject: [PATCH 15/15] Add provisional lit test for handshake rewrite terms --- .../Transforms/handshake-rewrite-terms.mlir | 719 ++++++++++-------- 1 file changed, 405 insertions(+), 314 deletions(-) diff --git a/experimental/test/Transforms/handshake-rewrite-terms.mlir b/experimental/test/Transforms/handshake-rewrite-terms.mlir index e9baba3ee2..bc78755c16 100644 --- a/experimental/test/Transforms/handshake-rewrite-terms.mlir +++ b/experimental/test/Transforms/handshake-rewrite-terms.mlir @@ -1,353 +1,444 @@ -handshake.func @removeBranchMUXPair_simple(%arg0: i32, %arg1: i1, %start: none) -> i32 { - %arg1_one, %arg1_two = fork [2] %arg1: i1 - %true, %false = cond_br %arg1_one , %arg0 : i32 - %result_mux= mux %arg1_two [%true, %false]: i1, i32 - end %result_mux : i32 -} -handshake.func @removeBranchCMergePair_simple(%arg0: i32, %arg1: i1, %start: none) -> (i32, index) { - %true, %false = cond_br %arg1, %arg0 : i32 - %result_cmerge, %index= control_merge %true, %false : i32, index - end %result_cmerge, %index : i32, index -} -handshake.func @removeBranchMUXPair_doNot(%arg0: i32, %arg1: i1, %start: none) -> i32 { - %arg1_one, %arg1_two = fork [2] %arg1: i1 - %true, %false = cond_br %arg1_one, %arg0: i32 - %num_1 = arith.constant 1 : i32 - %num_2 = arith.constant 2 : i32 - %add = arith.addi %false, %num_1 : i32 - %mul = arith.muli %true, %num_2 : i32 - %result_mux= mux %arg1_two [%mul, %add] : i1, i32 - end %result_mux : i32 -} -handshake.func @removeBranchCMergePair_doNot(%arg0: i32, %arg1: i1, %start: none) -> (i32, index) { - %true, %false = cond_br %arg1, %arg0: i32 - %num_1 = arith.constant 1 : i32 - %num_2 = arith.constant 2 : i32 - %add = arith.addi %false, %num_1 : i32 - %mul = arith.muli %true, %num_2 : i32 - %result_cmerge, %index= control_merge %mul, %add : i32, index - end %result_cmerge, %index : i32, index -} -handshake.func @removeBranchMUXPair(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %arg1_one, %arg1_two = fork [2] %arg1: i1 - %arg2_one, %arg2_two = fork [2] %arg2: i1 - %true, %false = cond_br %arg1_one, %arg0 : i32 - %result_mux= mux %arg1_two [%true, %false]: i1, i32 - %true_2, %false_2 = cond_br %arg2_one, %result_mux : i32 - %num_1 = arith.constant 1 : i32 - %num_2 = arith.constant 2 : i32 - %add = arith.addi %false_2, %num_1 : i32 - %mul = arith.muli %true_2, %num_2 : i32 - %result_mux_2= mux %arg2_two [%mul, %add] : i1, i32 - end %result_mux_2 : i32 -} -handshake.func @removeBranchCMergePair(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %true, %false = cond_br %arg1, %arg0 : i32 - %result_cmerge, %index= control_merge %true, %false : i32, index - %true_2, %false_2 = cond_br %arg2, %result_cmerge : i32 - %num_1 = arith.constant 1 : i32 - %num_2 = arith.constant 2 : i32 - %add = arith.addi %false_2, %num_1 : i32 - %mul = arith.muli %true_2, %num_2 : i32 - %result_cmerge_2, %index_2= control_merge %mul, %add : i32, index - end %result_cmerge_2 : i32 -} -handshake.func @removeBranchCMergePair_multiple(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %true, %false = cond_br %arg1, %arg0 : i32 - %true_2, %false_2 = cond_br %arg2, %true : i32 - %result_cmerge, %index= control_merge %true_2, %false_2 : i32, index - %result_cmerge_2, %index_2= control_merge %result_cmerge, %false : i32, index - end %result_cmerge_2 : i32 -} -handshake.func @removeBranchMUXPair_multiple(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %arg1_one, %arg1_two = fork [2] %arg1: i1 - %arg2_one, %arg2_two = fork [2] %arg2: i1 - %true, %false = cond_br %arg1_one, %arg0 : i32 - %true_2, %false_2 = cond_br %arg2_one, %true : i32 - %result_mux= mux %arg2_two [%true_2, %false_2]: i1, i32 - %result_mux_2= mux %arg1_two [%result_mux, %false]: i1, i32 - end %result_mux_2 : i32 -} - -handshake.func @removeBranchMUXPairloop_simple_case1(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %result_mux= mux %arg1 [%true, %arg0]: i1, i32 - %true, %false = cond_br %arg2, %result_mux : i32 - end %false : i32 -} - -handshake.func @removeBranchMUXPairloop_simple_case2(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %result_mux= mux %arg1 [%arg0, %false]: i1, i32 - %true, %false = cond_br %arg2, %result_mux : i32 - end %true : i32 -} - -handshake.func @removeBranchMUXPairloop_simple_case3(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %result_mux= mux %arg1 [%arg0, %true]: i1, i32 - %true, %false = cond_br %arg2, %result_mux : i32 - end %false : i32 -} - -handshake.func @removeBranchMUXPairloop_simple_case4(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %result_mux= mux %arg1 [%false, %arg0]: i1, i32 - %true, %false = cond_br %arg2, %result_mux : i32 - end %true : i32 -} - -handshake.func @removeBranchMergePairloop_simple_case1(%arg0: i32, %arg1: i1, %start: none) -> i32 { - %result_merge= merge %true, %arg0: i32 - %true, %false = cond_br %arg1, %result_merge : i32 - end %false : i32 -} - -handshake.func @removeBranchMergePairloop_simple_case2(%arg0: i32, %arg1: i1, %start: none) -> i32 { - %result_merge= merge %arg0, %false: i32 - %true, %false = cond_br %arg1, %result_merge : i32 - end %true : i32 -} - -handshake.func @removeBranchMergePairloop_simple_case3(%arg0: i32, %arg1: i1, %start: none) -> i32 { - %result_merge= merge %arg0, %true: i32 - %true, %false = cond_br %arg1, %result_merge : i32 - end %false : i32 -} - -handshake.func @removeBranchMergePairloop_simple_case4(%arg0: i32, %arg1: i1, %start: none) -> i32 { - %result_merge= merge %false, %arg0: i32 - %true, %false = cond_br %arg1, %result_merge : i32 - end %true : i32 -} - -handshake.func @removeBranchMUXPairloop_doNot_case1(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %result_mux= mux %arg1 [%true, %arg0]: i1, i32 - %num_1 = arith.constant 1: i32 - %data_in= arith.addi %result_mux, %num_1: i32 - %true, %false = cond_br %arg2, %data_in : i32 - end %false : i32 -} - -handshake.func @removeBranchMUXPairloop_doNot_case2(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %result_mux= mux %arg1 [%data_in, %arg0]: i1, i32 - %true, %false = cond_br %arg2, %result_mux : i32 - %num_2 = arith.constant 2: i32 - %data_in= arith.muli %true, %num_2: i32 - end %false : i32 -} - -handshake.func @removeBranchMergePairloop_doNot_case1(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %result_merge= merge %true, %arg0: i32 - %num_1 = arith.constant 1: i32 - %data_in= arith.addi %result_merge, %num_1: i32 - %true, %false = cond_br %arg2, %data_in : i32 - end %false : i32 -} - -handshake.func @removeBranchMergePairloop_doNot_case2(%arg0: i32, %arg1: i1, %start: none) -> i32 { - %result_merge= merge %arg0, %data_in: i32 - %true, %false = cond_br %arg1, %result_merge : i32 - %num_2 = arith.constant 2: i32 - %data_in= arith.muli %true, %num_2: i32 - end %false : i32 -} - -handshake.func @removeBranchMUXPairloop_multiple(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1, %start: none) -> i32 { - %result_mux_1= mux %arg1 [%true_2, %arg0]: i1, i32 - %result_mux_2= mux %arg2 [%true_1, %result_mux_1]: i1, i32 - %true_1, %false_1 = cond_br %arg3, %result_mux_2 : i32 - %true_2, %false_2 = cond_br %arg4, %false_1 : i32 - end %false_2 : i32 +// 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(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1, %start: none) -> i32 { - %result_merge_1= merge %true_2, %arg0: i32 - %result_merge_2= merge %true_1, %result_merge_1: i32 - %true_1, %false_1 = cond_br %arg3, %result_merge_2 : i32 - %true_2, %false_2 = cond_br %arg4, %false_1 : i32 - end %false_2 : i32 +// ----- + +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 @removeBranchMUXPairloop_multiple_doNot(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1, %start: none) -> i32 { - %result_mux_1= mux %arg1 [%true_2, %arg0]: i1, i32 - %result_mux_2= mux %arg2 [%true_1, %result_mux_1]: i1, i32 - %true_1, %false_1 = cond_br %arg3, %result_mux_2 : i32 - %num_1 = arith.constant 1 : i32 - %data_in = arith.addi %num_1, %false_1 : i32 - %true_2, %false_2 = cond_br %arg4, %data_in : i32 - end %false_2 : i32 +// ----- + +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 @removeBranchMergePairloop_multiple_doNot(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1, %start: none) -> i32 { - %result_merge_1= merge %true_2, %arg0: i32 - %result_merge_2= merge %true_1, %result_merge_1: i32 - %true_1, %false_1 = cond_br %arg3, %result_merge_2 : i32 - %num_1 = arith.constant 1 : i32 - %data_in = arith.addi %num_1, %false_1 : i32 - %true_2, %false_2 = cond_br %arg4, %data_in : i32 - end %false_2 : i32 +// ----- + +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 @removeBoth(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %start: none) -> i32 { - %result_mux= mux %arg1 [%true_2, %arg0]: i1, i32 - %result_merge= merge %true_1, %result_mux: i32 - %true_1, %false_1 = cond_br %arg2, %result_merge : i32 - %true_2, %false_2 = cond_br %arg3, %false_1 : i32 - end %false_2 : i32 +// ----- + +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 @removeBoth_doNot(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %start: none) -> i32 { - %result_mux= mux %arg1 [%true_2, %arg0]: i1, i32 - %result_merge= merge %true_1, %result_mux: i32 - %true_1, %false_1 = cond_br %arg2, %result_merge : i32 - %num_2 = arith.constant 2: i32 - %data_in= arith.muli %false_1, %num_2: i32 - %true_2, %false_2 = cond_br %arg3, %data_in : i32 - end %false_2 : i32 -} - -handshake.func @exampleBranchMUXPairloop(%x: i32, %j: i32, %c1: i1, %c2: i1) -> i32 { - %c1_one, %c1_two = fork [2] %c1 : i1 - %c2_one, %c2_two = fork [2] %c2 : i1 - %result_mux_1= mux %c1_one [%x, %false]: i1, i32 - %true, %false = cond_br %c1_two, %result_mux_1 : i32 - %result_mux_2= mux %c2_one [%true, %false_2]: i1, i32 - %result_mux_2one, %result_mux_2two = fork [2] %result_mux_2 : i32 - %true_2, %false_2 = cond_br %c2_two, %result_mux_2two : i32 - sink %true_2 : i32 - %sta = arith.addi %j, %result_mux_2one: i32 - end %sta : i32 -} - - -handshake.func @removeMUXBranchloop_fork_doNot(%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32 { - %result_mux = mux %arg1 [%arg0, %false] : i1, i32 - %c1, %c2 = fork [2] %result_mux : i32 - %num_2 = arith.constant 2: i32 - %answer = arith.muli %num_2, %c1 : i32 - %true, %false = cond_br %arg2, %c2 : i32 - sink %true: i32 - end %false: i32 + +// ----- + +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: i32, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1, %start: none) -> i32 { - %result_mux_1 = mux %arg1 [%arg0, %false_2] : i1, i32 - %c1, %c2 = fork [2] %result_mux_1 : i32 - %num_2 = arith.constant 2: i32 - %answer = arith.muli %num_2, %c1 : i32 - %result_mux_2 = mux %arg2 [%c2, %false_1] : i1, i32 - %true_1, %false_1 = cond_br %arg3, %result_mux_2 : i32 - %true_2, %false_2 = cond_br %arg4, %true_1 : i32 - sink %true_2: i32 - end %false_2: i32 +// ----- + +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: i32, %arg1: i1, %arg2: i1, %start: none) -> (i32, index) { - %result_cmerge_1, %index1= control_merge %true_2, %arg0: i32, index - %result_cmerge_2, %index2= control_merge %false_1, %result_cmerge_1: i32, index - %true_1, %false_1 = cond_br %arg1, %result_cmerge_2 : i32 - %num_1 = arith.constant 1 : i32 - %data_in = arith.addi %num_1, %true_1 : i32 - %true_2, %false_2 = cond_br %arg2, %data_in : i32 - end %false_2,%index2 : i32, index +// ----- + +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 @removeBothCMerge(%arg0: i32, %arg1: i1, %arg2: i1, %arg3: i1, %start: none) -> (i32, index, index) { - %result_cmerge_1, %index1= control_merge %true_2, %arg0: i32, index - %result_cmerge_2, %index2= control_merge %false_1, %result_cmerge_1: i32, index - %true_1, %false_1 = cond_br %arg2, %result_cmerge_2 : i32 - %true_2, %false_2 = cond_br %arg3, %true_1 : i32 - end %false_2, %index1, %index2 : i32, index, index +// ----- + +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 @removeSupressFork (%arg0: i1, %arg1: i32, %start: none) -> (i32, i32){ - %true, %false= cond_br %arg0, %arg1: i32 - sink %true: i32 - %one, %two = fork [2] %false : i32 - end %one, %two: i32, i32 +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 @removeSupressFork2 (%arg0: i32, %arg1: i1, %start: none) -> (i32, i32){ - %true, %false= cond_br %arg1, %arg0: i32 - %num1 = arith.constant 1: i32 - %answer= arith.addi %false, %num1 : i32 - %one, %two = fork [2] %answer: i32 - end %one, %two: i32, i32 +// ----- + +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 @removeSupressSupressPairs (%arg0: i32, %arg1: i1, %arg2: i1, %start: none) -> i32{ - %true, %false= cond_br %arg1, %arg0: i32 - %true2, %false2= cond_br %arg2, %false : i32 - end %false2: i32 +// ----- + +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 (%arg0: i32, %arg1: i1, %start: none) -> (i32, i32){ - %true, %false= cond_br %arg1, %arg0: i32 - end %true, %false: i32, i32 +// ----- + +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 @BranchtoForkSupressPairs_3 (%arg0: i32, %arg1: i1, %start: none) -> (i32, i32, i32, i32){ - %val1, %val2, %val3= fork [3] %arg1: i1 - %true, %false= cond_br %val1, %arg0: i32 - %true_1, %false_1 = cond_br %val2, %true: i32 - %true_2, %false_2 = cond_br %val3, %false: i32 - end %true_1, %false_1, %true_2, %false_2: i32, i32, i32, i32 +// ----- + +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 @removeForkForkPair(%arg0: i32, %start: none) -> (i32, i32, i32) { - %one, %two = fork [2] %arg0: i32 - %two1, %two2 = fork [2] %two: i32 - end %one, %two1, %two2: i32, i32, i32 +// ----- + +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 @removeForkForkPairMultiple(%arg0: i32, %start: none) -> (i32, i32, i32, i32, i32, i32, i32) { - %one, %two, %three = fork [3] %arg0: i32 - %two1, %two2 = fork [2] %two: i32 - %three1, %three2, %three3, %three4 = fork [4] %three: i32 - end %one, %two1, %two2, %three1, %three2, %three3, %three4 : i32, i32, i32, i32, i32, i32, i32 +// ----- + +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 @removeForkForkPairdonot(%arg0: i32, %start: none) -> (i32, i32, i32, i32, i32, i32) { - %one, %two, %three = fork [3] %arg0: i32 - %num1= arith.constant 1: i32 - %ans = arith.addi %two, %num1 : i32 - %two1, %two2 = fork [2] %ans: i32 - %three1, %three2, %three3, %three4 = fork [4] %three: i32 - end %one, %two1, %two2, %three1, %three2, %three3, %three4 : i32, i32, i32, i32, i32, i32, i32 +// ----- + +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(%arg0: i32, %start: none) -> (i32, i32, i32, i32, i32, i32) { - %one, %two= fork [2] %arg0: i32 - %two1, %two2 = fork [2] %two: i32 - %three1, %three2, %three3, %three4 = fork [4] %two1: i32 - end %one, %two2, %three1, %three2, %three3, %three4 : i32, i32, i32, i32, i32, i32 +// ----- + +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 @removeForkForkPairNested_only1(%arg0: i32, %start: none) -> (i32, i32, i32, i32, i32) { - %one, %two= fork [2] %arg0: i32 - %two1, %two2 = fork [2] %two: i32 - %num1= arith.constant 1: i32 - %ans = arith.addi %two1, %num1 : i32 - %three1, %three2, %three3, %three4 = fork [4] %ans: i32 - end %one, %three1, %three2, %three3, %three4 : i32, i32, i32, i32, i32 +// ----- + +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 @removeForkSuppressMUX(%arg0: i32, %arg1: i1, %start: none) -> i32 { - %one, %two= fork [2] %arg0: i32 - %one1, %two1, %three1 = fork [3] %arg1: i1 - %true, %false= cond_br %one1, %one: i32 - %ans = not %two1 : i1 - %true_2, %false_2= cond_br %ans, %two: i32 - %c = mux %three1 [%false_2, %false]:i1, i32 - end %c: i32 +// ----- + +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 : } - -handshake.func @removeBranchMUXPair_simplee(%arg0: i32, %arg1: i1, %arg2: none, ...) -> i32 attributes {argNames = ["arg0", "arg1", "start"], resNames = ["out0"]} { - %0:3 = fork [3] %arg1 : i1 - %1:2 = fork [2] %arg0 : i32 - %trueResult, %falseResult = cond_br %0#0, %1#0 : i32 - sink %trueResult : i32 - %2 = not %0#1 : i1 - %trueResult_0, %falseResult_1 = cond_br %2, %1#1 : i32 - sink %trueResult_0 : i32 - %3 = mux %0#2 [%falseResult, %falseResult_1] : i1, i32 - end %3 : i32 -} \ No newline at end of file