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/Program.h"
37#include "llvm/Support/TargetSelect.h"
38#include "llvm/Support/raw_ostream.h"
39#include "llvm/Target/TargetMachine.h"
47#if MLIR_XEVM_OCLOC_LIB_AVAILABLE
57enum OclocErrorCode :
int { OCLOC_SUCCESS = 0 };
112int oclocInvoke(
unsigned numArgs,
const char **argv,
unsigned numSources,
113 const uint8_t **dataSources,
const uint64_t *lenSources,
114 const char **nameSources,
unsigned numHeaders,
115 const uint8_t **dataHeaders,
const uint64_t *lenHeaders,
116 const char **nameHeaders,
unsigned *numOutputs,
117 uint8_t ***dataOutputs, uint64_t **lenOutputs,
118 char ***nameOutputs);
131int oclocFreeOutput(
unsigned *numOutputs, uint8_t ***dataOutputs,
132 uint64_t **lenOutputs,
char ***nameOutputs);
139class XeVMTargetAttrImpl
140 :
public gpu::TargetAttrInterface::FallbackModel<XeVMTargetAttrImpl> {
142 std::optional<mlir::gpu::SerializedObject>
143 serializeToObject(Attribute attribute, Operation *module,
144 const gpu::TargetOptions &
options)
const;
146 Attribute createObject(Attribute attribute, Operation *module,
147 const mlir::gpu::SerializedObject &
object,
148 const gpu::TargetOptions &
options)
const;
155 XeVMTargetAttr::attachInterface<XeVMTargetAttrImpl>(*ctx);
170 xeTarget(xeTarget), librariesToLink(targetOptions.getLibrariesToLink()),
171 targetOptions(targetOptions) {
172 if (xeTarget.getLinkFiles())
173 librariesToLink.append(xeTarget.getLinkFiles().begin(),
174 xeTarget.getLinkFiles().end());
179std::optional<SmallVector<std::unique_ptr<llvm::Module>>>
187 return std::move(bcFiles);
198#if MLIR_XEVM_OCLOC_LIB_AVAILABLE
199FailureOr<SmallVector<char, 0>>
200SerializeGPUModuleBase::compileToBinaryViaLibocloc(StringRef asmStr,
201 StringRef inputFormat) {
203 std::string asmFname = llvm::formatv(
204 "mlir-{0}-{1}-{2}.asm",
getGPUModuleOp().getNameAttr().getValue(),
208 std::pair<llvm::BumpPtrAllocator, SmallVector<const char *>> cmdOpts =
211 const std::string cmdOptsStr =
"\"" + llvm::join(cmdOpts.second,
" ") +
"\"";
212 std::vector<std::string> oclocArgs = {
"ocloc",
223#define DEBUG_TYPE "serialize-to-binary"
225 llvm::dbgs() <<
"libocloc invocation for module: "
227 llvm::interleave(oclocArgs, llvm::dbgs(),
" ");
228 llvm::dbgs() <<
"\n";
233 std::vector<const char *> argv;
234 for (
const auto &str : oclocArgs)
235 argv.push_back(str.c_str());
238 const uint8_t *dataSources[1] = {
239 reinterpret_cast<const uint8_t *
>(asmStr.data())};
240 const uint64_t lenSources[1] = {asmStr.size()};
241 const char *nameSources[1] = {asmFname.c_str()};
243 uint32_t outputsNum = 0;
244 uint8_t **outputs =
nullptr;
245 uint64_t *outputLengths =
nullptr;
246 char **outputNames =
nullptr;
248 auto freeOutputs = llvm::scope_exit([&]() {
249 oclocFreeOutput(&outputsNum, &outputs, &outputLengths, &outputNames);
252 int err = oclocInvoke(
static_cast<uint32_t
>(argv.size()), argv.data(),
253 1, dataSources, lenSources, nameSources,
254 0,
nullptr,
nullptr,
nullptr,
255 &outputsNum, &outputs, &outputLengths, &outputNames);
257 if (err != OCLOC_SUCCESS) {
258 emitError(loc) <<
"libocloc: `oclocInvoke` failed, error code: " << err;
260 for (uint32_t i = 0; i < outputsNum; ++i) {
261 if (llvm::StringRef(outputNames[i]).ends_with(
".log"))
263 << llvm::StringRef(
reinterpret_cast<char *
>(outputs[i]),
270 for (uint32_t i = 0; i < outputsNum; ++i) {
271 if (llvm::StringRef(outputNames[i]).ends_with(
".bin")) {
272 char *begin =
reinterpret_cast<char *
>(outputs[i]);
273 return SmallVector<char, 0>(begin, begin + outputLengths[i]);
276 return emitError(loc) <<
"`oclocInvoke` did not produce `.bin` output";
285FailureOr<SmallVector<char, 0>>
287 StringRef inputFormat) {
288 using TmpFile = std::pair<llvm::SmallString<128>, llvm::FileRemover>;
291 std::optional<std::string> oclocPath =
findTool(
"ocloc");
298 std::string basename = llvm::formatv(
303 auto createTemp = [&](StringRef name,
304 StringRef suffix) -> std::optional<TmpFile> {
306 if (
auto ec = llvm::sys::fs::createTemporaryFile(name, suffix, path)) {
307 emitError(loc) <<
"Couldn't create temp file `" << path
308 <<
"`: " << ec.message();
311 return TmpFile(path, llvm::FileRemover(path.c_str()));
314 std::optional<TmpFile> asmFile = createTemp(basename,
"asm");
315 std::optional<TmpFile> binFile = createTemp(basename,
"");
316 std::optional<TmpFile> logFile = createTemp(basename,
"log");
317 if (!asmFile || !binFile || !logFile)
323 llvm::raw_fd_ostream asmStream(asmFile->first, ec);
325 emitError(loc) <<
"Couldn't open `" << asmFile->first
326 <<
"`: " << ec.message();
330 if (asmStream.has_error()) {
331 emitError(loc) <<
"Error writing assembly to `" << asmFile->first <<
"`";
338 std::pair<llvm::BumpPtrAllocator, SmallVector<const char *>> cmdOpts =
340 const std::string cmdOptsStr =
"\"" + llvm::join(cmdOpts.second,
" ") +
"\"";
343 {
"ocloc",
"compile",
"-file", asmFile->first, inputFormat,
"-device",
344 getTarget().getChip(),
"-output", binFile->first,
"-output_no_suffix",
345 "-options", cmdOptsStr});
348#define DEBUG_TYPE "serialize-to-binary"
350 llvm::dbgs() <<
"Tool invocation for module: "
352 llvm::interleave(oclocArgs, llvm::dbgs(),
" ");
353 llvm::dbgs() <<
"\n";
358 std::optional<StringRef> redirects[] = {std::nullopt, logFile->first,
361 std::string errorMsg;
362 if (llvm::sys::ExecuteAndWait(*oclocPath, oclocArgs, std::nullopt, redirects,
365 if (!errorMsg.empty()) {
366 emitError(loc) <<
"`ocloc` invocation failed: " << errorMsg;
367 }
else if (
auto log = llvm::MemoryBuffer::getFile(logFile->first)) {
368 emitError(loc) <<
"`ocloc` invocation failed. Log:\n"
369 << (*log)->getBuffer();
371 emitError(loc) <<
"`ocloc` invocation failed (no log available)";
377 binFile->first.append(
".bin");
378 auto binaryBuffer = llvm::MemoryBuffer::getFile(binFile->first);
380 emitError(loc) <<
"Couldn't open binary output `" << binFile->first
381 <<
"`: " << binaryBuffer.getError().message();
384 StringRef bin = (*binaryBuffer)->getBuffer();
392FailureOr<SmallVector<char, 0>>
394 StringRef inputFormat) {
395#if MLIR_XEVM_OCLOC_LIB_AVAILABLE
396 return compileToBinaryViaLibocloc(asmStr, inputFormat);
406 if (!pathRef.empty()) {
407 path.insert(path.begin(), pathRef.begin(), pathRef.end());
408 llvm::sys::path::append(path,
"bin", tool);
409 if (llvm::sys::fs::can_execute(path))
410 return StringRef(path.data(), path.size()).str();
413 if (llvm::ErrorOr<std::string> toolPath = llvm::sys::findProgramByName(tool))
417 <<
"Couldn't find the `" << tool
418 <<
"` binary. Please specify the toolkit "
419 "path via GpuModuleToBinaryPass or add the compiler to $PATH`.";
425 SPIRVSerializer(
Operation &module, XeVMTargetAttr xeTarget,
433 FailureOr<SmallVector<char, 0>>
434 moduleToObject(llvm::Module &llvmModule)
override;
437 std::optional<SmallVector<char, 0>> run()
override;
442 std::optional<std::string>
443 translateToSPIRVBinary(llvm::Module &llvmModule,
444 llvm::TargetMachine &targetMachine);
448void SPIRVSerializer::init() {
449 static llvm::once_flag initializeBackendOnce;
450 llvm::call_once(initializeBackendOnce, []() {
451#if LLVM_HAS_SPIRV_TARGET
452 LLVMInitializeSPIRVTarget();
453 LLVMInitializeSPIRVTargetInfo();
454 LLVMInitializeSPIRVTargetMC();
455 LLVMInitializeSPIRVAsmPrinter();
460#if LLVM_HAS_SPIRV_TARGET
466static const std::vector<std::string> getDefaultSPIRVExtensions() {
469 "SPV_EXT_relaxed_printf_string_address_space",
470 "SPV_INTEL_cache_controls",
471 "SPV_INTEL_variable_length_array",
472 "SPV_INTEL_bfloat16_arithmetic",
480SPIRVTranslateModule(Module *M, std::string &SpirvObj, std::string &ErrMsg,
481 const std::vector<std::string> &AllowExtNames,
482 const std::vector<std::string> &Opts);
490FailureOr<SmallVector<char, 0>>
491SPIRVSerializer::moduleToObject(llvm::Module &llvmModule) {
492#define DEBUG_TYPE "serialize-to-llvm"
494 llvm::dbgs() <<
"LLVM IR for module: " << getGPUModuleOp().getNameAttr()
496 llvm::dbgs() << llvmModule <<
"\n";
497 llvm::dbgs().flush();
502 if (targetOptions.getCompilationTarget() == gpu::CompilationTarget::Offload)
505#if !LLVM_HAS_SPIRV_TARGET
506 return getGPUModuleOp()->emitError(
507 "The `SPIRV` target was not built. Please enable "
508 "it when building LLVM.");
510 std::string serializedSPIRVBinary;
512 std::vector<std::string> Opts;
513 Opts.push_back(triple.str());
514 Opts.push_back(std::to_string(optLevel));
518 SPIRVTranslateModule(&llvmModule, serializedSPIRVBinary, ErrMsg,
519 getDefaultSPIRVExtensions(), Opts);
522 return getGPUModuleOp().emitError()
523 <<
"Failed translating the module to Binary."
524 <<
"Error message: " << ErrMsg;
526 if (serializedSPIRVBinary.size() % 4)
527 return getGPUModuleOp().emitError()
528 <<
"SPIRV code size must be a multiple of 4.";
530 StringRef spirvBin(serializedSPIRVBinary.c_str(),
531 serializedSPIRVBinary.size());
537 if (targetOptions.getCompilationTarget() ==
538 gpu::CompilationTarget::Assembly) {
539#define DEBUG_TYPE "serialize-to-isa"
541 llvm::dbgs() <<
"SPIR-V for module: " << getGPUModuleOp().getNameAttr()
543 llvm::dbgs() << serializedSPIRVBinary <<
"\n";
544 llvm::dbgs().flush();
547 return SmallVector<char, 0>(spirvBin.begin(), spirvBin.end());
553 return compileToBinary(spirvBin,
"-spirv_input");
557std::optional<SmallVector<char, 0>> SPIRVSerializer::run() {
559 llvm::LLVMContext llvmContext;
560 std::unique_ptr<llvm::Module> llvmModule = translateToLLVMIR(llvmContext);
562 getOperation().emitError() <<
"Failed creating the llvm::Module.";
565 setDataLayoutAndTriple(*llvmModule);
567 if (initialLlvmIRCallback)
568 initialLlvmIRCallback(*llvmModule);
571 handleModulePreLink(*llvmModule);
573 auto libs = loadBitcodeFiles(*llvmModule);
577 if (
failed(linkFiles(*llvmModule, std::move(*libs))))
579 handleModulePostLink(*llvmModule);
582 if (linkedLlvmIRCallback)
583 linkedLlvmIRCallback(*llvmModule);
586 return moduleToObject(*llvmModule);
589std::optional<std::string>
590SPIRVSerializer::translateToSPIRVBinary(llvm::Module &llvmModule,
591 llvm::TargetMachine &targetMachine) {
592 std::string targetISA;
593 llvm::raw_string_ostream stream(targetISA);
596 llvm::buffer_ostream pstream(stream);
597 llvm::legacy::PassManager codegenPasses;
598 if (targetMachine.addPassesToEmitFile(codegenPasses, pstream,
nullptr,
599 llvm::CodeGenFileType::ObjectFile))
602 codegenPasses.run(llvmModule);
607std::optional<mlir::gpu::SerializedObject>
608XeVMTargetAttrImpl::serializeToObject(Attribute attribute, Operation *module,
609 const gpu::TargetOptions &
options)
const {
612 auto gpuMod = dyn_cast<gpu::GPUModuleOp>(module);
614 module->emitError("expected to be a gpu.module op");
617 auto xeTarget = cast<XeVMTargetAttr>(attribute);
618 if (xeTarget.getTriple().starts_with(
"spirv")) {
619 gpuMod.walk([&](LLVM::LLVMFuncOp funcOp) {
620 if (funcOp->hasDiscardableAttr(gpu::GPUDialect::getKernelFuncAttrName()))
621 funcOp.setIntelReqdSubGroupSize(16);
624 SPIRVSerializer serializer(*module, cast<XeVMTargetAttr>(attribute),
628#if !LLVM_HAS_SPIRV_TARGET
629 module->emitError("Cannot run `TargetRegistry::lookupTarget()` for SPIRV "
630 "without having the target built.");
633 std::optional<SmallVector<char, 0>> binary = serializer.run();
636 return gpu::SerializedObject{std::move(*binary)};
638 module->emitError("Unsupported XeVM target triple: ") << xeTarget.getTriple();
643XeVMTargetAttrImpl::createObject(Attribute attribute, Operation *module,
644 const mlir::gpu::SerializedObject &
object,
645 const gpu::TargetOptions &
options)
const {
647 gpu::CompilationTarget format =
options.getCompilationTarget();
648 auto xeTarget = cast<XeVMTargetAttr>(attribute);
649 SmallVector<NamedAttribute, 2> properties;
650 if (format == gpu::CompilationTarget::Assembly)
651 properties.push_back(
652 builder.getNamedAttr(
"O", builder.getI32IntegerAttr(xeTarget.getO())));
654 DictionaryAttr objectProps;
655 if (!properties.empty())
656 objectProps = builder.getDictionaryAttr(properties);
658 return builder.getAttr<gpu::ObjectAttr>(
660 builder.getStringAttr(
661 StringRef(
object.getObject().data(),
object.getObject().size())),
662 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.
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.