MLIR 23.0.0git
Target.cpp
Go to the documentation of this file.
1//===- Target.cpp - MLIR LLVM XeVM target compilation -----------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This files defines XeVM target related functions including registration
10// calls for the `#xevm.target` compilation attribute.
11//
12//===----------------------------------------------------------------------===//
13
15
28#include "llvm/IR/LegacyPassManager.h"
29#include "llvm/Target/TargetMachine.h"
30
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"
42
43#include <cstdint>
44#include <cstdlib>
45
46using namespace mlir;
47using namespace mlir::xevm;
48
49namespace {
50// XeVM implementation of the gpu:TargetAttrInterface.
51class XeVMTargetAttrImpl
52 : public gpu::TargetAttrInterface::FallbackModel<XeVMTargetAttrImpl> {
53public:
54 std::optional<mlir::gpu::SerializedObject>
55 serializeToObject(Attribute attribute, Operation *module,
56 const gpu::TargetOptions &options) const;
57
58 Attribute createObject(Attribute attribute, Operation *module,
59 const mlir::gpu::SerializedObject &object,
60 const gpu::TargetOptions &options) const;
61};
62} // namespace
63
65 DialectRegistry &registry) {
66 registry.addExtension(+[](MLIRContext *ctx, XeVMDialect *dialect) {
67 XeVMTargetAttr::attachInterface<XeVMTargetAttrImpl>(*ctx);
68 });
69}
70
77
79 Operation &module, XeVMTargetAttr xeTarget,
81 : ModuleToObject(module, xeTarget.getTriple(), "", {}, xeTarget.getO()),
82 xeTarget(xeTarget), librariesToLink(targetOptions.getLibrariesToLink()),
83 targetOptions(targetOptions) {
84 if (xeTarget.getLinkFiles())
85 librariesToLink.append(xeTarget.getLinkFiles().begin(),
86 xeTarget.getLinkFiles().end());
87}
88
89XeVMTargetAttr SerializeGPUModuleBase::getTarget() const { return xeTarget; }
90
91std::optional<SmallVector<std::unique_ptr<llvm::Module>>>
93 if (librariesToLink.empty())
96 if (failed(loadBitcodeFilesFromList(module.getContext(), librariesToLink,
97 bcFiles)))
98 return std::nullopt;
99 return std::move(bcFiles);
100}
101
103 return dyn_cast<gpu::GPUModuleOp>(&SerializeGPUModuleBase::getOperation());
104}
105
106// There is 1 way to finalize IL to native code: IGC
107// There are 2 ways to access IGC: AOT (ocloc) and JIT (L0 runtime).
108// - L0 runtime consumes IL and is external to MLIR codebase (rt wrappers).
109// - `ocloc` tool can be "queried" from within MLIR.
110FailureOr<SmallVector<char, 0>> SerializeGPUModuleBase::compileToBinary(
111 StringRef asmStr, StringRef inputFormat = "-spirv_input") {
112 using TmpFile = std::pair<llvm::SmallString<128>, llvm::FileRemover>;
113 // Find the `ocloc` tool.
114 std::optional<std::string> oclocCompiler = findTool("ocloc");
115 if (!oclocCompiler)
116 return failure();
117 Location loc = getGPUModuleOp().getLoc();
118 std::string basename = llvm::formatv(
119 "mlir-{0}-{1}-{2}", getGPUModuleOp().getNameAttr().getValue(),
120 getTarget().getTriple(), getTarget().getChip());
121
122 auto createTemp = [&](StringRef name,
123 StringRef suffix) -> FailureOr<TmpFile> {
124 llvm::SmallString<128> filePath;
125 if (auto ec = llvm::sys::fs::createTemporaryFile(name, suffix, filePath))
126 return getGPUModuleOp().emitError()
127 << "Couldn't create the temp file: `" << filePath
128 << "`, error message: " << ec.message();
129
130 return TmpFile(filePath, llvm::FileRemover(filePath.c_str()));
131 };
132 // Create temp file
133 FailureOr<TmpFile> asmFile = createTemp(basename, "asm");
134 FailureOr<TmpFile> binFile = createTemp(basename, "");
135 FailureOr<TmpFile> logFile = createTemp(basename, "log");
136 if (failed(logFile) || failed(asmFile) || failed(binFile))
137 return failure();
138 // Dump the assembly to a temp file
139 std::error_code ec;
140 {
141 llvm::raw_fd_ostream asmStream(asmFile->first, ec);
142 if (ec)
143 return emitError(loc) << "Couldn't open the file: `" << asmFile->first
144 << "`, error message: " << ec.message();
145
146 asmStream << asmStr;
147 if (asmStream.has_error())
148 return emitError(loc)
149 << "An error occurred while writing the assembly to: `"
150 << asmFile->first << "`.";
151
152 asmStream.flush();
153 }
154 // Set cmd options
155 std::pair<llvm::BumpPtrAllocator, SmallVector<const char *>> cmdOpts =
156 targetOptions.tokenizeCmdOptions();
157 // Example: --gpu-module-to-binary="opts='opt1 opt2'"
158 const std::string cmdOptsStr = "\"" + llvm::join(cmdOpts.second, " ") + "\"";
160 {"ocloc", "compile", "-file", asmFile->first, inputFormat, "-device",
161 getTarget().getChip(), "-output", binFile->first, "-output_no_suffix",
162 "-options", cmdOptsStr});
163
164// Dump tool invocation commands.
165#define DEBUG_TYPE "serialize-to-binary"
166 LLVM_DEBUG({
167 llvm::dbgs() << "Tool invocation for module: "
168 << getGPUModuleOp().getNameAttr() << "\n";
169 llvm::interleave(oclocArgs, llvm::dbgs(), " ");
170 llvm::dbgs() << "\n";
171 });
172#undef DEBUG_TYPE
173 // Helper function for printing tool error logs.
174 std::string message;
175 auto emitLogError =
176 [&](StringRef toolName) -> FailureOr<SmallVector<char, 0>> {
177 if (message.empty()) {
178 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> toolStderr =
179 llvm::MemoryBuffer::getFile(logFile->first);
180 if (toolStderr)
181 return emitError(loc) << toolName << " invocation failed. Log:\n"
182 << toolStderr->get()->getBuffer();
183 else
184 return emitError(loc) << toolName << " invocation failed.";
185 }
186 return emitError(loc) << toolName
187 << " invocation failed, error message: " << message;
188 };
189 std::optional<StringRef> redirects[] = {
190 std::nullopt,
191 logFile->first,
192 logFile->first,
193 };
194 // Invoke ocloc.
195 if (llvm::sys::ExecuteAndWait(oclocCompiler.value(), oclocArgs, std::nullopt,
196 redirects, 0, 0, &message))
197 return emitLogError("`ocloc`");
198 binFile->first.append(".bin");
199 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> binaryBuffer =
200 llvm::MemoryBuffer::getFile(binFile->first);
201 if (!binaryBuffer)
202 return emitError(loc) << "Couldn't open the file: `" << binFile->first
203 << "`, error message: "
204 << binaryBuffer.getError().message();
205
206 StringRef bin = (*binaryBuffer)->getBuffer();
207 return SmallVector<char, 0>(bin.begin(), bin.end());
208}
209
210std::optional<std::string> SerializeGPUModuleBase::findTool(StringRef tool) {
211 // 1. Check the toolkit path given in the command line.
212 StringRef pathRef = targetOptions.getToolkitPath();
214 if (!pathRef.empty()) {
215 path.insert(path.begin(), pathRef.begin(), pathRef.end());
216 llvm::sys::path::append(path, "bin", tool);
217 if (llvm::sys::fs::can_execute(path))
218 return StringRef(path.data(), path.size()).str();
219 }
220 // 2. Check PATH.
221 if (std::optional<std::string> toolPath =
222 llvm::sys::Process::FindInEnvPath("PATH", tool))
223 return *toolPath;
224
225 getGPUModuleOp().emitError()
226 << "Couldn't find the `" << tool
227 << "` binary. Please specify the toolkit "
228 "path via GpuModuleToBinaryPass or add the compiler to $PATH`.";
229 return std::nullopt;
230}
231
232namespace {
233class SPIRVSerializer : public SerializeGPUModuleBase {
234public:
235 SPIRVSerializer(Operation &module, XeVMTargetAttr xeTarget,
236 const gpu::TargetOptions &targetOptions)
237 : SerializeGPUModuleBase(module, xeTarget, targetOptions) {}
238
239 static void init();
240
241 /// Serializes the LLVM module to an object format, depending on the
242 /// compilation target selected in target options.
243 FailureOr<SmallVector<char, 0>>
244 moduleToObject(llvm::Module &llvmModule) override;
245
246 /// Runs the serialization pipeline, returning `std::nullopt` on error.
247 std::optional<SmallVector<char, 0>> run() override;
248
249private:
250 /// Translates the LLVM module to SPIR-V binary using LLVM's
251 /// SPIR-V target.
252 std::optional<std::string>
253 translateToSPIRVBinary(llvm::Module &llvmModule,
254 llvm::TargetMachine &targetMachine);
255};
256} // namespace
257
258void SPIRVSerializer::init() {
259 static llvm::once_flag initializeBackendOnce;
260 llvm::call_once(initializeBackendOnce, []() {
261#if LLVM_HAS_SPIRV_TARGET
262 LLVMInitializeSPIRVTarget();
263 LLVMInitializeSPIRVTargetInfo();
264 LLVMInitializeSPIRVTargetMC();
265 LLVMInitializeSPIRVAsmPrinter();
266#endif
267 });
268}
269
270#if LLVM_HAS_SPIRV_TARGET
271static const std::vector<std::string> getDefaultSPIRVExtensions() {
272 return {
273 "SPV_EXT_relaxed_printf_string_address_space",
274 "SPV_INTEL_cache_controls",
275 "SPV_INTEL_variable_length_array",
276 };
277}
278
279namespace llvm {
280class Module;
281
282extern "C" bool
283SPIRVTranslateModule(Module *M, std::string &SpirvObj, std::string &ErrMsg,
284 const std::vector<std::string> &AllowExtNames,
285 const std::vector<std::string> &Opts);
286} // namespace llvm
287#endif
288
289FailureOr<SmallVector<char, 0>>
290SPIRVSerializer::moduleToObject(llvm::Module &llvmModule) {
291#define DEBUG_TYPE "serialize-to-llvm"
292 LLVM_DEBUG({
293 llvm::dbgs() << "LLVM IR for module: " << getGPUModuleOp().getNameAttr()
294 << "\n";
295 llvm::dbgs() << llvmModule << "\n";
296 llvm::dbgs().flush();
297 });
298#undef DEBUG_TYPE
299
300 // Return LLVM IR if the compilation target is `offload`.
301 if (targetOptions.getCompilationTarget() == gpu::CompilationTarget::Offload)
303
304#if !LLVM_HAS_SPIRV_TARGET
305 return getGPUModuleOp()->emitError(
306 "The `SPIRV` target was not built. Please enable "
307 "it when building LLVM.");
308#else
309
310 // Return SPIRV text if the compilation target is `assembly`.
311 // Note: Optimization passes are skipped and SPIRV extensions are
312 // not supported in this mode.
313 if (targetOptions.getCompilationTarget() ==
314 gpu::CompilationTarget::Assembly) {
315 FailureOr<llvm::TargetMachine *> targetMachine = getOrCreateTargetMachine();
316 if (failed(targetMachine))
317 return getGPUModuleOp().emitError()
318 << "Target Machine unavailable for triple " << triple
319 << ", can't optimize with LLVM\n";
320
321 FailureOr<SmallString<0>> serializedISA =
322 translateModuleToISA(llvmModule, **targetMachine,
323 [&]() { return getGPUModuleOp().emitError(); });
324 if (failed(serializedISA))
325 return getGPUModuleOp().emitError()
326 << "Failed translating the module to ISA." << triple
327 << ", can't compile with LLVM\n";
328
329#define DEBUG_TYPE "serialize-to-isa"
330 LLVM_DEBUG({
331 llvm::dbgs() << "SPIR-V for module: " << getGPUModuleOp().getNameAttr()
332 << "\n";
333 llvm::dbgs() << *serializedISA << "\n";
334 llvm::dbgs().flush();
335 });
336#undef DEBUG_TYPE
337
338 // Make sure to include the null terminator.
339 StringRef bin(serializedISA->c_str(), serializedISA->size() + 1);
340 return SmallVector<char, 0>(bin.begin(), bin.end());
341 }
342
343 // Binary generation path for SPIR-V target. Optimization and SPIR-V
344 // extensions are enabled in this path. In this path, first the SPIR-V binary
345 // is generated directly using the SPIR-V backends `SPIRVTranslateModule` API.
346 // Resultant SPIR-V is then fed to `ocloc` compiler (Intel's OpenCL Offline
347 // Compiler) to generate the final binary for Intel GPUs.
348
349 // @TODO: This part is doing exact same SPIR-V code generation as the previous
350 // section under (targetOptions.getCompilationTarget() ==
351 // gpu::CompilationTarget::Assembly) condition. Only execption is, it enables
352 // optimization and SPIRV extensions support for SPIRV binary output. We need
353 // to decide which one do we use for our SPIRV code generation, and remove the
354 // other one to avoid confusion. For now, we keep both to have more
355 // flexibility for testing and comparison.
356
357 std::string serializedSPIRVBinary;
358 std::string ErrMsg;
359 std::vector<std::string> Opts;
360 Opts.push_back(triple.str());
361 Opts.push_back(std::to_string(optLevel));
362
363 bool success =
364 SPIRVTranslateModule(&llvmModule, serializedSPIRVBinary, ErrMsg,
365 getDefaultSPIRVExtensions(), Opts);
366
367 if (!success)
368 return getGPUModuleOp().emitError()
369 << "Failed translating the module to Binary."
370 << "Error message: " << ErrMsg;
371
372 if (serializedSPIRVBinary.size() % 4)
373 return getGPUModuleOp().emitError()
374 << "SPIRV code size must be a multiple of 4.";
375
376 StringRef spirvBin(serializedSPIRVBinary.c_str(),
377 serializedSPIRVBinary.size());
378 return compileToBinary(spirvBin, "-spirv_input");
379#endif // LLVM_HAS_SPIRV_TARGET
380}
381
382std::optional<SmallVector<char, 0>> SPIRVSerializer::run() {
383 // Translate the module to LLVM IR.
384 llvm::LLVMContext llvmContext;
385 std::unique_ptr<llvm::Module> llvmModule = translateToLLVMIR(llvmContext);
386 if (!llvmModule) {
387 getOperation().emitError() << "Failed creating the llvm::Module.";
388 return std::nullopt;
389 }
390 setDataLayoutAndTriple(*llvmModule);
391
392 if (initialLlvmIRCallback)
393 initialLlvmIRCallback(*llvmModule);
394
395 // Link bitcode files.
396 handleModulePreLink(*llvmModule);
397 {
398 auto libs = loadBitcodeFiles(*llvmModule);
399 if (!libs)
400 return std::nullopt;
401 if (!libs->empty())
402 if (failed(linkFiles(*llvmModule, std::move(*libs))))
403 return std::nullopt;
404 handleModulePostLink(*llvmModule);
405 }
406
407 if (linkedLlvmIRCallback)
408 linkedLlvmIRCallback(*llvmModule);
409
410 // Return the serialized object.
411 return moduleToObject(*llvmModule);
412}
413
414std::optional<std::string>
415SPIRVSerializer::translateToSPIRVBinary(llvm::Module &llvmModule,
416 llvm::TargetMachine &targetMachine) {
417 std::string targetISA;
418 llvm::raw_string_ostream stream(targetISA);
419
420 { // Drop pstream after this to prevent the ISA from being stuck buffering
421 llvm::buffer_ostream pstream(stream);
422 llvm::legacy::PassManager codegenPasses;
423 if (targetMachine.addPassesToEmitFile(codegenPasses, pstream, nullptr,
424 llvm::CodeGenFileType::ObjectFile))
425 return std::nullopt;
426
427 codegenPasses.run(llvmModule);
428 }
429 return targetISA;
430}
431
432std::optional<mlir::gpu::SerializedObject>
433XeVMTargetAttrImpl::serializeToObject(Attribute attribute, Operation *module,
434 const gpu::TargetOptions &options) const {
435 if (!module)
436 return std::nullopt;
437 auto gpuMod = dyn_cast<gpu::GPUModuleOp>(module);
438 if (!gpuMod) {
439 module->emitError("expected to be a gpu.module op");
440 return std::nullopt;
441 }
442 auto xeTarget = cast<XeVMTargetAttr>(attribute);
443 if (xeTarget.getTriple().starts_with("spirv")) {
444 gpuMod.walk([&](LLVM::LLVMFuncOp funcOp) {
445 if (funcOp->hasAttr(gpu::GPUDialect::getKernelFuncAttrName())) {
446 funcOp.setIntelReqdSubGroupSize(16);
447 return WalkResult::interrupt();
448 }
449 return WalkResult::advance();
450 });
451
452 SPIRVSerializer serializer(*module, cast<XeVMTargetAttr>(attribute),
453 options);
454 serializer.init();
455
456#if !LLVM_HAS_SPIRV_TARGET
457 module->emitError("Cannot run `TargetRegistry::lookupTarget()` for SPIRV "
458 "without having the target built.");
459#endif
460
461 std::optional<SmallVector<char, 0>> binary = serializer.run();
462 if (!binary)
463 return std::nullopt;
464 return gpu::SerializedObject{std::move(*binary)};
465 }
466 module->emitError("Unsupported XeVM target triple: ") << xeTarget.getTriple();
467 return std::nullopt;
468}
469
470Attribute
471XeVMTargetAttrImpl::createObject(Attribute attribute, Operation *module,
472 const mlir::gpu::SerializedObject &object,
473 const gpu::TargetOptions &options) const {
474 Builder builder(attribute.getContext());
475 gpu::CompilationTarget format = options.getCompilationTarget();
476 auto xeTarget = cast<XeVMTargetAttr>(attribute);
477 SmallVector<NamedAttribute, 2> properties;
478 if (format == gpu::CompilationTarget::Assembly)
479 properties.push_back(
480 builder.getNamedAttr("O", builder.getI32IntegerAttr(xeTarget.getO())));
481
482 DictionaryAttr objectProps;
483 if (!properties.empty())
484 objectProps = builder.getDictionaryAttr(properties);
485
486 return builder.getAttr<gpu::ObjectAttr>(
487 attribute, format,
488 builder.getStringAttr(
489 StringRef(object.getObject().data(), object.getObject().size())),
490 objectProps, /*kernels=*/nullptr);
491}
return success()
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...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
void appendDialectRegistry(const DialectRegistry &registry)
Append the contents of the given dialect registry to the registry associated with this context.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
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.
Definition Utils.h:27
gpu::TargetOptions targetOptions
GPU compilation target options.
Definition Utils.h:58
XeVMTargetAttr getTarget() const
Returns the target attribute.
Definition Target.cpp:89
FailureOr< SmallVector< char, 0 > > compileToBinary(StringRef asmStr, StringRef inputFormat)
Compiles to native code using ocloc.
Definition Target.cpp:110
std::optional< SmallVector< std::unique_ptr< llvm::Module > > > loadBitcodeFiles(llvm::Module &module) override
Loads the bitcode files in librariesToLink.
Definition Target.cpp:92
SmallVector< Attribute > librariesToLink
List of LLVM bitcode to link into after translation to LLVM IR.
Definition Utils.h:52
SerializeGPUModuleBase(Operation &module, XeVMTargetAttr target, const gpu::TargetOptions &targetOptions={})
Definition Target.cpp:78
XeVMTargetAttr xeTarget
XeVM Target attribute.
Definition Utils.h:48
std::optional< std::string > findTool(StringRef tool)
Returns the path to the tool used for serialization.
Definition Target.cpp:210
gpu::GPUModuleOp getGPUModuleOp()
Returns the gpu module being serialized.
Definition Target.cpp:102
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
void registerXeVMTargetInterfaceExternalModels(mlir::DialectRegistry &registry)
Registers the TargetAttrInterface for the #xevm.target attribute in the given registry.
Definition Target.cpp:64
Include the generated interface declarations.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.