blob: b1c96ba2193768e6904b72fb55dde2e961e644d9 [file]
// Copyright 2023 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <memory>
#include <utility>
#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
#include "iree/compiler/Dialect/HAL/IR/HALOps.h"
#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
#include "iree/compiler/Utils/ToolUtils.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "mlir/Parser/Parser.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassManager.h"
#define DEBUG_TYPE "iree-tools"
namespace mlir::iree_compiler::IREE::HAL {
#define GEN_PASS_DEF_PREPROCESSEXECUTABLESWITHPIPELINEPASS
#define GEN_PASS_DEF_PREPROCESSEXECUTABLESWITHTOOLPASS
#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
namespace {
static StringRef fixupArg(StringRef arg) {
// HACK: pass pipeline parsing doesn't handle strings with spaces and the only
// way to get them through (I could find) is to double quote them. This
// unfortunately breaks native path tokenization for single executable quoted
// paths.
if (arg.starts_with("\"") && arg.ends_with("\"")) {
arg = arg.drop_front(1).drop_back(1);
}
return arg;
}
static LogicalResult buildPassPipeline(StringRef rawPipelineStr,
OpPassManager &passManager) {
auto pipelineStr = fixupArg(rawPipelineStr);
// Strip the `builtin.module(...)` that surrounds the pass pipeline
// description. On failure an assertion is triggered, but in release builds
// it just will silently return and not raise an error. There is no
// way to handle the error in caller currently.
StringRef text(pipelineStr);
size_t pos = text.find_first_of("(");
if (pos == StringRef::npos) {
llvm::errs() << "ERROR: expected preprocessing pass pipeline string to be "
"nested within `builtin.module(..)`; got `"
<< pipelineStr << "`\n";
return failure();
}
if (text.substr(0, pos) != "builtin.module") {
llvm::errs() << "ERROR: expected preprocessing pass pipeline string to be "
"nested within `builtin.module(..)`\n";
return failure();
}
if (text.back() != ')') {
llvm::errs() << "ERROR: mismatched parenthesis in pass pipeline `"
<< pipelineStr << "`\n";
return failure();
}
text = text.substr(pos + 1);
if (failed(parsePassPipeline(text.drop_back(), passManager))) {
llvm::errs() << "ERROR: failed to parse textual pass pipeline `"
<< pipelineStr << "`\n";
return failure();
}
LLVM_DEBUG({
llvm::dbgs() << "Preprocessing pass pipeline : ";
passManager.printAsTextualPipeline(llvm::dbgs());
});
return success();
}
// Replaces the contents and attributes on |executableOp| with those of the
// given |replacementOp|.
static void replaceExecutableContents(IREE::HAL::ExecutableOp executableOp,
IREE::HAL::ExecutableOp replacementOp) {
// Drop all dialect attrs from the original and use those of the replacement.
for (auto attr :
llvm::make_early_inc_range(executableOp->getDialectAttrs())) {
executableOp->removeAttr(attr.getName());
}
executableOp->setDialectAttrs(replacementOp->getDialectAttrs());
// Drop the original body and take the replacement one.
executableOp.getBody().takeBody(replacementOp.getBody());
}
static LogicalResult preprocessWithCommand(IREE::HAL::ExecutableOp executableOp,
StringRef rawCommand) {
auto command = fixupArg(rawCommand);
// Setup IO redirects used to pass around the executable MLIR contents.
SmallString<32> stdinFile, stdoutFile, stderrFile;
int inputFd = 0;
llvm::sys::fs::createTemporaryFile("executable-preprocessor-stdin", "",
inputFd, stdinFile);
llvm::sys::fs::createTemporaryFile("executable-preprocessor-stdout", "",
stdoutFile);
llvm::sys::fs::createTemporaryFile("executable-preprocessor-stderr", "",
stderrFile);
llvm::FileRemover stdinRemover(stdinFile.c_str());
llvm::FileRemover stdoutRemover(stdoutFile.c_str());
llvm::FileRemover stderrRemover(stderrFile.c_str());
std::optional<StringRef> redirects[] = {
stdinFile.str(),
stdoutFile.str(),
stderrFile.str(),
};
// Serialize the executable contents.
// NOTE: this is currently being done in text format as it's easier to work
// with. We'll probably want to flip to binary or make it an option if we
// ever want to support versioning.
{
llvm::raw_fd_ostream inputStream(inputFd, /*shouldClose=*/true);
executableOp.print(inputStream,
OpPrintingFlags().useLocalScope().enableDebugInfo());
inputStream << "\n"; // newline at end of file
}
// LLVM wants all the args split up to launch the command so we tokenize here.
// This is exactly how the LLVM command line parser does it with a macro
// switch.
llvm::BumpPtrAllocator scratchAllocator;
llvm::StringSaver stringSaver(scratchAllocator);
SmallVector<const char *> rawArgs;
#ifdef _WIN32
auto Tokenize = llvm::cl::TokenizeWindowsCommandLine;
#else
auto Tokenize = llvm::cl::TokenizeGNUCommandLine;
#endif // _WIN32
Tokenize(command, stringSaver, rawArgs, /*MarkEOLs=*/false);
SmallVector<StringRef> args;
for (auto rawArg : rawArgs)
args.push_back(StringRef(rawArg));
// Try to find the tool either by absolute path or by looking it up in env.
auto tool = findTool(args[0].str());
if (tool.empty()) {
llvm::errs() << "ERROR: failed to find tool `" << args[0] << "` in PATH\n";
return failure();
}
LLVM_DEBUG({
llvm::dbgs() << "Launching hal.executable preprocessor: ";
for (auto arg : args)
llvm::dbgs() << arg << " ";
llvm::dbgs() << " 1> " << stdoutFile.str() << " 2> " << stderrFile.str()
<< "\n";
});
// Launch the preprocessing tool. Note that this may fail for tons of reasons
// (bad program path, bad command line, bad system state, bad IO, bad
// preprocessor, etc).
std::string errorMessage;
int runResult = llvm::sys::ExecuteAndWait(
unescapeCommandLineComponent(tool), args, /*Env=*/std::nullopt,
/*Redirects=*/redirects,
/*SecondsToWait=*/0, /*MemoryLimit=*/0, /*ErrMsg=*/&errorMessage);
if (runResult != 0) {
llvm::errs() << "ERROR: preprocessor invocation failed: " << errorMessage
<< "\n";
llvm::errs() << "ERROR: tool stderr preserved at " << stderrFile.str()
<< "\n";
stderrRemover.releaseFile();
return failure();
}
// NOTE: we could check for empty stdout and quickly skip replacement.
// Deserialize the resulting contents.
mlir::ParserConfig parserConfig(executableOp.getContext());
auto parsedOpRef = mlir::parseSourceFile(stdoutFile.str(), parserConfig);
if (!parsedOpRef) {
llvm::errs() << "ERROR: preprocessor failed to parse command output\n";
llvm::errs() << "ERROR: tool stdout preserved at " << stdoutFile.str()
<< "\n";
stdoutRemover.releaseFile();
return failure();
}
// Find the expected executable. This may come back as either an executable
// nested in a module or the executable itself.
IREE::HAL::ExecutableOp replacementOp;
if (auto tryCast = dyn_cast<IREE::HAL::ExecutableOp>(*parsedOpRef)) {
replacementOp = tryCast;
} else if (auto moduleOp = dyn_cast<mlir::ModuleOp>(*parsedOpRef)) {
auto executableOps = moduleOp.getOps<IREE::HAL::ExecutableOp>();
if (!executableOps.empty()) {
replacementOp = *executableOps.begin();
}
}
if (!replacementOp) {
llvm::errs()
<< "ERROR: preprocessor did not output a hal.executable as expected\n";
llvm::errs() << "ERROR: tool stdout preserved at " << stdoutFile.str()
<< "\n";
stdoutRemover.releaseFile();
return failure();
}
// Replace the executable with the contents of the file.
replaceExecutableContents(executableOp, replacementOp);
return success();
}
//===----------------------------------------------------------------------===//
// --iree-hal-preprocess-executables-with-pipeline
//===----------------------------------------------------------------------===//
struct PreprocessExecutablesWithPipelinePass
: public IREE::HAL::impl::PreprocessExecutablesWithPipelinePassBase<
PreprocessExecutablesWithPipelinePass> {
using IREE::HAL::impl::PreprocessExecutablesWithPipelinePassBase<
PreprocessExecutablesWithPipelinePass>::
PreprocessExecutablesWithPipelinePassBase;
void getDependentDialects(DialectRegistry &registry) const override {
registry.insert<IREE::HAL::HALDialect>();
if (pipeline.hasValue()) {
OpPassManager passManager(IREE::HAL::ExecutableOp::getOperationName());
// Can't signal failure here; things will fail during pass execution where
// we can signalPassFailure.
if (succeeded(buildPassPipeline(pipeline, passManager))) {
passManager.getDependentDialects(registry);
}
}
}
void runOnOperation() override {
if (!pipeline.hasValue())
return;
auto executableOp = getOperation();
OpPassManager passManager(executableOp.getOperationName());
if (failed(buildPassPipeline(pipeline, passManager))) {
llvm::errs() << "ERROR: failed to parse preprocessing pipeline `"
<< pipeline << "`\n";
return signalPassFailure();
}
if (failed(runPipeline(passManager, executableOp))) {
llvm::errs() << "ERROR: failed to preprocess executable `"
<< executableOp.getName() << "` using pipeline `" << pipeline
<< "`\n";
return signalPassFailure();
}
}
};
//===----------------------------------------------------------------------===//
// --iree-hal-preprocess-executables-with-tool
//===----------------------------------------------------------------------===//
struct PreprocessExecutablesWithToolPass
: public IREE::HAL::impl::PreprocessExecutablesWithToolPassBase<
PreprocessExecutablesWithToolPass> {
using IREE::HAL::impl::PreprocessExecutablesWithToolPassBase<
PreprocessExecutablesWithToolPass>::PreprocessExecutablesWithToolPassBase;
void runOnOperation() override {
if (!command.hasValue())
return;
auto executableOp = getOperation();
if (failed(preprocessWithCommand(executableOp, command))) {
llvm::errs() << "ERROR: failed to preprocess executable `"
<< executableOp.getName() << "` using command `" << command
<< "`\n";
return signalPassFailure();
}
}
};
} // namespace
std::unique_ptr<Pass> createPreprocessExecutablesPass(std::string rawCommand) {
auto command = fixupArg(rawCommand);
if (command.starts_with("builtin.module")) {
return createPreprocessExecutablesWithPipelinePass({command.str()});
} else {
return createPreprocessExecutablesWithToolPass({command.str()});
}
}
} // namespace mlir::iree_compiler::IREE::HAL