cxxrtl: reduce stack space consumed by `debug_info()`.

Before this commit, the creation of (constant) attribute maps caused
`debug_info()` (which is built with `__attribute__((optnone))`) to
consume large amounts of stack space; up to tens of megabytes. This
caused problems particularly on macOS, where the default stack size
is 512 KiB.

After this commit, `std::map` objects are no longer created inline in
the `debug_info()` function, but are compiled to and then expanded from
a string literal in a subroutine call. This reduces stack space usage
by about 50%.
This commit is contained in:
Catherine 2024-03-21 20:28:32 +00:00
parent 43ddd89ba5
commit 9134cd1928
2 changed files with 116 additions and 14 deletions

View File

@ -606,9 +606,10 @@ std::vector<std::string> split_by(const std::string &str, const std::string &sep
return result; 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) { for (auto c : input) {
if (::isprint(c)) { if (::isprint(c)) {
if (c == '\\') if (c == '\\')
@ -623,6 +624,12 @@ std::string escape_cxx_string(const std::string &input)
} }
} }
output.push_back('"'); 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) { if (output.find('\0') != std::string::npos) {
output.insert(0, "std::string {"); output.insert(0, "std::string {");
output.append(stringf(", %zu}", input.size())); output.append(stringf(", %zu}", input.size()));
@ -2276,14 +2283,23 @@ struct CxxrtlWorker {
dec_indent(); dec_indent();
} }
void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map) void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map, bool serialize = true)
{ {
if (metadata_map.empty()) { if (metadata_map.empty()) {
f << "metadata_map()"; f << "metadata_map()";
return; return;
} } else if (serialize) {
f << "metadata_map({\n"; // Creating thousands metadata_map objects using initializer lists in a single function results in one of:
inc_indent(); // 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;
}
};
for (auto metadata_item : metadata_map) { for (auto metadata_item : metadata_map) {
if (!metadata_item.first.isPublic()) if (!metadata_item.first.isPublic())
continue; continue;
@ -2291,21 +2307,58 @@ struct CxxrtlWorker {
f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n"; f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n";
continue; continue;
} }
f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", "; data += metadata_item.first.str().substr(1) + '\0';
// In Yosys, a real is a type of string. // In Yosys, a real is a type of string.
if (metadata_item.second.flags & RTLIL::CONST_FLAG_REAL) { if (metadata_item.second.flags & RTLIL::CONST_FLAG_REAL) {
f << std::showpoint << std::stod(metadata_item.second.decode_string()) << std::noshowpoint; 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) { } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) {
f << escape_cxx_string(metadata_item.second.decode_string()); data += 's';
data += metadata_item.second.decode_string();
data += '\0';
} else if (metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED) { } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED) {
f << "INT64_C(" << metadata_item.second.as_int(/*is_signed=*/true) << ")"; data += 'i';
put_u64((uint64_t)metadata_item.second.as_int(/*is_signed=*/true));
} else { } else {
f << "UINT64_C(" << metadata_item.second.as_int(/*is_signed=*/false) << ")"; data += 'u';
put_u64(metadata_item.second.as_int(/*is_signed=*/false));
} }
f << " },\n";
} }
dec_indent(); f << "metadata::deserialize(\n";
f << indent << "})"; inc_indent();
f << indent << escape_c_string(data) << "\n";
dec_indent();
f << indent << ")";
} 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) void dump_debug_attrs(const RTLIL::AttrObject *object)

View File

@ -941,6 +941,55 @@ struct metadata {
assert(value_type == DOUBLE); assert(value_type == DOUBLE);
return double_value; 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; typedef std::map<std::string, metadata> metadata_map;