| /* |
| * 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/ffi/extra/dataclass.h> |
| #include <tvm/ffi/function.h> |
| #include <tvm/ffi/reflection/registry.h> |
| #include <tvm/ir/expr.h> |
| #include <tvm/script/printer/printer.h> |
| |
| #include <algorithm> |
| #include <sstream> |
| |
| namespace tvm { |
| |
| namespace { |
| |
| std::string RenderFallbackWithInvisiblePathInfo(const ffi::String& script, |
| const PrinterConfig& config) { |
| if (!config->render_invisible_path_info || config->path_to_underline.empty()) { |
| return std::string(script); |
| } |
| |
| std::ostringstream os; |
| for (size_t i = 0; i < config->path_to_underline.size(); ++i) { |
| if (i != 0) os << "\n"; |
| os << "Access path: " << config->path_to_underline[i] |
| << "\nNote: No visible object for this path is rendered in TVMScript."; |
| } |
| os << "\n\n" << script; |
| return os.str(); |
| } |
| |
| } // namespace |
| |
| TVM_FFI_STATIC_INIT_BLOCK() { PrinterConfigNode::RegisterReflection(); } |
| |
| TVMScriptPrinter::FType& TVMScriptPrinter::vtable() { |
| static FType inst; |
| return inst; |
| } |
| |
| std::string Script(const ffi::ObjectRef& node, const ffi::Optional<PrinterConfig>& cfg) { |
| PrinterConfig config = cfg.value_or(PrinterConfig()); |
| if (!TVMScriptPrinter::vtable().can_dispatch(node)) { |
| // Fall back to ffi::ReprPrint for types not registered with TVMScriptPrinter. |
| return RenderFallbackWithInvisiblePathInfo(ffi::ReprPrint(ffi::Any(node)), config); |
| } |
| return TVMScriptPrinter::vtable()(node, config); |
| } |
| |
| bool IsIdentifier(const std::string& name) { |
| // Python identifiers follow the regex: "^[a-zA-Z_][a-zA-Z0-9_]*$" |
| // `std::regex` would cause a symbol conflict with PyTorch, we avoids to use it in the codebase. |
| // |
| // We convert the regex into following conditions: |
| // 1. The name is not empty. |
| // 2. The first character is either an alphabet or an underscore. |
| // 3. The rest of the characters are either an alphabet, a digit or an underscore. |
| return name.size() > 0 && // |
| (std::isalpha(name[0]) || name[0] == '_') && // |
| std::all_of(name.begin() + 1, name.end(), |
| [](char c) { return std::isalnum(c) || c == '_'; }); |
| } |
| |
| PrinterConfig::PrinterConfig(ffi::Map<ffi::String, Any> config_dict) { |
| ffi::ObjectPtr<PrinterConfigNode> n = ffi::make_object<PrinterConfigNode>(); |
| if (auto v = config_dict.Get("name")) { |
| n->binding_names.push_back(v.value().as_or_throw<ffi::String>()); |
| } |
| if (auto v = config_dict.Get("show_meta")) { |
| n->show_meta = v.value().cast<bool>(); |
| } |
| if (auto v = config_dict.Get("ir_prefix")) { |
| n->ir_prefix = v.value().as_or_throw<ffi::String>(); |
| } |
| if (auto v = config_dict.Get("module_alias")) { |
| n->module_alias = v.value().as_or_throw<ffi::String>(); |
| } |
| if (auto v = config_dict.Get("buffer_dtype")) { |
| n->buffer_dtype = ffi::StringToDLDataType(v.value().as_or_throw<ffi::String>()); |
| } |
| if (auto v = config_dict.Get("int_dtype")) { |
| n->int_dtype = ffi::StringToDLDataType(v.value().as_or_throw<ffi::String>()); |
| } |
| if (auto v = config_dict.Get("float_dtype")) { |
| n->float_dtype = ffi::StringToDLDataType(v.value().as_or_throw<ffi::String>()); |
| } |
| if (auto v = config_dict.Get("verbose_expr")) { |
| n->verbose_expr = v.value().cast<bool>(); |
| } |
| if (auto v = config_dict.Get("indent_spaces")) { |
| n->indent_spaces = v.value().cast<int>(); |
| } |
| if (auto v = config_dict.Get("print_line_numbers")) { |
| n->print_line_numbers = v.value().cast<bool>(); |
| } |
| if (auto v = config_dict.Get("num_context_lines")) { |
| n->num_context_lines = v.value().cast<int>(); |
| } |
| if (auto v = config_dict.Get("path_to_underline")) { |
| n->path_to_underline = |
| v.value().as_or_throw<ffi::Optional<ffi::Array<ffi::reflection::AccessPath>>>().value_or( |
| ffi::Array<ffi::reflection::AccessPath>()); |
| } |
| if (auto v = config_dict.Get("path_to_annotate")) { |
| n->path_to_annotate = |
| v.value() |
| .as_or_throw<ffi::Optional<ffi::Map<ffi::reflection::AccessPath, ffi::String>>>() |
| .value_or(ffi::Map<ffi::reflection::AccessPath, ffi::String>()); |
| } |
| if (auto v = config_dict.Get("obj_to_underline")) { |
| n->obj_to_underline = |
| v.value().as_or_throw<ffi::Optional<ffi::Array<ffi::ObjectRef>>>().value_or( |
| ffi::Array<ffi::ObjectRef>()); |
| } |
| if (auto v = config_dict.Get("obj_to_annotate")) { |
| n->obj_to_annotate = |
| v.value().as_or_throw<ffi::Optional<ffi::Map<ffi::ObjectRef, ffi::String>>>().value_or( |
| ffi::Map<ffi::ObjectRef, ffi::String>()); |
| } |
| if (auto v = config_dict.Get("syntax_sugar")) { |
| n->syntax_sugar = v.value().cast<bool>(); |
| } |
| if (auto v = config_dict.Get("show_object_address")) { |
| n->show_object_address = v.value().cast<bool>(); |
| } |
| if (auto v = config_dict.Get("render_invisible_path_info")) { |
| n->render_invisible_path_info = v.value().cast<bool>(); |
| } |
| // Dialect-specific keys are stored in extra_config with dotted-name keys. |
| // String-typed dialect keys passed through directly. |
| for (const char* key : {"tirx.prefix", "relax.prefix"}) { |
| if (auto v = config_dict.Get(key)) { |
| n->extra_config.Set(ffi::String(key), v.value()); |
| } |
| } |
| // Boolean dialect keys. |
| if (auto v = config_dict.Get("relax.show_all_ty")) { |
| n->extra_config.Set(ffi::String("relax.show_all_ty"), v.value()); |
| } |
| if (auto v = config_dict.Get("extra_config")) { |
| auto extra = v.value().as_or_throw<ffi::Map<ffi::String, ffi::Any>>(); |
| for (auto kv : extra) { |
| n->extra_config.Set(kv.first, kv.second); |
| } |
| if (auto render = extra.Get("render_invisible_path_info")) { |
| n->render_invisible_path_info = render.value().cast<bool>(); |
| } |
| } |
| |
| // Checking prefixes if they are valid Python identifiers. |
| TVM_FFI_ICHECK(IsIdentifier(std::string(n->ir_prefix))) |
| << "Invalid `ir_prefix`: " << n->ir_prefix; |
| ffi::String tir_prefix = n->GetExtraConfig<ffi::String>("tirx.prefix", "T"); |
| ffi::String relax_prefix = n->GetExtraConfig<ffi::String>("relax.prefix", "R"); |
| TVM_FFI_ICHECK(IsIdentifier(std::string(tir_prefix))) << "Invalid `tirx.prefix`: " << tir_prefix; |
| TVM_FFI_ICHECK(IsIdentifier(std::string(relax_prefix))) |
| << "Invalid `relax.prefix`: " << relax_prefix; |
| TVM_FFI_ICHECK(n->module_alias.empty() || IsIdentifier(std::string(n->module_alias))) |
| << "Invalid `module_alias`: " << n->module_alias; |
| |
| this->data_ = std::move(n); |
| } |
| |
| ffi::Array<ffi::String> PrinterConfigNode::GetBuiltinKeywords() { |
| ffi::String tir_prefix = GetExtraConfig<ffi::String>("tirx.prefix", "T"); |
| ffi::String relax_prefix = GetExtraConfig<ffi::String>("relax.prefix", "R"); |
| ffi::Array<ffi::String> result{this->ir_prefix, tir_prefix, relax_prefix}; |
| if (!this->module_alias.empty()) { |
| result.push_back(this->module_alias); |
| } |
| return result; |
| } |
| |
| TVM_FFI_STATIC_INIT_BLOCK() { |
| namespace refl = tvm::ffi::reflection; |
| refl::GlobalDef() |
| .def("node.PrinterConfig", |
| [](ffi::Map<ffi::String, Any> config_dict) { return PrinterConfig(config_dict); }) |
| .def("node.TVMScriptPrinterScript", tvm::Script); |
| } |
| |
| } // namespace tvm |