Improve Reloop to accept constant index offsets. (#2939)

V3Reloop now can roll up indexed assignments between arrays if there is a
constant offset between indices on the left and right hand sides, e.g.:

a[0] = b[2];
a[1] = b[3];
...
a[x] = b[x + 2];
This commit is contained in:
Geza Lore 2021-05-10 18:01:11 +01:00 committed by GitHub
parent 45fbd98ff1
commit 267c6f6dce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 167 additions and 46 deletions

View File

@ -18,12 +18,12 @@
// Each CFunc:
// Look for a series of assignments that would look better in a loop:
//
// ASSIGN(ARRAYREF(var, #), ARRAYREF(var, #))
// ASSIGN(ARRAYREF(var, #+1), ARRAYREF(var, #+1))
// ASSIGN(ARRAYREF(var, #), ARRAYREF(var, #+C))
// ASSIGN(ARRAYREF(var, #+1), ARRAYREF(var, #+1+C))
// ->
// Create __Vilp local variable
// FOR(__Vilp = low; __Vilp <= high; ++__Vlip)
// ASSIGN(ARRAYREF(var, __Vilp), ARRAYREF(var, __Vilp))
// ASSIGN(ARRAYREF(var, __Vilp), ARRAYREF(var, __Vilp + C))
//
// Likewise vector assign to the same constant converted to a loop.
//
@ -61,6 +61,7 @@ private:
AstNodeSel* m_mgSelRp = nullptr; // Parent select, nullptr = constant
AstNodeVarRef* m_mgVarrefLp = nullptr; // Parent varref
AstNodeVarRef* m_mgVarrefRp = nullptr; // Parent varref, nullptr = constant
int64_t m_mgOffset = 0; // Index offset
AstConst* m_mgConstRp = nullptr; // Parent RHS constant, nullptr = sel
uint32_t m_mgIndexLo = 0; // Merge range
uint32_t m_mgIndexHi = 0; // Merge range
@ -83,38 +84,50 @@ private:
if (!m_mgAssignps.empty()) {
uint32_t items = m_mgIndexHi - m_mgIndexLo + 1;
UINFO(9, "End merge iter=" << items << " " << m_mgIndexHi << ":" << m_mgIndexLo << " "
<< m_mgAssignps[0] << endl);
<< m_mgOffset << " " << m_mgAssignps[0] << endl);
if (items >= RELOOP_MIN_ITERS) {
UINFO(6, "Reloop merging items=" << items << " " << m_mgIndexHi << ":"
<< m_mgIndexLo << " " << m_mgAssignps[0] << endl);
<< m_mgIndexLo << " " << m_mgOffset << " "
<< m_mgAssignps[0] << endl);
++m_statReloops;
m_statReItems += items;
// Transform first assign into for loop body
AstNodeAssign* bodyp = m_mgAssignps.front();
AstNodeAssign* const bodyp = m_mgAssignps.front();
UASSERT_OBJ(bodyp->lhsp() == m_mgSelLp, bodyp, "Corrupt queue/state");
FileLine* fl = bodyp->fileline();
AstVar* itp = findCreateVarTemp(fl, m_mgCfuncp);
FileLine* const fl = bodyp->fileline();
AstVar* const itp = findCreateVarTemp(fl, m_mgCfuncp);
AstNode* initp = new AstAssign(fl, new AstVarRef(fl, itp, VAccess::WRITE),
new AstConst(fl, m_mgIndexLo));
AstNode* condp = new AstLte(fl, new AstVarRef(fl, itp, VAccess::READ),
new AstConst(fl, m_mgIndexHi));
AstNode* incp = new AstAssign(
if (m_mgOffset > 0) {
UASSERT_OBJ(m_mgIndexLo >= m_mgOffset, bodyp,
"Reloop iteration starts at negative index");
m_mgIndexLo -= m_mgOffset;
m_mgIndexHi -= m_mgOffset;
}
AstNode* const initp = new AstAssign(fl, new AstVarRef(fl, itp, VAccess::WRITE),
new AstConst(fl, m_mgIndexLo));
AstNode* const condp = new AstLte(fl, new AstVarRef(fl, itp, VAccess::READ),
new AstConst(fl, m_mgIndexHi));
AstNode* const incp = new AstAssign(
fl, new AstVarRef(fl, itp, VAccess::WRITE),
new AstAdd(fl, new AstConst(fl, 1), new AstVarRef(fl, itp, VAccess::READ)));
AstWhile* whilep = new AstWhile(fl, condp, nullptr, incp);
AstWhile* const whilep = new AstWhile(fl, condp, nullptr, incp);
initp->addNext(whilep);
bodyp->replaceWith(initp);
whilep->addBodysp(bodyp);
// Replace constant index with new loop index
AstNode* lbitp = m_mgSelLp->bitp();
lbitp->replaceWith(new AstVarRef(fl, itp, VAccess::READ));
AstNode* const offsetp
= m_mgOffset == 0 ? nullptr : new AstConst(fl, std::abs(m_mgOffset));
AstNode* const lbitp = m_mgSelLp->bitp();
AstNode* const lvrefp = new AstVarRef(fl, itp, VAccess::READ);
lbitp->replaceWith(m_mgOffset > 0 ? new AstAdd(fl, lvrefp, offsetp) : lvrefp);
VL_DO_DANGLING(lbitp->deleteTree(), lbitp);
if (m_mgSelRp) { // else constant and no replace
AstNode* rbitp = m_mgSelRp->bitp();
rbitp->replaceWith(new AstVarRef(fl, itp, VAccess::READ));
AstNode* const rbitp = m_mgSelRp->bitp();
AstNode* const rvrefp = new AstVarRef(fl, itp, VAccess::READ);
rbitp->replaceWith(m_mgOffset < 0 ? new AstAdd(fl, rvrefp, offsetp) : rvrefp);
VL_DO_DANGLING(rbitp->deleteTree(), lbitp);
}
if (debug() >= 9) initp->dumpTree(cout, "-new: ");
@ -133,6 +146,7 @@ private:
m_mgSelRp = nullptr;
m_mgVarrefLp = nullptr;
m_mgVarrefRp = nullptr;
m_mgOffset = 0;
m_mgConstRp = nullptr;
}
}
@ -143,6 +157,7 @@ private:
{
m_cfuncp = nodep;
iterateChildren(nodep);
mergeEnd(); // Finish last pending merge, if any
}
}
virtual void visit(AstNodeAssign* nodep) override {
@ -155,7 +170,7 @@ private:
return;
}
// Of a constant index
AstConst* lbitp = VN_CAST(lselp->bitp(), Const);
AstConst* const lbitp = VN_CAST(lselp->bitp(), Const);
if (!lbitp) {
mergeEnd();
return;
@ -164,53 +179,58 @@ private:
mergeEnd();
return;
}
uint32_t index = lbitp->toUInt();
const uint32_t lindex = lbitp->toUInt();
// Of variable
AstNodeVarRef* lvarrefp = VN_CAST(lselp->fromp(), NodeVarRef);
AstNodeVarRef* const lvarrefp = VN_CAST(lselp->fromp(), NodeVarRef);
if (!lvarrefp) {
mergeEnd();
return;
}
// RHS is a constant or a select
AstConst* rconstp = VN_CAST(nodep->rhsp(), Const);
AstNodeSel* rselp = VN_CAST(nodep->rhsp(), NodeSel);
AstConst* const rconstp = VN_CAST(nodep->rhsp(), Const);
AstNodeSel* const rselp = VN_CAST(nodep->rhsp(), NodeSel);
AstNodeVarRef* rvarrefp = nullptr;
uint32_t rindex = lindex;
if (rconstp) { // Ok
} else {
if (!rselp) {
mergeEnd();
return;
}
AstConst* rbitp = VN_CAST(rselp->bitp(), Const);
} else if (rselp) {
AstConst* const rbitp = VN_CAST(rselp->bitp(), Const);
rvarrefp = VN_CAST(rselp->fromp(), NodeVarRef);
if (!rbitp || rbitp->toUInt() != index || !rvarrefp
|| lvarrefp->varp() == rvarrefp->varp()) {
if (!rbitp || !rvarrefp || lvarrefp->varp() == rvarrefp->varp()) {
mergeEnd();
return;
}
rindex = rbitp->toUInt();
} else {
mergeEnd();
return;
}
if (m_mgSelLp) { // Old merge
if (m_mgCfuncp == m_cfuncp && m_mgNextp == nodep && m_mgSelLp->same(lselp)
&& m_mgVarrefLp->same(lvarrefp)
&& (m_mgConstRp
? (rconstp && m_mgConstRp->same(rconstp))
: (rselp && m_mgSelRp->same(rselp) && m_mgVarrefRp->same(rvarrefp)))
&& (index == m_mgIndexLo - 1 || index == m_mgIndexHi + 1)) {
if (m_mgCfuncp == m_cfuncp // In same function
&& m_mgNextp == nodep // Consecutive node
&& m_mgVarrefLp->same(lvarrefp) // Same array on left hand side
&& (m_mgConstRp // On the right hand side either ...
? (rconstp && m_mgConstRp->same(rconstp)) // ... same constant
: (rselp && m_mgVarrefRp->same(rvarrefp))) // ... or same array
&& (lindex == m_mgIndexLo - 1 || lindex == m_mgIndexHi + 1) // Left index +/- 1
&& (m_mgConstRp || lindex == rindex + m_mgOffset) // Same right index offset
) {
// Sequentially next to last assign; continue merge
if (index == m_mgIndexLo - 1) {
m_mgIndexLo = index;
} else if (index == m_mgIndexHi + 1) {
m_mgIndexHi = index;
if (lindex == m_mgIndexLo - 1) {
m_mgIndexLo = lindex;
} else if (lindex == m_mgIndexHi + 1) {
m_mgIndexHi = lindex;
}
UINFO(9, "Continue merge i=" << index << " " << m_mgIndexHi << ":" << m_mgIndexLo
UINFO(9, "Continue merge i=" << lindex << " " << m_mgIndexHi << ":" << m_mgIndexLo
<< " " << nodep << endl);
m_mgAssignps.push_back(nodep);
m_mgNextp = nodep->nextp();
return;
} else {
// This assign doesn't merge with previous assign,
UINFO(9, "End merge i="
<< lindex << " " << m_mgIndexHi << ":" << m_mgIndexLo << " " << nodep
<< endl); // This assign doesn't merge with previous assign,
// but should start a new merge
mergeEnd();
}
@ -224,10 +244,11 @@ private:
m_mgSelRp = rselp;
m_mgVarrefLp = lvarrefp;
m_mgVarrefRp = rvarrefp;
m_mgOffset = static_cast<int64_t>(lindex) - static_cast<int64_t>(rindex);
m_mgConstRp = rconstp;
m_mgIndexLo = index;
m_mgIndexHi = index;
UINFO(9, "Start merge i=" << index << " " << nodep << endl);
m_mgIndexLo = lindex;
m_mgIndexHi = lindex;
UINFO(9, "Start merge i=" << lindex << " o=" << m_mgOffset << nodep << endl);
}
//--------------------
virtual void visit(AstVar*) override {} // Accelerate

View File

@ -0,0 +1,17 @@
shift down 1
oarray[63] is 0
oarray[62] is 63
oarray[61] is 62
oarray[32] is 33
oarray[ 2] is 3
oarray[ 1] is 2
oarray[ 0] is 1
shift up 2
oarray[63] is 61
oarray[62] is 60
oarray[61] is 59
oarray[32] is 30
oarray[ 2] is 0
oarray[ 1] is 2
oarray[ 0] is 1
*-* All Finished *-*

View File

@ -0,0 +1,33 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2003 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
scenarios(simulator => 1);
compile(
verilator_flags2 => ["-unroll-count 1024",
$Self->wno_unopthreads_for_few_cores(),
"--stats"],
);
execute(
check_finished => 1,
expect_filename => $Self->{golden_filename},
);
if ($Self->{vlt}) {
# Note, with vltmt this might be split differently, so only checking vlt
file_grep($Self->{stats}, qr/Optimizations, Reloop iterations\s+(\d+)/i,
125);
file_grep($Self->{stats}, qr/Optimizations, Reloops\s+(\d+)/i,
2);
}
ok(1);
1;

View File

@ -0,0 +1,50 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2020 by Geza Lore.
// SPDX-License-Identifier: CC0-1.0
`define show(x) $display("oarray[%2d] is %2d", x, oarray[x])
module t (/*AUTOARG*/);
int iarray [63:0];
int oarray [63:0];
initial begin
for (int i = 0; i < 64 ; i = i + 1) begin
iarray[i] = i;
oarray[i] = 0;
end
for (int i = 0; i < 63; i = i + 1) begin
oarray[i] = iarray[i + 1];
end
$display("shift down 1");
`show(63);
`show(62);
`show(61);
`show(32);
`show(2);
`show(1);
`show(0);
for (int i = 63; i >= 2 ; i = i - 1) begin
oarray[i] = iarray[i - 2];
end
$display("shift up 2");
`show(63);
`show(62);
`show(61);
`show(32);
`show(2);
`show(1);
`show(0);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule