From a5ae9ae19d59a15def6538e55fd284c338f00f84 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 8 Jan 2024 05:56:07 -0800 Subject: [PATCH] Split standard context into a separate class and optimize --- include/fmt/core.h | 299 +++++++++++++++++++++--------------------- include/fmt/format.h | 44 +++++++ include/fmt/ostream.h | 11 +- include/fmt/std.h | 6 +- test/core-test.cc | 16 +-- 5 files changed, 205 insertions(+), 171 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index d6c941fa..9c90e639 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1099,6 +1099,7 @@ FMT_CONSTEXPR void basic_format_parse_context::check_dynamic_spec( FMT_EXPORT template class basic_format_arg; FMT_EXPORT template class basic_format_args; +FMT_EXPORT template class format_arg_store; FMT_EXPORT template class dynamic_format_arg_store; // A formatter for objects of type T. @@ -1129,7 +1130,7 @@ template class basic_appender { using difference_type = ptrdiff_t; FMT_UNCHECKED_ITERATOR(basic_appender); - basic_appender(detail::buffer& buf) : buffer_(&buf) {} + FMT_CONSTEXPR basic_appender(detail::buffer& buf) : buffer_(&buf) {} auto operator=(T c) -> basic_appender& { buffer_->push_back(c); @@ -1733,39 +1734,158 @@ FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( return arg.visit(static_cast(vis)); } -// Formatting context. -template class basic_format_context { +/** + \rst + A view of a collection of formatting arguments. To avoid lifetime issues it + should only be used as a parameter type in type-erased functions such as + ``vformat``:: + + void vlog(string_view format_str, format_args args); // OK + format_args args = make_format_args(); // Error: dangling reference + \endrst + */ +template class basic_format_args { + public: + using size_type = int; + using format_arg = basic_format_arg; + private: - OutputIt out_; - basic_format_args args_; + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; + union { + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const detail::value* values_; + const format_arg* args_; + }; + + constexpr auto is_packed() const -> bool { + return (desc_ & detail::is_unpacked_bit) == 0; + } + constexpr auto has_named_args() const -> bool { + return (desc_ & detail::has_named_args_bit) != 0; + } + + FMT_CONSTEXPR auto type(int index) const -> detail::type { + int shift = index * detail::packed_arg_bits; + unsigned int mask = (1 << detail::packed_arg_bits) - 1; + return static_cast((desc_ >> shift) & mask); + } + + constexpr FMT_INLINE basic_format_args(unsigned long long desc, + const detail::value* values) + : desc_(desc), values_(values) {} + constexpr basic_format_args(unsigned long long desc, const format_arg* args) + : desc_(desc), args_(args) {} + + public: + constexpr basic_format_args() : desc_(0), args_(nullptr) {} + + /** + \rst + Constructs a `basic_format_args` object from `~fmt::format_arg_store`. + \endrst + */ + template + constexpr FMT_INLINE basic_format_args( + const format_arg_store& store) + : basic_format_args(format_arg_store::desc, + store.data_.args()) {} + + /** + \rst + Constructs a `basic_format_args` object from + `~fmt::dynamic_format_arg_store`. + \endrst + */ + constexpr FMT_INLINE basic_format_args( + const dynamic_format_arg_store& store) + : basic_format_args(store.get_types(), store.data()) {} + + /** + \rst + Constructs a `basic_format_args` object from a dynamic set of arguments. + \endrst + */ + constexpr basic_format_args(const format_arg* args, int count) + : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), + args) {} + + /** Returns the argument with the specified id. */ + FMT_CONSTEXPR auto get(int id) const -> format_arg { + format_arg arg; + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (id >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ == detail::type::none_type) return arg; + arg.value_ = values_[id]; + return arg; + } + + template + auto get(basic_string_view name) const -> format_arg { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template + FMT_CONSTEXPR auto get_id(basic_string_view name) const -> int { + if (!has_named_args()) return -1; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return named_args.data[i].id; + } + return -1; + } + + auto max_size() const -> int { + unsigned long long max_packed = detail::max_packed_args; + return static_cast(is_packed() ? max_packed + : desc_ & ~detail::is_unpacked_bit); + } +}; + +// A formatting context. +class context { + private: + appender out_; + basic_format_args args_; detail::locale_ref loc_; public: - using iterator = OutputIt; - using format_arg = basic_format_arg; - using format_args = basic_format_args; - using parse_context_type = basic_format_parse_context; - template using formatter_type = formatter; - /** The character type for the output. */ - using char_type = Char; + using char_type = char; + + using iterator = appender; + using format_arg = basic_format_arg; + using format_args = basic_format_args; + using parse_context_type = basic_format_parse_context; + template using formatter_type = formatter; - basic_format_context(basic_format_context&&) = default; - basic_format_context(const basic_format_context&) = delete; - void operator=(const basic_format_context&) = delete; /** Constructs a ``basic_format_context`` object. References to the arguments are stored in the object so make sure they have appropriate lifetimes. */ - constexpr basic_format_context(OutputIt out, format_args ctx_args, - detail::locale_ref loc = {}) + FMT_CONSTEXPR context(iterator out, format_args ctx_args, + detail::locale_ref loc = {}) : out_(out), args_(ctx_args), loc_(loc) {} + context(context&&) = default; + context(const context&) = delete; + void operator=(const context&) = delete; - constexpr auto arg(int id) const -> format_arg { return args_.get(id); } - FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { - return args_.get(name); - } - FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { + FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } + auto arg(string_view name) -> format_arg { return args_.get(name); } + FMT_CONSTEXPR auto arg_id(string_view name) -> int { return args_.get_id(name); } auto args() const -> const format_args& { return args_; } @@ -1777,16 +1897,22 @@ template class basic_format_context { FMT_CONSTEXPR auto out() -> iterator { return out_; } // Advances the begin iterator to ``it``. - void advance_to(iterator it) { - if (!detail::is_back_insert_iterator()) out_ = it; - } + void advance_to(iterator) {} FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } }; +template class generic_context; + +// Longer aliases for C++20 compatibility. +template +using basic_format_context = + conditional_t::value, context, + generic_context>; +using format_context = context; + template using buffer_context = basic_format_context, Char>; -using format_context = basic_format_context; template using is_formattable = bool_constant detail::named_arg { } FMT_END_EXPORT -/** - \rst - A view of a collection of formatting arguments. To avoid lifetime issues it - should only be used as a parameter type in type-erased functions such as - ``vformat``:: - - void vlog(string_view format_str, format_args args); // OK - format_args args = make_format_args(); // Error: dangling reference - \endrst - */ -template class basic_format_args { - public: - using size_type = int; - using format_arg = basic_format_arg; - - private: - // A descriptor that contains information about formatting arguments. - // If the number of arguments is less or equal to max_packed_args then - // argument types are passed in the descriptor. This reduces binary code size - // per formatting function call. - unsigned long long desc_; - union { - // If is_packed() returns true then argument values are stored in values_; - // otherwise they are stored in args_. This is done to improve cache - // locality and reduce compiled code size since storing larger objects - // may require more code (at least on x86-64) even if the same amount of - // data is actually copied to stack. It saves ~10% on the bloat test. - const detail::value* values_; - const format_arg* args_; - }; - - constexpr auto is_packed() const -> bool { - return (desc_ & detail::is_unpacked_bit) == 0; - } - auto has_named_args() const -> bool { - return (desc_ & detail::has_named_args_bit) != 0; - } - - FMT_CONSTEXPR auto type(int index) const -> detail::type { - int shift = index * detail::packed_arg_bits; - unsigned int mask = (1 << detail::packed_arg_bits) - 1; - return static_cast((desc_ >> shift) & mask); - } - - constexpr FMT_INLINE basic_format_args(unsigned long long desc, - const detail::value* values) - : desc_(desc), values_(values) {} - constexpr basic_format_args(unsigned long long desc, const format_arg* args) - : desc_(desc), args_(args) {} - - public: - constexpr basic_format_args() : desc_(0), args_(nullptr) {} - - /** - \rst - Constructs a `basic_format_args` object from `~fmt::format_arg_store`. - \endrst - */ - template - constexpr FMT_INLINE basic_format_args( - const format_arg_store& store) - : basic_format_args(format_arg_store::desc, - store.data_.args()) {} - - /** - \rst - Constructs a `basic_format_args` object from - `~fmt::dynamic_format_arg_store`. - \endrst - */ - constexpr FMT_INLINE basic_format_args( - const dynamic_format_arg_store& store) - : basic_format_args(store.get_types(), store.data()) {} - - /** - \rst - Constructs a `basic_format_args` object from a dynamic set of arguments. - \endrst - */ - constexpr basic_format_args(const format_arg* args, int count) - : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), - args) {} - - /** Returns the argument with the specified id. */ - FMT_CONSTEXPR auto get(int id) const -> format_arg { - format_arg arg; - if (!is_packed()) { - if (id < max_size()) arg = args_[id]; - return arg; - } - if (id >= detail::max_packed_args) return arg; - arg.type_ = type(id); - if (arg.type_ == detail::type::none_type) return arg; - arg.value_ = values_[id]; - return arg; - } - - template - auto get(basic_string_view name) const -> format_arg { - int id = get_id(name); - return id >= 0 ? get(id) : format_arg(); - } - - template - auto get_id(basic_string_view name) const -> int { - if (!has_named_args()) return -1; - const auto& named_args = - (is_packed() ? values_[-1] : args_[-1].value_).named_args; - for (size_t i = 0; i < named_args.size; ++i) { - if (named_args.data[i].name == name) return named_args.data[i].id; - } - return -1; - } - - auto max_size() const -> int { - unsigned long long max_packed = detail::max_packed_args; - return static_cast(is_packed() ? max_packed - : desc_ & ~detail::is_unpacked_bit); - } -}; - /** An alias to ``basic_format_args``. */ // A separate type would result in shorter symbols but break ABI compatibility // between clang and gcc on ARM (#1919). diff --git a/include/fmt/format.h b/include/fmt/format.h index 282aeae1..9b74a89d 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1071,6 +1071,50 @@ constexpr auto compile_string_to_view(detail::std_string_view s) } } // namespace detail_exported +// A generic formatting context with custom output iterator and character +// (code unit) support. +template class generic_context { + private: + OutputIt out_; + basic_format_args args_; + detail::locale_ref loc_; + + public: + using char_type = Char; + using iterator = OutputIt; + using format_args = basic_format_args; + using parse_context_type = basic_format_parse_context; + template using formatter_type = formatter; + + constexpr generic_context(OutputIt out, format_args ctx_args, + detail::locale_ref loc = {}) + : out_(out), args_(ctx_args), loc_(loc) {} + generic_context(generic_context&&) = default; + generic_context(const generic_context&) = delete; + void operator=(const generic_context&) = delete; + + constexpr auto arg(int id) const -> basic_format_arg { + return args_.get(id); + } + auto arg(basic_string_view name) -> basic_format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { + return args_.get_id(name); + } + auto args() const -> const format_args& { return args_; } + + void on_error(const char* message) { throw_format_error(message); } + + FMT_CONSTEXPR auto out() -> iterator { return out_; } + + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator()) out_ = it; + } + + FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } +}; + class loc_value { private: basic_format_arg value_; diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index 26fb3b5a..9906e5f2 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -143,9 +143,8 @@ template struct basic_ostream_formatter : formatter, Char> { void set_debug_format() = delete; - template - auto format(const T& value, basic_format_context& ctx) const - -> OutputIt { + template + auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) { auto buffer = basic_memory_buffer(); detail::format_value(buffer, value); return formatter, Char>::format( @@ -158,9 +157,9 @@ using ostream_formatter = basic_ostream_formatter; template struct formatter, Char> : basic_ostream_formatter { - template - auto format(detail::streamed_view view, - basic_format_context& ctx) const -> OutputIt { + template + auto format(detail::streamed_view view, Context& ctx) const + -> decltype(ctx.out()) { return basic_ostream_formatter::format(view.value, ctx); } }; diff --git a/include/fmt/std.h b/include/fmt/std.h index 885176c0..d8f30a19 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -400,9 +400,9 @@ struct formatter< return it; } - template - auto format(const std::exception& ex, - basic_format_context& ctx) const -> OutputIt { + template + auto format(const std::exception& ex, Context& ctx) const + -> decltype(ctx.out()) { format_specs spec; auto out = ctx.out(); if (!with_typename_) diff --git a/test/core-test.cc b/test/core-test.cc index 3ebe3ee7..4a96ccda 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -368,7 +368,7 @@ VISIT_TYPE(unsigned long, unsigned long long); { \ testing::StrictMock> visitor; \ EXPECT_CALL(visitor, visit(expected)); \ - using iterator = std::back_insert_iterator>; \ + using iterator = fmt::basic_appender; \ auto var = value; \ fmt::detail::make_arg>(var) \ .visit(visitor); \ @@ -379,7 +379,6 @@ VISIT_TYPE(unsigned long, unsigned long long); using value_type = decltype(value); \ typename visit_type::type expected = value; \ CHECK_ARG(char, expected, value) \ - CHECK_ARG(wchar_t, expected, value) \ } template class numeric_arg_test : public testing::Test {}; @@ -419,23 +418,10 @@ TEST(arg_test, string_arg) { CHECK_ARG(char, sv, std::string(str)); } -TEST(arg_test, wstring_arg) { - wchar_t str_data[] = L"test"; - wchar_t* str = str_data; - const wchar_t* cstr = str; - - auto sv = fmt::basic_string_view(str); - CHECK_ARG(wchar_t, cstr, str); - CHECK_ARG(wchar_t, cstr, cstr); - CHECK_ARG(wchar_t, sv, std::wstring(str)); - CHECK_ARG(wchar_t, sv, fmt::basic_string_view(str)); -} - TEST(arg_test, pointer_arg) { void* p = nullptr; const void* cp = nullptr; CHECK_ARG(char, cp, p); - CHECK_ARG(wchar_t, cp, p); CHECK_ARG_SIMPLE(cp); }