11090a830SDenis Khalikov //===- ConvertGPULaunchFuncToVulkanLaunchFunc.cpp - MLIR conversion pass --===// 21090a830SDenis Khalikov // 31090a830SDenis Khalikov // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 41090a830SDenis Khalikov // See https://llvm.org/LICENSE.txt for license information. 51090a830SDenis Khalikov // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 61090a830SDenis Khalikov // 71090a830SDenis Khalikov //===----------------------------------------------------------------------===// 81090a830SDenis Khalikov // 91090a830SDenis Khalikov // This file implements a pass to convert gpu launch function into a vulkan 101090a830SDenis Khalikov // launch function. Creates a SPIR-V binary shader from the `spirv::ModuleOp` 111090a830SDenis Khalikov // using `spirv::serialize` function, attaches binary data and entry point name 121090a830SDenis Khalikov // as an attributes to vulkan launch call op. 131090a830SDenis Khalikov // 141090a830SDenis Khalikov //===----------------------------------------------------------------------===// 151090a830SDenis Khalikov 161834ad4aSRiver Riddle #include "../PassDetail.h" 171090a830SDenis Khalikov #include "mlir/Conversion/GPUToVulkan/ConvertGPUToVulkanPass.h" 181090a830SDenis Khalikov #include "mlir/Dialect/GPU/GPUDialect.h" 19f9dc2b70SMehdi Amini #include "mlir/Dialect/SPIRV/SPIRVDialect.h" 201090a830SDenis Khalikov #include "mlir/Dialect/SPIRV/SPIRVOps.h" 211090a830SDenis Khalikov #include "mlir/Dialect/SPIRV/Serialization.h" 221090a830SDenis Khalikov #include "mlir/Dialect/StandardOps/IR/Ops.h" 231090a830SDenis Khalikov #include "mlir/IR/Attributes.h" 241090a830SDenis Khalikov #include "mlir/IR/Builders.h" 251090a830SDenis Khalikov #include "mlir/IR/Function.h" 261090a830SDenis Khalikov #include "mlir/IR/Module.h" 271090a830SDenis Khalikov #include "mlir/IR/StandardTypes.h" 281090a830SDenis Khalikov 291090a830SDenis Khalikov using namespace mlir; 301090a830SDenis Khalikov 311090a830SDenis Khalikov static constexpr const char *kSPIRVBlobAttrName = "spirv_blob"; 321090a830SDenis Khalikov static constexpr const char *kSPIRVEntryPointAttrName = "spirv_entry_point"; 331090a830SDenis Khalikov static constexpr const char *kVulkanLaunch = "vulkanLaunch"; 341090a830SDenis Khalikov 351090a830SDenis Khalikov namespace { 361090a830SDenis Khalikov 37bfb2ce02SDenis Khalikov /// A pass to convert gpu launch op to vulkan launch call op, by creating a 38bfb2ce02SDenis Khalikov /// SPIR-V binary shader from `spirv::ModuleOp` using `spirv::serialize` 39bfb2ce02SDenis Khalikov /// function and attaching binary data and entry point name as an attributes to 40bfb2ce02SDenis Khalikov /// created vulkan launch call op. 411090a830SDenis Khalikov class ConvertGpuLaunchFuncToVulkanLaunchFunc 421834ad4aSRiver Riddle : public ConvertGpuLaunchFuncToVulkanLaunchFuncBase< 431834ad4aSRiver Riddle ConvertGpuLaunchFuncToVulkanLaunchFunc> { 441090a830SDenis Khalikov public: 45722f909fSRiver Riddle void runOnOperation() override; 461090a830SDenis Khalikov 471090a830SDenis Khalikov private: 481090a830SDenis Khalikov /// Creates a SPIR-V binary shader from the given `module` using 491090a830SDenis Khalikov /// `spirv::serialize` function. 501090a830SDenis Khalikov LogicalResult createBinaryShader(ModuleOp module, 511090a830SDenis Khalikov std::vector<char> &binaryShader); 521090a830SDenis Khalikov 53e5a85126SKazuaki Ishizaki /// Converts the given `launchOp` to vulkan launch call. 541090a830SDenis Khalikov void convertGpuLaunchFunc(gpu::LaunchFuncOp launchOp); 551090a830SDenis Khalikov 561090a830SDenis Khalikov /// Checks where the given type is supported by Vulkan runtime. 571090a830SDenis Khalikov bool isSupportedType(Type type) { 581009177dSDenis Khalikov if (auto memRefType = type.dyn_cast_or_null<MemRefType>()) { 591009177dSDenis Khalikov auto elementType = memRefType.getElementType(); 608f4ab8c7SDenis Khalikov return memRefType.hasRank() && 611009177dSDenis Khalikov (memRefType.getRank() >= 1 && memRefType.getRank() <= 3) && 621009177dSDenis Khalikov (elementType.isIntOrFloat()); 631009177dSDenis Khalikov } 641090a830SDenis Khalikov return false; 651090a830SDenis Khalikov } 661090a830SDenis Khalikov 671090a830SDenis Khalikov /// Declares the vulkan launch function. Returns an error if the any type of 681090a830SDenis Khalikov /// operand is unsupported by Vulkan runtime. 691090a830SDenis Khalikov LogicalResult declareVulkanLaunchFunc(Location loc, 701090a830SDenis Khalikov gpu::LaunchFuncOp launchOp); 71a48f0a3cSDenis Khalikov 72a48f0a3cSDenis Khalikov private: 73a48f0a3cSDenis Khalikov /// The number of vulkan launch configuration operands, placed at the leading 74a48f0a3cSDenis Khalikov /// positions of the operand list. 75a48f0a3cSDenis Khalikov static constexpr unsigned kVulkanLaunchNumConfigOperands = 3; 761090a830SDenis Khalikov }; 771090a830SDenis Khalikov 781090a830SDenis Khalikov } // anonymous namespace 791090a830SDenis Khalikov 80722f909fSRiver Riddle void ConvertGpuLaunchFuncToVulkanLaunchFunc::runOnOperation() { 811090a830SDenis Khalikov bool done = false; 82722f909fSRiver Riddle getOperation().walk([this, &done](gpu::LaunchFuncOp op) { 831090a830SDenis Khalikov if (done) { 841090a830SDenis Khalikov op.emitError("should only contain one 'gpu::LaunchFuncOp' op"); 851090a830SDenis Khalikov return signalPassFailure(); 861090a830SDenis Khalikov } 871090a830SDenis Khalikov done = true; 881090a830SDenis Khalikov convertGpuLaunchFunc(op); 891090a830SDenis Khalikov }); 901090a830SDenis Khalikov 911090a830SDenis Khalikov // Erase `gpu::GPUModuleOp` and `spirv::Module` operations. 921090a830SDenis Khalikov for (auto gpuModule : 93722f909fSRiver Riddle llvm::make_early_inc_range(getOperation().getOps<gpu::GPUModuleOp>())) 941090a830SDenis Khalikov gpuModule.erase(); 951090a830SDenis Khalikov 961090a830SDenis Khalikov for (auto spirvModule : 97722f909fSRiver Riddle llvm::make_early_inc_range(getOperation().getOps<spirv::ModuleOp>())) 981090a830SDenis Khalikov spirvModule.erase(); 991090a830SDenis Khalikov } 1001090a830SDenis Khalikov 1011090a830SDenis Khalikov LogicalResult ConvertGpuLaunchFuncToVulkanLaunchFunc::declareVulkanLaunchFunc( 1021090a830SDenis Khalikov Location loc, gpu::LaunchFuncOp launchOp) { 103722f909fSRiver Riddle OpBuilder builder(getOperation().getBody()->getTerminator()); 1041090a830SDenis Khalikov 105a48f0a3cSDenis Khalikov // Workgroup size is written into the kernel. So to properly modelling 106a48f0a3cSDenis Khalikov // vulkan launch, we have to skip local workgroup size configuration here. 107a48f0a3cSDenis Khalikov SmallVector<Type, 8> gpuLaunchTypes(launchOp.getOperandTypes()); 108a48f0a3cSDenis Khalikov // The first kVulkanLaunchNumConfigOperands of the gpu.launch_func op are the 109a48f0a3cSDenis Khalikov // same as the config operands for the vulkan launch call op. 110a48f0a3cSDenis Khalikov SmallVector<Type, 8> vulkanLaunchTypes(gpuLaunchTypes.begin(), 111a48f0a3cSDenis Khalikov gpuLaunchTypes.begin() + 112a48f0a3cSDenis Khalikov kVulkanLaunchNumConfigOperands); 113a48f0a3cSDenis Khalikov vulkanLaunchTypes.append(gpuLaunchTypes.begin() + 114a48f0a3cSDenis Khalikov gpu::LaunchOp::kNumConfigOperands, 115a48f0a3cSDenis Khalikov gpuLaunchTypes.end()); 116a48f0a3cSDenis Khalikov 117a48f0a3cSDenis Khalikov // Check that all operands have supported types except those for the 118a48f0a3cSDenis Khalikov // launch configuration. 1198f4ab8c7SDenis Khalikov for (auto type : 120a48f0a3cSDenis Khalikov llvm::drop_begin(vulkanLaunchTypes, kVulkanLaunchNumConfigOperands)) { 1211090a830SDenis Khalikov if (!isSupportedType(type)) 1221090a830SDenis Khalikov return launchOp.emitError() << type << " is unsupported to run on Vulkan"; 1231090a830SDenis Khalikov } 1241090a830SDenis Khalikov 1251090a830SDenis Khalikov // Declare vulkan launch function. 1261090a830SDenis Khalikov builder.create<FuncOp>( 1271090a830SDenis Khalikov loc, kVulkanLaunch, 12874145d58SRahul Joshi FunctionType::get(vulkanLaunchTypes, {}, loc->getContext())); 1291090a830SDenis Khalikov 1301090a830SDenis Khalikov return success(); 1311090a830SDenis Khalikov } 1321090a830SDenis Khalikov 1331090a830SDenis Khalikov LogicalResult ConvertGpuLaunchFuncToVulkanLaunchFunc::createBinaryShader( 1341090a830SDenis Khalikov ModuleOp module, std::vector<char> &binaryShader) { 1351090a830SDenis Khalikov bool done = false; 1361090a830SDenis Khalikov SmallVector<uint32_t, 0> binary; 1371090a830SDenis Khalikov for (auto spirvModule : module.getOps<spirv::ModuleOp>()) { 1381090a830SDenis Khalikov if (done) 1391090a830SDenis Khalikov return spirvModule.emitError("should only contain one 'spv.module' op"); 1401090a830SDenis Khalikov done = true; 1411090a830SDenis Khalikov 1421090a830SDenis Khalikov if (failed(spirv::serialize(spirvModule, binary))) 1431090a830SDenis Khalikov return failure(); 1441090a830SDenis Khalikov } 1451090a830SDenis Khalikov binaryShader.resize(binary.size() * sizeof(uint32_t)); 1461090a830SDenis Khalikov std::memcpy(binaryShader.data(), reinterpret_cast<char *>(binary.data()), 1471090a830SDenis Khalikov binaryShader.size()); 1481090a830SDenis Khalikov return success(); 1491090a830SDenis Khalikov } 1501090a830SDenis Khalikov 1511090a830SDenis Khalikov void ConvertGpuLaunchFuncToVulkanLaunchFunc::convertGpuLaunchFunc( 1521090a830SDenis Khalikov gpu::LaunchFuncOp launchOp) { 153722f909fSRiver Riddle ModuleOp module = getOperation(); 1541090a830SDenis Khalikov OpBuilder builder(launchOp); 1551090a830SDenis Khalikov Location loc = launchOp.getLoc(); 1561090a830SDenis Khalikov 1571090a830SDenis Khalikov // Serialize `spirv::Module` into binary form. 1581090a830SDenis Khalikov std::vector<char> binary; 1591090a830SDenis Khalikov if (failed(createBinaryShader(module, binary))) 1601090a830SDenis Khalikov return signalPassFailure(); 1611090a830SDenis Khalikov 1621090a830SDenis Khalikov // Declare vulkan launch function. 1631090a830SDenis Khalikov if (failed(declareVulkanLaunchFunc(loc, launchOp))) 1641090a830SDenis Khalikov return signalPassFailure(); 1651090a830SDenis Khalikov 166a48f0a3cSDenis Khalikov SmallVector<Value, 8> gpuLaunchOperands(launchOp.getOperands()); 167a48f0a3cSDenis Khalikov SmallVector<Value, 8> vulkanLaunchOperands( 168a48f0a3cSDenis Khalikov gpuLaunchOperands.begin(), 169a48f0a3cSDenis Khalikov gpuLaunchOperands.begin() + kVulkanLaunchNumConfigOperands); 170a48f0a3cSDenis Khalikov vulkanLaunchOperands.append(gpuLaunchOperands.begin() + 171a48f0a3cSDenis Khalikov gpu::LaunchOp::kNumConfigOperands, 172a48f0a3cSDenis Khalikov gpuLaunchOperands.end()); 173a48f0a3cSDenis Khalikov 1741090a830SDenis Khalikov // Create vulkan launch call op. 1751090a830SDenis Khalikov auto vulkanLaunchCallOp = builder.create<CallOp>( 176*08e4f078SRahul Joshi loc, TypeRange{}, builder.getSymbolRefAttr(kVulkanLaunch), 177a48f0a3cSDenis Khalikov vulkanLaunchOperands); 1781090a830SDenis Khalikov 1791090a830SDenis Khalikov // Set SPIR-V binary shader data as an attribute. 1801090a830SDenis Khalikov vulkanLaunchCallOp.setAttr( 1811090a830SDenis Khalikov kSPIRVBlobAttrName, 1821090a830SDenis Khalikov StringAttr::get({binary.data(), binary.size()}, loc->getContext())); 1831090a830SDenis Khalikov 1841090a830SDenis Khalikov // Set entry point name as an attribute. 1851090a830SDenis Khalikov vulkanLaunchCallOp.setAttr( 1861090a830SDenis Khalikov kSPIRVEntryPointAttrName, 1870372db05SFrederik Gossen StringAttr::get(launchOp.getKernelName(), loc->getContext())); 1881090a830SDenis Khalikov 1891090a830SDenis Khalikov launchOp.erase(); 1901090a830SDenis Khalikov } 1911090a830SDenis Khalikov 19280aca1eaSRiver Riddle std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>> 1931090a830SDenis Khalikov mlir::createConvertGpuLaunchFuncToVulkanLaunchFuncPass() { 1941090a830SDenis Khalikov return std::make_unique<ConvertGpuLaunchFuncToVulkanLaunchFunc>(); 1951090a830SDenis Khalikov } 196