blob: 47e72c96a0fab83080ee19066a2845a46e394e4f [file] [edit]
// Copyright 2026 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 "compiler/plugins/input/StableHLO/Conversion/TargetRegistry.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
namespace mlir::iree_compiler::stablehlo {
#define GEN_PASS_DEF_REWRITEREGISTEREDCUSTOMCALLSANDCOMPOSITESPASS
#include "compiler/plugins/input/StableHLO/Conversion/TargetRegistryPasses.h.inc"
LogicalResult
TargetRegistry::addCustomCall(StringRef name, CustomCallHandler handler,
DialectRegistration registerDialects) {
if (name.empty() || !handler) {
return failure();
}
auto guardedHandler = [handler = std::move(handler)](
mlir::stablehlo::CustomCallOp op,
PatternRewriter &rewriter) -> LogicalResult {
// Tokens and futures can occur inside tuples. Check every leaf so nested
// values cannot bypass the ordering and completion restrictions below.
auto hasOrderingType = [](Type type) {
SmallVector<Type> leaves;
if (auto tuple = dyn_cast<TupleType>(type)) {
tuple.getFlattenedTypes(leaves);
} else {
leaves.push_back(type);
}
return llvm::any_of(leaves, [](Type leaf) {
return isa<mlir::stablehlo::TokenType, mlir::stablehlo::FutureType>(
leaf);
});
};
// Registered dispatch supports pure value rewrites. Effects and
// asynchronous completion need ordering guarantees; aliases need storage
// identity. These contracts are outside this registry, so leave such calls
// unchanged.
if (op.getHasSideEffect() || !op.getOutputOperandAliases().empty() ||
llvm::any_of(op.getOperandTypes(), hasOrderingType) ||
llvm::any_of(op.getResultTypes(), hasOrderingType)) {
return failure();
}
return handler(op, rewriter);
};
return success(
customCalls
.try_emplace(name,
Entry<CustomCallHandler>{std::move(guardedHandler),
std::move(registerDialects)})
.second);
}
LogicalResult
TargetRegistry::addComposite(StringRef name, int32_t version,
CompositeHandler handler,
DialectRegistration registerDialects) {
if (name.empty() || !handler) {
return failure();
}
return success(
composites[name]
.emplace(version,
Entry<CompositeHandler>{std::move(handler),
std::move(registerDialects)})
.second);
}
const TargetRegistry::CustomCallHandler *
TargetRegistry::lookupCustomCall(StringRef name) const {
auto it = customCalls.find(name);
return it == customCalls.end() ? nullptr : &it->second.handler;
}
const TargetRegistry::CompositeHandler *
TargetRegistry::lookupComposite(StringRef name, int32_t version) const {
auto versions = composites.find(name);
if (versions == composites.end()) {
return nullptr;
}
auto it = versions->second.find(version);
return it == versions->second.end() ? nullptr : &it->second.handler;
}
void TargetRegistry::registerDependentDialects(
DialectRegistry &dialects) const {
for (const auto &customCall : customCalls) {
if (customCall.second.registerDialects) {
customCall.second.registerDialects(dialects);
}
}
for (const auto &versions : composites) {
for (const auto &[version, entry] : versions.second) {
if (entry.registerDialects) {
entry.registerDialects(dialects);
}
}
}
}
namespace {
using RegistrySnapshot = std::shared_ptr<const TargetRegistry>;
struct CustomCallDispatch final
: OpRewritePattern<mlir::stablehlo::CustomCallOp> {
CustomCallDispatch(MLIRContext *context, RegistrySnapshot registry)
: OpRewritePattern(context), registry(std::move(registry)) {}
LogicalResult matchAndRewrite(mlir::stablehlo::CustomCallOp op,
PatternRewriter &rewriter) const override {
auto *handler = registry->lookupCustomCall(op.getCallTargetName());
return handler ? (*handler)(op, rewriter) : failure();
}
RegistrySnapshot registry;
};
struct CompositeDispatch final
: OpRewritePattern<mlir::stablehlo::CompositeOp> {
CompositeDispatch(MLIRContext *context, RegistrySnapshot registry)
: OpRewritePattern(context), registry(std::move(registry)) {}
LogicalResult matchAndRewrite(mlir::stablehlo::CompositeOp op,
PatternRewriter &rewriter) const override {
auto *handler = registry->lookupComposite(op.getName(), op.getVersion());
if (!handler) {
return failure();
}
// Ops the handler creates carry the composite's name and numerical profile
// so later diagnostics can name their origin.
std::string provenance = ("composite " + op.getName()).str();
if (auto profile = op.getCompositeAttributes().getAs<StringAttr>(
"numerical_profile")) {
provenance += (" (" + profile.getValue() + ")").str();
}
OpBuilder::Listener *listener = rewriter.getListener();
ProvenanceListener provenanceListener(listener,
rewriter.getStringAttr(provenance));
rewriter.setListener(&provenanceListener);
LogicalResult result = (*handler)(op, rewriter);
rewriter.setListener(listener);
return result;
}
// Adds the composite's name to the locations of newly inserted operations.
// Handlers must use the supplied rewriter: a separate builder would bypass
// both this listener and the greedy rewrite driver.
struct ProvenanceListener final : RewriterBase::ForwardingListener {
ProvenanceListener(OpBuilder::Listener *listener, StringAttr provenance)
: ForwardingListener(listener), provenance(provenance) {}
void notifyOperationInserted(Operation *op,
OpBuilder::InsertPoint previous) override {
// A moved op keeps its location.
if (!previous.isSet()) {
op->setLoc(NameLoc::get(provenance, op->getLoc()));
}
ForwardingListener::notifyOperationInserted(op, previous);
}
StringAttr provenance;
};
RegistrySnapshot registry;
};
void populatePatterns(RewritePatternSet &patterns, RegistrySnapshot registry) {
patterns.add<CustomCallDispatch, CompositeDispatch>(patterns.getContext(),
registry);
}
class RewriteRegisteredCustomCallsAndCompositesPass final
: public impl::RewriteRegisteredCustomCallsAndCompositesPassBase<
RewriteRegisteredCustomCallsAndCompositesPass> {
public:
explicit RewriteRegisteredCustomCallsAndCompositesPass(
const TargetRegistry &registry)
: registry(std::make_shared<const TargetRegistry>(registry)) {}
RewriteRegisteredCustomCallsAndCompositesPass(
const RewriteRegisteredCustomCallsAndCompositesPass &) = default;
void getDependentDialects(DialectRegistry &dialects) const override {
Base::getDependentDialects(dialects);
registry->registerDependentDialects(dialects);
}
void runOnOperation() override {
RewritePatternSet patterns(&getContext());
populatePatterns(patterns, registry);
if (failed(applyPatternsGreedily(getOperation(), std::move(patterns)))) {
signalPassFailure();
}
}
private:
RegistrySnapshot registry;
};
} // namespace
void populateTargetRewritePatterns(RewritePatternSet &patterns,
const TargetRegistry &registry) {
populatePatterns(patterns, std::make_shared<const TargetRegistry>(registry));
}
std::unique_ptr<Pass> createRewriteRegisteredCustomCallsAndCompositesPass(
const TargetRegistry &registry) {
return std::make_unique<RewriteRegisteredCustomCallsAndCompositesPass>(
registry);
}
} // namespace mlir::iree_compiler::stablehlo