llvm-project/mlir/lib/Transforms/InlinerPass.cpp
Fabian Tschopp 5d187898f6
[mlir][inliner] Return early if the inliningThreshold is 0U or -1U. (#86287)
Computing the inlinling profitability can be costly due to walking the
graph when counting the number of operations.

This PR addresses that by returning early if the threshold is set to
never or always inline.
2024-03-23 00:09:11 +01:00

199 lines
7.1 KiB
C++

//===- 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));
}