DFG: Special case representation of AstSel
AstSel is a ternary node, but the 'widthp' is always constant and is hence redundant, and 'lsbp' is very often constant. As AstSel is fairly common, we special case as a DfgSel for the constant 'lsbp', and as 'DfgMux` for the non-constant 'lsbp'.
This commit is contained in:
parent
0570cb8d9f
commit
29a080dd9b
|
|
@ -539,12 +539,6 @@ void DfgGraph::runToFixedPoint(std::function<bool(DfgVertex&)> f) {
|
||||||
|
|
||||||
static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; }
|
static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; }
|
||||||
|
|
||||||
static bool isSimpleSel(const DfgSel* vtxp) {
|
|
||||||
const DfgConst* const lp = vtxp->lsbp()->cast<DfgConst>();
|
|
||||||
const DfgConst* const wp = vtxp->widthp()->cast<DfgConst>();
|
|
||||||
return lp && wp && !lp->hasMultipleSinks() && !wp->hasMultipleSinks();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dump one DfgVertex in Graphviz format
|
// Dump one DfgVertex in Graphviz format
|
||||||
static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
||||||
|
|
||||||
|
|
@ -598,11 +592,6 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const DfgConst* const constVtxp = vtx.cast<DfgConst>()) {
|
if (const DfgConst* const constVtxp = vtx.cast<DfgConst>()) {
|
||||||
const bool feedsSimpleSel = !constVtxp->findSink<DfgVertex>([](const DfgVertex& v) { //
|
|
||||||
return !v.is<DfgSel>() || !isSimpleSel(v.as<DfgSel>());
|
|
||||||
});
|
|
||||||
if (feedsSimpleSel) return; // Will draw it in the sel node as it is very common
|
|
||||||
|
|
||||||
const V3Number& num = constVtxp->constp()->num();
|
const V3Number& num = constVtxp->constp()->num();
|
||||||
|
|
||||||
os << toDotId(vtx);
|
os << toDotId(vtx);
|
||||||
|
|
@ -620,20 +609,18 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const DfgSel* const selVtxp = vtx.cast<DfgSel>()) {
|
if (const DfgSel* const selVtxp = vtx.cast<DfgSel>()) {
|
||||||
if (isSimpleSel(selVtxp)) {
|
const uint32_t lsb = selVtxp->lsb();
|
||||||
const uint32_t lsb = selVtxp->lsbp()->as<DfgConst>()->toU32();
|
const uint32_t msb = lsb + selVtxp->width() - 1;
|
||||||
const uint32_t msb = lsb + selVtxp->width() - 1;
|
os << toDotId(vtx);
|
||||||
os << toDotId(vtx);
|
os << " [label=\"SEL\n_[" << msb << ":" << lsb << "]\nW" << vtx.width() << " / F"
|
||||||
os << " [label=\"SEL\n_[" << msb << ":" << lsb << "]\nW" << vtx.width() << " / F"
|
<< vtx.fanout() << '"';
|
||||||
<< vtx.fanout() << '"';
|
if (vtx.hasMultipleSinks()) {
|
||||||
if (vtx.hasMultipleSinks()) {
|
os << ", shape=doublecircle";
|
||||||
os << ", shape=doublecircle";
|
} else {
|
||||||
} else {
|
os << ", shape=circle";
|
||||||
os << ", shape=circle";
|
|
||||||
}
|
|
||||||
os << "]" << endl;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
os << "]" << endl;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
os << toDotId(vtx);
|
os << toDotId(vtx);
|
||||||
|
|
@ -656,16 +643,6 @@ static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, const string& hea
|
||||||
// Dump one DfgVertex and all of its source DfgEdges in Graphviz format
|
// Dump one DfgVertex and all of its source DfgEdges in Graphviz format
|
||||||
static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) {
|
static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) {
|
||||||
dumpDotVertex(os, vtx);
|
dumpDotVertex(os, vtx);
|
||||||
|
|
||||||
if (const DfgSel* const selVtxp = vtx.cast<const DfgSel>()) {
|
|
||||||
if (isSimpleSel(selVtxp)) {
|
|
||||||
UASSERT_OBJ(selVtxp->sourceEdge<0>()->sourcep() == selVtxp->fromp(), selVtxp,
|
|
||||||
"Operand ordering changed");
|
|
||||||
dumpDotEdge(os, *selVtxp->sourceEdge<0>(), "");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { //
|
vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { //
|
||||||
if (edge.sourcep()) {
|
if (edge.sourcep()) {
|
||||||
string headLabel;
|
string headLabel;
|
||||||
|
|
@ -852,14 +829,14 @@ DfgVertex::~DfgVertex() {
|
||||||
if (VN_IS(m_dtypep, UnpackArrayDType)) VL_DO_DANGLING(delete m_dtypep, m_dtypep);
|
if (VN_IS(m_dtypep, UnpackArrayDType)) VL_DO_DANGLING(delete m_dtypep, m_dtypep);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DfgVertex::selfEquals(const DfgVertex& that) const {
|
bool DfgVertex::selfEquals(const DfgVertex& that) const { return true; }
|
||||||
return this->m_type == that.m_type && this->dtypep() == that.dtypep();
|
|
||||||
}
|
|
||||||
|
|
||||||
V3Hash DfgVertex::selfHash() const { return V3Hash{m_type} + width(); }
|
V3Hash DfgVertex::selfHash() const { return V3Hash{}; }
|
||||||
|
|
||||||
bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const {
|
bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const {
|
||||||
if (this == &that) return true;
|
if (this == &that) return true;
|
||||||
|
if (this->type() != that.type()) return false;
|
||||||
|
if (this->dtypep() != that.dtypep()) return false;
|
||||||
if (!this->selfEquals(that)) return false;
|
if (!this->selfEquals(that)) return false;
|
||||||
|
|
||||||
const auto key = (this < &that) ? EqualsCache::key_type{this, &that} //
|
const auto key = (this < &that) ? EqualsCache::key_type{this, &that} //
|
||||||
|
|
@ -893,6 +870,8 @@ V3Hash DfgVertex::hash() {
|
||||||
V3Hash& result = user<V3Hash>();
|
V3Hash& result = user<V3Hash>();
|
||||||
if (!result.value()) {
|
if (!result.value()) {
|
||||||
V3Hash hash;
|
V3Hash hash;
|
||||||
|
hash += m_type;
|
||||||
|
hash += width();
|
||||||
hash += selfHash();
|
hash += selfHash();
|
||||||
// Variables are defined by themselves, so there is no need to hash the sources. This
|
// Variables are defined by themselves, so there is no need to hash the sources. This
|
||||||
// enables sound hashing of graphs circular only through variables, which we rely on.
|
// enables sound hashing of graphs circular only through variables, which we rely on.
|
||||||
|
|
@ -933,13 +912,25 @@ void DfgVertex::replaceWith(DfgVertex* newSorucep) {
|
||||||
// Vertex classes
|
// Vertex classes
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// DfgConst ----------
|
||||||
|
|
||||||
|
bool DfgConst::selfEquals(const DfgVertex& that) const {
|
||||||
|
return constp()->sameTree(that.as<DfgConst>()->constp());
|
||||||
|
}
|
||||||
|
|
||||||
|
V3Hash DfgConst::selfHash() const { return m_constp->num().toHash(); }
|
||||||
|
|
||||||
|
// DfgSel ----------
|
||||||
|
|
||||||
|
bool DfgSel::selfEquals(const DfgVertex& that) const { return lsb() == that.as<DfgSel>()->lsb(); }
|
||||||
|
|
||||||
|
V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; }
|
||||||
|
|
||||||
// DfgVertexVar ----------
|
// DfgVertexVar ----------
|
||||||
|
|
||||||
bool DfgVertexVar::selfEquals(const DfgVertex& that) const {
|
bool DfgVertexVar::selfEquals(const DfgVertex& that) const {
|
||||||
if (const DfgVertexVar* otherp = that.cast<DfgVertexVar>()) {
|
UASSERT_OBJ(varp() != that.as<DfgVertexVar>()->varp(), this,
|
||||||
UASSERT_OBJ(varp() != otherp->varp(), this,
|
"There should only be one DfgVarPacked for a given AstVar");
|
||||||
"There should only be one DfgVarPacked for a given AstVar");
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -950,17 +941,6 @@ V3Hash DfgVertexVar::selfHash() const {
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DfgConst ----------
|
|
||||||
|
|
||||||
bool DfgConst::selfEquals(const DfgVertex& that) const {
|
|
||||||
if (const DfgConst* otherp = that.cast<DfgConst>()) {
|
|
||||||
return constp()->sameTree(otherp->constp());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
V3Hash DfgConst::selfHash() const { return m_constp->num().toHash(); }
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// DfgVisitor
|
// DfgVisitor
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -187,15 +187,12 @@ class AstToDfgVisitor final : public VNVisitor {
|
||||||
if (AstConcat* const concatp = VN_CAST(nodep, Concat)) {
|
if (AstConcat* const concatp = VN_CAST(nodep, Concat)) {
|
||||||
AstNode* const lhsp = concatp->lhsp();
|
AstNode* const lhsp = concatp->lhsp();
|
||||||
AstNode* const rhsp = concatp->rhsp();
|
AstNode* const rhsp = concatp->rhsp();
|
||||||
const uint32_t lWidth = lhsp->width();
|
|
||||||
const uint32_t rWidth = rhsp->width();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
FileLine* const lFlp = lhsp->fileline();
|
FileLine* const lFlp = lhsp->fileline();
|
||||||
DfgSel* const lVtxp = new DfgSel{*m_dfgp, lFlp, DfgVertex::dtypeFor(lhsp)};
|
DfgSel* const lVtxp = new DfgSel{*m_dfgp, lFlp, DfgVertex::dtypeFor(lhsp)};
|
||||||
lVtxp->fromp(vtxp);
|
lVtxp->fromp(vtxp);
|
||||||
lVtxp->lsbp(new DfgConst{*m_dfgp, new AstConst{lFlp, rWidth}});
|
lVtxp->lsb(rhsp->width());
|
||||||
lVtxp->widthp(new DfgConst{*m_dfgp, new AstConst{lFlp, lWidth}});
|
|
||||||
if (!convertAssignment(flp, lhsp, lVtxp)) return false;
|
if (!convertAssignment(flp, lhsp, lVtxp)) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,8 +200,7 @@ class AstToDfgVisitor final : public VNVisitor {
|
||||||
FileLine* const rFlp = rhsp->fileline();
|
FileLine* const rFlp = rhsp->fileline();
|
||||||
DfgSel* const rVtxp = new DfgSel{*m_dfgp, rFlp, DfgVertex::dtypeFor(rhsp)};
|
DfgSel* const rVtxp = new DfgSel{*m_dfgp, rFlp, DfgVertex::dtypeFor(rhsp)};
|
||||||
rVtxp->fromp(vtxp);
|
rVtxp->fromp(vtxp);
|
||||||
rVtxp->lsbp(new DfgConst{*m_dfgp, new AstConst{rFlp, 0u}});
|
rVtxp->lsb(0);
|
||||||
rVtxp->widthp(new DfgConst{*m_dfgp, new AstConst{rFlp, rWidth}});
|
|
||||||
return convertAssignment(flp, rhsp, rVtxp);
|
return convertAssignment(flp, rhsp, rVtxp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -467,6 +463,36 @@ class AstToDfgVisitor final : public VNVisitor {
|
||||||
nodep->user1p(vtxp);
|
nodep->user1p(vtxp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void visit(AstSel* nodep) override {
|
||||||
|
UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");
|
||||||
|
if (unhandled(nodep)) return;
|
||||||
|
if (!VN_IS(nodep->widthp(), Const)) { // This should never be taken, but paranoia
|
||||||
|
m_foundUnhandled = true;
|
||||||
|
++m_ctx.m_nonRepNode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
iterate(nodep->fromp());
|
||||||
|
if (m_foundUnhandled) return;
|
||||||
|
|
||||||
|
FileLine* const flp = nodep->fileline();
|
||||||
|
DfgVertex* vtxp = nullptr;
|
||||||
|
if (AstConst* const constp = VN_CAST(nodep->lsbp(), Const)) {
|
||||||
|
DfgSel* const selp = new DfgSel{*m_dfgp, flp, DfgVertex::dtypeFor(nodep)};
|
||||||
|
selp->fromp(nodep->fromp()->user1u().to<DfgVertex*>());
|
||||||
|
selp->lsb(constp->toUInt());
|
||||||
|
vtxp = selp;
|
||||||
|
} else {
|
||||||
|
iterate(nodep->lsbp());
|
||||||
|
if (m_foundUnhandled) return;
|
||||||
|
DfgMux* const muxp = new DfgMux{*m_dfgp, flp, DfgVertex::dtypeFor(nodep)};
|
||||||
|
muxp->fromp(nodep->fromp()->user1u().to<DfgVertex*>());
|
||||||
|
muxp->lsbp(nodep->lsbp()->user1u().to<DfgVertex*>());
|
||||||
|
vtxp = muxp;
|
||||||
|
}
|
||||||
|
m_uncommittedVertices.push_back(vtxp);
|
||||||
|
nodep->user1p(vtxp);
|
||||||
|
}
|
||||||
|
|
||||||
// The rest of the 'visit' methods are generated by 'astgen'
|
// The rest of the 'visit' methods are generated by 'astgen'
|
||||||
#include "V3Dfg__gen_ast_to_dfg.h"
|
#include "V3Dfg__gen_ast_to_dfg.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -332,6 +332,22 @@ class DfgToAstVisitor final : DfgVisitor {
|
||||||
m_resultp = vtxp->constp()->cloneTree(false);
|
m_resultp = vtxp->constp()->cloneTree(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void visit(DfgSel* vtxp) override {
|
||||||
|
FileLine* const flp = vtxp->fileline();
|
||||||
|
AstNodeMath* const fromp = convertSource(vtxp->fromp());
|
||||||
|
AstConst* const lsbp = new AstConst{flp, vtxp->lsb()};
|
||||||
|
AstConst* const widthp = new AstConst{flp, vtxp->width()};
|
||||||
|
m_resultp = new AstSel{flp, fromp, lsbp, widthp};
|
||||||
|
}
|
||||||
|
|
||||||
|
void visit(DfgMux* vtxp) override {
|
||||||
|
FileLine* const flp = vtxp->fileline();
|
||||||
|
AstNodeMath* const fromp = convertSource(vtxp->fromp());
|
||||||
|
AstNodeMath* const lsbp = convertSource(vtxp->lsbp());
|
||||||
|
AstConst* const widthp = new AstConst{flp, vtxp->width()};
|
||||||
|
m_resultp = new AstSel{flp, fromp, lsbp, widthp};
|
||||||
|
}
|
||||||
|
|
||||||
// The rest of the 'visit' methods are generated by 'astgen'
|
// The rest of the 'visit' methods are generated by 'astgen'
|
||||||
#include "V3Dfg__gen_dfg_to_ast.h"
|
#include "V3Dfg__gen_dfg_to_ast.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -682,6 +682,170 @@ class V3DfgPeephole final : public DfgVisitor {
|
||||||
|
|
||||||
void visit(DfgRedXor* vtxp) override { optimizeReduction(vtxp); }
|
void visit(DfgRedXor* vtxp) override { optimizeReduction(vtxp); }
|
||||||
|
|
||||||
|
void visit(DfgSel* vtxp) override {
|
||||||
|
DfgVertex* const fromp = vtxp->fromp();
|
||||||
|
|
||||||
|
FileLine* const flp = vtxp->fileline();
|
||||||
|
|
||||||
|
const uint32_t lsb = vtxp->lsb();
|
||||||
|
const uint32_t width = vtxp->width();
|
||||||
|
const uint32_t msb = lsb + width - 1;
|
||||||
|
|
||||||
|
if (DfgConst* const constp = fromp->cast<DfgConst>()) {
|
||||||
|
APPLYING(FOLD_SEL) {
|
||||||
|
DfgConst* const replacementp = makeZero(flp, width);
|
||||||
|
replacementp->num().opSel(constp->num(), msb, lsb);
|
||||||
|
vtxp->replaceWith(replacementp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<DfgConcat>()) {
|
||||||
|
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->lsb(lsb - rhsp->width());
|
||||||
|
}
|
||||||
|
} else if (lsb == 0 || msb == concatp->width() - 1 //
|
||||||
|
|| lhsp->is<DfgConst>() || rhsp->is<DfgConst>() //
|
||||||
|
|| !concatp->hasMultipleSinks()) {
|
||||||
|
// 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, or this concat has no other
|
||||||
|
// use, 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->lsb(0);
|
||||||
|
|
||||||
|
// The new Rhs vertex
|
||||||
|
DfgSel* const newRhsp = new DfgSel{m_dfg, flp, dtypeForWidth(rSelWidth)};
|
||||||
|
newRhsp->fromp(rhsp);
|
||||||
|
newRhsp->lsb(lsb);
|
||||||
|
|
||||||
|
// 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<DfgReplicate>()) {
|
||||||
|
// 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->lsb(newLsb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sel from Not
|
||||||
|
if (DfgNot* const notp = fromp->cast<DfgNot>()) {
|
||||||
|
// Replace "Sel from Not" with "Not of Sel"
|
||||||
|
if (!notp->hasMultipleSinks()) {
|
||||||
|
UASSERT_OBJ(notp->srcp()->dtypep() == notp->dtypep(), 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<DfgSel>()) {
|
||||||
|
APPLYING(REPLACE_SEL_FROM_SEL) {
|
||||||
|
// Make this Sel select from the source of the source Sel
|
||||||
|
vtxp->fromp(selp->fromp());
|
||||||
|
// Adjust LSB
|
||||||
|
vtxp->lsb(lsb + selp->lsb());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sel from Cond
|
||||||
|
if (DfgCond* const condp = fromp->cast<DfgCond>()) {
|
||||||
|
// If at least one of the branches are a constant, push the select past the cond
|
||||||
|
if (condp->thenp()->is<DfgConst>() || condp->elsep()->is<DfgConst>()) {
|
||||||
|
APPLYING(PUSH_SEL_THROUGH_COND) {
|
||||||
|
// The new 'then' vertex
|
||||||
|
DfgSel* const newThenp = new DfgSel{m_dfg, flp, vtxp->dtypep()};
|
||||||
|
newThenp->fromp(condp->thenp());
|
||||||
|
newThenp->lsb(lsb);
|
||||||
|
|
||||||
|
// The new 'else' vertex
|
||||||
|
DfgSel* const newElsep = new DfgSel{m_dfg, flp, vtxp->dtypep()};
|
||||||
|
newElsep->fromp(condp->elsep());
|
||||||
|
newElsep->lsb(lsb);
|
||||||
|
|
||||||
|
// 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<DfgShiftL>()) {
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// DfgVertexBinary - bitwise
|
// DfgVertexBinary - bitwise
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
|
@ -945,20 +1109,14 @@ class V3DfgPeephole final : public DfgVisitor {
|
||||||
if (lhsp->isZero()) {
|
if (lhsp->isZero()) {
|
||||||
DfgConst* const lConstp = lhsp->as<DfgConst>();
|
DfgConst* const lConstp = lhsp->as<DfgConst>();
|
||||||
if (DfgSel* const rSelp = rhsp->cast<DfgSel>()) {
|
if (DfgSel* const rSelp = rhsp->cast<DfgSel>()) {
|
||||||
if (DfgConst* const rSelLsbConstp = rSelp->lsbp()->cast<DfgConst>()) {
|
if (vtxp->dtypep() == rSelp->fromp()->dtypep()
|
||||||
if (vtxp->dtypep() == rSelp->fromp()->dtypep()
|
&& rSelp->lsb() == lConstp->width()) {
|
||||||
&& rSelLsbConstp->toU32() == lConstp->width()) {
|
APPLYING(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) {
|
||||||
const uint32_t rSelWidth = rSelp->widthp()->as<DfgConst>()->toU32();
|
DfgShiftR* const replacementp = new DfgShiftR{m_dfg, flp, vtxp->dtypep()};
|
||||||
UASSERT_OBJ(lConstp->width() + rSelWidth == vtxp->width(), vtxp,
|
replacementp->lhsp(rSelp->fromp());
|
||||||
"Inconsistent");
|
replacementp->rhsp(makeI32(flp, lConstp->width()));
|
||||||
APPLYING(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) {
|
vtxp->replaceWith(replacementp);
|
||||||
DfgShiftR* const replacementp
|
return;
|
||||||
= new DfgShiftR{m_dfg, flp, vtxp->dtypep()};
|
|
||||||
replacementp->lhsp(rSelp->fromp());
|
|
||||||
replacementp->rhsp(makeI32(flp, lConstp->width()));
|
|
||||||
vtxp->replaceWith(replacementp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -967,20 +1125,13 @@ class V3DfgPeephole final : public DfgVisitor {
|
||||||
if (rhsp->isZero()) {
|
if (rhsp->isZero()) {
|
||||||
DfgConst* const rConstp = rhsp->as<DfgConst>();
|
DfgConst* const rConstp = rhsp->as<DfgConst>();
|
||||||
if (DfgSel* const lSelp = lhsp->cast<DfgSel>()) {
|
if (DfgSel* const lSelp = lhsp->cast<DfgSel>()) {
|
||||||
if (DfgConst* const lSelLsbConstp = lSelp->lsbp()->cast<DfgConst>()) {
|
if (vtxp->dtypep() == lSelp->fromp()->dtypep() && lSelp->lsb() == 0) {
|
||||||
if (vtxp->dtypep() == lSelp->fromp()->dtypep()
|
APPLYING(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) {
|
||||||
&& lSelLsbConstp->toU32() == 0) {
|
DfgShiftL* const replacementp = new DfgShiftL{m_dfg, flp, vtxp->dtypep()};
|
||||||
const uint32_t lSelWidth = lSelp->widthp()->as<DfgConst>()->toU32();
|
replacementp->lhsp(lSelp->fromp());
|
||||||
UASSERT_OBJ(lSelWidth + rConstp->width() == vtxp->width(), vtxp,
|
replacementp->rhsp(makeI32(flp, rConstp->width()));
|
||||||
"Inconsistent");
|
vtxp->replaceWith(replacementp);
|
||||||
APPLYING(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) {
|
return;
|
||||||
DfgShiftL* const replacementp
|
|
||||||
= new DfgShiftL{m_dfg, flp, vtxp->dtypep()};
|
|
||||||
replacementp->lhsp(lSelp->fromp());
|
|
||||||
replacementp->rhsp(makeI32(flp, rConstp->width()));
|
|
||||||
vtxp->replaceWith(replacementp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1003,22 +1154,14 @@ class V3DfgPeephole final : public DfgVisitor {
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto joinSels = [this](DfgSel* lSelp, DfgSel* rSelp, FileLine* flp) -> DfgSel* {
|
const auto joinSels = [this](DfgSel* lSelp, DfgSel* rSelp, FileLine* flp) -> DfgSel* {
|
||||||
DfgConst* const lLsbp = lSelp->lsbp()->cast<DfgConst>();
|
if (lSelp->fromp()->equals(*rSelp->fromp())) {
|
||||||
DfgConst* const lWidthp = lSelp->widthp()->cast<DfgConst>();
|
if (lSelp->lsb() == rSelp->lsb() + rSelp->width()) {
|
||||||
DfgConst* const rLsbp = rSelp->lsbp()->cast<DfgConst>();
|
// Two consecutive Sels, make a single Sel.
|
||||||
DfgConst* const rWidthp = rSelp->widthp()->cast<DfgConst>();
|
const uint32_t width = lSelp->width() + rSelp->width();
|
||||||
if (lLsbp && lWidthp && rLsbp && rWidthp) {
|
DfgSel* const joinedSelp = new DfgSel{m_dfg, flp, dtypeForWidth(width)};
|
||||||
if (lSelp->fromp()->equals(*rSelp->fromp())) {
|
joinedSelp->fromp(rSelp->fromp());
|
||||||
if (lLsbp->toU32() == rLsbp->toU32() + rWidthp->toU32()) {
|
joinedSelp->lsb(rSelp->lsb());
|
||||||
// Two consecutive Sels, make a single Sel.
|
return joinedSelp;
|
||||||
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;
|
return nullptr;
|
||||||
|
|
@ -1252,187 +1395,6 @@ class V3DfgPeephole final : public DfgVisitor {
|
||||||
// DfgVertexTernary
|
// DfgVertexTernary
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
|
||||||
void visit(DfgSel* vtxp) override {
|
|
||||||
DfgVertex* const fromp = vtxp->fromp();
|
|
||||||
DfgConst* const lsbp = vtxp->lsbp()->cast<DfgConst>();
|
|
||||||
DfgConst* const widthp = vtxp->widthp()->cast<DfgConst>();
|
|
||||||
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");
|
|
||||||
|
|
||||||
if (DfgConst* const constp = fromp->cast<DfgConst>()) {
|
|
||||||
APPLYING(FOLD_SEL) {
|
|
||||||
DfgConst* const replacementp = makeZero(flp, width);
|
|
||||||
replacementp->num().opSel(constp->num(), msb, lsb);
|
|
||||||
vtxp->replaceWith(replacementp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<DfgConcat>()) {
|
|
||||||
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<DfgConst>() || rhsp->is<DfgConst>() //
|
|
||||||
|| !concatp->hasMultipleSinks()) {
|
|
||||||
// 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, or this concat has no other
|
|
||||||
// use, 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<DfgReplicate>()) {
|
|
||||||
// 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<DfgNot>()) {
|
|
||||||
// Replace "Sel from Not" with "Not of Sel"
|
|
||||||
if (!notp->hasMultipleSinks()) {
|
|
||||||
UASSERT_OBJ(notp->srcp()->dtypep() == notp->dtypep(), 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<DfgSel>()) {
|
|
||||||
UASSERT_OBJ(widthp->toU32() <= selp->width(), vtxp, "Out of bound Sel");
|
|
||||||
if (DfgConst* const sourceLsbp = selp->lsbp()->cast<DfgConst>()) {
|
|
||||||
UASSERT_OBJ(sourceLsbp->toI32() >= 0, selp, "negative");
|
|
||||||
UASSERT_OBJ(selp->widthp()->as<DfgConst>()->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<DfgCond>()) {
|
|
||||||
// If at least one of the branches are a constant, push the select past the cond
|
|
||||||
if (condp->thenp()->is<DfgConst>() || condp->elsep()->is<DfgConst>()) {
|
|
||||||
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<DfgShiftL>()) {
|
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void visit(DfgCond* vtxp) override {
|
void visit(DfgCond* vtxp) override {
|
||||||
UASSERT_OBJ(vtxp->dtypep() == vtxp->thenp()->dtypep(), vtxp, "Width mismatch");
|
UASSERT_OBJ(vtxp->dtypep() == vtxp->thenp()->dtypep(), vtxp, "Width mismatch");
|
||||||
UASSERT_OBJ(vtxp->dtypep() == vtxp->elsep()->dtypep(), vtxp, "Width mismatch");
|
UASSERT_OBJ(vtxp->dtypep() == vtxp->elsep()->dtypep(), vtxp, "Width mismatch");
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,47 @@ public:
|
||||||
} // LCOV_EXCL_STOP
|
} // LCOV_EXCL_STOP
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// === DfgVertexBinary ===
|
||||||
|
class DfgMux final : public DfgVertexBinary {
|
||||||
|
// AstSel is ternary, but the 'widthp' is always constant and is hence redundant, and
|
||||||
|
// 'lsbp' is very often constant. As AstSel is fairly common, we special case as a DfgSel for
|
||||||
|
// the constant 'lsbp', and as 'DfgMux` for the non-constant 'lsbp'.
|
||||||
|
public:
|
||||||
|
DfgMux(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep)
|
||||||
|
: DfgVertexBinary{dfg, dfgType(), flp, dtypep} {}
|
||||||
|
ASTGEN_MEMBERS_DfgMux;
|
||||||
|
|
||||||
|
DfgVertex* fromp() const { return source<0>(); }
|
||||||
|
void fromp(DfgVertex* vtxp) { relinkSource<0>(vtxp); }
|
||||||
|
DfgVertex* lsbp() const { return source<1>(); }
|
||||||
|
void lsbp(DfgVertex* vtxp) { relinkSource<1>(vtxp); }
|
||||||
|
|
||||||
|
const string srcName(size_t idx) const override { return idx ? "lsbp" : "fromp"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// === DfgVertexUnary ===
|
||||||
|
class DfgSel final : public DfgVertexUnary {
|
||||||
|
// AstSel is ternary, but the 'widthp' is always constant and is hence redundant, and
|
||||||
|
// 'lsbp' is very often constant. As AstSel is fairly common, we special case as a DfgSel for
|
||||||
|
// the constant 'lsbp', and as 'DfgMux` for the non-constant 'lsbp'.
|
||||||
|
uint32_t m_lsb = 0; // The LSB index
|
||||||
|
|
||||||
|
bool selfEquals(const DfgVertex& that) const override;
|
||||||
|
V3Hash selfHash() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DfgSel(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep)
|
||||||
|
: DfgVertexUnary{dfg, dfgType(), flp, dtypep} {}
|
||||||
|
ASTGEN_MEMBERS_DfgSel;
|
||||||
|
|
||||||
|
DfgVertex* fromp() const { return source<0>(); }
|
||||||
|
void fromp(DfgVertex* vtxp) { relinkSource<0>(vtxp); }
|
||||||
|
uint32_t lsb() const { return m_lsb; }
|
||||||
|
void lsb(uint32_t value) { m_lsb = value; }
|
||||||
|
|
||||||
|
const string srcName(size_t) const override { return "fromp"; }
|
||||||
|
};
|
||||||
|
|
||||||
// === DfgVertexVar ===
|
// === DfgVertexVar ===
|
||||||
class DfgVarArray final : public DfgVertexVar {
|
class DfgVarArray final : public DfgVertexVar {
|
||||||
friend class DfgVertex;
|
friend class DfgVertex;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue