Fix virtual interface method call inlining and IMPURE suppression (#7505)

This commit is contained in:
Nikolay Puzanov 2026-04-27 23:58:30 +03:00 committed by GitHub
parent cee174c4b6
commit 4437ae79e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 219 additions and 22 deletions

View File

@ -61,6 +61,7 @@ class TaskFTaskVertex final : public TaskBaseVertex {
// Every task gets a vertex, and we link tasks together based on funcrefs.
AstNodeFTask* const m_nodep;
AstCFunc* m_cFuncp = nullptr;
bool m_needsNonInlineCFunc = false; // Needs CFunc for selected non-inlined call sites
public:
TaskFTaskVertex(V3Graph* graphp, AstNodeFTask* nodep)
@ -72,6 +73,8 @@ public:
string dotColor() const override { return pure() ? "black" : "red"; }
AstCFunc* cFuncp() const { return m_cFuncp; }
void cFuncp(AstCFunc* nodep) { m_cFuncp = nodep; }
bool needsNonInlineCFunc() const { return m_needsNonInlineCFunc; }
void needsNonInlineCFunc(bool flag) { m_needsNonInlineCFunc = flag; }
};
class TaskCodeVertex final : public TaskBaseVertex {
@ -119,6 +122,17 @@ class TaskStateVisitor final : public VNVisitor {
public:
// METHODS
static bool isVirtualIfaceMethodCall(const AstNodeFTaskRef* refp) {
const AstMethodCall* const mrefp = VN_CAST(refp, MethodCall);
if (!mrefp) return false;
const AstNodeExpr* const fromp = mrefp->fromp();
if (!fromp || !fromp->dtypep()) return false;
const AstIfaceRefDType* const ifrefp = VN_CAST(fromp->dtypep()->skipRefp(), IfaceRefDType);
return ifrefp && ifrefp->isVirtual();
}
static bool isIfaceFTaskScope(const AstScope* scopep) {
return scopep && VN_IS(scopep->modp(), Iface);
}
AstScope* getScope(AstNodeFTask* nodep) {
AstScope* const scopep = VN_AS(nodep->user3p(), Scope);
UASSERT_OBJ(scopep, nodep, "No scope for function");
@ -138,6 +152,9 @@ public:
m_funcToClassMap[newp] = getClassp(nodep);
}
bool ftaskNoInline(AstNodeFTask* nodep) { return getFTaskVertex(nodep)->noInline(); }
bool ftaskNeedsNonInlineCFunc(AstNodeFTask* nodep) {
return getFTaskVertex(nodep)->needsNonInlineCFunc();
}
AstCFunc* ftaskCFuncp(AstNodeFTask* nodep) { return getFTaskVertex(nodep)->cFuncp(); }
void ftaskCFuncp(AstNodeFTask* nodep, AstCFunc* cfuncp) {
getFTaskVertex(nodep)->cFuncp(cfuncp);
@ -229,6 +246,9 @@ private:
UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked task");
TaskFTaskVertex* const taskVtxp = getFTaskVertex(nodep->taskp());
new TaskEdge{&m_callGraph, m_curVxp, taskVtxp};
if (isVirtualIfaceMethodCall(nodep) && isIfaceFTaskScope(getScope(nodep->taskp()))) {
taskVtxp->needsNonInlineCFunc(true);
}
// Do we have to disable inlining the function?
const V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp());
if (!taskVtxp->noInline()) { // Else short-circuit below
@ -251,13 +271,6 @@ private:
if (nodep->dpiImport()) m_curVxp->noInline(true);
if (nodep->classMethod()) m_curVxp->noInline(true); // Until V3Task supports it
if (nodep->recursive()) m_curVxp->noInline(true);
// V3Scope resolves virtual-interface MethodCalls via user2p (last-wins),
// so inlining would bake in the wrong instance's VarScope refs.
if (v3Global.hasVirtIfaces()) {
if (const AstScope* const scopep = VN_CAST(nodep->user3p(), Scope)) {
if (VN_IS(scopep->modp(), Iface)) m_curVxp->noInline(true);
}
}
if (nodep->isConstructor()) {
m_curVxp->noInline(true);
m_ctorp = nodep;
@ -1495,8 +1508,10 @@ class TaskVisitor final : public VNVisitor {
// Iterate into the FTask we are calling. Note it may be under a different
// scope then the caller, so we need to restore state.
VL_RESTORER(m_scopep);
VL_RESTORER(m_modp);
VL_RESTORER(m_insStmtp);
m_scopep = m_statep->getScope(nodep);
m_modp = m_scopep->modp();
iterate(nodep);
}
void insertBeforeStmt(AstNode* nodep, AstNode* newp) {
@ -1528,6 +1543,16 @@ class TaskVisitor final : public VNVisitor {
}
}
bool isIfaceLocalImpure(AstNodeFTask* nodep, AstNode* impurep) {
const AstScope* const scopep = m_statep->getScope(nodep);
if (!TaskStateVisitor::isIfaceFTaskScope(scopep)) return false;
const AstVarRef* const refp = VN_CAST(impurep, VarRef);
if (!refp || !refp->varScopep()) return false;
return refp->varScopep()->scopep() == scopep;
}
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
@ -1598,7 +1623,10 @@ class TaskVisitor final : public VNVisitor {
// Create cloned statements
AstNode* beginp;
AstCNew* cnewp = nullptr;
if (m_statep->ftaskNoInline(nodep->taskp())) {
const bool virtualIfaceCall
= TaskStateVisitor::isVirtualIfaceMethodCall(nodep)
&& TaskStateVisitor::isIfaceFTaskScope(m_statep->getScope(nodep->taskp()));
if (m_statep->ftaskNoInline(nodep->taskp()) || virtualIfaceCall) {
processArgs(nodep);
// This may share VarScope's with a public task, if any. Yuk.
beginp = createNonInlinedFTask(nodep, namePrefix, outvscp, cnewp /*ref*/);
@ -1697,32 +1725,38 @@ class TaskVisitor final : public VNVisitor {
}
const bool noInline = m_statep->ftaskNoInline(nodep);
const bool needsNonInlineCFunc = m_statep->ftaskNeedsNonInlineCFunc(nodep);
// Warn if not inlining an impure ftask (unless method, recursive,
// or interface function -- interface member access is not truly external).
// or interface-local member access, which is not truly external).
// Will likely not schedule correctly.
// TODO: Why not if recursive? It will not work ...
if (noInline && !nodep->classMethod() && !nodep->recursive()
&& !VN_IS(m_modp, Iface)) {
if (noInline && !nodep->classMethod() && !nodep->recursive()) {
if (AstNode* const impurep = m_statep->checkImpure(nodep)) {
nodep->v3warn(
IMPURE,
"Unsupported: External variable referenced by non-inlined function/task: "
<< nodep->prettyNameQ() << '\n'
<< nodep->warnContextPrimary() << '\n'
<< impurep->warnOther() << "... Location of the external reference: "
<< impurep->prettyNameQ() << '\n'
<< impurep->warnContextSecondary());
if (!isIfaceLocalImpure(nodep, impurep)) {
nodep->v3warn(
IMPURE,
"Unsupported: External variable referenced by non-inlined function/task: "
<< nodep->prettyNameQ() << '\n'
<< nodep->warnContextPrimary() << '\n'
<< impurep->warnOther() << "... Location of the external reference: "
<< impurep->prettyNameQ() << '\n'
<< impurep->warnContextSecondary());
}
}
}
if (noInline || nodep->dpiImport() || nodep->dpiExport() || nodep->taskPublic()) {
if (noInline || needsNonInlineCFunc || nodep->dpiImport() || nodep->dpiExport()
|| nodep->taskPublic()) {
// Clone it first, because we may have later FTaskRef's that still need
// the original version.
AstNodeFTask* const clonedFuncp = nodep->cloneTree(false);
if (nodep->isConstructor()) m_statep->remapFuncClassp(nodep, clonedFuncp);
if (AstCFunc* const cfuncp = makeUserFunc(clonedFuncp, noInline)) {
const bool makeNonInlineFunc = noInline || needsNonInlineCFunc;
if (AstCFunc* const cfuncp = makeUserFunc(clonedFuncp, makeNonInlineFunc)) {
nodep->addNextHere(cfuncp);
if (nodep->dpiImport() || noInline) m_statep->ftaskCFuncp(nodep, cfuncp);
if (nodep->dpiImport() || makeNonInlineFunc) {
m_statep->ftaskCFuncp(nodep, cfuncp);
}
iterateIntoFTask(clonedFuncp); // Do the clone too
}
}

View File

@ -0,0 +1,9 @@
%Error-IMPURE: t/t_interface_func_impure_bad.v:21:8: Unsupported: External variable referenced by non-inlined function/task: 'external_write'
: ... note: In instance 't.i'
21 | task external_write;
| ^~~~~~~~~~~~~~
t/t_interface_func_impure_bad.v:23:5: ... Location of the external reference: 'external_sig'
23 | external_sig = 1'b1;
| ^~~~~~~~~~~~
... For error description see https://verilator.org/warn/IMPURE?v=latest
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/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('vlt')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,34 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
package pkg;
bit external_sig;
endpackage
interface iface;
import pkg::*;
bit local_sig;
task local_write;
// verilator no_inline_task
local_sig = 1'b1;
endtask
task external_write;
// verilator no_inline_task
external_sig = 1'b1;
endtask
endinterface
module t;
iface i();
initial begin
i.local_write();
i.external_write();
end
endmodule

View File

@ -0,0 +1,18 @@
#!/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"])
test.execute()
test.passes()

View File

@ -0,0 +1,40 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
interface iface;
bit x;
function bit get_and_toggle();
return x++;
endfunction
endinterface
class Driver;
virtual iface vif;
function bit call();
return vif.get_and_toggle();
endfunction
endclass
module t;
iface a();
iface b();
initial begin
automatic Driver d = new;
a.x = 1'b0;
b.x = 1'b1;
d.vif = a;
if (d.call() !== 1'b0) $stop;
d.vif = b;
if (d.call() !== 1'b1) $stop;
if (a.x !== 1'b1) $stop;
if (b.x !== 1'b0) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/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"])
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.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
interface iface;
function bit get();
static bit x;
return x++;
endfunction
endinterface
class Holder;
virtual iface vif;
endclass
module t;
iface i();
initial begin
automatic Holder h = new;
if (i.get() !== 1'b0) $stop;
if (i.get() !== 1'b1) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule