Skip to content

Commit a738234

Browse files
committed
[AIG] Refactor AIG longest-path analysis with lazy mode and max-delay filtering.
Introduce LongestPathAnalysisOption with collectDebugInfo, lazyComputation, and keepOnlyMaxDelayPaths, replacing traceDebugPoints/incremental. Add path filtering that deduplicates by fan-in and, when enabled, keeps only max-delay paths (with special handling for module inputs); apply in local and global scopes. Rename and clarify APIs: getResults → computeGlobalPaths; getOrComputeResults/getResults → getOrComputePaths/getCachedPaths. getMaxDelay/getAverageMaxDelay now return FailureOr<int64_t> and support per-bit or all-bits queries. Update IncrementalLongestPathAnalysis: defaults to lazyComputation=true and keepOnlyMaxDelayPaths=true; add options-based constructor. Rework OperationAnalyzer to use its own analysis Context (lazy=true, keepOnlyMaxDelayPaths=false) for precise dependency discovery; rename getResults → analyzeOperation; cache by op signature. Extend C API: new constructor flags; add getPaths(value, bitPos, elaborate) and collection merge. Extend Python bindings: expose new constructor options; add get_paths and LongestPathCollection.merge; update integration test. Adjust DatapathToComb lowering to use analysis->getMaxDelay(...). Improve visitor concurrency: use getLocalVisitorMutable and wait only when running in parallel. Breaking changes: C API constructor signature changed; LongestPathAnalysisWithTrace removed. Several C++ methods renamed and now return FailureOr. Python API constructor and new methods added; call sites may need updates. This enables faster on-demand timing analysis, optional memory/time savings via max-delay filtering, and more flexible C/Python entry points.
1 parent dc21c22 commit a738234

File tree

13 files changed

+546
-227
lines changed

13 files changed

+546
-227
lines changed

include/circt-c/Dialect/AIG.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,17 @@ DEFINE_C_API_STRUCT(AIGLongestPathCollection, void);
4848

4949
// Create a LongestPathAnalysis for the given module
5050
MLIR_CAPI_EXPORTED AIGLongestPathAnalysis
51-
aigLongestPathAnalysisCreate(MlirOperation module, bool traceDebugPoints);
51+
aigLongestPathAnalysisCreate(MlirOperation module, bool collectDebugInfo,
52+
bool keepOnlyMaxDelayPaths, bool lazyComputation);
5253

5354
// Destroy a LongestPathAnalysis
5455
MLIR_CAPI_EXPORTED void
5556
aigLongestPathAnalysisDestroy(AIGLongestPathAnalysis analysis);
5657

58+
MLIR_CAPI_EXPORTED AIGLongestPathCollection
59+
aigLongestPathAnalysisGetPaths(AIGLongestPathAnalysis analysis, MlirValue value,
60+
int64_t bitPos, bool elaboratePaths);
61+
5762
MLIR_CAPI_EXPORTED AIGLongestPathCollection aigLongestPathAnalysisGetAllPaths(
5863
AIGLongestPathAnalysis analysis, MlirStringRef moduleName,
5964
bool elaboratePaths);
@@ -79,6 +84,10 @@ MLIR_CAPI_EXPORTED AIGLongestPathDataflowPath
7984
aigLongestPathCollectionGetDataflowPath(AIGLongestPathCollection collection,
8085
size_t pathIndex);
8186

87+
MLIR_CAPI_EXPORTED void
88+
aigLongestPathCollectionMerge(AIGLongestPathCollection dest,
89+
AIGLongestPathCollection src);
90+
8291
//===----------------------------------------------------------------------===//
8392
// DataflowPath API
8493
//===----------------------------------------------------------------------===//

include/circt/Dialect/AIG/Analysis/LongestPathAnalysis.h

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "llvm/ADT/ArrayRef.h"
3030
#include "llvm/ADT/ImmutableList.h"
3131
#include "llvm/ADT/SmallVector.h"
32+
#include "llvm/Support/ErrorHandling.h"
3233
#include "llvm/Support/JSON.h"
3334
#include <variant>
3435

@@ -172,14 +173,43 @@ class DataflowPath {
172173
// JSON serialization for DataflowPath
173174
llvm::json::Value toJSON(const circt::aig::DataflowPath &path);
174175

175-
// Options for the longest path analysis.
176+
/// Configuration options for the longest path analysis.
177+
///
178+
/// This struct controls various aspects of the analysis behavior, including
179+
/// debugging features, computation modes, and optimization settings. Different
180+
/// combinations of options are suitable for different use cases.
181+
///
182+
/// Example usage:
183+
/// // For timing-driven optimization with debug info
184+
/// LongestPathAnalysisOption options(true, true, false);
185+
///
186+
/// // For fast critical path identification only
187+
/// LongestPathAnalysisOption options(false, false, true);
176188
struct LongestPathAnalysisOption {
177-
bool traceDebugPoints = false;
178-
bool incremental = false;
179-
180-
LongestPathAnalysisOption(bool traceDebugPoints, bool incremental)
181-
: traceDebugPoints(traceDebugPoints), incremental(incremental) {}
182-
LongestPathAnalysisOption() = default;
189+
/// Enable collection of debug points along timing paths.
190+
/// When enabled, records intermediate points with delay values and comments
191+
/// for debugging, visualization, and understanding delay contributions.
192+
/// Moderate performance impact.
193+
bool collectDebugInfo = false;
194+
195+
/// Enable lazy computation mode for on-demand analysis.
196+
/// Performs delay computations lazily and caches results, tracking IR
197+
/// changes. Better for iterative workflows where only specific paths
198+
/// are queried. Disables parallel processing.
199+
bool lazyComputation = false;
200+
201+
/// Keep only the maximum delay path per fanout point.
202+
/// Focuses on finding maximum delays, discarding non-critical paths.
203+
/// Significantly faster and uses less memory when only delay bounds
204+
/// are needed rather than complete path enumeration.
205+
bool keepOnlyMaxDelayPaths = false;
206+
207+
/// Construct analysis options with the specified settings.
208+
LongestPathAnalysisOption(bool collectDebugInfo = false,
209+
bool lazyComputation = false,
210+
bool keepOnlyMaxDelayPaths = false)
211+
: collectDebugInfo(collectDebugInfo), lazyComputation(lazyComputation),
212+
keepOnlyMaxDelayPaths(keepOnlyMaxDelayPaths) {}
183213
};
184214

185215
// This analysis finds the longest paths in the dataflow graph across modules.
@@ -197,17 +227,22 @@ class LongestPathAnalysis {
197227

198228
// Return all longest paths to each Fanin for the given value and bit
199229
// position.
200-
LogicalResult getResults(Value value, size_t bitPos,
201-
SmallVectorImpl<DataflowPath> &results) const;
230+
LogicalResult computeGlobalPaths(Value value, size_t bitPos,
231+
SmallVectorImpl<DataflowPath> &results);
232+
233+
// Compute local paths for specified value and bit. Local paths are paths
234+
// that are fully contained within a module.
235+
FailureOr<ArrayRef<OpenPath>> computeLocalPaths(Value value, size_t bitPos);
202236

203-
// Return the maximum delay to the given value for all bit positions.
204-
int64_t getMaxDelay(Value value) const;
237+
// Return the maximum delay to the given value and bit position. If bitPos is
238+
// negative, then return the maximum delay across all bits.
239+
FailureOr<int64_t> getMaxDelay(Value value, int64_t bitPos = -1);
205240

206241
// Return the average of the maximum delays across all bits of the given
207242
// value, which is useful approximation for the delay of the value. For each
208243
// bit position, finds all paths and takes the maximum delay. Then averages
209244
// these maximum delays across all bits of the value.
210-
int64_t getAverageMaxDelay(Value value) const;
245+
FailureOr<int64_t> getAverageMaxDelay(Value value);
211246

212247
// Return paths that are closed under the given module. Closed paths are
213248
// typically register-to-register paths. A closed path is a path that starts
@@ -264,6 +299,7 @@ class LongestPathAnalysis {
264299

265300
private:
266301
mlir::MLIRContext *ctx;
302+
bool isAnalysisValid = true;
267303
};
268304

269305
// Incremental version of longest path analysis that supports on-demand
@@ -272,14 +308,20 @@ class IncrementalLongestPathAnalysis : private LongestPathAnalysis,
272308
public mlir::PatternRewriter::Listener {
273309
public:
274310
IncrementalLongestPathAnalysis(Operation *moduleOp, mlir::AnalysisManager &am)
275-
: LongestPathAnalysis(moduleOp, am,
276-
LongestPathAnalysisOption(false, true)) {}
277-
278-
// Compute maximum delay for specified value and bit.
279-
FailureOr<int64_t> getOrComputeMaxDelay(Value value, size_t bitPos);
311+
: LongestPathAnalysis(
312+
moduleOp, am,
313+
LongestPathAnalysisOption(/*collectDebugInfo=*/false,
314+
/*lazyComputation=*/true,
315+
/*keepOnlyMaxDelayPaths=*/true)) {}
316+
317+
IncrementalLongestPathAnalysis(Operation *moduleOp, mlir::AnalysisManager &am,
318+
const LongestPathAnalysisOption &option)
319+
: LongestPathAnalysis(moduleOp, am, option) {
320+
assert(option.lazyComputation && "Lazy computation must be enabled");
321+
}
280322

281-
// Compute all paths for specified value and bit.
282-
FailureOr<ArrayRef<OpenPath>> getOrComputePaths(Value value, size_t bitPos);
323+
using LongestPathAnalysis::getAverageMaxDelay;
324+
using LongestPathAnalysis::getMaxDelay;
283325

284326
// Check if operation can be safely modified without invalidating analysis.
285327
bool isOperationValidToMutate(Operation *op) const;
@@ -288,17 +330,6 @@ class IncrementalLongestPathAnalysis : private LongestPathAnalysis,
288330
void notifyOperationModified(Operation *op) override;
289331
void notifyOperationReplaced(Operation *op, ValueRange replacement) override;
290332
void notifyOperationErased(Operation *op) override;
291-
292-
private:
293-
bool isAnalysisValid = true;
294-
};
295-
296-
// A wrapper class for the longest path analysis that also traces debug points.
297-
// This is necessary for analysis manager to cache the analysis results.
298-
class LongestPathAnalysisWithTrace : public LongestPathAnalysis {
299-
public:
300-
LongestPathAnalysisWithTrace(Operation *moduleOp, mlir::AnalysisManager &am)
301-
: LongestPathAnalysis(moduleOp, am, {true, false}) {}
302333
};
303334

304335
// A collection of longest paths. The data structure owns the paths, and used
@@ -316,6 +347,9 @@ class LongestPathCollection {
316347
// Sort and drop all paths except the longest path per fanout point.
317348
void sortAndDropNonCriticalPathsPerFanOut();
318349

350+
// Merge another collection into this one.
351+
void merge(const LongestPathCollection &other);
352+
319353
private:
320354
MLIRContext *ctx;
321355
};

integration_test/Bindings/Python/dialects/aig.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,25 @@ def build_top(module):
102102
# CHECK: top:test_aig;c[0] 0
103103
# CHECK: top:test_aig;child:test_child;a[0] 2
104104
print(collection.longest_path.to_flamegraph())
105+
106+
# Access the second module (test_aig), then its body block, then the first operation (hw.instance), then its second result
107+
test_child = m.body.operations[0]
108+
body_block = test_child.regions[0].blocks[0]
109+
result0 = body_block.operations[0].results[0]
110+
result1 = body_block.operations[1].results[0]
111+
112+
analysis = LongestPathAnalysis(test_child,
113+
collect_debug_info=True,
114+
keep_only_max_delay_paths=True,
115+
lazy_computation=True)
116+
c1 = analysis.get_paths(result0, 0)
117+
c2 = analysis.get_paths(result1, 0)
118+
# CHECK-NEXT: len(c1) = 1
119+
# CHECK-NEXT: len(c2) = 1
120+
print("len(c1) =", len(c1))
121+
print("len(c2) =", len(c2))
122+
123+
s1, s2 = len(c1), len(c2)
124+
c1.merge(c2)
125+
# CHECK-NEXT: merge: True
126+
print("merge:", len(c1) == s1 + s2)

lib/Bindings/Python/AIGModule.cpp

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,32 @@ void circt::python::populateDialectAIGSubmodule(nb::module_ &m) {
3232
.def(
3333
"__init__",
3434
[](AIGLongestPathAnalysis *self, MlirOperation module,
35-
bool traceDebugPoints) {
36-
new (self) AIGLongestPathAnalysis(
37-
aigLongestPathAnalysisCreate(module, traceDebugPoints));
35+
bool collectDebugInfo, bool keepOnlyMaxDelayPaths,
36+
bool lazyComputation) {
37+
new (self) AIGLongestPathAnalysis(aigLongestPathAnalysisCreate(
38+
module, collectDebugInfo, keepOnlyMaxDelayPaths,
39+
lazyComputation));
3840
},
39-
nb::arg("module"), nb::arg("trace_debug_points") = true)
41+
nb::arg("module"), nb::arg("collect_debug_info") = false,
42+
nb::arg("keep_only_max_delay_paths") = false,
43+
nb::arg("lazy_computation") = false)
4044
.def("__del__",
4145
[](AIGLongestPathAnalysis &self) {
4246
aigLongestPathAnalysisDestroy(self);
4347
})
48+
.def("get_paths",
49+
[](AIGLongestPathAnalysis *self, MlirValue value, int64_t bitPos,
50+
bool elaboratePaths) -> AIGLongestPathCollection {
51+
auto collection =
52+
AIGLongestPathCollection(aigLongestPathAnalysisGetPaths(
53+
*self, value, bitPos, elaboratePaths));
54+
55+
if (aigLongestPathCollectionIsNull(collection))
56+
throw nb::value_error(
57+
"Failed to get all paths, see previous error(s).");
58+
59+
return collection;
60+
})
4461
.def("get_all_paths",
4562
[](AIGLongestPathAnalysis *self, const std::string &moduleName,
4663
bool elaboratePaths) -> AIGLongestPathCollection {
@@ -71,6 +88,10 @@ void circt::python::populateDialectAIGSubmodule(nb::module_ &m) {
7188
[](AIGLongestPathCollection &self,
7289
int pathIndex) -> AIGLongestPathDataflowPath {
7390
return aigLongestPathCollectionGetDataflowPath(self, pathIndex);
91+
})
92+
.def("merge",
93+
[](AIGLongestPathCollection &self, AIGLongestPathCollection &src) {
94+
aigLongestPathCollectionMerge(self, src);
7495
});
7596

7697
nb::class_<AIGLongestPathDataflowPath>(m, "_LongestPathDataflowPath")

lib/Bindings/Python/dialects/aig.py

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,16 @@ def print_summary(self) -> None:
321321
print(f"99th percentile delay: {self.get_by_delay_ratio(0.99).delay}")
322322
print(f"99.9th percentile delay: {self.get_by_delay_ratio(0.999).delay}")
323323

324+
def merge(self, src: "LongestPathCollection"):
325+
"""
326+
Merge another collection into this one.
327+
Args:
328+
src: The collection to merge into this one
329+
"""
330+
self.collection.merge(src.collection)
331+
# Re-initialize to reflect the merged collection
332+
self.__init__(self.collection)
333+
324334

325335
# ============================================================================
326336
# Main Analysis Interface
@@ -337,22 +347,48 @@ class LongestPathAnalysis:
337347
analysis: The underlying C++ analysis object
338348
"""
339349

340-
def __init__(self, module, trace_debug_points: bool = True):
350+
def __init__(self,
351+
module,
352+
collect_debug_info: bool = True,
353+
keep_only_max_delay_paths: bool = False,
354+
lazy_computation: bool = False):
341355
"""
342356
Initialize the longest path analysis for a given module.
343357
Args:
344358
module: The MLIR module to analyze
345-
trace_debug_points: Whether to include debug points in the analysis.
346-
The debug points provide additional information about the path,
347-
but increase the analysis time and memory usage.
359+
collect_debug_info: Whether to include debug points in the analysis.
360+
Debug points provide additional information about the path,
361+
but increase analysis time and memory usage.
362+
keep_only_max_delay_paths: Keep only maximum-delay paths in collections.
363+
lazy_computation: Enable lazy (on-demand) computation.
348364
"""
349-
self.analysis = aig._LongestPathAnalysis(module, trace_debug_points)
365+
self.analysis = aig._LongestPathAnalysis(module, collect_debug_info,
366+
keep_only_max_delay_paths,
367+
lazy_computation)
368+
369+
def get_paths(self,
370+
value,
371+
bit_pos: int,
372+
elaborate_paths: bool = True) -> LongestPathCollection:
373+
"""
374+
Perform longest path analysis and return all timing paths to the
375+
specified value and bit position.
376+
Args:
377+
value: The value to analyze
378+
bit_pos: The bit position to analyze
379+
elaborate_paths: Whether to elaborate the paths with detailed information
380+
Returns:
381+
LongestPathCollection containing all paths sorted by delay
382+
"""
383+
return LongestPathCollection(
384+
self.analysis.get_paths(value, bit_pos, elaborate_paths))
350385

351386
def get_all_paths(self,
352387
module_name: str,
353388
elaborate_paths: bool = True) -> LongestPathCollection:
354389
"""
355-
Perform longest path analysis and return all timing paths.
390+
Perform longest path analysis and return all timing paths in side
391+
the module hierarchy.
356392
This method analyzes the specified module and returns a collection
357393
of all timing paths, sorted by delay in descending order.
358394
Args:

lib/CAPI/Dialect/AIG.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,39 @@ AIGLongestPathObject wrap(const DataflowPath::OutputPort *object) {
7575
//===----------------------------------------------------------------------===//
7676

7777
AIGLongestPathAnalysis aigLongestPathAnalysisCreate(MlirOperation module,
78-
bool traceDebugPoints) {
78+
bool collectDebugInfo,
79+
bool keepOnlyMaxDelayPaths,
80+
bool lazyComputation) {
7981
auto *op = unwrap(module);
8082
auto *wrapper = new LongestPathAnalysisWrapper();
8183
wrapper->analysisManager =
8284
std::make_unique<mlir::ModuleAnalysisManager>(op, nullptr);
8385
mlir::AnalysisManager am = *wrapper->analysisManager;
84-
if (traceDebugPoints)
85-
wrapper->analysis = std::make_unique<LongestPathAnalysisWithTrace>(op, am);
86-
else
87-
wrapper->analysis = std::make_unique<LongestPathAnalysis>(op, am);
86+
wrapper->analysis = std::make_unique<LongestPathAnalysis>(
87+
op, am,
88+
LongestPathAnalysisOption(collectDebugInfo, lazyComputation,
89+
keepOnlyMaxDelayPaths));
8890
return wrap(wrapper);
8991
}
9092

9193
void aigLongestPathAnalysisDestroy(AIGLongestPathAnalysis analysis) {
9294
delete unwrap(analysis);
9395
}
9496

97+
AIGLongestPathCollection
98+
aigLongestPathAnalysisGetPaths(AIGLongestPathAnalysis analysis, MlirValue value,
99+
int64_t bitPos, bool elaboratePaths) {
100+
auto *wrapper = unwrap(analysis);
101+
auto *lpa = wrapper->analysis.get();
102+
auto *collection = new LongestPathCollection(lpa->getContext());
103+
auto result =
104+
lpa->computeGlobalPaths(unwrap(value), bitPos, collection->paths);
105+
if (failed(result))
106+
return {nullptr};
107+
collection->sortInDescendingOrder();
108+
return wrap(collection);
109+
}
110+
95111
AIGLongestPathCollection
96112
aigLongestPathAnalysisGetAllPaths(AIGLongestPathAnalysis analysis,
97113
MlirStringRef moduleName,
@@ -136,6 +152,13 @@ aigLongestPathCollectionGetDataflowPath(AIGLongestPathCollection collection,
136152
return wrap(&path);
137153
}
138154

155+
void aigLongestPathCollectionMerge(AIGLongestPathCollection dest,
156+
AIGLongestPathCollection src) {
157+
auto *destWrapper = unwrap(dest);
158+
auto *srcWrapper = unwrap(src);
159+
destWrapper->merge(*srcWrapper);
160+
}
161+
139162
//===----------------------------------------------------------------------===//
140163
// DataflowPath
141164
//===----------------------------------------------------------------------===//

lib/Conversion/DatapathToComb/DatapathToComb.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ struct DatapathCompressOpConversion : mlir::OpRewritePattern<CompressOp> {
9292
for (size_t j = 0; j < addends[0].size(); ++j) {
9393
SmallVector<std::pair<int64_t, Value>> delays;
9494
for (auto &addend : addends) {
95-
auto delay = analysis->getOrComputeMaxDelay(addend[j], 0);
95+
auto delay = analysis->getMaxDelay(addend[j], 0);
9696
if (failed(delay))
9797
return rewriter.notifyMatchFailure(op,
9898
"Failed to get delay for input");

0 commit comments

Comments
 (0)