Split standard context into a separate class and optimize

This commit is contained in:
Victor Zverovich 2024-01-08 05:56:07 -08:00
parent 23e8109d8d
commit a5ae9ae19d
5 changed files with 205 additions and 171 deletions

View File

@ -1099,6 +1099,7 @@ FMT_CONSTEXPR void basic_format_parse_context<Char>::check_dynamic_spec(
FMT_EXPORT template <typename Context> class basic_format_arg;
FMT_EXPORT template <typename Context> class basic_format_args;
FMT_EXPORT template <typename Context, typename... Args> class format_arg_store;
FMT_EXPORT template <typename Context> class dynamic_format_arg_store;
// A formatter for objects of type T.
@ -1129,7 +1130,7 @@ template <typename T> class basic_appender {
using difference_type = ptrdiff_t;
FMT_UNCHECKED_ITERATOR(basic_appender);
basic_appender(detail::buffer<T>& buf) : buffer_(&buf) {}
FMT_CONSTEXPR basic_appender(detail::buffer<T>& 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<Visitor&&>(vis));
}
// Formatting context.
template <typename OutputIt, typename Char> 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 <typename Context> class basic_format_args {
public:
using size_type = int;
using format_arg = basic_format_arg<Context>;
private:
OutputIt out_;
basic_format_args<basic_format_context> 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<Context>* 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<detail::type>((desc_ >> shift) & mask);
}
constexpr FMT_INLINE basic_format_args(unsigned long long desc,
const detail::value<Context>* 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 <typename... Args>
constexpr FMT_INLINE basic_format_args(
const format_arg_store<Context, Args...>& store)
: basic_format_args(format_arg_store<Context, Args...>::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<Context>& 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 <typename Char>
auto get(basic_string_view<Char> name) const -> format_arg {
int id = get_id(name);
return id >= 0 ? get(id) : format_arg();
}
template <typename Char>
FMT_CONSTEXPR auto get_id(basic_string_view<Char> 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<int>(is_packed() ? max_packed
: desc_ & ~detail::is_unpacked_bit);
}
};
// A formatting context.
class context {
private:
appender out_;
basic_format_args<context> args_;
detail::locale_ref loc_;
public:
using iterator = OutputIt;
using format_arg = basic_format_arg<basic_format_context>;
using format_args = basic_format_args<basic_format_context>;
using parse_context_type = basic_format_parse_context<Char>;
template <typename T> using formatter_type = formatter<T, Char>;
/** The character type for the output. */
using char_type = Char;
using char_type = char;
using iterator = appender;
using format_arg = basic_format_arg<context>;
using format_args = basic_format_args<context>;
using parse_context_type = basic_format_parse_context<char>;
template <typename T> using formatter_type = formatter<T, char>;
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<Char> name) -> format_arg {
return args_.get(name);
}
FMT_CONSTEXPR auto arg_id(basic_string_view<Char> 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 <typename OutputIt, typename Char> 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<iterator>()) out_ = it;
}
void advance_to(iterator) {}
FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; }
};
template <typename OutputIt, typename Char> class generic_context;
// Longer aliases for C++20 compatibility.
template <typename OutputIt, typename Char>
using basic_format_context =
conditional_t<std::is_same<OutputIt, appender>::value, context,
generic_context<OutputIt, Char>>;
using format_context = context;
template <typename Char>
using buffer_context = basic_format_context<basic_appender<Char>, Char>;
using format_context = basic_format_context<appender, char>;
template <typename T, typename Char = char>
using is_formattable = bool_constant<!std::is_base_of<
@ -1874,127 +2000,6 @@ inline auto arg(const Char* name, const T& arg) -> detail::named_arg<Char, T> {
}
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 <typename Context> class basic_format_args {
public:
using size_type = int;
using format_arg = basic_format_arg<Context>;
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<Context>* 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<detail::type>((desc_ >> shift) & mask);
}
constexpr FMT_INLINE basic_format_args(unsigned long long desc,
const detail::value<Context>* 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 <typename... Args>
constexpr FMT_INLINE basic_format_args(
const format_arg_store<Context, Args...>& store)
: basic_format_args(format_arg_store<Context, Args...>::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<Context>& 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 <typename Char>
auto get(basic_string_view<Char> name) const -> format_arg {
int id = get_id(name);
return id >= 0 ? get(id) : format_arg();
}
template <typename Char>
auto get_id(basic_string_view<Char> 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<int>(is_packed() ? max_packed
: desc_ & ~detail::is_unpacked_bit);
}
};
/** An alias to ``basic_format_args<format_context>``. */
// A separate type would result in shorter symbols but break ABI compatibility
// between clang and gcc on ARM (#1919).

View File

@ -1071,6 +1071,50 @@ constexpr auto compile_string_to_view(detail::std_string_view<Char> s)
}
} // namespace detail_exported
// A generic formatting context with custom output iterator and character
// (code unit) support.
template <typename OutputIt, typename Char> class generic_context {
private:
OutputIt out_;
basic_format_args<generic_context> args_;
detail::locale_ref loc_;
public:
using char_type = Char;
using iterator = OutputIt;
using format_args = basic_format_args<generic_context>;
using parse_context_type = basic_format_parse_context<Char>;
template <typename T> using formatter_type = formatter<T, Char>;
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<generic_context> {
return args_.get(id);
}
auto arg(basic_string_view<Char> name) -> basic_format_arg<generic_context> {
return args_.get(name);
}
FMT_CONSTEXPR auto arg_id(basic_string_view<Char> 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<iterator>()) out_ = it;
}
FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; }
};
class loc_value {
private:
basic_format_arg<format_context> value_;

View File

@ -143,9 +143,8 @@ template <typename Char>
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
void set_debug_format() = delete;
template <typename T, typename OutputIt>
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
-> OutputIt {
template <typename T, typename Context>
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
auto buffer = basic_memory_buffer<Char>();
detail::format_value(buffer, value);
return formatter<basic_string_view<Char>, Char>::format(
@ -158,9 +157,9 @@ using ostream_formatter = basic_ostream_formatter<char>;
template <typename T, typename Char>
struct formatter<detail::streamed_view<T>, Char>
: basic_ostream_formatter<Char> {
template <typename OutputIt>
auto format(detail::streamed_view<T> view,
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
template <typename Context>
auto format(detail::streamed_view<T> view, Context& ctx) const
-> decltype(ctx.out()) {
return basic_ostream_formatter<Char>::format(view.value, ctx);
}
};

View File

@ -400,9 +400,9 @@ struct formatter<
return it;
}
template <typename OutputIt>
auto format(const std::exception& ex,
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
template <typename Context>
auto format(const std::exception& ex, Context& ctx) const
-> decltype(ctx.out()) {
format_specs<Char> spec;
auto out = ctx.out();
if (!with_typename_)

View File

@ -368,7 +368,7 @@ VISIT_TYPE(unsigned long, unsigned long long);
{ \
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
EXPECT_CALL(visitor, visit(expected)); \
using iterator = std::back_insert_iterator<buffer<Char>>; \
using iterator = fmt::basic_appender<Char>; \
auto var = value; \
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>(var) \
.visit(visitor); \
@ -379,7 +379,6 @@ VISIT_TYPE(unsigned long, unsigned long long);
using value_type = decltype(value); \
typename visit_type<value_type>::type expected = value; \
CHECK_ARG(char, expected, value) \
CHECK_ARG(wchar_t, expected, value) \
}
template <typename T> 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<wchar_t>(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<wchar_t>(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);
}