Files
clang-p2996/mlir/lib/Conversion/GPUToVulkan/ConvertGPULaunchFuncToVulkanLaunchFunc.cpp
Markus Böck 91be3586b5 [mlir][GPUToVulkan] Port conversion passes and mlir-vulkan-runner to opaque pointers
Part of https://discourse.llvm.org/t/rfc-switching-the-llvm-dialect-and-dialect-lowerings-to-opaque-pointers/68179

This patch adds the new pass option 'use-opaque-pointers' to `-launch-func-to-vulkan` instructing the pass to emit LLVM opaque pointers instead of typed pointers.

Note that the pass as it was previously implemented relied on the fact LLVM pointers carried an element type. The passed used this information to deduce both the rank of a "lowered-to-llvm" MemRef as well as the element type. Since the element type when using LLVM opaque pointers is completely erased it is not possible to deduce the element type.

I therefore added a new attribute that is attached to the `vulkanLaunch` call alongside the binary blob and entry point name by the `-convert-gpu-launch-to-vulkan-launch` pass. It simply attaches a type array specifying the element types of each memref. This way the `-launch-func-to-vulkan` can simply read out the element type from the attribute.
The rank can still be deduced from the auto-generated C interface from `FinalizeMemRefToLLVM`. This is admittedly a bit fragile but I was not sure whether it was worth the effort to also add a rank array attribute.

As a last step, the use of opaque-pointers in `mlir-vulkan-runners` codegen pipeline was also enabled, since all covnersion passes used fully support it.

Differential Revision: https://reviews.llvm.org/D144460
2023-02-24 17:04:42 +01:00

212 lines
8.1 KiB
C++

//===- ConvertGPULaunchFuncToVulkanLaunchFunc.cpp - MLIR conversion pass --===//
//
// 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 pass to convert gpu launch function into a vulkan
// launch function. Creates a SPIR-V binary shader from the `spirv::ModuleOp`
// using `spirv::serialize` function, attaches binary data and entry point name
// as an attributes to vulkan launch call op.
//
//===----------------------------------------------------------------------===//
#include "mlir/Conversion/GPUToVulkan/ConvertGPUToVulkanPass.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/GPU/IR/GPUDialect.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVDialect.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Target/SPIRV/Serialization.h"
namespace mlir {
#define GEN_PASS_DEF_CONVERTGPULAUNCHFUNCTOVULKANLAUNCHFUNC
#include "mlir/Conversion/Passes.h.inc"
} // namespace mlir
using namespace mlir;
static constexpr const char *kSPIRVBlobAttrName = "spirv_blob";
static constexpr const char *kSPIRVEntryPointAttrName = "spirv_entry_point";
static constexpr const char *kSPIRVElementTypesAttrName = "spirv_element_types";
static constexpr const char *kVulkanLaunch = "vulkanLaunch";
namespace {
/// A pass to convert gpu launch op to vulkan launch call op, by creating a
/// SPIR-V binary shader from `spirv::ModuleOp` using `spirv::serialize`
/// function and attaching binary data and entry point name as an attributes to
/// created vulkan launch call op.
class ConvertGpuLaunchFuncToVulkanLaunchFunc
: public impl::ConvertGpuLaunchFuncToVulkanLaunchFuncBase<
ConvertGpuLaunchFuncToVulkanLaunchFunc> {
public:
void runOnOperation() override;
private:
/// Creates a SPIR-V binary shader from the given `module` using
/// `spirv::serialize` function.
LogicalResult createBinaryShader(ModuleOp module,
std::vector<char> &binaryShader);
/// Converts the given `launchOp` to vulkan launch call.
void convertGpuLaunchFunc(gpu::LaunchFuncOp launchOp);
/// Checks where the given type is supported by Vulkan runtime.
bool isSupportedType(Type type) {
if (auto memRefType = type.dyn_cast_or_null<MemRefType>()) {
auto elementType = memRefType.getElementType();
return memRefType.hasRank() &&
(memRefType.getRank() >= 1 && memRefType.getRank() <= 3) &&
(elementType.isIntOrFloat());
}
return false;
}
/// Declares the vulkan launch function. Returns an error if the any type of
/// operand is unsupported by Vulkan runtime.
LogicalResult declareVulkanLaunchFunc(Location loc,
gpu::LaunchFuncOp launchOp);
private:
/// The number of vulkan launch configuration operands, placed at the leading
/// positions of the operand list.
static constexpr unsigned kVulkanLaunchNumConfigOperands = 3;
};
} // namespace
void ConvertGpuLaunchFuncToVulkanLaunchFunc::runOnOperation() {
bool done = false;
getOperation().walk([this, &done](gpu::LaunchFuncOp op) {
if (done) {
op.emitError("should only contain one 'gpu::LaunchFuncOp' op");
return signalPassFailure();
}
done = true;
convertGpuLaunchFunc(op);
});
// Erase `gpu::GPUModuleOp` and `spirv::Module` operations.
for (auto gpuModule :
llvm::make_early_inc_range(getOperation().getOps<gpu::GPUModuleOp>()))
gpuModule.erase();
for (auto spirvModule :
llvm::make_early_inc_range(getOperation().getOps<spirv::ModuleOp>()))
spirvModule.erase();
}
LogicalResult ConvertGpuLaunchFuncToVulkanLaunchFunc::declareVulkanLaunchFunc(
Location loc, gpu::LaunchFuncOp launchOp) {
auto builder = OpBuilder::atBlockEnd(getOperation().getBody());
// Workgroup size is written into the kernel. So to properly modelling
// vulkan launch, we have to skip local workgroup size configuration here.
SmallVector<Type, 8> gpuLaunchTypes(launchOp.getOperandTypes());
// The first kVulkanLaunchNumConfigOperands of the gpu.launch_func op are the
// same as the config operands for the vulkan launch call op.
SmallVector<Type, 8> vulkanLaunchTypes(gpuLaunchTypes.begin(),
gpuLaunchTypes.begin() +
kVulkanLaunchNumConfigOperands);
vulkanLaunchTypes.append(gpuLaunchTypes.begin() +
gpu::LaunchOp::kNumConfigOperands,
gpuLaunchTypes.end());
// Check that all operands have supported types except those for the
// launch configuration.
for (auto type :
llvm::drop_begin(vulkanLaunchTypes, kVulkanLaunchNumConfigOperands)) {
if (!isSupportedType(type))
return launchOp.emitError() << type << " is unsupported to run on Vulkan";
}
// Declare vulkan launch function.
auto funcType = builder.getFunctionType(vulkanLaunchTypes, {});
builder.create<func::FuncOp>(loc, kVulkanLaunch, funcType).setPrivate();
return success();
}
LogicalResult ConvertGpuLaunchFuncToVulkanLaunchFunc::createBinaryShader(
ModuleOp module, std::vector<char> &binaryShader) {
bool done = false;
SmallVector<uint32_t, 0> binary;
for (auto spirvModule : module.getOps<spirv::ModuleOp>()) {
if (done)
return spirvModule.emitError("should only contain one 'spirv.module' op");
done = true;
if (failed(spirv::serialize(spirvModule, binary)))
return failure();
}
binaryShader.resize(binary.size() * sizeof(uint32_t));
std::memcpy(binaryShader.data(), reinterpret_cast<char *>(binary.data()),
binaryShader.size());
return success();
}
void ConvertGpuLaunchFuncToVulkanLaunchFunc::convertGpuLaunchFunc(
gpu::LaunchFuncOp launchOp) {
ModuleOp module = getOperation();
OpBuilder builder(launchOp);
Location loc = launchOp.getLoc();
// Serialize `spirv::Module` into binary form.
std::vector<char> binary;
if (failed(createBinaryShader(module, binary)))
return signalPassFailure();
// Declare vulkan launch function.
if (failed(declareVulkanLaunchFunc(loc, launchOp)))
return signalPassFailure();
SmallVector<Value, 8> gpuLaunchOperands(launchOp.getOperands());
SmallVector<Value, 8> vulkanLaunchOperands(
gpuLaunchOperands.begin(),
gpuLaunchOperands.begin() + kVulkanLaunchNumConfigOperands);
vulkanLaunchOperands.append(gpuLaunchOperands.begin() +
gpu::LaunchOp::kNumConfigOperands,
gpuLaunchOperands.end());
// Create vulkan launch call op.
auto vulkanLaunchCallOp = builder.create<func::CallOp>(
loc, TypeRange{}, SymbolRefAttr::get(builder.getContext(), kVulkanLaunch),
vulkanLaunchOperands);
// Set SPIR-V binary shader data as an attribute.
vulkanLaunchCallOp->setAttr(
kSPIRVBlobAttrName,
builder.getStringAttr(StringRef(binary.data(), binary.size())));
// Set entry point name as an attribute.
vulkanLaunchCallOp->setAttr(kSPIRVEntryPointAttrName,
launchOp.getKernelName());
// Add MemRef element types before they're lost when lowering to LLVM.
SmallVector<Type> elementTypes;
for (Type type : llvm::drop_begin(launchOp.getOperandTypes(),
gpu::LaunchOp::kNumConfigOperands)) {
// The below cast always succeeds as it has already been verified in
// 'declareVulkanLaunchFunc' that these are MemRefs with compatible element
// types.
elementTypes.push_back(type.cast<MemRefType>().getElementType());
}
vulkanLaunchCallOp->setAttr(kSPIRVElementTypesAttrName,
builder.getTypeArrayAttr(elementTypes));
launchOp.erase();
}
std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
mlir::createConvertGpuLaunchFuncToVulkanLaunchFuncPass() {
return std::make_unique<ConvertGpuLaunchFuncToVulkanLaunchFunc>();
}