MLIR 22.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<SmallVector<char, 0>>
55 serializeToObject(Attribute attribute, Operation *module,
56 const gpu::TargetOptions &options) const;
57
58 Attribute createObject(Attribute attribute, Operation *module,
59 const SmallVector<char, 0> &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.
110std::optional<SmallVector<char, 0>>
112 StringRef inputFormat) {
113 using TmpFile = std::pair<llvm::SmallString<128>, llvm::FileRemover>;
114 // Find the `ocloc` tool.
115 std::optional<std::string> oclocCompiler = findTool("ocloc");
116 if (!oclocCompiler)
117 return std::nullopt;
118 Location loc = getGPUModuleOp().getLoc();
119 std::string basename = llvm::formatv(
120 "mlir-{0}-{1}-{2}", getGPUModuleOp().getNameAttr().getValue(),
121 getTarget().getTriple(), getTarget().getChip());
122
123 auto createTemp = [&](StringRef name,
124 StringRef suffix) -> std::optional<TmpFile> {
125 llvm::SmallString<128> filePath;
126 if (auto ec = llvm::sys::fs::createTemporaryFile(name, suffix, filePath)) {
127 getGPUModuleOp().emitError()
128 << "Couldn't create the temp file: `" << filePath
129 << "`, error message: " << ec.message();
130 return std::nullopt;
131 }
132 return TmpFile(filePath, llvm::FileRemover(filePath.c_str()));
133 };
134 // Create temp file
135 std::optional<TmpFile> asmFile = createTemp(basename, "asm");
136 std::optional<TmpFile> binFile = createTemp(basename, "");
137 std::optional<TmpFile> logFile = createTemp(basename, "log");
138 if (!logFile || !asmFile || !binFile)
139 return std::nullopt;
140 // Dump the assembly to a temp file
141 std::error_code ec;
142 {
143 llvm::raw_fd_ostream asmStream(asmFile->first, ec);
144 if (ec) {
145 emitError(loc) << "Couldn't open the file: `" << asmFile->first
146 << "`, error message: " << ec.message();
147 return std::nullopt;
148 }
149 asmStream << asmStr;
150 if (asmStream.has_error()) {
151 emitError(loc) << "An error occurred while writing the assembly to: `"
152 << asmFile->first << "`.";
153 return std::nullopt;
154 }
155 asmStream.flush();
156 }
157 // Set cmd options
158 std::pair<llvm::BumpPtrAllocator, SmallVector<const char *>> cmdOpts =
159 targetOptions.tokenizeCmdOptions();
160 // Example: --gpu-module-to-binary="opts='opt1 opt2'"
161 const std::string cmdOptsStr = "\"" + llvm::join(cmdOpts.second, " ") + "\"";
163 {"ocloc", "compile", "-file", asmFile->first, inputFormat, "-device",
164 getTarget().getChip(), "-output", binFile->first, "-output_no_suffix",
165 "-options", cmdOptsStr});
166
167// Dump tool invocation commands.
168#define DEBUG_TYPE "serialize-to-binary"
169 LLVM_DEBUG({
170 llvm::dbgs() << "Tool invocation for module: "
171 << getGPUModuleOp().getNameAttr() << "\n";
172 llvm::interleave(oclocArgs, llvm::dbgs(), " ");
173 llvm::dbgs() << "\n";
174 });
175#undef DEBUG_TYPE
176 // Helper function for printing tool error logs.
177 std::string message;
178 auto emitLogError =
179 [&](StringRef toolName) -> std::optional<SmallVector<char, 0>> {
180 if (message.empty()) {
181 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> toolStderr =
182 llvm::MemoryBuffer::getFile(logFile->first);
183 if (toolStderr)
184 emitError(loc) << toolName << " invocation failed. Log:\n"
185 << toolStderr->get()->getBuffer();
186 else
187 emitError(loc) << toolName << " invocation failed.";
188 return std::nullopt;
189 }
190 emitError(loc) << toolName
191 << " invocation failed, error message: " << message;
192 return std::nullopt;
193 };
194 std::optional<StringRef> redirects[] = {
195 std::nullopt,
196 logFile->first,
197 logFile->first,
198 };
199 // Invoke ocloc.
200 if (llvm::sys::ExecuteAndWait(oclocCompiler.value(), oclocArgs, std::nullopt,
201 redirects, 0, 0, &message))
202 return emitLogError("`ocloc`");
203 binFile->first.append(".bin");
204 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> binaryBuffer =
205 llvm::MemoryBuffer::getFile(binFile->first);
206 if (!binaryBuffer) {
207 emitError(loc) << "Couldn't open the file: `" << binFile->first
208 << "`, error message: " << binaryBuffer.getError().message();
209 return std::nullopt;
210 }
211 StringRef bin = (*binaryBuffer)->getBuffer();
212 return SmallVector<char, 0>(bin.begin(), bin.end());
213}
214
215std::optional<std::string> SerializeGPUModuleBase::findTool(StringRef tool) {
216 // 1. Check the toolkit path given in the command line.
217 StringRef pathRef = targetOptions.getToolkitPath();
219 if (!pathRef.empty()) {
220 path.insert(path.begin(), pathRef.begin(), pathRef.end());
221 llvm::sys::path::append(path, "bin", tool);
222 if (llvm::sys::fs::can_execute(path))
223 return StringRef(path.data(), path.size()).str();
224 }
225 // 2. Check PATH.
226 if (std::optional<std::string> toolPath =
227 llvm::sys::Process::FindInEnvPath("PATH", tool))
228 return *toolPath;
229
230 getGPUModuleOp().emitError()
231 << "Couldn't find the `" << tool
232 << "` binary. Please specify the toolkit "
233 "path via GpuModuleToBinaryPass or add the compiler to $PATH`.";
234 return std::nullopt;
235}
236
237namespace {
238class SPIRVSerializer : public SerializeGPUModuleBase {
239public:
240 SPIRVSerializer(Operation &module, XeVMTargetAttr xeTarget,
241 const gpu::TargetOptions &targetOptions)
242 : SerializeGPUModuleBase(module, xeTarget, targetOptions) {}
243
244 static void init();
245
246 /// Serializes the LLVM module to an object format, depending on the
247 /// compilation target selected in target options.
248 std::optional<SmallVector<char, 0>>
249 moduleToObject(llvm::Module &llvmModule) override;
250
251private:
252 /// Translates the LLVM module to SPIR-V binary using LLVM's
253 /// SPIR-V target.
254 std::optional<std::string>
255 translateToSPIRVBinary(llvm::Module &llvmModule,
256 llvm::TargetMachine &targetMachine);
257};
258} // namespace
259
260void SPIRVSerializer::init() {
261 static llvm::once_flag initializeBackendOnce;
262 llvm::call_once(initializeBackendOnce, []() {
263#if LLVM_HAS_SPIRV_TARGET
264 LLVMInitializeSPIRVTarget();
265 LLVMInitializeSPIRVTargetInfo();
266 LLVMInitializeSPIRVTargetMC();
267 LLVMInitializeSPIRVAsmPrinter();
268#endif
269 });
270}
271
272std::optional<SmallVector<char, 0>>
273SPIRVSerializer::moduleToObject(llvm::Module &llvmModule) {
274#define DEBUG_TYPE "serialize-to-llvm"
275 LLVM_DEBUG({
276 llvm::dbgs() << "LLVM IR for module: " << getGPUModuleOp().getNameAttr()
277 << "\n";
278 llvm::dbgs() << llvmModule << "\n";
279 llvm::dbgs().flush();
280 });
281#undef DEBUG_TYPE
282
283 // Return LLVM IR if the compilation target is `offload`.
284 if (targetOptions.getCompilationTarget() == gpu::CompilationTarget::Offload)
286
287#if !LLVM_HAS_SPIRV_TARGET
288 getGPUModuleOp()->emitError("The `SPIRV` target was not built. Please enable "
289 "it when building LLVM.");
290 return std::nullopt;
291#endif // LLVM_HAS_SPIRV_TARGET
292
293 std::optional<llvm::TargetMachine *> targetMachine =
294 getOrCreateTargetMachine();
295 if (!targetMachine) {
296 getGPUModuleOp().emitError() << "Target Machine unavailable for triple "
297 << triple << ", can't optimize with LLVM\n";
298 return std::nullopt;
299 }
300
301 // Return SPIRV if the compilation target is `assembly`.
302 if (targetOptions.getCompilationTarget() ==
303 gpu::CompilationTarget::Assembly) {
304 std::optional<std::string> serializedISA =
305 translateToISA(llvmModule, **targetMachine);
306 if (!serializedISA) {
307 getGPUModuleOp().emitError() << "Failed translating the module to ISA."
308 << triple << ", can't compile with LLVM\n";
309 return std::nullopt;
310 }
311
312#define DEBUG_TYPE "serialize-to-isa"
313 LLVM_DEBUG({
314 llvm::dbgs() << "SPIR-V for module: " << getGPUModuleOp().getNameAttr()
315 << "\n";
316 llvm::dbgs() << *serializedISA << "\n";
317 llvm::dbgs().flush();
318 });
319#undef DEBUG_TYPE
320
321 // Make sure to include the null terminator.
322 StringRef bin(serializedISA->c_str(), serializedISA->size() + 1);
323 return SmallVector<char, 0>(bin.begin(), bin.end());
324 }
325
326 // Level zero runtime is set up to accept SPIR-V binary
327 // translateToSPIRVBinary translates the LLVM module to SPIR-V binary
328 // using LLVM's SPIRV target.
329 // compileToBinary can be used in the future if level zero runtime
330 // implementation switches to native XeVM binary format.
331 std::optional<std::string> serializedSPIRVBinary =
332 translateToSPIRVBinary(llvmModule, **targetMachine);
333 if (!serializedSPIRVBinary) {
334 getGPUModuleOp().emitError() << "Failed translating the module to Binary.";
335 return std::nullopt;
336 }
337 if (serializedSPIRVBinary->size() % 4) {
338 getGPUModuleOp().emitError() << "SPIRV code size must be a multiple of 4.";
339 return std::nullopt;
340 }
341 StringRef bin(serializedSPIRVBinary->c_str(), serializedSPIRVBinary->size());
342 return SmallVector<char, 0>(bin.begin(), bin.end());
343}
344
345std::optional<std::string>
346SPIRVSerializer::translateToSPIRVBinary(llvm::Module &llvmModule,
347 llvm::TargetMachine &targetMachine) {
348 std::string targetISA;
349 llvm::raw_string_ostream stream(targetISA);
350
351 { // Drop pstream after this to prevent the ISA from being stuck buffering
352 llvm::buffer_ostream pstream(stream);
353 llvm::legacy::PassManager codegenPasses;
354 if (targetMachine.addPassesToEmitFile(codegenPasses, pstream, nullptr,
355 llvm::CodeGenFileType::ObjectFile))
356 return std::nullopt;
357
358 codegenPasses.run(llvmModule);
359 }
360 return targetISA;
361}
362
363std::optional<SmallVector<char, 0>>
364XeVMTargetAttrImpl::serializeToObject(Attribute attribute, Operation *module,
365 const gpu::TargetOptions &options) const {
366 if (!module)
367 return std::nullopt;
368 auto gpuMod = dyn_cast<gpu::GPUModuleOp>(module);
369 if (!gpuMod) {
370 module->emitError("expected to be a gpu.module op");
371 return std::nullopt;
372 }
373 auto xeTarget = cast<XeVMTargetAttr>(attribute);
374 if (xeTarget.getTriple().starts_with("spirv")) {
375 gpuMod.walk([&](LLVM::LLVMFuncOp funcOp) {
376 if (funcOp->hasAttr(gpu::GPUDialect::getKernelFuncAttrName())) {
377 funcOp.setIntelReqdSubGroupSize(16);
378 return WalkResult::interrupt();
379 }
380 return WalkResult::advance();
381 });
382
383 SPIRVSerializer serializer(*module, cast<XeVMTargetAttr>(attribute),
384 options);
385 serializer.init();
386
387#if !LLVM_HAS_SPIRV_TARGET
388 module->emitError("Cannot run `TargetRegistry::lookupTarget()` for SPIRV "
389 "without having the target built.");
390#endif
391
392 return serializer.run();
393 }
394 module->emitError("Unsupported XeVM target triple: ") << xeTarget.getTriple();
395 return std::nullopt;
396}
397
398Attribute
399XeVMTargetAttrImpl::createObject(Attribute attribute, Operation *module,
400 const SmallVector<char, 0> &object,
401 const gpu::TargetOptions &options) const {
402 Builder builder(attribute.getContext());
403 gpu::CompilationTarget format = options.getCompilationTarget();
404 auto xeTarget = cast<XeVMTargetAttr>(attribute);
405 SmallVector<NamedAttribute, 2> properties;
406 if (format == gpu::CompilationTarget::Assembly)
407 properties.push_back(
408 builder.getNamedAttr("O", builder.getI32IntegerAttr(xeTarget.getO())));
409
410 DictionaryAttr objectProps;
411 if (!properties.empty())
412 objectProps = builder.getDictionaryAttr(properties);
413
414 return builder.getAttr<gpu::ObjectAttr>(
415 attribute, format,
416 builder.getStringAttr(StringRef(object.data(), object.size())),
417 objectProps, /*kernels=*/nullptr);
418}
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 std::optional< 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
std::optional< SmallVector< std::unique_ptr< llvm::Module > > > loadBitcodeFiles(llvm::Module &module) override
Loads the bitcode files in librariesToLink.
Definition Target.cpp:92
std::optional< SmallVector< char, 0 > > compileToBinary(const std::string &asmStr, StringRef inputFormat)
Compiles to native code using ocloc.
Definition Target.cpp:111
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:215
gpu::GPUModuleOp getGPUModuleOp()
Returns the gpu module being serialized.
Definition Target.cpp:102
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.