mirror of
https://github.com/intel/llvm.git
synced 2026-01-26 03:56:16 +08:00
[mlir] Use dialect interfaces to translate OpenMP dialect to LLVM IR
Migrate the translation of the OpenMP dialect operations to LLVM IR to the new dialect-based mechanism. Depends On D96503 Reviewed By: nicolasvasilache Differential Revision: https://reviews.llvm.org/D96504
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
//===- OpenMPToLLVMIRTranslation.h - OpenMP Dialect to LLVM IR --*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file implements the dialect interface for translating the OpenMP dialect
|
||||
// to LLVM IR.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef MLIR_TARGET_LLVMIR_DIALECT_OPENMP_OPENMPTOLLVMIRTRANSLATION_H
|
||||
#define MLIR_TARGET_LLVMIR_DIALECT_OPENMP_OPENMPTOLLVMIRTRANSLATION_H
|
||||
|
||||
#include "mlir/Target/LLVMIR/LLVMTranslationInterface.h"
|
||||
|
||||
namespace mlir {
|
||||
|
||||
/// Implementation of the dialect interface that converts operations beloning to
|
||||
/// the OpenMP dialect to LLVM IR.
|
||||
class OpenMPDialectLLVMIRTranslationInterface
|
||||
: public LLVMTranslationDialectInterface {
|
||||
public:
|
||||
using LLVMTranslationDialectInterface::LLVMTranslationDialectInterface;
|
||||
|
||||
/// Translates the given operation to LLVM IR using the provided IR builder
|
||||
/// and saving the state in `moduleTranslation`.
|
||||
LogicalResult
|
||||
convertOperation(Operation *op, llvm::IRBuilderBase &builder,
|
||||
LLVM::ModuleTranslation &moduleTranslation) const final;
|
||||
};
|
||||
|
||||
} // namespace mlir
|
||||
|
||||
#endif // MLIR_TARGET_LLVMIR_DIALECT_OPENMP_OPENMPTOLLVMIRTRANSLATION_H
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "mlir/Target/LLVMIR/LLVMTranslationInterface.h"
|
||||
#include "mlir/Target/LLVMIR/TypeTranslation.h"
|
||||
|
||||
#include "llvm/ADT/SetVector.h"
|
||||
#include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
|
||||
#include "llvm/IR/BasicBlock.h"
|
||||
#include "llvm/IR/Function.h"
|
||||
@@ -160,6 +161,29 @@ public:
|
||||
return globalsMapping.lookup(op);
|
||||
}
|
||||
|
||||
/// Returns the OpenMP IR builder associated with the LLVM IR module being
|
||||
/// constructed.
|
||||
llvm::OpenMPIRBuilder *getOpenMPBuilder() {
|
||||
if (!ompBuilder) {
|
||||
ompBuilder = std::make_unique<llvm::OpenMPIRBuilder>(*llvmModule);
|
||||
ompBuilder->initialize();
|
||||
}
|
||||
return ompBuilder.get();
|
||||
}
|
||||
|
||||
/// Translates the given location.
|
||||
const llvm::DILocation *translateLoc(Location loc, llvm::DILocalScope *scope);
|
||||
|
||||
/// Translates the contents of the given block to LLVM IR using this
|
||||
/// translator. The LLVM IR basic block corresponding to the given block is
|
||||
/// expected to exist in the mapping of this translator. Uses `builder` to
|
||||
/// translate the IR, leaving it at the end of the block. If `ignoreArguments`
|
||||
/// is set, does not produce PHI nodes for the block arguments. Otherwise, the
|
||||
/// PHI nodes are constructed for block arguments but are _not_ connected to
|
||||
/// the predecessors that may not exist yet.
|
||||
LogicalResult convertBlock(Block &bb, bool ignoreArguments,
|
||||
llvm::IRBuilder<> &builder);
|
||||
|
||||
protected:
|
||||
/// Translate the given MLIR module expressed in MLIR LLVM IR dialect into an
|
||||
/// LLVM IR module. The MLIR LLVM IR dialect holds a pointer to an
|
||||
@@ -170,19 +194,6 @@ protected:
|
||||
|
||||
virtual LogicalResult convertOperation(Operation &op,
|
||||
llvm::IRBuilder<> &builder);
|
||||
virtual LogicalResult convertOmpOperation(Operation &op,
|
||||
llvm::IRBuilder<> &builder);
|
||||
virtual LogicalResult convertOmpParallel(Operation &op,
|
||||
llvm::IRBuilder<> &builder);
|
||||
virtual LogicalResult convertOmpMaster(Operation &op,
|
||||
llvm::IRBuilder<> &builder);
|
||||
void convertOmpOpRegions(Region ®ion, StringRef blockName,
|
||||
llvm::BasicBlock &sourceBlock,
|
||||
llvm::BasicBlock &continuationBlock,
|
||||
llvm::IRBuilder<> &builder,
|
||||
LogicalResult &bodyGenStatus);
|
||||
virtual LogicalResult convertOmpWsLoop(Operation &opInst,
|
||||
llvm::IRBuilder<> &builder);
|
||||
|
||||
static std::unique_ptr<llvm::Module>
|
||||
prepareLLVMModule(Operation *m, llvm::LLVMContext &llvmContext,
|
||||
@@ -196,8 +207,6 @@ private:
|
||||
LogicalResult convertFunctions();
|
||||
LogicalResult convertGlobals();
|
||||
LogicalResult convertOneFunction(LLVMFuncOp func);
|
||||
LogicalResult convertBlock(Block &bb, bool ignoreArguments,
|
||||
llvm::IRBuilder<> &builder);
|
||||
|
||||
/// Original and translated module.
|
||||
Operation *mlirModule;
|
||||
@@ -232,6 +241,16 @@ private:
|
||||
DenseMap<Operation *, llvm::Instruction *> branchMapping;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
/// For all blocks in the region that were converted to LLVM IR using the given
|
||||
/// ModuleTranslation, connect the PHI nodes of the corresponding LLVM IR blocks
|
||||
/// to the results of preceding blocks.
|
||||
void connectPHINodes(Region ®ion, const ModuleTranslation &state);
|
||||
|
||||
/// Get a topologically sorted list of blocks of the given region.
|
||||
llvm::SetVector<Block *> getTopologicallySortedBlocks(Region ®ion);
|
||||
} // namespace detail
|
||||
|
||||
} // namespace LLVM
|
||||
} // namespace mlir
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ add_mlir_translation_library(MLIRTargetLLVMIR
|
||||
|
||||
LINK_LIBS PUBLIC
|
||||
MLIRLLVMToLLVMIRTranslation
|
||||
MLIROpenMPToLLVMIRTranslation
|
||||
MLIRTargetLLVMIRModuleTranslation
|
||||
)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
|
||||
#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h"
|
||||
#include "mlir/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.h"
|
||||
#include "mlir/Target/LLVMIR/ModuleTranslation.h"
|
||||
#include "mlir/Translation.h"
|
||||
|
||||
@@ -70,6 +71,8 @@ void registerToLLVMIRTranslation() {
|
||||
},
|
||||
[](DialectRegistry ®istry) {
|
||||
registry.insert<omp::OpenMPDialect>();
|
||||
registry.addDialectInterface<omp::OpenMPDialect,
|
||||
OpenMPDialectLLVMIRTranslationInterface>();
|
||||
registerLLVMDialectTranslation(registry);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
add_subdirectory(LLVMIR)
|
||||
add_subdirectory(OpenMP)
|
||||
|
||||
13
mlir/lib/Target/LLVMIR/Dialect/OpenMP/CMakeLists.txt
Normal file
13
mlir/lib/Target/LLVMIR/Dialect/OpenMP/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
add_mlir_translation_library(MLIROpenMPToLLVMIRTranslation
|
||||
OpenMPToLLVMIRTranslation.cpp
|
||||
|
||||
LINK_COMPONENTS
|
||||
Core
|
||||
|
||||
LINK_LIBS PUBLIC
|
||||
MLIRIR
|
||||
MLIRLLVMIR
|
||||
MLIROpenMP
|
||||
MLIRSupport
|
||||
MLIRTargetLLVMIRModuleTranslation
|
||||
)
|
||||
@@ -0,0 +1,309 @@
|
||||
//===- OpenMPToLLVMIRTranslation.cpp - Translate OpenMP dialect to LLVM IR-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file implements a translation between the MLIR OpenMP dialect and LLVM
|
||||
// IR.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "mlir/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.h"
|
||||
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
|
||||
#include "mlir/IR/Operation.h"
|
||||
#include "mlir/Support/LLVM.h"
|
||||
#include "mlir/Target/LLVMIR/ModuleTranslation.h"
|
||||
|
||||
#include "llvm/ADT/SetVector.h"
|
||||
#include "llvm/ADT/TypeSwitch.h"
|
||||
#include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
|
||||
#include "llvm/IR/IRBuilder.h"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
/// Converts the given region that appears within an OpenMP dialect operation to
|
||||
/// LLVM IR, creating a branch from the `sourceBlock` to the entry block of the
|
||||
/// region, and a branch from any block with an successor-less OpenMP terminator
|
||||
/// to `continuationBlock`.
|
||||
static void convertOmpOpRegions(Region ®ion, StringRef blockName,
|
||||
llvm::BasicBlock &sourceBlock,
|
||||
llvm::BasicBlock &continuationBlock,
|
||||
llvm::IRBuilderBase &builder,
|
||||
LLVM::ModuleTranslation &moduleTranslation,
|
||||
LogicalResult &bodyGenStatus) {
|
||||
llvm::LLVMContext &llvmContext = builder.getContext();
|
||||
for (Block &bb : region) {
|
||||
llvm::BasicBlock *llvmBB = llvm::BasicBlock::Create(
|
||||
llvmContext, blockName, builder.GetInsertBlock()->getParent());
|
||||
moduleTranslation.mapBlock(&bb, llvmBB);
|
||||
}
|
||||
|
||||
llvm::Instruction *sourceTerminator = sourceBlock.getTerminator();
|
||||
|
||||
// Convert blocks one by one in topological order to ensure
|
||||
// defs are converted before uses.
|
||||
llvm::SetVector<Block *> blocks =
|
||||
LLVM::detail::getTopologicallySortedBlocks(region);
|
||||
for (Block *bb : blocks) {
|
||||
llvm::BasicBlock *llvmBB = moduleTranslation.lookupBlock(bb);
|
||||
// Retarget the branch of the entry block to the entry block of the
|
||||
// converted region (regions are single-entry).
|
||||
if (bb->isEntryBlock()) {
|
||||
assert(sourceTerminator->getNumSuccessors() == 1 &&
|
||||
"provided entry block has multiple successors");
|
||||
assert(sourceTerminator->getSuccessor(0) == &continuationBlock &&
|
||||
"ContinuationBlock is not the successor of the entry block");
|
||||
sourceTerminator->setSuccessor(0, llvmBB);
|
||||
}
|
||||
|
||||
llvm::IRBuilder<>::InsertPointGuard guard(builder);
|
||||
if (failed(moduleTranslation.convertBlock(
|
||||
*bb, bb->isEntryBlock(),
|
||||
// TODO: this downcast should be removed after all of
|
||||
// ModuleTranslation migrated to using IRBuilderBase &; the cast is
|
||||
// safe in practice because the builder always comes from
|
||||
// ModuleTranslation itself that only uses this subclass.
|
||||
static_cast<llvm::IRBuilder<> &>(builder)))) {
|
||||
bodyGenStatus = failure();
|
||||
return;
|
||||
}
|
||||
|
||||
// Special handling for `omp.yield` and `omp.terminator` (we may have more
|
||||
// than one): they return the control to the parent OpenMP dialect operation
|
||||
// so replace them with the branch to the continuation block. We handle this
|
||||
// here to avoid relying inter-function communication through the
|
||||
// ModuleTranslation class to set up the correct insertion point. This is
|
||||
// also consistent with MLIR's idiom of handling special region terminators
|
||||
// in the same code that handles the region-owning operation.
|
||||
if (isa<omp::TerminatorOp, omp::YieldOp>(bb->getTerminator()))
|
||||
builder.CreateBr(&continuationBlock);
|
||||
}
|
||||
// Finally, after all blocks have been traversed and values mapped,
|
||||
// connect the PHI nodes to the results of preceding blocks.
|
||||
LLVM::detail::connectPHINodes(region, moduleTranslation);
|
||||
}
|
||||
|
||||
/// Converts the OpenMP parallel operation to LLVM IR.
|
||||
static LogicalResult
|
||||
convertOmpParallel(Operation &opInst, llvm::IRBuilderBase &builder,
|
||||
LLVM::ModuleTranslation &moduleTranslation) {
|
||||
using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
|
||||
// TODO: support error propagation in OpenMPIRBuilder and use it instead of
|
||||
// relying on captured variables.
|
||||
LogicalResult bodyGenStatus = success();
|
||||
|
||||
auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
|
||||
llvm::BasicBlock &continuationBlock) {
|
||||
// ParallelOp has only one region associated with it.
|
||||
auto ®ion = cast<omp::ParallelOp>(opInst).getRegion();
|
||||
convertOmpOpRegions(region, "omp.par.region", *codeGenIP.getBlock(),
|
||||
continuationBlock, builder, moduleTranslation,
|
||||
bodyGenStatus);
|
||||
};
|
||||
|
||||
// TODO: Perform appropriate actions according to the data-sharing
|
||||
// attribute (shared, private, firstprivate, ...) of variables.
|
||||
// Currently defaults to shared.
|
||||
auto privCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
|
||||
llvm::Value &, llvm::Value &vPtr,
|
||||
llvm::Value *&replacementValue) -> InsertPointTy {
|
||||
replacementValue = &vPtr;
|
||||
|
||||
return codeGenIP;
|
||||
};
|
||||
|
||||
// TODO: Perform finalization actions for variables. This has to be
|
||||
// called for variables which have destructors/finalizers.
|
||||
auto finiCB = [&](InsertPointTy codeGenIP) {};
|
||||
|
||||
llvm::Value *ifCond = nullptr;
|
||||
if (auto ifExprVar = cast<omp::ParallelOp>(opInst).if_expr_var())
|
||||
ifCond = moduleTranslation.lookupValue(ifExprVar);
|
||||
llvm::Value *numThreads = nullptr;
|
||||
if (auto numThreadsVar = cast<omp::ParallelOp>(opInst).num_threads_var())
|
||||
numThreads = moduleTranslation.lookupValue(numThreadsVar);
|
||||
llvm::omp::ProcBindKind pbKind = llvm::omp::OMP_PROC_BIND_default;
|
||||
if (auto bind = cast<omp::ParallelOp>(opInst).proc_bind_val())
|
||||
pbKind = llvm::omp::getProcBindKind(bind.getValue());
|
||||
// TODO: Is the Parallel construct cancellable?
|
||||
bool isCancellable = false;
|
||||
// TODO: Determine the actual alloca insertion point, e.g., the function
|
||||
// entry or the alloca insertion point as provided by the body callback
|
||||
// above.
|
||||
llvm::OpenMPIRBuilder::InsertPointTy allocaIP(builder.saveIP());
|
||||
if (failed(bodyGenStatus))
|
||||
return failure();
|
||||
llvm::OpenMPIRBuilder::LocationDescription ompLoc(
|
||||
builder.saveIP(), builder.getCurrentDebugLocation());
|
||||
builder.restoreIP(moduleTranslation.getOpenMPBuilder()->createParallel(
|
||||
ompLoc, allocaIP, bodyGenCB, privCB, finiCB, ifCond, numThreads, pbKind,
|
||||
isCancellable));
|
||||
return success();
|
||||
}
|
||||
|
||||
/// Converts an OpenMP 'master' operation into LLVM IR using OpenMPIRBuilder.
|
||||
static LogicalResult
|
||||
convertOmpMaster(Operation &opInst, llvm::IRBuilderBase &builder,
|
||||
LLVM::ModuleTranslation &moduleTranslation) {
|
||||
using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
|
||||
// TODO: support error propagation in OpenMPIRBuilder and use it instead of
|
||||
// relying on captured variables.
|
||||
LogicalResult bodyGenStatus = success();
|
||||
|
||||
auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
|
||||
llvm::BasicBlock &continuationBlock) {
|
||||
// MasterOp has only one region associated with it.
|
||||
auto ®ion = cast<omp::MasterOp>(opInst).getRegion();
|
||||
convertOmpOpRegions(region, "omp.master.region", *codeGenIP.getBlock(),
|
||||
continuationBlock, builder, moduleTranslation,
|
||||
bodyGenStatus);
|
||||
};
|
||||
|
||||
// TODO: Perform finalization actions for variables. This has to be
|
||||
// called for variables which have destructors/finalizers.
|
||||
auto finiCB = [&](InsertPointTy codeGenIP) {};
|
||||
|
||||
llvm::OpenMPIRBuilder::LocationDescription ompLoc(
|
||||
builder.saveIP(), builder.getCurrentDebugLocation());
|
||||
builder.restoreIP(moduleTranslation.getOpenMPBuilder()->createMaster(
|
||||
ompLoc, bodyGenCB, finiCB));
|
||||
return success();
|
||||
}
|
||||
|
||||
/// Converts an OpenMP workshare loop into LLVM IR using OpenMPIRBuilder.
|
||||
LogicalResult convertOmpWsLoop(Operation &opInst, llvm::IRBuilderBase &builder,
|
||||
LLVM::ModuleTranslation &moduleTranslation) {
|
||||
auto loop = cast<omp::WsLoopOp>(opInst);
|
||||
// TODO: this should be in the op verifier instead.
|
||||
if (loop.lowerBound().empty())
|
||||
return failure();
|
||||
|
||||
if (loop.getNumLoops() != 1)
|
||||
return opInst.emitOpError("collapsed loops not yet supported");
|
||||
|
||||
if (loop.schedule_val().hasValue() &&
|
||||
omp::symbolizeClauseScheduleKind(loop.schedule_val().getValue()) !=
|
||||
omp::ClauseScheduleKind::Static)
|
||||
return opInst.emitOpError(
|
||||
"only static (default) loop schedule is currently supported");
|
||||
|
||||
// Find the loop configuration.
|
||||
llvm::Value *lowerBound = moduleTranslation.lookupValue(loop.lowerBound()[0]);
|
||||
llvm::Value *upperBound = moduleTranslation.lookupValue(loop.upperBound()[0]);
|
||||
llvm::Value *step = moduleTranslation.lookupValue(loop.step()[0]);
|
||||
llvm::Type *ivType = step->getType();
|
||||
llvm::Value *chunk =
|
||||
loop.schedule_chunk_var()
|
||||
? moduleTranslation.lookupValue(loop.schedule_chunk_var())
|
||||
: llvm::ConstantInt::get(ivType, 1);
|
||||
|
||||
// Set up the source location value for OpenMP runtime.
|
||||
llvm::DISubprogram *subprogram =
|
||||
builder.GetInsertBlock()->getParent()->getSubprogram();
|
||||
const llvm::DILocation *diLoc =
|
||||
moduleTranslation.translateLoc(opInst.getLoc(), subprogram);
|
||||
llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder.saveIP(),
|
||||
llvm::DebugLoc(diLoc));
|
||||
|
||||
// Generator of the canonical loop body. Produces an SESE region of basic
|
||||
// blocks.
|
||||
// TODO: support error propagation in OpenMPIRBuilder and use it instead of
|
||||
// relying on captured variables.
|
||||
LogicalResult bodyGenStatus = success();
|
||||
auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy ip, llvm::Value *iv) {
|
||||
llvm::IRBuilder<>::InsertPointGuard guard(builder);
|
||||
|
||||
// Make sure further conversions know about the induction variable.
|
||||
moduleTranslation.mapValue(loop.getRegion().front().getArgument(0), iv);
|
||||
|
||||
llvm::BasicBlock *entryBlock = ip.getBlock();
|
||||
llvm::BasicBlock *exitBlock =
|
||||
entryBlock->splitBasicBlock(ip.getPoint(), "omp.wsloop.exit");
|
||||
|
||||
// Convert the body of the loop.
|
||||
convertOmpOpRegions(loop.region(), "omp.wsloop.region", *entryBlock,
|
||||
*exitBlock, builder, moduleTranslation, bodyGenStatus);
|
||||
};
|
||||
|
||||
// Delegate actual loop construction to the OpenMP IRBuilder.
|
||||
// TODO: this currently assumes WsLoop is semantically similar to SCF loop,
|
||||
// i.e. it has a positive step, uses signed integer semantics. Reconsider
|
||||
// this code when WsLoop clearly supports more cases.
|
||||
llvm::BasicBlock *insertBlock = builder.GetInsertBlock();
|
||||
llvm::CanonicalLoopInfo *loopInfo =
|
||||
moduleTranslation.getOpenMPBuilder()->createCanonicalLoop(
|
||||
ompLoc, bodyGen, lowerBound, upperBound, step, /*IsSigned=*/true,
|
||||
/*InclusiveStop=*/loop.inclusive());
|
||||
if (failed(bodyGenStatus))
|
||||
return failure();
|
||||
|
||||
// TODO: get the alloca insertion point from the parallel operation builder.
|
||||
// If we insert the at the top of the current function, they will be passed as
|
||||
// extra arguments into the function the parallel operation builder outlines.
|
||||
// Put them at the start of the current block for now.
|
||||
llvm::OpenMPIRBuilder::InsertPointTy allocaIP(
|
||||
insertBlock, insertBlock->getFirstInsertionPt());
|
||||
loopInfo = moduleTranslation.getOpenMPBuilder()->createStaticWorkshareLoop(
|
||||
ompLoc, loopInfo, allocaIP, !loop.nowait(), chunk);
|
||||
|
||||
// Continue building IR after the loop.
|
||||
builder.restoreIP(loopInfo->getAfterIP());
|
||||
return success();
|
||||
}
|
||||
|
||||
/// Given an OpenMP MLIR operation, create the corresponding LLVM IR
|
||||
/// (including OpenMP runtime calls).
|
||||
LogicalResult mlir::OpenMPDialectLLVMIRTranslationInterface::convertOperation(
|
||||
Operation *op, llvm::IRBuilderBase &builder,
|
||||
LLVM::ModuleTranslation &moduleTranslation) const {
|
||||
|
||||
llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
|
||||
|
||||
return llvm::TypeSwitch<Operation *, LogicalResult>(op)
|
||||
.Case([&](omp::BarrierOp) {
|
||||
ompBuilder->createBarrier(builder.saveIP(), llvm::omp::OMPD_barrier);
|
||||
return success();
|
||||
})
|
||||
.Case([&](omp::TaskwaitOp) {
|
||||
ompBuilder->createTaskwait(builder.saveIP());
|
||||
return success();
|
||||
})
|
||||
.Case([&](omp::TaskyieldOp) {
|
||||
ompBuilder->createTaskyield(builder.saveIP());
|
||||
return success();
|
||||
})
|
||||
.Case([&](omp::FlushOp) {
|
||||
// No support in Openmp runtime function (__kmpc_flush) to accept
|
||||
// the argument list.
|
||||
// OpenMP standard states the following:
|
||||
// "An implementation may implement a flush with a list by ignoring
|
||||
// the list, and treating it the same as a flush without a list."
|
||||
//
|
||||
// The argument list is discarded so that, flush with a list is treated
|
||||
// same as a flush without a list.
|
||||
ompBuilder->createFlush(builder.saveIP());
|
||||
return success();
|
||||
})
|
||||
.Case([&](omp::ParallelOp) {
|
||||
return convertOmpParallel(*op, builder, moduleTranslation);
|
||||
})
|
||||
.Case([&](omp::MasterOp) {
|
||||
return convertOmpMaster(*op, builder, moduleTranslation);
|
||||
})
|
||||
.Case([&](omp::WsLoopOp) {
|
||||
return convertOmpWsLoop(*op, builder, moduleTranslation);
|
||||
})
|
||||
.Case<omp::YieldOp, omp::TerminatorOp>([](auto op) {
|
||||
// `yield` and `terminator` can be just omitted. The block structure was
|
||||
// created in the function that handles their parent operation.
|
||||
assert(op->getNumOperands() == 0 &&
|
||||
"unexpected OpenMP terminator with operands");
|
||||
return success();
|
||||
})
|
||||
.Default([&](Operation *inst) {
|
||||
return inst->emitError("unsupported OpenMP operation: ")
|
||||
<< inst->getName();
|
||||
});
|
||||
}
|
||||
@@ -239,11 +239,12 @@ static Value getPHISourceValue(Block *current, Block *pred,
|
||||
}
|
||||
|
||||
/// Connect the PHI nodes to the results of preceding blocks.
|
||||
template <typename T>
|
||||
static void connectPHINodes(T &func, const ModuleTranslation &state) {
|
||||
void mlir::LLVM::detail::connectPHINodes(Region ®ion,
|
||||
const ModuleTranslation &state) {
|
||||
// Skip the first block, it cannot be branched to and its arguments correspond
|
||||
// to the arguments of the LLVM function.
|
||||
for (auto it = std::next(func.begin()), eit = func.end(); it != eit; ++it) {
|
||||
for (auto it = std::next(region.begin()), eit = region.end(); it != eit;
|
||||
++it) {
|
||||
Block *bb = &*it;
|
||||
llvm::BasicBlock *llvmBB = state.lookupBlock(bb);
|
||||
auto phis = llvmBB->phis();
|
||||
@@ -270,294 +271,32 @@ static void connectPHINodes(T &func, const ModuleTranslation &state) {
|
||||
}
|
||||
|
||||
/// Sort function blocks topologically.
|
||||
template <typename T>
|
||||
static llvm::SetVector<Block *> topologicalSort(T &f) {
|
||||
llvm::SetVector<Block *>
|
||||
mlir::LLVM::detail::getTopologicallySortedBlocks(Region ®ion) {
|
||||
// For each block that has not been visited yet (i.e. that has no
|
||||
// predecessors), add it to the list as well as its successors.
|
||||
llvm::SetVector<Block *> blocks;
|
||||
for (Block &b : f) {
|
||||
for (Block &b : region) {
|
||||
if (blocks.count(&b) == 0) {
|
||||
llvm::ReversePostOrderTraversal<Block *> traversal(&b);
|
||||
blocks.insert(traversal.begin(), traversal.end());
|
||||
}
|
||||
}
|
||||
assert(blocks.size() == f.getBlocks().size() && "some blocks are not sorted");
|
||||
assert(blocks.size() == region.getBlocks().size() &&
|
||||
"some blocks are not sorted");
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/// Convert the OpenMP parallel Operation to LLVM IR.
|
||||
LogicalResult
|
||||
ModuleTranslation::convertOmpParallel(Operation &opInst,
|
||||
llvm::IRBuilder<> &builder) {
|
||||
using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
|
||||
// TODO: support error propagation in OpenMPIRBuilder and use it instead of
|
||||
// relying on captured variables.
|
||||
LogicalResult bodyGenStatus = success();
|
||||
|
||||
auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
|
||||
llvm::BasicBlock &continuationBlock) {
|
||||
// ParallelOp has only one region associated with it.
|
||||
auto ®ion = cast<omp::ParallelOp>(opInst).getRegion();
|
||||
convertOmpOpRegions(region, "omp.par.region", *codeGenIP.getBlock(),
|
||||
continuationBlock, builder, bodyGenStatus);
|
||||
};
|
||||
|
||||
// TODO: Perform appropriate actions according to the data-sharing
|
||||
// attribute (shared, private, firstprivate, ...) of variables.
|
||||
// Currently defaults to shared.
|
||||
auto privCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
|
||||
llvm::Value &, llvm::Value &vPtr,
|
||||
llvm::Value *&replacementValue) -> InsertPointTy {
|
||||
replacementValue = &vPtr;
|
||||
|
||||
return codeGenIP;
|
||||
};
|
||||
|
||||
// TODO: Perform finalization actions for variables. This has to be
|
||||
// called for variables which have destructors/finalizers.
|
||||
auto finiCB = [&](InsertPointTy codeGenIP) {};
|
||||
|
||||
llvm::Value *ifCond = nullptr;
|
||||
if (auto ifExprVar = cast<omp::ParallelOp>(opInst).if_expr_var())
|
||||
ifCond = lookupValue(ifExprVar);
|
||||
llvm::Value *numThreads = nullptr;
|
||||
if (auto numThreadsVar = cast<omp::ParallelOp>(opInst).num_threads_var())
|
||||
numThreads = lookupValue(numThreadsVar);
|
||||
llvm::omp::ProcBindKind pbKind = llvm::omp::OMP_PROC_BIND_default;
|
||||
if (auto bind = cast<omp::ParallelOp>(opInst).proc_bind_val())
|
||||
pbKind = llvm::omp::getProcBindKind(bind.getValue());
|
||||
// TODO: Is the Parallel construct cancellable?
|
||||
bool isCancellable = false;
|
||||
// TODO: Determine the actual alloca insertion point, e.g., the function
|
||||
// entry or the alloca insertion point as provided by the body callback
|
||||
// above.
|
||||
llvm::OpenMPIRBuilder::InsertPointTy allocaIP(builder.saveIP());
|
||||
if (failed(bodyGenStatus))
|
||||
return failure();
|
||||
builder.restoreIP(
|
||||
ompBuilder->createParallel(builder, allocaIP, bodyGenCB, privCB, finiCB,
|
||||
ifCond, numThreads, pbKind, isCancellable));
|
||||
return success();
|
||||
}
|
||||
|
||||
void ModuleTranslation::convertOmpOpRegions(
|
||||
Region ®ion, StringRef blockName,
|
||||
llvm::BasicBlock &sourceBlock, llvm::BasicBlock &continuationBlock,
|
||||
llvm::IRBuilder<> &builder, LogicalResult &bodyGenStatus) {
|
||||
llvm::LLVMContext &llvmContext = builder.getContext();
|
||||
for (Block &bb : region) {
|
||||
llvm::BasicBlock *llvmBB = llvm::BasicBlock::Create(
|
||||
llvmContext, blockName, builder.GetInsertBlock()->getParent());
|
||||
mapBlock(&bb, llvmBB);
|
||||
}
|
||||
|
||||
llvm::Instruction *sourceTerminator = sourceBlock.getTerminator();
|
||||
|
||||
// Convert blocks one by one in topological order to ensure
|
||||
// defs are converted before uses.
|
||||
llvm::SetVector<Block *> blocks = topologicalSort(region);
|
||||
for (Block *bb : blocks) {
|
||||
llvm::BasicBlock *llvmBB = lookupBlock(bb);
|
||||
// Retarget the branch of the entry block to the entry block of the
|
||||
// converted region (regions are single-entry).
|
||||
if (bb->isEntryBlock()) {
|
||||
assert(sourceTerminator->getNumSuccessors() == 1 &&
|
||||
"provided entry block has multiple successors");
|
||||
assert(sourceTerminator->getSuccessor(0) == &continuationBlock &&
|
||||
"ContinuationBlock is not the successor of the entry block");
|
||||
sourceTerminator->setSuccessor(0, llvmBB);
|
||||
}
|
||||
|
||||
llvm::IRBuilder<>::InsertPointGuard guard(builder);
|
||||
if (failed(convertBlock(*bb, bb->isEntryBlock(), builder))) {
|
||||
bodyGenStatus = failure();
|
||||
return;
|
||||
}
|
||||
|
||||
// Special handling for `omp.yield` and `omp.terminator` (we may have more
|
||||
// than one): they return the control to the parent OpenMP dialect operation
|
||||
// so replace them with the branch to the continuation block. We handle this
|
||||
// here to avoid relying inter-function communication through the
|
||||
// ModuleTranslation class to set up the correct insertion point. This is
|
||||
// also consistent with MLIR's idiom of handling special region terminators
|
||||
// in the same code that handles the region-owning operation.
|
||||
if (isa<omp::TerminatorOp, omp::YieldOp>(bb->getTerminator()))
|
||||
builder.CreateBr(&continuationBlock);
|
||||
}
|
||||
// Finally, after all blocks have been traversed and values mapped,
|
||||
// connect the PHI nodes to the results of preceding blocks.
|
||||
connectPHINodes(region, *this);
|
||||
}
|
||||
|
||||
LogicalResult ModuleTranslation::convertOmpMaster(Operation &opInst,
|
||||
llvm::IRBuilder<> &builder) {
|
||||
using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
|
||||
// TODO: support error propagation in OpenMPIRBuilder and use it instead of
|
||||
// relying on captured variables.
|
||||
LogicalResult bodyGenStatus = success();
|
||||
|
||||
auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
|
||||
llvm::BasicBlock &continuationBlock) {
|
||||
// MasterOp has only one region associated with it.
|
||||
auto ®ion = cast<omp::MasterOp>(opInst).getRegion();
|
||||
convertOmpOpRegions(region, "omp.master.region", *codeGenIP.getBlock(),
|
||||
continuationBlock, builder, bodyGenStatus);
|
||||
};
|
||||
|
||||
// TODO: Perform finalization actions for variables. This has to be
|
||||
// called for variables which have destructors/finalizers.
|
||||
auto finiCB = [&](InsertPointTy codeGenIP) {};
|
||||
|
||||
builder.restoreIP(ompBuilder->createMaster(builder, bodyGenCB, finiCB));
|
||||
return success();
|
||||
}
|
||||
|
||||
/// Converts an OpenMP workshare loop into LLVM IR using OpenMPIRBuilder.
|
||||
LogicalResult ModuleTranslation::convertOmpWsLoop(Operation &opInst,
|
||||
llvm::IRBuilder<> &builder) {
|
||||
auto loop = cast<omp::WsLoopOp>(opInst);
|
||||
// TODO: this should be in the op verifier instead.
|
||||
if (loop.lowerBound().empty())
|
||||
return failure();
|
||||
|
||||
if (loop.getNumLoops() != 1)
|
||||
return opInst.emitOpError("collapsed loops not yet supported");
|
||||
|
||||
if (loop.schedule_val().hasValue() &&
|
||||
omp::symbolizeClauseScheduleKind(loop.schedule_val().getValue()) !=
|
||||
omp::ClauseScheduleKind::Static)
|
||||
return opInst.emitOpError(
|
||||
"only static (default) loop schedule is currently supported");
|
||||
|
||||
// Find the loop configuration.
|
||||
llvm::Value *lowerBound = lookupValue(loop.lowerBound()[0]);
|
||||
llvm::Value *upperBound = lookupValue(loop.upperBound()[0]);
|
||||
llvm::Value *step = lookupValue(loop.step()[0]);
|
||||
llvm::Type *ivType = step->getType();
|
||||
llvm::Value *chunk = loop.schedule_chunk_var()
|
||||
? lookupValue(loop.schedule_chunk_var())
|
||||
: llvm::ConstantInt::get(ivType, 1);
|
||||
|
||||
// Set up the source location value for OpenMP runtime.
|
||||
llvm::DISubprogram *subprogram =
|
||||
builder.GetInsertBlock()->getParent()->getSubprogram();
|
||||
const llvm::DILocation *diLoc =
|
||||
debugTranslation->translateLoc(opInst.getLoc(), subprogram);
|
||||
llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder.saveIP(),
|
||||
llvm::DebugLoc(diLoc));
|
||||
|
||||
// Generator of the canonical loop body. Produces an SESE region of basic
|
||||
// blocks.
|
||||
// TODO: support error propagation in OpenMPIRBuilder and use it instead of
|
||||
// relying on captured variables.
|
||||
LogicalResult bodyGenStatus = success();
|
||||
auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy ip, llvm::Value *iv) {
|
||||
llvm::IRBuilder<>::InsertPointGuard guard(builder);
|
||||
|
||||
// Make sure further conversions know about the induction variable.
|
||||
mapValue(loop.getRegion().front().getArgument(0), iv);
|
||||
|
||||
llvm::BasicBlock *entryBlock = ip.getBlock();
|
||||
llvm::BasicBlock *exitBlock =
|
||||
entryBlock->splitBasicBlock(ip.getPoint(), "omp.wsloop.exit");
|
||||
|
||||
// Convert the body of the loop.
|
||||
convertOmpOpRegions(loop.region(), "omp.wsloop.region", *entryBlock,
|
||||
*exitBlock, builder, bodyGenStatus);
|
||||
};
|
||||
|
||||
// Delegate actual loop construction to the OpenMP IRBuilder.
|
||||
// TODO: this currently assumes WsLoop is semantically similar to SCF loop,
|
||||
// i.e. it has a positive step, uses signed integer semantics. Reconsider
|
||||
// this code when WsLoop clearly supports more cases.
|
||||
llvm::BasicBlock *insertBlock = builder.GetInsertBlock();
|
||||
llvm::CanonicalLoopInfo *loopInfo = ompBuilder->createCanonicalLoop(
|
||||
ompLoc, bodyGen, lowerBound, upperBound, step, /*IsSigned=*/true,
|
||||
/*InclusiveStop=*/loop.inclusive());
|
||||
if (failed(bodyGenStatus))
|
||||
return failure();
|
||||
|
||||
// TODO: get the alloca insertion point from the parallel operation builder.
|
||||
// If we insert the at the top of the current function, they will be passed as
|
||||
// extra arguments into the function the parallel operation builder outlines.
|
||||
// Put them at the start of the current block for now.
|
||||
llvm::OpenMPIRBuilder::InsertPointTy allocaIP(
|
||||
insertBlock, insertBlock->getFirstInsertionPt());
|
||||
loopInfo = ompBuilder->createStaticWorkshareLoop(ompLoc, loopInfo, allocaIP,
|
||||
!loop.nowait(), chunk);
|
||||
|
||||
// Continue building IR after the loop.
|
||||
builder.restoreIP(loopInfo->getAfterIP());
|
||||
return success();
|
||||
}
|
||||
|
||||
/// Given an OpenMP MLIR operation, create the corresponding LLVM IR
|
||||
/// (including OpenMP runtime calls).
|
||||
LogicalResult
|
||||
ModuleTranslation::convertOmpOperation(Operation &opInst,
|
||||
llvm::IRBuilder<> &builder) {
|
||||
if (!ompBuilder) {
|
||||
ompBuilder = std::make_unique<llvm::OpenMPIRBuilder>(*llvmModule);
|
||||
ompBuilder->initialize();
|
||||
}
|
||||
return llvm::TypeSwitch<Operation *, LogicalResult>(&opInst)
|
||||
.Case([&](omp::BarrierOp) {
|
||||
ompBuilder->createBarrier(builder.saveIP(), llvm::omp::OMPD_barrier);
|
||||
return success();
|
||||
})
|
||||
.Case([&](omp::TaskwaitOp) {
|
||||
ompBuilder->createTaskwait(builder.saveIP());
|
||||
return success();
|
||||
})
|
||||
.Case([&](omp::TaskyieldOp) {
|
||||
ompBuilder->createTaskyield(builder.saveIP());
|
||||
return success();
|
||||
})
|
||||
.Case([&](omp::FlushOp) {
|
||||
// No support in Openmp runtime function (__kmpc_flush) to accept
|
||||
// the argument list.
|
||||
// OpenMP standard states the following:
|
||||
// "An implementation may implement a flush with a list by ignoring
|
||||
// the list, and treating it the same as a flush without a list."
|
||||
//
|
||||
// The argument list is discarded so that, flush with a list is treated
|
||||
// same as a flush without a list.
|
||||
ompBuilder->createFlush(builder.saveIP());
|
||||
return success();
|
||||
})
|
||||
.Case(
|
||||
[&](omp::ParallelOp) { return convertOmpParallel(opInst, builder); })
|
||||
.Case([&](omp::MasterOp) { return convertOmpMaster(opInst, builder); })
|
||||
.Case([&](omp::WsLoopOp) { return convertOmpWsLoop(opInst, builder); })
|
||||
.Case<omp::YieldOp, omp::TerminatorOp>([](auto op) {
|
||||
// `yield` and `terminator` can be just omitted. The block structure was
|
||||
// created in the function that handles their parent operation.
|
||||
assert(op->getNumOperands() == 0 &&
|
||||
"unexpected OpenMP terminator with operands");
|
||||
return success();
|
||||
})
|
||||
.Default([&](Operation *inst) {
|
||||
return inst->emitError("unsupported OpenMP operation: ")
|
||||
<< inst->getName();
|
||||
});
|
||||
}
|
||||
|
||||
/// Given a single MLIR operation, create the corresponding LLVM IR operation
|
||||
/// using the `builder`. LLVM IR Builder does not have a generic interface so
|
||||
/// this has to be a long chain of `if`s calling different functions with a
|
||||
/// different number of arguments.
|
||||
LogicalResult ModuleTranslation::convertOperation(Operation &opInst,
|
||||
llvm::IRBuilder<> &builder) {
|
||||
|
||||
// TODO(zinenko): this should be the "main" conversion here, remove the
|
||||
// dispatch below.
|
||||
if (succeeded(iface.convertOperation(&opInst, builder, *this)))
|
||||
return success();
|
||||
|
||||
if (ompDialect && opInst.getDialect() == ompDialect)
|
||||
return convertOmpOperation(opInst, builder);
|
||||
|
||||
return opInst.emitError("unsupported or non-LLVM operation: ")
|
||||
<< opInst.getName();
|
||||
}
|
||||
@@ -812,7 +551,7 @@ LogicalResult ModuleTranslation::convertOneFunction(LLVMFuncOp func) {
|
||||
|
||||
// Then, convert blocks one by one in topological order to ensure defs are
|
||||
// converted before uses.
|
||||
auto blocks = topologicalSort(func);
|
||||
auto blocks = detail::getTopologicallySortedBlocks(func.getBody());
|
||||
for (Block *bb : blocks) {
|
||||
llvm::IRBuilder<> builder(llvmContext);
|
||||
if (failed(convertBlock(*bb, bb->isEntryBlock(), builder)))
|
||||
@@ -821,7 +560,7 @@ LogicalResult ModuleTranslation::convertOneFunction(LLVMFuncOp func) {
|
||||
|
||||
// Finally, after all blocks have been traversed and values mapped, connect
|
||||
// the PHI nodes to the results of preceding blocks.
|
||||
connectPHINodes(func, *this);
|
||||
detail::connectPHINodes(func.getBody(), *this);
|
||||
return success();
|
||||
}
|
||||
|
||||
@@ -881,6 +620,11 @@ ModuleTranslation::lookupValues(ValueRange values) {
|
||||
return remapped;
|
||||
}
|
||||
|
||||
const llvm::DILocation *
|
||||
ModuleTranslation::translateLoc(Location loc, llvm::DILocalScope *scope) {
|
||||
return debugTranslation->translateLoc(loc, scope);
|
||||
}
|
||||
|
||||
std::unique_ptr<llvm::Module> ModuleTranslation::prepareLLVMModule(
|
||||
Operation *m, llvm::LLVMContext &llvmContext, StringRef name) {
|
||||
m->getContext()->getOrLoadDialect<LLVM::LLVMDialect>();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "mlir/ExecutionEngine/OptUtils.h"
|
||||
#include "mlir/IR/Dialect.h"
|
||||
#include "mlir/Target/LLVMIR.h"
|
||||
#include "mlir/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.h"
|
||||
|
||||
#include "llvm/Support/InitLLVM.h"
|
||||
#include "llvm/Support/TargetSelect.h"
|
||||
@@ -32,6 +33,8 @@ int main(int argc, char **argv) {
|
||||
mlir::DialectRegistry registry;
|
||||
registry.insert<mlir::LLVM::LLVMDialect, mlir::omp::OpenMPDialect>();
|
||||
mlir::registerLLVMDialectTranslation(registry);
|
||||
registry.addDialectInterface<mlir::omp::OpenMPDialect,
|
||||
mlir::OpenMPDialectLLVMIRTranslationInterface>();
|
||||
|
||||
return mlir::JitRunnerMain(argc, argv, registry);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user