Skip to content

Commit afd381f

Browse files
erwei-xilinxclaude
andauthored
Declare effects on air.dma_memcpy_nd; migrate FoldExecute to mightHaveEffect (#1575)
* Declare memory effects on air.dma_memcpy_nd Read on src, Write on dst — mirrors memref.copy's MemReadAt/MemWriteAt declarations. Without this the op was opaque to side-effect queries: ExecuteOp::getEffects had to fall back to conservative R+W+Free for any execute body that contained a DMA (the catch-all sawUnknownOp branch); FoldExecute's hand-rolled Write check silently skipped DMA ops; LocalAliasAnalysis::getModRef returned MayMod/MayRef for them. air.channel.put / air.channel.get are intentionally left out — modelling them correctly requires a side effect on the channel symbol (not just the local memref), since put has no Write to memory but is still observable. That needs a custom Resource and is filed as a followup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Migrate FoldExecute side-effect check to upstream helper Replace the hand-rolled walk over body.without_terminator() with mlir::mightHaveEffect<MemoryEffects::Write>(op). ExecuteOp::getEffects already mirrors body effects up; the upstream helper additionally returns true when the body contains an unknown-effect op, which the old walk silently skipped (latent unsafety: any effect-bearing op without MemoryEffectOpInterface declared was treated as no-Write). execute_3's expectation changes accordingly: its body contains a dma_memcpy_nd that now reports a Write on dst, so the execute is conservatively kept. Folding it is only safe if the dst is provably body-local; that needs escape analysis and is left as a followup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Use upstream hasEffect in checkOpOperandReadOrWrite Replace the explicit air.dma_memcpy_nd branch with a generic mlir::hasEffect<MemoryEffects::{Read,Write}> query. Now that the DMA op declares MemReadAt<src>/MemWriteAt<dst>, the upstream helper returns the same answer the dialect-specific switch did, and any future op that declares effects is picked up automatically. The air.channel.put/get and linalg branches stay because channels do not yet declare effects (separate followup) and the linalg path uses the DPS interface, not memory effects. Two callers (AIRDependencyScheduleOpt.cpp:1126/1146, Dependency.cpp:2928) benefit transparently — the return-code contract is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5c2418f commit afd381f

4 files changed

Lines changed: 26 additions & 29 deletions

File tree

mlir/include/air/Dialect/AIR/AIR.td

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,11 +466,13 @@ def air_DmaMemcpyNdOp: air_Op<"dma_memcpy_nd",
466466
let summary = "dma operator";
467467
let arguments = (
468468
ins Variadic<air_AsyncToken>:$async_dependencies,
469-
AnyRankedOrUnrankedMemRef:$dst,
469+
Arg<AnyRankedOrUnrankedMemRef, "destination memref",
470+
[MemWriteAt<0, FullEffect>]>:$dst,
470471
Variadic<Index>:$dst_offsets,
471472
Variadic<Index>:$dst_sizes,
472473
Variadic<Index>:$dst_strides,
473-
AnyRankedOrUnrankedMemRef:$src,
474+
Arg<AnyRankedOrUnrankedMemRef, "source memref",
475+
[MemReadAt<0, FullEffect>]>:$src,
474476
Variadic<Index>:$src_offsets,
475477
Variadic<Index>:$src_sizes,
476478
Variadic<Index>:$src_strides,

mlir/lib/Dialect/AIR/IR/AIRDialect.cpp

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,19 +2103,10 @@ static LogicalResult FoldExecute(air::ExecuteOp op, PatternRewriter &rewriter) {
21032103

21042104
// if we get here then only the async token result has uses.
21052105

2106-
// Don't fold if body contains ops with memory write side effects
2107-
// (e.g., memref.store). These ops must be preserved even if the
2108-
// execute's async token becomes unused due to wait_all folding.
2109-
for (auto &bodyOp : body.without_terminator()) {
2110-
if (auto memEffects =
2111-
dyn_cast_if_present<MemoryEffectOpInterface>(bodyOp)) {
2112-
SmallVector<MemoryEffects::EffectInstance> effects;
2113-
memEffects.getEffects(effects);
2114-
for (auto &effect : effects)
2115-
if (isa<MemoryEffects::Write>(effect.getEffect()))
2116-
return failure();
2117-
}
2118-
}
2106+
// ExecuteOp::getEffects mirrors body effects up; an inner Write or any
2107+
// unknown-effect op blocks folding.
2108+
if (mlir::mightHaveEffect<MemoryEffects::Write>(op))
2109+
return failure();
21192110

21202111
// if there are extra results than async token, and none of them are used,
21212112
// then replace the execute with a wait_all no-op.

mlir/lib/Util/Util.cpp

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -824,19 +824,18 @@ char air::checkOpOperandReadOrWrite(Value v, Operation *owner) {
824824
}
825825
char air::checkOpOperandReadOrWrite(mlir::OpOperand &op_operand) {
826826
auto owner = op_operand.getOwner();
827-
// If used in DmaMemcpy Op
828-
if (auto dma = dyn_cast_if_present<xilinx::air::DmaMemcpyNdOp>(owner)) {
829-
if (op_operand.is(dma.getSrcMemref())) {
830-
return 'r';
831-
} else if (op_operand.is(dma.getDstMemref())) {
832-
return 'w';
833-
} else {
834-
return 'u';
835-
}
836-
}
827+
// Generic side-effect query first. air.dma_memcpy_nd declares
828+
// MemReadAt<src> + MemWriteAt<dst>, so it answers here without an
829+
// explicit dialect branch. air.channel.put/get do not yet declare
830+
// effects (channel-resource modeling is a separate followup) and
831+
// continue to use the dialect-specific branches below.
832+
if (mlir::hasEffect<mlir::MemoryEffects::Write>(owner, op_operand.get()))
833+
return 'w';
834+
if (mlir::hasEffect<mlir::MemoryEffects::Read>(owner, op_operand.get()))
835+
return 'r';
837836
// If used in Channel Put Op
838-
else if (auto channel_put =
839-
dyn_cast_if_present<xilinx::air::ChannelPutOp>(owner)) {
837+
if (auto channel_put =
838+
dyn_cast_if_present<xilinx::air::ChannelPutOp>(owner)) {
840839
if (op_operand.is(channel_put.getSrc())) {
841840
return 'r';
842841
} else {

mlir/test/Dialect/AIR/air_canonicalize.mlir

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,15 @@ func.func @execute_2() -> (index, !air.async.token) {
291291
return %results, %t : index, !air.async.token
292292
}
293293

294+
// air.dma_memcpy_nd has a Write effect on its dst, so the execute body
295+
// is conservatively kept by FoldExecute. The unused %results index is
296+
// still replaced by the constant.
294297
// CHECK-LABEL: execute_3
295298
// CHECK-NEXT: arith.constant 0 : index
296-
// CHECK-NEXT: air.wait_all async
297-
// CHECK-NEXT: return %{{.*}}, %{{.*}} : index, !air.async.token
299+
// CHECK-NEXT: air.execute
300+
// CHECK-NEXT: memref.alloc
301+
// CHECK-NEXT: air.dma_memcpy_nd
302+
// CHECK: return %{{.*}}, %{{.*}} : index, !air.async.token
298303
func.func @execute_3(%arg0: memref<1xi32>) -> (index, !air.async.token) {
299304
%c0 = arith.constant 0 : index
300305
%async_token, %results = air.execute -> (index) {

0 commit comments

Comments
 (0)