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
463static const std::vector<std::string> getDefaultSPIRVExtensions() {
465 "SPV_EXT_relaxed_printf_string_address_space",
466 "SPV_INTEL_cache_controls",
467 "SPV_INTEL_variable_length_array",
475SPIRVTranslateModule(Module *M, std::string &SpirvObj, std::string &ErrMsg,
476 const std::vector<std::string> &AllowExtNames,
477 const std::vector<std::string> &Opts);
485FailureOr<SmallVector<char, 0>>
486SPIRVSerializer::moduleToObject(llvm::Module &llvmModule) {
487#define DEBUG_TYPE "serialize-to-llvm"
489 llvm::dbgs() <<
"LLVM IR for module: " << getGPUModuleOp().getNameAttr()
491 llvm::dbgs() << llvmModule <<
"\n";
492 llvm::dbgs().flush();
497 if (targetOptions.getCompilationTarget() == gpu::CompilationTarget::Offload)
500#if !LLVM_HAS_SPIRV_TARGET
501 return getGPUModuleOp()->emitError(
502 "The `SPIRV` target was not built. Please enable "
503 "it when building LLVM.");
505 std::string serializedSPIRVBinary;
507 std::vector<std::string> Opts;
508 Opts.push_back(triple.str());
509 Opts.push_back(std::to_string(optLevel));
513 SPIRVTranslateModule(&llvmModule, serializedSPIRVBinary, ErrMsg,
514 getDefaultSPIRVExtensions(), Opts);
517 return getGPUModuleOp().emitError()
518 <<
"Failed translating the module to Binary."
519 <<
"Error message: " << ErrMsg;
521 if (serializedSPIRVBinary.size() % 4)
522 return getGPUModuleOp().emitError()
523 <<
"SPIRV code size must be a multiple of 4.";
525 StringRef spirvBin(serializedSPIRVBinary.c_str(),
526 serializedSPIRVBinary.size());
532 if (targetOptions.getCompilationTarget() ==
533 gpu::CompilationTarget::Assembly) {
534#define DEBUG_TYPE "serialize-to-isa"
536 llvm::dbgs() <<
"SPIR-V for module: " << getGPUModuleOp().getNameAttr()
538 llvm::dbgs() << serializedSPIRVBinary <<
"\n";
539 llvm::dbgs().flush();
542 return SmallVector<char, 0>(spirvBin.begin(), spirvBin.end());
548 return compileToBinary(spirvBin,
"-spirv_input");
552std::optional<SmallVector<char, 0>> SPIRVSerializer::run() {
554 llvm::LLVMContext llvmContext;
555 std::unique_ptr<llvm::Module> llvmModule = translateToLLVMIR(llvmContext);
557 getOperation().emitError() <<
"Failed creating the llvm::Module.";
560 setDataLayoutAndTriple(*llvmModule);
562 if (initialLlvmIRCallback)
563 initialLlvmIRCallback(*llvmModule);
566 handleModulePreLink(*llvmModule);
568 auto libs = loadBitcodeFiles(*llvmModule);
572 if (
failed(linkFiles(*llvmModule, std::move(*libs))))
574 handleModulePostLink(*llvmModule);
577 if (linkedLlvmIRCallback)
578 linkedLlvmIRCallback(*llvmModule);
581 return moduleToObject(*llvmModule);
584std::optional<std::string>
585SPIRVSerializer::translateToSPIRVBinary(llvm::Module &llvmModule,
586 llvm::TargetMachine &targetMachine) {
587 std::string targetISA;
588 llvm::raw_string_ostream stream(targetISA);
591 llvm::buffer_ostream pstream(stream);
592 llvm::legacy::PassManager codegenPasses;
593 if (targetMachine.addPassesToEmitFile(codegenPasses, pstream,
nullptr,
594 llvm::CodeGenFileType::ObjectFile))
597 codegenPasses.run(llvmModule);
602std::optional<mlir::gpu::SerializedObject>
603XeVMTargetAttrImpl::serializeToObject(Attribute attribute, Operation *module,
604 const gpu::TargetOptions &
options)
const {
607 auto gpuMod = dyn_cast<gpu::GPUModuleOp>(module);
609 module->emitError("expected to be a gpu.module op");
612 auto xeTarget = cast<XeVMTargetAttr>(attribute);
613 if (xeTarget.getTriple().starts_with(
"spirv")) {
614 gpuMod.walk([&](LLVM::LLVMFuncOp funcOp) {
615 if (funcOp->hasAttr(gpu::GPUDialect::getKernelFuncAttrName())) {
616 funcOp.setIntelReqdSubGroupSize(16);
622 SPIRVSerializer serializer(*module, cast<XeVMTargetAttr>(attribute),
626#if !LLVM_HAS_SPIRV_TARGET
627 module->emitError("Cannot run `TargetRegistry::lookupTarget()` for SPIRV "
628 "without having the target built.");
631 std::optional<SmallVector<char, 0>> binary = serializer.run();
634 return gpu::SerializedObject{std::move(*binary)};
636 module->emitError("Unsupported XeVM target triple: ") << xeTarget.getTriple();
641XeVMTargetAttrImpl::createObject(Attribute attribute, Operation *module,
642 const mlir::gpu::SerializedObject &
object,
643 const gpu::TargetOptions &
options)
const {
645 gpu::CompilationTarget format =
options.getCompilationTarget();
646 auto xeTarget = cast<XeVMTargetAttr>(attribute);
647 SmallVector<NamedAttribute, 2> properties;
648 if (format == gpu::CompilationTarget::Assembly)
649 properties.push_back(
650 builder.getNamedAttr(
"O", builder.getI32IntegerAttr(xeTarget.getO())));
652 DictionaryAttr objectProps;
653 if (!properties.empty())
654 objectProps = builder.getDictionaryAttr(properties);
656 return builder.getAttr<gpu::ObjectAttr>(
658 builder.getStringAttr(
659 StringRef(
object.getObject().data(),
object.getObject().size())),
660 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.