MLIR-AIE 1.4.5.dev
AIEFuseTraceBuffers.cpp
Go to the documentation of this file.
1//===- AIEFuseTraceBuffers.cpp ----------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
11
12#include "mlir/Dialect/MemRef/IR/MemRef.h"
13#include "mlir/IR/Builders.h"
14#include "mlir/Pass/Pass.h"
15
16namespace xilinx::AIEX {
17#define GEN_PASS_DEF_AIEFUSETRACEBUFFERS
18#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
19} // namespace xilinx::AIEX
20
21using namespace mlir;
22using namespace xilinx;
23using namespace xilinx::AIEX;
24
25namespace {
26
27struct AIEFuseTraceBuffersPass
28 : xilinx::AIEX::impl::AIEFuseTraceBuffersBase<AIEFuseTraceBuffersPass> {
29
30 // Bytes each sequence claims for itself and everything it calls.
31 llvm::DenseMap<Operation *, int64_t> claim;
32 // Flattened slice list per sequence, in fused-buffer order.
33 llvm::DenseMap<Operation *, SmallVector<AIE::TraceSliceAttr>> slices;
34 llvm::DenseSet<Operation *> done;
35
36 void runOnOperation() override {
37 ModuleOp mod = getOperation();
38 for (auto device : mod.getOps<AIE::DeviceOp>()) {
39 for (auto seq : device.getOps<AIE::RuntimeSequenceOp>()) {
40 if (failed(fuse(seq))) {
41 return signalPassFailure();
42 }
43 }
44 }
45 }
46
47 /// Give `seq` one trace buffer covering its own traces and those of every
48 /// sequence it calls. Pass each call site its slice.
49 LogicalResult fuse(AIE::RuntimeSequenceOp seq) {
50 if (!done.insert(seq.getOperation()).second) {
51 return success();
52 }
53
54 SmallVector<RunOp> runs;
55 seq.walk([&](RunOp run) { runs.push_back(run); });
56
57 // A callee's claim covers its own callees. Recurse before summing.
58 for (RunOp run : runs) {
59 AIE::RuntimeSequenceOp callee = run.getCalleeRuntimeSequenceOp();
60 if (!callee) {
61 return run.emitOpError("cannot resolve callee runtime sequence");
62 }
63 if (failed(fuse(callee))) {
64 return failure();
65 }
66 }
67
68 AIE::TraceBufferAttr ownBuffer = seq.getTraceBufferAttr();
69 int64_t ownClaim = ownBuffer ? ownBuffer.getSize() : 0;
70 int64_t total = ownClaim;
71 for (RunOp run : runs) {
72 total += claim.lookup(run.getCalleeRuntimeSequenceOp().getOperation());
73 }
74
75 claim[seq.getOperation()] = total;
76 if (total == 0) {
77 return success();
78 }
79
80 // No callee claims to place: the argument from trace lowering already
81 // covers this sequence.
82 bool needsFusing = total > ownClaim;
83 if (!needsFusing) {
84 slices[seq.getOperation()] = ownSlices(seq, ownBuffer, /*base=*/0);
85 return success();
86 }
87
88 if (ownBuffer && !ownBuffer.getDedicated()) {
89 return seq.emitOpError(
90 "runs traced designs and sets trace.host_config "
91 "reuse_output_buffer=true. Growing the output buffer to hold their "
92 "slices is not supported. Use a separate trace buffer instead");
93 }
94
95 OpBuilder builder(seq);
96 Block &entry = seq.getBody().front();
97 auto i8 = IntegerType::get(seq.getContext(), 8);
98 auto fusedType = MemRefType::get({total}, i8);
99
100 BlockArgument fusedArg;
101 if (ownBuffer) {
102 fusedArg = entry.getArgument(ownBuffer.getArgIndex());
103 fusedArg.setType(fusedType);
104 } else {
105 fusedArg = entry.addArgument(fusedType, seq.getLoc());
106 }
107
108 // This sequence's own patches use offsets from the argument base. Its
109 // traces must occupy the front.
110 SmallVector<AIE::TraceSliceAttr> flattened =
111 ownSlices(seq, ownBuffer, /*base=*/0);
112 int64_t cursor = ownClaim;
113
114 for (RunOp run : runs) {
115 AIE::RuntimeSequenceOp callee = run.getCalleeRuntimeSequenceOp();
116 int64_t calleeClaim = claim.lookup(callee.getOperation());
117 if (calleeClaim == 0) {
118 continue;
119 }
120
121 AIE::TraceBufferAttr calleeBuffer = callee.getTraceBufferAttr();
122 if (calleeBuffer && !calleeBuffer.getDedicated()) {
123 return run.emitOpError(
124 "calls a design that sets trace.host_config "
125 "reuse_output_buffer=true. That design writes its trace data into "
126 "its own output buffer, so this call site has no argument to "
127 "slice. Slicing an output buffer is not supported. Use a separate "
128 "trace buffer instead");
129 }
130 unsigned calleeArgIdx = calleeBuffer
131 ? calleeBuffer.getArgIndex()
132 : callee.getBody().getNumArguments() - 1;
133 auto calleeType = cast<MemRefType>(
134 callee.getBody().getArgument(calleeArgIdx).getType());
135
136 builder.setInsertionPoint(run);
137 Value slice =
138 makeSlice(builder, run.getLoc(), fusedArg, cursor, calleeType);
139 run.getOperation()->insertOperands(run.getOperation()->getNumOperands(),
140 {slice});
141
142 for (AIE::TraceSliceAttr s : slices.lookup(callee.getOperation())) {
143 flattened.push_back(AIE::TraceSliceAttr::get(
144 seq.getContext(), s.getDevice(), s.getSequence(),
145 s.getOffset() + cursor, s.getSize()));
146 }
147 cursor += calleeClaim;
148 }
149
150 slices[seq.getOperation()] = flattened;
151 seq.setTraceSlicesAttr(builder.getArrayAttr(
152 SmallVector<Attribute>(flattened.begin(), flattened.end())));
153 seq.setTraceBufferAttr(AIE::TraceBufferAttr::get(
154 seq.getContext(), fusedArg.getArgNumber(), /*offset=*/0, total,
155 /*dedicated=*/true));
156 return success();
157 }
158
159 SmallVector<AIE::TraceSliceAttr> ownSlices(AIE::RuntimeSequenceOp seq,
160 AIE::TraceBufferAttr ownBuffer,
161 int64_t base) {
162 SmallVector<AIE::TraceSliceAttr> result;
163 if (ArrayAttr existing = seq.getTraceSlicesAttr()) {
164 for (Attribute a : existing) {
165 auto s = cast<AIE::TraceSliceAttr>(a);
166 result.push_back(AIE::TraceSliceAttr::get(
167 seq.getContext(), s.getDevice(), s.getSequence(),
168 base + s.getOffset(), s.getSize()));
169 }
170 return result;
171 }
172 if (!ownBuffer) {
173 return result;
174 }
175 auto device = seq->getParentOfType<AIE::DeviceOp>();
176 StringAttr deviceName = device.getSymNameAttr();
177 result.push_back(AIE::TraceSliceAttr::get(
178 seq.getContext(), deviceName ? deviceName.getValue() : StringRef(),
179 seq.getSymName(), base, ownBuffer.getSize()));
180 return result;
181 }
182
183 /// A `size`-byte window of `buffer` at `offset`, typed as the callee's
184 /// argument. `aiex.run` requires exact type equality. The reinterpret_cast
185 /// therefore erases the subview's strided layout.
186 /// `traceSubviewToBlockArgument` reads the byte offset from the subview.
187 Value makeSlice(OpBuilder &builder, Location loc, Value buffer,
188 int64_t offset, MemRefType resultType) {
189 int64_t size = resultType.getNumElements();
190 Value sub = memref::SubViewOp::create(
191 builder, loc, buffer,
192 ArrayRef<OpFoldResult>{builder.getIndexAttr(offset)},
193 ArrayRef<OpFoldResult>{builder.getIndexAttr(size)},
194 ArrayRef<OpFoldResult>{builder.getIndexAttr(1)});
195 return memref::ReinterpretCastOp::create(
196 builder, loc, resultType, sub, builder.getIndexAttr(0),
197 ArrayRef<OpFoldResult>{builder.getIndexAttr(size)},
198 ArrayRef<OpFoldResult>{builder.getIndexAttr(1)});
199 }
200};
201
202} // namespace
203
204std::unique_ptr<OperationPass<ModuleOp>>
206 return std::make_unique<AIEFuseTraceBuffersPass>();
207}
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createAIEFuseTraceBuffersPass()