blob: 869b842a108a4f9c32f7d59818dd8b13e9811dff [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 <algorithm>
#include "./utils.h"
namespace tvm {
namespace script {
namespace printer {
static bool HasDefaultExternFuncType(const relax::ExternFunc& n) {
const auto* ty = n->ty.as<relax::FuncTypeNode>();
if (ty == nullptr || ty->params.has_value() || ty->purity ||
!ty->ret->IsInstance<relax::AnyTypeNode>()) {
return false;
}
return true;
}
bool AtTopLevelFunction(const IRDocsifier& d) {
// fewer than 2 frames: not in a function at all
if (d->frames.size() < 2) {
return false;
}
// if the first frame is a RelaxFrame, then this is not inside a module.
// 2 frames => we are at a function (more than 2 => nested function)
if (d->frames[0]->IsInstance<RelaxFrameNode>()) {
return d->frames.size() == 2;
}
// otherwise the first two frames pertain to an IR module,
// so 3 frames => we are at a top-level function (more than 3 => nested function)
return d->frames.size() == 3;
}
TVM_FFI_STATIC_INIT_BLOCK() { RelaxFrameNode::RegisterReflection(); }
TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
.set_dispatch<relax::Function>("", [](relax::Function n, AccessPath n_p, IRDocsifier d) -> Doc {
std::unordered_set<const VarNode*> func_vars;
std::unordered_set<const VarNode*> type_vars;
std::unordered_set<const VarNode*> prim_params;
With<RelaxFrame> f(d);
IdDoc func_name("");
// if we are binding a local definition, then calling d->Define
// will result in a repeated definition and an incorrect displayed name
if (ffi::Optional<ffi::String> name = GetBindingName(d)) {
func_name = IdDoc(name.value());
} else {
func_name = IdDoc(FindFunctionName(d, n).value_or("main"));
}
(*f)->AddDispatchToken(d, "relax");
(*f)->is_func = true;
(*f)->func_vars = &func_vars;
(*f)->type_vars = &type_vars;
(*f)->prim_params = &prim_params;
for (const Var& param : n->params) {
if (param->ty.as<PrimTypeNode>()) {
prim_params.insert(param.get());
}
}
// Step 1. Print params
ffi::Array<AssignDoc> params;
{
AccessPath params_p = n_p->Attr("params");
for (int i = 0, l = n->params.size(); i < l; ++i) {
params.push_back(AssignDoc(
/*lhs=*/DefineRelaxVar(n->params[i], *f, d),
/*rhs=*/std::nullopt,
TypeAsAnn(n->params[i], params_p->ArrayItem(i), d, std::nullopt)));
}
}
// Step 2. Print the return type
ffi::Optional<ExprDoc> ret_type = d->AsDoc<ExprDoc>(n->ret_ty, n_p->Attr("ret_ty"));
// Step 3. Clean up func variables
(*f)->func_vars = nullptr;
(*f)->type_vars = nullptr;
(*f)->prim_params = nullptr;
// Step 4. Print attributes
ffi::Map<ffi::String, Any> printable_attrs;
for (const auto& [key, value] : n->attrs->dict) {
// A matching global symbol is implicit for a top-level function.
if (key == tvm::attr::kGlobalSymbol && AtTopLevelFunction(d) &&
value.as_or_throw<ffi::String>() == func_name->name) {
continue;
}
printable_attrs.Set(key, value);
}
if (!printable_attrs.empty()) {
(*f)->stmts.push_back(ExprStmtDoc(
Relax(d, "func_attr") //
->Call({d->AsDoc<ExprDoc>(DictAttrs(printable_attrs), n_p->Attr("attrs"))})));
}
// Step 5. Prepare the decorator (include purity if it's impure)
ExprDoc decorator = Relax(d, "function");
ffi::Array<ExprDoc, void> pos_args = {};
ffi::Array<ffi::String, void> dec_keys;
ffi::Array<ExprDoc, void> dec_values;
if (!n->is_pure) {
dec_keys.push_back("pure");
dec_values.push_back(LiteralDoc::Boolean(false, ffi::Optional<AccessPath>()));
}
// if the function is global or is not in a module and does not have a global symbol,
// indicate that it's private
if (AtTopLevelFunction(d) && !n->attrs->dict.count(tvm::attr::kGlobalSymbol)) {
dec_keys.push_back("private");
dec_values.push_back(LiteralDoc::Boolean(true, ffi::Optional<AccessPath>()));
}
if (dec_keys.size()) {
decorator = decorator->Call(pos_args, dec_keys, dec_values);
}
// Step 6. Print body
ffi::Array<StmtDoc> body = PrintSeqExpr(n->body, n_p->Attr("body"), d, /*use_ret=*/true);
(*f)->stmts.insert((*f)->stmts.end(), body.begin(), body.end());
auto type_var_docs = DefineTypeVarDocs(type_vars, ffi::GetRef<Frame>((*f).get()), d);
return WrapFunctionDocWithTypeVars(
d, FunctionDoc(func_name, params, {decorator}, ret_type, (*f)->stmts), type_var_docs);
});
TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
.set_dispatch<relax::ExternFunc>( //
"", [](relax::ExternFunc n, AccessPath n_p, IRDocsifier d) -> Doc {
ffi::Array<ExprDoc> args;
args.push_back(LiteralDoc::Str(n->global_symbol, n_p->Attr("global_symbol")));
if (!HasDefaultExternFuncType(n)) {
args.push_back(d->AsDoc<ExprDoc>(n->ty, n_p->Attr("ty")));
}
return Relax(d, "ExternFunc")->Call(args);
});
TVM_REGISTER_SCRIPT_AS_REPR(relax::FunctionNode, ReprPrintRelax);
TVM_REGISTER_SCRIPT_AS_REPR(relax::ExternFuncNode, ReprPrintRelax);
} // namespace printer
} // namespace script
} // namespace tvm