//===- 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 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(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())) gpuModule.erase(); for (auto spirvModule : llvm::make_early_inc_range(getOperation().getOps())) 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 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 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(loc, kVulkanLaunch, funcType).setPrivate(); return success(); } FailureOr ConvertGpuLaunchFuncToVulkanLaunchFunc::getBinaryShader(ModuleOp module) { bool done = false; StringAttr binaryAttr; gpu::BinaryOp binaryToErase; for (auto gpuBinary : module.getOps()) { if (done) return gpuBinary.emitError("should only contain one 'gpu.binary' op"); done = true; ArrayRef objects = gpuBinary.getObjectsAttr().getValue(); if (objects.size() != 1) return gpuBinary.emitError("should only contain a single object"); auto object = cast(objects[0]); if (!isa(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 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 gpuLaunchOperands(launchOp.getOperands()); SmallVector vulkanLaunchOperands( gpuLaunchOperands.begin(), gpuLaunchOperands.begin() + kVulkanLaunchNumConfigOperands); vulkanLaunchOperands.append(gpuLaunchOperands.begin() + gpu::LaunchOp::kNumConfigOperands, gpuLaunchOperands.end()); // Create vulkan launch call op. auto vulkanLaunchCallOp = builder.create( 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 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(type).getElementType()); } vulkanLaunchCallOp->setAttr(kSPIRVElementTypesAttrName, builder.getTypeArrayAttr(elementTypes)); launchOp.erase(); } std::unique_ptr> mlir::createConvertGpuLaunchFuncToVulkanLaunchFuncPass() { return std::make_unique(); }