Skip to content
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
39 changes: 39 additions & 0 deletions include/gc/Target/LLVM/XeVM/Utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===-- Utils.h - MLIR XeVM target utils ------------------------*- C++ -*-===//
//
// This file is licensed 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 files declares XeVM target related utility classes and functions.
//
//===----------------------------------------------------------------------===//

#ifndef MLIR_TARGET_LLVM_XEVM_UTILS_H
#define MLIR_TARGET_LLVM_XEVM_UTILS_H

#include "gc/Dialect/LLVMIR/XeVMDialect.h"
#include "mlir/Dialect/GPU/IR/CompilationInterfaces.h"
#include "mlir/Target/LLVM/ModuleToObject.h"

namespace mlir {
namespace xevm {

/// Base class for all XeVM serializations from GPU modules into binary strings.
/// By default this class serializes into LLVM bitcode.
class SerializeGPUModuleBase : public LLVM::ModuleToObject {
public:
SerializeGPUModuleBase(Operation &module, XeVMTargetAttr target,
const gpu::TargetOptions &targetOptions = {});

static void init();
XeVMTargetAttr getTarget() const;

protected:
XeVMTargetAttr target;
};
} // namespace xevm
} // namespace mlir

#endif // MLIR_TARGET_LLVM_XEVM_UTILS_H
150 changes: 144 additions & 6 deletions lib/gc/Target/LLVM/XeVM/Target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "gc/Target/LLVM/XeVM/Target.h"

#include "gc/Dialect/LLVMIR/XeVMDialect.h"
#include "gc/Target/LLVM/XeVM/Utils.h"
#include "mlir/Dialect/GPU/IR/CompilationInterfaces.h"
#include "mlir/Dialect/GPU/IR/GPUDialect.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
Expand All @@ -36,15 +37,11 @@ class XeVMTargetAttrImpl
public:
std::optional<SmallVector<char, 0>>
serializeToObject(Attribute attribute, Operation *module,
const gpu::TargetOptions &options) const { /*TODO*/
return {};
}
const gpu::TargetOptions &options) const;

Attribute createObject(Attribute attribute, Operation *module,
const SmallVector<char, 0> &object,
const gpu::TargetOptions &options) const { /*TODO*/
return {};
}
const gpu::TargetOptions &options) const;
};
} // namespace

Expand All @@ -61,3 +58,144 @@ void mlir::xevm::registerXeVMTargetInterfaceExternalModels(
registerXeVMTargetInterfaceExternalModels(registry);
context.appendDialectRegistry(registry);
}

SerializeGPUModuleBase::SerializeGPUModuleBase(
Operation &module, XeVMTargetAttr target,
const gpu::TargetOptions &targetOptions)
: ModuleToObject(module, target.getTriple(), target.getChip(), {},
target.getO()),
target(target) {}

void SerializeGPUModuleBase::init() {
static llvm::once_flag initializeBackendOnce;
llvm::call_once(initializeBackendOnce, []() {
#if LLVM_HAS_SPIRV_TARGET
LLVMInitializeSPIRVTarget();
LLVMInitializeSPIRVTargetInfo();
LLVMInitializeSPIRVTargetMC();
LLVMInitializeSPIRVAsmPrinter();
#endif
});
}

XeVMTargetAttr SerializeGPUModuleBase::getTarget() const { return target; }

namespace {
class SpirSerializer : public SerializeGPUModuleBase {
public:
SpirSerializer(Operation &module, XeVMTargetAttr target,
const gpu::TargetOptions &targetOptions)
: SerializeGPUModuleBase(module, target, targetOptions) {}

gpu::GPUModuleOp getOperation();

std::optional<SmallVector<char, 0>>
moduleToObject(llvm::Module &llvmModule) override;

private:
std::optional<std::string>
translateToSPIRVBinary(llvm::Module &llvmModule,
llvm::TargetMachine &targetMachine);
gpu::TargetOptions targetOptions;
};
} // namespace

gpu::GPUModuleOp SpirSerializer::getOperation() {
return dyn_cast<gpu::GPUModuleOp>(&SerializeGPUModuleBase::getOperation());
}

std::optional<SmallVector<char, 0>>
SpirSerializer::moduleToObject(llvm::Module &llvmModule) {
// Return LLVM IR if the compilation target is `offload`.
if (targetOptions.getCompilationTarget() == gpu::CompilationTarget::Offload)
return SerializeGPUModuleBase::moduleToObject(llvmModule);

#if !LLVM_HAS_SPIRV_TARGET
getOperation()->emitError(
"The `SPIRV` target was not built. Please enable it when building LLVM.");
return std::nullopt;
#endif // LLVM_HAS_SPIRV_TARGET

std::optional<llvm::TargetMachine *> targetMachine =
getOrCreateTargetMachine();
if (!targetMachine) {
getOperation().emitError() << "Target Machine unavailable for triple "
<< triple << ", can't compile with LLVM\n";
return std::nullopt;
}

// Return SPIRV if the compilation target is `assembly`.
if (targetOptions.getCompilationTarget() ==
gpu::CompilationTarget::Assembly) {
std::optional<std::string> serializedISA =
translateToISA(llvmModule, **targetMachine);
if (!serializedISA) {
getOperation().emitError() << "Failed translating the module to ISA.";
return std::nullopt;
}
// Make sure to include the null terminator.
StringRef bin(serializedISA->c_str(), serializedISA->size() + 1);
return SmallVector<char, 0>(bin.begin(), bin.end());
}

std::optional<std::string> serializedSPIRVBinary =
translateToSPIRVBinary(llvmModule, **targetMachine);
if (!serializedSPIRVBinary) {
getOperation().emitError() << "Failed translating the module to Binary.";
return std::nullopt;
}

StringRef bin(serializedSPIRVBinary->c_str(),
serializedSPIRVBinary->size() + 1);
return SmallVector<char, 0>(bin.begin(), bin.end());
}

std::optional<std::string>
SpirSerializer::translateToSPIRVBinary(llvm::Module &llvmModule,
llvm::TargetMachine &targetMachine) {
std::string targetISA;
llvm::raw_string_ostream stream(targetISA);

{ // Drop pstream after this to prevent the ISA from being stuck buffering
llvm::buffer_ostream pstream(stream);
llvm::legacy::PassManager codegenPasses;

if (targetMachine.addPassesToEmitFile(codegenPasses, pstream, nullptr,
llvm::CodeGenFileType::ObjectFile))
return std::nullopt;

codegenPasses.run(llvmModule);
}
return stream.str();
}

std::optional<SmallVector<char, 0>>
XeVMTargetAttrImpl::serializeToObject(Attribute attribute, Operation *module,
const gpu::TargetOptions &options) const {
if (!module)
return std::nullopt;
auto gpuMod = dyn_cast<gpu::GPUModuleOp>(module);
if (!gpuMod) {
module->emitError("expected to be a gpu.module op");
return std::nullopt;
}

// TODO: reroute to another serializer for a different target?
SpirSerializer serializer(*module, cast<XeVMTargetAttr>(attribute), options);
serializer.init();

return serializer.run();
}

Attribute
XeVMTargetAttrImpl::createObject(Attribute attribute, Operation *module,
const SmallVector<char, 0> &object,
const gpu::TargetOptions &options) const {
gpu::CompilationTarget format = options.getCompilationTarget();
DictionaryAttr objectProps;
Builder builder(attribute.getContext());
return builder.getAttr<gpu::ObjectAttr>(
attribute, format,
builder.getStringAttr(StringRef(object.data(), object.size())),
objectProps, /*kernels=*/nullptr);
}