diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 0223f6bd6..d4b73eaf7 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -293,6 +293,9 @@ class ParamProcessor final { std::vector> m_classParams; std::unordered_map m_paramIndex; + // Guard against infinite recursion in classTypeMatchesDefaultClone slow path + std::unordered_set m_defaultCloneInProgress; + // member names cached for fast lookup VMemberMap m_memberMap; @@ -1250,13 +1253,20 @@ class ParamProcessor final { = VN_CAST(origClassRefp->classp()->user4p(), Class); if (defaultClonep && defaultClonep == exprClassRefp->classp()) return true; // Slow path: deparameterize the default type and compare the result. + // Different templates can never match; use origName() because exprp's + // class may already be a specialization (clone) of the template. if (!origClassRefp->classp()->hasGParam()) return false; + if (origClassRefp->classp()->origName() != exprClassRefp->classp()->origName()) + return false; + // Prevent re-entry when classRefDeparam recurses through cellPinCleanup. + if (!m_defaultCloneInProgress.insert(origClassRefp->classp()).second) return false; // const_cast safe: cloneTree doesn't modify the source AstClassRefDType* const origClonep = static_cast( const_cast(origClassRefp)->cloneTree(false)); AstNodeModule* const resolvedModp = classRefDeparam(origClonep, origClassRefp->classp()); const bool match = resolvedModp && VN_CAST(resolvedModp, Class) == exprClassRefp->classp(); VL_DO_DANGLING(origClonep->deleteTree(), origClonep); + m_defaultCloneInProgress.erase(origClassRefp->classp()); return match; } diff --git a/test_regress/t/t_class_param_comparator.py b/test_regress/t/t_class_param_comparator.py new file mode 100755 index 000000000..31b1f0e53 --- /dev/null +++ b/test_regress/t/t_class_param_comparator.py @@ -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: 2025 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() diff --git a/test_regress/t/t_class_param_comparator.v b/test_regress/t/t_class_param_comparator.v new file mode 100644 index 000000000..66a40e744 --- /dev/null +++ b/test_regress/t/t_class_param_comparator.v @@ -0,0 +1,55 @@ +// Minimal non-UVM repro. +// base class with type-of-type default overridden by derived class. +// +// 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 + +package pkg; + + class builtin_comp #(type T = int); + static function bit comp(T a, T b); + return 1; + endfunction + endclass + + class class_comp #(type T = int); + static function bit comp(T a, T b); + return 1; + endfunction + endclass + + virtual class comparator #( + type T = int, + type comp_type = builtin_comp #(T) + ); + endclass + + class class_comparator #(type T = int) + extends comparator #(T, class_comp #(T)); + endclass + +endpackage + +module t; + import pkg::*; + + class c; + int i; + function bit compare(c rhs); + return i == rhs.i; + endfunction + function void copy(c rhs); + rhs.i = i; + endfunction + endclass + + initial begin + class_comparator #(c) sb; + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule