blob: 554dba11c5492f828c6e22c544a38dc782a6e0ce [file]
/*
* 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.
*/
#include <tvm/runtime/logging.h>
#include <utility>
#include "./utils.h"
namespace tvm {
namespace script {
namespace printer {
TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
.set_dispatch<tirx::PrimFunc>("", [](tirx::PrimFunc func, AccessPath p, IRDocsifier d) -> Doc {
With<TIRFrame> f(d, func);
(*f)->AddDispatchToken(d, "tirx");
IdDoc func_name = IdDoc(FindFunctionName(d, func).value_or("main"));
d->SetCommonPrefix(func, [](const ffi::ObjectRef& obj) {
return obj->IsInstance<tirx::VarNode>() || obj->IsInstance<tirx::BufferTypeNode>();
});
std::unordered_set<const VarNode*> runtime_params;
for (const tirx::Var& param : func->params) {
runtime_params.insert(param.get());
}
std::unordered_set<const VarNode*> type_vars;
auto collect_type_vars = [&](const PrimExpr& expr) {
for (const tirx::Var& var : tirx::UndefinedVars(expr)) {
const auto* var_ty_node = var->ty.as<PrimTypeNode>();
if (var_ty_node == nullptr) {
continue;
}
PrimType var_ty(var_ty_node->dtype);
if (!runtime_params.count(var.get()) && var_ty.IsScalar() &&
var_ty.MatchesElementType(DLDataTypeCode::kDLInt, 64)) {
type_vars.insert(var.get());
}
}
};
for (const tirx::Var& param : func->params) {
if (!param->ty.as<tirx::BufferTypeNode>()) {
continue;
}
tirx::BufferVar buffer(param);
for (const PrimExpr& extent : buffer->shape) {
collect_type_vars(extent);
}
for (const PrimExpr& stride : buffer->strides) {
collect_type_vars(stride);
}
collect_type_vars(buffer->elem_offset);
for (const PrimExpr& address : buffer->allocated_addr) {
collect_type_vars(address);
}
}
auto type_var_docs = DefineTypeVarDocs(type_vars, ffi::GetRef<Frame>((*f).get()), d);
bool use_postponed_annotations = UsePEP695TypeVars(d) && !type_vars.empty();
int n_args = func->params.size();
// Step 1. Handle `func->params`
ffi::Array<AssignDoc> args;
args.reserve(n_args);
std::unordered_map<const tirx::VarNode*, ExprDoc> scalar_param_docs;
// Define scalar docs up front so a preceding Buffer parameter can render
// a reference to a later scalar parameter. `bound_signature_vars`
// separately tracks source order: the first shape expression that sees
// an unbound Var must be quoted because Buffer shapes are match scopes.
std::unordered_set<tirx::Var> bound_signature_vars;
for (const tirx::Var& param : func->params) {
if (!param->ty.as<tirx::BufferTypeNode>()) {
scalar_param_docs.emplace(param.get(), DefineVar(param, *f, d));
}
}
for (int i = 0; i < n_args; ++i) {
tirx::Var var = func->params[i];
AccessPath var_p = p->Attr("params")->ArrayItem(i);
if (var->ty.as<tirx::BufferTypeNode>()) {
tirx::BufferVar buffer(var);
std::unordered_set<tirx::Var> stringify_shape_vars;
std::unordered_set<tirx::Var> stringify_compound_shape_vars;
std::unordered_set<tirx::Var> shape_vars;
for (const PrimExpr& shape : buffer->shape) {
tirx::PostOrderVisit(shape, [&](const ffi::ObjectRef& obj) {
if (const auto* shape_var_node = obj.as<tirx::VarNode>()) {
tirx::Var shape_var = ffi::GetRef<tirx::Var>(shape_var_node);
shape_vars.insert(shape_var);
bool is_type_var = type_vars.count(shape_var.get());
if (!use_postponed_annotations && !bound_signature_vars.count(shape_var) &&
!is_type_var) {
stringify_shape_vars.insert(shape_var);
}
if (!use_postponed_annotations && is_type_var) {
stringify_compound_shape_vars.insert(shape_var);
}
}
});
}
IdDoc lhs = DefineBuffer(buffer, *f, d);
ExprDoc annotation =
BufferAttn(buffer, var_p->Attr("ty"), *f, d, std::move(stringify_shape_vars),
std::move(stringify_compound_shape_vars));
args.push_back(AssignDoc(lhs, std::nullopt, annotation));
for (const tirx::Var& shape_var : shape_vars) {
bound_signature_vars.insert(shape_var);
}
continue;
}
ExprDoc a = d->AsDoc<ExprDoc>(var->ty, var_p->Attr("ty"));
args.push_back(AssignDoc(scalar_param_docs.at(var.get()), std::nullopt, a));
bound_signature_vars.insert(var);
}
ffi::Optional<ExprDoc> ret_type = std::nullopt;
if (!func->ret_type.IsMissing()) {
const auto* as_tuple = func->ret_type.as<TupleTypeNode>();
if (!as_tuple || as_tuple->fields.size()) {
ret_type = d->AsDoc<ExprDoc>(func->ret_type, p->Attr("ret_type"));
}
}
// Step 2. Handle `func->attrs`
if (!func->attrs->dict.empty()) {
// for global symbol, don't display it if it matches the func name
std::unordered_set<ffi::String> keys_to_remove;
if (func->attrs->dict.count(tvm::attr::kGlobalSymbol) &&
func->attrs->dict.at(tvm::attr::kGlobalSymbol).as_or_throw<ffi::String>() ==
func_name->name) {
keys_to_remove.insert(tvm::attr::kGlobalSymbol);
}
// s_tir is shown in decorator, not in attr dict.
if (func->attrs->dict.count(tvm::attr::kSTir)) {
keys_to_remove.insert(tvm::attr::kSTir);
}
// for persistent, don't display it (shown in decorator)
if (func->attrs->dict.count(tirx::attr::kPersistentKernel)) {
keys_to_remove.insert(tirx::attr::kPersistentKernel);
}
ffi::Map<ffi::String, Any> new_attrs;
for (auto kv : func->attrs->dict) {
if (!keys_to_remove.count(kv.first)) {
new_attrs.Set(kv.first, kv.second);
}
}
if (!new_attrs.empty()) {
(*f)->stmts.push_back(
ExprStmtDoc(TIR(d, "func_attr") //
->Call({d->AsDoc<ExprDoc>(DictAttrs(new_attrs), p->Attr("attrs"))})));
}
}
// Step 3. Handle `func->body`
ffi::Optional<tirx::SBlock> implicit_root_block = [&]() -> ffi::Optional<tirx::SBlock> {
const tirx::SBlockRealizeNode* root_block_realize =
func->body.as<tirx::SBlockRealizeNode>();
if (root_block_realize && !root_block_realize->iter_values.size() &&
tirx::is_one(root_block_realize->predicate)) {
tirx::SBlock root_block = root_block_realize->block;
if (!root_block->annotations.size() && !root_block->match_buffers.size() &&
!root_block->reads.size() && !root_block->writes.size() &&
!root_block->init.has_value()) {
const tirx::SBlockRealizeNode* block_realize =
root_block->body.as<tirx::SBlockRealizeNode>();
if (root_block->alloc_buffers.size() ||
(block_realize && block_realize->block->iter_vars.size()) ||
(!block_realize && tirx::ContainsNode<tirx::SBlockRealizeNode>(root_block->body))) {
return root_block;
}
}
}
return std::nullopt;
}();
if (d->cfg->syntax_sugar && implicit_root_block) {
tirx::SBlock root_block = implicit_root_block.value();
AccessPath root_block_p = p->Attr("body")->Attr("block");
(*f)->stmts.push_back(CommentDoc("with T.sblock(\"root\"):"));
// Handle root block `alloc_buffer`
for (int i = 0, n = root_block->alloc_buffers.size(); i < n; ++i) {
tirx::BufferVar buffer = root_block->alloc_buffers[i];
AccessPath buffer_p = root_block_p->Attr("alloc_buffers")->ArrayItem(i);
IdDoc lhs = DefineBuffer(buffer, *f, d);
ExprDoc rhs = BufferDecl(buffer, "sblock_alloc_buffer", {}, buffer_p, *f, d,
BufferVarDefinition::DataPointer);
(*f)->stmts.push_back(AssignDoc(lhs, rhs, std::nullopt));
}
AsDocBody(root_block->body, root_block_p->Attr("body"), f->get(), d);
} else {
AsDocBody(func->body, p->Attr("body"), f->get(), d);
}
// Step 5. Determine if we need to display the private annotation in the decorator
ExprDoc decorator = TIR(d, "prim_func");
ffi::Array<ffi::String, void> kwargs_keys;
ffi::Array<ExprDoc, void> kwargs_values;
// mark private if there is no global symbol
if (!func->attrs->dict.count(tvm::attr::kGlobalSymbol)) {
kwargs_keys.push_back("private");
kwargs_values.push_back(LiteralDoc::Boolean(true, ffi::Optional<AccessPath>()));
}
if (func->attrs->dict.count(tvm::attr::kSTir)) {
kwargs_keys.push_back("s_tir");
kwargs_values.push_back(LiteralDoc::Boolean(true, ffi::Optional<AccessPath>()));
}
if (func->attrs->dict.count(tirx::attr::kPersistentKernel)) {
kwargs_keys.push_back("persistent");
kwargs_values.push_back(LiteralDoc::Boolean(true, ffi::Optional<AccessPath>()));
}
// Only emit ``@T.prim_func(...)`` when there is at least one keyword
// argument; otherwise print bare ``@T.prim_func`` to match apache.
if (!kwargs_keys.empty()) {
ffi::Array<ExprDoc> pos_args;
decorator = std::move(decorator->Call(pos_args, kwargs_keys, kwargs_values));
}
return WrapFunctionDocWithTypeVars(d,
FunctionDoc(
/*name=*/func_name,
/*args=*/args,
/*decorators=*/{decorator},
/*return_type=*/ret_type,
/*body=*/(*f)->stmts),
type_var_docs);
});
TVM_REGISTER_SCRIPT_AS_REPR(tirx::PrimFuncNode, ReprPrintTIR);
TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
.set_dispatch<tvm::GlobalVar>( //
"tirx", [](tvm::GlobalVar n, AccessPath n_p, IRDocsifier d) -> Doc { //
if (ffi::Optional<ExprDoc> doc = d->GetVarDoc(n)) {
return doc.value();
} else {
IdDoc ret(n->name_hint);
ret->source_paths.push_back(n_p);
return ret;
}
});
TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
.set_dispatch<tvm::IRModule>( //
"tirx", [](tvm::IRModule mod, AccessPath n_p, IRDocsifier d) -> Doc { //
ffi::Optional<ExprDoc> doc = d->GetVarDoc(mod);
TVM_FFI_ICHECK(doc) << "Unable to print IRModule before definition in TIR.";
return doc.value();
});
} // namespace printer
} // namespace script
} // namespace tvm