Skip to content

Fix two quadratic blowups in foreign-block Verilog generation#8

Open
nanavati wants to merge 3 commits into
mainfrom
claude/verilog-codegen-perf-rw8p7e
Open

Fix two quadratic blowups in foreign-block Verilog generation#8
nanavati wants to merge 3 commits into
mainfrom
claude/verilog-codegen-perf-rw8p7e

Conversation

@nanavati

@nanavati nanavati commented Jul 17, 2026

Copy link
Copy Markdown
Owner

When a rule takes the result of an ActionValue system task through a body of defs into a later system task, those defs are inlined into the negedge foreign always-block. Generating that block was quadratic in the number of such defs, from two separate list operations. On a module with ~100k foreign-block defs (which a compact source can reach via a Vector fold), Verilog generation went from unfinished-after-an-hour to about a minute.

Both fixes keep the generated Verilog byte-identical; only the data structures behind membership/selection change.

AVerilogUtil: Set-based def selection in vForeignBlock

The set of defs to inline is av_depend_defs ∩ fcall_depend_defs, and getAVDependDefs removes the avalues from a reachable set. Both used Data.List (intersect, \\) on lists that are each O(n) for such a module, so selection was O(n²). Both inputs come from closeOverMap (Set-backed: duplicate-free and sorted), so S.intersection and an S.notMember filter produce the same lists in the same order — identical output.

AVerilog: Set-based reg-marking of foreign-block defs

The vDef closure tests, per def, whether the def is assigned in a foreign block (and so declared reg with no continuous assignment). The membership set was a list consulted with elem once per def — O(n²) for a module whose defs are mostly foreign-block-assigned. It is now a Set tested with S.member.

Tests

  • bsc.verilog/foreign_inline — correctness: a $stime-fed Vector fold whose defs are inlined into the system-task block; checks the reg-declaration count (no defs dropped or duplicated) and simulates.
  • bsc.long_tests/verilog_foreign_inline_large — scale tripwire: the same shape at 8192 elements (~24k inlined defs) from a compact source, near-linear with these fixes and quadratic without.

Both quadratics are longstanding and independent of any in-flight work. (A related but separate SYB-traversal cost in IExpand, which shows up under -keep-method-conds, is addressed in its own PR.)

🤖 Generated with Claude Code

https://claude.ai/code/session_01VVN2QMZtLUwkag1vCYLaao

claude and others added 3 commits July 17, 2026 04:24
vForeignBlock chooses the defs to inline into a system-task always
block by intersecting the defs reachable from an ActionValue call's
result with the defs an fcall depends on, and getAVDependDefs removes
the avalues from a reachable set.  Both used Data.List (intersect and
\\) on lists that, for a rule with many foreign-call-dependent defs,
are both O(n) long -- so the selection was O(n^2) in the number of
such defs.

Use Data.Set for both.  The inputs come from closeOverMap (Set-backed,
so duplicate-free and sorted), so S.toList . S.intersection and a
S.notMember filter produce exactly the same lists, in the same order,
that the Data.List versions did -- the generated Verilog is
byte-identical.  Measured on a module with ~98k such defs, this alone
cut the intersect cost from ~3 minutes to seconds.

Co-Authored-By: Ravi Nanavati <ravi@matx.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVN2QMZtLUwkag1vCYLaao
The vDef closure decides, per def, whether a def is assigned inside a
foreign (system-task) always block -- in which case it is declared
"reg" with no continuous assignment.  The membership set
(defs_in_foreign_blocks) was a list consulted with elem once per def,
so a module whose defs are mostly foreign-block-assigned (an
ActionValue result flowing through a large body of defs) spent O(n^2)
here.

Convert it to a Set once and test with S.member.  Output is
unchanged.  This is the high-end companion to the vForeignBlock
selection fix: on a ~98k-def module the two together bring Verilog
generation down from unfinished-after-an-hour to about a minute.

Co-Authored-By: Ravi Nanavati <ravi@matx.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVN2QMZtLUwkag1vCYLaao
Add motivating tests for the foreign-block inline codegen fixes.

bsc.verilog/foreign_inline is a correctness test: a rule takes an
ActionValue system task result ($stime) through a Vector fold into a
later task, so the fold's defs are inlined into the negedge block and
declared reg.  It checks the reg-declaration count (all inline defs
present, none dropped or duplicated) and simulates the result.  This
exercises the vForeignBlock selection and the vDef reg-marking with a
small module, independent of the performance fixes.

bsc.long_tests/verilog_foreign_inline_large is the scale tripwire: the
same shape with an 8192-element vector elaborates to ~24k inlined
defs from a compact source.  With the Set-based membership tests
Verilog generation is near-linear; a regression to the old list-based
tests makes it quadratic and turns tens of seconds into many minutes.

Co-Authored-By: Ravi Nanavati <ravi@matx.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVN2QMZtLUwkag1vCYLaao
@nanavati
nanavati force-pushed the claude/verilog-codegen-perf-rw8p7e branch from 041dc67 to 556683d Compare July 17, 2026 05:46
@nanavati nanavati changed the title Fix quadratic blowups in foreign-block Verilog generation Fix two quadratic blowups in foreign-block Verilog generation Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants