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 a55f5d5d62 [REFACTOR][TIRx] Use regions for CPU and runtime context 
scopes (#20567)
a55f5d5d62 is described below

commit a55f5d5d623a3bf2444b89a7b6d289a89a1844a1
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Oct 6 13:25:05 2026 -0400

    [REFACTOR][TIRx] Use regions for CPU and runtime context scopes (#20567)
    
    Represent device context, CPU compute outlining, and explicit parallel
    launches with dedicated RegionStmt ops. Pair device type and ID as
    ordinary operands, retain compute-helper naming and existing worker-team
    behavior, and migrate packed-API production, lowering, codegen, and
    script syntax together.
    
    Reuse typed operator signatures at region construction and preserve
    lexical workspace cleanup. Remove the superseded attribute encodings.
---
 include/tvm/tirx/builtin.h                         |  6 ++
 include/tvm/tirx/stmt.h                            |  9 ---
 python/tvm/tirx/op.py                              |  2 +-
 .../tvm/tirx/script/ir_builder/parser_protocol.py  | 21 ++++++
 src/target/llvm/codegen_cpu.cc                     | 22 ++++---
 src/target/llvm/codegen_cpu.h                      |  3 +-
 src/target/llvm/codegen_llvm.cc                    |  4 ++
 src/target/source/codegen_c.cc                     |  4 ++
 src/tirx/ir/stmt.cc                                |  5 ++
 src/tirx/op/builtin.cc                             | 12 ++++
 src/tirx/script/printer/stmt.cc                    | 11 ++++
 src/tirx/transform/ir_utils.cc                     |  8 +--
 src/tirx/transform/lower_tvm_builtin.cc            | 74 ++++++++++++----------
 src/tirx/transform/make_packed_api.cc              | 16 ++---
 .../s_tir/script/test_s_tir_script_printer.py      | 67 ++++++++++----------
 .../test_tir_transform_lower_tvm_builtin.py        | 32 ++++------
 .../test_tir_transform_make_packed_api.py          | 12 ++--
 .../test_tir_transform_storage_rewrite.py          |  5 +-
 18 files changed, 188 insertions(+), 125 deletions(-)

diff --git a/include/tvm/tirx/builtin.h b/include/tvm/tirx/builtin.h
index 139f8a3972..688198c60c 100644
--- a/include/tvm/tirx/builtin.h
+++ b/include/tvm/tirx/builtin.h
@@ -50,6 +50,12 @@ TVM_DLL const Op& launch_thread();
 
 /*! \brief Mark a user-facing device entry region. */
 TVM_DLL const Op& device_entry();
+/*! \brief Supply the device type and ID for lowering the region body. */
+TVM_DLL const Op& device_context();
+/*! \brief Outline the region body as a named CPU compute helper. */
+TVM_DLL const Op& compute_scope();
+/*! \brief Launch a CPU worker team around the region body. */
+TVM_DLL const Op& parallel_launch();
 /*!
  * \brief Allocate a buffer: alloc_tensor(shape, dtype, scope) -> TensorType.
  *
diff --git a/include/tvm/tirx/stmt.h b/include/tvm/tirx/stmt.h
index 6331344f52..4894b8b55f 100644
--- a/include/tvm/tirx/stmt.h
+++ b/include/tvm/tirx/stmt.h
@@ -852,15 +852,6 @@ class ScopeIdDefStmt : public Stmt {
 
 /*! \brief Statement attribute and loop annotation keys. */
 namespace attr {
-/*!
- * \brief Mark the scope as when computation start to happen.
- *  This can hint some code generator to create a new function for compute.
- */
-constexpr const char* compute_scope = "compute_scope";
-/*! \brief The allocation device for global malloc in host. */
-constexpr const char* device_id = "device_id";
-/*! \brief The device type. */
-constexpr const char* device_type = "device_type";
 /*!
  * \brief For annotation: maximum work for automatic unrolling.
  *
diff --git a/python/tvm/tirx/op.py b/python/tvm/tirx/op.py
index 3ab04608b7..9014497141 100644
--- a/python/tvm/tirx/op.py
+++ b/python/tvm/tirx/op.py
@@ -918,7 +918,7 @@ def cpu_parallel_barrier():
     """Synchronize all workers in the current CPU parallel launch.
 
     Every worker must reach this operation at the same program point. Place it
-    inside a ``pragma_parallel_launch_point`` scope, outside parallel loops,
+    inside a ``parallel_launch`` region, outside parallel loops,
     whose iteration counts can differ between workers. Writes before the 
barrier
     are visible to all workers after it.
 
diff --git a/python/tvm/tirx/script/ir_builder/parser_protocol.py 
b/python/tvm/tirx/script/ir_builder/parser_protocol.py
index 4f88013707..34eb8b90e3 100644
--- a/python/tvm/tirx/script/ir_builder/parser_protocol.py
+++ b/python/tvm/tirx/script/ir_builder/parser_protocol.py
@@ -980,6 +980,24 @@ def region(
     return _ffi_api.Region(op, args, [] if body_params is None else 
body_params, attrs)
 
 
+def device_context(device_type: Expr, device_id: Expr) -> frame.RegionFrame:
+    """Supply lexical device context for allocation and packed-call lowering.
+
+    This region does not change the active runtime device.
+    """
+    return region("tirx.device_context", [device_type, device_id])
+
+
+def compute_scope(name: str) -> frame.RegionFrame:
+    """Outline the body as a named CPU compute helper."""
+    return region("tirx.compute_scope", [_StringImm(name)])
+
+
+def parallel_launch() -> frame.RegionFrame:
+    """Launch a CPU worker team around parallel loops and team barriers."""
+    return region("tirx.parallel_launch", [])
+
+
 def launch_thread(thread_tag: str, extent: Expr) -> frame.RegionFrame:
     """Launch a hardware or virtual thread with a fresh lexical variable.
 
@@ -1017,8 +1035,10 @@ __all__ = [
     "buffer_store",
     "call_global_var_",
     "check_well_formed_",
+    "compute_scope",
     "continue_",
     "decl_mutable_cell_",
+    "device_context",
     "device_entry",
     "else_",
     "emit",
@@ -1043,6 +1063,7 @@ __all__ = [
     "not_",
     "or_",
     "parallel",
+    "parallel_launch",
     "prim_func",
     "range_",
     "region",
diff --git a/src/target/llvm/codegen_cpu.cc b/src/target/llvm/codegen_cpu.cc
index bb42c87b5b..62d0113ac9 100644
--- a/src/target/llvm/codegen_cpu.cc
+++ b/src/target/llvm/codegen_cpu.cc
@@ -508,7 +508,7 @@ llvm::BasicBlock* CodeGenCPU::CheckCallSuccess(llvm::Value* 
retcode) {
   return end_block;
 }
 
-void CodeGenCPU::CreateComputeScope(const AttrStmtNode* op) {
+void CodeGenCPU::CreateComputeScope(const RegionStmtNode* op) {
   EmitDebugLocation(op);
   /*! \brief maintain states that should be guarded when step into compute 
scope */
   struct ComputeScopeStates {
@@ -555,8 +555,7 @@ void CodeGenCPU::CreateComputeScope(const AttrStmtNode* op) 
{
   // $xxx_compute_ functions are not global. They should be marked as static 
(via InternalLinkage)
   // to call them correctly on MIPS platform (CALL16 issue)
   // Linkage ld Error: CALL16 reloc at 0x290 not against global symbol
-  const StringImmNode* value = op->value.as<StringImmNode>();
-  TVM_FFI_ICHECK(value != nullptr);
+  const auto* value = op->args[0].as<StringImmNode>();
   llvm::Function* fcompute = llvm::Function::Create(ftype, 
llvm::Function::InternalLinkage,
                                                     
MakeStringRef(value->value), module_.get());
   SetComputeScopeAttributes(fcompute);
@@ -1131,18 +1130,25 @@ void CodeGenCPU::Dispatch_(const AssertStmtNode* op) {
   CodeGenLLVM::Dispatch_(op);
 }
 
+void CodeGenCPU::Dispatch_(const RegionStmtNode* op) {
+  EmitDebugLocation(op);
+  if (op->op.same_as(tirx::builtin::compute_scope())) {
+    CreateComputeScope(op);
+  } else if (op->op.same_as(tirx::builtin::parallel_launch())) {
+    CreateParallelLaunch(op->body, 0, "pragma_parallel");
+  } else {
+    CodeGenLLVM::Dispatch_(op);
+  }
+}
+
 void CodeGenCPU::Dispatch_(const AttrStmtNode* op) {
   EmitDebugLocation(op);
-  if (op->attr_key == tirx::attr::compute_scope) {
-    this->CreateComputeScope(op);
-  } else if (tirx::attr::IsPragmaKey(op->attr_key)) {
+  if (tirx::attr::IsPragmaKey(op->attr_key)) {
     if (op->attr_key == "pragma_parallel_stride_pattern") {
       TVM_FFI_THROW(ValueError)
           << "pragma_parallel_stride_pattern is retired; annotate every 
affected parallel For "
           << "with parallel_stride_pattern=True, including later loops in the 
same launch "
           << "outside the former attribute body";
-    } else if (op->attr_key == "pragma_parallel_launch_point") {
-      CreateParallelLaunch(op->body, 0, "pragma_parallel");
     } else if (op->attr_key == "pragma_parallel_barrier_when_finish") {
       TVM_FFI_THROW(ValueError)
           << "pragma_parallel_barrier_when_finish is retired; place 
cpu_parallel_barrier() "
diff --git a/src/target/llvm/codegen_cpu.h b/src/target/llvm/codegen_cpu.h
index a73210d151..dba07332d8 100644
--- a/src/target/llvm/codegen_cpu.h
+++ b/src/target/llvm/codegen_cpu.h
@@ -72,6 +72,7 @@ class CodeGenCPU : public CodeGenLLVM {
   void AddMainFunction(const std::string& entry_func_name) override;
   std::unique_ptr<llvm::Module> Finish() override;
   void Dispatch_(const AssertStmtNode* op) override;
+  void Dispatch_(const RegionStmtNode* op) override;
   void Dispatch_(const AttrStmtNode* op) override;
   void Dispatch_(const ForNode* op) override;
   llvm::Value* CreateIntrinsic(const CallNode* op) override;
@@ -147,7 +148,7 @@ class CodeGenCPU : public CodeGenLLVM {
   // Create parallel launch
   void CreateParallelLaunch(const Stmt& body, int num_task, std::string name = 
"");
   // Create a new compute scope.
-  void CreateComputeScope(const AttrStmtNode* op);
+  void CreateComputeScope(const RegionStmtNode* op);
   // Check if the call to packed function is successful
   // if not directly finalize function and pass on return code.
   // return the end block after the check
diff --git a/src/target/llvm/codegen_llvm.cc b/src/target/llvm/codegen_llvm.cc
index 33a00a7d10..9d8f6da7b2 100644
--- a/src/target/llvm/codegen_llvm.cc
+++ b/src/target/llvm/codegen_llvm.cc
@@ -2256,6 +2256,10 @@ void CodeGenLLVM::Dispatch_(const RegionStmtNode* op) {
     With<sym::ConstraintContext> thread_scope(analyzer_, var >= 0 && var < 
extent);
     this->Dispatch(op->body);
     var_map_.erase(var.get());
+  } else if (op->op.same_as(tirx::builtin::device_context()) ||
+             op->op.same_as(tirx::builtin::compute_scope()) ||
+             op->op.same_as(tirx::builtin::parallel_launch())) {
+    this->Dispatch(op->body);
   } else {
     TVM_FFI_THROW(ValueError) << "Unsupported region op " << op->op;
   }
diff --git a/src/target/source/codegen_c.cc b/src/target/source/codegen_c.cc
index aa6313b76b..7e693f5c24 100644
--- a/src/target/source/codegen_c.cc
+++ b/src/target/source/codegen_c.cc
@@ -1303,6 +1303,10 @@ void CodeGenC::Dispatch_(const RegionStmtNode* op) {
     BindThreadIndex(var, tag);
     this->PrintStmt(op->body);
     var_idmap_.erase(var.get());
+  } else if (op->op.same_as(tirx::builtin::device_context()) ||
+             op->op.same_as(tirx::builtin::compute_scope()) ||
+             op->op.same_as(tirx::builtin::parallel_launch())) {
+    this->PrintStmt(op->body);
   } else {
     TVM_FFI_THROW(ValueError) << "Unsupported region op " << op->op;
   }
diff --git a/src/tirx/ir/stmt.cc b/src/tirx/ir/stmt.cc
index d11518e973..874861b318 100644
--- a/src/tirx/ir/stmt.cc
+++ b/src/tirx/ir/stmt.cc
@@ -813,6 +813,11 @@ RegionStmt::RegionStmt(Op op, ffi::Array<Expr> args, 
ffi::Array<Var> body_params
   signature.args = args;
   signature.attrs = attrs;
   op.Validate(&signature);
+  if (op.same_as(tirx::builtin::device_context()) || 
op.same_as(tirx::builtin::compute_scope()) ||
+      op.same_as(tirx::builtin::parallel_launch())) {
+    TVM_FFI_CHECK(body_params.empty() && result_vars.empty() && 
attrs->dict.empty(), ValueError)
+        << op->name << " expects no body parameters, results or attributes";
+  }
   std::unordered_set<const VarNode*> definitions;
   for (const auto& vars : {body_params, result_vars}) {
     for (const Var& var : vars) {
diff --git a/src/tirx/op/builtin.cc b/src/tirx/op/builtin.cc
index d791c0d0b3..b4332fcb15 100644
--- a/src/tirx/op/builtin.cc
+++ b/src/tirx/op/builtin.cc
@@ -191,6 +191,9 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 TVM_DEFINE_CACHED_OP_GETTER(reinterpret, "tirx.reinterpret")
 TVM_DEFINE_CACHED_OP_GETTER(launch_thread, "tirx.launch_thread")
 TVM_DEFINE_CACHED_OP_GETTER(device_entry, "tirx.device_entry")
+TVM_DEFINE_CACHED_OP_GETTER(device_context, "tirx.device_context")
+TVM_DEFINE_CACHED_OP_GETTER(compute_scope, "tirx.compute_scope")
+TVM_DEFINE_CACHED_OP_GETTER(parallel_launch, "tirx.parallel_launch")
 TVM_DEFINE_CACHED_OP_GETTER(thread_return, "tirx.thread_return")
 TVM_DEFINE_CACHED_OP_GETTER(filter, "tirx.filter")
 TVM_DEFINE_CACHED_OP_GETTER(selector, "tirx.selector")
@@ -276,6 +279,15 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 
   OpDef("tirx.launch_thread", "Bind a thread index within a body with a launch 
extent.")
       .set_attr<TIRxOpCategory>("TIRxOpCategory", ffi::String("builtin"));
+  OpDef("tirx.device_context", "Supply the device type and ID within a 
region.")
+      .signature(sig::arg<IntExpr>("device_type"), 
sig::arg<IntExpr>("device_id"))
+      .set_attr<TIRxOpCategory>("TIRxOpCategory", ffi::String("builtin"));
+  OpDef("tirx.compute_scope", "Outline a named CPU compute region.")
+      .signature(sig::arg<StringImm>("name"))
+      .set_attr<TIRxOpCategory>("TIRxOpCategory", ffi::String("builtin"));
+  OpDef("tirx.parallel_launch", "Launch a CPU worker team around a region.")
+      .signature()
+      .set_attr<TIRxOpCategory>("TIRxOpCategory", ffi::String("builtin"));
   OpDef("tirx.thread_return")
       .set_attr<TScriptPrinterName>("TScriptPrinterName", 
ffi::String("tirx.thread_return"))
       .set_attr<TIRxOpCategory>("TIRxOpCategory", ffi::String("builtin"))
diff --git a/src/tirx/script/printer/stmt.cc b/src/tirx/script/printer/stmt.cc
index 5088fe5289..0ffc17c34d 100644
--- a/src/tirx/script/printer/stmt.cc
+++ b/src/tirx/script/printer/stmt.cc
@@ -367,6 +367,17 @@ ffi::Optional<ExprDoc> 
RegionStmtDocTranslate(DocTranslatorObj* d, ffi::AnyView
               ->Attr("launch_thread")
               
->Call({LiteralDoc::Str(stmt->args[0].as_or_throw<StringImm>()->value, 
std::nullopt),
                       
d->Translate(stmt->args[1].as_or_throw<PrimExpr>()).value()});
+  } else if (stmt->op.same_as(tirx::builtin::device_context())) {
+    rhs = NamespaceDoc("tirx")
+              ->Attr("device_context")
+              ->Call({d->Translate(stmt->args[0]).value(), 
d->Translate(stmt->args[1]).value()});
+  } else if (stmt->op.same_as(tirx::builtin::compute_scope())) {
+    rhs =
+        NamespaceDoc("tirx")
+            ->Attr("compute_scope")
+            
->Call({LiteralDoc::Str(stmt->args[0].as_or_throw<StringImm>()->value, 
std::nullopt)});
+  } else if (stmt->op.same_as(tirx::builtin::parallel_launch())) {
+    rhs = NamespaceDoc("tirx")->Attr("parallel_launch")->Call({});
   } else {
     ffi::Array<ExprDoc> args;
     for (const Expr& arg : stmt->args) 
args.push_back(d->Translate(arg).value());
diff --git a/src/tirx/transform/ir_utils.cc b/src/tirx/transform/ir_utils.cc
index 0362497aa7..a13c7130c6 100644
--- a/src/tirx/transform/ir_utils.cc
+++ b/src/tirx/transform/ir_utils.cc
@@ -304,12 +304,8 @@ UnchangedOr<Stmt> IRConvertSSA::Mutate_(const 
AttrStmtNode* op, InplaceMode inpl
       function_scope_var_remap_.insert({var.get(), new_var});
       var = new_var;
     } else {
-      // The AttrStmt refers to an undefined variable.  This is
-      // allowed for some attributes, such as
-      // "pragma_parallel_launch_point", which annotates a variable
-      // that is about to occur in a ForNode.  In these cases, the
-      // ForNode and the AttrStmt must continue using the same
-      // variable defintion.
+      // An attribute can refer to a variable defined by a later ForNode.
+      // Preserve the same variable definition in the attribute and the loop.
       //
       // Preserve the annotated variable's identity for later definitions
       // and independent functions, without introducing a lexical body binding.
diff --git a/src/tirx/transform/lower_tvm_builtin.cc 
b/src/tirx/transform/lower_tvm_builtin.cc
index a2c1414546..2046074656 100644
--- a/src/tirx/transform/lower_tvm_builtin.cc
+++ b/src/tirx/transform/lower_tvm_builtin.cc
@@ -337,39 +337,49 @@ class BuiltinLower : public StmtExprMutator {
     return SeqStmt({alloc_bind, alloc_nullptr_check});
   }
 
-  UnchangedOr<Stmt> Mutate_(const AttrStmtNode* op, InplaceMode inplace_mode) 
final {
-    if (op->attr_key == attr::device_id) {
-      auto cache = device_id_;
-      device_id_ = op->value.as_or_throw<PrimExpr>();
-      Stmt out = scope_.WithNewScope([&]() -> Stmt {
-        Stmt body = this->Mutate(op->body, 
inplace_mode).ValueOrUnchanged(op->body);
-        return AppendPendingFrees(body);
-      });
-      device_id_ = cache;
-      return out;
-    } else if (op->attr_key == attr::device_type) {
-      auto cache = device_type_;
-      device_type_ = op->value.as_or_throw<PrimExpr>();
-      Stmt out = scope_.WithNewScope([&]() -> Stmt {
-        Stmt body = this->Mutate(op->body, 
inplace_mode).ValueOrUnchanged(op->body);
-        return AppendPendingFrees(body);
+  UnchangedOr<Stmt> Mutate_(const RegionStmtNode* op, InplaceMode 
inplace_mode) final {
+    if (op->op.same_as(builtin::device_context())) {
+      PrimExpr device_type = this->Mutate(op->args[0], inplace_mode)
+                                 .ValueOrUnchanged(op->args[0])
+                                 .as_or_throw<PrimExpr>();
+      PrimExpr device_id = this->Mutate(op->args[1], inplace_mode)
+                               .ValueOrUnchanged(op->args[1])
+                               .as_or_throw<PrimExpr>();
+      auto saved_type = device_type_;
+      auto saved_id = device_id_;
+      device_type_ = device_type;
+      device_id_ = device_id;
+      Stmt body = scope_.WithNewScope([&]() -> Stmt {
+        return AppendPendingFrees(this->Mutate(op->body, 
inplace_mode).ValueOrUnchanged(op->body));
       });
-      device_type_ = cache;
-      return out;
-    } else {
-      return scope_.WithNewScope([&]() -> Stmt {
-        Stmt visited =
-            StmtExprMutator::Mutate_(op, 
inplace_mode).ValueOrUnchanged(ffi::GetRef<Stmt>(op));
-        if (!scope_.Current().pending_frees.empty()) {
-          const auto* attr = visited.as<AttrStmtNode>();
-          if (attr) {
-            return AttrStmt(attr->node, attr->attr_key, attr->value, 
AppendPendingFrees(attr->body),
-                            attr->span);
-          }
+      device_type_ = saved_type;
+      device_id_ = saved_id;
+      return body;
+    }
+    return scope_.WithNewScope([&]() -> Stmt {
+      RegionStmt region = StmtExprMutator::Mutate_(op, inplace_mode)
+                              .ValueOrUnchanged(ffi::GetRef<Stmt>(op))
+                              .as_or_throw<RegionStmt>();
+      if (!scope_.Current().pending_frees.empty()) {
+        region.CopyOnWrite()->body = AppendPendingFrees(region->body);
+      }
+      return region;
+    });
+  }
+
+  UnchangedOr<Stmt> Mutate_(const AttrStmtNode* op, InplaceMode inplace_mode) 
final {
+    return scope_.WithNewScope([&]() -> Stmt {
+      Stmt visited =
+          StmtExprMutator::Mutate_(op, 
inplace_mode).ValueOrUnchanged(ffi::GetRef<Stmt>(op));
+      if (!scope_.Current().pending_frees.empty()) {
+        const auto* attr = visited.as<AttrStmtNode>();
+        if (attr) {
+          return AttrStmt(attr->node, attr->attr_key, attr->value, 
AppendPendingFrees(attr->body),
+                          attr->span);
         }
-        return visited;
-      });
-    }
+      }
+      return visited;
+    });
   }
   UnchangedOr<Stmt> Mutate_(const ForNode* op, InplaceMode inplace_mode) final 
{
     auto min_result = this->Mutate(op->min, inplace_mode);
@@ -467,7 +477,7 @@ class BuiltinLower : public StmtExprMutator {
 
   StringImm GetDeviceMethodName(const char* method_name) const {
     TVM_FFI_ICHECK(device_type_) << "Method " << method_name << " requires the 
device type, "
-                                 << "but occurred outside of a \"device_type\" 
annotation";
+                                 << "but occurred outside of a 
\"device_context\" region";
 
     auto as_int = device_type_.as<IntImmNode>();
     TVM_FFI_ICHECK(as_int) << "Method " << method_name
diff --git a/src/tirx/transform/make_packed_api.cc 
b/src/tirx/transform/make_packed_api.cc
index 7ccca70c4c..b3b554f067 100644
--- a/src/tirx/transform/make_packed_api.cc
+++ b/src/tirx/transform/make_packed_api.cc
@@ -231,7 +231,6 @@ PrimFunc MakePackedAPI(PrimFunc func) {
   }
 
   auto* func_ptr = func.CopyOnWrite();
-  const Stmt nop = Evaluate(0);
 
   // Data field definitions
   Var v_self_handle("self_handle", PointerType::VoidPointerTy());
@@ -251,8 +250,6 @@ PrimFunc MakePackedAPI(PrimFunc func) {
   auto result = binder.Finalize();
   bool need_set_device = result.var_defs.count(device_id.get());
 
-  std::vector<Stmt> seq_check;
-
   // signature: (void* handle, TVMFFIAny* packed_args, int num_args, 
TVMFFIAny* v_result)
   ffi::Array<Var> args{v_self_handle, v_packed_args, v_num_packed_args, 
v_result};
 
@@ -265,13 +262,10 @@ PrimFunc MakePackedAPI(PrimFunc func) {
   Stmt body = ffi::make_object<ReturnRewriter>(v_result)
                   ->Mutate(func_ptr->body.value(), InplaceMode::kAllow)
                   .ValueOrUnchanged(func_ptr->body.value());
-  body = AttrStmt(0, attr::compute_scope, StringImm(name_hint + "_compute_"), 
body);
+  body = RegionStmt(builtin::compute_scope(), {StringImm(name_hint + 
"_compute_")}, {}, DictAttrs(),
+                    body);
   // Set device context
   if (need_set_device) {
-    ffi::Any node = ffi::String("default");
-    seq_check.push_back(AttrStmt(node, attr::device_id, 
device_id.as_or_throw<PrimExpr>(), nop));
-    seq_check.push_back(AttrStmt(node, attr::device_type, device_type, nop));
-
     if (runtime::DeviceAPI::NeedSetDevice(target_device_type)) {
       Stmt set_device = Evaluate(Call(PrimType::Int(32), 
builtin::tvm_call_packed(),
                                       
{StringImm(runtime::symbol::tvm_set_device), device_type,
@@ -287,7 +281,11 @@ PrimFunc MakePackedAPI(PrimFunc func) {
   // Tensor declarations and alignment assumptions are ordinary statements,
   // not scopes with a body hole for MergeNest to fill.
   body = SeqStmt::Flatten(result.decl_buffers, body);
-  body = MergeNest({std::move(result.init_nest), seq_check, 
std::move(result.asserts)}, body);
+  body = MergeNest(std::move(result.asserts), body);
+  if (need_set_device) {
+    body = RegionStmt(builtin::device_context(), {device_type, device_id}, {}, 
DictAttrs(), body);
+  }
+  body = MergeNest(std::move(result.init_nest), body);
   func_ptr->body = body;
   func_ptr->params = args;
 
diff --git a/tests/python/s_tir/script/test_s_tir_script_printer.py 
b/tests/python/s_tir/script/test_s_tir_script_printer.py
index 7d9e1ec37b..102232b06c 100644
--- a/tests/python/s_tir/script/test_s_tir_script_printer.py
+++ b/tests/python/s_tir/script/test_s_tir_script_printer.py
@@ -1149,26 +1149,26 @@ def opt_conv_tensorcore_mod_host():
         T.evaluate(T.tvm_struct_set(stack_value, 1, 12, T.cast(dev_id, 
"int64")))
         stack_tcode[1] = 0
         T.evaluate(T.tvm_call_packed_lowered("__tvm_set_device", stack_value, 
0, 2))
-        T.attr(0, "compute_scope", "default_function_compute_")
-        T.evaluate(T.tvm_struct_set(stack_value, 0, 12, A))
-        stack_tcode[0] = 3
-        T.evaluate(T.tvm_struct_set(stack_value, 1, 12, W))
-        stack_tcode[1] = 3
-        T.evaluate(T.tvm_struct_set(stack_value, 2, 12, Conv))
-        stack_tcode[2] = 3
-        T.evaluate(T.tvm_struct_set(stack_value, 3, 12, T.cast(196, "int64")))
-        stack_tcode[3] = 0
-        T.evaluate(T.tvm_struct_set(stack_value, 4, 12, T.cast(2, "int64")))
-        stack_tcode[4] = 0
-        T.evaluate(T.tvm_struct_set(stack_value, 5, 12, T.cast(4, "int64")))
-        stack_tcode[5] = 0
-        T.evaluate(T.tvm_struct_set(stack_value, 6, 12, T.cast(4, "int64")))
-        stack_tcode[6] = 0
-        T.evaluate(T.tvm_struct_set(stack_value, 7, 12, T.cast(2, "int64")))
-        stack_tcode[7] = 0
-        T.evaluate(T.tvm_struct_set(stack_value, 8, 12, T.cast(32, "int64")))
-        stack_tcode[8] = 0
-        T.evaluate(T.tvm_call_packed_lowered("default_function_kernel0", 
stack_value, 0, 9))
+        with T.compute_scope("default_function_compute_"):
+            T.evaluate(T.tvm_struct_set(stack_value, 0, 12, A))
+            stack_tcode[0] = 3
+            T.evaluate(T.tvm_struct_set(stack_value, 1, 12, W))
+            stack_tcode[1] = 3
+            T.evaluate(T.tvm_struct_set(stack_value, 2, 12, Conv))
+            stack_tcode[2] = 3
+            T.evaluate(T.tvm_struct_set(stack_value, 3, 12, T.cast(196, 
"int64")))
+            stack_tcode[3] = 0
+            T.evaluate(T.tvm_struct_set(stack_value, 4, 12, T.cast(2, 
"int64")))
+            stack_tcode[4] = 0
+            T.evaluate(T.tvm_struct_set(stack_value, 5, 12, T.cast(4, 
"int64")))
+            stack_tcode[5] = 0
+            T.evaluate(T.tvm_struct_set(stack_value, 6, 12, T.cast(4, 
"int64")))
+            stack_tcode[6] = 0
+            T.evaluate(T.tvm_struct_set(stack_value, 7, 12, T.cast(2, 
"int64")))
+            stack_tcode[7] = 0
+            T.evaluate(T.tvm_struct_set(stack_value, 8, 12, T.cast(32, 
"int64")))
+            stack_tcode[8] = 0
+            T.evaluate(T.tvm_call_packed_lowered("default_function_kernel0", 
stack_value, 0, 9))
 
     return opt_conv_tensorcore_mod_host
 
@@ -1927,21 +1927,20 @@ def tvm_struct_set_generated_in_cpp():
     class Module:
         @Ts.prim_func
         def tir_packed_call(A: T.Tensor(16)):
-            T.attr(0, "device_id", 0)
-            T.attr(0, "device_type", 0)
-            T.evaluate(
-                T.tvm_call_cpacked(
-                    "tvm_test_cpacked",
-                    T.tvm_stack_make_array(
-                        A.data,
-                        T.tvm_stack_make_shape(16),
-                        T.reinterpret(T.uint64(0), dtype="handle"),
-                        T.uint32(1),
-                        T.Cast("float32", 0),
-                        0,
-                    ),
+            with T.device_context(0, 0):
+                T.evaluate(
+                    T.tvm_call_cpacked(
+                        "tvm_test_cpacked",
+                        T.tvm_stack_make_array(
+                            A.data,
+                            T.tvm_stack_make_shape(16),
+                            T.reinterpret(T.uint64(0), dtype="handle"),
+                            T.uint32(1),
+                            T.Cast("float32", 0),
+                            0,
+                        ),
+                    )
                 )
-            )
 
     return tvm.tirx.transform.LowerTVMBuiltin()(Module)
 
diff --git 
a/tests/python/tirx-transform/test_tir_transform_lower_tvm_builtin.py 
b/tests/python/tirx-transform/test_tir_transform_lower_tvm_builtin.py
index 7ed2f330be..82b7938d91 100644
--- a/tests/python/tirx-transform/test_tir_transform_lower_tvm_builtin.py
+++ b/tests/python/tirx-transform/test_tir_transform_lower_tvm_builtin.py
@@ -40,8 +40,8 @@ def test_lower_call_packed():
             C: T.Tensor((64, 64), "float32"),
         ):
             T.func_attr({"target": tvm.target.Target("llvm")})
-            T.attr("", "device_id", T.int32(0))
-            T.call_packed("tvm.test_matmul", A, B, C)
+            with T.device_context(1, 0):
+                T.call_packed("tvm.test_matmul", A, B, C)
 
     @I.ir_module(check_well_formed=False)
     class Expected:
@@ -218,11 +218,10 @@ def test_lower_device_allocate():
         @T.prim_func
         def main():
             T.func_attr({"target": T.target("llvm")})
-            T.attr("dummy", "device_type", 2)  # kDLCuda
-            T.attr("dummy", "device_id", 0)
-            ptr = T.alloc_tensor((16,), "float32")
-            buf = T.decl_tensor(16, "float32", data=ptr.data)
-            buf[0] = 0.0
+            with T.device_context(2, 0):  # kDLCuda
+                ptr = T.alloc_tensor((16,), "float32")
+                buf = T.decl_tensor(16, "float32", data=ptr.data)
+                buf[0] = 0.0
 
     After = tvm.tirx.transform.LowerTVMBuiltin()(Before)
     # Verify the lowered module can be printed (no crash)
@@ -242,11 +241,10 @@ def test_lower_cpu_allocation():
         @T.prim_func
         def main():
             T.func_attr({"target": T.target("llvm")})
-            T.attr("dummy", "device_type", 1)  # kDLCPU
-            T.attr("dummy", "device_id", 0)
-            ptr = T.alloc_tensor((16,), "float32")
-            buf = T.decl_tensor(16, "float32", data=ptr.data)
-            buf[0] = 0.0
+            with T.device_context(1, 0):  # kDLCPU
+                ptr = T.alloc_tensor((16,), "float32")
+                buf = T.decl_tensor(16, "float32", data=ptr.data)
+                buf[0] = 0.0
 
     @I.ir_module
     class Expected:
@@ -268,8 +266,7 @@ def test_lower_allocate_requires_device_id():
     class Before:
         @T.prim_func
         def main():
-            T.func_attr({"target": T.target("llvm")})
-            T.attr("dummy", "device_type", 2)  # kDLCuda
+            T.func_attr({"target": T.target("cuda"), "tirx.is_host_func": 
True})
             ptr = T.alloc_tensor((16,), "float32")
             buf = T.decl_tensor(16, "float32", data=ptr.data)
             buf[0] = 0.0
@@ -281,8 +278,8 @@ def test_lower_allocate_requires_device_id():
 def test_lower_allocate_requires_device_type():
     """If device type is missing, error.
 
-    The device type can be inferred either from the `"device_type"`
-    statement attribute, or from the `"target"` function attribute.
+    The device type can be inferred either from a device_context
+    region or from the `"target"` function attribute.
     Here, we provide neither.  The `"tirx.is_host_func"` attribute is
     provided as otherwise the function would be skipped altogether by
     LowerTVMBuiltin.
@@ -293,7 +290,6 @@ def test_lower_allocate_requires_device_type():
         @T.prim_func
         def main():
             T.func_attr({"tirx.is_host_func": True})
-            T.attr("dummy", "device_id", 0)
             ptr = T.alloc_tensor((1024 * 1024,), "float32")
             buf = T.decl_tensor(1024 * 1024, "float32", data=ptr.data)
             buf[0] = 0.0
@@ -306,7 +302,7 @@ def test_lower_cpu_alloc_with_function_attr():
     """CPU allocations can be handled at codegen time
 
     Like `test_lower_cpu_allocation`, but the device type is taken from
-    the function attribute.  The `AttrStmt` can override the device
+    the function attribute.  A device_context region can override the device
     type for allocations within its scope, but it defaults to the
     function's target.
     """
diff --git a/tests/python/tirx-transform/test_tir_transform_make_packed_api.py 
b/tests/python/tirx-transform/test_tir_transform_make_packed_api.py
index bcb5ce14b9..00efe234ab 100644
--- a/tests/python/tirx-transform/test_tir_transform_make_packed_api.py
+++ b/tests/python/tirx-transform/test_tir_transform_make_packed_api.py
@@ -43,7 +43,9 @@ def _find_compute_scope(func):
     result = None
 
     def _visitor(stmt):
-        if isinstance(stmt, tirx.AttrStmt) and stmt.attr_key == 
"compute_scope":
+        if isinstance(stmt, tirx.RegionStmt) and stmt.op.same_as(
+            tvm.ir.Op.get("tirx.compute_scope")
+        ):
             nonlocal result
             result = stmt
 
@@ -194,7 +196,7 @@ def test_zero_arg_function():
                 "TypeError",
                 ["Expected ", "0", " arguments", " when calling:\n  `", 
"func_without_arg()", "`"],
             )
-            with T.attr(0, "compute_scope", "func_without_arg_compute_"):
+            with T.compute_scope("func_without_arg_compute_"):
                 T.tvm_struct_set(result, 0, 13, 1)
                 T.tvm_struct_set(result, 0, 14, 0)
                 T.tvm_struct_set(result, 0, 15, T.Cast("int64", T.int64(42)))
@@ -292,7 +294,7 @@ def test_int_parameter():
                 ],
             )
             arg: T.let[T.int32] = T.Cast("int32", T.tvm_struct_get(args, 0, 
15, "int64"))
-            with T.attr(0, "compute_scope", "main_compute_"):
+            with T.compute_scope("main_compute_"):
                 if arg > 0:
                     T.tvm_struct_set(result, 0, 13, 1)
                     T.tvm_struct_set(result, 0, 14, 0)
@@ -359,7 +361,7 @@ def test_bool_parameter():
                 ],
             )
             arg: T.let[T.bool] = T.Cast("bool", T.tvm_struct_get(args, 0, 15, 
"int64"))
-            with T.attr(0, "compute_scope", "main_compute_"):
+            with T.compute_scope("main_compute_"):
                 if arg:
                     T.tvm_struct_set(result, 0, 13, 1)
                     T.tvm_struct_set(result, 0, 14, 0)
@@ -430,7 +432,7 @@ def test_float_parameter():
                 T.Cast("float32", T.tvm_struct_get(args, 0, 15, "float64")),
                 T.Cast("float32", T.tvm_struct_get(args, 0, 15, "int64")),
             )
-            with T.attr(0, "compute_scope", "main_compute_"):
+            with T.compute_scope("main_compute_"):
                 if arg > T.float32(0.0):
                     T.tvm_struct_set(result, 0, 13, 1)
                     T.tvm_struct_set(result, 0, 14, 0)
diff --git a/tests/python/tirx-transform/test_tir_transform_storage_rewrite.py 
b/tests/python/tirx-transform/test_tir_transform_storage_rewrite.py
index 63c7365a36..c9da13728a 100644
--- a/tests/python/tirx-transform/test_tir_transform_storage_rewrite.py
+++ b/tests/python/tirx-transform/test_tir_transform_storage_rewrite.py
@@ -197,7 +197,7 @@ def test_parallel_alloc():
     @T.prim_func
     def func2(n: T.int32):
         for t in T.serial(n):
-            with T.attr(T.int32(1), "pragma_scope", "parallel_launch_point"):
+            with T.parallel_launch():
                 for i in T.parallel(n):
                     for j in range(10):
                         A = T.alloc_tensor((n,))
@@ -206,7 +206,8 @@ def test_parallel_alloc():
     mod = tvm.IRModule.from_expr(func2)
     body = tvm.tirx.transform.StorageRewrite()(mod)["func2"]
 
-    assert _is_buffer_binding(body.body.body.body.body[0], "tirx.alloc_tensor")
+    # The launch body owns the allocation executed by each worker.
+    assert _is_buffer_binding(body.body.body.body[0], "tirx.alloc_tensor")
 
 
 def test_while_alloc():

Reply via email to