Encode common flag type names as a 5-bit ID and drop the type pointer. Every `absl::Flag` in writable data carries an 8-byte type-name pointer and a dynamic relocation entry, even though type names are consulted only on cold reflection and help paths. Most instances of `absl::flags_internal::FlagImpl` use one of 30 common built-in type spellings, while custom types and type aliases still need their exact macro-spelled type string preserved. Encode those 30 common type spellings as a 5-bit type index into a shared read-only table, keeping common flag names in mergeable string sections with no extra read-only bytes. For uncommon types and aliases, concatenate the flag name and type name into a single null-separated string literal and scan past the first null byte on demand. Removing the pointer shrinks `FlagImpl` from 88 bytes to 80 bytes and scalar flags from 96 bytes to 88 bytes, while returning an empty string for retired flags and stripped-name builds. PiperOrigin-RevId: 1006216029
diff --git a/absl/flags/commandlineflag_test.cc b/absl/flags/commandlineflag_test.cc index b6d01b2..35ce567 100644 --- a/absl/flags/commandlineflag_test.cc +++ b/absl/flags/commandlineflag_test.cc
@@ -16,7 +16,9 @@ #include "absl/flags/commandlineflag.h" #include <memory> +#include <optional> #include <string> +#include <vector> #include "gtest/gtest.h" #include "absl/flags/config.h" @@ -38,6 +40,21 @@ ABSL_FLAG(int, int_flag2, 201, ""); ABSL_FLAG(std::string, string_flag2, "dflt", ""); +using CustomIntAlias = int; +using CustomStringListAlias = std::vector<std::string>; + +struct CustomFlagUDT {}; +bool AbslParseFlag(absl::string_view, CustomFlagUDT*, std::string*) { + return true; +} +std::string AbslUnparseFlag(const CustomFlagUDT&) { return ""; } + +ABSL_FLAG(CustomIntAlias, int_alias_flag, 7, ""); +ABSL_FLAG(CustomStringListAlias, string_list_alias_flag, {}, ""); +ABSL_FLAG(std::vector<std::string>, string_vec_flag, {}, ""); +ABSL_FLAG(std::optional<int>, optional_int_flag, std::nullopt, ""); +ABSL_FLAG(CustomFlagUDT, custom_udt_flag, {}, ""); + namespace { namespace flags = absl::flags_internal; @@ -233,4 +250,67 @@ // EXPECT_EQ(err, "ERROR: int_flag is already set to 201"); } +void MakeEmptyString(void* dst) { new (dst) std::string(); } + +constexpr flags::FlagHelpArg kCompatHelpArg{flags::FlagHelpMsg("help"), + flags::FlagHelpKind::kLiteral}; +constexpr flags::FlagDefaultArg kCompatStringDefaultArg{ + flags::FlagDefaultSrc(&MakeEmptyString), flags::FlagDefaultKind::kGenFunc}; +constexpr absl::Flag<std::string> kCompat4ArgFlag{"compat4", "file", + kCompatHelpArg, + kCompatStringDefaultArg}; +constexpr absl::Flag<std::string> kCompat5ArgCommonFlag{ + "compat5_common", "std::string", "file", kCompatHelpArg, + kCompatStringDefaultArg}; +constexpr absl::Flag<std::string> kCompat5ArgCustomFlag{ + "compat5_custom", "UnlistedStringAlias", "file", kCompatHelpArg, + kCompatStringDefaultArg}; + +TEST_F(CommandLineFlagTest, TestTypeName) { + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + *absl::FindCommandLineFlag("int_flag")), + "int"); + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + *absl::FindCommandLineFlag("string_flag")), + "std::string"); + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + *absl::FindCommandLineFlag("string_vec_flag")), + "std::vector<std::string>"); + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + *absl::FindCommandLineFlag("optional_int_flag")), + "std::optional<int>"); + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + *absl::FindCommandLineFlag("int_alias_flag")), + "CustomIntAlias"); + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + *absl::FindCommandLineFlag("string_list_alias_flag")), + "CustomStringListAlias"); + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + *absl::FindCommandLineFlag("custom_udt_flag")), + "CustomFlagUDT"); + + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + absl::GetFlagReflectionHandle(kCompat4ArgFlag)), + ""); + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + absl::GetFlagReflectionHandle(kCompat5ArgCommonFlag)), + "std::string"); + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + absl::GetFlagReflectionHandle(kCompat5ArgCustomFlag)), + ""); +} + +TEST(CommandLineFlagStrippedTest, TestStrippedTypeName) { +#if ABSL_FLAGS_STRIP_NAMES + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_int_flag).Name(), ""); + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + absl::GetFlagReflectionHandle(FLAGS_int_flag)), + ""); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_custom_udt_flag).Name(), ""); + EXPECT_EQ(flags::PrivateHandleAccessor::TypeName( + absl::GetFlagReflectionHandle(FLAGS_custom_udt_flag)), + ""); +#endif +} + } // namespace
diff --git a/absl/flags/flag.h b/absl/flags/flag.h index 2e9a9e6..ea2b889 100644 --- a/absl/flags/flag.h +++ b/absl/flags/flag.h
@@ -207,6 +207,9 @@ #define ABSL_FLAG_IMPL_FLAGNAME(txt) "" #define ABSL_FLAG_IMPL_TYPENAME(txt) "" #define ABSL_FLAG_IMPL_FILENAME() "" +#define ABSL_FLAG_IMPL_NAME_ARG(name) \ + absl::flags_internal::FlagNameArg{"", absl::flags_internal::kEmptyFlagTypeId} +#define ABSL_FLAG_IMPL_DECLARE_NAME_METHODS(name, Type) #define ABSL_FLAG_IMPL_REGISTRAR(T, flag) \ absl::flags_internal::FlagRegistrar<T, false>(ABSL_FLAG_IMPL_FLAG_PTR(flag), \ nullptr) @@ -214,6 +217,18 @@ #define ABSL_FLAG_IMPL_FLAGNAME(txt) txt #define ABSL_FLAG_IMPL_TYPENAME(txt) txt #define ABSL_FLAG_IMPL_FILENAME() __FILE__ +#define ABSL_FLAG_IMPL_NAME_ARG(name) \ + absl::flags_internal::MakeFlagNameArg<AbslFlagDefaultGenFor##name>() +#define ABSL_FLAG_IMPL_DECLARE_NAME_METHODS(name, Type) \ + static constexpr const char* AbslFlagName() { \ + return #name; \ + } \ + static constexpr absl::string_view AbslFlagTypeName() { \ + return absl::string_view(#Type, sizeof(#Type) - 1); \ + } \ + static constexpr const char* AbslFlagPackedNameAndType() { \ + return #name "\0" #Type; \ + } #define ABSL_FLAG_IMPL_REGISTRAR(T, flag) \ absl::flags_internal::FlagRegistrar<T, true>(ABSL_FLAG_IMPL_FLAG_PTR(flag), \ __FILE__) @@ -241,7 +256,7 @@ /* default value argument. That keeps temporaries alive */ \ /* long enough for NonConst to work correctly. */ \ static constexpr absl::string_view Value( \ - absl::string_view absl_flag_help ABSL_ATTRIBUTE_LIFETIME_BOUND = \ + absl::string_view absl_flag_help ABSL_ATTRIBUTE_LIFETIME_BOUND = \ ABSL_FLAG_IMPL_FLAGHELP(txt)) { \ return absl_flag_help; \ } \ @@ -258,6 +273,7 @@ static void Gen(void* absl_flag_default_loc) { \ new (absl_flag_default_loc) Type(AbslFlagDefaultGenFor##name{}.value); \ } \ + ABSL_FLAG_IMPL_DECLARE_NAME_METHODS(name, Type) \ }; // ABSL_FLAG_IMPL @@ -265,17 +281,16 @@ // Note: Name of registrar object is not arbitrary. It is used to "grab" // global name for FLAGS_no<flag_name> symbol, thus preventing the possibility // of defining two flags with names foo and nofoo. -#define ABSL_FLAG_IMPL(Type, name, default_value, help) \ - extern ::absl::Flag<Type> FLAGS_##name; \ - namespace absl /* block flags in namespaces */ {} \ - ABSL_FLAG_IMPL_DECLARE_DEF_VAL_WRAPPER(name, Type, default_value) \ - ABSL_FLAG_IMPL_DECLARE_HELP_WRAPPER(name, help) \ - ABSL_CONST_INIT absl::Flag<Type> FLAGS_##name{ \ - ABSL_FLAG_IMPL_FLAGNAME(#name), ABSL_FLAG_IMPL_TYPENAME(#Type), \ - ABSL_FLAG_IMPL_FILENAME(), ABSL_FLAG_IMPL_HELP_ARG(name), \ - ABSL_FLAG_IMPL_DEFAULT_ARG(Type, name)}; \ - extern absl::flags_internal::FlagRegistrarEmpty FLAGS_no##name; \ - absl::flags_internal::FlagRegistrarEmpty FLAGS_no##name = \ +#define ABSL_FLAG_IMPL(Type, name, default_value, help) \ + extern ::absl::Flag<Type> FLAGS_##name; \ + namespace absl /* block flags in namespaces */ {} \ + ABSL_FLAG_IMPL_DECLARE_DEF_VAL_WRAPPER(name, Type, default_value) \ + ABSL_FLAG_IMPL_DECLARE_HELP_WRAPPER(name, help) \ + ABSL_CONST_INIT absl::Flag<Type> FLAGS_##name{ \ + ABSL_FLAG_IMPL_NAME_ARG(name), ABSL_FLAG_IMPL_FILENAME(), \ + ABSL_FLAG_IMPL_HELP_ARG(name), ABSL_FLAG_IMPL_DEFAULT_ARG(Type, name)}; \ + extern absl::flags_internal::FlagRegistrarEmpty FLAGS_no##name; \ + absl::flags_internal::FlagRegistrarEmpty FLAGS_no##name = \ ABSL_FLAG_IMPL_REGISTRAR(Type, FLAGS_##name) // ABSL_RETIRED_FLAG
diff --git a/absl/flags/flag_test.cc b/absl/flags/flag_test.cc index 5cacf64..b1ac209 100644 --- a/absl/flags/flag_test.cc +++ b/absl/flags/flag_test.cc
@@ -137,6 +137,17 @@ flags::FlagValueStorageKind::kSequenceLocked); EXPECT_EQ(flags::StorageKind<absl::uint128>(), flags::FlagValueStorageKind::kSequenceLocked); + + // These sizes are not hard limits, but we want to keep per-flag size as + // small as possible because production binaries may define thousands of + // flags. + if (sizeof(void*) == 8) { + EXPECT_EQ(sizeof(flags::FlagImpl), 80); + EXPECT_EQ(sizeof(absl::Flag<bool>), 88); + EXPECT_EQ(sizeof(absl::Flag<int>), 88); + EXPECT_EQ(sizeof(absl::Flag<int64_t>), 88); + EXPECT_EQ(sizeof(absl::Flag<double>), 88); + } } // -------------------------------------------------------------------- @@ -151,9 +162,9 @@ #define DEFINE_CONSTRUCTED_FLAG(T, dflt, dflt_kind) \ constexpr flags::FlagDefaultArg f1default##T{ \ flags::FlagDefaultSrc{dflt}, flags::FlagDefaultKind::dflt_kind}; \ - constexpr absl::Flag<T> f1##T{"f1", #T, "file", help_arg, f1default##T}; \ + constexpr absl::Flag<T> f1##T{"f1", "file", help_arg, f1default##T}; \ ABSL_CONST_INIT absl::Flag<T> f2##T { \ - "f2", #T, "file", \ + "f2", "file", \ {flags::FlagHelpMsg(&TestHelpMsg), flags::FlagHelpKind::kGenFunc}, \ flags::FlagDefaultArg { \ flags::FlagDefaultSrc(&TestMakeDflt<T>), \ @@ -175,6 +186,11 @@ DEFINE_CONSTRUCTED_FLAG(int128, 13, kGenFunc); DEFINE_CONSTRUCTED_FLAG(uint128, 14, kGenFunc); +constexpr absl::Flag<String> f_compat_5arg{ + "f_compat", "std::string", "file", help_arg, + flags::FlagDefaultArg{flags::FlagDefaultSrc(&TestMakeDflt<String>), + flags::FlagDefaultKind::kGenFunc}}; + template <typename T> bool TestConstructionFor(const absl::Flag<T>& f1, absl::Flag<T>& f2) { EXPECT_EQ(absl::GetFlagReflectionHandle(f1).Name(), "f1"); @@ -207,6 +223,10 @@ TEST_CONSTRUCTED_FLAG(UDT); TEST_CONSTRUCTED_FLAG(int128); TEST_CONSTRUCTED_FLAG(uint128); + EXPECT_EQ(absl::GetFlagReflectionHandle(f_compat_5arg).Name(), "f_compat"); + EXPECT_EQ(absl::GetFlagReflectionHandle(f_compat_5arg).Help(), + "literal help"); + EXPECT_EQ(absl::GetFlagReflectionHandle(f_compat_5arg).Filename(), "file"); } // --------------------------------------------------------------------
diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc index 662b342..4b4756c 100644 --- a/absl/flags/internal/flag.cc +++ b/absl/flags/internal/flag.cc
@@ -354,7 +354,12 @@ absl::string_view FlagImpl::Name() const { return name_; } -absl::string_view FlagImpl::TypeName() const { return type_name_; } +absl::string_view FlagImpl::TypeName() const { + if (ABSL_PREDICT_TRUE(type_id_ != kCustomFlagTypeId)) { + return kCommonFlagTypeNames[type_id_]; + } + return name_ + std::strlen(name_) + 1; +} std::string FlagImpl::Filename() const { return flags_internal::GetUsageConfig().normalize_filename(filename_);
diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index c12c924..fcc6a68 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h
@@ -21,6 +21,7 @@ #include <atomic> #include <cstring> +#include <iterator> #include <memory> #include <string> #include <type_traits> @@ -166,7 +167,7 @@ } /////////////////////////////////////////////////////////////////////////////// -// Flag help auxiliary structs. +// Flag help and name/type-name auxiliary structs. // This is help argument for absl::Flag encapsulating the string literal pointer // or pointer to function generating it as well as enum discriminating two @@ -184,6 +185,81 @@ } }; +inline constexpr uint8_t kEmptyFlagTypeId = 0; +inline constexpr uint8_t kCustomFlagTypeId = 31; + +// Common macro-stringified `#Type` spellings stored as a 5-bit ID in `FlagImpl` +// so common flags avoid emitting a per-flag type-name string. Entry 0 (`""`) is +// used when flag names are stripped or omitted; ID 31 (`kCustomFlagTypeId`) +// indicates that `name_` points to a packed `"<name>\0<type_name>\0"` array. +inline constexpr absl::string_view kCommonFlagTypeNames[] = { + "", + "bool", + "int", + "int16_t", + "uint16_t", + "int32_t", + "uint32_t", + "int64_t", + "uint64_t", + "size_t", + "float", + "double", + "std::string", + "string", + "std::vector<std::string>", + "absl::Duration", + "absl::Time", + "absl::int128", + "absl::uint128", + "std::int32_t", + "std::int64_t", + "std::optional<bool>", + "std::optional<int>", + "std::optional<int32_t>", + "std::optional<uint32_t>", + "std::optional<int64_t>", + "std::optional<uint64_t>", + "std::optional<size_t>", + "std::optional<double>", + "std::optional<std::string>", + "std::optional<absl::Duration>", +}; +static_assert(std::size(kCommonFlagTypeNames) == kCustomFlagTypeId, + "kCommonFlagTypeNames must have kCustomFlagTypeId entries"); + +constexpr uint8_t LookupCommonFlagTypeId(absl::string_view type_name) { + for (size_t i = 0; i < kCustomFlagTypeId; ++i) { + if (kCommonFlagTypeNames[i] == type_name) { + return static_cast<uint8_t>(i); + } + } + return kCustomFlagTypeId; +} + +constexpr uint8_t LookupCommonOrEmptyFlagTypeId(const char* type_name) { + if (type_name == nullptr) return kEmptyFlagTypeId; + size_t len = 0; + while (type_name[len] != '\0') ++len; + const uint8_t id = LookupCommonFlagTypeId(absl::string_view(type_name, len)); + return id == kCustomFlagTypeId ? kEmptyFlagTypeId : id; +} + +struct FlagNameArg { + const char* name; + uint8_t type_id; +}; + +template <typename Gen> +constexpr FlagNameArg MakeFlagNameArg() { + constexpr uint8_t kTypeId = LookupCommonFlagTypeId(Gen::AbslFlagTypeName()); + if constexpr (kTypeId != kCustomFlagTypeId) { + return {Gen::AbslFlagName(), kTypeId}; + } else { + return {Gen::AbslFlagPackedNameAndType(), kCustomFlagTypeId}; + } +} + template <typename Gen, size_t N = Gen::Value().size()> constexpr FixedCharArray<N + 1> HelpStringAsArray(int) { return FixedCharArray<N + 1>::FromLiteralString( @@ -581,17 +657,16 @@ #endif class FlagImpl final : public CommandLineFlag { public: - constexpr FlagImpl(const char* name, const char* type_name, - const char* filename, FlagOpFn op, FlagHelpArg help, - FlagValueStorageKind value_kind, + constexpr FlagImpl(FlagNameArg name, const char* filename, FlagOpFn op, + FlagHelpArg help, FlagValueStorageKind value_kind, FlagDefaultArg default_arg) - : name_(name), - type_name_(type_name), + : name_(name.name), filename_(filename), op_(op), help_(help.source), help_source_kind_(static_cast<uint8_t>(help.kind)), value_storage_kind_(static_cast<uint8_t>(value_kind)), + type_id_(name.type_id), def_kind_(static_cast<uint8_t>(default_arg.kind)), modified_(false), on_command_line_(false), @@ -731,12 +806,10 @@ // Immutable flag's state. - // Flags name passed to ABSL_FLAG as second arg. + // Flags name passed to ABSL_FLAG as second arg (followed by NUL-terminated + // type name when `type_id_ == kCustomFlagTypeId`). const char* const name_; - // Flags type passed to ABSL_FLAG as first arg. - const char* const type_name_; - // The file name where ABSL_FLAG resides. const char* const filename_; // Type-specific operations vtable. @@ -747,6 +820,9 @@ const uint8_t help_source_kind_ : 1; // Kind of storage this flag is using for the flag's value. const uint8_t value_storage_kind_ : 2; + // Index into `kCommonFlagTypeNames`, or `kCustomFlagTypeId` when `name_` + // stores `"<name>\0<type_name>\0"`. + const uint8_t type_id_ : 5; uint8_t : 0; // The bytes containing the const bitfields must not be // shared with bytes containing the mutable bitfields. @@ -803,11 +879,21 @@ template <typename T> class Flag { public: - constexpr Flag(const char* name, const char* type_name, const char* filename, - FlagHelpArg help, const FlagDefaultArg default_arg) - : impl_(name, type_name, filename, &FlagOps<T>, help, + constexpr Flag(FlagNameArg name, const char* filename, FlagHelpArg help, + const FlagDefaultArg default_arg) + : impl_(name, filename, &FlagOps<T>, help, flags_internal::StorageKind<T>(), default_arg), value_() {} + constexpr Flag(const char* name, const char* filename, FlagHelpArg help, + const FlagDefaultArg default_arg) + : Flag(FlagNameArg{name, kEmptyFlagTypeId}, filename, help, default_arg) { + } + // Kept for backwards compatibility. + constexpr Flag(const char* name, const char* type_name, + const char* filename, FlagHelpArg help, + const FlagDefaultArg default_arg) + : Flag(FlagNameArg{name, LookupCommonOrEmptyFlagTypeId(type_name)}, + filename, help, default_arg) {} // CommandLineFlag interface absl::string_view Name() const { return impl_.Name(); }