From f7e628562ffd223d16a8aaeec3a62039b02223f9 Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Thu, 11 Jun 2026 10:53:31 -0700 Subject: [PATCH] Shrink debug info emitted for `CARBON_CHECK` message formatting (#7339) This is based on an idea suggested during review of the original type erasure PR. The type-erased check failure path lowers each check through `CheckFailFormat`, which builds one format adapter per value, down to the out-of-line `CheckFailImpl`, which takes an array of base-class `format_adapter*` pointers. Previously a separate variadic `CheckFailWithAdapters` template sat in between: it existed only to bind the adapter temporaries to named parameters so that pointers to their base class could be collected into a `std::array` and outlive the call to `CheckFailImpl`. This removes that layer. `CheckFailFormat` now builds the pointer array directly, in the braced-init-list of the `CheckFailImpl` call, using a small `CheckFailFormatAdapterAddr` helper to take the base-class address of each adapter. The adapter temporaries are materialized as named parameters of that helper within the same full-expression as the `CheckFailImpl` call, so they remain alive across the call without a dedicated function for that purpose. The win is in debug info, not code. `CheckFailWithAdapters` was instantiated once per distinct adapter-type sequence in every translation unit that uses `CARBON_CHECK`, and each instantiation carried its own DWARF records -- type entries, string-table offsets, and range lists. Dropping it removes those records from every such translation unit. The generated machine code is unchanged. Measured impact (fastbuild, the inputs to the inner-loop links): - First-party object files shrink by 173,728 bytes (-0.233%) across the `carbon-busybox` link inputs; 232 of 270 objects get smaller and none grow. The largest reductions are in the check-densest translation units (`type_completion`, `type`, `import_ref`, `function_context`, `constant`, ...). - Per `bloaty`, the reduction is almost entirely DWARF (`.debug_info`, `.debug_str`, `.rela.debug_str_offsets`, `.debug_rnglists`); the loaded code (VM size) is unchanged. Assisted-by: Claude Code --------- Co-authored-by: Geoff Romer --- common/check_internal.h | 48 ++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/common/check_internal.h b/common/check_internal.h index e94e713a6b17..a97a43259823 100644 --- a/common/check_internal.h +++ b/common/check_internal.h @@ -31,9 +31,9 @@ CheckCondition(bool condition) // Implements the check failure message printing. // // This is out-of-line and will arrange to stop the program, print any debugging -// information and this string. In `!NDEBUG` mode (`dbg` and `fastbuild`), check -// failures can be made non-fatal by a build flag, so this is not `[[noreturn]]` -// in that case. +// information and the failure message. In `!NDEBUG` mode (`dbg` and +// `fastbuild`), check failures can be made non-fatal by a build flag, so this +// is not `[[noreturn]]` in that case. // // This API uses `const char*` C string arguments rather than `llvm::StringRef` // because we know that these are available as C strings and passing them that @@ -52,8 +52,8 @@ auto CheckFailImpl( llvm::ArrayRef extra_adapters) -> void; -// Allow converting format values; the default behaviour is to just pass them -// through. +// Allow custom conversion of format values; the default behaviour is to just +// pass them through. template auto ConvertFormatValue(T&& t) -> T&& { return std::forward(t); @@ -74,28 +74,21 @@ auto ConvertFormatValue(T&& t) -> auto { } } -// Collects pointers to the given type-erased format adapters and passes them, -// with the rest of the check metadata, to the out-of-line `CheckFailImpl`. -// -// We need a separate function accepting all the adapters as arguments to ensure -// those objects stay alive for pointers to their base class to be put into an -// array and passed to the type erased implementation. -template -#ifdef NDEBUG -[[noreturn]] -#endif -auto CheckFailWithAdapters(const char* kind, const char* file, int line, - const char* condition_str, const char* extra_format, - Adapters&&... adapters) -> void { - std::array - adapter_pointers = {&adapters...}; - CheckFailImpl(kind, file, line, condition_str, extra_format, - adapter_pointers); +// Helper to compute a pointer to the base class of a given LLVM +// `format_adapter` object. This both handles converting to the base class and +// allows taking the address of temporaries within arguments to another function +// call. +template +auto CheckFailFormatAdapterAddr(T&& adapter) + -> llvm::support::detail::format_adapter* { + return &adapter; } // Builds one type-erased format adapter per value -- forwarding each value -// through the conversion machinery -- and hands them to -// `CheckFailWithAdapters`. +// through the conversion machinery. The address of the base classes of each of +// these are then collected into an init list that can be accessed with an +// `ArrayRef`. All of this is then passed to the out-of-line rendering function +// `CheckFailImpl`. // // This is templated only on the value types, not on the per-check-site // metadata (file, line, etc., which are passed as ordinary arguments), so the @@ -108,9 +101,10 @@ template auto CheckFailFormat(const char* kind, const char* file, int line, const char* condition_str, const char* extra_format, Ts&&... values) -> void { - CheckFailWithAdapters(kind, file, line, condition_str, extra_format, - llvm::support::detail::build_format_adapter( - ConvertFormatValue(std::forward(values)))...); + CheckFailImpl( + kind, file, line, condition_str, extra_format, + {CheckFailFormatAdapterAddr(llvm::support::detail::build_format_adapter( + ConvertFormatValue(std::forward(values))))...}); } // Prints a check failure, including rendering any user-provided message using