blob: 9a7d5403a95dc53e6386439a924ac9cc225c2f6b [file] [log] [blame]
//===- InlinerPass.cpp - Pass to inline function calls --------------------===//
//
// Part of the LLVM Project, 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
//
//===----------------------------------------------------------------------===//
//
// This file implements a basic inlining algorithm that operates bottom up over
// the Strongly Connect Components(SCCs) of the CallGraph. This enables a more
// incremental propagation of inlining decisions from the leafs to the roots of
// the callgraph.
//
//===----------------------------------------------------------------------===//
#include "mlir/Transforms/Passes.h"
#include "mlir/Analysis/CallGraph.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Transforms/Inliner.h"
namespace mlir {
#define GEN_PASS_DEF_INLINER
#include "mlir/Transforms/Passes.h.inc"
} // namespace mlir
#define DEBUG_TYPE "inliner-pass"
using namespace mlir;
/// This function implements the inliner optimization pipeline.
static void defaultInlinerOptPipeline(OpPassManager &pm) {
pm.addPass(createCanonicalizerPass());
}
//===----------------------------------------------------------------------===//
// InlinerPass
//===----------------------------------------------------------------------===//
namespace {
class InlinerPass : public impl::InlinerBase<InlinerPass> {
public:
InlinerPass();
InlinerPass(const InlinerPass &) = default;
InlinerPass(std::function<void(OpPassManager &)> defaultPipeline);
InlinerPass(std::function<void(OpPassManager &)> defaultPipeline,
llvm::StringMap<OpPassManager> opPipelines);
void runOnOperation() override;
/// A callback provided to the inliner driver to execute
/// the specified pass pipeline on the given operation
/// within the context of the current inliner pass,
/// which is passed as the first argument.
/// runPipeline API is protected within the Pass class,
/// so this helper is required to call it from the foreign
/// inliner driver.
static LogicalResult runPipelineHelper(Pass &pass, OpPassManager &pipeline,
Operation *op) {
return mlir::cast<InlinerPass>(pass).runPipeline(pipeline, op);
}
private:
/// Attempt to initialize the options of this pass from the given string.
/// Derived classes may override this method to hook into the point at which
/// options are initialized, but should generally always invoke this base
/// class variant.
LogicalResult initializeOptions(StringRef options) override;
/// Inliner configuration parameters created from the pass options.
InlinerConfig config;
};
} // namespace
InlinerPass::InlinerPass() : InlinerPass(defaultInlinerOptPipeline) {}
InlinerPass::InlinerPass(
std::function<void(OpPassManager &)> defaultPipelineArg)
: InlinerPass(std::move(defaultPipelineArg),
llvm::StringMap<OpPassManager>{}) {}
InlinerPass::InlinerPass(std::function<void(OpPassManager &)> defaultPipeline,
llvm::StringMap<OpPassManager> opPipelines)
: config(std::move(defaultPipeline), maxInliningIterations) {
if (opPipelines.empty())
return;
// Update the option for the op specific optimization pipelines.
for (auto &it : opPipelines)
opPipelineList.addValue(it.second);
config.setOpPipelines(std::move(opPipelines));
}
// Return true if the inlining ratio does not exceed the threshold.
static bool isProfitableToInline(const Inliner::ResolvedCall &resolvedCall,
unsigned inliningThreshold) {
// Return early, ratio <= 0U will always be false.
if (inliningThreshold == 0U)
return false;
// Return early, ratio <= -1U will always be true.
if (inliningThreshold == -1U)
return true;
Region *callerRegion = resolvedCall.sourceNode->getCallableRegion();
Region *calleeRegion = resolvedCall.targetNode->getCallableRegion();
// We should not get external nodes here, but just return true
// for now to preserve the original behavior of the inliner pass.
if (!callerRegion || !calleeRegion)
return true;
auto countOps = [](Region *region) {
unsigned count = 0;
region->walk([&](Operation *) { ++count; });
return count;
};
unsigned callerOps = countOps(callerRegion);
// Always inline empty callees (if it is possible at all).
if (callerOps == 0)
return true;
unsigned ratio = countOps(calleeRegion) * 100 / callerOps;
LLVM_DEBUG(llvm::dbgs() << "Callee / caller operation ratio (max: "
<< inliningThreshold << "%): " << ratio << "%\n");
return ratio <= inliningThreshold;
}
void InlinerPass::runOnOperation() {
CallGraph &cg = getAnalysis<CallGraph>();
// The inliner should only be run on operations that define a symbol table,
// as the callgraph will need to resolve references.
Operation *op = getOperation();
if (!op->hasTrait<OpTrait::SymbolTable>()) {
op->emitOpError() << " was scheduled to run under the inliner, but does "
"not define a symbol table";
return signalPassFailure();
}
// By default, assume that any inlining is profitable.
auto profitabilityCb = [=](const Inliner::ResolvedCall &call) {
return isProfitableToInline(call, inliningThreshold);
};
// Get an instance of the inliner.
Inliner inliner(op, cg, *this, getAnalysisManager(), runPipelineHelper,
config, profitabilityCb);
// Run the inlining.
if (failed(inliner.doInlining()))
signalPassFailure();
return;
}
LogicalResult InlinerPass::initializeOptions(StringRef options) {
if (failed(Pass::initializeOptions(options)))
return failure();
// Initialize the pipeline builder for operations without the dedicated
// optimization pipeline in opPipelineList to use the option string.
// TODO: Use a generic pass manager for the pre-inline pipeline, and remove
// this.
if (!defaultPipelineStr.empty()) {
std::string defaultPipelineCopy = defaultPipelineStr;
config.setDefaultPipeline([=](OpPassManager &pm) {
(void)parsePassPipeline(defaultPipelineCopy, pm);
});
} else if (defaultPipelineStr.getNumOccurrences()) {
config.setDefaultPipeline(nullptr);
}
// Initialize the op specific pass pipelines.
llvm::StringMap<OpPassManager> pipelines;
for (OpPassManager pipeline : opPipelineList)
if (!pipeline.empty())
pipelines.try_emplace(pipeline.getOpAnchorName(), pipeline);
config.setOpPipelines(std::move(pipelines));
config.setMaxInliningIterations(maxInliningIterations);
return success();
}
std::unique_ptr<Pass> mlir::createInlinerPass() {
return std::make_unique<InlinerPass>();
}
std::unique_ptr<Pass>
mlir::createInlinerPass(llvm::StringMap<OpPassManager> opPipelines) {
return std::make_unique<InlinerPass>(defaultInlinerOptPipeline,
std::move(opPipelines));
}
std::unique_ptr<Pass> mlir::createInlinerPass(
llvm::StringMap<OpPassManager> opPipelines,
std::function<void(OpPassManager &)> defaultPipelineBuilder) {
return std::make_unique<InlinerPass>(std::move(defaultPipelineBuilder),
std::move(opPipelines));
}