mirror of https://github.com/YosysHQ/yosys.git
cxxrtl: expose scope information in the C++ API.
This commit adds a `debug_scopes` container, which can collect metadata about scopes in a design. Currently the only scope is that of a module. A module scope can be represented either by a module and cell pair, or a `$scopeinfo` cell in a flattened netlist. The metadata produced by the C++ API is identical between these two cases, so flattening remains transparent to a netlist with CXXRTL. The existing `debug_items` method is deprecated. This isn't strictly necessary, but the user experience is better if the path is provided as e.g. `"top "` (as some VCD viewers make it awkward to select topmost anonymous scope), and the upgrade flow encourages that, which should reduce frustration later. While the new `debug_items` method could still be broken in the future as the C++ API permits, this seems unlikely since the debug information can now capture all common netlist aspects and includes several extension points (via `debug_item`, `debug_scope` types). Also, naming of scope paths was normalized to `path` or `top_path`, as applicable.
This commit is contained in:
parent
d903f47d41
commit
1a44645aef
|
@ -25,6 +25,7 @@
|
|||
#include "kernel/mem.h"
|
||||
#include "kernel/log.h"
|
||||
#include "kernel/fmt.h"
|
||||
#include "kernel/scopeinfo.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
@ -2311,11 +2312,14 @@ struct CxxrtlWorker {
|
|||
dict<RTLIL::IdString, RTLIL::Const> attributes = object->attributes;
|
||||
// Inherently necessary to get access to the object, so a waste of space to emit.
|
||||
attributes.erase(ID::hdlname);
|
||||
// Internal Yosys attribute that should be removed but isn't.
|
||||
attributes.erase(ID::module_not_derived);
|
||||
dump_metadata_map(attributes);
|
||||
}
|
||||
|
||||
void dump_debug_info_method(RTLIL::Module *module)
|
||||
{
|
||||
size_t count_scopes = 0;
|
||||
size_t count_public_wires = 0;
|
||||
size_t count_member_wires = 0;
|
||||
size_t count_undriven = 0;
|
||||
|
@ -2328,153 +2332,188 @@ struct CxxrtlWorker {
|
|||
size_t count_skipped_wires = 0;
|
||||
inc_indent();
|
||||
f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n";
|
||||
for (auto wire : module->wires()) {
|
||||
const auto &debug_wire_type = debug_wire_types[wire];
|
||||
if (!wire->name.isPublic())
|
||||
continue;
|
||||
count_public_wires++;
|
||||
switch (debug_wire_type.type) {
|
||||
case WireType::BUFFERED:
|
||||
case WireType::MEMBER: {
|
||||
// Member wire
|
||||
std::vector<std::string> flags;
|
||||
|
||||
if (wire->port_input && wire->port_output)
|
||||
flags.push_back("INOUT");
|
||||
else if (wire->port_output)
|
||||
flags.push_back("OUTPUT");
|
||||
else if (wire->port_input)
|
||||
flags.push_back("INPUT");
|
||||
|
||||
bool has_driven_sync = false;
|
||||
bool has_driven_comb = false;
|
||||
bool has_undriven = false;
|
||||
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
|
||||
for (auto bit : SigSpec(wire))
|
||||
if (!bit_has_state.count(bit))
|
||||
has_undriven = true;
|
||||
else if (bit_has_state[bit])
|
||||
has_driven_sync = true;
|
||||
else
|
||||
has_driven_comb = true;
|
||||
} else if (wire->port_output) {
|
||||
switch (cxxrtl_port_type(module, wire->name)) {
|
||||
case CxxrtlPortType::SYNC:
|
||||
has_driven_sync = true;
|
||||
break;
|
||||
case CxxrtlPortType::COMB:
|
||||
has_driven_comb = true;
|
||||
break;
|
||||
case CxxrtlPortType::UNKNOWN:
|
||||
has_driven_sync = has_driven_comb = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
has_undriven = true;
|
||||
}
|
||||
if (has_undriven)
|
||||
flags.push_back("UNDRIVEN");
|
||||
if (!has_driven_sync && !has_driven_comb && has_undriven)
|
||||
count_undriven++;
|
||||
if (has_driven_sync)
|
||||
flags.push_back("DRIVEN_SYNC");
|
||||
if (has_driven_sync && !has_driven_comb && !has_undriven)
|
||||
count_driven_sync++;
|
||||
if (has_driven_comb)
|
||||
flags.push_back("DRIVEN_COMB");
|
||||
if (!has_driven_sync && has_driven_comb && !has_undriven)
|
||||
count_driven_comb++;
|
||||
if (has_driven_sync + has_driven_comb + has_undriven > 1)
|
||||
count_mixed_driver++;
|
||||
|
||||
f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||
f << ", debug_item(" << mangle(wire) << ", " << wire->start_offset;
|
||||
bool first = true;
|
||||
for (auto flag : flags) {
|
||||
if (first) {
|
||||
first = false;
|
||||
f << ", ";
|
||||
} else {
|
||||
f << "|";
|
||||
}
|
||||
f << "debug_item::" << flag;
|
||||
}
|
||||
f << "), ";
|
||||
dump_debug_attrs(wire);
|
||||
f << indent << "if (scopes) {\n";
|
||||
inc_indent();
|
||||
// The module is responsible for adding its own scope.
|
||||
f << indent << "scopes->add(path.empty() ? path : path.substr(0, path.size() - 1), ";
|
||||
f << escape_cxx_string(get_hdl_name(module)) << ", ";
|
||||
dump_debug_attrs(module);
|
||||
f << ", std::move(cell_attrs));\n";
|
||||
count_scopes++;
|
||||
// If there were any submodules that were flattened, the module is also responsible for adding them.
|
||||
for (auto cell : module->cells()) {
|
||||
if (cell->type != ID($scopeinfo)) continue;
|
||||
if (cell->getParam(ID::TYPE).decode_string() == "module") {
|
||||
auto module_attrs = scopeinfo_attributes(cell, ScopeinfoAttrs::Module);
|
||||
auto cell_attrs = scopeinfo_attributes(cell, ScopeinfoAttrs::Cell);
|
||||
cell_attrs.erase(ID::module_not_derived);
|
||||
f << indent << "scopes->add(path + " << escape_cxx_string(get_hdl_name(cell)) << ", ";
|
||||
f << escape_cxx_string(cell->get_string_attribute(ID(module))) << ", ";
|
||||
dump_metadata_map(module_attrs);
|
||||
f << ", ";
|
||||
dump_metadata_map(cell_attrs);
|
||||
f << ");\n";
|
||||
count_member_wires++;
|
||||
break;
|
||||
}
|
||||
case WireType::ALIAS: {
|
||||
// Alias of a member wire
|
||||
const RTLIL::Wire *aliasee = debug_wire_type.sig_subst.as_wire();
|
||||
f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||
f << ", debug_item(";
|
||||
// If the aliasee is an outline, then the alias must be an outline, too; otherwise downstream
|
||||
// tooling has no way to find out about the outline.
|
||||
if (debug_wire_types[aliasee].is_outline())
|
||||
f << "debug_eval_outline";
|
||||
else
|
||||
f << "debug_alias()";
|
||||
f << ", " << mangle(aliasee) << ", " << wire->start_offset << "), ";
|
||||
dump_debug_attrs(aliasee);
|
||||
f << ");\n";
|
||||
count_alias_wires++;
|
||||
break;
|
||||
}
|
||||
case WireType::CONST: {
|
||||
// Wire tied to a constant
|
||||
f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = ";
|
||||
dump_const(debug_wire_type.sig_subst.as_const());
|
||||
f << ";\n";
|
||||
f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||
f << ", debug_item(const_" << mangle(wire) << ", " << wire->start_offset << "), ";
|
||||
dump_debug_attrs(wire);
|
||||
f << ");\n";
|
||||
count_const_wires++;
|
||||
break;
|
||||
}
|
||||
case WireType::OUTLINE: {
|
||||
// Localized or inlined, but rematerializable wire
|
||||
f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||
f << ", debug_item(debug_eval_outline, " << mangle(wire) << ", " << wire->start_offset << "), ";
|
||||
dump_debug_attrs(wire);
|
||||
f << ");\n";
|
||||
count_inline_wires++;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Localized or inlined wire with no debug information
|
||||
count_skipped_wires++;
|
||||
break;
|
||||
}
|
||||
} else log_assert(false && "Unknown $scopeinfo type");
|
||||
count_scopes++;
|
||||
}
|
||||
}
|
||||
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
|
||||
for (auto &mem : mod_memories[module]) {
|
||||
if (!mem.memid.isPublic())
|
||||
dec_indent();
|
||||
f << indent << "}\n";
|
||||
f << indent << "if (items) {\n";
|
||||
inc_indent();
|
||||
for (auto wire : module->wires()) {
|
||||
const auto &debug_wire_type = debug_wire_types[wire];
|
||||
if (!wire->name.isPublic())
|
||||
continue;
|
||||
f << indent << "items.add(path + " << escape_cxx_string(mem.packed ? get_hdl_name(mem.cell) : get_hdl_name(mem.mem));
|
||||
f << ", debug_item(" << mangle(&mem) << ", ";
|
||||
f << mem.start_offset << "), ";
|
||||
if (mem.packed) {
|
||||
dump_debug_attrs(mem.cell);
|
||||
} else {
|
||||
dump_debug_attrs(mem.mem);
|
||||
count_public_wires++;
|
||||
switch (debug_wire_type.type) {
|
||||
case WireType::BUFFERED:
|
||||
case WireType::MEMBER: {
|
||||
// Member wire
|
||||
std::vector<std::string> flags;
|
||||
|
||||
if (wire->port_input && wire->port_output)
|
||||
flags.push_back("INOUT");
|
||||
else if (wire->port_output)
|
||||
flags.push_back("OUTPUT");
|
||||
else if (wire->port_input)
|
||||
flags.push_back("INPUT");
|
||||
|
||||
bool has_driven_sync = false;
|
||||
bool has_driven_comb = false;
|
||||
bool has_undriven = false;
|
||||
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
|
||||
for (auto bit : SigSpec(wire))
|
||||
if (!bit_has_state.count(bit))
|
||||
has_undriven = true;
|
||||
else if (bit_has_state[bit])
|
||||
has_driven_sync = true;
|
||||
else
|
||||
has_driven_comb = true;
|
||||
} else if (wire->port_output) {
|
||||
switch (cxxrtl_port_type(module, wire->name)) {
|
||||
case CxxrtlPortType::SYNC:
|
||||
has_driven_sync = true;
|
||||
break;
|
||||
case CxxrtlPortType::COMB:
|
||||
has_driven_comb = true;
|
||||
break;
|
||||
case CxxrtlPortType::UNKNOWN:
|
||||
has_driven_sync = has_driven_comb = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
has_undriven = true;
|
||||
}
|
||||
if (has_undriven)
|
||||
flags.push_back("UNDRIVEN");
|
||||
if (!has_driven_sync && !has_driven_comb && has_undriven)
|
||||
count_undriven++;
|
||||
if (has_driven_sync)
|
||||
flags.push_back("DRIVEN_SYNC");
|
||||
if (has_driven_sync && !has_driven_comb && !has_undriven)
|
||||
count_driven_sync++;
|
||||
if (has_driven_comb)
|
||||
flags.push_back("DRIVEN_COMB");
|
||||
if (!has_driven_sync && has_driven_comb && !has_undriven)
|
||||
count_driven_comb++;
|
||||
if (has_driven_sync + has_driven_comb + has_undriven > 1)
|
||||
count_mixed_driver++;
|
||||
|
||||
f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||
f << ", debug_item(" << mangle(wire) << ", " << wire->start_offset;
|
||||
bool first = true;
|
||||
for (auto flag : flags) {
|
||||
if (first) {
|
||||
first = false;
|
||||
f << ", ";
|
||||
} else {
|
||||
f << "|";
|
||||
}
|
||||
f << "debug_item::" << flag;
|
||||
}
|
||||
f << "), ";
|
||||
dump_debug_attrs(wire);
|
||||
f << ");\n";
|
||||
count_member_wires++;
|
||||
break;
|
||||
}
|
||||
case WireType::ALIAS: {
|
||||
// Alias of a member wire
|
||||
const RTLIL::Wire *aliasee = debug_wire_type.sig_subst.as_wire();
|
||||
f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||
f << ", debug_item(";
|
||||
// If the aliasee is an outline, then the alias must be an outline, too; otherwise downstream
|
||||
// tooling has no way to find out about the outline.
|
||||
if (debug_wire_types[aliasee].is_outline())
|
||||
f << "debug_eval_outline";
|
||||
else
|
||||
f << "debug_alias()";
|
||||
f << ", " << mangle(aliasee) << ", " << wire->start_offset << "), ";
|
||||
dump_debug_attrs(aliasee);
|
||||
f << ");\n";
|
||||
count_alias_wires++;
|
||||
break;
|
||||
}
|
||||
case WireType::CONST: {
|
||||
// Wire tied to a constant
|
||||
f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = ";
|
||||
dump_const(debug_wire_type.sig_subst.as_const());
|
||||
f << ";\n";
|
||||
f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||
f << ", debug_item(const_" << mangle(wire) << ", " << wire->start_offset << "), ";
|
||||
dump_debug_attrs(wire);
|
||||
f << ");\n";
|
||||
count_const_wires++;
|
||||
break;
|
||||
}
|
||||
case WireType::OUTLINE: {
|
||||
// Localized or inlined, but rematerializable wire
|
||||
f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||
f << ", debug_item(debug_eval_outline, " << mangle(wire) << ", " << wire->start_offset << "), ";
|
||||
dump_debug_attrs(wire);
|
||||
f << ");\n";
|
||||
count_inline_wires++;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Localized or inlined wire with no debug information
|
||||
count_skipped_wires++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
f << ");\n";
|
||||
}
|
||||
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
|
||||
for (auto &mem : mod_memories[module]) {
|
||||
if (!mem.memid.isPublic())
|
||||
continue;
|
||||
f << indent << "items->add(path + " << escape_cxx_string(mem.packed ? get_hdl_name(mem.cell) : get_hdl_name(mem.mem));
|
||||
f << ", debug_item(" << mangle(&mem) << ", ";
|
||||
f << mem.start_offset << "), ";
|
||||
if (mem.packed) {
|
||||
dump_debug_attrs(mem.cell);
|
||||
} else {
|
||||
dump_debug_attrs(mem.mem);
|
||||
}
|
||||
f << ");\n";
|
||||
}
|
||||
}
|
||||
dec_indent();
|
||||
f << indent << "}\n";
|
||||
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
|
||||
for (auto cell : module->cells()) {
|
||||
if (is_internal_cell(cell->type))
|
||||
continue;
|
||||
const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : ".";
|
||||
f << indent << mangle(cell) << access << "debug_info(items, ";
|
||||
f << "path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ");\n";
|
||||
f << indent << mangle(cell) << access;
|
||||
f << "debug_info(items, scopes, path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ", ";
|
||||
dump_debug_attrs(cell);
|
||||
f << ");\n";
|
||||
}
|
||||
}
|
||||
dec_indent();
|
||||
|
||||
log_debug("Debug information statistics for module `%s':\n", log_id(module));
|
||||
log_debug(" Scopes: %zu", count_scopes);
|
||||
log_debug(" Public wires: %zu, of which:\n", count_public_wires);
|
||||
log_debug(" Member wires: %zu, of which:\n", count_member_wires);
|
||||
log_debug(" Undriven: %zu (incl. inputs)\n", count_undriven);
|
||||
|
@ -2522,7 +2561,8 @@ struct CxxrtlWorker {
|
|||
f << indent << "}\n";
|
||||
if (debug_info) {
|
||||
f << "\n";
|
||||
f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n";
|
||||
f << indent << "void debug_info(debug_items *items, debug_scopes *scopes, "
|
||||
<< "std::string path, metadata_map &&cell_attrs = {}) override {\n";
|
||||
dump_debug_info_method(module);
|
||||
f << indent << "}\n";
|
||||
}
|
||||
|
@ -2631,7 +2671,8 @@ struct CxxrtlWorker {
|
|||
}
|
||||
}
|
||||
f << "\n";
|
||||
f << indent << "void debug_info(debug_items &items, std::string path = \"\") override;\n";
|
||||
f << indent << "void debug_info(debug_items *items, debug_scopes *scopes, "
|
||||
<< "std::string path, metadata_map &&cell_attrs = {}) override;\n";
|
||||
}
|
||||
dec_indent();
|
||||
f << indent << "}; // struct " << mangle(module) << "\n";
|
||||
|
@ -2659,7 +2700,8 @@ struct CxxrtlWorker {
|
|||
}
|
||||
f << "\n";
|
||||
f << indent << "CXXRTL_EXTREMELY_COLD\n";
|
||||
f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n";
|
||||
f << indent << "void " << mangle(module) << "::debug_info(debug_items *items, debug_scopes *scopes, "
|
||||
<< "std::string path, metadata_map &&cell_attrs) {\n";
|
||||
dump_debug_info_method(module);
|
||||
f << indent << "}\n";
|
||||
}
|
||||
|
|
|
@ -35,19 +35,19 @@ cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design) {
|
|||
return cxxrtl_create_at(design, "");
|
||||
}
|
||||
|
||||
cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *root) {
|
||||
std::string path = root;
|
||||
if (!path.empty()) {
|
||||
cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *top_path_) {
|
||||
std::string top_path = top_path_;
|
||||
if (!top_path.empty()) {
|
||||
// module::debug_info() accepts either an empty path, or a path ending in space to simplify
|
||||
// the logic in generated code. While this is sketchy at best to expose in the C++ API, this
|
||||
// would be a lot worse in the C API, so don't expose it here.
|
||||
assert(path.back() != ' ');
|
||||
path += ' ';
|
||||
assert(top_path.back() != ' ');
|
||||
top_path += ' ';
|
||||
}
|
||||
|
||||
cxxrtl_handle handle = new _cxxrtl_handle;
|
||||
handle->module = std::move(design->module);
|
||||
handle->module->debug_info(handle->objects, path);
|
||||
handle->module->debug_info(handle->objects, top_path);
|
||||
delete design;
|
||||
return handle;
|
||||
}
|
||||
|
|
|
@ -55,8 +55,8 @@ cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design);
|
|||
// Create a design handle at a given hierarchy position from a design toplevel.
|
||||
//
|
||||
// This operation is similar to `cxxrtl_create`, except the full hierarchical name of every object
|
||||
// is prepended with `root`.
|
||||
cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *root);
|
||||
// is prepended with `top_path`.
|
||||
cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *top_path);
|
||||
|
||||
// Release all resources used by a design and its handle.
|
||||
void cxxrtl_destroy(cxxrtl_handle handle);
|
||||
|
|
|
@ -1331,14 +1331,15 @@ struct debug_items {
|
|||
std::map<std::string, std::vector<debug_item>> table;
|
||||
std::map<std::string, std::unique_ptr<debug_attrs>> attrs_table;
|
||||
|
||||
void add(const std::string &name, debug_item &&item, metadata_map &&item_attrs = {}) {
|
||||
std::unique_ptr<debug_attrs> &attrs = attrs_table[name];
|
||||
void add(const std::string &path, debug_item &&item, metadata_map &&item_attrs = {}) {
|
||||
assert((path.empty() || path[path.size() - 1] != ' ') && path.find(" ") == std::string::npos);
|
||||
std::unique_ptr<debug_attrs> &attrs = attrs_table[path];
|
||||
if (attrs.get() == nullptr)
|
||||
attrs = std::unique_ptr<debug_attrs>(new debug_attrs);
|
||||
for (auto attr : item_attrs)
|
||||
attrs->map.insert(attr);
|
||||
item.attrs = attrs.get();
|
||||
std::vector<debug_item> &parts = table[name];
|
||||
std::vector<debug_item> &parts = table[path];
|
||||
parts.emplace_back(item);
|
||||
std::sort(parts.begin(), parts.end(),
|
||||
[](const debug_item &a, const debug_item &b) {
|
||||
|
@ -1346,25 +1347,58 @@ struct debug_items {
|
|||
});
|
||||
}
|
||||
|
||||
size_t count(const std::string &name) const {
|
||||
if (table.count(name) == 0)
|
||||
size_t count(const std::string &path) const {
|
||||
if (table.count(path) == 0)
|
||||
return 0;
|
||||
return table.at(name).size();
|
||||
return table.at(path).size();
|
||||
}
|
||||
|
||||
const std::vector<debug_item> &at(const std::string &name) const {
|
||||
return table.at(name);
|
||||
const std::vector<debug_item> &at(const std::string &path) const {
|
||||
return table.at(path);
|
||||
}
|
||||
|
||||
// Like `at()`, but operates only on single-part debug items.
|
||||
const debug_item &operator [](const std::string &name) const {
|
||||
const std::vector<debug_item> &parts = table.at(name);
|
||||
const debug_item &operator [](const std::string &path) const {
|
||||
const std::vector<debug_item> &parts = table.at(path);
|
||||
assert(parts.size() == 1);
|
||||
return parts.at(0);
|
||||
}
|
||||
|
||||
const metadata_map &attrs(const std::string &name) const {
|
||||
return attrs_table.at(name)->map;
|
||||
bool is_memory(const std::string &path) const {
|
||||
return at(path).at(0).type == debug_item::MEMORY;
|
||||
}
|
||||
|
||||
const metadata_map &attrs(const std::string &path) const {
|
||||
return attrs_table.at(path)->map;
|
||||
}
|
||||
};
|
||||
|
||||
// Only `module` scopes are defined. The type is implicit, since Yosys does not currently support
|
||||
// any other scope types.
|
||||
struct debug_scope {
|
||||
std::string module_name;
|
||||
std::unique_ptr<debug_attrs> module_attrs;
|
||||
std::unique_ptr<debug_attrs> cell_attrs;
|
||||
};
|
||||
|
||||
struct debug_scopes {
|
||||
std::map<std::string, debug_scope> table;
|
||||
|
||||
void add(const std::string &path, const std::string &module_name, metadata_map &&module_attrs, metadata_map &&cell_attrs) {
|
||||
assert((path.empty() || path[path.size() - 1] != ' ') && path.find(" ") == std::string::npos);
|
||||
assert(table.count(path) == 0);
|
||||
debug_scope &scope = table[path];
|
||||
scope.module_name = module_name;
|
||||
scope.module_attrs = std::unique_ptr<debug_attrs>(new debug_attrs { module_attrs });
|
||||
scope.cell_attrs = std::unique_ptr<debug_attrs>(new debug_attrs { cell_attrs });
|
||||
}
|
||||
|
||||
size_t contains(const std::string &path) const {
|
||||
return table.count(path);
|
||||
}
|
||||
|
||||
const debug_scope &operator [](const std::string &path) const {
|
||||
return table.at(path);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1412,8 +1446,16 @@ struct module {
|
|||
return deltas;
|
||||
}
|
||||
|
||||
virtual void debug_info(debug_items &items, std::string path = "") {
|
||||
(void)items, (void)path;
|
||||
virtual void debug_info(debug_items *items, debug_scopes *scopes, std::string path, metadata_map &&cell_attrs = {}) {
|
||||
(void)items, (void)scopes, (void)path, (void)cell_attrs;
|
||||
}
|
||||
|
||||
// Compatibility method.
|
||||
#if __has_attribute(deprecated)
|
||||
__attribute__((deprecated("Use `debug_info(path, &items, /*scopes=*/nullptr);` instead. (`path` could be \"top \".)")))
|
||||
#endif
|
||||
void debug_info(debug_items &items, std::string path) {
|
||||
debug_info(&items, /*scopes=*/nullptr, path);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -491,9 +491,9 @@ public:
|
|||
template<typename ...Args>
|
||||
recorder(Args &&...args) : writer(std::forward<Args>(args)...) {}
|
||||
|
||||
void start(module &module) {
|
||||
void start(module &module, std::string top_path = "") {
|
||||
debug_items items;
|
||||
module.debug_info(items);
|
||||
module.debug_info(&items, /*scopes=*/nullptr, top_path);
|
||||
start(items);
|
||||
}
|
||||
|
||||
|
@ -621,9 +621,10 @@ public:
|
|||
template<typename ...Args>
|
||||
player(Args &&...args) : reader(std::forward<Args>(args)...) {}
|
||||
|
||||
void start(module &module) {
|
||||
// The `top_path` must match the one given to the recorder.
|
||||
void start(module &module, std::string top_path = "") {
|
||||
debug_items items;
|
||||
module.debug_info(items);
|
||||
module.debug_info(&items, /*scopes=*/nullptr, top_path);
|
||||
start(items);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue