This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new a4bfa7e116 [REFACTOR][s_tir] Retire legacy double-buffer injection 
(#20553)
a4bfa7e116 is described below

commit a4bfa7e1168f6b3e81edba07b1b4a2d1a58c5a67
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Oct 5 15:44:58 2026 -0400

    [REFACTOR][s_tir] Retire legacy double-buffer injection (#20553)
    
    Remove InjectDoubleBuffer and its attribute-region protocol, including
    the pass API and configuration, pipeline callers, and legacy
    synchronization bookkeeping.
    
    Keep the SBlock double_buffer_scope annotation used by
    InjectSoftwarePipeline and its scheduling producers, and clarify the
    annotation's write-buffer index semantics.
---
 include/tvm/s_tir/stmt.h                           |   8 +-
 include/tvm/s_tir/transform.h                      |   7 -
 python/tvm/s_tir/backend/adreno/pipeline.py        |   1 -
 python/tvm/s_tir/pipeline.py                       |   1 -
 python/tvm/s_tir/transform/transform.py            |  16 -
 .../postproc/disallow_async_strided_mem_copy.cc    |   1 -
 .../meta_schedule/postproc/verify_gpu_code.cc      |   1 -
 src/s_tir/transform/inject_double_buffer.cc        | 475 ---------------------
 src/s_tir/transform/storage_access.cc              |  23 +-
 src/s_tir/transform/storage_access.h               |   4 -
 src/s_tir/transform/thread_storage_sync.cc         |  27 +-
 .../test_s_tir_transform_inject_double_buffer.py   | 177 --------
 12 files changed, 10 insertions(+), 731 deletions(-)

diff --git a/include/tvm/s_tir/stmt.h b/include/tvm/s_tir/stmt.h
index 379a1ba005..09d66aab2a 100644
--- a/include/tvm/s_tir/stmt.h
+++ b/include/tvm/s_tir/stmt.h
@@ -234,15 +234,11 @@ constexpr const char* async_scope = "async_scope";
 constexpr const char* buffer_bound = "buffer_bound";
 
 /*!
- * \brief Marks production of double buffer data
+ * \brief SBlock annotation selecting a write-buffer index for double 
buffering in
+ * InjectSoftwarePipeline.
  */
 constexpr const char* double_buffer_scope = "double_buffer_scope";
 
-/*!
- * \brief Marks region used by double buffer write
- */
-constexpr const char* double_buffer_write = "double_buffer_write";
-
 /*!
  * \brief String-valued allocation Call attribute containing the TensorCore 
fragment shape
  */
diff --git a/include/tvm/s_tir/transform.h b/include/tvm/s_tir/transform.h
index 90cdf5cba1..fcaa2c530b 100644
--- a/include/tvm/s_tir/transform.h
+++ b/include/tvm/s_tir/transform.h
@@ -242,13 +242,6 @@ TVM_DLL Pass LoopPartition();
  */
 TVM_DLL Pass InjectVirtualThread();
 
-/*!
- * \brief Inject double buffer statements.
- *
- * \return The pass.
- */
-TVM_DLL Pass InjectDoubleBuffer();
-
 /*!
  * \brief Hoist loop-invariant IfThenElse nodes to
  * outside the eligible loops.
diff --git a/python/tvm/s_tir/backend/adreno/pipeline.py 
b/python/tvm/s_tir/backend/adreno/pipeline.py
index e5a8dd0774..ddd15a2820 100644
--- a/python/tvm/s_tir/backend/adreno/pipeline.py
+++ b/python/tvm/s_tir/backend/adreno/pipeline.py
@@ -57,7 +57,6 @@ def default_tir_pipeline(*, prepare_only=False):
             tirx.transform.NarrowDataType(32),
             tirx.transform.VectorizeLoop(not 
bool(config.get("tirx.disable_vectorize", False))),
             s_tir.transform.InjectVirtualThread(),
-            s_tir.transform.InjectDoubleBuffer(),
         ]
         if not bool(config.get("tirx.disable_storage_rewrite", False)):
             passes.append(tirx.transform.StorageRewrite())
diff --git a/python/tvm/s_tir/pipeline.py b/python/tvm/s_tir/pipeline.py
index d6f159f976..b0c5624a08 100644
--- a/python/tvm/s_tir/pipeline.py
+++ b/python/tvm/s_tir/pipeline.py
@@ -57,7 +57,6 @@ def default_s_tir_pipeline(*, prepare_only=False):
             tirx.transform.NarrowDataType(32),
             tirx.transform.VectorizeLoop(not 
bool(config.get("tirx.disable_vectorize", False))),
             s_tir.transform.InjectVirtualThread(),
-            s_tir.transform.InjectDoubleBuffer(),
         ]
         if not bool(config.get("tirx.disable_storage_rewrite", False)):
             passes.append(tirx.transform.StorageRewrite())
diff --git a/python/tvm/s_tir/transform/transform.py 
b/python/tvm/s_tir/transform/transform.py
index 65eb20b4f9..e0ac0a60cc 100644
--- a/python/tvm/s_tir/transform/transform.py
+++ b/python/tvm/s_tir/transform/transform.py
@@ -255,22 +255,6 @@ def InjectVirtualThread():
     return _ffi_api.InjectVirtualThread()  # type: ignore
 
 
-@_ffi.register_object("s_tir.transform.InjectDoubleBufferConfig")
-class InjectDoubleBufferConfig(_ffi.Object):
-    """Config for inject double buffer pass"""
-
-
-def InjectDoubleBuffer():
-    """Inject double buffer statements.
-
-    Returns
-    -------
-    fpass : tvm.transform.Pass
-        The result pass
-    """
-    return _ffi_api.InjectDoubleBuffer()  # type: ignore
-
-
 def HoistIfThenElse(variant=None):
     """Hoist loop-invariant IfThenElse nodes to outside the eligible loops.
 
diff --git 
a/src/s_tir/meta_schedule/postproc/disallow_async_strided_mem_copy.cc 
b/src/s_tir/meta_schedule/postproc/disallow_async_strided_mem_copy.cc
index 2069b478ba..762a66457a 100644
--- a/src/s_tir/meta_schedule/postproc/disallow_async_strided_mem_copy.cc
+++ b/src/s_tir/meta_schedule/postproc/disallow_async_strided_mem_copy.cc
@@ -158,7 +158,6 @@ class DisallowAsyncStridedMemCopyNode : public PostprocNode 
{
           pass_list.push_back(tirx::transform::NarrowDataType(32));
           pass_list.push_back(tirx::transform::StmtSimplify());
           pass_list.push_back(s_tir::transform::InjectVirtualThread());
-          pass_list.push_back(s_tir::transform::InjectDoubleBuffer());
           pass_list.push_back(tirx::transform::VectorizeLoop(true));
           pass_list.push_back(tirx::transform::StorageRewrite());
           tirx::PrimFunc f = WithAttr(ffi::GetRef<tirx::PrimFunc>(prim_func), 
"global_symbol",
diff --git a/src/s_tir/meta_schedule/postproc/verify_gpu_code.cc 
b/src/s_tir/meta_schedule/postproc/verify_gpu_code.cc
index 9a6a6c2061..76b0251a79 100644
--- a/src/s_tir/meta_schedule/postproc/verify_gpu_code.cc
+++ b/src/s_tir/meta_schedule/postproc/verify_gpu_code.cc
@@ -190,7 +190,6 @@ class VerifyGPUCodeNode : public PostprocNode {
           // Phase 2
           pass_list.push_back(tirx::transform::VectorizeLoop(true));
           pass_list.push_back(s_tir::transform::InjectVirtualThread());
-          pass_list.push_back(s_tir::transform::InjectDoubleBuffer());
           pass_list.push_back(tirx::transform::StorageRewrite());
           
pass_list.push_back(s_tir::transform::MergeSharedMemoryAllocations());
           pass_list.push_back(tirx::transform::LowerIntrin());
diff --git a/src/s_tir/transform/inject_double_buffer.cc 
b/src/s_tir/transform/inject_double_buffer.cc
deleted file mode 100644
index 36cea3567f..0000000000
--- a/src/s_tir/transform/inject_double_buffer.cc
+++ /dev/null
@@ -1,475 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/*!
- * \brief Inject double buffering optimization for data fetch.
- * \file inject_double_buffer.cc
- */
-#include <tvm/ffi/cast.h>
-#include <tvm/ffi/extra/structural_mutate.h>
-#include <tvm/ffi/function.h>
-#include <tvm/ffi/reflection/registry.h>
-#include <tvm/ir/attrs.h>
-#include <tvm/runtime/logging.h>
-#include <tvm/s_tir/stmt.h>
-#include <tvm/s_tir/stmt_functor.h>
-#include <tvm/s_tir/transform.h>
-#include <tvm/tirx/builtin.h>
-#include <tvm/tirx/op.h>
-
-#include "ir_utils.h"
-
-namespace tvm {
-namespace s_tir {
-using namespace tvm::prim;
-using namespace tvm::tirx;
-
-namespace {
-
-ffi::Optional<Var> GetBufferDataVar(const ffi::Any& data) {
-  if (auto var = data.as<Var>()) {
-    return var;
-  }
-  if (const auto* call = data.as<CallNode>();
-      call && call->op.same_as(tirx::builtin::buffer_data()) && 
call->args.size() == 1) {
-    return call->args[0].as<Var>();
-  }
-  return std::nullopt;
-}
-
-}  // namespace
-
-struct InjectDoubleBufferConfigNode : public ffi::Object {
-  int split_loop;
-
-  static void RegisterReflection() {
-    namespace refl = tvm::ffi::reflection;
-    refl::ObjectDef<InjectDoubleBufferConfigNode>().def_ro(
-        "split_loop", &InjectDoubleBufferConfigNode::split_loop, "Split loop 
factors",
-        refl::DefaultValue(1));
-  }
-  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.transform.InjectDoubleBufferConfig",
-                                    InjectDoubleBufferConfigNode, ffi::Object);
-};
-
-class InjectDoubleBufferConfig : public ffi::ObjectRef {
- public:
-  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(InjectDoubleBufferConfig, 
ffi::ObjectRef,
-                                                InjectDoubleBufferConfigNode);
-};
-
-TVM_FFI_STATIC_INIT_BLOCK() { 
InjectDoubleBufferConfigNode::RegisterReflection(); }
-
-TVM_REGISTER_PASS_CONFIG_OPTION("s_tir.InjectDoubleBuffer", 
InjectDoubleBufferConfig);
-
-// Detect double buffer variables.
-class DoubleBufferDetector : public StmtExprVisitor {
- public:
-  using StmtExprVisitor::Visit_;
-  ffi::Optional<VisitInterrupt> Visit_(const AttrStmtNode* op) final {
-    if (op->attr_key == s_tir::attr::double_buffer_scope) {
-      if (auto buffer = GetBufferDataVar(op->node)) {
-        touched_.insert(buffer.value().get());
-      }
-      TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(StmtExprVisitor::Visit_(op));
-    } else {
-      TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(StmtExprVisitor::Visit_(op));
-    }
-    return std::nullopt;
-  }
-
-  // Known loads and stores are not opaque escapes of the buffer variable.
-  ffi::Optional<VisitInterrupt> Visit_(const TensorLoadNode* op) final {
-    for (const auto& index : op->indices) {
-      TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Visit(index));
-    }
-    return std::nullopt;
-  }
-
-  ffi::Optional<VisitInterrupt> Visit_(const BufferStoreNode* op) final {
-    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Visit(op->value));
-    for (const auto& index : op->indices) {
-      TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Visit(index));
-    }
-    return std::nullopt;
-  }
-
-  // Declared regions carry bounds, not opaque runtime accesses.
-  ffi::Optional<VisitInterrupt> Visit_(const TensorRegionNode* op) final {
-    if (!op->source.as<TensorVar>()) return StmtExprVisitor::Visit_(op);
-    for (const Range& range : op->region) {
-      TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Visit(range->min));
-      TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Visit(range->extent));
-    }
-    return std::nullopt;
-  }
-
-  ffi::Optional<VisitInterrupt> Visit_(const VarNode* op) final {
-    if (def_region_kind() != kTVMFFIDefRegionKindNone) return 
StmtExprVisitor::Visit_(op);
-    if (touched_.count(op)) {
-      touched_.erase(op);
-    }
-    return std::nullopt;
-  }
-  // The set of touched variable.
-  std::unordered_set<const VarNode*> touched_;
-};
-
-class StripDoubleBufferWrite : public StmtExprMutator {
- public:
-  using StmtExprMutator::Mutate;
-  using StmtExprMutator::Mutate_;
-  UnchangedOr<ffi::Any> Mutate(ffi::AnyView value, InplaceMode inplace_mode) 
override {
-    if (value.as<ExprNode>()) return ffi::Unchanged();
-    return StmtExprMutator::Mutate(value, inplace_mode);
-  }
-
-  UnchangedOr<Stmt> Mutate_(const AttrStmtNode* op, InplaceMode inplace_mode) 
final {
-    if (op->attr_key == s_tir::attr::double_buffer_write) {
-      return Mutate(op->body, inplace_mode).ValueOrUnchanged(op->body);
-    } else {
-      return StmtExprMutator::Mutate_(op, inplace_mode);
-    }
-  }
-};
-
-class DoubleBufferInjector : public StmtExprMutator {
- public:
-  using StmtExprMutator::Mutate;
-  using StmtExprMutator::Mutate_;
-
-  explicit DoubleBufferInjector(int split_loop) : split_loop_(split_loop) {}
-
-  Stmt Inject(Stmt stmt) {
-    auto detector = ffi::make_object<DoubleBufferDetector>();
-    detector->Visit(stmt);
-    if (detector->touched_.empty()) return stmt;
-    for (const VarNode* v : detector->touched_) {
-      dbuffer_info_[v] = StorageEntry();
-    }
-    return s_tir::ConvertSSA(Mutate(stmt, 
InplaceMode::kAllow).ValueOrUnchanged(std::move(stmt)));
-  }
-
-  UnchangedOr<Stmt> Mutate_(const AttrStmtNode* op, InplaceMode inplace_mode) 
final {
-    if (op->attr_key == s_tir::attr::double_buffer_scope) {
-      return MakeProducer(op, inplace_mode);
-    } else {
-      return StmtExprMutator::Mutate_(op, inplace_mode);
-    }
-  }
-
-  UnchangedOr<Stmt> Mutate_(const BindNode* op, InplaceMode inplace_mode) 
final {
-    if (const auto* call = op->value.as<CallNode>();
-        call && call->op.same_as(tirx::builtin::alloc_tensor())) {
-      return Mutate_AllocTensor(op, call, inplace_mode);
-    }
-    return StmtExprMutator::Mutate_(op, inplace_mode);
-  }
-
-  UnchangedOr<Stmt> Mutate_AllocTensor(const BindNode* op, const CallNode* 
call,
-                                       InplaceMode inplace_mode) {
-    const VarNode* buf = op->var.as_or_throw<TensorVar>().get();
-    auto it = dbuffer_info_.find(buf);
-    if (it != dbuffer_info_.end()) {
-      StorageEntry& entry = it->second;
-      tvm::Tuple shape = call->args[0].as_or_throw<tvm::Tuple>();
-      entry.scope = call->args[2].as_or_throw<StringImm>()->value;
-
-      TVM_FFI_ICHECK_EQ(shape->fields.size(), 1) << "InjectDoubleBuffer 
expects flat 1-d buffers.  "
-                                                 << "Has FlattenBuffer been 
run?";
-      entry.stride = shape->fields[0].as_or_throw<PrimExpr>();
-
-      // In flat IR, AllocTensor appears before its usage in the SeqStmt,
-      // so entry.loop may not be set yet. Defer double-buffer allocation
-      // processing to be handled in VisitStmt_(ForNode*).
-      pending_dbuffer_allocs_.insert_or_assign(buf, ffi::GetRef<Bind>(op));
-      // Remove the original AllocTensor (will be re-emitted in ForNode 
visitor)
-      return Evaluate(0);
-    } else {
-      return StmtExprMutator::Mutate_(op, inplace_mode);
-    }
-  }
-
-  UnchangedOr<Stmt> Mutate_(const ForNode* op, InplaceMode inplace_mode) final 
{
-    loop_nest_.push_back(op);
-    Stmt stmt = StmtExprMutator::Mutate_(op, 
inplace_mode).ValueOrUnchanged(ffi::GetRef<Stmt>(op));
-    // Process any pending double-buffer allocations that were deferred
-    // from allocation binding visitor -- now entry.loop should be set.
-    for (auto pend_it = pending_dbuffer_allocs_.begin();
-         pend_it != pending_dbuffer_allocs_.end();) {
-      auto db_it = dbuffer_info_.find(pend_it->first);
-      if (db_it != dbuffer_info_.end() && db_it->second.loop != nullptr) {
-        StorageEntry& entry = db_it->second;
-        const Bind& alloc = pend_it->second;
-        auto new_buf = GetRemappedBuffer(alloc->var.as_or_throw<TensorVar>(), 
entry.stride.value());
-        auto& alloc_nest = loop_allocs_[entry.loop];
-        const auto* call = alloc->value.as<CallNode>();
-        alloc_nest.emplace_back(Bind(new_buf.var(),
-                                     Call(new_buf.type(), 
tirx::builtin::alloc_tensor(),
-                                          {tvm::Tuple(new_buf->shape, 
call->args[0]->span),
-                                           DataTypeImm(new_buf->dtype->dtype, 
call->args[1]->span),
-                                           StringImm(new_buf.scope(), 
call->args[2]->span)},
-                                          call->attrs, call->ty_args, 
call->span),
-                                     alloc->span));
-        pend_it = pending_dbuffer_allocs_.erase(pend_it);
-      } else {
-        ++pend_it;
-      }
-    }
-    auto it = loop_pre_.find(op);
-    if (it != loop_pre_.end()) {
-      const ForNode* old_loop = stmt.as<ForNode>();
-      if (split_loop_ != 0) {
-        // Explicitly unroll the loop
-        TVM_FFI_ICHECK(split_loop_ % 2 == 0 || split_loop_ == 1)
-            << "It is better to split with multiple of 2";
-        TVM_FFI_ICHECK(is_zero(old_loop->min));
-        PrimExpr zero = old_loop->min;
-        PrimExpr new_ext = old_loop->extent - IntImm(old_loop->loop_var.ty(), 
1);
-        PrimExpr factor = IntImm(new_ext.ty(), split_loop_);
-        PrimExpr outer_ext = new_ext / factor;
-        PrimExpr tail_base = outer_ext * factor;
-        Var outer_var(old_loop->loop_var->name + ".outer", 
old_loop->loop_var.ty());
-        std::unordered_map<const VarNode*, PrimExpr> vmap;
-        auto map_var = [&vmap](const Var& var) -> 
ffi::Expected<ffi::UnchangedOr<ffi::Any>> {
-          if (auto it = vmap.find(var.get()); it != vmap.end()) return 
ffi::Any(it->second);
-          return ffi::Unchanged();
-        };
-        std::vector<Stmt> loop_seq;
-        for (int32_t i = 0; i < split_loop_; ++i) {
-          vmap.insert_or_assign(
-              old_loop->loop_var.get(),
-              outer_var.as_or_throw<PrimExpr>() * factor + IntImm(factor.ty(), 
i));
-          loop_seq.emplace_back(
-              ffi::StructuralMap<ffi::WalkOrder::kPreOrder>(old_loop->body, 
map_var)
-                  .as_or_throw<Stmt>());
-        }
-        Stmt loop = For(outer_var.as_or_throw<PrimVar>(), zero, outer_ext, 
old_loop->kind,
-                        SeqStmt::Flatten(loop_seq));
-        // tail
-        std::vector<Stmt> tail_seq;
-        Stmt tail_body = ffi::make_object<StripDoubleBufferWrite>()
-                             ->Mutate(old_loop->body)
-                             .ValueOrUnchanged(old_loop->body);
-        for (int32_t i = 0; i < split_loop_; ++i) {
-          PrimExpr idx = tail_base + IntImm(tail_base.ty(), i);
-          vmap.insert_or_assign(old_loop->loop_var.get(), idx);
-          tail_seq.emplace_back(
-              IfThenElse(idx < old_loop->extent,
-                         
ffi::StructuralMap<ffi::WalkOrder::kPreOrder>(tail_body, map_var)
-                             .as_or_throw<Stmt>()));
-        }
-        stmt = SeqStmt::Flatten(loop, tail_seq);
-      }
-      stmt = SeqStmt::Flatten(it->second, stmt);
-    }
-    it = loop_allocs_.find(op);
-    if (it != loop_allocs_.end()) {
-      stmt = MergeNest(it->second, stmt);
-    }
-    loop_nest_.pop_back();
-    return stmt;
-  }
-
-  UnchangedOr<Stmt> Mutate_(const BufferStoreNode* op, InplaceMode 
inplace_mode) final {
-    auto value = Mutate(op->value);
-    auto indices = 
Mutate(op->indices).as_or_throw<UnchangedOr<ffi::Array<PrimExpr>>>();
-    BufferStore node = ffi::GetRef<BufferStore>(op);
-    if (!value.UnchangedOrSameAs(op->value) || 
!indices.UnchangedOrSameAs(op->indices)) {
-      auto* n = node.CopyOnWrite();
-      n->value = std::move(value).ValueOrUnchanged(op->value);
-      n->indices = std::move(indices).ValueOrUnchanged(op->indices);
-    }
-
-    auto it = dbuffer_info_.find(node->buffer.get());
-    if (it != dbuffer_info_.end()) {
-      const StorageEntry& e = it->second;
-      TVM_FFI_ICHECK(in_double_buffer_scope_);
-      TVM_FFI_ICHECK(e.switch_write_var.has_value());
-
-      TVM_FFI_ICHECK_EQ(node->indices.size(), 1) << "InjectDoubleBuffer 
expects flat 1-d buffers.  "
-                                                 << "Has FlattenBuffer been 
run?";
-
-      auto writer = node.CopyOnWrite();
-      writer->buffer = GetRemappedBuffer(node->buffer, e.stride.value());
-      writer->indices = {e.switch_write_var.value().as_or_throw<PrimExpr>() * 
e.stride.value() +
-                         node->indices[0]};
-    }
-
-    return node;
-  }
-
-  UnchangedOr<PrimExpr> Mutate_(const TensorLoadNode* op, InplaceMode 
inplace_mode) final {
-    auto indices = 
Mutate(op->indices).as_or_throw<UnchangedOr<ffi::Array<PrimExpr>>>();
-    TensorLoad node = ffi::GetRef<TensorLoad>(op);
-    if (!indices.UnchangedOrSameAs(op->indices)) {
-      node.CopyOnWrite()->indices = std::move(indices).ValueUnchecked();
-    }
-    TensorVar buffer = node->source.as_or_throw<tvm::tirx::TensorVar>();
-
-    auto it = dbuffer_info_.find(buffer.get());
-    if (it != dbuffer_info_.end()) {
-      const StorageEntry& e = it->second;
-      TVM_FFI_ICHECK(e.switch_read_var.has_value());
-
-      TVM_FFI_ICHECK_EQ(node->indices.size(), 1) << "InjectDoubleBuffer 
expects flat 1-d buffers.  "
-                                                 << "Has FlattenBuffer been 
run?";
-
-      auto* writer = node.CopyOnWrite();
-      writer->source = GetRemappedBuffer(buffer, e.stride.value());
-      writer->indices = {e.switch_read_var.value() * e.stride.value() + 
node->indices[0]};
-      return node;
-    }
-
-    return node;
-  }
-
-  TensorVar GetRemappedBuffer(TensorVar buf, PrimExpr stride) {
-    TensorVar original = buf;
-    if (auto replacement = VarRemapGet(buf).as<TensorVar>()) return 
replacement.value();
-
-    TVM_FFI_ICHECK(stride.defined());
-    // TODO(Lunderberg): Move this pass to before
-    // FlattenBuffer.  That will simplify the
-    // implementation, to be the insertion of a new dimension for the
-    // buffer, rather than adjusting the other indices.
-    TVM_FFI_ICHECK_EQ(buf->shape.size(), 1) << "InjectDoubleBuffer expects 
flat 1-d buffers.  "
-                                            << "Has FlattenBuffer been run?";
-
-    // Stride gives the distance between the two halves of the
-    // double-buffer, not the stride of the buffer's index.
-    auto type = CopyTensorType(buf);
-    type->shape = {buf->shape[0] + stride};
-    buf = RebuildTensorVar(buf, std::move(type));
-
-    VarRemapSet(original, buf);
-    return buf;
-  }
-
-  UnchangedOr<Expr> Mutate_(const TensorRegionNode* op, InplaceMode 
inplace_mode) final {
-    if (!op->source.as<TensorVar>()) {
-      return StmtExprMutator::Mutate_(op, inplace_mode);
-    }
-    auto region = 
Mutate(op->region).as_or_throw<UnchangedOr<ffi::Array<Range>>>();
-    if (region.UnchangedOrSameAs(op->region)) return ffi::Unchanged();
-    TensorRegion node = ffi::GetRef<TensorRegion>(op);
-    node.CopyOnWrite()->region = std::move(region).ValueUnchecked();
-    return node;
-  }
-
-  UnchangedOr<Expr> Mutate_(const VarNode* op, InplaceMode inplace_mode) final 
{
-    if (def_region_kind() == kTVMFFIDefRegionKindNone) {
-      TVM_FFI_ICHECK(!dbuffer_info_.count(op));
-    }
-    return StmtExprMutator::Mutate_(op, inplace_mode);
-  }
-
- private:
-  Stmt MakeProducer(const AttrStmtNode* op, InplaceMode inplace_mode) {
-    const Var buffer = GetBufferDataVar(op->node).value();
-    TVM_FFI_ICHECK_NE(loop_nest_.size(), 0U) << "Double buffer scope must be 
inside a loop";
-    auto it = dbuffer_info_.find(buffer.get());
-    if (it == dbuffer_info_.end()) {
-      LOG(WARNING) << "Skip double buffer scope " << op->node;
-      return this->Mutate(op->body, inplace_mode).ValueOrUnchanged(op->body);
-    }
-    StorageEntry& e = it->second;
-    e.loop = loop_nest_.back();
-    PrimExpr zero = IntImm(e.loop->loop_var.ty(), 0);
-    PrimExpr one = IntImm(e.loop->loop_var.ty(), 1);
-    PrimExpr two = IntImm(e.loop->loop_var.ty(), 2);
-    PrimExpr loop_shift = e.loop->loop_var + one;
-    e.switch_write_var = Var(e.loop->loop_var->name + ".db", 
e.loop->loop_var.ty());
-    e.switch_read_var = indexmod(e.loop->loop_var, two);
-    in_double_buffer_scope_ = true;
-    Stmt body = this->Mutate(op->body, 
inplace_mode).ValueOrUnchanged(op->body);
-    in_double_buffer_scope_ = false;
-    std::unordered_map<const VarNode*, PrimExpr> vmap;
-    auto map_var = [&vmap](const Var& var) -> 
ffi::Expected<ffi::UnchangedOr<ffi::Any>> {
-      if (auto it = vmap.find(var.get()); it != vmap.end()) return 
ffi::Any(it->second);
-      return ffi::Unchanged();
-    };
-    vmap.insert_or_assign(e.switch_write_var.value().get(), zero);
-    vmap.insert_or_assign(e.loop->loop_var.get(), zero);
-    loop_pre_[e.loop].emplace_back(
-        ffi::StructuralMap<ffi::WalkOrder::kPreOrder>(body, 
map_var).as_or_throw<Stmt>());
-    vmap.insert_or_assign(e.loop->loop_var.get(), loop_shift);
-    vmap.insert_or_assign(e.switch_write_var.value().get(), 
indexmod(loop_shift, two));
-    body = ffi::StructuralMap<ffi::WalkOrder::kPostOrder>(body, 
map_var).as_or_throw<Stmt>();
-    body = AttrStmt(GetRemappedBuffer(buffer.as_or_throw<TensorVar>(), 
e.stride.value()).data(),
-                    s_tir::attr::double_buffer_write, IntImm::Int32(1), body);
-    body = IfThenElse(loop_shift < e.loop->extent, body);
-    return body;
-  }
-  // Storage entry for those who need double buffering.
-  struct StorageEntry {
-    // The size of the buffer
-    ffi::Optional<PrimExpr> stride;
-    // The loop we need
-    const ForNode* loop{nullptr};
-    // The switch variable.
-    ffi::Optional<Var> switch_write_var;
-    // The switch variable for reading.
-    ffi::Optional<PrimExpr> switch_read_var;
-    // The storage scope.
-    std::string scope;
-  };
-  // Whether split loop
-  int32_t split_loop_;
-  // Whether we are inside double buffer scope.
-  bool in_double_buffer_scope_{false};
-  // The current loop next
-  std::vector<const ForNode*> loop_nest_;
-  // The allocs to be appended before the loop
-  std::unordered_map<const ForNode*, std::vector<Stmt>> loop_allocs_;
-  // The stmt to be appended before the loop
-  std::unordered_map<const ForNode*, std::vector<Stmt>> loop_pre_;
-  // The allocation size of the buffer
-  std::unordered_map<const VarNode*, StorageEntry> dbuffer_info_;
-  // The updated TensorVar objects
-  // Pending double-buffer AllocTensor nodes (deferred from flat AllocTensor 
visit)
-  std::unordered_map<const VarNode*, Bind> pending_dbuffer_allocs_;
-};
-
-namespace transform {
-
-Pass InjectDoubleBuffer() {
-  auto pass_func = [=](PrimFunc f, IRModule m, PassContext ctx) {
-    if (!f->body.has_value()) return f;
-    auto* n = f.CopyOnWrite();
-    auto cfg = 
ctx->GetConfig<InjectDoubleBufferConfig>("s_tir.InjectDoubleBuffer");
-    if (!cfg.has_value()) {
-      cfg = tvm::transform::PassConfigWithDefaults<InjectDoubleBufferConfig>();
-    }
-    n->body = ffi::make_object<DoubleBufferInjector>(cfg.value()->split_loop)
-                  ->Inject(std::move(n->body).value());
-    return f;
-  };
-  return CreatePrimFuncPass(pass_func, 0, "s_tir.InjectDoubleBuffer", {});
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
-  namespace refl = tvm::ffi::reflection;
-  refl::GlobalDef().def("s_tir.transform.InjectDoubleBuffer", 
InjectDoubleBuffer);
-}
-
-}  // namespace transform
-
-}  // namespace s_tir
-}  // namespace tvm
diff --git a/src/s_tir/transform/storage_access.cc 
b/src/s_tir/transform/storage_access.cc
index e390ed3266..e491800e12 100644
--- a/src/s_tir/transform/storage_access.cc
+++ b/src/s_tir/transform/storage_access.cc
@@ -139,28 +139,7 @@ ffi::Optional<VisitInterrupt> 
StorageAccessVisitor::Visit_(const BindNode* op) {
 }
 
 ffi::Optional<VisitInterrupt> StorageAccessVisitor::Visit_(const AttrStmtNode* 
op) {
-  if (op->attr_key == s_tir::attr::double_buffer_write) {
-    TVM_FFI_ICHECK(double_buffer_write_ == nullptr);
-    auto buffer = GetBufferDataVar(op->node);
-    TVM_FFI_ICHECK(buffer.has_value())
-        << "Expected a buffer data expression for double-buffer writes, but 
received " << op->node;
-    double_buffer_write_ = ResolveBuffer(buffer.value()).get();
-    scope_.push_back(std::vector<StmtEntry>());
-    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(StmtExprVisitor::Visit_(op));
-    StmtEntry s;
-    s.stmt = op;
-    s.access = Summarize(std::move(scope_.back()), nullptr);
-    scope_.pop_back();
-    if (!s.access.empty()) {
-      for (AccessEntry& e : s.access) {
-        if (e.type == kWrite && e.buffer.get() == double_buffer_write_) {
-          e.double_buffer_write = true;
-        }
-      }
-      scope_.back().emplace_back(std::move(s));
-    }
-    double_buffer_write_ = nullptr;
-  } else if (op->attr_key == tirx::attr::thread_extent) {
+  if (op->attr_key == tirx::attr::thread_extent) {
     IterVar iv = op->node.as_or_throw<IterVar>();
     env_threads_.push_back(iv);
     if (!in_device_env_) {
diff --git a/src/s_tir/transform/storage_access.h 
b/src/s_tir/transform/storage_access.h
index afa121643d..b78a8bb378 100644
--- a/src/s_tir/transform/storage_access.h
+++ b/src/s_tir/transform/storage_access.h
@@ -73,8 +73,6 @@ class StorageAccessVisitor : public StmtExprVisitor {
     AccessType type;
     /*! \brief The storage scope */
     StorageScope scope;
-    /*! \brief Whether the access is double buffer write */
-    bool double_buffer_write = false;
   };
   /*! \brief Access pattern about a single statement */
   struct StmtEntry {
@@ -138,8 +136,6 @@ class StorageAccessVisitor : public StmtExprVisitor {
   bool in_device_env_{false};
   // Whether we are inside condition.
   int condition_counter_{0};
-  // The current double buffer write scope.
-  const VarNode* double_buffer_write_{nullptr};
   // the current free stmt entry.
   StmtEntry curr_stmt_;
   // The involving threads
diff --git a/src/s_tir/transform/thread_storage_sync.cc 
b/src/s_tir/transform/thread_storage_sync.cc
index fe653711d1..237988e7ee 100644
--- a/src/s_tir/transform/thread_storage_sync.cc
+++ b/src/s_tir/transform/thread_storage_sync.cc
@@ -88,12 +88,12 @@ class ThreadSyncPlanner : public StorageAccessVisitor {
       }
       for (const AccessEntry& acc : s.access) {
         if (acc.type == kRead) {
-          if (FindConflict(writes, acc, false)) {
+          if (FindConflict(writes, acc)) {
             sync_before_stmt = true;
             break;
           }
         } else if (acc.type == kWrite) {
-          if (FindConflict(reads, acc, false)) {
+          if (FindConflict(reads, acc)) {
             sync_before_stmt = true;
             break;
           }
@@ -131,12 +131,12 @@ class ThreadSyncPlanner : public StorageAccessVisitor {
         bool sync_before_stmt = false;
         for (const AccessEntry& acc : s.access) {
           if (acc.type == kRead) {
-            if (FindConflict(writes, acc, true)) {
+            if (FindConflict(writes, acc)) {
               sync_before_stmt = true;
               break;
             }
           } else if (acc.type == kWrite) {
-            if (FindConflict(reads, acc, true)) {
+            if (FindConflict(reads, acc)) {
               sync_before_stmt = true;
               break;
             }
@@ -188,28 +188,21 @@ class ThreadSyncPlanner : public StorageAccessVisitor {
       }
     }
     head.insert(head.end(), tail.begin(), tail.end());
-    if (loop != nullptr) {
-      // clear double buffer flag after a loop is finished.
-      for (AccessEntry& e : head) {
-        e.double_buffer_write = false;
-      }
-    }
     return head;
   }
 
  private:
   // find conflicting entry in vec.
-  bool FindConflict(const std::vector<AccessEntry>& prev, const AccessEntry& 
curr,
-                    bool loop_carry) {
+  bool FindConflict(const std::vector<AccessEntry>& prev, const AccessEntry& 
curr) {
     for (const AccessEntry& x : prev) {
-      if (FindConflict(x, curr, loop_carry)) {
+      if (FindConflict(x, curr)) {
         return true;
       }
     }
     return false;
   }
 
-  bool FindConflict(const AccessEntry& prev, const AccessEntry& curr, bool 
loop_carry) {
+  bool FindConflict(const AccessEntry& prev, const AccessEntry& curr) {
     // Access to different buffers does not conflict.
     if (!prev.buffer.same_as(curr.buffer)) {
       return false;
@@ -261,12 +254,6 @@ class ThreadSyncPlanner : public StorageAccessVisitor {
       return false;
     }
 
-    // If this is a read into a double buffer that was previously
-    // swapped out, then it doesn't conflict.
-    if (prev.double_buffer_write && curr.type == kRead && !loop_carry) {
-      return false;
-    }
-
     // If nothing else allows sharing the same buffer, then they are
     // in conflict.
     return true;
diff --git 
a/tests/python/s_tir/transform/test_s_tir_transform_inject_double_buffer.py 
b/tests/python/s_tir/transform/test_s_tir_transform_inject_double_buffer.py
deleted file mode 100644
index a9d49efa0d..0000000000
--- a/tests/python/s_tir/transform/test_s_tir_transform_inject_double_buffer.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# ruff: noqa: F841
-
-import tvm_ffi
-
-import tvm
-import tvm.testing
-from tvm.script import ir as I
-from tvm.script import s_tir as Ts
-from tvm.script import tirx as T
-
-
-def _is_buffer_binding(node, *op_names):
-    return (
-        isinstance(node, tvm.tirx.Bind)
-        and isinstance(node.value, tvm.ir.Call)
-        and isinstance(node.value.op, tvm.ir.Op)
-        and node.value.op.name in op_names
-    )
-
-
-def test_double_buffer():
-    n = 100
-    m = 4
-
-    @I.ir_module
-    class Module:
-        @Ts.prim_func
-        def db(A: T.handle("float32"), C: T.handle("float32")):
-            A_buf = T.decl_tensor((n * m,), "float32", data=A)
-            C_buf = T.decl_tensor((m,), "float32", data=C)
-            tx = T.launch_thread("threadIdx.x", 1)
-            for i in range(n):
-                B = T.alloc_tensor((m,), "float32", scope="shared")
-                with T.attr(B.data, "double_buffer_scope", 1):
-                    for j in range(m):
-                        B[j] = A_buf[i * 4 + j]
-                for j in range(m):
-                    C_buf[j] = B[j] + T.float32(1.0)
-
-    mod = Module
-
-    opt = tvm.transform.Sequential(
-        [tvm.s_tir.transform.InjectDoubleBuffer(), 
tvm.tirx.transform.StmtSimplify()]
-    )
-
-    with tvm.transform.PassContext(config={"s_tir.InjectDoubleBuffer": 
{"split_loop": 2}}):
-        mod = opt(mod)
-    stmt = mod["db"].body
-
-    # After transformation, the buffer allocation should be doubled
-    allocate_node = None
-
-    def visitor(op):
-        nonlocal allocate_node
-        if _is_buffer_binding(op, "tirx.alloc_tensor") and "B" in 
str(op.var.data):
-            allocate_node = op
-
-    tvm_ffi.structural_walk(stmt, visitor)
-    assert allocate_node is not None
-    assert list(allocate_node.var.shape) == [m * 2]
-
-    f = tvm.s_tir.transform.ThreadSync("shared")(mod)["db"]
-    count = [0]
-
-    def count_sync(op):
-        if isinstance(op, tvm.ir.Call) and 
op.op.same_as(tvm.ir.Op.get("tirx.tvm_storage_sync")):
-            count[0] += 1
-
-    tvm_ffi.structural_walk(f.body, count_sync)
-    assert count[0] == 4
-
-
-def test_double_buffer_transform():
-    transform = tvm.ir.transform.Sequential(
-        [
-            tvm.s_tir.transform.InjectDoubleBuffer(),
-            tvm.tirx.transform.StmtSimplify(),
-        ]
-    )
-
-    @I.ir_module
-    class Before:
-        @Ts.prim_func
-        def main(A: T.Tensor([16, 32], "float32"), B: T.Tensor(16, "float32")):
-            for i in range(16):
-                cache = T.alloc_tensor((32,), "float32")
-
-                T.attr(cache.data, "double_buffer_scope", 1)
-
-                for j in range(32):
-                    cache[j] = A[i, j]
-
-                B[i] = 0.0
-                for j in range(32):
-                    B[i] = B[i] + cache[j]
-
-    After = transform(Before)
-    # Verify the double buffer transformation: the allocation should be 
doubled (32 -> 64)
-    allocate_node = None
-
-    def visitor(op):
-        nonlocal allocate_node
-        if _is_buffer_binding(op, "tirx.alloc_tensor"):
-            allocate_node = op
-
-    tvm_ffi.structural_walk(After["main"].body, visitor)
-    assert allocate_node is not None
-    assert list(allocate_node.var.shape) == [64]
-
-
-def test_double_buffer_with_decl_buffer():
-    """Like test_double_buffer_transform, but with a declared buffer object"""
-
-    transform = tvm.ir.transform.Sequential(
-        [
-            tvm.s_tir.transform.InjectDoubleBuffer(),
-            tvm.tirx.transform.StmtSimplify(),
-        ]
-    )
-
-    @I.ir_module
-    class Before:
-        @Ts.prim_func
-        def main(A: T.Tensor((16, 32), "float32"), B: T.Tensor(16, "float32")):
-            for i in range(16):
-                cache = T.decl_tensor(32, "float32")
-                T.attr(cache.data, "double_buffer_scope", 1)
-
-                for j in range(32):
-                    cache[j] = A[i, j]
-
-                B[i] = 0.0
-                for j in range(32):
-                    B[i] = B[i] + cache[j]
-
-    @I.ir_module
-    class Expected:
-        @Ts.prim_func
-        def main(A: T.Tensor((16, 32), "float32"), B: T.Tensor(16, "float32")):
-            cache = T.decl_tensor(64, "float32")
-            for j in range(32):
-                cache[j] = A[0, j]
-
-            B[0] = T.float32(0.0)
-            for j in range(32):
-                B[0] = B[0] + cache[j]
-
-            for i_outer in range(15):
-                T.attr(cache.data, "double_buffer_write", 1)
-                for j in range(32):
-                    cache[(i_outer + 1) % 2 * 32 + j] = A[i_outer + 1, j]
-                B[i_outer + 1] = T.float32(0.0)
-                for j in range(32):
-                    B[i_outer + 1] = B[i_outer + 1] + cache[(i_outer + 1) % 2 
* 32 + j]
-
-    After = transform(Before)
-    tvm.ir.assert_structural_equal(After, Expected)
-
-
-if __name__ == "__main__":
-    tvm.testing.main()

Reply via email to