Properly marking stream alloca ops as non-pure and expanding folding. (#13863)

Previously only the AsyncCloneOp was folding allocas/splats but this is
useful at the TensorCloneOp level too for making IR more readable.

Progress on #13545 (makes the IR easier to read during stream
transformation).
diff --git a/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOpFolders.cpp b/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOpFolders.cpp
index bad55a8..e6e1f17 100644
--- a/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOpFolders.cpp
+++ b/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOpFolders.cpp
@@ -313,6 +313,38 @@
   }
 };
 
+// Clones ops that prefer to be cloned directly.
+// This prevents us from splatting out a value and then cloning that (keeping
+// the memory live/etc) instead of just splatting it again on-demand.
+//
+// Example:
+//  %0 = stream.async.splat %c123_i32
+//  %1 = stream.async.clone %0
+// ->
+//  %1 = stream.async.splat %c123_i32
+template <typename Op>
+struct PropagateClonableOps : public OpRewritePattern<Op> {
+  using OpRewritePattern<Op>::OpRewritePattern;
+  LogicalResult matchAndRewrite(Op cloneOp,
+                                PatternRewriter &rewriter) const override {
+    if (cloneOp.use_empty()) return failure();
+    auto sourceOp =
+        cloneOp.getSource()
+            .template getDefiningOp<IREE::Stream::StreamableOpInterface>();
+    if (!sourceOp || !sourceOp.preferCloneToConsumers()) return failure();
+    for (auto &use :
+         llvm::make_early_inc_range(cloneOp.getResult().getUses())) {
+      rewriter.setInsertionPoint(use.getOwner());
+      auto clonedOp = rewriter.clone(*sourceOp);
+      use.set(clonedOp->getResult(0));
+    }
+    if (cloneOp.use_empty()) {
+      rewriter.eraseOp(cloneOp);
+    }
+    return success();
+  }
+};
+
 // Materialize copy-on-write (🐄) ops where required for |rootValue|.
 // Only valid in tensor/async ops - don't use with stream.cmd.*.
 static bool materializeCOW(Location loc, Value rootValue, OpBuilder &builder) {
@@ -1121,11 +1153,11 @@
 
 void TensorCloneOp::getCanonicalizationPatterns(RewritePatternSet &results,
                                                 MLIRContext *context) {
-  // TODO(benvanik): splat -> clone duplicates splat.
   // TODO(benvanik): some way to reduce deep clone->clone->clone chains.
   // TODO(benvanik): clone + slice => slice.
   // TODO(benvanik): if both operand and result are used once then elide.
   //                 (if not tied block/fn arguments)
+  results.insert<PropagateClonableOps<TensorCloneOp>>(context);
   results.insert<ElideUnneededTensorClones>(context);
 }
 
@@ -1326,44 +1358,10 @@
   return {};
 }
 
-namespace {
-
-// Clones ops that prefer to be cloned directly.
-// This prevents us from splatting out a value and then cloning that (keeping
-// the memory live/etc) instead of just splatting it again on-demand.
-//
-// Example:
-//  %0 = stream.async.splat %c123_i32
-//  %1 = stream.async.clone %0
-// ->
-//  %1 = stream.async.splat %c123_i32
-struct PropagateClonableOps : public OpRewritePattern<AsyncCloneOp> {
-  using OpRewritePattern::OpRewritePattern;
-  LogicalResult matchAndRewrite(AsyncCloneOp cloneOp,
-                                PatternRewriter &rewriter) const override {
-    if (cloneOp.use_empty()) return failure();
-    auto sourceOp = cloneOp.getSource()
-                        .getDefiningOp<IREE::Stream::StreamableOpInterface>();
-    if (!sourceOp || !sourceOp.preferCloneToConsumers()) return failure();
-    for (auto &use :
-         llvm::make_early_inc_range(cloneOp.getResult().getUses())) {
-      rewriter.setInsertionPoint(use.getOwner());
-      auto clonedOp = rewriter.clone(*sourceOp);
-      use.set(clonedOp->getResult(0));
-    }
-    if (cloneOp.use_empty()) {
-      rewriter.eraseOp(cloneOp);
-    }
-    return success();
-  }
-};
-
-}  // namespace
-
 void AsyncCloneOp::getCanonicalizationPatterns(RewritePatternSet &results,
                                                MLIRContext *context) {
   // TODO(benvanik): some way to reduce deep clone->clone->clone chains.
-  results.insert<PropagateClonableOps>(context);
+  results.insert<PropagateClonableOps<AsyncCloneOp>>(context);
   results.insert<ElideUnusedOp<AsyncCloneOp>>(context);
 }
 
diff --git a/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOps.cpp b/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOps.cpp
index e7699cf..7a730c4 100644
--- a/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOps.cpp
+++ b/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOps.cpp
@@ -862,6 +862,10 @@
   return success();
 }
 
+bool TensorEmptyOp::isMetadata() { return true; }
+
+bool TensorEmptyOp::preferCloneToConsumers() { return true; }
+
 //===----------------------------------------------------------------------===//
 // stream.tensor.constant
 //===----------------------------------------------------------------------===//
@@ -893,6 +897,8 @@
   return success();
 }
 
+bool TensorSplatOp::preferCloneToConsumers() { return true; }
+
 //===----------------------------------------------------------------------===//
 // stream.tensor.clone
 //===----------------------------------------------------------------------===//
diff --git a/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOps.td b/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOps.td
index 716b697..d6e3111 100644
--- a/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOps.td
+++ b/compiler/src/iree/compiler/Dialect/Stream/IR/StreamOps.td
@@ -26,12 +26,13 @@
 // Generic resource ops
 //===----------------------------------------------------------------------===//
 
-def Stream_ResourceAllocOp : Stream_PureOp<"resource.alloc", [
+def Stream_ResourceAllocOp : Stream_Op<"resource.alloc", [
   DeclareOpInterfaceMethods<Stream_AffinityOp, [
     "getAffinity",
     "setAffinity",
   ]>,
   Util_SizeAwareOp,
+  AlwaysSpeculatable,
   MemoryEffects<[MemAlloc]>,
 ]> {
   let summary = [{allocates a persistent value with undefined contents}];
@@ -79,13 +80,14 @@
   let hasCanonicalizer = 1;
 }
 
-def Stream_ResourceAllocaOp : Stream_PureOp<"resource.alloca", [
+def Stream_ResourceAllocaOp : Stream_Op<"resource.alloca", [
   DeclareOpInterfaceMethods<Stream_AffinityOp, [
     "getAffinity",
     "setAffinity",
   ]>,
   Stream_TimelineOp,
   Util_SizeAwareOp,
+  AlwaysSpeculatable,
   MemoryEffects<[MemAlloc]>,
 ]> {
   let summary = [{allocates a transient value with undefined contents}];
@@ -747,7 +749,10 @@
 
 def Stream_TensorEmptyOp : Stream_PureOp<"tensor.empty", [
   Stream_AffinityOp,
-  Stream_StreamableOp,
+  DeclareOpInterfaceMethods<Stream_StreamableOp, [
+    "isMetadata",
+    "preferCloneToConsumers",
+  ]>,
   Stream_TensorPhaseOp,
   Util_ShapeAwareOp,
   Util_SizeAwareOp,
@@ -836,7 +841,9 @@
 
 def Stream_TensorSplatOp : Stream_PureOp<"tensor.splat", [
   Stream_AffinityOp,
-  Stream_StreamableOp,
+  DeclareOpInterfaceMethods<Stream_StreamableOp, [
+    "preferCloneToConsumers",
+  ]>,
   Stream_TensorPhaseOp,
   Util_ShapeAwareOp,
   Util_SizeAwareOp,
@@ -1331,7 +1338,7 @@
 // Resource transfer ops
 //===----------------------------------------------------------------------===//
 
-def Stream_AsyncAllocaOp : Stream_PureOp<"async.alloca", [
+def Stream_AsyncAllocaOp : Stream_Op<"async.alloca", [
   DeclareOpInterfaceMethods<Stream_AffinityOp, [
     "getAffinity",
     "setAffinity",
@@ -1342,6 +1349,7 @@
     "preferCloneToConsumers",
   ]>,
   Util_SizeAwareOp,
+  AlwaysSpeculatable,
   MemoryEffects<[MemAlloc]>,
 ]> {
   let summary = [{allocates a transient value with undefined contents}];