27#include "llvm/ADT/ScopeExit.h"
28#include "llvm/Bitcode/BitcodeWriter.h"
29#include "llvm/Config/Targets.h"
30#include "llvm/IR/LegacyPassManager.h"
31#include "llvm/Support/FileSystem.h"
32#include "llvm/Support/FileUtilities.h"
33#include "llvm/Support/FormatVariadic.h"
34#include "llvm/Support/MemoryBuffer.h"
35#include "llvm/Support/Path.h"
36#include "llvm/Support/Process.h"
37#include "llvm/Support/Program.h"
38#include "llvm/Support/TargetSelect.h"
39#include "llvm/Support/raw_ostream.h"
40#include "llvm/Target/TargetMachine.h"
48#if MLIR_XEVM_OCLOC_LIB_AVAILABLE
58enum OclocErrorCode :
int { OCLOC_SUCCESS = 0 };
113int oclocInvoke(
unsigned numArgs,
const char **argv,
unsigned numSources,
114 const uint8_t **dataSources,
const uint64_t *lenSources,
115 const char **nameSources,
unsigned numHeaders,
116 const uint8_t **dataHeaders,
const uint64_t *lenHeaders,
117 const char **nameHeaders,
unsigned *numOutputs,
118 uint8_t ***dataOutputs, uint64_t **lenOutputs,
119 char ***nameOutputs);
132int oclocFreeOutput(
unsigned *numOutputs, uint8_t ***dataOutputs,
133 uint64_t **lenOutputs,
char ***nameOutputs);
140class XeVMTargetAttrImpl
141 :
public gpu::TargetAttrInterface::FallbackModel<XeVMTargetAttrImpl> {
143 std::optional<mlir::gpu::SerializedObject>
144 serializeToObject(Attribute attribute, Operation *module,
145 const gpu::TargetOptions &
options)
const;
147 Attribute createObject(Attribute attribute, Operation *module,
148 const mlir::gpu::SerializedObject &
object,
149 const gpu::TargetOptions &
options)
const;
156 XeVMTargetAttr::attachInterface<XeVMTargetAttrImpl>(*ctx);
171 xeTarget(xeTarget), librariesToLink(targetOptions.getLibrariesToLink()),
172 targetOptions(targetOptions) {
173 if (xeTarget.getLinkFiles())
174 librariesToLink.append(xeTarget.getLinkFiles().begin(),
175 xeTarget.getLinkFiles().end());
180std::optional<SmallVector<std::unique_ptr<llvm::Module>>>
188 return std::move(bcFiles);
199#if MLIR_XEVM_OCLOC_LIB_AVAILABLE
200FailureOr<SmallVector<char, 0>>
201SerializeGPUModuleBase::compileToBinaryViaLibocloc(StringRef asmStr,
202 StringRef inputFormat) {
204 std::string asmFname = llvm::formatv(
205 "mlir-{0}-{1}-{2}.asm",
getGPUModuleOp().getNameAttr().getValue(),
209 std::pair<llvm::BumpPtrAllocator, SmallVector<const char *>> cmdOpts =
212 const std::string cmdOptsStr =
"\"" + llvm::join(cmdOpts.second,
" ") +
"\"";
213 std::vector<std::string> oclocArgs = {
"ocloc",
224#define DEBUG_TYPE "serialize-to-binary"
226 llvm::dbgs() <<
"libocloc invocation for module: "
228 llvm::interleave(oclocArgs, llvm::dbgs(),
" ");
229 llvm::dbgs() <<
"\n";
234 std::vector<const char *> argv;
235 for (
const auto &str : oclocArgs)
236 argv.push_back(str.c_str());
239 const uint8_t *dataSources[1] = {
240 reinterpret_cast<const uint8_t *
>(asmStr.data())};
241 const uint64_t lenSources[1] = {asmStr.size()};
242 const char *nameSources[1] = {asmFname.c_str()};
244 uint32_t outputsNum = 0;
245 uint8_t **outputs =
nullptr;
246 uint64_t *outputLengths =
nullptr;
247 char **outputNames =
nullptr;
249 auto freeOutputs = llvm::scope_exit([&]() {
250 oclocFreeOutput(&outputsNum, &outputs, &outputLengths, &outputNames);
253 int err = oclocInvoke(
static_cast<uint32_t
>(argv.size()), argv.data(),
254 1, dataSources, lenSources, nameSources,
255 0,
nullptr,
nullptr,
nullptr,
256 &outputsNum, &outputs, &outputLengths, &outputNames);
258 if (err != OCLOC_SUCCESS) {
259 emitError(loc) <<
"libocloc: `oclocInvoke` failed, error code: " << err;
261 for (uint32_t i = 0; i < outputsNum; ++i) {
262 if (llvm::StringRef(outputNames[i]).ends_with(
".log"))
264 << llvm::StringRef(
reinterpret_cast<char *
>(outputs[i]),
271 for (uint32_t i = 0; i < outputsNum; ++i) {
272 if (llvm::StringRef(outputNames[i]).ends_with(
".bin")) {
273 char *begin =
reinterpret_cast<char *
>(outputs[i]);
274 return SmallVector<char, 0>(begin, begin + outputLengths[i]);
277 return emitError(loc) <<
"`oclocInvoke` did not produce `.bin` output";
286FailureOr<SmallVector<char, 0>>
288 StringRef inputFormat) {
289 using TmpFile = std::pair<llvm::SmallString<128>, llvm::FileRemover>;
292 std::optional<std::string> oclocPath =
findTool(
"ocloc");
299 std::string basename = llvm::formatv(
304 auto createTemp = [&](StringRef name,
305 StringRef suffix) -> std::optional<TmpFile> {
307 if (
auto ec = llvm::sys::fs::createTemporaryFile(name, suffix, path)) {
308 emitError(loc) <<
"Couldn't create temp file `" << path
309 <<
"`: " << ec.message();
312 return TmpFile(path, llvm::FileRemover(path.c_str()));
315 std::optional<TmpFile> asmFile = createTemp(basename,
"asm");
316 std::optional<TmpFile> binFile = createTemp(basename,
"");
317 std::optional<TmpFile> logFile = createTemp(basename,
"log");
318 if (!asmFile || !binFile || !logFile)
324 llvm::raw_fd_ostream asmStream(asmFile->first, ec);
326 emitError(loc) <<
"Couldn't open `" << asmFile->first
327 <<
"`: " << ec.message();
331 if (asmStream.has_error()) {
332 emitError(loc) <<
"Error writing assembly to `" << asmFile->first <<
"`";
339 std::pair<llvm::BumpPtrAllocator, SmallVector<const char *>> cmdOpts =
341 const std::string cmdOptsStr =
"\"" + llvm::join(cmdOpts.second,
" ") +
"\"";
344 {
"ocloc",
"compile",
"-file", asmFile->first, inputFormat,
"-device",
345 getTarget().getChip(),
"-output", binFile->first,
"-output_no_suffix",
346 "-options", cmdOptsStr});
349#define DEBUG_TYPE "serialize-to-binary"
351 llvm::dbgs() <<
"Tool invocation for module: "
353 llvm::interleave(oclocArgs, llvm::dbgs(),
" ");
354 llvm::dbgs() <<
"\n";
359 std::optional<StringRef> redirects[] = {std::nullopt, logFile->first,
362 std::string errorMsg;
363 if (llvm::sys::ExecuteAndWait(*oclocPath, oclocArgs, std::nullopt, redirects,
366 if (!errorMsg.empty()) {
367 emitError(loc) <<
"`ocloc` invocation failed: " << errorMsg;
368 }
else if (
auto log = llvm::MemoryBuffer::getFile(logFile->first)) {
369 emitError(loc) <<
"`ocloc` invocation failed. Log:\n"
370 << (*log)->getBuffer();
372 emitError(loc) <<
"`ocloc` invocation failed (no log available)";
378 binFile->first.append(
".bin");
379 auto binaryBuffer = llvm::MemoryBuffer::getFile(binFile->first);
381 emitError(loc) <<
"Couldn't open binary output `" << binFile->first
382 <<
"`: " << binaryBuffer.getError().message();
385 StringRef bin = (*binaryBuffer)->getBuffer();
393FailureOr<SmallVector<char, 0>>
395 StringRef inputFormat) {
396#if MLIR_XEVM_OCLOC_LIB_AVAILABLE
397 return compileToBinaryViaLibocloc(asmStr, inputFormat);
407 if (!pathRef.empty()) {
408 path.insert(path.begin(), pathRef.begin(), pathRef.end());
409 llvm::sys::path::append(path,
"bin", tool);
410 if (llvm::sys::fs::can_execute(path))
411 return StringRef(path.data(), path.size()).str();
414 if (std::optional<std::string> toolPath =
415 llvm::sys::Process::FindInEnvPath(
"PATH", tool))
419 <<
"Couldn't find the `" << tool
420 <<
"` binary. Please specify the toolkit "
421 "path via GpuModuleToBinaryPass or add the compiler to $PATH`.";
427 SPIRVSerializer(
Operation &module, XeVMTargetAttr xeTarget,
435 FailureOr<SmallVector<char, 0>>
436 moduleToObject(llvm::Module &llvmModule)
override;
439 std::optional<SmallVector<char, 0>> run()
override;
444 std::optional<std::string>
445 translateToSPIRVBinary(llvm::Module &llvmModule,
446 llvm::TargetMachine &targetMachine);
450void SPIRVSerializer::init() {
451 static llvm::once_flag initializeBackendOnce;
452 llvm::call_once(initializeBackendOnce, []() {
453#if LLVM_HAS_SPIRV_TARGET
454 LLVMInitializeSPIRVTarget();
455 LLVMInitializeSPIRVTargetInfo();
456 LLVMInitializeSPIRVTargetMC();
457 LLVMInitializeSPIRVAsmPrinter();
462#if LLVM_HAS_SPIRV_TARGET
468static const std::vector<std::string> getDefaultSPIRVExtensions() {
471 "SPV_EXT_relaxed_printf_string_address_space",
472 "SPV_INTEL_cache_controls",
473 "SPV_INTEL_variable_length_array",
474 "SPV_INTEL_bfloat16_arithmetic",
482SPIRVTranslateModule(Module *M, std::string &SpirvObj, std::string &ErrMsg,
483 const std::vector<std::string> &AllowExtNames,
484 const std::vector<std::string> &Opts);
492FailureOr<SmallVector<char, 0>>
493SPIRVSerializer::moduleToObject(llvm::Module &llvmModule) {
494#define DEBUG_TYPE "serialize-to-llvm"
496 llvm::dbgs() <<
"LLVM IR for module: " << getGPUModuleOp().getNameAttr()
498 llvm::dbgs() << llvmModule <<
"\n";
499 llvm::dbgs().flush();
504 if (targetOptions.getCompilationTarget() == gpu::CompilationTarget::Offload)
507#if !LLVM_HAS_SPIRV_TARGET
508 return getGPUModuleOp()->emitError(
509 "The `SPIRV` target was not built. Please enable "
510 "it when building LLVM.");
512 std::string serializedSPIRVBinary;
514 std::vector<std::string> Opts;
515 Opts.push_back(triple.str());
516 Opts.push_back(std::to_string(optLevel));
520 SPIRVTranslateModule(&llvmModule, serializedSPIRVBinary, ErrMsg,
521 getDefaultSPIRVExtensions(), Opts);
524 return getGPUModuleOp().emitError()
525 <<
"Failed translating the module to Binary."
526 <<
"Error message: " << ErrMsg;
528 if (serializedSPIRVBinary.size() % 4)
529 return getGPUModuleOp().emitError()
530 <<
"SPIRV code size must be a multiple of 4.";
532 StringRef spirvBin(serializedSPIRVBinary.c_str(),
533 serializedSPIRVBinary.size());
539 if (targetOptions.getCompilationTarget() ==
540 gpu::CompilationTarget::Assembly) {
541#define DEBUG_TYPE "serialize-to-isa"
543 llvm::dbgs() <<
"SPIR-V for module: " << getGPUModuleOp().getNameAttr()
545 llvm::dbgs() << serializedSPIRVBinary <<
"\n";
546 llvm::dbgs().flush();
549 return SmallVector<char, 0>(spirvBin.begin(), spirvBin.end());
555 return compileToBinary(spirvBin,
"-spirv_input");
559std::optional<SmallVector<char, 0>> SPIRVSerializer::run() {
561 llvm::LLVMContext llvmContext;
562 std::unique_ptr<llvm::Module> llvmModule = translateToLLVMIR(llvmContext);
564 getOperation().emitError() <<
"Failed creating the llvm::Module.";
567 setDataLayoutAndTriple(*llvmModule);
569 if (initialLlvmIRCallback)
570 initialLlvmIRCallback(*llvmModule);
573 handleModulePreLink(*llvmModule);
575 auto libs = loadBitcodeFiles(*llvmModule);
579 if (
failed(linkFiles(*llvmModule, std::move(*libs))))
581 handleModulePostLink(*llvmModule);
584 if (linkedLlvmIRCallback)
585 linkedLlvmIRCallback(*llvmModule);
588 return moduleToObject(*llvmModule);
591std::optional<std::string>
592SPIRVSerializer::translateToSPIRVBinary(llvm::Module &llvmModule,
593 llvm::TargetMachine &targetMachine) {
594 std::string targetISA;
595 llvm::raw_string_ostream stream(targetISA);
598 llvm::buffer_ostream pstream(stream);
599 llvm::legacy::PassManager codegenPasses;
600 if (targetMachine.addPassesToEmitFile(codegenPasses, pstream,
nullptr,
601 llvm::CodeGenFileType::ObjectFile))
604 codegenPasses.run(llvmModule);
609std::optional<mlir::gpu::SerializedObject>
610XeVMTargetAttrImpl::serializeToObject(Attribute attribute, Operation *module,
611 const gpu::TargetOptions &
options)
const {
614 auto gpuMod = dyn_cast<gpu::GPUModuleOp>(module);
616 module->emitError("expected to be a gpu.module op");
619 auto xeTarget = cast<XeVMTargetAttr>(attribute);
620 if (xeTarget.getTriple().starts_with(
"spirv")) {
621 gpuMod.walk([&](LLVM::LLVMFuncOp funcOp) {
622 if (funcOp->hasAttr(gpu::GPUDialect::getKernelFuncAttrName())) {
623 funcOp.setIntelReqdSubGroupSize(16);
629 SPIRVSerializer serializer(*module, cast<XeVMTargetAttr>(attribute),
633#if !LLVM_HAS_SPIRV_TARGET
634 module->emitError("Cannot run `TargetRegistry::lookupTarget()` for SPIRV "
635 "without having the target built.");
638 std::optional<SmallVector<char, 0>> binary = serializer.run();
641 return gpu::SerializedObject{std::move(*binary)};
643 module->emitError("Unsupported XeVM target triple: ") << xeTarget.getTriple();
648XeVMTargetAttrImpl::createObject(Attribute attribute, Operation *module,
649 const mlir::gpu::SerializedObject &
object,
650 const gpu::TargetOptions &
options)
const {
652 gpu::CompilationTarget format =
options.getCompilationTarget();
653 auto xeTarget = cast<XeVMTargetAttr>(attribute);
654 SmallVector<NamedAttribute, 2> properties;
655 if (format == gpu::CompilationTarget::Assembly)
656 properties.push_back(
657 builder.getNamedAttr(
"O", builder.getI32IntegerAttr(xeTarget.getO())));
659 DictionaryAttr objectProps;
660 if (!properties.empty())
661 objectProps = builder.getDictionaryAttr(properties);
663 return builder.getAttr<gpu::ObjectAttr>(
665 builder.getStringAttr(
666 StringRef(
object.getObject().data(),
object.getObject().size())),
667 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.
std::pair< llvm::BumpPtrAllocator, SmallVector< const char * > > tokenizeCmdOptions() const
Returns a tokenization of the command line options.
Base class for all XeVM serializations from GPU modules into binary strings.
gpu::TargetOptions targetOptions
Compiles to native code using the ocloc shared library API, in-process, without temporary files.
XeVMTargetAttr getTarget() const
Returns the target attribute.
FailureOr< SmallVector< char, 0 > > compileToBinary(StringRef asmStr, StringRef inputFormat)
Compiles to native code using ocloc (API or tool).
FailureOr< SmallVector< char, 0 > > compileToBinaryViaOclocTool(StringRef asmStr, StringRef inputFormat)
Compiles to native code using the ocloc command-line tool, communicating through temporary files.
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.