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;
}
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 == '\\')
@ -623,6 +624,12 @@ std::string escape_cxx_string(const std::string &input)
}
}
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()));
@ -2276,12 +2283,57 @@ struct CxxrtlWorker {
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()) {
f << "metadata_map()";
return;
} else if (serialize) {
// 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;
}
};
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 << "metadata::deserialize(\n";
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) {
@ -2307,6 +2359,7 @@ struct CxxrtlWorker {
dec_indent();
f << indent << "})";
}
}
void dump_debug_attrs(const RTLIL::AttrObject *object)
{

View File

@ -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;