365 lines
13 KiB
C++
365 lines
13 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
#ifndef TVM_SCRIPT_PRINTER_IR_DOCSIFIER_H_
|
|
#define TVM_SCRIPT_PRINTER_IR_DOCSIFIER_H_
|
|
|
|
#include <tvm/ffi/reflection/access_path.h>
|
|
#include <tvm/ffi/reflection/registry.h>
|
|
#include <tvm/ir/module.h>
|
|
#include <tvm/script/printer/config.h>
|
|
#include <tvm/script/printer/doc.h>
|
|
#include <tvm/script/printer/ir_docsifier_functor.h>
|
|
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace tvm {
|
|
namespace script {
|
|
namespace printer {
|
|
|
|
using AccessPath = ffi::reflection::AccessPath;
|
|
|
|
//////////////////////// Frame ////////////////////////
|
|
|
|
class IRDocsifier;
|
|
class IRDocsifierNode;
|
|
|
|
/*!
|
|
* Frame is the core data structure for semantic information
|
|
* when printing IR graph into TVMScript code.
|
|
*/
|
|
class FrameNode : public ffi::Object {
|
|
public:
|
|
/*! The docs generated in the frame */
|
|
ffi::Array<StmtDoc> stmts;
|
|
/*! The corresponding IRDocsifier */
|
|
IRDocsifierNode* d;
|
|
/*! The callbacks that are going to be invoked when the frame exits */
|
|
std::vector<std::function<void()>> callbacks;
|
|
|
|
static void RegisterReflection() {
|
|
namespace refl = tvm::ffi::reflection;
|
|
refl::ObjectDef<FrameNode>().def_ro("stmts", &FrameNode::stmts);
|
|
}
|
|
|
|
static constexpr const bool _type_mutable = true;
|
|
TVM_FFI_DECLARE_OBJECT_INFO("script.printer.Frame", FrameNode, ffi::Object);
|
|
|
|
public:
|
|
virtual ~FrameNode() = default;
|
|
|
|
/*!
|
|
* \brief Add a callback function to be called when this frame exits.
|
|
* \param cb The callback function. It should have signature void().
|
|
*/
|
|
template <typename TCallback>
|
|
void AddExitCallback(TCallback&& cb) {
|
|
callbacks.emplace_back(std::forward<TCallback>(cb));
|
|
}
|
|
/*!
|
|
* \brief Add a dispatch token to the docsifier, and a callback that pops the token when this
|
|
* frame exits.
|
|
* \param d The docsifier.
|
|
* \param token The token to be added.
|
|
*/
|
|
void AddDispatchToken(const IRDocsifier& d, const ffi::String& token);
|
|
/*!
|
|
* \brief Method that's called when Frame enters the scope.
|
|
*/
|
|
virtual void EnterWithScope();
|
|
/*!
|
|
* \brief Method that's called when Frame exits the scope.
|
|
*/
|
|
virtual void ExitWithScope();
|
|
};
|
|
|
|
/*!
|
|
* \brief Reference type of FrameNode
|
|
*/
|
|
class Frame : public ffi::ObjectRef {
|
|
protected:
|
|
Frame() = default;
|
|
|
|
public:
|
|
virtual ~Frame() = default;
|
|
|
|
/*! \brief Method that's called when Frame enters the scope. */
|
|
void EnterWithScope() { get()->EnterWithScope(); }
|
|
|
|
/*! \brief Method that's called when Frame exits the scope. */
|
|
void ExitWithScope() { get()->ExitWithScope(); }
|
|
|
|
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Frame, ffi::ObjectRef, FrameNode);
|
|
};
|
|
|
|
//////////////////////// IRDocsifier ////////////////////////
|
|
|
|
/*!
|
|
* \brief IRDocsifier is the top-level interface in the IR->Doc process.
|
|
*
|
|
* It provides methods to convert IR node object to Doc, operate on Frame
|
|
* objects and change dispatch tokens.
|
|
*/
|
|
class IRDocsifierNode : public ffi::Object {
|
|
public:
|
|
/*! \brief A function that creates the doc for a variable */
|
|
using DocCreator = std::function<ExprDoc()>;
|
|
/*! \brief Information about a variable, including its optional name and its doc creator */
|
|
struct VariableInfo {
|
|
/*! \brief The creator */
|
|
DocCreator creator;
|
|
/*! \brief The name of the variable */
|
|
ffi::Optional<ffi::String> name;
|
|
};
|
|
/*! \brief The configuration of the printer */
|
|
PrinterConfig cfg{ffi::UnsafeInit()};
|
|
/*!
|
|
* \brief The stack of frames.
|
|
* \sa FrameNode
|
|
*/
|
|
ffi::Array<Frame> frames;
|
|
/*!
|
|
* \brief The stack of dispatch tokens.
|
|
*
|
|
* The dispatch token on the top decides which dispatch function to use
|
|
* when converting IR node object to Doc.
|
|
*/
|
|
ffi::Array<ffi::String> dispatch_tokens;
|
|
/*! \brief Mapping from a var to its info */
|
|
std::unordered_map<ffi::ObjectRef, VariableInfo, ffi::ObjectPtrHash, ffi::ObjectPtrEqual>
|
|
obj2info;
|
|
/*! \brief Metadata printing */
|
|
std::unordered_map<ffi::String, ffi::Array<ffi::Any>> metadata;
|
|
/*! \brief GlobalInfo printing */
|
|
std::unordered_map<ffi::String, ffi::Array<GlobalInfo>> global_infos;
|
|
/*! \brief The variable names used already */
|
|
std::unordered_set<ffi::String> defined_names;
|
|
/*! \brief Common prefixes of variable usages */
|
|
std::unordered_map<const ffi::Object*, std::vector<const ffi::Object*>> common_prefix;
|
|
/*! \brief The IR usages for headers printing */
|
|
std::unordered_set<std::string> ir_usage;
|
|
|
|
static void RegisterReflection() {
|
|
namespace refl = tvm::ffi::reflection;
|
|
refl::ObjectDef<IRDocsifierNode>()
|
|
.def_ro("frames", &IRDocsifierNode::frames)
|
|
.def_ro("dispatch_tokens", &IRDocsifierNode::dispatch_tokens);
|
|
}
|
|
|
|
static constexpr const bool _type_mutable = true;
|
|
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("script.printer.IRDocsifier", IRDocsifierNode, ffi::Object);
|
|
|
|
public:
|
|
/*!
|
|
* \brief Define variable by name.
|
|
* \param obj The variable object.
|
|
* \param frame The frame that this variable is defined in.
|
|
* \param name_hint The hint for variable name.
|
|
*
|
|
* \return The id doc for this variable.
|
|
*
|
|
* This function will rename the variable to avoid name conflict with other variables
|
|
* in the table.
|
|
*/
|
|
IdDoc Define(const ffi::ObjectRef& obj, const Frame& frame, const ffi::String& name_hint);
|
|
|
|
/*!
|
|
* \brief Define variable by doc factory.
|
|
* \param obj The variable object.
|
|
* \param frame The frame that this variable is defined in.
|
|
* \param doc_factory The function to return an ExprDoc object for this variable.
|
|
*
|
|
* This function is a special form of `Define`. Variable is mapped to ExprDoc rather
|
|
* than IdDoc. It's useful when a variable is implicitly defined without a name, like
|
|
* the buf->data in TIR, which should be mapped to `AttrDoc(IdDoc("<buffer_name>"), "data")`.
|
|
*
|
|
* This function takes a DocFactory instead of Doc. It's because GetVarDoc needs to
|
|
* return a new Doc object every time it's called, as the returned doc will have
|
|
* different `source_path`. Currently there isn't a good way to deep copy a TVMObject
|
|
* so VarTable needs to call a factory function to get a freshly-constructed Doc object
|
|
* every time GetVarDoc is called.
|
|
*/
|
|
void Define(const ffi::ObjectRef& obj, const Frame& frame, DocCreator doc_factory);
|
|
|
|
/*!
|
|
* \brief Get the doc for variable.
|
|
* \param obj The variable object.
|
|
*
|
|
* \return The doc for variable, if it exists in the table. Otherwise it returns std::nullopt.
|
|
*/
|
|
ffi::Optional<ExprDoc> GetVarDoc(const ffi::ObjectRef& obj) const;
|
|
/*! \brief Add a TVM object to the metadata section*/
|
|
ExprDoc AddMetadata(const ffi::Any& obj);
|
|
/*! \brief Add a GlobalInfo to the global_infos map.
|
|
* \param name The name of key of global_infos.
|
|
* \param ginfo The GlobalInfo to be added.
|
|
*/
|
|
void AddGlobalInfo(const ffi::String& name, const GlobalInfo& ginfo);
|
|
/*!
|
|
* \brief Check if a variable exists in the table.
|
|
* \param obj The variable object.
|
|
*
|
|
* \return a boolean for whether variable exists.
|
|
*/
|
|
bool IsVarDefined(const ffi::ObjectRef& obj) const;
|
|
/*! \brief Remove the variable defined */
|
|
void RemoveVar(const ffi::ObjectRef& obj);
|
|
/*!
|
|
* \brief Set the common prefix information of variable usage.
|
|
* \param root The root of the AST.
|
|
* \param is_var A function that returns true if the given object is considered a variable.
|
|
*/
|
|
void SetCommonPrefix(const ffi::ObjectRef& root, ffi::TypedFunction<bool(ffi::ObjectRef)> is_var);
|
|
/*!
|
|
* \brief Transform the input object into TDoc.
|
|
* \param obj The object to be transformed.
|
|
* \param path The path to this object.
|
|
*
|
|
* \return The Doc object.
|
|
*/
|
|
template <class TDoc = Doc>
|
|
inline TDoc AsDoc(const Any& obj, const AccessPath& path) const;
|
|
};
|
|
|
|
/*!
|
|
* \brief Reference type of IRDocsifierNode.
|
|
*/
|
|
class IRDocsifier : public ffi::ObjectRef {
|
|
public:
|
|
using FType = IRDocsifierFunctor<printer::Doc, AccessPath, IRDocsifier>;
|
|
/*! \brief Create a IRDocsifier. */
|
|
explicit IRDocsifier(const PrinterConfig& cfg);
|
|
/*! \brief The registration table for IRDocsifier. */
|
|
TVM_DLL static FType& vtable();
|
|
|
|
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(IRDocsifier, ffi::ObjectRef, IRDocsifierNode);
|
|
};
|
|
|
|
//////////////////////// Implementation ////////////////////////
|
|
|
|
inline void FrameNode::EnterWithScope() {
|
|
if (d != nullptr) {
|
|
d->frames.push_back(ffi::GetRef<Frame>(this));
|
|
}
|
|
}
|
|
|
|
inline void FrameNode::ExitWithScope() {
|
|
for (const std::function<void()>& callback : callbacks) {
|
|
callback();
|
|
}
|
|
callbacks.clear();
|
|
if (d != nullptr) {
|
|
d->frames.pop_back();
|
|
}
|
|
}
|
|
|
|
template <class TDoc>
|
|
inline static void AddDocDecoration(const Doc& d, const ffi::ObjectRef& obj, const AccessPath& path,
|
|
const PrinterConfig& cfg) {
|
|
if (cfg->obj_to_annotate.count(obj)) {
|
|
if (const auto* stmt = d.as<StmtDocNode>()) {
|
|
if (stmt->comment.has_value()) {
|
|
stmt->comment = stmt->comment.value() + "\n" + cfg->obj_to_annotate.at(obj);
|
|
} else {
|
|
stmt->comment = cfg->obj_to_annotate.at(obj);
|
|
}
|
|
} else {
|
|
LOG(WARNING) << "Expect StmtDoc to be annotated for object " << obj << ", but got "
|
|
<< d.as_or_throw<TDoc>()->_type_key;
|
|
}
|
|
}
|
|
for (const ffi::ObjectRef& o : cfg->obj_to_underline) {
|
|
if (o.same_as(obj)) {
|
|
cfg->path_to_underline.push_back(path);
|
|
}
|
|
}
|
|
for (const auto& pair : cfg->path_to_annotate) {
|
|
AccessPath p = pair.first;
|
|
ffi::String attn = pair.second;
|
|
if (p->IsPrefixOf(path) && path->IsPrefixOf(p)) {
|
|
if (const auto* stmt = d.as<StmtDocNode>()) {
|
|
if (stmt->comment.has_value()) {
|
|
stmt->comment = stmt->comment.value() + "\n" + attn;
|
|
} else {
|
|
stmt->comment = attn;
|
|
}
|
|
} else {
|
|
LOG(WARNING) << "Expect StmtDoc to be annotated at object path " << p << ", but got "
|
|
<< d.as_or_throw<TDoc>()->_type_key;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class TDoc>
|
|
inline TDoc IRDocsifierNode::AsDoc(const Any& value, const AccessPath& path) const {
|
|
switch (value.type_index()) {
|
|
case ffi::TypeIndex::kTVMFFINone:
|
|
return LiteralDoc::None(path).as_or_throw<TDoc>();
|
|
case ffi::TypeIndex::kTVMFFIBool:
|
|
return LiteralDoc::Boolean(value.as<bool>().value(), path).as_or_throw<TDoc>();
|
|
case ffi::TypeIndex::kTVMFFIInt:
|
|
return LiteralDoc::Int(value.as<int64_t>().value(), path).as_or_throw<TDoc>();
|
|
case ffi::TypeIndex::kTVMFFIFloat:
|
|
return LiteralDoc::Float(value.as<double>().value(), path).as_or_throw<TDoc>();
|
|
case ffi::TypeIndex::kTVMFFISmallStr:
|
|
case ffi::TypeIndex::kTVMFFIStr: {
|
|
std::string string_value = value.cast<std::string>();
|
|
bool has_multiple_lines = string_value.find_first_of('\n') != std::string::npos;
|
|
if (has_multiple_lines) {
|
|
Doc d = const_cast<IRDocsifierNode*>(this)->AddMetadata(string_value);
|
|
// TODO(tqchen): cross check AddDocDecoration
|
|
return d.as_or_throw<TDoc>();
|
|
}
|
|
return LiteralDoc::Str(string_value, path).as_or_throw<TDoc>();
|
|
}
|
|
case ffi::TypeIndex::kTVMFFIDataType:
|
|
return LiteralDoc::DataType(value.as<DLDataType>().value(), path).as_or_throw<TDoc>();
|
|
case ffi::TypeIndex::kTVMFFIDevice:
|
|
return LiteralDoc::Device(value.as<DLDevice>().value(), path).as_or_throw<TDoc>();
|
|
default: {
|
|
if (auto opt_obj = value.as<ffi::ObjectRef>()) {
|
|
ffi::ObjectRef obj = opt_obj.value();
|
|
Doc d = IRDocsifier::vtable()(dispatch_tokens.back(), obj, path,
|
|
ffi::GetRef<IRDocsifier>(this));
|
|
d->source_paths.push_back(path);
|
|
AddDocDecoration<TDoc>(d, obj, path, cfg);
|
|
return d.as_or_throw<TDoc>();
|
|
} else {
|
|
TVM_FFI_THROW(TypeError) << "Cannot handle Any type: `" << value.GetTypeKey() << "`";
|
|
TVM_FFI_UNREACHABLE();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void FrameNode::AddDispatchToken(const IRDocsifier& d, const ffi::String& token) {
|
|
d->dispatch_tokens.push_back(token);
|
|
this->AddExitCallback([doc = d.get()]() { doc->dispatch_tokens.pop_back(); });
|
|
}
|
|
|
|
} // namespace printer
|
|
} // namespace script
|
|
} // namespace tvm
|
|
|
|
#endif // TVM_SCRIPT_PRINTER_IR_DOCSIFIER_H_
|