blob: b0c237507d2f3b30b4c2c0573e9edf40157d3560 [file] [edit]
//===- StaticMemoryPlanning.cpp - Memory planning algorithms --------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/Bufferization/Transforms/StaticMemoryPlanning.h"
#include "llvm/Support/MathExtras.h"
#include <numeric>
using namespace mlir::bufferization;
/// Align an offset to the specified alignment.
static int64_t alignOffset(int64_t offset, int64_t alignment) {
return llvm::alignTo(offset, alignment);
}
/// Trivial sequential packing: places each allocation immediately after the
/// previous one with alignment padding. Does not consider lifetimes, so no
/// memory is reused. This gives a simple upper bound on arena size.
/// Complexity: O(n) where n is the number of allocations.
llvm::SmallVector<int64_t> mlir::bufferization::trivialMemoryPlanner(
int64_t arenaAlignment, llvm::ArrayRef<MemoryPlannerAlloc> allocs) {
llvm::SmallVector<int64_t> offsets;
int64_t currentOffset = 0;
for (const auto &alloc : allocs) {
currentOffset = alignOffset(currentOffset, alloc.alignment);
assert((arenaAlignment + currentOffset) % alloc.alignment == 0 &&
"invalid alignment");
offsets.push_back(currentOffset);
currentOffset += alloc.sizeInBytes;
}
return offsets;
}
/// Best-fit lifetime-aware packing: processes allocations in start-time order
/// and places each one in the smallest gap left by allocations whose lifetimes
/// have ended. If no existing gap is large enough, the arena is extended.
/// This minimizes peak memory usage when allocations have non-overlapping
/// lifetimes.
/// Complexity: O(n^2) where n is the number of allocations.
llvm::SmallVector<int64_t> mlir::bufferization::bestFitMemoryPlanner(
int64_t arenaAlignment, llvm::ArrayRef<MemoryPlannerAlloc> allocs) {
// Tracks where each allocation was placed. We only need timeEnd because
// allocations are processed in timeStart order — by the time we place a new
// allocation, all earlier placements already started, so we only need to
// check which ones are still live.
struct Placement {
int64_t offset;
int64_t size;
int64_t timeEnd;
};
// Process allocations in order of start time.
llvm::SmallVector<unsigned> order(allocs.size());
std::iota(order.begin(), order.end(), 0);
llvm::sort(order, [&](unsigned a, unsigned b) {
return allocs[a].timeStart < allocs[b].timeStart;
});
llvm::SmallVector<Placement> placements;
llvm::SmallVector<int64_t> offsets(allocs.size(), 0);
int64_t arenaEnd = 0;
// Loop over all required allocations and fit them into best gaps.
for (unsigned idx : order) {
const MemoryPlannerAlloc &alloc = allocs[idx];
// Collect allocations that are still live at this alloc's start time.
// occupied is pairs of <offset_start, offset_end>.
llvm::SmallVector<std::pair<int64_t, int64_t>> occupied;
for (const auto &p : placements) {
if (p.timeEnd > alloc.timeStart)
occupied.push_back({p.offset, p.offset + p.size});
}
llvm::sort(occupied);
// Find the best (smallest) gap that fits this allocation.
int64_t bestOffset = -1;
int64_t bestGapSize = INT64_MAX;
int64_t gapStart = 0;
for (const auto &[occStart, occEnd] : occupied) {
int64_t alignedStart = alignOffset(gapStart, alloc.alignment);
if (alignedStart >= occStart) {
gapStart = std::max(gapStart, occEnd);
continue;
}
int64_t gapEnd = occStart;
int64_t gapSize = gapEnd - alignedStart;
// Gap is large enough to fit this allocation (after alignment).
if (gapSize >= alloc.sizeInBytes) {
// Track the smallest sufficient gap (best-fit strategy).
if (gapSize < bestGapSize) {
bestGapSize = gapSize;
bestOffset = alignedStart;
}
}
gapStart = std::max(gapStart, occEnd);
}
// Check the trailing gap (between last occupied region and arena end).
// This is only a reuse candidate if the allocation fits within the current
// arena bounds. If it doesn't fit, placing here would extend the arena —
// that case is handled by the fallback below (bestOffset < 0).
int64_t alignedTrailing = alignOffset(gapStart, alloc.alignment);
if (alignedTrailing + alloc.sizeInBytes <= arenaEnd) {
int64_t trailingSize = arenaEnd - alignedTrailing;
if (trailingSize < bestGapSize) {
bestGapSize = trailingSize;
bestOffset = alignedTrailing;
}
}
// If no existing gap worked, append at the end.
if (bestOffset < 0)
bestOffset = alignedTrailing;
assert((arenaAlignment + bestOffset) % alloc.alignment == 0 &&
"invalid alignment");
offsets[idx] = bestOffset;
placements.push_back({bestOffset, alloc.sizeInBytes, alloc.timeEnd});
arenaEnd = std::max(arenaEnd, bestOffset + alloc.sizeInBytes);
}
return offsets;
}