MLIR-AIE 1.4.6.dev
AIESCFToControlFlow.cpp
Go to the documentation of this file.
1//===- AIESCFToControlFlow.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// Lower scf to cf everywhere in the module EXCEPT inside aie.runtime_sequence
9// bodies.
10//
11// A runtime sequence is NoTerminator and is translated to a flat NPU
12// instruction stream with no concept of branches. Its loops are lowered by the
13// dedicated runtime-sequence path (unrolling for compile-time-constant trip
14// counts; the dynamic EmitC path otherwise), not by generic scf->cf. Applying
15// convert-scf-to-cf to a runtime sequence would (1) break the NoTerminator
16// block invariant -- the ops after a lowered loop land in a terminator-less
17// block -- and (2) emit cf.br / cf.cond_br that the flat NPU emitter silently
18// drops, a miscompile. So we mark scf inside a runtime sequence as legal
19// (leave it alone) and convert everything else.
20//
21//===----------------------------------------------------------------------===//
22
27
28#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
29#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h"
30#include "mlir/Dialect/SCF/IR/SCF.h"
31#include "mlir/Pass/Pass.h"
32#include "mlir/Transforms/DialectConversion.h"
33
34namespace xilinx::AIEX {
35#define GEN_PASS_DEF_AIESCFTOCONTROLFLOW
36#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
37} // namespace xilinx::AIEX
38
39using namespace mlir;
40using namespace xilinx;
41using namespace xilinx::AIEX;
42
43namespace {
44
45struct AIESCFToControlFlowPass
46 : xilinx::AIEX::impl::AIESCFToControlFlowBase<AIESCFToControlFlowPass> {
47
48 void runOnOperation() override {
49 ModuleOp module = getOperation();
50
51 SmallVector<scf::ForOp> foreverLoops;
52 module.walk([&](scf::ForOp forOp) {
53 if (forOp->getParentOfType<AIE::CoreOp>() &&
54 forOp.getInitArgs().empty() && AIE::isForeverForLoop(forOp))
55 foreverLoops.push_back(forOp);
56 });
57 for (scf::ForOp forOp : foreverLoops)
59
60 ConversionTarget target(getContext());
61 // Everything is legal by default; only scf outside a runtime sequence is
62 // illegal. (Matching upstream convert-scf-to-cf, which marks every other
63 // op legal so the structural block-splitting can relocate them freely.)
64 target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
65 target.addLegalDialect<cf::ControlFlowDialect>();
66 // scf is illegal (must be converted) UNLESS it lives inside a runtime
67 // sequence, whose control flow has its own lowering path.
68 target.addDynamicallyLegalDialect<scf::SCFDialect>([](Operation *op) {
69 return op->getParentOfType<AIE::RuntimeSequenceOp>() != nullptr;
70 });
71
72 RewritePatternSet patterns(&getContext());
73 populateSCFToControlFlowConversionPatterns(patterns);
74
75 if (failed(applyPartialConversion(module, target, std::move(patterns))))
76 signalPassFailure();
77 }
78};
79
80} // namespace
81
82std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
84 return std::make_unique<AIESCFToControlFlowPass>();
85}
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createAIESCFToControlFlowPass()
mlir::scf::WhileOp replaceWithForeverLoop(mlir::scf::ForOp forOp)
Replace forOp, which isForeverForLoop accepts, with an scf.while that isForeverLoop accepts and that ...