//===- 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 &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 = 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(); } LogicalResult ConvertGpuLaunchFuncToVulkanLaunchFunc::createBinaryShader( ModuleOp module, std::vector &binaryShader) { bool done = false; SmallVector binary; for (auto spirvModule : module.getOps()) { 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(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 binary; if (failed(createBinaryShader(module, binary))) 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, 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 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(); }