28#include "llvm/IR/LegacyPassManager.h"
29#include "llvm/Target/TargetMachine.h"
31#include "llvm/Bitcode/BitcodeWriter.h"
32#include "llvm/Config/Targets.h"
33#include "llvm/Support/FileSystem.h"
34#include "llvm/Support/FileUtilities.h"
35#include "llvm/Support/FormatVariadic.h"
36#include "llvm/Support/MemoryBuffer.h"
37#include "llvm/Support/Path.h"
38#include "llvm/Support/Process.h"
39#include "llvm/Support/Program.h"
40#include "llvm/Support/TargetSelect.h"
41#include "llvm/Support/raw_ostream.h"
43#if LLVM_HAS_SPIRV_TARGET
44#include "MCTargetDesc/SPIRVBaseInfo.h"
45#include "SPIRVCommandLine.h"
46#include "SPIRVSubtarget.h"
47#include "SPIRVTargetMachine.h"
58class XeVMTargetAttrImpl
59 :
public gpu::TargetAttrInterface::FallbackModel<XeVMTargetAttrImpl> {
61 std::optional<mlir::gpu::SerializedObject>
62 serializeToObject(Attribute attribute, Operation *module,
63 const gpu::TargetOptions &
options)
const;
65 Attribute createObject(Attribute attribute, Operation *module,
66 const mlir::gpu::SerializedObject &
object,
67 const gpu::TargetOptions &
options)
const;
74 XeVMTargetAttr::attachInterface<XeVMTargetAttrImpl>(*ctx);
89 xeTarget(xeTarget), librariesToLink(targetOptions.getLibrariesToLink()),
90 targetOptions(targetOptions) {
91 if (xeTarget.getLinkFiles())
92 librariesToLink.append(xeTarget.getLinkFiles().begin(),
93 xeTarget.getLinkFiles().end());
98std::optional<SmallVector<std::unique_ptr<llvm::Module>>>
106 return std::move(bcFiles);
117FailureOr<SmallVector<char, 0>>
119 StringRef inputFormat) {
120 using TmpFile = std::pair<llvm::SmallString<128>, llvm::FileRemover>;
122 std::optional<std::string> oclocCompiler =
findTool(
"ocloc");
126 std::string basename = llvm::formatv(
130 auto createTemp = [&](StringRef name,
131 StringRef suffix) -> FailureOr<TmpFile> {
133 if (
auto ec = llvm::sys::fs::createTemporaryFile(name, suffix, filePath))
135 <<
"Couldn't create the temp file: `" << filePath
136 <<
"`, error message: " << ec.message();
138 return TmpFile(filePath, llvm::FileRemover(filePath.c_str()));
141 FailureOr<TmpFile> asmFile = createTemp(basename,
"asm");
142 FailureOr<TmpFile> binFile = createTemp(basename,
"");
143 FailureOr<TmpFile> logFile = createTemp(basename,
"log");
144 if (failed(logFile) || failed(asmFile) || failed(binFile))
149 llvm::raw_fd_ostream asmStream(asmFile->first, ec);
151 return emitError(loc) <<
"Couldn't open the file: `" << asmFile->first
152 <<
"`, error message: " << ec.message();
155 if (asmStream.has_error())
157 <<
"An error occurred while writing the assembly to: `"
158 << asmFile->first <<
"`.";
163 std::pair<llvm::BumpPtrAllocator, SmallVector<const char *>> cmdOpts =
166 const std::string cmdOptsStr =
"\"" + llvm::join(cmdOpts.second,
" ") +
"\"";
168 {
"ocloc",
"compile",
"-file", asmFile->first, inputFormat,
"-device",
169 getTarget().getChip(),
"-output", binFile->first,
"-output_no_suffix",
170 "-options", cmdOptsStr});
173#define DEBUG_TYPE "serialize-to-binary"
175 llvm::dbgs() <<
"Tool invocation for module: "
177 llvm::interleave(oclocArgs, llvm::dbgs(),
" ");
178 llvm::dbgs() <<
"\n";
185 if (message.empty()) {
186 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> toolStderr =
187 llvm::MemoryBuffer::getFile(logFile->first);
189 return emitError(loc) << toolName <<
" invocation failed. Log:\n"
190 << toolStderr->get()->getBuffer();
192 return emitError(loc) << toolName <<
" invocation failed.";
195 <<
" invocation failed, error message: " << message;
197 std::optional<StringRef> redirects[] = {
203 if (llvm::sys::ExecuteAndWait(oclocCompiler.value(), oclocArgs, std::nullopt,
204 redirects, 0, 0, &message))
205 return emitLogError(
"`ocloc`");
206 binFile->first.append(
".bin");
207 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> binaryBuffer =
208 llvm::MemoryBuffer::getFile(binFile->first);
210 return emitError(loc) <<
"Couldn't open the file: `" << binFile->first
211 <<
"`, error message: "
212 << binaryBuffer.getError().message();
214 StringRef bin = (*binaryBuffer)->getBuffer();
222 if (!pathRef.empty()) {
223 path.insert(path.begin(), pathRef.begin(), pathRef.end());
224 llvm::sys::path::append(path,
"bin", tool);
225 if (llvm::sys::fs::can_execute(path))
226 return StringRef(path.data(), path.size()).str();
229 if (std::optional<std::string> toolPath =
230 llvm::sys::Process::FindInEnvPath(
"PATH", tool))
234 <<
"Couldn't find the `" << tool
235 <<
"` binary. Please specify the toolkit "
236 "path via GpuModuleToBinaryPass or add the compiler to $PATH`.";
243 SPIRVSerializer(
Operation &module, XeVMTargetAttr xeTarget,
250 std::optional<SmallVector<char, 0>> run()
override;
255 FailureOr<SmallVector<char, 0>>
256 moduleToObject(llvm::Module &llvmModule)
override;
261 std::optional<std::string>
262 translateToSPIRVBinary(llvm::Module &llvmModule,
263 llvm::TargetMachine &targetMachine);
267void SPIRVSerializer::init() {
268 static llvm::once_flag initializeBackendOnce;
269 llvm::call_once(initializeBackendOnce, []() {
270#if LLVM_HAS_SPIRV_TARGET
271 LLVMInitializeSPIRVTarget();
272 LLVMInitializeSPIRVTargetInfo();
273 LLVMInitializeSPIRVTargetMC();
274 LLVMInitializeSPIRVAsmPrinter();
279#if LLVM_HAS_SPIRV_TARGET
280static const std::vector<std::string> getDefaultSPIRVExtensions() {
282 "SPV_EXT_relaxed_printf_string_address_space",
283 "SPV_INTEL_cache_controls",
284 "SPV_INTEL_variable_length_array",
289std::optional<SmallVector<char, 0>> SPIRVSerializer::run() {
291 llvm::LLVMContext llvmContext;
292 std::unique_ptr<llvm::Module> llvmModule = translateToLLVMIR(llvmContext);
294 getOperation().emitError() <<
"Failed creating the llvm::Module.";
298#define DEBUG_TYPE "serialize-to-llvm"
300 llvm::dbgs() <<
"LLVM IR for module: " << getGPUModuleOp().getNameAttr()
302 llvm::dbgs() << *llvmModule <<
"\n";
303 llvm::dbgs().flush();
308 if (targetOptions.getCompilationTarget() == gpu::CompilationTarget::Offload)
311#if LLVM_HAS_SPIRV_TARGET
312 setDataLayoutAndTriple(*llvmModule);
315 FailureOr<llvm::TargetMachine *> targetMachine = getOrCreateTargetMachine();
316 if (
failed(targetMachine)) {
317 getOperation().emitError()
318 <<
"Target Machine unavailable for triple " << triple
319 <<
", can't output compilation target.\n";
323 std::set<llvm::SPIRV::Extension::Extension> AllowedExtIds;
324 llvm::StringRef UnknownExt = llvm::SPIRVExtensionsParser::checkExtensions(
325 getDefaultSPIRVExtensions(), AllowedExtIds);
326 if (!UnknownExt.empty()) {
327 std::string ErrMsg{
"Unknown SPIR-V extension: "};
328 ErrMsg.append(UnknownExt.str());
329 getOperation().emitError() << ErrMsg;
333 llvm::SPIRVTargetMachine *STM =
334 static_cast<llvm::SPIRVTargetMachine *
>(*targetMachine);
335 const_cast<llvm::SPIRVSubtarget *
>(STM->getSubtargetImpl())
336 ->initAvailableExtensions(AllowedExtIds);
338 if (initialLlvmIRCallback)
339 initialLlvmIRCallback(*llvmModule);
342 handleModulePreLink(*llvmModule);
344 auto libs = loadBitcodeFiles(*llvmModule);
348 if (
failed(linkFiles(*llvmModule, std::move(*libs))))
350 handleModulePostLink(*llvmModule);
353 if (linkedLlvmIRCallback)
354 linkedLlvmIRCallback(*llvmModule);
357 if (
failed(optimizeModule(*llvmModule, optLevel)))
360 if (optimizedLlvmIRCallback)
361 optimizedLlvmIRCallback(*llvmModule);
364 return moduleToObject(*llvmModule);
366 getOperation().emitError(
"The `SPIRV` target was not built. Please enable "
367 "it when building LLVM.");
372FailureOr<SmallVector<char, 0>>
373SPIRVSerializer::moduleToObject(llvm::Module &llvmModule) {
374 FailureOr<llvm::TargetMachine *> targetMachine = getOrCreateTargetMachine();
375 if (
failed(targetMachine))
376 return getGPUModuleOp().emitError()
377 <<
"Target Machine unavailable for triple " << triple
378 <<
", can't optimize with LLVM\n";
381 if (targetOptions.getCompilationTarget() ==
382 gpu::CompilationTarget::Assembly) {
383 FailureOr<SmallString<0>> serializedISA =
384 translateModuleToISA(llvmModule, **targetMachine,
385 [&]() {
return getGPUModuleOp().emitError(); });
386 if (
failed(serializedISA))
387 return getGPUModuleOp().emitError()
388 <<
"Failed translating the module to ISA." << triple
389 <<
", can't compile with LLVM\n";
391#define DEBUG_TYPE "serialize-to-isa"
393 llvm::dbgs() <<
"SPIR-V for module: " << getGPUModuleOp().getNameAttr()
395 llvm::dbgs() << *serializedISA <<
"\n";
396 llvm::dbgs().flush();
401 StringRef bin(serializedISA->c_str(), serializedISA->size() + 1);
402 return SmallVector<char, 0>(bin.begin(), bin.end());
410 std::optional<std::string> serializedSPIRVBinary =
411 translateToSPIRVBinary(llvmModule, **targetMachine);
412 if (!serializedSPIRVBinary)
413 return getGPUModuleOp().emitError()
414 <<
"Failed translating the module to Binary.";
416 if (serializedSPIRVBinary->size() % 4)
417 return getGPUModuleOp().emitError()
418 <<
"SPIRV code size must be a multiple of 4.";
420 StringRef bin(serializedSPIRVBinary->c_str(), serializedSPIRVBinary->size());
421 return SmallVector<char, 0>(bin.begin(), bin.end());
424std::optional<std::string>
425SPIRVSerializer::translateToSPIRVBinary(llvm::Module &llvmModule,
426 llvm::TargetMachine &targetMachine) {
427 std::string targetISA;
428 llvm::raw_string_ostream stream(targetISA);
431 llvm::buffer_ostream pstream(stream);
432 llvm::legacy::PassManager codegenPasses;
433 if (targetMachine.addPassesToEmitFile(codegenPasses, pstream,
nullptr,
434 llvm::CodeGenFileType::ObjectFile))
437 codegenPasses.run(llvmModule);
442std::optional<mlir::gpu::SerializedObject>
443XeVMTargetAttrImpl::serializeToObject(Attribute attribute, Operation *module,
444 const gpu::TargetOptions &
options)
const {
447 auto gpuMod = dyn_cast<gpu::GPUModuleOp>(module);
449 module->emitError("expected to be a gpu.module op");
452 auto xeTarget = cast<XeVMTargetAttr>(attribute);
453 if (xeTarget.getTriple().starts_with(
"spirv")) {
454 gpuMod.walk([&](LLVM::LLVMFuncOp funcOp) {
455 if (funcOp->hasAttr(gpu::GPUDialect::getKernelFuncAttrName())) {
456 funcOp.setIntelReqdSubGroupSize(16);
462 SPIRVSerializer serializer(*module, cast<XeVMTargetAttr>(attribute),
466#if !LLVM_HAS_SPIRV_TARGET
467 module->emitError("Cannot run `TargetRegistry::lookupTarget()` for SPIRV "
468 "without having the target built.");
471 std::optional<SmallVector<char, 0>> binary = serializer.run();
474 return gpu::SerializedObject{std::move(*binary)};
476 module->emitError("Unsupported XeVM target triple: ") << xeTarget.getTriple();
481XeVMTargetAttrImpl::createObject(Attribute attribute, Operation *module,
482 const mlir::gpu::SerializedObject &
object,
483 const gpu::TargetOptions &
options)
const {
485 gpu::CompilationTarget format =
options.getCompilationTarget();
486 auto xeTarget = cast<XeVMTargetAttr>(attribute);
487 SmallVector<NamedAttribute, 2> properties;
488 if (format == gpu::CompilationTarget::Assembly)
489 properties.push_back(
490 builder.getNamedAttr(
"O", builder.getI32IntegerAttr(xeTarget.getO())));
492 DictionaryAttr objectProps;
493 if (!properties.empty())
494 objectProps = builder.getDictionaryAttr(properties);
496 return builder.getAttr<gpu::ObjectAttr>(
498 builder.getStringAttr(
499 StringRef(
object.getObject().data(),
object.getObject().size())),
500 objectProps,
nullptr);
static llvm::ManagedStatic< PassManagerOptions > options
MLIRContext * getContext() const
Return the context this attribute belongs to.
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
LogicalResult loadBitcodeFilesFromList(llvm::LLVMContext &context, ArrayRef< Attribute > librariesToLink, SmallVector< std::unique_ptr< llvm::Module > > &llvmModules, bool failureOnError=true)
Loads multiple bitcode files.
virtual FailureOr< SmallVector< char, 0 > > moduleToObject(llvm::Module &llvmModule)
Serializes the LLVM IR bitcode to an object file, by default it serializes to LLVM bitcode.
Operation & getOperation()
Returns the operation being serialized.
ModuleToObject(Operation &module, StringRef triple, StringRef chip, StringRef features={}, int optLevel=3, function_ref< void(llvm::Module &)> initialLlvmIRCallback={}, function_ref< void(llvm::Module &)> linkedLlvmIRCallback={}, function_ref< void(llvm::Module &)> optimizedLlvmIRCallback={}, function_ref< void(StringRef)> isaCallback={})
Operation & module
Module to transform to a binary object.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
MLIRContext is the top-level object for a collection of MLIR operations.
void appendDialectRegistry(const DialectRegistry ®istry)
Append the contents of the given dialect registry to the registry associated with this context.
Operation is the basic unit of execution within MLIR.
static WalkResult advance()
static WalkResult interrupt()
This class serves as an opaque interface for passing options to the TargetAttrInterface methods.
Base class for all XeVM serializations from GPU modules into binary strings.
gpu::TargetOptions targetOptions
GPU compilation target options.
XeVMTargetAttr getTarget() const
Returns the target attribute.
FailureOr< SmallVector< char, 0 > > compileToBinary(StringRef asmStr, StringRef inputFormat)
Compiles to native code using ocloc.
std::optional< SmallVector< std::unique_ptr< llvm::Module > > > loadBitcodeFiles(llvm::Module &module) override
Loads the bitcode files in librariesToLink.
SmallVector< Attribute > librariesToLink
List of LLVM bitcode to link into after translation to LLVM IR.
SerializeGPUModuleBase(Operation &module, XeVMTargetAttr target, const gpu::TargetOptions &targetOptions={})
XeVMTargetAttr xeTarget
XeVM Target attribute.
std::optional< std::string > findTool(StringRef tool)
Returns the path to the tool used for serialization.
gpu::GPUModuleOp getGPUModuleOp()
Returns the gpu module being serialized.
void registerXeVMTargetInterfaceExternalModels(mlir::DialectRegistry ®istry)
Registers the TargetAttrInterface for the #xevm.target attribute in the given registry.
Include the generated interface declarations.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.