Dump DFG patterns with --stats (#4889)

With --stats, we will print DFG pattern combinations, one per line, as
S-expressions to new stat files, together with their frequency, to aid
discovery of new peephole patterns.
This commit is contained in:
Geza Lore 2024-02-11 15:41:10 +00:00 committed by GitHub
parent d667b73e8d
commit cbc76a7816
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 412 additions and 2 deletions

View File

@ -1267,6 +1267,8 @@ Summary:
Creates a dump file with statistics on the design in
:file:`<prefix>__stats.txt`.
Also dumps DFG patterns to
:file:`<prefix>__stats_dfg_patterns__*.txt`.
.. option:: --stats-vars

View File

@ -25,6 +25,8 @@
#include "V3AstUserAllocator.h"
#include "V3Dfg.h"
#include "V3DfgPasses.h"
#include "V3DfgPatternStats.h"
#include "V3File.h"
#include "V3Graph.h"
#include "V3UniqueNames.h"
@ -252,6 +254,8 @@ void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) {
V3DfgOptimizationContext ctx{label};
V3DfgPatternStats patternStats;
// Run the optimization phase
for (AstNode* nodep = netlistp->modulesp(); nodep; nodep = nodep->nextp()) {
// Only optimize proper modules
@ -295,10 +299,34 @@ void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) {
dfg->addGraph(*component);
}
// Accumulate patterns from the optimized graph for reporting
if (v3Global.opt.stats()) patternStats.accumulate(*dfg);
// Convert back to Ast
if (dumpDfgLevel() >= 8) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-optimized");
AstModule* const resultModp = V3DfgPasses::dfgToAst(*dfg, ctx);
UASSERT_OBJ(resultModp == modp, modp, "Should be the same module");
}
// Print the collected patterns
if (v3Global.opt.stats()) {
// Label to lowercase, without spaces
std::string ident = label;
std::transform(ident.begin(), ident.end(), ident.begin(), [](unsigned char c) { //
return c == ' ' ? '_' : std::tolower(c);
});
// File to dump to
const std::string filename = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix()
+ "__stats_dfg_patterns__" + ident + ".txt";
// Open, write, close
std::ofstream* const ofp = V3File::new_ofstream(filename);
if (ofp->fail()) v3fatal("Can't write " << filename);
patternStats.dump(label, *ofp);
ofp->close();
VL_DO_DANGLING(delete ofp, ofp);
}
V3Global::dumpCheckGlobalTree("dfg-optimize", 0, dumpTreeEitherLevel() >= 3);
}

197
src/V3DfgPatternStats.h Normal file
View File

@ -0,0 +1,197 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Implementations of simple passes over DfgGraph
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2024 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
//
//*************************************************************************
#include "V3PchAstNoMT.h"
#include "V3Dfg.h"
#include "V3DfgPasses.h"
#include <algorithm>
#include <map>
#include <unordered_map>
class V3DfgPatternStats final {
static constexpr uint32_t MIN_PATTERN_DEPTH = 1;
static constexpr uint32_t MAX_PATTERN_DEPTH = 4;
std::map<std::string, std::string> m_internedConsts; // Interned constants
std::map<const AstVar*, std::string> m_internedVars; // Interned variables
std::map<uint32_t, std::string> m_internedSelLsbs; // Interned lsb value for selects
std::map<uint32_t, std::string> m_internedWordWidths; // Interned widths
std::map<uint32_t, std::string> m_internedWideWidths; // Interned widths
std::map<const DfgVertex*, std::string> m_internedVertices; // Interned vertices
// Maps from pattern to the number of times it appears, for each pattern depth
std::vector<std::unordered_map<std::string, size_t>> m_patterCounts{MAX_PATTERN_DEPTH + 1};
static std::string toLetters(size_t value, bool lowerCase = false) {
const char base = lowerCase ? 'a' : 'A';
std::string s;
do { s += static_cast<char>(base + value % 26); } while (value /= 26);
return s;
}
const std::string& internConst(const DfgConst& vtx) {
const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "c");
if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1);
return pair.first->second;
}
const std::string& internVar(const DfgVertexVar& vtx) {
const auto pair = m_internedVars.emplace(vtx.varp(), "v");
if (pair.second) pair.first->second += toLetters(m_internedVars.size() - 1);
return pair.first->second;
}
const std::string& internSelLsb(uint32_t value) {
const auto pair = m_internedSelLsbs.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1);
return pair.first->second;
}
const std::string& internWordWidth(uint32_t value) {
const auto pair = m_internedWordWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true);
return pair.first->second;
}
const std::string& internWideWidth(uint32_t value) {
const auto pair = m_internedWideWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1);
return pair.first->second;
}
const std::string& internVertex(const DfgVertex& vtx) {
const auto pair = m_internedVertices.emplace(&vtx, "_");
if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1);
return pair.first->second;
}
// Render the vertx into ss, and return true if the recursion reached the given depth,
// meaning an S-expression with that nesting level has been rendered.
bool render(std::ostringstream& ss, const DfgVertex& vtx, uint32_t depth) {
bool deep = depth == 0;
if (const DfgConst* const constp = vtx.cast<DfgConst>()) {
// Base case 1: constant
if (constp->isZero()) {
ss << "'0";
} else if (constp->isOnes()) {
ss << "'1";
} else {
ss << internConst(*constp);
}
} else if (const DfgVertexVar* const varp = vtx.cast<DfgVertexVar>()) {
// Base case 2: variable
ss << internVar(*varp);
} else if (depth == 0) {
// Base case 3: deep vertex
ss << internVertex(vtx);
} else {
// Recursively print an S-expression for the vertex
// S-expression begin
ss << '(';
// Name
ss << vtx.typeName();
// Specials
if (const DfgSel* const selp = vtx.cast<DfgSel>()) {
ss << '@';
if (selp->lsb() == 0) {
ss << '0';
} else {
ss << internSelLsb(selp->lsb());
}
}
// Operands
vtx.forEachSource([&](const DfgVertex& src) {
ss << ' ';
if (render(ss, src, depth - 1)) deep = true;
});
// S-expression end
ss << ')';
// Mark it if it has multiple sinks
if (vtx.hasMultipleSinks()) ss << '*';
}
// Annotate type
ss << ':';
const AstNodeDType* const dtypep = vtx.dtypep();
if (!VN_IS(dtypep, BasicDType)) {
dtypep->dumpSmall(ss);
} else {
const uint32_t width = dtypep->width();
if (width == 1) {
ss << '1';
} else if (width <= VL_QUADSIZE) {
ss << internWordWidth(width);
} else {
ss << internWideWidth(width);
}
}
// Done
return deep;
}
public:
V3DfgPatternStats() = default;
void accumulate(const DfgGraph& dfg) {
dfg.forEachVertex([&](const DfgVertex& vtx) {
for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) {
std::ostringstream ss;
if (render(ss, vtx, i)) m_patterCounts[i][ss.str()] += 1;
m_internedConsts.clear();
m_internedVars.clear();
m_internedSelLsbs.clear();
m_internedWordWidths.clear();
m_internedWideWidths.clear();
m_internedVertices.clear();
}
});
}
void dump(const std::string& stage, std::ostream& os) {
using Line = std::pair<std::string, size_t>;
for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) {
os << "DFG '" << stage << "' patterns with depth " << i << '\n';
// Pick up pattern accumulators with given depth
const auto& patternCounts = m_patterCounts[i];
// Sort patterns, first by descending frequency, then lexically
std::vector<Line> lines;
lines.reserve(patternCounts.size());
for (const auto& pair : patternCounts) lines.emplace_back(pair);
std::sort(lines.begin(), lines.end(), [](const Line& a, const Line& b) {
if (a.second != b.second) return a.second > b.second;
return a.first < b.first;
});
// Print each pattern
for (const auto& line : lines) {
os << ' ' << std::setw(12) << std::right << line.second;
os << ' ' << std::left << line.first << '\n';
}
// Trailing new-line to separate sections
os << '\n';
}
}
};

View File

@ -279,7 +279,7 @@ static void process() {
if (v3Global.opt.fDfgPreInline()) {
// Pre inline DFG optimization
V3DfgOptimizer::optimize(v3Global.rootp(), " pre inline");
V3DfgOptimizer::optimize(v3Global.rootp(), "pre inline");
}
if (!(v3Global.opt.serializeOnly() && !v3Global.opt.flatten())) {

View File

@ -0,0 +1,37 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
module t (
input wire [3:0] a,
input wire [3:0] b,
input wire [3:0] c,
output wire [3:0] x,
output wire [3:0] y,
output wire [3:0] z,
output wire [ 0:0] w1,
output wire [ 7:0] w8,
output wire [15:0] w16,
output wire [31:0] w32,
output wire [63:0] w64a,
output wire [63:0] w64b,
output wire [62:0] w63,
output wire [95:0] w96
);
assign x = ~a & ~b;
assign y = ~b & ~c;
assign z = ~c & ~a;
assign w1 = x[0];
assign w8 = {8{x[1]}};
assign w16 = {2{w8}};
assign w32 = {2{w16}};
assign w64a = {2{w32}};
assign w64b = {2{~w32}};
assign w63 = 63'({2{~w32}});
assign w96 = 96'(w64a);
endmodule

View File

@ -0,0 +1,50 @@
DFG 'post inline' patterns with depth 1
3 (NOT vA:a)*:a
2 (AND _A:a _B:a):a
2 (REPLICATE _A:a cA:a)*:b
1 (AND _A:a _B:a)*:a
1 (CONCAT '0:a _A:b):A
1 (NOT _A:a):a
1 (REPLICATE _A:1 cA:a)*:b
1 (REPLICATE _A:a cA:b)*:b
1 (REPLICATE _A:a cA:b)*:c
1 (SEL@0 _A:a):1
1 (SEL@0 _A:a):b
1 (SEL@A _A:a):1
DFG 'post inline' patterns with depth 2
2 (AND (NOT vA:a)*:a (NOT vB:a)*:a):a
1 (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a
1 (CONCAT '0:a (REPLICATE _A:a cA:a)*:b):A
1 (NOT (REPLICATE _A:a cA:b)*:b):b
1 (REPLICATE (NOT _A:a):a cA:a)*:b
1 (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c
1 (REPLICATE (REPLICATE _A:a cA:b)*:b cA:b)*:c
1 (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b
1 (REPLICATE (SEL@A _A:a):1 cA:b)*:c
1 (SEL@0 (AND _A:a _B:a)*:a):1
1 (SEL@0 (REPLICATE _A:a cA:a)*:b):c
1 (SEL@A (AND _A:a _B:a)*:a):1
DFG 'post inline' patterns with depth 3
1 (CONCAT '0:a (REPLICATE (REPLICATE _A:b cA:a)*:a cA:a)*:c):A
1 (NOT (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b):b
1 (REPLICATE (NOT (REPLICATE _A:a cA:b)*:b):b cA:b)*:c
1 (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a
1 (REPLICATE (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b cA:b)*:d
1 (REPLICATE (REPLICATE (SEL@A _A:a):1 cA:b)*:c cB:b)*:d
1 (REPLICATE (SEL@A (AND _A:a _B:a)*:a):1 cA:b)*:c
1 (SEL@0 (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1
1 (SEL@0 (REPLICATE (NOT _A:a):a cA:a)*:b):c
1 (SEL@A (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1
DFG 'post inline' patterns with depth 4
1 (CONCAT '0:a (REPLICATE (REPLICATE (REPLICATE _A:b cA:a)*:c cA:a)*:a cA:a)*:d):A
1 (NOT (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a):a
1 (REPLICATE (NOT (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b):b cA:b)*:d
1 (REPLICATE (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a cB:a)*:d
1 (REPLICATE (REPLICATE (REPLICATE (SEL@A _A:a):1 cA:b)*:c cB:b)*:d cB:b)*:b
1 (REPLICATE (REPLICATE (SEL@A (AND _A:a _B:a)*:a):1 cA:b)*:c cB:b)*:d
1 (REPLICATE (SEL@A (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1 cA:b)*:c
1 (SEL@0 (REPLICATE (NOT (REPLICATE _A:a cA:b)*:b):b cA:b)*:c):d

View File

@ -0,0 +1,23 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 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(vlt => 1);
top_filename("t/t_dfg_stats_patterns.v");
compile(
verilator_flags2 => ["--stats --no-skip-identical -fno-dfg-pre-inline"],
);
my $f = glob_one("$Self->{obj_dir}/$Self->{vm_prefix}__stats_dfg_patterns*");
files_identical($f, $Self->{golden_filename});
ok(1);
1;

View File

@ -0,0 +1,50 @@
DFG 'pre inline' patterns with depth 1
3 (NOT vA:a)*:a
2 (AND _A:a _B:a):a
2 (REPLICATE _A:a cA:a)*:b
1 (AND _A:a _B:a)*:a
1 (CONCAT '0:a _A:b):A
1 (NOT _A:a):a
1 (REPLICATE _A:1 cA:a)*:b
1 (REPLICATE _A:a cA:b)*:b
1 (REPLICATE _A:a cA:b)*:c
1 (SEL@0 _A:a):1
1 (SEL@0 _A:a):b
1 (SEL@A _A:a):1
DFG 'pre inline' patterns with depth 2
2 (AND (NOT vA:a)*:a (NOT vB:a)*:a):a
1 (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a
1 (CONCAT '0:a (REPLICATE _A:a cA:a)*:b):A
1 (NOT (REPLICATE _A:a cA:b)*:b):b
1 (REPLICATE (NOT _A:a):a cA:a)*:b
1 (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c
1 (REPLICATE (REPLICATE _A:a cA:b)*:b cA:b)*:c
1 (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b
1 (REPLICATE (SEL@A _A:a):1 cA:b)*:c
1 (SEL@0 (AND _A:a _B:a)*:a):1
1 (SEL@0 (REPLICATE _A:a cA:a)*:b):c
1 (SEL@A (AND _A:a _B:a)*:a):1
DFG 'pre inline' patterns with depth 3
1 (CONCAT '0:a (REPLICATE (REPLICATE _A:b cA:a)*:a cA:a)*:c):A
1 (NOT (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b):b
1 (REPLICATE (NOT (REPLICATE _A:a cA:b)*:b):b cA:b)*:c
1 (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a
1 (REPLICATE (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b cA:b)*:d
1 (REPLICATE (REPLICATE (SEL@A _A:a):1 cA:b)*:c cB:b)*:d
1 (REPLICATE (SEL@A (AND _A:a _B:a)*:a):1 cA:b)*:c
1 (SEL@0 (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1
1 (SEL@0 (REPLICATE (NOT _A:a):a cA:a)*:b):c
1 (SEL@A (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1
DFG 'pre inline' patterns with depth 4
1 (CONCAT '0:a (REPLICATE (REPLICATE (REPLICATE _A:b cA:a)*:c cA:a)*:a cA:a)*:d):A
1 (NOT (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a):a
1 (REPLICATE (NOT (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b):b cA:b)*:d
1 (REPLICATE (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a cB:a)*:d
1 (REPLICATE (REPLICATE (REPLICATE (SEL@A _A:a):1 cA:b)*:c cB:b)*:d cB:b)*:b
1 (REPLICATE (REPLICATE (SEL@A (AND _A:a _B:a)*:a):1 cA:b)*:c cB:b)*:d
1 (REPLICATE (SEL@A (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1 cA:b)*:c
1 (SEL@0 (REPLICATE (NOT (REPLICATE _A:a cA:b)*:b):b cA:b)*:c):d

View File

@ -0,0 +1,23 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 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(vlt => 1);
top_filename("t/t_dfg_stats_patterns.v");
compile(
verilator_flags2 => ["--stats --no-skip-identical -fno-dfg-post-inline"],
);
my $f = glob_one("$Self->{obj_dir}/$Self->{vm_prefix}__stats_dfg_patterns*");
files_identical($f, $Self->{golden_filename});
ok(1);
1;

View File

@ -14,7 +14,7 @@ compile(
verilator_flags2 => ["--stats"],
);
file_grep($Self->{stats}, qr/Optimizations, DFG pre inline Ast2Dfg, non-representable \(impure\)\s+(\d+)/i, 1);
file_grep($Self->{stats}, qr/Optimizations, DFG pre inline Ast2Dfg, non-representable \(impure\)\s+(\d+)/i, 1);
ok(1);
1;