Skip to content

[FIRRTL] Add alternative constraint-based InferWidths pass#9173

Open
wky17 wants to merge 34 commits intollvm:mainfrom
wky17:issue-9140-new-inferwidths
Open

[FIRRTL] Add alternative constraint-based InferWidths pass#9173
wky17 wants to merge 34 commits intollvm:mainfrom
wky17:issue-9140-new-inferwidths

Conversation

@wky17
Copy link
Copy Markdown

@wky17 wky17 commented Nov 3, 2025

[WIP][FIRRTL] Add formally verified alternative InferWidths pass

🔍 Related Issue

Fixes #9140 (InferWidths failures on cyclic constraints)

📖 Background & Motivation

The current InferWidths implementation has critical limitations:
Correctness gaps: Fails on cyclic constraint cases like issue9140_0.mlir and issue9140_1.mlir

This PR introduces InferWidths_new - a drop-in alternative pass that:

⚙️ Technical Approach

The new pass implements a constraint-based width inference engine with:

  • Graph propagation core: Using Boost's graph algorithms for: SCC decomposition (Tarjan)
  • Adapted Floyd-Warshall algorithm and **Adapted Branch-and-Bound algorithm **: Solving inside SCC
  • Formal verification: Core algorithm mechanically proven in Rocq
  • Incremental adoption: Preserves existing pass pipeline

Implementation note:
Boost is used for rapid prototyping of graph algorithms. We have prepped replacement stubs for:

  • Tarjan's algorithm (SCC decomposition)
  • Floyd-Warshall (computing the least path)
  • DFS (finding any existing path between two nodes)
    These can be swapped in if necessary

🧪 Testing Strategy

Comprehensive verification across 2 test dimensions:

Test Category Coverage Verified Method
Legacy compliance 100% existing tests
(test/Dialect/FIRRTL/infer-widths.mlir)
✅ Passes all 60 existing tests
Issue regression test_new_inferwidths/
• issue9140_0.mlir (example 1)
• issue9140_1.mlir (example 2)
🐛 Fixes #9140 failures

🚀 Integration Plan

Following maintainer's phased adoption strategy:

  1. [Current PR] Add alternative pass with firrtl-infer-widths-new opt-in
  2. [Next] Enable in downstream CI with shadow testing
  3. [Phase 3] Performance benchmarking (memory/runtime)
  4. [Future] Switch default after bake period

Comment thread test_new_inferwidths/issue9140_0.mlir Outdated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please collect all the tests to a single file and put them in the appropriate tests folder with the normal LIT headers and directives and checks.

Comment thread lib/Firtool/Firtool.cpp Outdated

// Width inference creates canonicalization opportunities.
pm.nest<firrtl::CircuitOp>().addPass(firrtl::createInferWidths());
//pm.nest<firrtl::CircuitOp>().addPass(firrtl::createInferWidths());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please either add a cmdline control over this or remove the old line. Don't leave commented out code in the file.

auto cs_duration = std::chrono::duration_cast<std::chrono::microseconds>(cs_time - start);
auto solve_duration = std::chrono::duration_cast<std::chrono::microseconds>(update_time - solve_time);
auto update_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - update_time);
// std::cout << "generate constraint time : " << cs_duration.count() / 1000.0 << " ms\n";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't commit commented out code. I might suggest adding some performance counters from MLIR to track and report these.

if (mapping.areAllModulesSkipped())
return markAllAnalysesPreserved();

auto solve_time = std::chrono::high_resolution_clock::now();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See if MLIR's timing mechanisms are sufficient.


// 输出结果
if (solution.has_value()) {
// LLVM_DEBUG(llvm::dbgs() << "可行解找到:\n";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't commit commented out code. It looks like the LLVM_DEBUG guard is sufficient to have the code in.

// bab
//===----------------------------------------------------------------------===//

using Bounds = std::unordered_map<FieldRef, std::pair<int, int>, FieldRef::Hash>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked, but unordered_map is a red flag for deterministic behavior. The code might be fine, but it needs to be carefully checked.

return success();

bool mappingFailed = false;
TypeSwitch<Operation *>(op)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using an OpVisitor.

#include "llvm/ADT/SetVector.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include <boost/graph/adjacency_list.hpp>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLVM has a dim view of adding external dependencies. These would have to be very carefully considered.

#include "llvm/Support/ErrorHandling.h"
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/floyd_warshall_shortest.hpp>
#include <boost/graph/strong_components.hpp>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a SCC implementation in MLIR which should likely be used.

#include <boost/graph/floyd_warshall_shortest.hpp>
#include <boost/graph/strong_components.hpp>
#include <iostream>
#include <iomanip>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use MLIR/LLVM streams rather than the standard library.

//===----------------------------------------------------------------------===//

// 节点类型
using Node = std::variant<Zero, FieldRef>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using a sentinel FieldRef. I think one already exists (value null).

@wky17 wky17 force-pushed the issue-9140-new-inferwidths branch from c9aaa58 to 2f3745a Compare November 24, 2025 03:04
@wky17 wky17 requested a review from darthscsi November 24, 2025 05:46
@wky17
Copy link
Copy Markdown
Author

wky17 commented Nov 24, 2025

PR Update Summary

Hi maintainers, I've comprehensively updated this PR to address all previous feedback:

  1. Formatting & Structure

    • Fixed all clang-format issues
    • Rebased onto latest upstream
    • Added proper timing via MLIR's timing mechanisms
  2. Implementation Improvements

    • Added new -use-new-infer-widths option in firtool (instead of commenting-out)
    • Replaced unordered_map with DenseMap for deterministic behavior and better performance
    • Removed Boost dependency by using GraphTraits from LLVM
    • Utilized MLIR/LLVM streams instead of standard library iostreams
  3. Semantic Refinements

    • Used LLVM_DEBUG for diagnostic output
    • Used sentinel FieldRef instead of custom Zero variable
    • Added width constraint: w_c <= 1 for condition handling

@llvm/circt-maintainers @seldridge @darthscsi Could you please re-run CI and review when convenient?

@wky17 wky17 force-pushed the issue-9140-new-inferwidths branch 4 times, most recently from 3c15856 to 221639e Compare December 8, 2025 02:04
@wky17 wky17 force-pushed the issue-9140-new-inferwidths branch 2 times, most recently from 1fe9115 to b837c49 Compare December 11, 2025 03:03
@wky17
Copy link
Copy Markdown
Author

wky17 commented Dec 11, 2025

@schulyer Hi! I have resolved the conflict in Firtool.cpp, please re-run again!

@wky17
Copy link
Copy Markdown
Author

wky17 commented Dec 11, 2025

@seldridge @darthscsi Hi! I have resolved the conflict in Firtool.cpp, please re-run again!

@wky17 wky17 force-pushed the issue-9140-new-inferwidths branch from b837c49 to 0f16622 Compare December 11, 2025 09:55
@wky17
Copy link
Copy Markdown
Author

wky17 commented Dec 11, 2025

@seldridge Hi! I have resolved the build error. I think the reason for the error is that my local environment is different from the Docker test environment. Please re-run again!

@wky17 wky17 force-pushed the issue-9140-new-inferwidths branch from 0f16622 to d1856cc Compare December 12, 2025 01:59
@wky17
Copy link
Copy Markdown
Author

wky17 commented Dec 12, 2025

@seldridge @darthscsi Dear reviewers,
All CI checks have passed after the recent fixes. Could you please review this PR when you have time?
Thank you for your time and consideration!

Comment thread lib/Dialect/FIRRTL/Transforms/InferWidths_new.cpp Outdated
}
};

#include <list>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Please include header at the top of file

Comment on lines +417 to +419
std::deque<Node> nodes;
DenseMap<FieldRef, Node *> nodeMap;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't nodes reallocated when deque size is modified? If so I think nodeMap could refer to invalided node addresses. Certainly using bump allocator or unique_ptr is better here.

Comment on lines +752 to +761
LLVM_DEBUG(llvm::dbgs() << "initial matrix:\n");
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (graph[i][j] == INF)
LLVM_DEBUG(llvm::dbgs() << " INF ");
else
LLVM_DEBUG(llvm::dbgs() << " " << graph[i][j] << " ");
}
LLVM_DEBUG(llvm::dbgs() << "\n");
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you can put entire for loops under LLVM_DEBUG

Suggested change
LLVM_DEBUG(llvm::dbgs() << "initial matrix:\n");
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (graph[i][j] == INF)
LLVM_DEBUG(llvm::dbgs() << " INF ");
else
LLVM_DEBUG(llvm::dbgs() << " " << graph[i][j] << " ");
}
LLVM_DEBUG(llvm::dbgs() << "\n");
}
LLVM_DEBUG({
lvm::dbgs() << "initial matrix:\n";
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (graph[i][j] == INF)
llvm::dbgs() << " INF ";
else
llvm::dbgs() << " " << graph[i][j] << " ";
}
llvm::dbgs() << "\n";
}
});

Comment on lines +1 to +2
//===- InferWidths_new.cpp - Infer width of types -------------------*- C++
//-*-===//
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//===- InferWidths_new.cpp - Infer width of types -------------------*- C++
//-*-===//
//===----------------------------------------------------------------------===//

@uenoku
Copy link
Copy Markdown
Member

uenoku commented Dec 15, 2025

Could you add

circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-infer-widths-new))' --verify-diagnostics %s | FileCheck %s 

to

// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-infer-widths))' --verify-diagnostics %s | FileCheck %s
and see if it passes?

@wky17 wky17 force-pushed the issue-9140-new-inferwidths branch from 42ace46 to db8244b Compare December 19, 2025 03:44
Comment thread test/Dialect/FIRRTL/infer-widths.mlir
@wky17
Copy link
Copy Markdown
Author

wky17 commented Dec 19, 2025

Hi @uenoku @seldridge 👋,
Thanks for your thorough review! I've addressed all your suggestions in the latest push. Below is a breakdown of the changes:

✅ Implemented Suggestions:

  1. Include Statements

    • Moved #include <list> and #include <stack> to the top of the file for better organization.
  2. Memory Safety Improvement

    • Replaced unsafe deque usage with a bump allocator as suggested.
  3. Debugging Optimization

    • Wrapped entire loops in LLVM_DEBUG blocks.
  4. Test Pipeline Enhancement

    • Added explicit verification command to infer-widths.mlir:
      circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-infer-widths-new))' --verify-diagnostics %s | FileCheck %s
    • Verification: All tests pass successfully ✅
  5. Code Documentation

    • Added detailed comments for:
      • Constraint1/Constraint2/Min classes
      • Key function behaviors throughout the file
    • (Let me know if any section needs further clarification!)

➡️ Next Steps:

  • Happy to refine any remaining points
  • Will address additional feedback
  • Appreciate your expertise! 🙌

@wky17 wky17 requested review from seldridge and uenoku December 21, 2025 14:53
…idth inference, but I don't find 'firrtl.domain.define %B, %A' in infer-widths.mlir:468:5. Don't know the reason about this error
@wky17
Copy link
Copy Markdown
Author

wky17 commented Dec 23, 2025

Hi @uenoku @seldridge 👋,

Thank you for your feedback on the PR. I've addressed the memory safety concerns by implementing the suggested BumpPtrAllocator approach. However, I'm encountering an unexpected CI failure that needs your help.

CI Failure Analysis

The Windows CI test is failing with this error:

RUN: at line 2

d:\a\circt\circt\build\bin\circt-opt.exe --pass-pipeline='builtin.module(firrtl.circuit(firrtl-infer-widths-new))' --verify-diagnostics D:\a\circt\circt\test\Dialect\FIRRTL\infer-widths.mlir | d:\a\circt\circt\build\bin\filecheck.exe D:\a\circt\circt\test\Dialect\FIRRTL\infer-widths.mlir

executed command: 'd:\a\circt\circt\build\bin\circt-opt.exe' '--pass-pipeline=builtin.module(firrtl.circuit(firrtl-infer-widths-new))' --verify-diagnostics 'D:\a\circt\circt\test\Dialect\FIRRTL\infer-widths.mlir'

.---command stderr------------
D:\a\circt\circt\test\Dialect\FIRRTL\infer-widths.mlir:468:5: error: unexpected error: 'firrtl.domain.define' op not supported in width inference
    firrtl.domain.define %B, %A
    ^

Investigation Done

I've thoroughly investigated this issue:

  1. File content verification:

    • The referenced line (468) in infer-widths.mlir does not contain firrtl.domain.define %B, %A
    • The entire file infer-widths.mlir has zero occurrence of firrtl.domain.define
  2. Reproduction attempts:

    • Ubuntu 22.04: ninja -C build check-circt passes all tests
    • macOS (Sequoia 15.5): ninja -C build check-circt passes all tests
    • Docker environment (using ./utils/run-docker.sh): cannot reproduce this error

Request for Assistance

Since I can't reproduce this locally, could you please help with:

  1. Confirming if this is a known Windows CI issue
  2. Suggesting additional debugging steps
  3. Recommending Windows-specific test commands I could try

I'm happy to investigate further with your guidance. The complete test file and changes can be seen in the PR.

@seldridge
Copy link
Copy Markdown
Member

It should be more than just Windows. See this commit: 1be4b77

Domain information can show up and it should ignored.

seldridge and others added 7 commits December 23, 2025 10:43
Add the `DomainDefineOp` to the list of operations which are ignored by
FIRRTL's `InferWidths` pass as they have no effect.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@sifive.com>
Sort the types for the "no-op" case when visiting operations in FIRRTL's
`InferWidths` pass.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@sifive.com>
Cleanup trailing whitespace in an `InferWidths` test.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@sifive.com>
Programs are essentially just modules. There are a few subtle semantic
differences, but we aren't far enough down the simulation path to have
those show up. For the time being, just treat programs as modules. We
will add the necessary exit blocking mechanisms later on when we have
sufficient simulation support in Arcilator.
Add support for lowering the `moore.builtin.time` op to a new
`llhd.current_time` op that simply materializes the current simulation
time as an SSA value.

Error diff on circt-tests:
```diff
-37 error: failed to legalize operation 'moore.builtin.time'
+22 error: failed to legalize operation 'moore.time_to_logic'
+15 error: failed to legalize operation 'moore.fmt.time'
  0 total change
```
@wky17
Copy link
Copy Markdown
Author

wky17 commented Jan 12, 2026

Hi @uenoku @seldridge 👋, I have now resolved the upstream conflicts and can merge cleanly. Please help me review the code again.

@seldridge
Copy link
Copy Markdown
Member

I gave this a spin on an internal design that is 1.7GiB of FIRRTL. There's a pretty major performance regression vs. the old infer widths:

Old:

Total Execution Time: 377.2114 seconds
----User Time----  ----Wall Time----  ----Name----
5.4665 (  0.7%)    5.4665 (  1.9%)    InferWidths

New:

Total Execution Time: 377.2114 seconds
----User Time----  ----Wall Time----  ----Name----
42.6200 (  4.3%)   42.6200 ( 11.3%)    InferWidths_new

I haven't dug into this more to see what is going on.

@wky17
Copy link
Copy Markdown
Author

wky17 commented Jan 28, 2026

Hi @uenoku @seldridge 👋,

Thank you for catching the performance regression in the earlier version and sharing those critical metrics! 🙏 I've taken this feedback seriously and conducted optimization on the width inference algorithm.

Performance Improvements

Using the largest available FIRRTL design I could access (309MB), here are the optimized results:

Version solver.solve() Time Speed vs Old
Old (original) 49.337 ms 1.0x
Initial PR 204.366 ms 4.14x slower
Optimized (the latest commitment) 11.123 ms 4.44x faster

Key optimizations:

  1. Eliminated Heavy Copy Operations
    Avoided copying large intermediate results (e.g., maps with hundreds of thousands of entries) during variable declarations and function result passing by using move semantics and pointers.

  2. Constraint Map Pruning
    Proactively removed solved variables from the constraint map after resolution, reducing the search space for subsequent operations. This is safe due to our topological sorting approach and improves performance as the algorithm progresses.

Could you please re-run your 1.7GiB test case with this optimized implementation? I'm confident you'll see significant improvements.

@seldridge
Copy link
Copy Markdown
Member

Yes, this looks way better now. I reran this and am seeing:

Old:

  Total Execution Time: 311.8410 seconds 
  ----User Time----  ----Wall Time----  ----Name----
    6.3407 (  0.6%)    6.3407 (  2.0%)    InferWidths

New:

  Total Execution Time: 350.9843 seconds
  ----User Time----  ----Wall Time----  ----Name----
   12.7613 (  1.2%)   12.7613 (  3.6%)    InferWidths_new 

@wky17
Copy link
Copy Markdown
Author

wky17 commented Feb 23, 2026

Hi @uenoku @seldridge 👋,

First, thank you for your last feedback - I'm really glad to hear the optimizations are heading in the right direction! 🚀

Since it's been about a month since your last review, would you be available for another look when convenient?

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.

InferWidths Pass Fails on Solvable Width Constraints with Circular Dependencies.

5 participants