* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "TritonToGraph/ControlFlowGraphBuilder.h"
#include "TritonToGraph/DataflowGraph.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "triton/Dialect/Triton/IR/Dialect.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#define DEBUG_TYPE "build-cfg"
using namespace mlir;
using namespace mlir::triton;
using namespace cfg;
void BuildCFGPass::runOnOperation() {
auto module = getOperation();
llvm::errs() << "Building CFG for module\n";
std::string outputDir = this->outputDir;
llvm::SmallString<128> outputPath(outputDir);
llvm::sys::fs::create_directories(outputPath);
llvm::errs() << "CFG output directory: " << outputPath << "\n";
for (triton::FuncOp func : module.getOps<triton::FuncOp>()) {
llvm::errs() << "Processing function: " << func.getName() << "\n";
auto cfg = buildForFunction(func);
if (!cfg) {
func.emitError() << "Failed to build CFG for function";
signalPassFailure();
return;
}
std::string baseName = func.getName().str();
llvm::SmallString<128> textPath(outputPath);
llvm::sys::path::append(textPath, baseName + "_cfg.txt");
if (auto err = cfg->exportToFile(textPath)) {
llvm::errs() << "Failed to export CFG to " << textPath << "\n";
} else {
llvm::errs() << "Exported CFG to " << textPath << "\n";
}
llvm::SmallString<128> dotPath(outputPath);
llvm::sys::path::append(dotPath, baseName + "_cfg.dot");
if (auto err = cfg->exportToFile(dotPath)) {
llvm::errs() << "Failed to export CFG to " << dotPath << "\n";
} else {
llvm::errs() << "Exported CFG to " << dotPath << "\n";
}
llvm::SmallString<128> jsonPath(outputPath);
llvm::sys::path::append(jsonPath, baseName + "_cfg.json");
if (auto err = cfg->exportToFile(jsonPath)) {
llvm::errs() << "Failed to export CFG to " << jsonPath << "\n";
} else {
llvm::errs() << "Exported CFG to " << jsonPath << "\n";
}
llvm::SmallString<128> htmlPath(outputPath);
llvm::sys::path::append(htmlPath, baseName + "_cfg.html");
if (auto err = cfg->exportToHTML(htmlPath)) {
llvm::errs() << "Failed to export CFG to " << htmlPath << "\n";
} else {
llvm::errs() << "Exported CFG to " << htmlPath << "\n";
}
llvm::errs() << " Building DataFlowGraph with Memory SSA...\n";
DataFlowGraph dataFlowGraph(*cfg);
dataFlowGraph.build();
std::error_code ec;
llvm::SmallString<128> dataflowPath(outputPath);
llvm::sys::path::append(dataflowPath, baseName + "_dataflow.json");
llvm::raw_fd_ostream dfOs(dataflowPath, ec);
if (!ec) {
dataFlowGraph.exportToJSON(dfOs);
llvm::errs() << " Exported DataFlowGraph to " << dataflowPath << "\n";
}
}
}
std::unique_ptr<cfg::ControlFlowGraph>
BuildCFGPass::buildForFunction(triton::FuncOp func) {
ControlFlowGraphBuilder cfgBuilder;
return cfgBuilder.build(func);
}
ControlFlowGraphBuilder::RegionBlocks
ControlFlowGraphBuilder::buildForRegion(Region ®ion, cfg::ControlFlowGraph &cfg,
cfg::BasicBlock *entryBlock,
cfg::BasicBlock *parentStructure) {
cfg::BasicBlock *currentBB = entryBlock;
cfg::BasicBlock *lastBlock = entryBlock;
for (Block &block : region) {
if (!blockToBasicBlockMap.count(&block)) {
auto *bb = cfg.createBasicBlock(cfg::BlockType::NORMAL, parentStructure);
registerBlockMapping(&block, bb);
}
}
for (Block &block : region) {
cfg::BasicBlock *blockBB = blockToBasicBlockMap[&block];
if (blockBB == blockToBasicBlockMap.lookup(®ion.front())) {
currentBB = processBlock(block, cfg, currentBB, parentStructure);
} else {
if (lastBlock && lastBlock != blockBB) {
bool hasEdge = false;
for (auto *succ : lastBlock->getSuccessors()) {
if (succ == blockBB) {
hasEdge = true;
break;
}
}
if (!hasEdge && !lastBlock->endsWithReturnOp()) {
cfg.addEdge(lastBlock, blockBB);
}
}
currentBB = processBlock(block, cfg, blockBB, parentStructure);
}
if (currentBB) {
lastBlock = currentBB;
if(lastBlock->endsWithReturnOp())
{
cfg.addEdge(lastBlock, cfg.getExitBlock());
}
}
}
return {entryBlock, lastBlock};
}
cfg::BasicBlock *ControlFlowGraphBuilder::processBlock(Block &block, cfg::ControlFlowGraph &cfg,
cfg::BasicBlock *currentBB,
cfg::BasicBlock *parentStructure) {
if (!currentBB) return nullptr;
for (Operation &op : block) {
if (isa<scf::IfOp>(op)) {
auto *ifCondBB = cfg.createBasicBlock(BlockType::IF_COND, parentStructure);
createInstruction(&op, ifCondBB, cfg);
cfg.addEdge(currentBB, ifCondBB);
currentBB = handleIfOp(cast<scf::IfOp>(op), cfg, ifCondBB, parentStructure);
}
else if (isa<scf::ForOp>(op)) {
auto *forCondBB = cfg.createBasicBlock(BlockType::FOR_COND, parentStructure);
createInstruction(&op, forCondBB, cfg);
cfg.addEdge(currentBB, forCondBB);
currentBB = handleForOp(cast<scf::ForOp>(op), cfg, forCondBB, parentStructure);
}
else if (isa<scf::WhileOp>(op)) {
auto *whileCondBB = cfg.createBasicBlock(BlockType::WHILE_COND, parentStructure);
createInstruction(&op, whileCondBB, cfg);
cfg.addEdge(currentBB, whileCondBB);
currentBB = handleWhileOp(cast<scf::WhileOp>(op), cfg, whileCondBB, parentStructure);
}
else if (isa<scf::YieldOp>(op)) {
createInstruction(&op, currentBB, cfg);
}
else if (isa<scf::ConditionOp>(op)) {
createInstruction(&op, currentBB, cfg);
}
else if (isa<cf::CondBranchOp>(op)) {
auto *condBrBB = cfg.createBasicBlock(BlockType::COND_BR, parentStructure);
createInstruction(&op, condBrBB, cfg);
cfg.addEdge(currentBB, condBrBB);
currentBB = handleCondBranchOp(cast<cf::CondBranchOp>(op), cfg, condBrBB, parentStructure);
}
else if (isa<cf::BranchOp>(op)) {
auto *brBB = cfg.createBasicBlock(BlockType::BR, parentStructure);
createInstruction(&op, brBB, cfg);
cfg.addEdge(currentBB, brBB);
currentBB = handleBranchOp(cast<cf::BranchOp>(op), cfg, brBB, parentStructure);
}
else if (isa<triton::ReturnOp>(op)) {
createInstruction(&op, currentBB, cfg);
}
else if (op.getNumRegions() > 0) {
auto *inst = createInstruction(&op, currentBB, cfg);
auto subGraph = std::make_unique<cfg::ControlFlowGraph>(cfg.getFunction());
auto *subEntry = subGraph->createBasicBlock(cfg::BlockType::ENTRY);
subGraph->setEntryBlock(subEntry);
auto *subExit = subGraph->createBasicBlock(cfg::BlockType::EXIT);
subGraph->setExitBlock(subExit);
size_t subInstId = 0;
for (size_t regionIdx = 0; regionIdx < op.getNumRegions(); ++regionIdx) {
Region ®ion = op.getRegion(regionIdx);
if (!region.empty()) {
cfg::BasicBlock *regionEntryBB = nullptr;
cfg::BasicBlock *regionLastBB = nullptr;
for (Block ®ionBlock : region) {
auto *bb = subGraph->createBasicBlock(cfg::BlockType::NORMAL);
if (!regionEntryBB) regionEntryBB = bb;
for (Operation ®ionOp : regionBlock) {
auto regionInst = std::make_unique<cfg::Instruction>(
subInstId++, ®ionOp, bb);
cfg::Instruction *instPtr = regionInst.get();
bb->addInstruction(std::move(regionInst));
subGraph->addOpToInstruction(®ionOp, instPtr);
}
if (regionLastBB) {
subGraph->addEdge(regionLastBB, bb);
}
regionLastBB = bb;
}
if (regionEntryBB) {
subGraph->addEdge(subEntry, regionEntryBB);
}
if (regionLastBB) {
subGraph->addEdge(regionLastBB, subExit);
}
}
}
inst->setSubGraph(std::move(subGraph));
}
else {
createInstruction(&op, currentBB, cfg);
}
}
return currentBB;
}
cfg::BasicBlock *ControlFlowGraphBuilder::handleIfOp(scf::IfOp ifOp, cfg::ControlFlowGraph &cfg,
cfg::BasicBlock *ifCondBB,
cfg::BasicBlock *parentStructure) {
auto *mergeBB = cfg.createBasicBlock(BlockType::NORMAL, parentStructure);
ifCondBB->setExitBlock(mergeBB);
cfg::BasicBlock *thenExitBB = nullptr;
if (!ifOp.getThenRegion().empty()) {
auto *thenEntryBB = cfg.createBasicBlock(BlockType::NORMAL, ifCondBB);
cfg.addEdge(ifCondBB, thenEntryBB);
auto result = buildForRegion(ifOp.getThenRegion(), cfg, thenEntryBB, ifCondBB);
thenExitBB = result.exitBlock;
}
cfg::BasicBlock *elseExitBB = nullptr;
bool hasElse = !ifOp.getElseRegion().empty();
if (hasElse) {
auto *elseEntryBB = cfg.createBasicBlock(BlockType::NORMAL, ifCondBB);
cfg.addEdge(ifCondBB, elseEntryBB);
auto result = buildForRegion(ifOp.getElseRegion(), cfg, elseEntryBB, ifCondBB);
elseExitBB = result.exitBlock;
}
if (thenExitBB) {
cfg.addEdge(thenExitBB, mergeBB);
} else {
cfg.addEdge(ifCondBB, mergeBB);
}
if (hasElse) {
if (elseExitBB) {
cfg.addEdge(elseExitBB, mergeBB);
} else {
cfg.addEdge(ifCondBB, mergeBB);
}
} else {
cfg.addEdge(ifCondBB, mergeBB);
}
return mergeBB;
}
cfg::BasicBlock *ControlFlowGraphBuilder::handleForOp(scf::ForOp forOp, cfg::ControlFlowGraph &cfg,
cfg::BasicBlock *forCondBB,
cfg::BasicBlock *parentStructure) {
auto *loopBodyEntryBB = cfg.createBasicBlock(BlockType::LOOP_BODY, forCondBB);
cfg.addEdge(forCondBB, loopBodyEntryBB);
auto *loopExitBB = cfg.createBasicBlock(BlockType::LOOP_EXIT, parentStructure);
forCondBB->setExitBlock(loopExitBB);
auto result = buildForRegion(forOp.getRegion(), cfg, loopBodyEntryBB, forCondBB);
if (result.exitBlock) {
cfg.addEdge(result.exitBlock, forCondBB);
}
cfg.addEdge(forCondBB, loopExitBB);
return loopExitBB;
}
cfg::BasicBlock *ControlFlowGraphBuilder::handleWhileOp(scf::WhileOp whileOp, cfg::ControlFlowGraph &cfg,
cfg::BasicBlock *whileCondBB,
cfg::BasicBlock *parentStructure) {
auto *beforeEntryBB = cfg.createBasicBlock(BlockType::LOOP_BODY, whileCondBB);
cfg.addEdge(whileCondBB, beforeEntryBB);
auto *afterEntryBB = cfg.createBasicBlock(BlockType::LOOP_BODY, whileCondBB);
auto *loopExitBB = cfg.createBasicBlock(BlockType::LOOP_EXIT, parentStructure);
whileCondBB->setExitBlock(loopExitBB);
auto beforeResult = buildForRegion(whileOp.getBefore(), cfg, beforeEntryBB, whileCondBB);
auto afterResult = buildForRegion(whileOp.getAfter(), cfg, afterEntryBB, whileCondBB);
if (beforeResult.exitBlock) {
cfg.addEdge(beforeResult.exitBlock, afterEntryBB);
cfg.addEdge(beforeResult.exitBlock, loopExitBB);
}
if (afterResult.exitBlock) {
cfg.addEdge(afterResult.exitBlock, whileCondBB);
}
return loopExitBB;
}
cfg::Instruction *ControlFlowGraphBuilder::createInstruction(Operation *op, cfg::BasicBlock *parentBlock, cfg::ControlFlowGraph &cfg) {
if (!op || !parentBlock) return nullptr;
auto inst = std::make_unique<cfg::Instruction>(getNextInstructionId(), op, parentBlock);
cfg::Instruction *instPtr = inst.get();
parentBlock->addInstruction(std::move(inst));
cfg.addOpToInstruction(op, instPtr);
return instPtr;
}
std::unique_ptr<cfg::ControlFlowGraph>
ControlFlowGraphBuilder::build(triton::FuncOp func) {
auto cfg = std::make_unique<cfg::ControlFlowGraph>(func);
if (func.getBody().empty()) {
auto *entry = cfg->createBasicBlock(BlockType::ENTRY);
auto *exit = cfg->createBasicBlock(BlockType::EXIT);
cfg->addEdge(entry, exit);
return cfg;
}
auto *entryBlock = cfg->createBasicBlock(BlockType::ENTRY);
cfg->setEntryBlock(entryBlock);
auto *exitBlock = cfg->createBasicBlock(BlockType::EXIT);
cfg->setExitBlock(exitBlock);
auto result = buildForRegion(func.getBody(), *cfg, entryBlock, nullptr);
if (result.exitBlock) {
cfg->addEdge(result.exitBlock, exitBlock);
}
return cfg;
}
std::vector<std::unique_ptr<cfg::ControlFlowGraph>>
ControlFlowGraphBuilder::buildForModule(ModuleOp module) {
std::vector<std::unique_ptr<cfg::ControlFlowGraph>> cfgs;
for (triton::FuncOp func : module.getOps<triton::FuncOp>()) {
auto cfg = build(func);
if (cfg) {
cfgs.push_back(std::move(cfg));
}
}
return cfgs;
}
cfg::BasicBlock *ControlFlowGraphBuilder::handleCondBranchOp(cf::CondBranchOp condBrOp,
cfg::ControlFlowGraph &cfg,
cfg::BasicBlock *condBrBB,
cfg::BasicBlock *parentStructure) {
auto *mergeBB = cfg.createBasicBlock(BlockType::NORMAL, parentStructure);
condBrBB->setExitBlock(mergeBB);
Value condition = condBrOp.getCondition();
Block *trueDest = condBrOp.getTrueDest();
SmallVector<Value> trueOperands(condBrOp.getTrueDestOperands());
Block *falseDest = condBrOp.getFalseDest();
SmallVector<Value> falseOperands(condBrOp.getFalseDestOperands());
LLVM_DEBUG(llvm::dbgs() << " CondBr: condition=" << condition << "\n");
LLVM_DEBUG(llvm::dbgs() << " True dest: " << trueDest << "\n");
LLVM_DEBUG(llvm::dbgs() << " False dest: " << falseDest << "\n");
cfg::BasicBlock *trueEntryBB = getOrCreateBasicBlockForBlock(trueDest, cfg, parentStructure);
cfg::BasicBlock *falseEntryBB = getOrCreateBasicBlockForBlock(falseDest, cfg, parentStructure);
cfg.addEdge(condBrBB, trueEntryBB);
cfg.addEdge(condBrBB, falseEntryBB);
if (condBrBB->getNumInstructions() > 0) {
cfg::Instruction *inst = condBrBB->getInstruction(0);
}
return mergeBB;
}
cfg::BasicBlock *ControlFlowGraphBuilder::handleBranchOp(cf::BranchOp brOp,
cfg::ControlFlowGraph &cfg,
cfg::BasicBlock *brBB,
cfg::BasicBlock *parentStructure) {
Block *dest = brOp.getDest();
SmallVector<Value> destOperands(brOp.getDestOperands());
LLVM_DEBUG(llvm::dbgs() << " Br: unconditional branch\n");
LLVM_DEBUG(llvm::dbgs() << " Dest: " << dest << "\n");
cfg::BasicBlock *destBB = getOrCreateBasicBlockForBlock(dest, cfg, parentStructure);
cfg.addEdge(brBB, destBB);
return nullptr;
}
cfg::BasicBlock *ControlFlowGraphBuilder::getOrCreateBasicBlockForBlock(
Block *block, cfg::ControlFlowGraph &cfg, cfg::BasicBlock *parentStructure) {
auto it = blockToBasicBlockMap.find(block);
if (it != blockToBasicBlockMap.end()) {
return it->second;
}
auto *bb = cfg.createBasicBlock(BlockType::NORMAL, parentStructure);
registerBlockMapping(block, bb);
return bb;
}
void ControlFlowGraphBuilder::registerBlockMapping(Block *mlirBlock, cfg::BasicBlock *cfgBlock) {
blockToBasicBlockMap[mlirBlock] = cfgBlock;
}
SmallVector<cfg::BasicBlock *>
ControlFlowGraphBuilder::collectCondBrBlocks(cfg::ControlFlowGraph &cfg) {
SmallVector<cfg::BasicBlock *> condBrBlocks;
for (size_t i = 0; i < cfg.getNumBlocks(); ++i) {
cfg::BasicBlock *bb = cfg.getBasicBlock(i);
if (bb && bb->getType() == BlockType::COND_BR) {
condBrBlocks.push_back(bb);
}
}
return condBrBlocks;
}
std::optional<CondBranchMapping>
ControlFlowGraphBuilder::getCondBranchMapping(cfg::BasicBlock *condBrBB) {
if (!condBrBB || condBrBB->getType() != BlockType::COND_BR) {
return std::nullopt;
}
if (condBrBB->getNumInstructions() == 0) {
return std::nullopt;
}
cfg::Instruction *inst = condBrBB->getInstruction(0);
Operation *op = inst->getOperation();
auto condBrOp = dyn_cast<cf::CondBranchOp>(op);
if (!condBrOp) {
return std::nullopt;
}
CondBranchMapping mapping;
mapping.condition = condBrOp.getCondition();
mapping.trueDest = condBrOp.getTrueDest();
for (Value operand : condBrOp.getTrueDestOperands()) {
mapping.trueOperands.push_back(operand);
}
mapping.falseDest = condBrOp.getFalseDest();
for (Value operand : condBrOp.getFalseDestOperands()) {
mapping.falseOperands.push_back(operand);
}
return mapping;
}
SmallVector<cfg::BasicBlock *>
ControlFlowGraphBuilder::collectBrBlocks(cfg::ControlFlowGraph &cfg) {
SmallVector<cfg::BasicBlock *> brBlocks;
for (size_t i = 0; i < cfg.getNumBlocks(); ++i) {
cfg::BasicBlock *bb = cfg.getBasicBlock(i);
if (bb && bb->getType() == BlockType::BR) {
brBlocks.push_back(bb);
}
}
return brBlocks;
}
std::optional<BranchMapping>
ControlFlowGraphBuilder::getBranchMapping(cfg::BasicBlock *brBB) {
if (!brBB || brBB->getType() != BlockType::BR) {
return std::nullopt;
}
if (brBB->getNumInstructions() == 0) {
return std::nullopt;
}
cfg::Instruction *inst = brBB->getInstruction(0);
Operation *op = inst->getOperation();
auto brOp = dyn_cast<cf::BranchOp>(op);
if (!brOp) {
return std::nullopt;
}
BranchMapping mapping;
mapping.dest = brOp.getDest();
for (Value operand : brOp.getDestOperands()) {
mapping.destOperands.push_back(operand);
}
return mapping;
}
std::unique_ptr<OperationPass<ModuleOp>> mlir::triton::cfg::createBuildCFGPass() {
return std::unique_ptr<OperationPass<ModuleOp>>(new BuildCFGPass());
}
SmallVector<cfg::BasicBlock *>
ControlFlowGraphBuilder::collectIfCondBlocks(cfg::ControlFlowGraph &cfg) {
SmallVector<cfg::BasicBlock *> ifCondBlocks;
for (size_t i = 0; i < cfg.getNumBlocks(); ++i) {
cfg::BasicBlock *bb = cfg.getBasicBlock(i);
if (bb && bb->getType() == BlockType::IF_COND) {
ifCondBlocks.push_back(bb);
}
}
return ifCondBlocks;
}
SmallVector<cfg::BasicBlock *>
ControlFlowGraphBuilder::collectForCondBlocks(cfg::ControlFlowGraph &cfg) {
SmallVector<cfg::BasicBlock *> forCondBlocks;
for (size_t i = 0; i < cfg.getNumBlocks(); ++i) {
cfg::BasicBlock *bb = cfg.getBasicBlock(i);
if (bb && bb->getType() == BlockType::FOR_COND) {
forCondBlocks.push_back(bb);
}
}
return forCondBlocks;
}
std::optional<IfYieldResultMapping>
ControlFlowGraphBuilder::getIfYieldResultMapping(cfg::BasicBlock *ifCondBB) {
if (!ifCondBB || ifCondBB->getType() != BlockType::IF_COND) {
return std::nullopt;
}
if (ifCondBB->getNumInstructions() == 0) {
return std::nullopt;
}
cfg::Instruction *inst = ifCondBB->getInstruction(0);
Operation *op = inst->getOperation();
auto ifOp = dyn_cast<scf::IfOp>(op);
if (!ifOp) {
return std::nullopt;
}
IfYieldResultMapping mapping;
for (Value result : ifOp.getResults()) {
mapping.resultValues.push_back(result);
}
if (!ifOp.getThenRegion().empty()) {
Block &thenBlock = ifOp.getThenRegion().back();
for (Operation &thenOp : thenBlock) {
if (auto yieldOp = dyn_cast<scf::YieldOp>(thenOp)) {
for (Value operand : yieldOp.getOperands()) {
mapping.thenYieldValues.push_back(operand);
}
break;
}
}
}
if (!ifOp.getElseRegion().empty()) {
Block &elseBlock = ifOp.getElseRegion().back();
for (Operation &elseOp : elseBlock) {
if (auto yieldOp = dyn_cast<scf::YieldOp>(elseOp)) {
for (Value operand : yieldOp.getOperands()) {
mapping.elseYieldValues.push_back(operand);
}
break;
}
}
}
return mapping;
}
std::optional<ForYieldIterArgMapping>
ControlFlowGraphBuilder::getForYieldIterArgMapping(cfg::BasicBlock *forCondBB) {
if (!forCondBB || forCondBB->getType() != BlockType::FOR_COND) {
return std::nullopt;
}
if (forCondBB->getNumInstructions() == 0) {
return std::nullopt;
}
cfg::Instruction *inst = forCondBB->getInstruction(0);
Operation *op = inst->getOperation();
auto forOp = dyn_cast<scf::ForOp>(op);
if (!forOp) {
return std::nullopt;
}
ForYieldIterArgMapping mapping;
for (Value iterArg : forOp.getRegionIterArgs()) {
mapping.iterArgValues.push_back(iterArg);
}
for (Value result : forOp.getResults()) {
mapping.resultValues.push_back(result);
}
if (!forOp.getRegion().empty()) {
Block &loopBlock = forOp.getRegion().back();
for (Operation &loopOp : loopBlock) {
if (auto yieldOp = dyn_cast<scf::YieldOp>(loopOp)) {
for (Value operand : yieldOp.getOperands()) {
mapping.yieldValues.push_back(operand);
}
break;
}
}
}
return mapping;
}