From 9134cd1928a0a043f241fb6e3deef5e0a59d1908 Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 21 Mar 2024 20:28:32 +0000 Subject: [PATCH] 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%. --- backends/cxxrtl/cxxrtl_backend.cc | 81 ++++++++++++++++++++----- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 49 +++++++++++++++ 2 files changed, 116 insertions(+), 14 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 86fe9e81b..42b379637 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -606,9 +606,10 @@ std::vector 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,14 +2283,23 @@ struct CxxrtlWorker { dec_indent(); } - void dump_metadata_map(const dict &metadata_map) + void dump_metadata_map(const dict &metadata_map, bool serialize = true) { if (metadata_map.empty()) { f << "metadata_map()"; return; - } - f << "metadata_map({\n"; - inc_indent(); + } 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; @@ -2291,21 +2307,58 @@ struct CxxrtlWorker { 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)) << ", "; + 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) { - 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) { - 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) { - 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 { - 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 << indent << "})"; + 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) { + 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) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 8546a8411..068dc3505 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -941,6 +941,55 @@ struct metadata { assert(value_type == DOUBLE); return double_value; } + + // Internal CXXRTL use only. + static std::map deserialize(const char *ptr) { + std::map result; + std::string name; + // Grammar: + // string ::= [^\0]+ \0 + // metadata ::= [uid] .{8} | s + // map ::= ( )* \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 metadata_map;