MLIR-AIE 1.4.5.dev
AIECoreIntRangeNarrowing.cpp
Go to the documentation of this file.
1//===- AIECoreIntRangeNarrowing.cpp -----------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7//
8// The upstream arith-int-range-narrowing pass cannot nest under aie.core
9// (cores are not IsolatedFromAbove), and running it over the whole device
10// would also rewrite runtime sequences. So this pass applies the same patterns
11// to each core and function body in turn.
12//
13//===----------------------------------------------------------------------===//
14
17
18#include "mlir/Analysis/DataFlow/ConstantPropagationAnalysis.h"
19#include "mlir/Analysis/DataFlow/IntegerRangeAnalysis.h"
20#include "mlir/Analysis/DataFlow/Utils.h"
21#include "mlir/Analysis/DataFlowFramework.h"
22#include "mlir/Dialect/Arith/IR/Arith.h"
23#include "mlir/Dialect/Arith/Transforms/Passes.h"
24#include "mlir/Dialect/Func/IR/FuncOps.h"
25#include "mlir/Pass/Pass.h"
26#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
27#include "mlir/Transforms/RegionUtils.h"
28
29namespace xilinx::AIE {
30#define GEN_PASS_DEF_AIECOREINTRANGENARROWING
31#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
32} // namespace xilinx::AIE
33
34#define DEBUG_TYPE "aie-core-int-range-narrowing"
35
36using namespace mlir;
37using namespace xilinx;
38using namespace xilinx::AIE;
39
40namespace {
41
42// Drops the solver state of erased ops, so an op allocated at the same address
43// later does not inherit a stale range. Mirrors the listener the upstream pass
44// uses.
45class DataFlowListener : public RewriterBase::Listener {
46public:
47 DataFlowListener(DataFlowSolver &s) : s(s) {}
48
49protected:
50 void notifyOperationErased(Operation *op) override {
51 s.eraseState(s.getProgramPointAfter(op));
52 for (Value res : op->getResults())
53 s.eraseState(res);
54 }
55
56 DataFlowSolver &s;
57};
58
59struct AIECoreIntRangeNarrowingPass
60 : xilinx::AIE::impl::AIECoreIntRangeNarrowingBase<
61 AIECoreIntRangeNarrowingPass> {
62 using AIECoreIntRangeNarrowingBase::AIECoreIntRangeNarrowingBase;
63
64 void runOnOperation() override {
65 SmallVector<unsigned> bitwidths(bitwidthsSupported.begin(),
66 bitwidthsSupported.end());
67 if (bitwidths.empty())
68 bitwidths.push_back(32);
69
70 SmallVector<Operation *> bodies;
71 getOperation().walk([&](Operation *op) {
72 if (isa<CoreOp>(op))
73 bodies.push_back(op);
74 else if (auto func = dyn_cast<func::FuncOp>(op);
75 func && !func.isExternal())
76 bodies.push_back(op);
77 });
78
79 for (Operation *body : bodies)
80 if (failed(narrow(body, bitwidths)))
81 return signalPassFailure();
82 }
83
84 LogicalResult narrow(Operation *body, ArrayRef<unsigned> bitwidths) {
85 MLIRContext *ctx = body->getContext();
86 DataFlowSolver solver;
87 dataflow::loadBaselineAnalyses(solver);
88 solver.load<dataflow::IntegerRangeAnalysis>();
89 // A solver run on the core never visits the values defined above it (its
90 // buffers, locks and tiles). Left uninitialized, they make any control
91 // flow that depends on them look dead, such as a loop bounded by a buffer
92 // load. Start them pessimistic, like entry block arguments.
93 SetVector<Value> captured;
94 getUsedValuesDefinedAbove(body->getRegions(), captured);
95 for (Value v : captured) {
96 (void)solver
97 .getOrCreateState<dataflow::Lattice<dataflow::ConstantValue>>(v)
98 ->join(dataflow::ConstantValue::getUnknownConstant());
99 (void)solver.getOrCreateState<dataflow::IntegerValueRangeLattice>(v)
100 ->join(IntegerValueRange::getMaxRange(v));
101 }
102 if (failed(solver.initializeAndRun(body)))
103 return failure();
104
105 DataFlowListener listener(solver);
106 RewritePatternSet patterns(ctx);
107 arith::populateIntRangeNarrowingPatterns(patterns, solver, bitwidths);
108 arith::populateControlFlowValuesNarrowingPatterns(patterns, solver,
109 bitwidths);
110 // A core is not IsolatedFromAbove, so hand the driver its ops rather than
111 // its region. Collected in postorder, they are visited bottom-up, as in
112 // the upstream pass: the cmpi pattern needs the ranges attached to its
113 // original operands.
114 SmallVector<Operation *> ops;
115 body->getRegion(0).walk([&](Operation *op) { ops.push_back(op); });
116 return applyOpPatternsGreedily(ops, std::move(patterns),
117 GreedyRewriteConfig()
118 .setScope(&body->getRegion(0))
119 .setListener(&listener));
120 }
121};
122
123} // namespace
124
125std::unique_ptr<OperationPass<DeviceOp>>
127 return std::make_unique<AIECoreIntRangeNarrowingPass>();
128}
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIECoreIntRangeNarrowingPass()