[Mlir-commits] [mlir] [mlir][bufferization] Add best-fit algorithm to static memory planner (PR #207403)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Fri Jul 3 07:31:05 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-bufferization

Author: Javed Absar (javedabsar1)

<details>
<summary>Changes</summary>

Introduces an algorithm selection option to the static memory planner pass and
adds a best-fit algorithm that reuses memory from expired allocations by finding
the smallest suitable gap.

The planning algorithms are factored into a separate StaticMemoryPlanning.{h,cpp}
to keep them independent of MLIR IR and easily testable.

---

Patch is 22.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/207403.diff


7 Files Affected:

- (modified) mlir/include/mlir/Dialect/Bufferization/IR/BufferizationEnums.td (+8) 
- (modified) mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td (+21-10) 
- (added) mlir/include/mlir/Dialect/Bufferization/Transforms/StaticMemoryPlanning.h (+50) 
- (modified) mlir/lib/Dialect/Bufferization/Transforms/CMakeLists.txt (+1) 
- (modified) mlir/lib/Dialect/Bufferization/Transforms/StaticMemoryPlannerAnalysis.cpp (+35-62) 
- (added) mlir/lib/Dialect/Bufferization/Transforms/StaticMemoryPlanning.cpp (+111) 
- (added) mlir/test/Dialect/Bufferization/Transforms/static-memory-planner-best-fit.mlir (+118) 


``````````diff
diff --git a/mlir/include/mlir/Dialect/Bufferization/IR/BufferizationEnums.td b/mlir/include/mlir/Dialect/Bufferization/IR/BufferizationEnums.td
index bafa84645e57b..a8cad26e9341b 100644
--- a/mlir/include/mlir/Dialect/Bufferization/IR/BufferizationEnums.td
+++ b/mlir/include/mlir/Dialect/Bufferization/IR/BufferizationEnums.td
@@ -24,4 +24,12 @@ def LayoutMapOption : I32EnumAttr<"LayoutMapOption",
   let cppNamespace = "::mlir::bufferization";
 }
 
+def MemoryPlannerAlgorithm : I32EnumAttr<"MemoryPlannerAlgorithm",
+                                          "memory planning algorithm", [
+  I32EnumAttrCase<"Trivial", 0, "trivial">,
+  I32EnumAttrCase<"BestFit", 1, "best-fit">
+]> {
+  let cppNamespace = "::mlir::bufferization";
+}
+
 #endif // BUFFERIZATION_ENUMS
diff --git a/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td b/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td
index c7f6cae571e40..8408315dda607 100644
--- a/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td
@@ -197,13 +197,11 @@ def StaticMemoryPlannerAnalysisPass
     - Unique same-block `memref.dealloc`.
     - Allocations in nested blocks are ignored for now.
 
-    Eligible allocations are packed into a single arena. Currently, we use a
-    trivial sequential allocation strategy with alignment padding. But the
-    interface will also allow any packing algorithm to be plugged in as long as
-    it respects the interface. The arena is an i8 byte buffer
-    (`memref<Nxi8>`) that can hold allocations of different element types.
-    Each original allocation is replaced with a `memref.view` operation that
-    creates a typed view into the arena at the computed offset.
+    Eligible allocations are packed into a single arena using a configurable
+    planning algorithm (see the `algorithm` option). The arena is an i8 byte
+    buffer (`memref<Nxi8>`) that can hold allocations of different element
+    types. Each original allocation is replaced with a `memref.view` operation
+    that creates a typed view into the arena at the computed offset.
 
     Ineligible allocations are skipped and retain their original
     alloc/dealloc operations. Skip reasons are reported via op remarks.
@@ -234,11 +232,24 @@ def StaticMemoryPlannerAnalysisPass
     ```
   }];
 
-  let options = [Option<
-      "arenaMode", "arena-mode", "std::string",
+  let options = [
+    Option<"arenaMode", "arena-mode", "std::string",
       /*default=*/"\"allocate\"",
       "Arena allocation mode: 'allocate' creates arena via AllocOp, "
-      "'arg' extracts arena from function arguments">];
+      "'arg' extracts arena from function arguments">,
+    Option<"algorithm", "algorithm",
+      "::mlir::bufferization::MemoryPlannerAlgorithm",
+      /*default=*/"::mlir::bufferization::MemoryPlannerAlgorithm::Trivial",
+      "Memory planning algorithm to use.",
+      [{::llvm::cl::values(
+        clEnumValN(::mlir::bufferization::MemoryPlannerAlgorithm::Trivial,
+                   "trivial",
+                   "Sequential packing without lifetime overlap"),
+        clEnumValN(::mlir::bufferization::MemoryPlannerAlgorithm::BestFit,
+                   "best-fit",
+                   "Best-fit packing with lifetime-aware gap reuse")
+      )}]>,
+  ];
 
   let statistics =
       [Statistic<"numEligible", "num-eligible",
diff --git a/mlir/include/mlir/Dialect/Bufferization/Transforms/StaticMemoryPlanning.h b/mlir/include/mlir/Dialect/Bufferization/Transforms/StaticMemoryPlanning.h
new file mode 100644
index 0000000000000..626e384ce4642
--- /dev/null
+++ b/mlir/include/mlir/Dialect/Bufferization/Transforms/StaticMemoryPlanning.h
@@ -0,0 +1,50 @@
+//===- StaticMemoryPlanning.h - Memory planning algorithms ------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Pure memory planning algorithms for static arena allocation. These operate
+// on abstract allocation descriptors (size, alignment, lifetime) and produce
+// byte offsets. They are independent of MLIR IR.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_DIALECT_BUFFERIZATION_TRANSFORMS_STATICMEMORYPLANNING_H
+#define MLIR_DIALECT_BUFFERIZATION_TRANSFORMS_STATICMEMORYPLANNING_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallVector.h"
+#include <cstdint>
+
+namespace mlir {
+namespace bufferization {
+
+/// Descriptor for a single allocation to be placed by the memory planner.
+struct MemoryPlannerAlloc {
+  int64_t sizeInBytes = 0;
+  int64_t alignment = 1;
+  int64_t timeStart = 0; // Operation index when allocation becomes live
+  int64_t timeEnd = 0;   // Operation index when allocation is freed
+};
+
+/// Sequential packing without lifetime overlap. Each allocation is placed
+/// immediately after the previous one (with alignment padding). Ignores
+/// lifetimes entirely.
+llvm::SmallVector<int64_t>
+trivialMemoryPlanner(int64_t arenaAlignment,
+                     llvm::ArrayRef<MemoryPlannerAlloc> allocs);
+
+/// Best-fit packing with lifetime-aware gap reuse. Processes allocations in
+/// time order and places each one in the smallest gap left by expired
+/// allocations. Falls back to extending the arena if no gap fits.
+llvm::SmallVector<int64_t>
+bestFitMemoryPlanner(int64_t arenaAlignment,
+                     llvm::ArrayRef<MemoryPlannerAlloc> allocs);
+
+} // namespace bufferization
+} // namespace mlir
+
+#endif // MLIR_DIALECT_BUFFERIZATION_TRANSFORMS_STATICMEMORYPLANNING_H
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/CMakeLists.txt b/mlir/lib/Dialect/Bufferization/Transforms/CMakeLists.txt
index 5df9f19a5e30a..006fcd1ce0ec7 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/Bufferization/Transforms/CMakeLists.txt
@@ -15,6 +15,7 @@ add_mlir_dialect_library(MLIRBufferizationTransforms
   OwnershipBasedBufferDeallocation.cpp
   TensorCopyInsertion.cpp
   OptimizeAllocationLiveness.cpp
+  StaticMemoryPlanning.cpp
   StaticMemoryPlannerAnalysis.cpp
 
   ADDITIONAL_HEADER_DIRS
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/StaticMemoryPlannerAnalysis.cpp b/mlir/lib/Dialect/Bufferization/Transforms/StaticMemoryPlannerAnalysis.cpp
index 48b6e97c809ef..c695230870bde 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/StaticMemoryPlannerAnalysis.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/StaticMemoryPlannerAnalysis.cpp
@@ -7,18 +7,18 @@
 //===----------------------------------------------------------------------===//
 //
 // Transforms memref.alloc/memref.dealloc pairs into a single arena allocation
-// with memref.view. Uses simple sequential offset assignment where each
-// allocation gets its own space without overlap (baseline algorithm).
+// with memref.view. Delegates offset computation to planning algorithms in
+// StaticMemoryPlanning.h.
 //
 //===----------------------------------------------------------------------===//
 
 #include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/Dialect/Bufferization/Transforms/Passes.h"
+#include "mlir/Dialect/Bufferization/Transforms/StaticMemoryPlanning.h"
 #include "mlir/Dialect/MemRef/IR/MemRef.h"
 #include "mlir/IR/Builders.h"
 #include "mlir/Interfaces/FunctionInterfaces.h"
 #include "llvm/Support/Debug.h"
-#include "llvm/Support/MathExtras.h"
 #include <numeric>
 
 #define DEBUG_TYPE "static-memory-planner"
@@ -34,19 +34,6 @@ using namespace mlir;
 
 namespace {
 
-//===----------------------------------------------------------------------===//
-// Data structures
-//===----------------------------------------------------------------------===//
-
-/// Allocation info for memory planning (independent of MLIR).
-/// This can be used with pure planning algorithms.
-struct Alloc {
-  int64_t sizeInBytes = 0; // Size in bytes
-  int64_t alignment = 1;   // Required alignment in bytes
-  int64_t timeStart = 0;   // Operation index when allocation starts
-  int64_t timeEnd = 0;     // Operation index when allocation ends (dealloc)
-};
-
 /// A candidate allocation with its matching deallocation and assigned offset.
 struct AllocationCandidate {
   memref::AllocOp alloc;
@@ -81,38 +68,6 @@ static int64_t computeSizeInBytes(MemRefType memrefType) {
   return (numElements * elementSizeInBits + 7) / 8; // Round up to bytes
 }
 
-/// Align an offset to the specified alignment.
-/// Returns the smallest value >= offset that is a multiple of alignment.
-static int64_t alignOffset(int64_t offset, int64_t alignment) {
-  return llvm::alignTo(offset, alignment);
-}
-
-//===----------------------------------------------------------------------===//
-// Memory Planning Algorithms
-//===----------------------------------------------------------------------===//
-
-/// Simple sequential memory planner (baseline algorithm).
-/// arenaAlignment must be a multiple (LCM) of all alloc.alignment values.
-/// Allocates each buffer one after another with proper alignment padding.
-/// Returns offsets in bytes for each allocation.
-static SmallVector<int64_t> trivialMemoryPlanner(int64_t arenaAlignment,
-                                                 ArrayRef<Alloc> allocs) {
-  SmallVector<int64_t> offsets;
-  int64_t currentOffset = 0;
-
-  for (const auto &alloc : allocs) {
-    currentOffset = alignOffset(currentOffset, alloc.alignment);
-#ifndef NDEBUG
-    assert((arenaAlignment + currentOffset) % alloc.alignment == 0 &&
-           "invalid alignment");
-#endif
-    offsets.push_back(currentOffset);
-    currentOffset += alloc.sizeInBytes;
-  }
-
-  return offsets;
-}
-
 //===----------------------------------------------------------------------===//
 // StaticMemoryPlannerAnalysisPass
 //===----------------------------------------------------------------------===//
@@ -137,7 +92,7 @@ struct StaticMemoryPlannerAnalysisPass
       }
     }
 
-    // Step 1: Collect eligible allocation candidates
+    // Step 1: Collect eligible allocation candidates.
     SmallVector<AllocationCandidate> candidates;
 
     funcOp->walk([&](memref::AllocOp allocOp) {
@@ -174,22 +129,40 @@ struct StaticMemoryPlannerAnalysisPass
     if (candidates.empty())
       return;
 
-    // Step 2: Prepare allocation info for planner
-    SmallVector<Alloc> allocInfos;
+    // Step 2: Build allocation descriptors with lifetime info.
+    SmallVector<bufferization::MemoryPlannerAlloc> allocInfos;
     int64_t arenaAlignment = 1;
-    for (const auto &candidate : candidates) {
-      Alloc allocInfo;
-      allocInfo.sizeInBytes = candidate.sizeInBytes;
-      allocInfo.alignment = candidate.alignment;
-      allocInfos.push_back(allocInfo);
+    for (auto &candidate : candidates) {
+      bufferization::MemoryPlannerAlloc info;
+      info.sizeInBytes = candidate.sizeInBytes;
+      info.alignment = candidate.alignment;
+
+      Block *block = candidate.alloc->getBlock();
+      int64_t opIdx = 0;
+      for (Operation &op : *block) {
+        if (&op == candidate.alloc.getOperation())
+          info.timeStart = opIdx;
+        if (&op == candidate.dealloc.getOperation())
+          info.timeEnd = opIdx;
+        ++opIdx;
+      }
+
+      allocInfos.push_back(info);
       arenaAlignment = std::lcm(arenaAlignment, candidate.alignment);
     }
 
-    // Step 3: Run the planning algorithm
-    SmallVector<int64_t> offsets =
-        trivialMemoryPlanner(arenaAlignment, allocInfos);
+    // Step 3: Run the planning algorithm.
+    SmallVector<int64_t> offsets;
+    switch (algorithm) {
+    case bufferization::MemoryPlannerAlgorithm::Trivial:
+      offsets = bufferization::trivialMemoryPlanner(arenaAlignment, allocInfos);
+      break;
+    case bufferization::MemoryPlannerAlgorithm::BestFit:
+      offsets = bufferization::bestFitMemoryPlanner(arenaAlignment, allocInfos);
+      break;
+    }
 
-    // Assign computed offsets back to candidates
+    // Step 4: Compute total arena size and assign offsets.
     int64_t totalSize = 0;
     for (size_t i = 0; i < candidates.size(); ++i) {
       candidates[i].offset = offsets[i];
@@ -200,7 +173,7 @@ struct StaticMemoryPlannerAnalysisPass
                  << " alignment=" << candidates[i].alignment << "\n");
     }
 
-    // Step 4: Obtain arena based on arena mode
+    // Step 5: Obtain arena based on arena mode.
     Operation *firstAlloc = candidates.front().alloc;
     OpBuilder builder(firstAlloc);
     Value arenaValue;
@@ -242,7 +215,7 @@ struct StaticMemoryPlannerAnalysisPass
       return signalPassFailure();
     }
 
-    // Step 5: Replace each alloc with memref.view directly on arena
+    // Step 6: Replace each alloc with memref.view into the arena.
     for (auto &candidate : candidates) {
       builder.setInsertionPoint(candidate.alloc);
       Location loc = candidate.alloc.getLoc();
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/StaticMemoryPlanning.cpp b/mlir/lib/Dialect/Bufferization/Transforms/StaticMemoryPlanning.cpp
new file mode 100644
index 0000000000000..e1779cb8e5831
--- /dev/null
+++ b/mlir/lib/Dialect/Bufferization/Transforms/StaticMemoryPlanning.cpp
@@ -0,0 +1,111 @@
+//===- 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);
+}
+
+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;
+}
+
+llvm::SmallVector<int64_t>
+mlir::bufferization::bestFitMemoryPlanner(int64_t arenaAlignment,
+                                          llvm::ArrayRef<MemoryPlannerAlloc> allocs) {
+  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);
+
+  for (unsigned idx : order) {
+    const MemoryPlannerAlloc &alloc = allocs[idx];
+
+    // Collect intervals that are still live at this allocation's start time.
+    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;
+
+    // Arena high-water mark from all placements so far.
+    int64_t arenaEnd = 0;
+    for (const auto &p : placements)
+      arenaEnd = std::max(arenaEnd, p.offset + p.size);
+
+    int64_t gapStart = 0;
+    for (const auto &[occStart, occEnd] : occupied) {
+      int64_t alignedStart = alignOffset(gapStart, alloc.alignment);
+      int64_t gapEnd = occStart;
+      if (alignedStart + alloc.sizeInBytes <= gapEnd) {
+        int64_t gapSize = gapEnd - alignedStart;
+        if (gapSize < bestGapSize) {
+          bestGapSize = gapSize;
+          bestOffset = alignedStart;
+        }
+      }
+      gapStart = std::max(gapStart, occEnd);
+    }
+
+    // Check the trailing gap (between last occupied and arena end).
+    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});
+  }
+
+  return offsets;
+}
diff --git a/mlir/test/Dialect/Bufferization/Transforms/static-memory-planner-best-fit.mlir b/mlir/test/Dialect/Bufferization/Transforms/static-memory-planner-best-fit.mlir
new file mode 100644
index 0000000000000..6c261880140a5
--- /dev/null
+++ b/mlir/test/Dialect/Bufferization/Transforms/static-memory-planner-best-fit.mlir
@@ -0,0 +1,118 @@
+// RUN: mlir-opt %s -pass-pipeline="builtin.module(func.func(static-memory-planner-analysis{algorithm=best-fit}))" \
+// RUN:     -split-input-file | FileCheck %s
+
+// -----
+
+// Test 1: Non-overlapping lifetimes reuse the same memory.
+// With trivial packing this would be 8192 bytes; best-fit reuses the space.
+// CHECK-LABEL: func @reuse_non_overlapping
+func.func @reuse_non_overlapping() {
+  // Arena should be 4096 bytes (1024 * 4), not 8192.
+  // CHECK: %[[ARENA:.*]] = memref.alloc() {alignment = 1 : i64} : memref<4096xi8>
+  // First allocation at offset 0
+  // CHECK-NEXT: %[[C0_0:.*]] = arith.constant 0 : index
+  // CHECK-NEXT: %{{.*}} = memref.view %[[ARENA]][%[[C0_0]]][] : memref<4096xi8> to memref<1024xf32>
+  // Second allocation also at offset 0 (reuses freed space)
+  // CHECK-NEXT: %[[C0_1:.*]] = arith.constant 0 : index
+  // CHECK-NEXT: %{{.*}} = memref.view %[[ARENA]][%[[C0_1]]][] : memref<4096xi8> to memref<1024xf32>
+  %0 = memref.alloc() : memref<1024xf32>
+  memref.dealloc %0 : memref<1024xf32>
+  %1 = memref.alloc() : memref<1024xf32>
+  memref.dealloc %1 : memref<1024xf32>
+  return
+}
+
+// -----
+
+// Test 2: Overlapping lifetimes cannot reuse memory.
+// CHECK-LABEL: func @no_reuse_overlapping
+func.func @no_reuse_overlapping() {
+  // Both are live at the same time, so arena = 4096 + 2048 = 6144 bytes.
+  // CHECK: %[[ARENA:.*]] = memref.alloc() {alignment = 1 : i64} : memref<6144xi8>
+  // CHECK-NEXT: %[[C0:.*]] = arith.constant 0 : index
+  // CHECK-NEXT: %{{.*}} = memref.view %[[ARENA]][%[[C0]]][] : memref<6144xi8> to memref<1024xf32>
+  // CHECK-NEXT: %[[C4096:.*]] = arith.constant 4096 : index
+  // CHECK-NEXT: %{{.*}} = memref.view %[[ARENA]][%[[C4096]]][] : memref<6144xi8> to memref<512xf32>
+  %0 = memref.alloc() : memref<1024xf32>
+  %1 = memref.alloc() : memref<512xf32>
+  memref.dealloc %0 : memref<1024xf32>
+  memref.dealloc %1 : memref<512xf32>
+  return
+}
+
+// -----
+
+// Test 3: Best-fit picks the smallest suitable gap.
+// Layout: A(4096) at 0, B(1024) at 4096, C(4096) at 5120, D(1024) at 9216.
+// B and D are freed while A and C are still live, creating two gaps:
+//   [4096, 5120) = 1024 bytes (B's slot)
+//   [9216, 10240) = 1024 bytes (D's slot)
+// Then we free A, creating gap [0, 4096) = 4096 bytes.
+// Now allocate E(512 bytes). Gaps: [0,4096)=4096, [4096,5120)=1024, [9216,10240)=1024.
+// Best-fit should pick one of the 1024-byte gaps (smallest fit for 512).
+// CHECK-LABEL: func @best_fit_smallest_gap
+func.func @best_fit_smallest_gap() {
+  // CHECK: %[[ARENA:.*]] = memref.alloc() {alignment = 1 : i64} : memref<10240xi8>
+  // A at offset 0
+  // CHECK-NEXT: %{{.*}} = arith.constant 0 : index
+  // CHECK-NEXT: %{{.*}} = memref.view %[[ARENA]]
+  // B at offset 4096
+  // CHECK-NEXT: %{{.*}} = arith.constant 4096 : index
+  // CHECK-NEXT: %{{.*}}...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/207403


More information about the Mlir-commits mailing list