Optimize dead functions without references (#6380)

This commit is contained in:
Artur Bieniek 2025-09-05 19:23:43 +02:00 committed by GitHub
parent a364704e3a
commit 905c0c4f6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 207 additions and 18 deletions

View File

@ -37,6 +37,8 @@
#include "V3Dead.h"
#include "V3Stats.h"
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -51,6 +53,7 @@ class DeadVisitor final : public VNVisitor {
// AstVar::user1() -> int. Count of number of references
// AstVarScope::user1() -> int. Count of number of references
// AstNodeDType::user1() -> int. Count of number of references
// AstNodeFTask::user1() -> int. Non-zero if ever referenced (called)
const VNUser1InUse m_inuser1;
// TYPES
@ -69,6 +72,7 @@ class DeadVisitor final : public VNVisitor {
std::vector<AstCell*> m_cellsp;
std::vector<AstClass*> m_classesp;
std::vector<AstTypedef*> m_typedefsp;
std::vector<AstNodeFTask*> m_tasksp; // All the tasks that could be removed if not called
AssignMap m_assignMap; // List of all simple assignments for each variable
bool m_sideEffect = false; // Side effects discovered in assign RHS
@ -78,6 +82,9 @@ class DeadVisitor final : public VNVisitor {
AstNodeModule* m_modp = nullptr; // Current module
AstSelLoopVars* m_selloopvarsp = nullptr; // Current loop vars
// STATE - Statistic tracking
VDouble0 m_statFTasksDeadified;
// METHODS
void deleting(AstNode* nodep) {
@ -166,6 +173,7 @@ class DeadVisitor final : public VNVisitor {
void visit(AstNodeFTaskRef* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
if (nodep->taskp()) nodep->taskp()->user1(1);
if (nodep->classOrPackagep()) {
if (m_elimCells) {
nodep->classOrPackagep(nullptr);
@ -174,9 +182,10 @@ class DeadVisitor final : public VNVisitor {
}
}
}
void visit(AstMethodCall* nodep) override {
void visit(AstModportFTaskRef* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
if (nodep->ftaskp()) nodep->ftaskp()->user1(1);
}
void visit(AstRefDType* nodep) override {
iterateChildren(nodep);
@ -312,6 +321,19 @@ class DeadVisitor final : public VNVisitor {
}
if (assignInAssign) m_sideEffect = true; // Parent assign shouldn't optimize
}
void visit(AstNodeFTask* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
if (!nodep->taskPublic() && !nodep->dpiExport() && !nodep->dpiImport())
m_tasksp.push_back(nodep);
if (nodep->classOrPackagep()) {
if (m_elimCells) {
nodep->classOrPackagep(nullptr);
} else {
nodep->classOrPackagep()->user1Inc();
}
}
}
//-----
void visit(AstClockingItem* nodep) override {
@ -342,6 +364,15 @@ class DeadVisitor final : public VNVisitor {
return m_elimCells && !typedefp->attrPublic();
}
void deadCheckTasks() {
for (AstNodeFTask* taskp : m_tasksp) {
if (!taskp->user1() && !taskp->classMethod()) {
deleting(taskp);
m_statFTasksDeadified++;
}
}
}
void deadCheckMod() {
// Kill any unused modules
// V3LinkCells has a graph that is capable of this too, but we need to do it
@ -515,7 +546,7 @@ class DeadVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
DeadVisitor(AstNetlist* nodep, bool elimUserVars, bool elimDTypes, bool elimScopes,
bool elimCells, bool elimTopIfaces)
bool elimCells, bool elimTopIfaces, bool elimTasks)
: m_elimUserVars{elimUserVars}
, m_elimDTypes{elimDTypes}
, m_elimCells{elimCells} {
@ -534,6 +565,7 @@ public:
if (itr.first->user1()) itr.second->user1Inc();
}
if (elimTasks) deadCheckTasks();
deadCheckTypedefs();
deadCheckVar();
// We only eliminate scopes when in a flattened structure
@ -549,7 +581,9 @@ public:
nodep->typeTablep()->repairCache();
VIsCached::clearCacheTree(); // Removing assignments may affect isPure
}
~DeadVisitor() override = default;
~DeadVisitor() override {
V3Stats::addStatSum("Optimizations, deadified FTasks", m_statFTasksDeadified);
};
};
//######################################################################
@ -558,31 +592,31 @@ public:
void V3Dead::deadifyModules(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ // node, elimUserVars, elimDTypes, elimScopes, elimCells, elimTopIfaces
DeadVisitor{nodep, false, false, false, false, !v3Global.opt.topIfacesSupported()};
DeadVisitor{nodep, false, false, false, false, !v3Global.opt.topIfacesSupported(), false};
} // Destruct before checking
V3Global::dumpCheckGlobalTree("deadModules", 0, dumpTreeEitherLevel() >= 6);
}
void V3Dead::deadifyDTypes(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ DeadVisitor{nodep, false, true, false, false, false}; } // Destruct before checking
{ DeadVisitor{nodep, false, true, false, false, false, true}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("deadDtypes", 0, dumpTreeEitherLevel() >= 3);
}
void V3Dead::deadifyDTypesScoped(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ DeadVisitor{nodep, false, true, true, false, false}; } // Destruct before checking
{ DeadVisitor{nodep, false, true, true, false, false, false}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("deadDtypesScoped", 0, dumpTreeEitherLevel() >= 3);
}
void V3Dead::deadifyAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ DeadVisitor{nodep, true, true, false, true, false}; } // Destruct before checking
{ DeadVisitor{nodep, true, true, false, true, false, false}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("deadAll", 0, dumpTreeEitherLevel() >= 3);
}
void V3Dead::deadifyAllScoped(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ DeadVisitor{nodep, true, true, true, true, false}; } // Destruct before checking
{ DeadVisitor{nodep, true, true, true, true, false, false}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("deadAllScoped", 0, dumpTreeEitherLevel() >= 3);
}

View File

@ -4582,11 +4582,6 @@ class WidthVisitor final : public VNVisitor {
// Either made explicitly or V3LinkDot made implicitly
classp->v3fatalSrc("Can't find class's new");
}
if (classp->isVirtual() || classp->isInterfaceClass()) {
nodep->v3error("Illegal to call 'new' using an abstract virtual class "
+ AstNode::prettyNameQ(classp->origName())
+ " (IEEE 1800-2023 8.21)");
}
} else { // super.new case
// in this case class and taskp() should be properly linked in V3LinkDot.cpp during
// "super" reference resolution

View File

@ -38,7 +38,11 @@ VL_DEFINE_DEBUG_FUNCTIONS;
class WidthCommitVisitor final : public VNVisitor {
// NODE STATE
// AstVar::user1p -> bool, processed
// AstNodeFTask::user2() -> int. Non-zero if ever referenced (called)
// AstNew::user2() -> int. Count of number of references, minus references in
// functions never called
const VNUser1InUse m_inuser1;
const VNUser2InUse m_inuser2;
// STATE
AstNodeModule* m_modp = nullptr;
@ -47,6 +51,8 @@ class WidthCommitVisitor final : public VNVisitor {
= false; // Writing a dynamically-sized array element, not the array itself
VMemberMap m_memberMap; // Member names cached for fast lookup
bool m_underSel = false; // Whether is currently under AstMemberSel or AstSel
std::vector<AstNew*> m_virtualNewsp; // Instantiations of virtual classes
std::vector<AstNodeFTask*> m_tasksp; // All the tasks, we will check if they are ever called
public:
// METHODS
@ -151,6 +157,20 @@ private:
}
}
void deadCheckTasks() {
for (AstNodeFTask* taskp : m_tasksp) {
if (!taskp->user2()) {
taskp->foreach([](AstNew* newp) { newp->user2Inc(-1); });
}
}
for (AstNew* newp : m_virtualNewsp) {
if (newp->user2() > 0)
newp->v3error("Illegal to call 'new' using an abstract virtual class "
+ AstNode::prettyNameQ(newp->classOrPackagep()->origName())
+ " (IEEE 1800-2023 8.21)");
}
}
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
@ -291,6 +311,8 @@ private:
nodep->virtRefDType2p(editOneDType(nodep->virtRefDType2p()));
}
void visit(AstNodeFTask* nodep) override {
if (!nodep->taskPublic() && !nodep->dpiExport() && !nodep->dpiImport())
m_tasksp.push_back(nodep);
iterateChildren(nodep);
editDType(nodep);
{
@ -371,6 +393,16 @@ private:
iterateChildren(nodep);
editDType(nodep);
classEncapCheck(nodep, nodep->taskp(), VN_CAST(nodep->classOrPackagep(), Class));
if (nodep->taskp()) nodep->taskp()->user2(1);
if (AstNew* const newp = VN_CAST(nodep, New)) {
if (!VN_IS(newp->backp(), Assign)) return;
if (AstClass* const classp = VN_CAST(newp->classOrPackagep(), Class)) {
if (classp->isVirtual() || classp->isInterfaceClass()) {
m_virtualNewsp.push_back(newp);
newp->user2Inc();
}
}
}
}
void visit(AstMemberSel* nodep) override {
{
@ -430,6 +462,7 @@ public:
// Were changing widthMin's, so the table is now somewhat trashed
nodep->typeTablep()->clearCache();
iterate(nodep);
deadCheckTasks();
// Don't want to AstTypeTable::repairCache, as all needed nodes
// have been added back in; a repair would prevent dead nodes from
// being detected

View File

@ -1,10 +1,10 @@
%Error: t/t_class_param_virtual_bad.v:13:11: Illegal to call 'new' using an abstract virtual class 'VBase' (IEEE 1800-2023 8.21)
: ... note: In instance 't'
13 | t = new;
| ^~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_class_param_virtual_bad.v:23:28: Illegal to call 'new' using an abstract virtual class 'ClsVirt' (IEEE 1800-2023 8.21)
: ... note: In instance 't'
23 | ClsVirt#(VBase) cv = new;
| ^~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_class_param_virtual_bad.v:13:11: Illegal to call 'new' using an abstract virtual class 'VBase' (IEEE 1800-2023 8.21)
: ... note: In instance 't'
13 | t = new;
| ^~~
%Error: Exiting due to

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 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
import vltest_bootstrap
test.scenarios('vlt')
test.lint()
test.compile()
test.passes()

View File

@ -0,0 +1,19 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
class cl #(type T= int);
function void f();
T obj = new;
endfunction
endclass
virtual class vcl;
endclass;
module t;
cl #(vcl) c = new;
initial begin
end
endmodule

View File

@ -0,0 +1,6 @@
%Error: t/t_func_virt_new_bad.v:9:13: Illegal to call 'new' using an abstract virtual class 'vcl' (IEEE 1800-2023 8.21)
: ... note: In instance 't'
9 | T obj = new;
| ^~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 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
import vltest_bootstrap
test.scenarios('vlt')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,19 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
class cl #(type T= int);
function void f();
T obj = new;
endfunction
endclass
virtual class vcl;
endclass;
module t;
cl #(vcl) c = new;
initial begin
c.f();
end
endmodule

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 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
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--stats"])
if test.vlt_all:
test.file_grep(test.stats, r'Optimizations, deadified FTasks\s+(\d+)', 2)
test.execute()
test.passes()

View File

@ -0,0 +1,28 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
module t (input clk);
// AstLet and AstProperty are also NodeFTasks, but lets are substituted earlier and properties should be "used" by their asserts so also not deadified
let nclk = ~clk;
assert property(@(posedge clk)1);
function void livefunc();
endfunction
task livetask;
endtask
// Tasks/functions that are called somewhere will not be deadified
initial begin
livefunc();
livetask();
$finish;
end
// These should be deadified
function deadfunc();
endfunction
task deadtask;
endtask
endmodule