Fix elaboration of classes reached through deferred references (#8440) (#8510)

This commit is contained in:
Ethan Sifferman
2026-09-25 17:34:47 -04:00
committed by GitHub
parent 4e29ecbf44
commit f6ff5b4cfb
3 changed files with 73 additions and 0 deletions
+21
View File
@@ -309,6 +309,8 @@ class ParamProcessor final {
// Guard against infinite recursion in classTypeMatchesDefaultClone slow path
std::unordered_set<const AstClass*> m_defaultCloneInProgress;
std::vector<AstClass*> m_specializedClassps; // Classes specialized since last drained
// member names cached for fast lookup
VMemberMap m_memberMap;
@@ -2143,6 +2145,14 @@ class ParamProcessor final {
const bool cloned = (newModp != srcModp);
UINFO(9, "nodeDeparamCommon result: " << newModp->prettyNameQ() << " cloned=" << cloned);
// ParamVisitor skips the body of a class still marked hasGParam(), relying on it
// being visited through a reference instead. A class reached only by a deferred
// class-scoped reference (e.g. the 'C#(V)::t' default of a type parameter that the
// instantiation overrides) is never visited, so record specializations here.
// user2() is set once processWorkQ has elaborated the body, which needs no re-queue.
AstClass* const newClassp = VN_CAST(newModp, Class);
if (newClassp && !newClassp->user2()) m_specializedClassps.push_back(newClassp);
// Link source class to its specialized version for later relinking of method references
if (defaultsResolved) srcModp->user4p(newModp);
@@ -2455,6 +2465,13 @@ public:
return newModp;
}
// Return, and forget, the classes specialized since the previous call
std::vector<AstClass*> takeSpecializedClassps() {
std::vector<AstClass*> taken;
taken.swap(m_specializedClassps);
return taken;
}
// CONSTRUCTORS
explicit ParamProcessor(AstNetlist* nodep)
: m_hierBlocks{v3Global.opt.hierBlocks(), nodep} {
@@ -2775,6 +2792,10 @@ class ParamVisitor final : public VNVisitor {
// Visit all cells under module, recursively
while (true) {
// Classes specialized since the last pass still need their bodies elaborated
for (AstClass* const classp : m_processor.takeSpecializedClassps()) {
m_state.m_workQueueNext.emplace(ParamState::WQKey{true, classp->level()}, classp);
}
if (workQueue.empty()) std::swap(workQueue, m_state.m_workQueueNext);
if (workQueue.empty()) break;
+16
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('linter')
test.lint()
test.passes()
@@ -0,0 +1,36 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// SPDX-FileCopyrightText: 2026 Ethan Sifferman
// SPDX-License-Identifier: CC0-1.0
// Overriding a type parameter whose default is a parameterized class scope
// still specializes the class, so its body must be elaborated (#8440).
package pkg;
function automatic integer func();
return 1;
endfunction
endpackage
class class_with_package_call #(
parameter int unused_param = 0
);
typedef int value_t;
localparam int local_param = pkg::func();
endclass
module module_with_type_parameter #(
parameter type value_t = class_with_package_call#(0)::value_t
);
endmodule
interface iface_with_type_parameter #(
parameter type value_t = class_with_package_call#(1)::value_t
) ();
value_t value;
endinterface
module top;
module_with_type_parameter #(.value_t(logic signed [15:0])) dut ();
iface_with_type_parameter #(.value_t(logic signed [15:0])) itf ();
endmodule