Skip to content

[mlir] Prepare convert-gpu-to-spirv for OpenCL support #69941

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion mlir/include/mlir/Conversion/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,10 @@ def ConvertGPUToSPIRV : Pass<"convert-gpu-to-spirv", "ModuleOp"> {
to control the set and binding if wanted.
}];
let constructor = "mlir::createConvertGPUToSPIRVPass()";
let dependentDialects = ["spirv::SPIRVDialect"];
let dependentDialects = [
"func::FuncDialect",
"spirv::SPIRVDialect",
];
let options = [
Option<"use64bitIndex", "use-64bit-index",
"bool", /*default=*/"false",
Expand Down
50 changes: 47 additions & 3 deletions mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRVPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "mlir/Conversion/FuncToSPIRV/FuncToSPIRV.h"
#include "mlir/Conversion/GPUToSPIRV/GPUToSPIRV.h"
#include "mlir/Conversion/MemRefToSPIRV/MemRefToSPIRV.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"
Expand Down Expand Up @@ -54,22 +55,47 @@ void GPUToSPIRVPass::runOnOperation() {

SmallVector<Operation *, 1> gpuModules;
OpBuilder builder(context);

auto targetEnvSupportsKernelCapability = [](gpu::GPUModuleOp moduleOp) {
Operation *gpuModule = moduleOp.getOperation();
auto targetAttr = spirv::lookupTargetEnvOrDefault(gpuModule);
spirv::TargetEnv targetEnv(targetAttr);
return targetEnv.allows(spirv::Capability::Kernel);
};

module.walk([&](gpu::GPUModuleOp moduleOp) {
// Clone each GPU kernel module for conversion, given that the GPU
// launch op still needs the original GPU kernel module.
builder.setInsertionPoint(moduleOp.getOperation());
// For Vulkan Shader capabilities, we insert the newly converted SPIR-V
// module right after the original GPU module, as that's the expectation of
// the in-tree Vulkan runner.
// For OpenCL Kernel capabilities, we insert the newly converted SPIR-V
// module inside the original GPU module, as that's the expectaion of the
// normal GPU compilation pipeline.
if (targetEnvSupportsKernelCapability(moduleOp)) {
builder.setInsertionPoint(moduleOp.getBody(),
moduleOp.getBody()->begin());
} else {
builder.setInsertionPoint(moduleOp.getOperation());
}
gpuModules.push_back(builder.clone(*moduleOp.getOperation()));
});

// Run conversion for each module independently as they can have different
// TargetEnv attributes.
for (Operation *gpuModule : gpuModules) {
spirv::TargetEnvAttr targetAttr =
spirv::lookupTargetEnvOrDefault(gpuModule);

// Map MemRef memory space to SPIR-V storage class first if requested.
if (mapMemorySpace) {
std::unique_ptr<ConversionTarget> target =
spirv::getMemorySpaceToStorageClassTarget(*context);
spirv::MemorySpaceToStorageClassMap memorySpaceMap =
spirv::mapMemorySpaceToVulkanStorageClass;
targetEnvSupportsKernelCapability(
dyn_cast<gpu::GPUModuleOp>(gpuModule))
? spirv::mapMemorySpaceToOpenCLStorageClass
: spirv::mapMemorySpaceToVulkanStorageClass;
spirv::MemorySpaceToStorageClassConverter converter(memorySpaceMap);

RewritePatternSet patterns(context);
Expand All @@ -79,7 +105,6 @@ void GPUToSPIRVPass::runOnOperation() {
return signalPassFailure();
}

auto targetAttr = spirv::lookupTargetEnvOrDefault(gpuModule);
std::unique_ptr<ConversionTarget> target =
SPIRVConversionTarget::get(targetAttr);

Expand Down Expand Up @@ -108,6 +133,25 @@ void GPUToSPIRVPass::runOnOperation() {
if (failed(applyFullConversion(gpuModule, *target, std::move(patterns))))
return signalPassFailure();
}

// For OpenCL, the gpu.func op in the original gpu.module op needs to be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this gpu.func->func.func necessary? Can you just keep the original gpu.func instead?

Copy link
Contributor Author

@silee2 silee2 Oct 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the original gpu.func causes legality check error later in the gpu compile pipeline.
If target attr is set for a gpu.module, gpu-to-llvm pass doesn't lower gpu.launch_func
Instead, it is replaced with another gpu.launch_func that has lowered argument types (llvm ptrs).
If a gpu.func remains as an input to gpu-to-llvm pass, there is an argument mismatch between the new gpu.launch_func and the gpu.func. And an error is fired.
The reason for putting a dummy func.func here is to work around that check.
For func types other than gpu.func, argument types are not checked against gpu.launch_func. A func.func is still need as there will be a symbol check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the problem is, the lack of spirv support in gpu dialect. For example, gpu.func needs to be able to wrap spirv.func so gpu-to-llvm pass (for the host code) can properly handle the relation between gpu.launch_func and spirv.func.
Basically, using dummy func.func looks little hacky and it'd be also nice if the divergence between Vulkan/OpenCL IR structures could be avoided. However, considering the progress of this commit, we can discuss this later for the future enhancement.
Really appreciate this work and look forward to seeing it merged.

// replaced with an empty func.func op with the same arguments as the gpu.func
// op. The func.func op needs gpu.kernel attribute set.
module.walk([&](gpu::GPUModuleOp moduleOp) {
if (targetEnvSupportsKernelCapability(moduleOp)) {
moduleOp.walk([&](gpu::GPUFuncOp funcOp) {
builder.setInsertionPoint(funcOp);
auto newFuncOp = builder.create<func::FuncOp>(
funcOp.getLoc(), funcOp.getName(), funcOp.getFunctionType());
auto entryBlock = newFuncOp.addEntryBlock();
builder.setInsertionPointToEnd(entryBlock);
builder.create<func::ReturnOp>(funcOp.getLoc());
newFuncOp->setAttr(gpu::GPUDialect::getKernelFuncAttrName(),
builder.getUnitAttr());
funcOp.erase();
});
}
});
}

} // namespace
Expand Down
4 changes: 4 additions & 0 deletions mlir/test/Conversion/GPUToSPIRV/module-opencl.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ module attributes {
// CHECK-SAME: {{%.*}}: !spirv.ptr<!spirv.array<12 x f32>, CrossWorkgroup>
// CHECK-NOT: spirv.interface_var_abi
// CHECK-SAME: spirv.entry_point_abi = #spirv.entry_point_abi<workgroup_size = [32, 4, 1]>
// CHECK-LABEL: func.func @basic_module_structure
// CHECK-SAME: attributes {gpu.kernel}
gpu.func @basic_module_structure(%arg0 : f32, %arg1 : memref<12xf32, #spirv.storage_class<CrossWorkgroup>>) kernel
attributes {spirv.entry_point_abi = #spirv.entry_point_abi<workgroup_size = [32, 4, 1]>} {
gpu.return
Expand Down Expand Up @@ -45,6 +47,8 @@ module attributes {
// CHECK-SAME: {{%.*}}: !spirv.ptr<!spirv.array<12 x f32>, CrossWorkgroup>
// CHECK-NOT: spirv.interface_var_abi
// CHECK-SAME: spirv.entry_point_abi = #spirv.entry_point_abi<workgroup_size = [32, 4, 1]>
// CHECK-LABEL: func.func @basic_module_structure
// CHECK-SAME: attributes {gpu.kernel}
gpu.func @basic_module_structure(%arg0 : f32, %arg1 : memref<12xf32, #spirv.storage_class<CrossWorkgroup>>) kernel
attributes {spirv.entry_point_abi = #spirv.entry_point_abi<workgroup_size = [32, 4, 1]>} {
gpu.return
Expand Down