[PATCH] D78484: Providing buffer assignment for MLIR

Mehdi AMINI via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 20 20:36:32 PDT 2020


mehdi_amini added a comment.

Thanks for upstreaming this!



================
Comment at: mlir/include/mlir/Transforms/BufferAssignment.h:9
+//
+// This header file defines buffer assginment helper methods to compute proper
+// and valid positions for placing Alloc and Dealloc operations.
----------------
Typo `assginment`


================
Comment at: mlir/include/mlir/Transforms/BufferAssignment.h:50
+  /// Computes the actual position to place allocs for the given value.
+  OpBuilder::InsertPoint computeAllocPosition(Value value);
+
----------------
There seems to be a precondition that is does not work for a BlockArgument, can you take an `OpResult` as argument instead?


================
Comment at: mlir/include/mlir/Transforms/BufferAssignment.h:58
+/// Helper conversion pattern that encapsulates a BufferAssignmentPlacer
+/// instance.
+template <typename SourceOp>
----------------
Can you expand on the intended uses of it? Maybe a small example?


================
Comment at: mlir/include/mlir/Transforms/BufferAssignment.h:107
+    auto numReturnValues = returnOp.getNumOperands();
+    auto funcOp = returnOp.template getParentOfType<FuncOp>();
+    auto numFuncArgs = funcOp.getNumArguments();
----------------
rriddle wrote:
> Do we need to hard-code FuncOp here? Could we instead just check the number of arguments in the entry block of the function? Or is there some better way to make this more general?
I agree. Some tricky part is that this goes with the other pattern above which is also hard-coded on FuncOp and this pattern can only operate correctly after the other one rewrote the function. This is an even stronger point of coupling I believe.


================
Comment at: mlir/include/mlir/Transforms/BufferAssignment.h:113
+    for (auto operand : llvm::enumerate(operands)) {
+      auto returnArgNumber = numFuncArgs - numReturnValues + operand.index();
+      auto dstBuffer = funcOp.getArgument(returnArgNumber);
----------------
Nit: `numFuncArgs - numReturnValues` is a loop invariant, and it could help readability to hoist it out and name it, like `int firstReturnParameter = numFuncArgs - numReturnValues;` or similar.



================
Comment at: mlir/include/mlir/Transforms/BufferAssignment.h:120
+      rewriter.setInsertionPoint(
+          returnOp.getOperation()->getBlock()->getTerminator());
+      rewriter.create<CopyOpTy>(loc, operand.value(),
----------------
I am puzzled about why not just `rewriter.setInsertionPoint(returnOp.getOperation());` here?


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:44
+// is currently missing is a high-level loop analysis that allows us to move
+// allocs and deallocs outside of the loop blocks.
+//
----------------
When you say "does not support loops": can you be more precise in what will happen in the presence of loops (miscompile, crash, or just suboptimal allocations?)


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:117
+            auto successorOps =
+                branchInterface.getSuccessorOperands(successorIndex);
+            if (successorOps.hasValue()) {
----------------
`successorOps`->`successorOperands` ("Op" is for "Operation" in general)


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:120
+              // Build the actual mapping of values to their immediate aliases.
+              for (auto arg : block.getArguments()) {
+                Value predecessorArgValue =
----------------
Nit: `Value arg`


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:122
+                Value predecessorArgValue =
+                    successorOps.getValue()[arg.getArgNumber()];
+                aliases[predecessorArgValue].insert(arg);
----------------
I'd consider iterating on  `llvm::zip(block.getArguments(), successorOps.getValue())`


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:126
+            }
+          }
+        }
----------------
what happens in the "else" case with respect to aliasing? It isn't clear to me that this is safe?


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:185
+                                       value.getDefiningOp());
+    }
+    // Get all possible aliases
----------------
Nit: avoid trivial braces


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:194
+  /// information.
+  DeallocSetT findAssociatedDeallocs(AllocOp alloc) const {
+    DeallocSetT result;
----------------
Isn't there a trait we could use instead of hard-coding the `AllocOp` here (and everywhere)?


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:232
+                            const DominatorT &doms) const {
+    assert(!value.isa<BlockArgument>() && "Cannot place a block argument");
+    // Start with the current block the value is defined in.
----------------
Can you write the API as taking an `OpResult` instead of the base `Value`?


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:249
+  template <typename AliasesT>
+  Operation *getAllocPosition(Value value, const AliasesT &aliases) const {
+    // Determine the actual block to place the alloc and get liveness
----------------
Does it need to be a template? This is a private method and I see a single use?


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:368
+      }
+    });
+  };
----------------
Isn't this all assuming a particular model with a single allocation / deallocation per buffer?
What about a pattern like the following with two allocs feeding into one dealloc? (we can imagine more complex cases)

```
    cond_br %cond, ^bb1, ^bb2
  ^bb1:
    %buffer1 = alloc ...
    br ^exit(%buffer1 : ....)
  ^bb2:
    %buffer2 = alloc ...
    br ^exit(%buffer2 : ...)
  ^exit(%buffer: ...):
    dealloc %buffer
```


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:388
+  return opBuilder.saveInsertionPoint();
+}
+
----------------
This does not seem to be "computing" anything? Is this like "future work"?


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:409
+    conversion.addInputs(argType.index(), toMemrefConverter(argType.value()));
+  for (auto resType : funcType.getResults())
+    conversion.addInputs(toMemrefConverter(resType));
----------------
Nit: auto -> Type


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:416
+  });
+  // Converting tensor-type block arugments of all blocks inside the
+  // function region to memref-type except for the entry block.
----------------
Typo: `arugments`


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:426
+      oldArg.replaceAllUsesWith(newArg);
+      block.eraseArgument(i + 1);
+    }
----------------
Can't you just call ` setType()` on the BlockArgument?
(also don't use auto when unnecessary)


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:446
+    bool legality = true;
+    for (auto &block2 : funcOp.getBlocks()) {
+      legality &= llvm::all_of(block2.getArguments(), isLegalBlockArg);
----------------
`block` instead of `block2` here?
Also don't use auto when it does not make the code mode readable, I think that `Block &block` would be just fine here.


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:449
+      if (!legality)
+        break;
+    }
----------------
Remove the `legality` boolean and just `if (all_of(...)) return false;` here.

You can even replace the outer loop with an all_of/any_of ;)

```
return all_of(funcOp.getBlocks(), [] (Block &block) {
  return all_of(block2.getArguments(), isLegalBlockArg);
}
```


================
Comment at: mlir/lib/Transforms/BufferAssignment.cpp:464
+      "Executes buffer assignment pass to automatically move alloc and dealloc "
+      "operations into their proper positions");
+}
----------------
It seems to me that the pass is a misnomer: it does not really "assign" buffers, but optimizes the placement, would there be a more accurate name?

Also saying "into their proper positions" seems like it is intended for correctness.


================
Comment at: mlir/test/Transforms/buffer-assignment.mlir:1
+// RUN: mlir-opt -buffer-assignment -allow-unregistered-dialect -split-input-file %s | FileCheck %s -dump-input-on-failure
+
----------------
Can you try to use only operations with registered dialects? The test dialect in-tree is a potential host for such operations, and `std.call @external_func` is also frequently a good escape hatch.


================
Comment at: mlir/test/Transforms/buffer-assignment.mlir:64
+// CHECK-NEXT: dealloc %[[ALLOC]]
+// CHECK-NEXT: return
+
----------------
Can you document the property you're trying to test in each test-case? I really helps to interpret minimal check lines.


================
Comment at: mlir/test/lib/Transforms/TestBufferAssignment.cpp:29
+/// TestBufferAssignmentPreparationPass is equal to
+/// Linalg-Tensor-To-Linalg-Buffer pass and can be separately published.
+/// TODO(dfki): It can be removed later on.
----------------
Can you clarify this sentence? I am missing context


================
Comment at: mlir/tools/mlir-opt/mlir-opt.cpp:98
 void registerTestPasses() {
+  registerBufferAssignmentPass();
   registerConvertToTargetEnvPass();
----------------
This isn't in the test directory, so it shouldn't be registered here but use the same mechanism as the other non-tests passes.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D78484/new/

https://reviews.llvm.org/D78484





More information about the llvm-commits mailing list