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// From llvm/lib/Target/SPIRV
43#if LLVM_HAS_SPIRV_TARGET
44#include "MCTargetDesc/SPIRVBaseInfo.h"
45#include "SPIRVCommandLine.h"
46#include "SPIRVSubtarget.h"
47#include "SPIRVTargetMachine.h"
48#endif
49
50#include <cstdint>
51#include <cstdlib>
52
53using namespace mlir;
54using namespace mlir::xevm;
55
56namespace {
57// XeVM implementation of the gpu:TargetAttrInterface.
58class XeVMTargetAttrImpl
59 : public gpu::TargetAttrInterface::FallbackModel<XeVMTargetAttrImpl> {
60public:
61 std::optional<mlir::gpu::SerializedObject>
62 serializeToObject(Attribute attribute, Operation *module,
63 const gpu::TargetOptions &options) const;
64
65 Attribute createObject(Attribute attribute, Operation *module,
66 const mlir::gpu::SerializedObject &object,
67 const gpu::TargetOptions &options) const;
68};
69} // namespace
70
72 DialectRegistry &registry) {
73 registry.addExtension(+[](MLIRContext *ctx, XeVMDialect *dialect) {
74 XeVMTargetAttr::attachInterface<XeVMTargetAttrImpl>(*ctx);
75 });
76}
77
84
86 Operation &module, XeVMTargetAttr xeTarget,
88 : ModuleToObject(module, xeTarget.getTriple(), "", {}, xeTarget.getO()),
89 xeTarget(xeTarget), librariesToLink(targetOptions.getLibrariesToLink()),
90 targetOptions(targetOptions) {
91 if (xeTarget.getLinkFiles())
92 librariesToLink.append(xeTarget.getLinkFiles().begin(),
93 xeTarget.getLinkFiles().end());
94}
95
96XeVMTargetAttr SerializeGPUModuleBase::getTarget() const { return xeTarget; }
97
98std::optional<SmallVector<std::unique_ptr<llvm::Module>>>
100 if (librariesToLink.empty())
103 if (failed(loadBitcodeFilesFromList(module.getContext(), librariesToLink,
104 bcFiles)))
105 return std::nullopt;
106 return std::move(bcFiles);
107}
108
110 return dyn_cast<gpu::GPUModuleOp>(&SerializeGPUModuleBase::getOperation());
111}
112
113// There is 1 way to finalize IL to native code: IGC
114// There are 2 ways to access IGC: AOT (ocloc) and JIT (L0 runtime).
115// - L0 runtime consumes IL and is external to MLIR codebase (rt wrappers).
116// - `ocloc` tool can be "queried" from within MLIR.
117FailureOr<SmallVector<char, 0>>
119 StringRef inputFormat) {
120 using TmpFile = std::pair<llvm::SmallString<128>, llvm::FileRemover>;
121 // Find the `ocloc` tool.
122 std::optional<std::string> oclocCompiler = findTool("ocloc");
123 if (!oclocCompiler)
124 return failure();
125 Location loc = getGPUModuleOp().getLoc();
126 std::string basename = llvm::formatv(
127 "mlir-{0}-{1}-{2}", getGPUModuleOp().getNameAttr().getValue(),
128 getTarget().getTriple(), getTarget().getChip());
129
130 auto createTemp = [&](StringRef name,
131 StringRef suffix) -> FailureOr<TmpFile> {
132 llvm::SmallString<128> filePath;
133 if (auto ec = llvm::sys::fs::createTemporaryFile(name, suffix, filePath))
134 return getGPUModuleOp().emitError()
135 << "Couldn't create the temp file: `" << filePath
136 << "`, error message: " << ec.message();
137
138 return TmpFile(filePath, llvm::FileRemover(filePath.c_str()));
139 };
140 // Create temp file
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))
145 return failure();
146 // Dump the assembly to a temp file
147 std::error_code ec;
148 {
149 llvm::raw_fd_ostream asmStream(asmFile->first, ec);
150 if (ec)
151 return emitError(loc) << "Couldn't open the file: `" << asmFile->first
152 << "`, error message: " << ec.message();
153
154 asmStream << asmStr;
155 if (asmStream.has_error())
156 return emitError(loc)
157 << "An error occurred while writing the assembly to: `"
158 << asmFile->first << "`.";
159
160 asmStream.flush();
161 }
162 // Set cmd options
163 std::pair<llvm::BumpPtrAllocator, SmallVector<const char *>> cmdOpts =
164 targetOptions.tokenizeCmdOptions();
165 // Example: --gpu-module-to-binary="opts='opt1 opt2'"
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});
171
172// Dump tool invocation commands.
173#define DEBUG_TYPE "serialize-to-binary"
174 LLVM_DEBUG({
175 llvm::dbgs() << "Tool invocation for module: "
176 << getGPUModuleOp().getNameAttr() << "\n";
177 llvm::interleave(oclocArgs, llvm::dbgs(), " ");
178 llvm::dbgs() << "\n";
179 });
180#undef DEBUG_TYPE
181 // Helper function for printing tool error logs.
182 std::string message;
183 auto emitLogError =
184 [&](StringRef toolName) -> FailureOr<SmallVector<char, 0>> {
185 if (message.empty()) {
186 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> toolStderr =
187 llvm::MemoryBuffer::getFile(logFile->first);
188 if (toolStderr)
189 return emitError(loc) << toolName << " invocation failed. Log:\n"
190 << toolStderr->get()->getBuffer();
191 else
192 return emitError(loc) << toolName << " invocation failed.";
193 }
194 return emitError(loc) << toolName
195 << " invocation failed, error message: " << message;
196 };
197 std::optional<StringRef> redirects[] = {
198 std::nullopt,
199 logFile->first,
200 logFile->first,
201 };
202 // Invoke ocloc.
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);
209 if (!binaryBuffer)
210 return emitError(loc) << "Couldn't open the file: `" << binFile->first
211 << "`, error message: "
212 << binaryBuffer.getError().message();
213
214 StringRef bin = (*binaryBuffer)->getBuffer();
215 return SmallVector<char, 0>(bin.begin(), bin.end());
216}
217
218std::optional<std::string> SerializeGPUModuleBase::findTool(StringRef tool) {
219 // 1. Check the toolkit path given in the command line.
220 StringRef pathRef = targetOptions.getToolkitPath();
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();
227 }
228 // 2. Check PATH.
229 if (std::optional<std::string> toolPath =
230 llvm::sys::Process::FindInEnvPath("PATH", tool))
231 return *toolPath;
232
233 getGPUModuleOp().emitError()
234 << "Couldn't find the `" << tool
235 << "` binary. Please specify the toolkit "
236 "path via GpuModuleToBinaryPass or add the compiler to $PATH`.";
237 return std::nullopt;
238}
239
240namespace {
241class SPIRVSerializer : public SerializeGPUModuleBase {
242public:
243 SPIRVSerializer(Operation &module, XeVMTargetAttr xeTarget,
244 const gpu::TargetOptions &targetOptions)
245 : SerializeGPUModuleBase(module, xeTarget, targetOptions) {}
246
247 static void init();
248
249 /// Runs the serialization pipeline, returning `std::nullopt` on error.
250 std::optional<SmallVector<char, 0>> run() override;
251
252protected:
253 /// Serializes the LLVM module to an object format, depending on the
254 /// compilation target selected in target options.
255 FailureOr<SmallVector<char, 0>>
256 moduleToObject(llvm::Module &llvmModule) override;
257
258private:
259 /// Translates the LLVM module to SPIR-V binary using LLVM's
260 /// SPIR-V target.
261 std::optional<std::string>
262 translateToSPIRVBinary(llvm::Module &llvmModule,
263 llvm::TargetMachine &targetMachine);
264};
265} // namespace
266
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();
275#endif
276 });
277}
278
279#if LLVM_HAS_SPIRV_TARGET
280static const std::vector<std::string> getDefaultSPIRVExtensions() {
281 return {
282 "SPV_EXT_relaxed_printf_string_address_space",
283 "SPV_INTEL_cache_controls",
284 "SPV_INTEL_variable_length_array",
285 };
286}
287#endif
288
289std::optional<SmallVector<char, 0>> SPIRVSerializer::run() {
290 // Translate the module to LLVM IR.
291 llvm::LLVMContext llvmContext;
292 std::unique_ptr<llvm::Module> llvmModule = translateToLLVMIR(llvmContext);
293 if (!llvmModule) {
294 getOperation().emitError() << "Failed creating the llvm::Module.";
295 return std::nullopt;
296 }
297
298#define DEBUG_TYPE "serialize-to-llvm"
299 LLVM_DEBUG({
300 llvm::dbgs() << "LLVM IR for module: " << getGPUModuleOp().getNameAttr()
301 << "\n";
302 llvm::dbgs() << *llvmModule << "\n";
303 llvm::dbgs().flush();
304 });
305#undef DEBUG_TYPE
306
307 // Return LLVM IR if the compilation target is `offload`.
308 if (targetOptions.getCompilationTarget() == gpu::CompilationTarget::Offload)
309 return SerializeGPUModuleBase::moduleToObject(*llvmModule);
310
311#if LLVM_HAS_SPIRV_TARGET
312 setDataLayoutAndTriple(*llvmModule);
313
314 // Create the target machine.
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";
320 return std::nullopt;
321 }
322 // Setup allowed SPIR-V extensions.
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;
330 return std::nullopt;
331 }
332
333 llvm::SPIRVTargetMachine *STM =
334 static_cast<llvm::SPIRVTargetMachine *>(*targetMachine);
335 const_cast<llvm::SPIRVSubtarget *>(STM->getSubtargetImpl())
336 ->initAvailableExtensions(AllowedExtIds);
337
338 if (initialLlvmIRCallback)
339 initialLlvmIRCallback(*llvmModule);
340
341 // Link bitcode files.
342 handleModulePreLink(*llvmModule);
343 {
344 auto libs = loadBitcodeFiles(*llvmModule);
345 if (!libs)
346 return std::nullopt;
347 if (!libs->empty())
348 if (failed(linkFiles(*llvmModule, std::move(*libs))))
349 return std::nullopt;
350 handleModulePostLink(*llvmModule);
351 }
352
353 if (linkedLlvmIRCallback)
354 linkedLlvmIRCallback(*llvmModule);
355
356 // Optimize the module.
357 if (failed(optimizeModule(*llvmModule, optLevel)))
358 return std::nullopt;
359
360 if (optimizedLlvmIRCallback)
361 optimizedLlvmIRCallback(*llvmModule);
362
363 // Return the serialized object.
364 return moduleToObject(*llvmModule);
365#else
366 getOperation().emitError("The `SPIRV` target was not built. Please enable "
367 "it when building LLVM.");
368 return std::nullopt;
369#endif // LLVM_HAS_SPIRV_TARGET
370}
371
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";
379
380 // Return SPIRV if the compilation target is `assembly`.
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";
390
391#define DEBUG_TYPE "serialize-to-isa"
392 LLVM_DEBUG({
393 llvm::dbgs() << "SPIR-V for module: " << getGPUModuleOp().getNameAttr()
394 << "\n";
395 llvm::dbgs() << *serializedISA << "\n";
396 llvm::dbgs().flush();
397 });
398#undef DEBUG_TYPE
399
400 // Make sure to include the null terminator.
401 StringRef bin(serializedISA->c_str(), serializedISA->size() + 1);
402 return SmallVector<char, 0>(bin.begin(), bin.end());
403 }
404
405 // Level zero runtime is set up to accept SPIR-V binary
406 // translateToSPIRVBinary translates the LLVM module to SPIR-V binary
407 // using LLVM's SPIRV target.
408 // compileToBinary can be used in the future if level zero runtime
409 // implementation switches to native XeVM binary format.
410 std::optional<std::string> serializedSPIRVBinary =
411 translateToSPIRVBinary(llvmModule, **targetMachine);
412 if (!serializedSPIRVBinary)
413 return getGPUModuleOp().emitError()
414 << "Failed translating the module to Binary.";
415
416 if (serializedSPIRVBinary->size() % 4)
417 return getGPUModuleOp().emitError()
418 << "SPIRV code size must be a multiple of 4.";
419
420 StringRef bin(serializedSPIRVBinary->c_str(), serializedSPIRVBinary->size());
421 return SmallVector<char, 0>(bin.begin(), bin.end());
422}
423
424std::optional<std::string>
425SPIRVSerializer::translateToSPIRVBinary(llvm::Module &llvmModule,
426 llvm::TargetMachine &targetMachine) {
427 std::string targetISA;
428 llvm::raw_string_ostream stream(targetISA);
429
430 { // Drop pstream after this to prevent the ISA from being stuck buffering
431 llvm::buffer_ostream pstream(stream);
432 llvm::legacy::PassManager codegenPasses;
433 if (targetMachine.addPassesToEmitFile(codegenPasses, pstream, nullptr,
434 llvm::CodeGenFileType::ObjectFile))
435 return std::nullopt;
436
437 codegenPasses.run(llvmModule);
438 }
439 return targetISA;
440}
441
442std::optional<mlir::gpu::SerializedObject>
443XeVMTargetAttrImpl::serializeToObject(Attribute attribute, Operation *module,
444 const gpu::TargetOptions &options) const {
445 if (!module)
446 return std::nullopt;
447 auto gpuMod = dyn_cast<gpu::GPUModuleOp>(module);
448 if (!gpuMod) {
449 module->emitError("expected to be a gpu.module op");
450 return std::nullopt;
451 }
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);
457 return WalkResult::interrupt();
458 }
459 return WalkResult::advance();
460 });
461
462 SPIRVSerializer serializer(*module, cast<XeVMTargetAttr>(attribute),
463 options);
464 serializer.init();
465
466#if !LLVM_HAS_SPIRV_TARGET
467 module->emitError("Cannot run `TargetRegistry::lookupTarget()` for SPIRV "
468 "without having the target built.");
469#endif
470
471 std::optional<SmallVector<char, 0>> binary = serializer.run();
472 if (!binary)
473 return std::nullopt;
474 return gpu::SerializedObject{std::move(*binary)};
475 }
476 module->emitError("Unsupported XeVM target triple: ") << xeTarget.getTriple();
477 return std::nullopt;
478}
479
480Attribute
481XeVMTargetAttrImpl::createObject(Attribute attribute, Operation *module,
482 const mlir::gpu::SerializedObject &object,
483 const gpu::TargetOptions &options) const {
484 Builder builder(attribute.getContext());
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())));
491
492 DictionaryAttr objectProps;
493 if (!properties.empty())
494 objectProps = builder.getDictionaryAttr(properties);
495
496 return builder.getAttr<gpu::ObjectAttr>(
497 attribute, format,
498 builder.getStringAttr(
499 StringRef(object.getObject().data(), object.getObject().size())),
500 objectProps, /*kernels=*/nullptr);
501}
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:96
FailureOr< SmallVector< char, 0 > > compileToBinary(StringRef asmStr, StringRef inputFormat)
Compiles to native code using ocloc.
Definition Target.cpp:118
std::optional< SmallVector< std::unique_ptr< llvm::Module > > > loadBitcodeFiles(llvm::Module &module) override
Loads the bitcode files in librariesToLink.
Definition Target.cpp:99
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:85
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:218
gpu::GPUModuleOp getGPUModuleOp()
Returns the gpu module being serialized.
Definition Target.cpp:109
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:71
Include the generated interface declarations.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.