Fix O(n^2) runtime in V3CUse for large package-scoped structs

CUseVisitor::visit(AstNodeDType*) walked the full (recursively nested)
member tree of every package-scoped struct each time the struct's dtype
was reached.  A large unpacked struct used as a module port produces one
variable scope per leaf field, and the top-level struct dtype is reached
once per such reference, so the member walk ran O(references x members) =
O(n^2) times.

The walk only feeds addNewUse(), which already de-duplicates by name, so
the repeated traversals produced no additional output.  Guard the walk with
user1SetOnce() so each struct's members are traversed at most once per
module, matching the process-once pattern already used by the other
visitors in this file.

This was found on a real, auto-generated PeakRDL-regblock register block
that did not finish verilating before the fix; with the fix it completes.
The original design is proprietary, so the included regression test and the
scaling reproducer are self-contained stand-ins that mirror its structure.

Reproducer scaling, V3CUse pass elapsed (doubling the size quadruples the
unpatched time):

  registers  leaf fields  before    after
  200        400          ~5 s      ~0.001 s
  400        800          ~20 s     ~0.002 s
  800        1600         ~80 s     ~0.004 s
  2500       5000         ~804 s    ~0.012 s

At 2500 registers (5000 leaf fields, ~real-design scale) total verilation
drops from ~806 s to ~1.3 s.  Generated C++ output is byte-for-byte
identical before and after the fix.

Disclosure: investigated and drafted with the assistance of Claude (an AI
tool); reviewed, built, and tested by the human contributor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Wolfgang Mayerwieser <wolfgang@mayerwieser.com>
This commit is contained in:
Wolfgang Mayerwieser 2026-06-23 15:54:11 +02:00
parent 6fbc7042a5
commit 6cd43b1bb1
4 changed files with 96 additions and 0 deletions

View File

@ -300,6 +300,7 @@ Vito Gamberini
Wei-Lun Chiu Wei-Lun Chiu
William D. Jones William D. Jones
Wilson Snyder Wilson Snyder
Wolfgang Mayerwieser
Xi Zhang Xi Zhang
Yan Xu Yan Xu
Yangyu Chen Yangyu Chen

View File

@ -65,6 +65,7 @@ class CUseVisitor final : public VNVisitorConst {
iterateConst(nodep->lhsp()->dtypep()); iterateConst(nodep->lhsp()->dtypep());
} }
void visit(AstNodeDType* nodep) override { void visit(AstNodeDType* nodep) override {
if (nodep->user1SetOnce()) return; // Process once
if (nodep->virtRefDTypep()) iterateConst(nodep->virtRefDTypep()); if (nodep->virtRefDTypep()) iterateConst(nodep->virtRefDTypep());
if (nodep->virtRefDType2p()) iterateConst(nodep->virtRefDType2p()); if (nodep->virtRefDType2p()) iterateConst(nodep->virtRefDType2p());

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()
test.execute()
test.passes()

View File

@ -0,0 +1,76 @@
// 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;
typedef struct {
logic [7:0] value;
} field_t;
typedef struct {
field_t f0;
field_t f1;
} reg_t;
typedef struct {
reg_t R0;
reg_t R1;
reg_t R2;
} hwif_t;
endpackage
module t (/*AUTOARG*/
// Inputs
clk
);
input clk;
import pkg::*;
hwif_t hwif_in;
hwif_t hwif_out;
hwif_t storage;
integer cyc = 0;
always_ff @(posedge clk) begin
storage.R0.f0.value <= hwif_in.R0.f0.value;
storage.R0.f1.value <= hwif_in.R0.f1.value;
storage.R1.f0.value <= hwif_in.R1.f0.value;
storage.R1.f1.value <= hwif_in.R1.f1.value;
storage.R2.f0.value <= hwif_in.R2.f0.value;
storage.R2.f1.value <= hwif_in.R2.f1.value;
end
always_comb begin
hwif_out.R0.f0.value = storage.R0.f0.value;
hwif_out.R0.f1.value = storage.R0.f1.value;
hwif_out.R1.f0.value = storage.R1.f0.value;
hwif_out.R1.f1.value = storage.R1.f1.value;
hwif_out.R2.f0.value = storage.R2.f0.value;
hwif_out.R2.f1.value = storage.R2.f1.value;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 0) begin
hwif_in.R0.f0.value <= 8'h11;
hwif_in.R0.f1.value <= 8'h22;
hwif_in.R1.f0.value <= 8'h33;
hwif_in.R1.f1.value <= 8'h44;
hwif_in.R2.f0.value <= 8'h55;
hwif_in.R2.f1.value <= 8'h66;
end
else if (cyc == 3) begin
if (hwif_out.R0.f0.value !== 8'h11) $stop;
if (hwif_out.R0.f1.value !== 8'h22) $stop;
if (hwif_out.R1.f0.value !== 8'h33) $stop;
if (hwif_out.R1.f1.value !== 8'h44) $stop;
if (hwif_out.R2.f0.value !== 8'h55) $stop;
if (hwif_out.R2.f1.value !== 8'h66) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule