diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 90a0d3ab4..3c9edefa0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -369,6 +369,7 @@ set(COMMON_SOURCES V3Unroll.cpp V3UnrollGen.cpp V3VariableOrder.cpp + V3Virtual.cpp V3Waiver.cpp V3Width.cpp V3WidthCommit.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index f5a728063..bb6b60a4d 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -348,6 +348,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Unknown.o \ V3Unroll.o \ V3UnrollGen.o \ + V3Virtual.o \ V3Width.o \ V3WidthCommit.o \ V3WidthSel.o \ diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index afce44efd..fb130c0db 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -29,6 +29,7 @@ #include "V3AstUserAllocator.h" #include "V3EmitCBase.h" #include "V3Sched.h" +#include "V3Virtual.h" #include @@ -192,6 +193,8 @@ class AwaitVisitor final : public VNVisitor { AstNodeStmt*& m_postUpdatesr; // Post updates for the trigger eval function // Additional var sensitivities std::map>& m_externalDomains; + std::unique_ptr + m_classGraphp; // class graph to get possibly called functions from a virtual call std::set m_processDomains; // Sentrees from the current process // Variables written by suspendable processes std::set m_writtenBySuspendable; @@ -307,7 +310,12 @@ class AwaitVisitor final : public VNVisitor { void visit(AstNodeCCall* const nodep) override { iterateChildren(nodep); // We need to visit bodies of non-inlined functions - visitCalledCFunc(nodep->funcp()); + const auto& cfuncps = m_classGraphp->getCallPossibleCFuncs(nodep); + if (cfuncps.empty()) { + visitCalledCFunc(nodep->funcp()); + } else { + for (AstCFunc* const cfuncp : cfuncps) visitCalledCFunc(cfuncp); + } } void visit(AstCFunc* const nodep) override { const auto& value = m_cfuncsCache(nodep); @@ -369,7 +377,8 @@ public: : m_scopeTopp{nodep->topScopep()->scopep()} , m_lbs{lbs} , m_postUpdatesr{postUpdatesr} - , m_externalDomains{externalDomains} { + , m_externalDomains{externalDomains} + , m_classGraphp{V3Virtual::buildClassGraph(nodep)} { iterate(nodep); } ~AwaitVisitor() override { diff --git a/src/V3Virtual.cpp b/src/V3Virtual.cpp new file mode 100644 index 000000000..b68877727 --- /dev/null +++ b/src/V3Virtual.cpp @@ -0,0 +1,226 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Virtual function calls - function finder +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// 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-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// Builds a graph of inheritance (implements & extends) +// Allows for lookups of what AstCFunc may be called +// due to a virtual function call +// +// Current limitation: This only may be called after V3Task +// when AstNodeFTasks are substituted with AstCFuncs +// +//************************************************************************* +// TODO: +// - Probably in future this phase shall have a check for overriding +// virtual functions with static functions - currently +// there is no such check - it shall happen before V3Task +// since it moves static functions outside classes +// - This shall check for error that is currently emitted in V3LinkDot +// `Class 'Derived' implements 'Base' but is missing implementation +// for 'function' (IEEE 1800-2023 8.26)` +// Sometimes this error is emitted and it is a false positive +// since the following snipped is correct but Verilator emits an error: +// +// class Base; +// virtual task foo(); +// endtask +// endclass +// +// interface class Iface; +// pure virtual task foo(); +// endclass +// +// class Derived extends Base implements Iface; +// // This is legal as it is but Verilator fails +// endclass +// +// Mentioned `IEEE 1800-2023 8.26` says: +// `When an interface class is implemented by a class, the required +// implementations of interface class methods may be +// provided by inherited virtual method implementations.` +// +// Therefore, this shall probably become a full phase +// and check for those errors somewhere before V3Task +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "V3Virtual.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +class V3ClassGraphVertex final : public V3GraphVertex { + VL_RTTI_IMPL(V3ClassGraphVertex, V3GraphVertex) + friend class ClassGraphBuilderVisitor; + enum Virtualization : uint8_t { + NON_VIRTUAL = 0, // Not a virtual member function + VIRTUAL_IMPLICIT, // virtual keyword is not next to a function definition the ancestor + // class has such virtual function therefore, it is treated as an + // virtual + VIRTUAL, // function explicitly defined as virtual with a keyword + UNDEFINED_VIRTUAL, // Function not declared in a current class however, it is inherited + // and it is virtual + // Note: non-virtual inherited function may not be described with this enum - no need for + // now as well as static functions + }; + struct FuncInfo final { + AstCFunc* const m_cfuncp; // pointer to a described function + std::unordered_set + m_cache; // set of functions that may be called due to a virtual call + Virtualization m_virtualization; // Whether function is virtual (see Virtualization) + bool m_cacheInitialized; // true when m_cache stores a valid value + FuncInfo() + : m_cfuncp{nullptr} + , m_virtualization{UNDEFINED_VIRTUAL} + , m_cacheInitialized{false} {} + FuncInfo(AstCFunc* const cfuncp, Virtualization virtualization) + : m_cfuncp{cfuncp} + , m_virtualization{virtualization} + , m_cacheInitialized{false} {} + }; + std::unordered_map + m_funcsVirtualization; // Map from function names to their description + const AstClass* const m_classp; // A class of which the function is a member of + +public: + explicit V3ClassGraphVertex(V3Graph* const graphp, const AstClass* const classp) + : V3GraphVertex{graphp} + , m_classp{classp} {} + ~V3ClassGraphVertex() override = default; + + std::string name() const override { return m_classp->name(); } + + void addFunc(AstCFunc* const cfuncp) { + m_funcsVirtualization.emplace(cfuncp->name(), + FuncInfo{cfuncp, cfuncp->isVirtual() + ? V3ClassGraphVertex::VIRTUAL + : V3ClassGraphVertex::NON_VIRTUAL}); + } + void propagateVirt(const V3ClassGraphVertex& from) { + for (auto funcs : from.m_funcsVirtualization) { + if (funcs.second.m_virtualization == NON_VIRTUAL) continue; + auto& vertex = m_funcsVirtualization[funcs.first]; + if (vertex.m_virtualization == NON_VIRTUAL) vertex.m_virtualization = VIRTUAL_IMPLICIT; + } + } + const std::unordered_set& getCallPossibleCFuncs(const std::string& name) & { + auto it = m_funcsVirtualization.find(name); + UASSERT(it != m_funcsVirtualization.end(), + "Unexpected call - CFunc with name '" << name << "' not found"); + FuncInfo& info = it->second; + if (!info.m_cacheInitialized) { + if (info.m_cfuncp) info.m_cache.insert(info.m_cfuncp); + if (info.m_virtualization != V3ClassGraphVertex::NON_VIRTUAL) { + for (V3GraphEdge& edge : outEdges()) { + V3ClassGraphVertex* const other = edge.top()->as(); + const std::unordered_set otherCalls + = other->getCallPossibleCFuncs(name); + info.m_cache.insert(otherCalls.begin(), otherCalls.end()); + } + } + info.m_cacheInitialized = true; + } + return info.m_cache; + } +}; + +V3ClassesGraphWrapper::V3ClassesGraphWrapper( + std::unique_ptr graph, + std::unordered_map vertexMap) + : m_graphp{std::move(graph)} + , m_vertexMap{std::move(vertexMap)} {} +V3ClassesGraphWrapper::~V3ClassesGraphWrapper() + = default; // Must be here because only this translation unit actually knows size + // of the V3ClassGraphVertex which is needed for ~unique_ptr() + +const std::unordered_set& +V3ClassesGraphWrapper::getCallPossibleCFuncs(const AstNodeCCall* const callp) const& { + // super references and AstCNew shall have AstNodeCCall::funcp() pointing to the only possible + // AstCFunc that may be called + if (callp->superReference() || VN_IS(callp, CNew)) return m_emptySet; + AstNode* nodep = callp->funcp(); + while (nodep && nodep->backp() + && (nodep->backp()->nextp() == nodep || VN_IS(nodep->backp(), Scope))) { + nodep = nodep->backp(); + } + AstClass* const classp = VN_CAST(nodep->backp(), Class); + if (!classp) return m_emptySet; // Not under class return empty set + auto it = m_vertexMap.find(classp); + UASSERT_OBJ(it != m_vertexMap.end(), classp, "Class not mapped"); + return it->second->getCallPossibleCFuncs(callp->funcp()->name()); +} + +// Build a Class graph and returns it in a V3ClassesGraphWrapper +class ClassGraphBuilderVisitor final : VNVisitor { + std::unique_ptr m_graphp; // Graph of classes + std::unordered_map + m_vertexMap; // Maps classes to their vertices + AstClass* m_classp = nullptr; // Current class + V3ClassGraphVertex* m_classVertexp = nullptr; // vertex corresponding to a current class - + // getClassVertex(m_classp) == m_classVertexp + + V3ClassGraphVertex* getClassVertex(const AstClass* const classp) { + auto it = m_vertexMap.find(classp); + if (it != m_vertexMap.end()) return it->second; + return m_vertexMap.emplace(classp, new V3ClassGraphVertex{m_graphp.get(), classp}) + .first->second; + } + + void visit(AstCFunc* const nodep) override { + if (!m_classp || nodep->isConstructor() || nodep->isDestructor()) return; + UASSERT_OBJ(m_classVertexp, nodep, + "If function is under class m_classVertexp shall be set"); + m_classVertexp->addFunc(nodep); + } + void visit(AstClassExtends* const nodep) override { + UASSERT_OBJ(m_classVertexp, nodep, + "m_classVertexp shall be set while visiting AstClassExtends"); + new V3GraphEdge{m_graphp.get(), + getClassVertex(VN_AS(nodep->childDTypep(), ClassRefDType)->classp()), + m_classVertexp, 1}; + } + void visit(AstNodeModule* const nodep) override { + VL_RESTORER(m_classp); + VL_RESTORER(m_classVertexp); + if ((m_classp = VN_CAST(nodep, Class))) m_classVertexp = getClassVertex(m_classp); + iterateChildren(nodep); + } + void visit(AstNode* const nodep) override { iterateChildren(nodep); } + +public: + explicit ClassGraphBuilderVisitor(AstNetlist* const nodep) + : m_graphp{new V3Graph} { + iterate(nodep); + + // Propagate virtualization + m_graphp->order(); + for (V3GraphVertex& graphVertex : m_graphp->vertices()) { + V3ClassGraphVertex* const vertexp = graphVertex.as(); + for (V3GraphEdge& outEdge : vertexp->outEdges()) { + V3ClassGraphVertex* const successorp = outEdge.top()->as(); + successorp->propagateVirt(*vertexp); + } + } + } + ~ClassGraphBuilderVisitor() override = default; + std::unique_ptr takeGraph() && { + return std::unique_ptr( + new V3ClassesGraphWrapper{std::move(m_graphp), std::move(m_vertexMap)}); + } +}; + +std::unique_ptr V3Virtual::buildClassGraph(AstNetlist* netlistp) { + return ClassGraphBuilderVisitor{netlistp}.takeGraph(); +} diff --git a/src/V3Virtual.h b/src/V3Virtual.h new file mode 100644 index 000000000..6510fa7d5 --- /dev/null +++ b/src/V3Virtual.h @@ -0,0 +1,64 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Virtual function calls - function finder +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// 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-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3VIRTUAL_H_ +#define VERILATOR_V3VIRTUAL_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3Graph.h" + +#include +#include +#include + +//============================================================================ + +class V3ClassGraphVertex; + +// Graph of classes - build by V3Virtual::buildClassGraph() and used to get CFuncs that may be +// called by a AstNodeCCall (due to virtualization) +class V3ClassesGraphWrapper final { + friend class ClassGraphBuilderVisitor; + + const std::unique_ptr m_graphp; // graph of classes + const std::unordered_map + m_vertexMap; // Map from AstClass to a corresponding vertex + const std::unordered_set + m_emptySet; // Always empty set - used to return a reference to an empty set + + V3ClassesGraphWrapper(std::unique_ptr graph, + std::unordered_map vertexMap); + +public: + ~V3ClassesGraphWrapper(); + + // Returns possible CFuncs that may be called due to a NodeCCall passed as a callp argument + // Warning: returned set may be empty if NodeCCall is a New call or a super call - in such + // cases only `callp->funcp()` may be called. Also, if callp is to a non-member function the + // set will be empty - post V3Task static functions are not member functions anymore. + const std::unordered_set& + getCallPossibleCFuncs(const AstNodeCCall* const callp) const&; +}; + +class V3Virtual final { +public: + static std::unique_ptr buildClassGraph(AstNetlist*); +}; + +#endif // Guard diff --git a/test_regress/t/t_sched_noninlined_virt_func_suspendable.py b/test_regress/t/t_sched_noninlined_virt_func_suspendable.py new file mode 100755 index 000000000..b6c85ee2d --- /dev/null +++ b/test_regress/t/t_sched_noninlined_virt_func_suspendable.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-dfg']) + +test.execute() + +test.file_grep(test.stats, + r'Scheduling, count of non-inlined signal writes in suspendables\s+(\d+)', 7) + +test.passes() diff --git a/test_regress/t/t_sched_noninlined_virt_func_suspendable.v b/test_regress/t/t_sched_noninlined_virt_func_suspendable.v new file mode 100644 index 000000000..1d3fe9da6 --- /dev/null +++ b/test_regress/t/t_sched_noninlined_virt_func_suspendable.v @@ -0,0 +1,117 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +// verilog_format: off + +class TestBase; + virtual function int bar(); + endfunction +endclass + +interface class D1; + pure virtual task run(); +endclass + +interface class D2 implements D1; + pure virtual task a(bit x = 0); +endclass + +interface class D3 implements D1; + pure virtual task b(bit x = 0); +endclass + +interface class D4 implements D2, D3; +endclass + +package pkg; + bit [2:0] y3; +endpackage + +module t; + bit [2:0] y; + bit [2:0] z; + assign z[0] = 1'b1; + assign z[1] = !(y[0]); + assign z[2] = !(|y[1:0]); + + bit [2:0] y2; + bit [2:0] z2; + assign z2[0] = 1'b1; + assign z2[1] = !(y2[0]); + assign z2[2] = !(|y2[1:0]); + + import pkg::y3; + bit [2:0] z3; + assign z3[0] = 1'b1; + assign z3[1] = !(y3[0]); + assign z3[2] = !(|y3[1:0]); + + bit [2:0] y4; + bit [2:0] z4; + assign z4[0] = 1'b1; + assign z4[1] = !(y4[0]); + assign z4[2] = !(|y4[1:0]); + class Foo extends TestBase implements D4; + function automatic int bar(); + // verilator no_inline_task + y2 = 3'b111; + y3 = 3'b111; + return 1; + endfunction + task run(); + y = 3'b111; + #1; + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + endtask + task a(bit x = 0); + // verilator no_inline_task + y4 = ~y4; + #1; + if (!x) b(!x); + endtask + task b(bit x = 0); + // verilator no_inline_task + if (!x) a(!x); + endtask + endclass + initial begin + static Foo inst = new; + static TestBase foo = inst; + static D3 d3 = inst; + static D1 d1 = inst; + static D4 d4 = inst; + #1; + `checkh(z, 3'b111); + `checkh(z2, 3'b111); + `checkh(z3, 3'b111); + `checkh(z4, 3'b111); + void'(foo.bar()); + #1; + `checkh(z, 3'b111); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + d1.run(); + d4.a(); + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b001); + d3.b(); + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule