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 
20 #include "mlir/IR/BuiltinDialect.h"
21 #include "mlir/IR/BuiltinTypes.h"
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 
46 using namespace mlir;
47 using namespace mlir::xevm;
48 
49 namespace {
50 // XeVM implementation of the gpu:TargetAttrInterface.
51 class XeVMTargetAttrImpl
52  : public gpu::TargetAttrInterface::FallbackModel<XeVMTargetAttrImpl> {
53 public:
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 
72  MLIRContext &context) {
73  DialectRegistry registry;
75  context.appendDialectRegistry(registry);
76 }
77 
79  Operation &module, XeVMTargetAttr xeTarget,
80  const gpu::TargetOptions &targetOptions)
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 
89 XeVMTargetAttr SerializeGPUModuleBase::getTarget() const { return xeTarget; }
90 
91 std::optional<SmallVector<std::unique_ptr<llvm::Module>>>
93  if (librariesToLink.empty())
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.
110 std::optional<SmallVector<char, 0>>
111 SerializeGPUModuleBase::compileToBinary(const std::string &asmStr,
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 =
160  // Example: --gpu-module-to-binary="opts='opt1 opt2'"
161  const std::string cmdOptsStr = "\"" + llvm::join(cmdOpts.second, " ") + "\"";
162  SmallVector<StringRef, 12> oclocArgs(
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 
215 std::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 
237 namespace {
238 class SPIRVSerializer : public SerializeGPUModuleBase {
239 public:
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 
251 private:
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 
260 void 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 
272 std::optional<SmallVector<char, 0>>
273 SPIRVSerializer::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)
285  return SerializeGPUModuleBase::moduleToObject(llvmModule);
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 
345 std::optional<std::string>
346 SPIRVSerializer::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 
363 std::optional<SmallVector<char, 0>>
364 XeVMTargetAttrImpl::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 
398 Attribute
399 XeVMTargetAttrImpl::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);
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
Attributes are known-constant values of operations.
Definition: Attributes.h:25
MLIRContext * getContext() const
Return the context this attribute belongs to.
Definition: Attributes.cpp:37
This class is a general helper class for creating context-global objects like types,...
Definition: Builders.h:51
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.
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
MLIRContext * getContext()
Return the context this operation is associated with.
Definition: Operation.h:216
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
Definition: Operation.cpp:267
static WalkResult advance()
Definition: WalkResult.h:47
This class serves as an opaque interface for passing options to the TargetAttrInterface methods.
std::pair< llvm::BumpPtrAllocator, SmallVector< const char * > > tokenizeCmdOptions() const
Returns a tokenization of the command line options.
StringRef getToolkitPath() const
Returns the toolkit path.
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
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition: Remarks.h:491
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.