// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Peephole optimizations over DfgGraph // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2022 by Wilson Snyder. This program is free software; you // can redistribute it and/or modify it under the terms of either the GNU // Lesser General Public License Version 3 or the Perl Artistic License // Version 2.0. // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // // A pattern-matching based optimizer for DfgGraph. This is in some aspects similar to V3Const, but // more powerful in that it does not care about ordering combinational statement. This is also less // broadly applicable than V3Const, as it does not apply to procedural statements with sequential // execution semantics. // //************************************************************************* #include "config_build.h" #include "V3DfgPeephole.h" #include "V3Ast.h" #include "V3Dfg.h" #include "V3DfgPasses.h" #include "V3Stats.h" #include #include VL_DEFINE_DEBUG_FUNCTIONS; V3DfgPeepholeContext::V3DfgPeepholeContext(const std::string& label) : m_label{label} { const auto checkEnabled = [this](VDfgPeepholePattern id) { string str{id.ascii()}; std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { // return c == '_' ? '-' : std::tolower(c); }); m_enabled[id] = v3Global.opt.fDfgPeepholeEnabled(str); }; #define OPTIMIZATION_CHECK_ENABLED(id, name) checkEnabled(VDfgPeepholePattern::id); FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_CHECK_ENABLED) #undef OPTIMIZATION_CHECK_ENABLED } V3DfgPeepholeContext::~V3DfgPeepholeContext() { const auto emitStat = [this](VDfgPeepholePattern id) { string str{id.ascii()}; std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { // return c == '_' ? ' ' : std::tolower(c); }); V3Stats::addStat("Optimizations, DFG " + m_label + " Peephole, " + str, m_count[id]); }; #define OPTIMIZATION_EMIT_STATS(id, name) emitStat(VDfgPeepholePattern::id); FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_EMIT_STATS) #undef OPTIMIZATION_EMIT_STATS } class V3DfgPeephole final : public DfgVisitor { // STATE DfgGraph& m_dfg; // The DfgGraph being visited V3DfgPeepholeContext& m_ctx; // The config structure bool m_changed = false; // Changed a vertex AstNodeDType* const m_bitDType = DfgVertex::dtypeForWidth(1); // Common, so grab it up front #define APPLYING(id) if (checkApplying(VDfgPeepholePattern::id)) // METHODS bool checkApplying(VDfgPeepholePattern id) { if (!m_ctx.m_enabled[id]) return false; UINFO(9, "Applying DFG patten " << id.ascii() << endl); ++m_ctx.m_count[id]; m_changed = true; return true; } // Shorthand static AstNodeDType* dtypeForWidth(uint32_t width) { return DfgVertex::dtypeForWidth(width); } // Create a new DfgConst vertex with the given width and value DfgConst* makeConst(FileLine* flp, uint32_t width, uint32_t value) { const int widthInt = static_cast(width); return new DfgConst{m_dfg, new AstConst{flp, AstConst::WidthedValue{}, widthInt, value}}; } // Create a new 32-bit DfgConst vertex DfgConst* makeI32(FileLine* flp, uint32_t value) { return makeConst(flp, 32, value); } // Create a new DfgConst vertex with the given width and value zero DfgConst* makeZero(FileLine* flp, uint32_t width) { return makeConst(flp, width, 0); } // Transformations that apply to all commutative binary vertices void commutativeBinary(DfgVertexWithArity<2>* vtxp) { DfgVertex* const lhsp = vtxp->source<0>(); DfgVertex* const rhsp = vtxp->source<1>(); // Ensure Const is on left-hand side to simplify other patterns if (lhsp->is()) return; if (rhsp->is()) { APPLYING(SWAP_CONST_IN_COMMUTATIVE_BINARY) { vtxp->lhsp(rhsp); vtxp->rhsp(lhsp); return; } } // Ensure Not is on the left-hand side to simplify other patterns if (lhsp->is()) return; if (rhsp->is()) { APPLYING(SWAP_NOT_IN_COMMUTATIVE_BINARY) { vtxp->lhsp(rhsp); vtxp->rhsp(lhsp); return; } } // If both sides are variable references, order the side in some defined way. This allows // CSE to later merge 'a op b' with 'b op a'. if (lhsp->is() && rhsp->is()) { AstVar* const lVarp = lhsp->as()->varp(); AstVar* const rVarp = rhsp->as()->varp(); if (lVarp->name() > rVarp->name()) { APPLYING(SWAP_VAR_IN_COMMUTATIVE_BINARY) { vtxp->lhsp(rhsp); vtxp->rhsp(lhsp); return; } } } } // Bitwise operation with one side Const, and the other side a Concat template bool tryPushBitwiseOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) { UASSERT_OBJ(constp->width() == concatp->width(), vtxp, "Mismatched widths"); FileLine* const flp = vtxp->fileline(); // If at least one of the sides of the Concat constant, or width 1 (i.e.: can be // further simplified), then push the Vertex past the Concat if (concatp->lhsp()->is() || concatp->rhsp()->is() // || concatp->lhsp()->width() == 1 || concatp->rhsp()->width() == 1) { APPLYING(PUSH_BITWISE_OP_THROUGH_CONCAT) { const uint32_t width = concatp->width(); AstNodeDType* const lDtypep = concatp->lhsp()->dtypep(); AstNodeDType* const rDtypep = concatp->rhsp()->dtypep(); const uint32_t lWidth = lDtypep->width(); const uint32_t rWidth = rDtypep->width(); // The new Lhs vertex Vertex* const newLhsp = new Vertex{m_dfg, flp, lDtypep}; DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth); newLhsConstp->num().opSel(constp->num(), width - 1, rWidth); newLhsp->lhsp(newLhsConstp); newLhsp->rhsp(concatp->lhsp()); // The new Rhs vertex Vertex* const newRhsp = new Vertex{m_dfg, flp, rDtypep}; DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth); newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0); newRhsp->lhsp(newRhsConstp); newRhsp->rhsp(concatp->rhsp()); // The replacement Concat vertex DfgConcat* const newConcat = new DfgConcat{m_dfg, concatp->fileline(), concatp->dtypep()}; newConcat->lhsp(newLhsp); newConcat->rhsp(newRhsp); // Replace this vertex vtxp->replaceWith(newConcat); return true; } } return false; } template bool tryPushCompareOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) { UASSERT_OBJ(constp->width() == concatp->width(), vtxp, "Mismatched widths"); FileLine* const flp = vtxp->fileline(); // If at least one of the sides of the Concat is constant, then push the Vertex past the // Concat if (concatp->lhsp()->is() || concatp->rhsp()->is()) { APPLYING(PUSH_COMPARE_OP_THROUGH_CONCAT) { const uint32_t width = concatp->width(); const uint32_t lWidth = concatp->lhsp()->width(); const uint32_t rWidth = concatp->rhsp()->width(); // The new Lhs vertex Vertex* const newLhsp = new Vertex{m_dfg, flp, m_bitDType}; DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth); newLhsConstp->num().opSel(constp->num(), width - 1, rWidth); newLhsp->lhsp(newLhsConstp); newLhsp->rhsp(concatp->lhsp()); // The new Rhs vertex Vertex* const newRhsp = new Vertex{m_dfg, flp, m_bitDType}; DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth); newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0); newRhsp->lhsp(newRhsConstp); newRhsp->rhsp(concatp->rhsp()); // The replacement Vertex DfgVertexWithArity<2>* const replacementp = std::is_same::value ? new DfgAnd{m_dfg, concatp->fileline(), m_bitDType} : nullptr; UASSERT_OBJ(replacementp, vtxp, "Unhandled vertex type in 'tryPushCompareOpThroughConcat': " << vtxp->typeName()); replacementp->relinkSource<0>(newLhsp); replacementp->relinkSource<1>(newRhsp); // Replace this vertex vtxp->replaceWith(replacementp); return true; } } return false; } template void optimizeReduction(Vertex* vtxp) { static_assert(std::is_same::value || std::is_same::value || std::is_same::value, "Invalid 'Vertex' type for this method"); DfgVertex* const srcp = vtxp->srcp(); // Reduction of 1-bit value -- Currently unreachable as V3Const can remove these, but // in the future they will be created during this optimization. if (srcp->width() == 1) { // LCOV_EXCL_START APPLYING(REMOVE_WIDTH_ONE_REDUCTION) { vtxp->replaceWith(srcp); return; } } // LCOV_EXCL_STOP if (DfgCond* const condp = srcp->cast()) { if (condp->thenp()->is() || condp->elsep()->is()) { APPLYING(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) { // The new 'then' vertex Vertex* const newThenp = new Vertex{m_dfg, vtxp->fileline(), m_bitDType}; newThenp->srcp(condp->thenp()); // The new 'else' vertex Vertex* const newElsep = new Vertex{m_dfg, vtxp->fileline(), m_bitDType}; newElsep->srcp(condp->elsep()); // The replacement Cond vertex DfgCond* const newCondp = new DfgCond{m_dfg, condp->fileline(), m_bitDType}; newCondp->condp(condp->condp()); newCondp->thenp(newThenp); newCondp->elsep(newElsep); // Replace this vertex vtxp->replaceWith(newCondp); return; } } } if (DfgConst* const constp = srcp->cast()) { APPLYING(REPLACE_REDUCTION_OF_CONST) { DfgConst* const replacementp = makeZero(vtxp->fileline(), 1); if (std::is_same::value) { replacementp->num().opRedAnd(constp->num()); } else if (std::is_same::value) { replacementp->num().opRedOr(constp->num()); } else { replacementp->num().opRedXor(constp->num()); } vtxp->replaceWith(replacementp); return; } } } void optimizeShiftRHS(DfgVertexWithArity<2>* vtxp) { if (const DfgConcat* const concatp = vtxp->rhsp()->cast()) { if (concatp->lhsp()->isZero()) { // Drop redundant zero extension APPLYING(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) { // vtxp->rhsp(concatp->rhsp()); } } } } // VISIT methods void visit(DfgVertex*) override {} void visit(DfgExtend* vtxp) override { const uint32_t extension = vtxp->width() - vtxp->srcp()->width(); UASSERT_OBJ(extension > 0, vtxp, "Useless Extend"); FileLine* const flp = vtxp->fileline(); // Convert Extend into Concat with zeros. This simplifies other patterns as they only need // to handle Concat, which is more generic, and don't need special cases for Extend. APPLYING(REPLACE_EXTEND) { DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; replacementp->lhsp(makeZero(flp, extension)); replacementp->rhsp(vtxp->srcp()); vtxp->replaceWith(replacementp); } } void visit(DfgNot* vtxp) override { UASSERT_OBJ(vtxp->width() == vtxp->srcp()->width(), vtxp, "Mismatched width: " << vtxp->width() << " != " << vtxp->srcp()->width()); // Not of Cond if (DfgCond* const condp = vtxp->srcp()->cast()) { // If at least one of the branches are a constant, push the Not past the Cond if (condp->thenp()->is() || condp->elsep()->is()) { APPLYING(PUSH_NOT_THROUGH_COND) { // The new 'then' vertex DfgNot* const newThenp = new DfgNot{m_dfg, vtxp->fileline(), vtxp->dtypep()}; newThenp->srcp(condp->thenp()); // The new 'else' vertex DfgNot* const newElsep = new DfgNot{m_dfg, vtxp->fileline(), vtxp->dtypep()}; newElsep->srcp(condp->elsep()); // The replacement Cond vertex DfgCond* const newCondp = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()}; newCondp->condp(condp->condp()); newCondp->thenp(newThenp); newCondp->elsep(newElsep); // Replace this vertex vtxp->replaceWith(newCondp); return; } } } // Not of Not if (DfgNot* const notp = vtxp->srcp()->cast()) { UASSERT_OBJ(vtxp->width() == notp->srcp()->width(), vtxp, "Width mismatch"); APPLYING(REMOVE_NOT_NOT) { vtxp->replaceWith(notp->srcp()); return; } } // Not of Neq if (DfgNeq* const neqp = vtxp->srcp()->cast()) { APPLYING(REPLACE_NOT_NEQ) { DfgEq* const replacementp = new DfgEq{m_dfg, neqp->fileline(), vtxp->dtypep()}; replacementp->lhsp(neqp->lhsp()); replacementp->rhsp(neqp->rhsp()); vtxp->replaceWith(replacementp); return; } } // Not of Const if (DfgConst* const constp = vtxp->srcp()->cast()) { APPLYING(REPLACE_NOT_OF_CONST) { DfgConst* const replacementp = makeZero(vtxp->fileline(), vtxp->width()); replacementp->num().opNot(constp->num()); vtxp->replaceWith(replacementp); return; } } } void visit(DfgAnd* vtxp) override { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); commutativeBinary(vtxp); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); FileLine* const flp = vtxp->fileline(); // Bubble pushing if (lhsp->is() && rhsp->is()) { APPLYING(REPLACE_AND_OF_NOT_AND_NOT) { DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()}; orp->lhsp(lhsp->as()->srcp()); orp->rhsp(rhsp->as()->srcp()); DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; notp->srcp(orp); vtxp->replaceWith(notp); return; } } if (DfgConst* const lhsConstp = lhsp->cast()) { if (DfgConst* const rhsConstp = rhsp->cast()) { APPLYING(REPLACE_AND_OF_CONST_AND_CONST) { DfgConst* const replacementp = makeZero(flp, vtxp->width()); replacementp->num().opAnd(lhsConstp->num(), rhsConstp->num()); vtxp->replaceWith(replacementp); return; } } if (lhsConstp->isZero()) { APPLYING(REPLACE_AND_WITH_ZERO) { vtxp->replaceWith(lhsConstp); return; } } if (lhsConstp->isOnes()) { APPLYING(REMOVE_AND_WITH_ONES) { vtxp->replaceWith(rhsp); return; } } if (DfgConcat* const rhsConcatp = rhsp->cast()) { if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; } } if (DfgNot* const lhsNotp = lhsp->cast()) { // ~A & A is all zeroes if (lhsNotp->srcp() == rhsp) { APPLYING(REPLACE_CONTRADICTORY_AND) { DfgConst* const replacementp = makeZero(flp, vtxp->width()); vtxp->replaceWith(replacementp); return; } } } } void visit(DfgOr* vtxp) override { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); commutativeBinary(vtxp); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); FileLine* const flp = vtxp->fileline(); // Bubble pushing if (DfgNot* const lhsNotp = lhsp->cast()) { if (DfgNot* const rhsNotp = rhsp->cast()) { APPLYING(REPLACE_OR_OF_NOT_AND_NOT) { DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()}; andp->lhsp(lhsNotp->srcp()); andp->rhsp(rhsNotp->srcp()); DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; notp->srcp(andp); vtxp->replaceWith(notp); return; } } if (DfgNeq* const rhsNeqp = rhsp->cast()) { APPLYING(REPLACE_OR_OF_NOT_AND_NEQ) { DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()}; andp->lhsp(lhsNotp->srcp()); DfgEq* const newRhsp = new DfgEq{m_dfg, rhsp->fileline(), rhsp->dtypep()}; newRhsp->lhsp(rhsNeqp->lhsp()); newRhsp->rhsp(rhsNeqp->rhsp()); andp->rhsp(newRhsp); DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; notp->srcp(andp); vtxp->replaceWith(notp); return; } } } if (DfgConcat* const lhsConcatp = lhsp->cast()) { if (DfgConcat* const rhsConcatp = rhsp->cast()) { if (lhsConcatp->lhsp()->width() == rhsConcatp->lhsp()->width()) { if (lhsConcatp->lhsp()->isZero() && rhsConcatp->rhsp()->isZero()) { APPLYING(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) { DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; replacementp->lhsp(rhsConcatp->lhsp()); replacementp->rhsp(lhsConcatp->rhsp()); vtxp->replaceWith(replacementp); return; } } if (lhsConcatp->rhsp()->isZero() && rhsConcatp->lhsp()->isZero()) { APPLYING(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) { DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; replacementp->lhsp(lhsConcatp->lhsp()); replacementp->rhsp(rhsConcatp->rhsp()); vtxp->replaceWith(replacementp); return; } } } } } if (DfgConst* const lhsConstp = lhsp->cast()) { if (DfgConst* const rhsConstp = rhsp->cast()) { APPLYING(REPLACE_OR_OF_CONST_AND_CONST) { DfgConst* const replacementp = makeZero(flp, vtxp->width()); replacementp->num().opOr(lhsConstp->num(), rhsConstp->num()); vtxp->replaceWith(replacementp); return; } } if (lhsConstp->isZero()) { APPLYING(REMOVE_OR_WITH_ZERO) { vtxp->replaceWith(rhsp); return; } } if (lhsConstp->isOnes()) { APPLYING(REPLACE_OR_WITH_ONES) { vtxp->replaceWith(lhsp); return; } } if (DfgConcat* const rhsConcatp = rhsp->cast()) { if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; } } if (DfgNot* const lhsNotp = lhsp->cast()) { // ~A | A is all ones if (lhsNotp->srcp() == rhsp) { APPLYING(REPLACE_TAUTOLOGICAL_OR) { DfgConst* const replacementp = makeZero(flp, vtxp->width()); replacementp->num().setAllBits1(); vtxp->replaceWith(replacementp); return; } } } } void visit(DfgXor* vtxp) override { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); commutativeBinary(vtxp); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); if (DfgConst* const lhsConstp = lhsp->cast()) { if (DfgConcat* const rhsConcatp = rhsp->cast()) { if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; } } } void visit(DfgAdd* vtxp) override { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); commutativeBinary(vtxp); } void visit(DfgSub* vtxp) override { DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); UASSERT_OBJ(lhsp->width() == rhsp->width(), vtxp, "Width mismatch"); UASSERT_OBJ(lhsp->width() == vtxp->width(), vtxp, "Width mismatch"); if (DfgConst* const rConstp = rhsp->cast()) { if (rConstp->isZero()) { APPLYING(REMOVE_SUB_ZERO) { vtxp->replaceWith(lhsp); return; } } if (vtxp->width() == 1 && rConstp->toU32() == 1) { APPLYING(REPLACE_SUB_WITH_NOT) { DfgNot* const replacementp = new DfgNot{m_dfg, vtxp->fileline(), m_bitDType}; replacementp->srcp(lhsp); vtxp->replaceWith(replacementp); return; } } } } void visit(DfgShiftL* vtxp) override { optimizeShiftRHS(vtxp); } void visit(DfgShiftR* vtxp) override { optimizeShiftRHS(vtxp); } void visit(DfgShiftRS* vtxp) override { optimizeShiftRHS(vtxp); } void visit(DfgEq* vtxp) override { commutativeBinary(vtxp); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); if (DfgConst* const lhsConstp = lhsp->cast()) { if (DfgConst* const rhsConstp = rhsp->cast()) { APPLYING(REPLACE_EQ_OF_CONST_AND_CONST) { DfgConst* const replacementp = makeZero(vtxp->fileline(), 1); replacementp->num().opEq(lhsConstp->num(), rhsConstp->num()); vtxp->replaceWith(replacementp); return; } } if (DfgConcat* const rhsConcatp = rhsp->cast()) { if (tryPushCompareOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; } } } void visit(DfgSel* vtxp) override { DfgVertex* const fromp = vtxp->fromp(); DfgConst* const lsbp = vtxp->lsbp()->cast(); DfgConst* const widthp = vtxp->widthp()->cast(); if (!lsbp || !widthp) return; FileLine* const flp = vtxp->fileline(); UASSERT_OBJ(lsbp->toI32() >= 0, vtxp, "Negative LSB in Sel"); const uint32_t lsb = lsbp->toU32(); const uint32_t width = widthp->toU32(); const uint32_t msb = lsb + width - 1; UASSERT_OBJ(width == vtxp->width(), vtxp, "Incorrect Sel width"); // Full width select, replace with the source. if (fromp->width() == width) { UASSERT_OBJ(lsb == 0, fromp, "OOPS"); APPLYING(REMOVE_FULL_WIDTH_SEL) { vtxp->replaceWith(fromp); return; } } // Sel from Concat if (DfgConcat* const concatp = fromp->cast()) { DfgVertex* const lhsp = concatp->lhsp(); DfgVertex* const rhsp = concatp->rhsp(); if (msb < rhsp->width()) { // If the select is entirely from rhs, then replace with sel from rhs APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { // vtxp->fromp(rhsp); } } else if (lsb >= rhsp->width()) { // If the select is entirely from the lhs, then replace with sel from lhs APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) { vtxp->fromp(lhsp); vtxp->lsbp(makeI32(flp, lsb - rhsp->width())); } } else if (lsb == 0 || msb == concatp->width() - 1 // || lhsp->is() || rhsp->is()) { // If the select straddles both sides, but at least one of the sides is wholly // selected, or at least one of the sides is a Const, then push the Sel past // the Concat APPLYING(PUSH_SEL_THROUGH_CONCAT) { const uint32_t rSelWidth = rhsp->width() - lsb; const uint32_t lSelWidth = width - rSelWidth; // The new Lhs vertex DfgSel* const newLhsp = new DfgSel{m_dfg, flp, dtypeForWidth(lSelWidth)}; newLhsp->fromp(lhsp); newLhsp->lsbp(makeI32(lsbp->fileline(), 0)); newLhsp->widthp(makeI32(widthp->fileline(), lSelWidth)); // The new Rhs vertex DfgSel* const newRhsp = new DfgSel{m_dfg, flp, dtypeForWidth(rSelWidth)}; newRhsp->fromp(rhsp); newRhsp->lsbp(makeI32(lsbp->fileline(), lsb)); newRhsp->widthp(makeI32(widthp->fileline(), rSelWidth)); // The replacement Concat vertex DfgConcat* const newConcat = new DfgConcat{m_dfg, concatp->fileline(), vtxp->dtypep()}; newConcat->lhsp(newLhsp); newConcat->rhsp(newRhsp); // Replace this vertex vtxp->replaceWith(newConcat); return; } } } if (DfgReplicate* const repp = fromp->cast()) { // If the Sel is wholly into the source of the Replicate, push the Sel through the // Replicate and apply it directly to the source of the Replicate. const uint32_t srcWidth = repp->srcp()->width(); if (width <= srcWidth) { const uint32_t newLsb = lsb % srcWidth; if (newLsb + width <= srcWidth) { APPLYING(PUSH_SEL_THROUGH_REPLICATE) { vtxp->fromp(repp->srcp()); vtxp->lsbp(makeI32(flp, newLsb)); } } } } // Sel from Not if (DfgNot* const notp = fromp->cast()) { // Replace "Sel from Not" with "Not of Sel" if (!notp->hasMultipleSinks()) { UASSERT_OBJ(notp->srcp()->width() == notp->width(), notp, "Mismatched widths"); APPLYING(PUSH_SEL_THROUGH_NOT) { // Make Sel select from source of Not vtxp->fromp(notp->srcp()); // Add Not after Sel DfgNot* const replacementp = new DfgNot{m_dfg, notp->fileline(), vtxp->dtypep()}; vtxp->replaceWith(replacementp); replacementp->srcp(vtxp); } } } // Sel from Sel if (DfgSel* const selp = fromp->cast()) { UASSERT_OBJ(widthp->toU32() <= selp->width(), vtxp, "Out of bound Sel"); if (DfgConst* const sourceLsbp = selp->lsbp()->cast()) { UASSERT_OBJ(sourceLsbp->toI32() >= 0, selp, "negative"); UASSERT_OBJ(selp->widthp()->as()->toU32() >= widthp->toU32(), selp, "negative"); APPLYING(REPLACE_SEL_FROM_SEL) { // Make this Sel select from the source of the source Sel vtxp->fromp(selp->fromp()); // Adjust LSB vtxp->lsbp(makeI32(flp, lsb + sourceLsbp->toU32())); } } } // Sel from Cond if (DfgCond* const condp = fromp->cast()) { // If at least one of the branches are a constant, push the select past the cond if (condp->thenp()->is() || condp->elsep()->is()) { APPLYING(PUSH_SEL_THROUGH_COND) { // The new 'then' vertex DfgSel* const newThenp = new DfgSel{m_dfg, flp, vtxp->dtypep()}; newThenp->fromp(condp->thenp()); newThenp->lsbp(makeI32(lsbp->fileline(), lsb)); newThenp->widthp(makeI32(widthp->fileline(), width)); // The new 'else' vertex DfgSel* const newElsep = new DfgSel{m_dfg, flp, vtxp->dtypep()}; newElsep->fromp(condp->elsep()); newElsep->lsbp(makeI32(lsbp->fileline(), lsb)); newElsep->widthp(makeI32(widthp->fileline(), width)); // The replacement Cond vertex DfgCond* const newCondp = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()}; newCondp->condp(condp->condp()); newCondp->thenp(newThenp); newCondp->elsep(newElsep); // Replace this vertex vtxp->replaceWith(newCondp); return; } } } // Sel from ShiftL if (DfgShiftL* const shiftLp = fromp->cast()) { // If selecting bottom bits of left shift, push the Sel before the shift if (lsb == 0) { UASSERT_OBJ(shiftLp->lhsp()->width() >= width, vtxp, "input of shift narrow"); APPLYING(PUSH_SEL_THROUGH_SHIFTL) { vtxp->fromp(shiftLp->lhsp()); DfgShiftL* const newShiftLp = new DfgShiftL{m_dfg, shiftLp->fileline(), vtxp->dtypep()}; vtxp->replaceWith(newShiftLp); newShiftLp->lhsp(vtxp); newShiftLp->rhsp(shiftLp->rhsp()); } } } // Sel from Const if (DfgConst* const constp = fromp->cast()) { APPLYING(REPLACE_SEL_FROM_CONST) { DfgConst* const replacementp = makeZero(flp, width); replacementp->num().opSel(constp->num(), msb, lsb); vtxp->replaceWith(replacementp); return; } } } void visit(DfgRedOr* vtxp) override { optimizeReduction(vtxp); } void visit(DfgRedAnd* vtxp) override { optimizeReduction(vtxp); } void visit(DfgRedXor* vtxp) override { optimizeReduction(vtxp); } void visit(DfgConcat* vtxp) override { DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); UASSERT_OBJ(vtxp->width() == lhsp->width() + rhsp->width(), vtxp, "Incorrect Concat width: " << vtxp->width() << " != " << lhsp->width() << " + " << rhsp->width()); FileLine* const flp = vtxp->fileline(); { const auto joinConsts = [this](DfgConst* lConstp, DfgConst* rConstp, FileLine* flp) -> DfgConst* { DfgConst* const newConstp = makeZero(flp, lConstp->width() + rConstp->width()); newConstp->num().opSelInto(rConstp->num(), 0, rConstp->width()); newConstp->num().opSelInto(lConstp->num(), rConstp->width(), lConstp->width()); return newConstp; }; DfgConst* const lConstp = lhsp->cast(); DfgConst* const rConstp = rhsp->cast(); if (lConstp && rConstp) { APPLYING(REPLACE_CONCAT_OF_CONSTS) { vtxp->replaceWith(joinConsts(lConstp, rConstp, flp)); return; } } if (lConstp) { if (DfgConcat* const rConcatp = rhsp->cast()) { if (DfgConst* const rlConstp = rConcatp->lhsp()->cast()) { APPLYING(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS) { DfgConst* const joinedConstp = joinConsts(lConstp, rlConstp, flp); DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; replacementp->lhsp(joinedConstp); replacementp->rhsp(rConcatp->rhsp()); vtxp->replaceWith(replacementp); return; } } } if (lConstp->isZero()) { if (DfgSel* const rSelp = rhsp->cast()) { if (DfgConst* const rSelLsbConstp = rSelp->lsbp()->cast()) { if (vtxp->width() == rSelp->fromp()->width() && rSelLsbConstp->toU32() == lConstp->width()) { const uint32_t rSelWidth = rSelp->widthp()->as()->toU32(); UASSERT_OBJ(lConstp->width() + rSelWidth == vtxp->width(), vtxp, "Inconsistent"); APPLYING(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) { DfgShiftR* const replacementp = new DfgShiftR{m_dfg, flp, vtxp->dtypep()}; replacementp->lhsp(rSelp->fromp()); replacementp->rhsp(makeI32(flp, lConstp->width())); vtxp->replaceWith(replacementp); return; } } } } } } if (rConstp) { if (DfgConcat* const lConcatp = lhsp->cast()) { if (DfgConst* const lrConstp = lConcatp->rhsp()->cast()) { APPLYING(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS) { DfgConst* const joinedConstp = joinConsts(lrConstp, rConstp, flp); DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; replacementp->lhsp(lConcatp->lhsp()); replacementp->rhsp(joinedConstp); vtxp->replaceWith(replacementp); return; } } } if (rConstp->isZero()) { if (DfgSel* const lSelp = lhsp->cast()) { if (DfgConst* const lSelLsbConstp = lSelp->lsbp()->cast()) { if (vtxp->width() == lSelp->fromp()->width() && lSelLsbConstp->toU32() == 0) { const uint32_t lSelWidth = lSelp->widthp()->as()->toU32(); UASSERT_OBJ(lSelWidth + rConstp->width() == vtxp->width(), vtxp, "Inconsistent"); APPLYING(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) { DfgShiftL* const replacementp = new DfgShiftL{m_dfg, flp, vtxp->dtypep()}; replacementp->lhsp(lSelp->fromp()); replacementp->rhsp(makeI32(flp, rConstp->width())); vtxp->replaceWith(replacementp); return; } } } } } } } { DfgNot* const lNot = lhsp->cast(); DfgNot* const rNot = rhsp->cast(); if (lNot && rNot) { APPLYING(PUSH_CONCAT_THROUGH_NOTS) { vtxp->lhsp(lNot->srcp()); vtxp->rhsp(rNot->srcp()); DfgNot* const replacementp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; vtxp->replaceWith(replacementp); replacementp->srcp(vtxp); return; } } } { const auto joinSels = [this](DfgSel* lSelp, DfgSel* rSelp, FileLine* flp) -> DfgSel* { DfgConst* const lLsbp = lSelp->lsbp()->cast(); DfgConst* const lWidthp = lSelp->widthp()->cast(); DfgConst* const rLsbp = rSelp->lsbp()->cast(); DfgConst* const rWidthp = rSelp->widthp()->cast(); if (lLsbp && lWidthp && rLsbp && rWidthp) { if (lSelp->fromp()->equals(*rSelp->fromp())) { if (lLsbp->toU32() == rLsbp->toU32() + rWidthp->toU32()) { // Two consecutive Sels, make a single Sel. const uint32_t width = lWidthp->toU32() + rWidthp->toU32(); AstNodeDType* const dtypep = dtypeForWidth(width); DfgSel* const joinedSelp = new DfgSel{m_dfg, flp, dtypep}; joinedSelp->fromp(rSelp->fromp()); joinedSelp->lsbp(rSelp->lsbp()); joinedSelp->widthp(makeI32(flp, width)); return joinedSelp; } } } return nullptr; }; DfgSel* const lSelp = lhsp->cast(); DfgSel* const rSelp = rhsp->cast(); if (lSelp && rSelp) { if (DfgSel* const jointSelp = joinSels(lSelp, rSelp, flp)) { APPLYING(REMOVE_CONCAT_OF_ADJOINING_SELS) { vtxp->replaceWith(jointSelp); return; } } } if (lSelp) { if (DfgConcat* const rConcatp = rhsp->cast()) { if (DfgSel* const rlSelp = rConcatp->lhsp()->cast()) { if (DfgSel* const jointSelp = joinSels(lSelp, rlSelp, flp)) { APPLYING(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) { DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; replacementp->lhsp(jointSelp); replacementp->rhsp(rConcatp->rhsp()); vtxp->replaceWith(replacementp); return; } } } } } if (rSelp) { if (DfgConcat* const lConcatp = lhsp->cast()) { if (DfgSel* const lrlSelp = lConcatp->rhsp()->cast()) { if (DfgSel* const jointSelp = joinSels(lrlSelp, rSelp, flp)) { APPLYING(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) { DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; replacementp->lhsp(lConcatp->lhsp()); replacementp->rhsp(jointSelp); vtxp->replaceWith(replacementp); return; } } } } } } } void visit(DfgCond* vtxp) override { DfgVertex* const condp = vtxp->condp(); DfgVertex* const thenp = vtxp->thenp(); DfgVertex* const elsep = vtxp->elsep(); UASSERT_OBJ(vtxp->width() == thenp->width(), vtxp, "Width mismatch"); UASSERT_OBJ(vtxp->width() == elsep->width(), vtxp, "Width mismatch"); if (condp->width() != 1) return; FileLine* const flp = vtxp->fileline(); if (condp->isOnes()) { APPLYING(REMOVE_COND_WITH_TRUE_CONDITION) { vtxp->replaceWith(thenp); return; } } if (condp->isZero()) { APPLYING(REMOVE_COND_WITH_FALSE_CONDITION) { vtxp->replaceWith(elsep); return; } } if (DfgNot* const condNotp = condp->cast()) { APPLYING(SWAP_COND_WITH_NOT_CONDITION) { vtxp->condp(condNotp->srcp()); vtxp->thenp(elsep); vtxp->elsep(thenp); visit(vtxp); return; } } if (DfgNeq* const condNeqp = condp->cast()) { APPLYING(SWAP_COND_WITH_NEQ_CONDITION) { DfgEq* const newCondp = new DfgEq{m_dfg, condp->fileline(), condp->dtypep()}; newCondp->lhsp(condNeqp->lhsp()); newCondp->rhsp(condNeqp->rhsp()); vtxp->condp(newCondp); vtxp->thenp(elsep); vtxp->elsep(thenp); visit(vtxp); return; } } if (DfgNot* const thenNotp = thenp->cast()) { if (DfgNot* const elseNotp = elsep->cast()) { APPLYING(PULL_NOTS_THROUGH_COND) { DfgNot* const replacementp = new DfgNot{m_dfg, thenp->fileline(), vtxp->dtypep()}; vtxp->thenp(thenNotp->srcp()); vtxp->elsep(elseNotp->srcp()); vtxp->replaceWith(replacementp); replacementp->srcp(vtxp); return; } } } if (vtxp->width() == 1) { AstNodeDType* const dtypep = vtxp->dtypep(); if (thenp->isZero()) { // a ? 0 : b becomes ~a & b APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) { DfgAnd* const repalcementp = new DfgAnd{m_dfg, flp, dtypep}; DfgNot* const notp = new DfgNot{m_dfg, flp, dtypep}; notp->srcp(condp); repalcementp->lhsp(notp); repalcementp->rhsp(elsep); vtxp->replaceWith(repalcementp); return; } } if (thenp->isOnes()) { // a ? 1 : b becomes a | b APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) { DfgOr* const repalcementp = new DfgOr{m_dfg, flp, dtypep}; repalcementp->lhsp(condp); repalcementp->rhsp(elsep); vtxp->replaceWith(repalcementp); return; } } if (elsep->isZero()) { // a ? b : 0 becomes a & b APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) { DfgAnd* const repalcementp = new DfgAnd{m_dfg, flp, dtypep}; repalcementp->lhsp(condp); repalcementp->rhsp(thenp); vtxp->replaceWith(repalcementp); return; } } if (elsep->isOnes()) { // a ? b : 1 becomes ~a | b APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) { DfgOr* const repalcementp = new DfgOr{m_dfg, flp, dtypep}; DfgNot* const notp = new DfgNot{m_dfg, flp, dtypep}; notp->srcp(condp); repalcementp->lhsp(notp); repalcementp->rhsp(thenp); vtxp->replaceWith(repalcementp); return; } } } } #undef APPLYING // Process one vertex. Return true if graph changed bool processVertex(DfgVertex& vtx) { // Keep DfgVars in this pass, we will remove them later if they become redundant // Note: We want to keep the original variables for non-var vertices that drive multiple // sinks (otherwise we would need to introduce a temporary, it is better for debugging to // keep the original variable name, if one is available), so we can't remove redundant // variables here. if (vtx.is()) return false; // If it has no sinks (unused), we can remove it if (!vtx.hasSinks()) { vtx.unlinkDelete(m_dfg); return true; } // Transform node m_changed = false; iterate(&vtx); if (!vtx.hasSinks()) vtx.unlinkDelete(m_dfg); // If it became unused, we can remove it return m_changed; } V3DfgPeephole(DfgGraph& dfg, V3DfgPeepholeContext& ctx) : m_dfg{dfg} , m_ctx{ctx} {} public: static void apply(DfgGraph& dfg, V3DfgPeepholeContext& ctx) { V3DfgPeephole visitor{dfg, ctx}; dfg.runToFixedPoint([&](DfgVertex& vtx) { return visitor.processVertex(vtx); }); } }; void V3DfgPasses::peephole(DfgGraph& dfg, V3DfgPeepholeContext& ctx) { V3DfgPeephole::apply(dfg, ctx); }