This commit is a further incremental step toward moving the whole mlir-vulkan-runner MLIR pass pipeline into mlir-opt (see #73457). The previous step was b225b3adf7b78387c9fcb97a3ff0e0a1e26eafe2, which moved all device passes prior to SPIR-V serialization into a new mlir-opt test pass, `-test-vulkan-runner-pipeline`. This commit changes how SPIR-V serialization is accomplished for Vulkan runner tests. Until now, this was done by the Vulkan-specific ConvertGpuLaunchFuncToVulkanLaunchFunc pass. With this commit, this responsibility is removed from that pass, and is instead done with the existing generic GpuModuleToBinaryPass. In addition, the SPIR-V serialization step is no longer done inside mlir-vulkan-runner, but rather inside mlir-opt (in the `-test-vulkan-runner-pipeline` pass). Both of these changes represent a greater alignment between mlir-vulkan-runner and the other GPU integration tests. Notably, the IR shapes produced by the mlir-opt pipelines for the Vulkan and SYCL runners are now much more similar, with both using a gpu.binary op for the serialized SPIR-V kernel. In order to enable this, this commit includes these supporting changes: - ConvertToSPIRVPass is enhanced to support producing the IR shape where a spirv.module is nested inside a gpu.module, since this is what GpuModuleToBinaryPass expects. - ConvertGPULaunchFuncToVulkanLaunchFunc is changed to remove its SPIR-V serialization functionality, and instead now extracts the SPIR-V from a gpu.binary operation (as produced by ConvertToSPIRVPass). - `-test-vulkan-runner-pipeline` now attaches SPIR-V target information required by GpuModuleToBinaryPass. - The WebGPU pass option, which had been removed from mlir-vulkan-runner in the previous commit in this series, is restored as an option to `-test-vulkan-runner-pipeline` instead, so that the WebGPU pass continues being inserted into the pipeline just before SPIR-V serialization.
220 lines
8.3 KiB
C++
220 lines
8.3 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. Extracts the SPIR-V from a `gpu::BinaryOp` and attaches it
|
|
// along with the entry point name as attributes to a 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 extracting a
|
|
/// SPIR-V binary shader from a `gpu::BinaryOp` 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:
|
|
/// Extracts a SPIR-V binary shader from the given `module`, if any.
|
|
/// Note that this also removes the binary from the IR.
|
|
FailureOr<StringAttr> getBinaryShader(ModuleOp module);
|
|
|
|
/// 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 = dyn_cast_or_null<MemRefType>(type)) {
|
|
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();
|
|
}
|
|
|
|
FailureOr<StringAttr>
|
|
ConvertGpuLaunchFuncToVulkanLaunchFunc::getBinaryShader(ModuleOp module) {
|
|
bool done = false;
|
|
StringAttr binaryAttr;
|
|
gpu::BinaryOp binaryToErase;
|
|
for (auto gpuBinary : module.getOps<gpu::BinaryOp>()) {
|
|
if (done)
|
|
return gpuBinary.emitError("should only contain one 'gpu.binary' op");
|
|
done = true;
|
|
|
|
ArrayRef<Attribute> objects = gpuBinary.getObjectsAttr().getValue();
|
|
if (objects.size() != 1)
|
|
return gpuBinary.emitError("should only contain a single object");
|
|
|
|
auto object = cast<gpu::ObjectAttr>(objects[0]);
|
|
|
|
if (!isa<spirv::TargetEnvAttr>(object.getTarget()))
|
|
return gpuBinary.emitError(
|
|
"should contain an object with a SPIR-V target environment");
|
|
|
|
binaryAttr = object.getObject();
|
|
binaryToErase = gpuBinary;
|
|
}
|
|
if (!done)
|
|
return module.emitError("should contain a 'gpu.binary' op");
|
|
|
|
// Remove the binary to avoid confusing later conversion passes.
|
|
binaryToErase.erase();
|
|
return binaryAttr;
|
|
}
|
|
|
|
void ConvertGpuLaunchFuncToVulkanLaunchFunc::convertGpuLaunchFunc(
|
|
gpu::LaunchFuncOp launchOp) {
|
|
ModuleOp module = getOperation();
|
|
OpBuilder builder(launchOp);
|
|
Location loc = launchOp.getLoc();
|
|
|
|
FailureOr<StringAttr> binaryAttr = getBinaryShader(module);
|
|
// Extract SPIR-V from `gpu.binary` op.
|
|
if (failed(binaryAttr))
|
|
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, *binaryAttr);
|
|
|
|
// 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(cast<MemRefType>(type).getElementType());
|
|
}
|
|
vulkanLaunchCallOp->setAttr(kSPIRVElementTypesAttrName,
|
|
builder.getTypeArrayAttr(elementTypes));
|
|
|
|
launchOp.erase();
|
|
}
|
|
|
|
std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
|
|
mlir::createConvertGpuLaunchFuncToVulkanLaunchFuncPass() {
|
|
return std::make_unique<ConvertGpuLaunchFuncToVulkanLaunchFunc>();
|
|
}
|