mirror of https://github.com/YosysHQ/yosys.git
Merge pull request #4380 from whitequark/cxxrtl-metadata-compression
cxxrtl: reduce stack space used by the `debug_info()` function
This commit is contained in:
commit
1ac00088a6
|
@ -606,22 +606,30 @@ std::vector<std::string> split_by(const std::string &str, const std::string &sep
|
|||
return result;
|
||||
}
|
||||
|
||||
std::string escape_cxx_string(const std::string &input)
|
||||
std::string escape_c_string(const std::string &input)
|
||||
{
|
||||
std::string output = "\"";
|
||||
std::string output;
|
||||
output.push_back('"');
|
||||
for (auto c : input) {
|
||||
if (::isprint(c)) {
|
||||
if (c == '\\')
|
||||
output.push_back('\\');
|
||||
output.push_back(c);
|
||||
} else {
|
||||
char l = c & 0xf, h = (c >> 4) & 0xf;
|
||||
output.append("\\x");
|
||||
output.push_back((h < 10 ? '0' + h : 'a' + h - 10));
|
||||
output.push_back((l < 10 ? '0' + l : 'a' + l - 10));
|
||||
char l = c & 0x3, m = (c >> 3) & 0x3, h = (c >> 6) & 0x3;
|
||||
output.append("\\");
|
||||
output.push_back('0' + h);
|
||||
output.push_back('0' + m);
|
||||
output.push_back('0' + l);
|
||||
}
|
||||
}
|
||||
output.push_back('"');
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string escape_cxx_string(const std::string &input)
|
||||
{
|
||||
std::string output = escape_c_string(input);
|
||||
if (output.find('\0') != std::string::npos) {
|
||||
output.insert(0, "std::string {");
|
||||
output.append(stringf(", %zu}", input.size()));
|
||||
|
@ -2275,46 +2283,92 @@ struct CxxrtlWorker {
|
|||
dec_indent();
|
||||
}
|
||||
|
||||
void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map)
|
||||
{
|
||||
if (metadata_map.empty()) {
|
||||
f << "metadata_map()";
|
||||
return;
|
||||
}
|
||||
f << "metadata_map({\n";
|
||||
inc_indent();
|
||||
for (auto metadata_item : metadata_map) {
|
||||
if (!metadata_item.first.isPublic())
|
||||
continue;
|
||||
if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) {
|
||||
f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n";
|
||||
continue;
|
||||
}
|
||||
f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", ";
|
||||
// In Yosys, a real is a type of string.
|
||||
if (metadata_item.second.flags & RTLIL::CONST_FLAG_REAL) {
|
||||
f << std::showpoint << std::stod(metadata_item.second.decode_string()) << std::noshowpoint;
|
||||
} else if (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) {
|
||||
f << escape_cxx_string(metadata_item.second.decode_string());
|
||||
} else if (metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED) {
|
||||
f << "INT64_C(" << metadata_item.second.as_int(/*is_signed=*/true) << ")";
|
||||
} else {
|
||||
f << "UINT64_C(" << metadata_item.second.as_int(/*is_signed=*/false) << ")";
|
||||
}
|
||||
f << " },\n";
|
||||
void dump_serialized_metadata(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map) {
|
||||
// Creating thousands metadata_map objects using initializer lists in a single function results in one of:
|
||||
// 1. Megabytes of stack usage (with __attribute__((optnone))).
|
||||
// 2. Minutes of compile time (without __attribute__((optnone))).
|
||||
// So, don't create them.
|
||||
std::string data;
|
||||
auto put_u64 = [&](uint64_t value) {
|
||||
for (size_t count = 0; count < 8; count++) {
|
||||
data += (char)(value >> 56);
|
||||
value <<= 8;
|
||||
}
|
||||
dec_indent();
|
||||
f << indent << "})";
|
||||
};
|
||||
for (auto metadata_item : metadata_map) {
|
||||
if (!metadata_item.first.isPublic())
|
||||
continue;
|
||||
if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) {
|
||||
f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n";
|
||||
continue;
|
||||
}
|
||||
data += metadata_item.first.str().substr(1) + '\0';
|
||||
// In Yosys, a real is a type of string.
|
||||
if (metadata_item.second.flags & RTLIL::CONST_FLAG_REAL) {
|
||||
double dvalue = std::stod(metadata_item.second.decode_string());
|
||||
uint64_t uvalue;
|
||||
static_assert(sizeof(dvalue) == sizeof(uvalue), "double must be 64 bits in size");
|
||||
memcpy(&uvalue, &dvalue, sizeof(uvalue));
|
||||
data += 'd';
|
||||
put_u64(uvalue);
|
||||
} else if (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) {
|
||||
data += 's';
|
||||
data += metadata_item.second.decode_string();
|
||||
data += '\0';
|
||||
} else if (metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED) {
|
||||
data += 'i';
|
||||
put_u64((uint64_t)metadata_item.second.as_int(/*is_signed=*/true));
|
||||
} else {
|
||||
data += 'u';
|
||||
put_u64(metadata_item.second.as_int(/*is_signed=*/false));
|
||||
}
|
||||
}
|
||||
f << escape_c_string(data);
|
||||
}
|
||||
|
||||
void dump_debug_attrs(const RTLIL::AttrObject *object)
|
||||
void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map) {
|
||||
if (metadata_map.empty()) {
|
||||
f << "metadata_map()";
|
||||
} else {
|
||||
f << "metadata_map({\n";
|
||||
inc_indent();
|
||||
for (auto metadata_item : metadata_map) {
|
||||
if (!metadata_item.first.isPublic())
|
||||
continue;
|
||||
if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) {
|
||||
f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n";
|
||||
continue;
|
||||
}
|
||||
f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", ";
|
||||
// In Yosys, a real is a type of string.
|
||||
if (metadata_item.second.flags & RTLIL::CONST_FLAG_REAL) {
|
||||
f << std::showpoint << std::stod(metadata_item.second.decode_string()) << std::noshowpoint;
|
||||
} else if (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) {
|
||||
f << escape_cxx_string(metadata_item.second.decode_string());
|
||||
} else if (metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED) {
|
||||
f << "INT64_C(" << metadata_item.second.as_int(/*is_signed=*/true) << ")";
|
||||
} else {
|
||||
f << "UINT64_C(" << metadata_item.second.as_int(/*is_signed=*/false) << ")";
|
||||
}
|
||||
f << " },\n";
|
||||
}
|
||||
dec_indent();
|
||||
f << indent << "})";
|
||||
}
|
||||
}
|
||||
|
||||
void dump_debug_attrs(const RTLIL::AttrObject *object, bool serialize = true)
|
||||
{
|
||||
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);
|
||||
if (serialize) {
|
||||
dump_serialized_metadata(attributes);
|
||||
} else {
|
||||
dump_metadata_map(attributes);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_debug_info_method(RTLIL::Module *module)
|
||||
|
@ -2337,7 +2391,7 @@ struct CxxrtlWorker {
|
|||
// 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);
|
||||
dump_debug_attrs(module, /*serialize=*/false);
|
||||
f << ", std::move(cell_attrs));\n";
|
||||
count_scopes++;
|
||||
// If there were any submodules that were flattened, the module is also responsible for adding them.
|
||||
|
@ -2347,11 +2401,11 @@ struct CxxrtlWorker {
|
|||
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 << 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);
|
||||
dump_serialized_metadata(module_attrs);
|
||||
f << ", ";
|
||||
dump_metadata_map(cell_attrs);
|
||||
dump_serialized_metadata(cell_attrs);
|
||||
f << ");\n";
|
||||
} else log_assert(false && "Unknown $scopeinfo type");
|
||||
count_scopes++;
|
||||
|
@ -2419,20 +2473,22 @@ struct CxxrtlWorker {
|
|||
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 << "), ";
|
||||
f << indent << "items->add(path, " << escape_cxx_string(get_hdl_name(wire)) << ", ";
|
||||
dump_debug_attrs(wire);
|
||||
f << ", " << mangle(wire);
|
||||
if (wire->start_offset != 0 || !flags.empty()) {
|
||||
f << ", " << wire->start_offset;
|
||||
bool first = true;
|
||||
for (auto flag : flags) {
|
||||
if (first) {
|
||||
first = false;
|
||||
f << ", ";
|
||||
} else {
|
||||
f << "|";
|
||||
}
|
||||
f << "debug_item::" << flag;
|
||||
}
|
||||
}
|
||||
f << ");\n";
|
||||
count_member_wires++;
|
||||
break;
|
||||
|
@ -2440,16 +2496,18 @@ struct CxxrtlWorker {
|
|||
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(";
|
||||
f << indent << "items->add(path, " << escape_cxx_string(get_hdl_name(wire)) << ", ";
|
||||
dump_debug_attrs(aliasee);
|
||||
f << ", ";
|
||||
// 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 << ", " << mangle(aliasee);
|
||||
if (wire->start_offset != 0)
|
||||
f << ", " << wire->start_offset;
|
||||
f << ");\n";
|
||||
count_alias_wires++;
|
||||
break;
|
||||
|
@ -2459,18 +2517,22 @@ struct CxxrtlWorker {
|
|||
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 << "), ";
|
||||
f << indent << "items->add(path, " << escape_cxx_string(get_hdl_name(wire)) << ", ";
|
||||
dump_debug_attrs(wire);
|
||||
f << ", const_" << mangle(wire);
|
||||
if (wire->start_offset != 0)
|
||||
f << ", " << wire->start_offset;
|
||||
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 << "), ";
|
||||
f << indent << "items->add(path, " << escape_cxx_string(get_hdl_name(wire)) << ", ";
|
||||
dump_debug_attrs(wire);
|
||||
f << ", debug_eval_outline, " << mangle(wire);
|
||||
if (wire->start_offset != 0)
|
||||
f << ", " << wire->start_offset;
|
||||
f << ");\n";
|
||||
count_inline_wires++;
|
||||
break;
|
||||
|
@ -2486,15 +2548,14 @@ struct CxxrtlWorker {
|
|||
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 << "), ";
|
||||
f << indent << "items->add(path, " << escape_cxx_string(mem.packed ? get_hdl_name(mem.cell) : get_hdl_name(mem.mem)) << ", ";
|
||||
if (mem.packed) {
|
||||
dump_debug_attrs(mem.cell);
|
||||
} else {
|
||||
dump_debug_attrs(mem.mem);
|
||||
}
|
||||
f << ");\n";
|
||||
f << ", " << mangle(&mem) << ", ";
|
||||
f << mem.start_offset << ");\n";
|
||||
}
|
||||
}
|
||||
dec_indent();
|
||||
|
@ -2506,7 +2567,7 @@ struct CxxrtlWorker {
|
|||
const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : ".";
|
||||
f << indent << mangle(cell) << access;
|
||||
f << "debug_info(items, scopes, path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ", ";
|
||||
dump_debug_attrs(cell);
|
||||
dump_debug_attrs(cell, /*serialize=*/false);
|
||||
f << ");\n";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -941,6 +941,55 @@ struct metadata {
|
|||
assert(value_type == DOUBLE);
|
||||
return double_value;
|
||||
}
|
||||
|
||||
// Internal CXXRTL use only.
|
||||
static std::map<std::string, metadata> deserialize(const char *ptr) {
|
||||
std::map<std::string, metadata> result;
|
||||
std::string name;
|
||||
// Grammar:
|
||||
// string ::= [^\0]+ \0
|
||||
// metadata ::= [uid] .{8} | s <string>
|
||||
// map ::= ( <string> <metadata> )* \0
|
||||
for (;;) {
|
||||
if (*ptr) {
|
||||
name += *ptr++;
|
||||
} else if (!name.empty()) {
|
||||
ptr++;
|
||||
auto get_u64 = [&]() {
|
||||
uint64_t result = 0;
|
||||
for (size_t count = 0; count < 8; count++)
|
||||
result = (result << 8) | *ptr++;
|
||||
return result;
|
||||
};
|
||||
char type = *ptr++;
|
||||
if (type == 'u') {
|
||||
uint64_t value = get_u64();
|
||||
result.emplace(name, value);
|
||||
} else if (type == 'i') {
|
||||
int64_t value = (int64_t)get_u64();
|
||||
result.emplace(name, value);
|
||||
} else if (type == 'd') {
|
||||
double dvalue;
|
||||
uint64_t uvalue = get_u64();
|
||||
static_assert(sizeof(dvalue) == sizeof(uvalue), "double must be 64 bits in size");
|
||||
memcpy(&dvalue, &uvalue, sizeof(dvalue));
|
||||
result.emplace(name, dvalue);
|
||||
} else if (type == 's') {
|
||||
std::string value;
|
||||
while (*ptr)
|
||||
value += *ptr++;
|
||||
ptr++;
|
||||
result.emplace(name, value);
|
||||
} else {
|
||||
assert(false && "Unknown type specifier");
|
||||
return result;
|
||||
}
|
||||
name.clear();
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::map<std::string, metadata> metadata_map;
|
||||
|
@ -1417,6 +1466,12 @@ struct debug_items {
|
|||
});
|
||||
}
|
||||
|
||||
// This overload exists to reduce excessive stack slot allocation in `CXXRTL_EXTREMELY_COLD void debug_info()`.
|
||||
template<class... T>
|
||||
void add(const std::string &base_path, const char *path, const char *serialized_item_attrs, T&&... args) {
|
||||
add(base_path + path, debug_item(std::forward<T>(args)...), metadata::deserialize(serialized_item_attrs));
|
||||
}
|
||||
|
||||
size_t count(const std::string &path) const {
|
||||
if (table.count(path) == 0)
|
||||
return 0;
|
||||
|
@ -1463,6 +1518,11 @@ struct debug_scopes {
|
|||
scope.cell_attrs = std::unique_ptr<debug_attrs>(new debug_attrs { cell_attrs });
|
||||
}
|
||||
|
||||
// This overload exists to reduce excessive stack slot allocation in `CXXRTL_EXTREMELY_COLD void debug_info()`.
|
||||
void add(const std::string &base_path, const char *path, const char *module_name, const char *serialized_module_attrs, const char *serialized_cell_attrs) {
|
||||
add(base_path + path, module_name, metadata::deserialize(serialized_module_attrs), metadata::deserialize(serialized_cell_attrs));
|
||||
}
|
||||
|
||||
size_t contains(const std::string &path) const {
|
||||
return table.count(path);
|
||||
}
|
||||
|
|
|
@ -512,9 +512,10 @@ public:
|
|||
spool &operator=(const spool &) = delete;
|
||||
|
||||
~spool() {
|
||||
if (int fd = writefd.exchange(-1))
|
||||
int fd;
|
||||
if ((fd = writefd.exchange(-1)) != -1)
|
||||
close(fd);
|
||||
if (int fd = readfd.exchange(-1))
|
||||
if ((fd = readfd.exchange(-1)) != -1)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue