llvm-project
283 строки · 10.3 Кб
1//===-- SROA.cpp - Scalar Replacement Of Aggregates -------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "mlir/Transforms/SROA.h"10#include "mlir/Analysis/DataLayoutAnalysis.h"11#include "mlir/Analysis/SliceAnalysis.h"12#include "mlir/Analysis/TopologicalSortUtils.h"13#include "mlir/Interfaces/MemorySlotInterfaces.h"14#include "mlir/Transforms/Passes.h"15
16namespace mlir {17#define GEN_PASS_DEF_SROA18#include "mlir/Transforms/Passes.h.inc"19} // namespace mlir20
21#define DEBUG_TYPE "sroa"22
23using namespace mlir;24
25namespace {26
27/// Information computed by destructurable memory slot analysis used to perform
28/// actual destructuring of the slot. This struct is only constructed if
29/// destructuring is possible, and contains the necessary data to perform it.
30struct MemorySlotDestructuringInfo {31/// Set of the indices that are actually used when accessing the subelements.32SmallPtrSet<Attribute, 8> usedIndices;33/// Blocking uses of a given user of the memory slot that must be eliminated.34DenseMap<Operation *, SmallPtrSet<OpOperand *, 4>> userToBlockingUses;35/// List of potentially indirect accessors of the memory slot that need36/// rewiring.37SmallVector<DestructurableAccessorOpInterface> accessors;38};39
40} // namespace41
42/// Computes information for slot destructuring. This will compute whether this
43/// slot can be destructured and data to perform the destructuring. Returns
44/// nothing if the slot cannot be destructured or if there is no useful work to
45/// be done.
46static std::optional<MemorySlotDestructuringInfo>47computeDestructuringInfo(DestructurableMemorySlot &slot,48const DataLayout &dataLayout) {49assert(isa<DestructurableTypeInterface>(slot.elemType));50
51if (slot.ptr.use_empty())52return {};53
54MemorySlotDestructuringInfo info;55
56SmallVector<MemorySlot> usedSafelyWorklist;57
58auto scheduleAsBlockingUse = [&](OpOperand &use) {59SmallPtrSetImpl<OpOperand *> &blockingUses =60info.userToBlockingUses.getOrInsertDefault(use.getOwner());61blockingUses.insert(&use);62};63
64// Initialize the analysis with the immediate users of the slot.65for (OpOperand &use : slot.ptr.getUses()) {66if (auto accessor =67dyn_cast<DestructurableAccessorOpInterface>(use.getOwner())) {68if (accessor.canRewire(slot, info.usedIndices, usedSafelyWorklist,69dataLayout)) {70info.accessors.push_back(accessor);71continue;72}73}74
75// If it cannot be shown that the operation uses the slot safely, maybe it76// can be promoted out of using the slot?77scheduleAsBlockingUse(use);78}79
80SmallPtrSet<OpOperand *, 16> visited;81while (!usedSafelyWorklist.empty()) {82MemorySlot mustBeUsedSafely = usedSafelyWorklist.pop_back_val();83for (OpOperand &subslotUse : mustBeUsedSafely.ptr.getUses()) {84if (!visited.insert(&subslotUse).second)85continue;86Operation *subslotUser = subslotUse.getOwner();87
88if (auto memOp = dyn_cast<SafeMemorySlotAccessOpInterface>(subslotUser))89if (succeeded(memOp.ensureOnlySafeAccesses(90mustBeUsedSafely, usedSafelyWorklist, dataLayout)))91continue;92
93// If it cannot be shown that the operation uses the slot safely, maybe it94// can be promoted out of using the slot?95scheduleAsBlockingUse(subslotUse);96}97}98
99SetVector<Operation *> forwardSlice;100mlir::getForwardSlice(slot.ptr, &forwardSlice);101for (Operation *user : forwardSlice) {102// If the next operation has no blocking uses, everything is fine.103if (!info.userToBlockingUses.contains(user))104continue;105
106SmallPtrSet<OpOperand *, 4> &blockingUses = info.userToBlockingUses[user];107auto promotable = dyn_cast<PromotableOpInterface>(user);108
109// An operation that has blocking uses must be promoted. If it is not110// promotable, destructuring must fail.111if (!promotable)112return {};113
114SmallVector<OpOperand *> newBlockingUses;115// If the operation decides it cannot deal with removing the blocking uses,116// destructuring must fail.117if (!promotable.canUsesBeRemoved(blockingUses, newBlockingUses, dataLayout))118return {};119
120// Then, register any new blocking uses for coming operations.121for (OpOperand *blockingUse : newBlockingUses) {122assert(llvm::is_contained(user->getResults(), blockingUse->get()));123
124SmallPtrSetImpl<OpOperand *> &newUserBlockingUseSet =125info.userToBlockingUses.getOrInsertDefault(blockingUse->getOwner());126newUserBlockingUseSet.insert(blockingUse);127}128}129
130return info;131}
132
133/// Performs the destructuring of a destructible slot given associated
134/// destructuring information. The provided slot will be destructured in
135/// subslots as specified by its allocator.
136static void destructureSlot(137DestructurableMemorySlot &slot,138DestructurableAllocationOpInterface allocator, OpBuilder &builder,139const DataLayout &dataLayout, MemorySlotDestructuringInfo &info,140SmallVectorImpl<DestructurableAllocationOpInterface> &newAllocators,141const SROAStatistics &statistics) {142OpBuilder::InsertionGuard guard(builder);143
144builder.setInsertionPointToStart(slot.ptr.getParentBlock());145DenseMap<Attribute, MemorySlot> subslots =146allocator.destructure(slot, info.usedIndices, builder, newAllocators);147
148if (statistics.slotsWithMemoryBenefit &&149slot.subelementTypes.size() != info.usedIndices.size())150(*statistics.slotsWithMemoryBenefit)++;151
152if (statistics.maxSubelementAmount)153statistics.maxSubelementAmount->updateMax(slot.subelementTypes.size());154
155SetVector<Operation *> usersToRewire;156for (Operation *user : llvm::make_first_range(info.userToBlockingUses))157usersToRewire.insert(user);158for (DestructurableAccessorOpInterface accessor : info.accessors)159usersToRewire.insert(accessor);160usersToRewire = mlir::topologicalSort(usersToRewire);161
162llvm::SmallVector<Operation *> toErase;163for (Operation *toRewire : llvm::reverse(usersToRewire)) {164builder.setInsertionPointAfter(toRewire);165if (auto accessor = dyn_cast<DestructurableAccessorOpInterface>(toRewire)) {166if (accessor.rewire(slot, subslots, builder, dataLayout) ==167DeletionKind::Delete)168toErase.push_back(accessor);169continue;170}171
172auto promotable = cast<PromotableOpInterface>(toRewire);173if (promotable.removeBlockingUses(info.userToBlockingUses[promotable],174builder) == DeletionKind::Delete)175toErase.push_back(promotable);176}177
178for (Operation *toEraseOp : toErase)179toEraseOp->erase();180
181assert(slot.ptr.use_empty() && "after destructuring, the original slot "182"pointer should no longer be used");183
184LLVM_DEBUG(llvm::dbgs() << "[sroa] Destructured memory slot: " << slot.ptr185<< "\n");186
187if (statistics.destructuredAmount)188(*statistics.destructuredAmount)++;189
190std::optional<DestructurableAllocationOpInterface> newAllocator =191allocator.handleDestructuringComplete(slot, builder);192// Add newly created allocators to the worklist for further processing.193if (newAllocator)194newAllocators.push_back(*newAllocator);195}
196
197LogicalResult mlir::tryToDestructureMemorySlots(198ArrayRef<DestructurableAllocationOpInterface> allocators,199OpBuilder &builder, const DataLayout &dataLayout,200SROAStatistics statistics) {201bool destructuredAny = false;202
203SmallVector<DestructurableAllocationOpInterface> workList(allocators.begin(),204allocators.end());205SmallVector<DestructurableAllocationOpInterface> newWorkList;206newWorkList.reserve(allocators.size());207// Destructuring a slot can allow for further destructuring of other208// slots, destructuring is tried until no destructuring succeeds.209while (true) {210bool changesInThisRound = false;211
212for (DestructurableAllocationOpInterface allocator : workList) {213bool destructuredAnySlot = false;214for (DestructurableMemorySlot slot : allocator.getDestructurableSlots()) {215std::optional<MemorySlotDestructuringInfo> info =216computeDestructuringInfo(slot, dataLayout);217if (!info)218continue;219
220destructureSlot(slot, allocator, builder, dataLayout, *info,221newWorkList, statistics);222destructuredAnySlot = true;223
224// A break is required, since destructuring a slot may invalidate the225// remaning slots of an allocator.226break;227}228if (!destructuredAnySlot)229newWorkList.push_back(allocator);230changesInThisRound |= destructuredAnySlot;231}232
233if (!changesInThisRound)234break;235destructuredAny |= changesInThisRound;236
237// Swap the vector's backing memory and clear the entries in newWorkList238// afterwards. This ensures that additional heap allocations can be avoided.239workList.swap(newWorkList);240newWorkList.clear();241}242
243return success(destructuredAny);244}
245
246namespace {247
248struct SROA : public impl::SROABase<SROA> {249using impl::SROABase<SROA>::SROABase;250
251void runOnOperation() override {252Operation *scopeOp = getOperation();253
254SROAStatistics statistics{&destructuredAmount, &slotsWithMemoryBenefit,255&maxSubelementAmount};256
257auto &dataLayoutAnalysis = getAnalysis<DataLayoutAnalysis>();258const DataLayout &dataLayout = dataLayoutAnalysis.getAtOrAbove(scopeOp);259bool changed = false;260
261for (Region ®ion : scopeOp->getRegions()) {262if (region.getBlocks().empty())263continue;264
265OpBuilder builder(®ion.front(), region.front().begin());266
267SmallVector<DestructurableAllocationOpInterface> allocators;268// Build a list of allocators to attempt to destructure the slots of.269region.walk([&](DestructurableAllocationOpInterface allocator) {270allocators.emplace_back(allocator);271});272
273// Attempt to destructure as many slots as possible.274if (succeeded(tryToDestructureMemorySlots(allocators, builder, dataLayout,275statistics)))276changed = true;277}278if (!changed)279markAllAnalysesPreserved();280}281};282
283} // namespace284