From 9e0a7ef7b7ef4ff536de0aff0fbfff646046f88d Mon Sep 17 00:00:00 2001 From: yumeyao Date: Mon, 8 Feb 2021 09:55:57 +0800 Subject: [PATCH 01/13] Make some fields public for use in fmtlog --- include/fmt/core.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 0a81e0cc..b081dc46 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1565,6 +1565,7 @@ class format_arg_store using value_type = conditional_t, basic_format_arg>; + public: detail::arg_data data_; @@ -1578,7 +1579,6 @@ class format_arg_store ? static_cast(detail::has_named_args_bit) : 0); - public: format_arg_store(const Args&... args) : #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 @@ -1857,13 +1857,13 @@ template class basic_format_args { return static_cast((desc_ >> shift) & mask); } + public: basic_format_args(unsigned long long desc, const detail::value* values) : desc_(desc), values_(values) {} basic_format_args(unsigned long long desc, const format_arg* args) : desc_(desc), args_(args) {} - public: basic_format_args() : desc_(0) {} /** From dabad1e0c7681ae3f860418a895096ff649f3a4c Mon Sep 17 00:00:00 2001 From: yumeyao Date: Mon, 8 Feb 2021 09:57:31 +0800 Subject: [PATCH 02/13] Add test.cpp for fmtlog --- include/fmt/test.cpp | 226 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 include/fmt/test.cpp diff --git a/include/fmt/test.cpp b/include/fmt/test.cpp new file mode 100644 index 00000000..42b89857 --- /dev/null +++ b/include/fmt/test.cpp @@ -0,0 +1,226 @@ +#define FMT_HEADER_ONLY +#include "format.h" + +namespace fmtlog { +using namespace fmt; + +template struct basic_format_entry { + using char_type = typename Context::char_type; + using format_arg = basic_format_arg; + using arg_destructor = void (*)(void *p); + + basic_string_view format_; + unsigned long long desc_; + arg_destructor dtor_; + + void destruct() { + if (dtor_) { + fmt::print("calling dtor\n"); + dtor_(this); + } else { + fmt::print("dtor is empty\n"); + } + } + FMT_CONSTEXPR basic_format_entry(basic_string_view format) : format_(format), desc_(0), dtor_(0) {} +}; + +template struct format_entry : basic_format_entry { + using entry = basic_format_entry; + + format_arg_store arg_store_; + // char stored_objs_[0]; + // char stored_bufs_[]; + + template + FMT_CONSTEXPR format_entry(const S& format_str, const Args&... args) : entry(to_string_view(format_str)), arg_store_(args...) { + entry::desc_ = arg_store_.desc; + } + + +}; + + + +template struct obj_store; + + + + + + +template > +inline auto mk_format_entry(const S& format_str, Args&&... args) -> format_entry, remove_reference_t...> { + return { format_str, args... }; +} + +template > +inline size_t write_format_entry(void* buf, const S& format_str, Args&&... args) { + using entry = format_entry, remove_reference_t...>; + auto p = new(buf) entry{ format_str, args... }; + (void) p; + return sizeof(entry); +} + + + + + +enum class store_method { + numeric, // stored by libfmt as numeric value, no need for extra storage + object, // stored by libfmt as object, copy/move construct the object manually (requires properly calling destructor) + buffer, // string/hexdump, store the string/binary trivially as buffer. + constexpr_str // compile-time string, can be stored as pointer directly (requires c++20 is_constant_evaluated()) +}; + +template +using stored_as_numeric = std::integral_constant; +template +using stored_as_string = std::integral_constant; +template struct is_basic_string : std::false_type {}; +template struct is_basic_string> : std::true_type {}; +template +using stored_as_string_object = std::integral_constant::value && is_basic_string>::value && std::is_rvalue_reference::value>; + +template , Context>> +struct stored_method_constant : std::integral_constant::value ? store_method::numeric : + stored_as_string::value ? (stored_as_string_object::value ? store_method::object : store_method::buffer) : + store_method::object> {}; + +struct stored_objs_dtor_base : std::false_type { + static void destruct(void* p) {} +}; + +template +struct stored_objs_dtor_gen : std::true_type { + static void destruct(void* p) { + Base::destruct(p); + reinterpret_cast((reinterpret_cast(p) + offset))->~T(); + } +}; + + +template +struct stored_objs_dtor_select { + using RawT = std::remove_reference_t; + using type = std::conditional_t::value == store_method::object, + typename stored_objs_dtor_select, Args...>::type, + typename stored_objs_dtor_select::type>; +}; + +template +struct stored_objs_dtor_select { using type = Base; }; + + +template +using stored_objs_dtor = typename stored_objs_dtor_select::type; + + +template +inline char* store_objs(void** stored_args, char* p) { return p; } + +template +inline char* store_objs(void** stored_args, char* p, T t, Args... args) { + using RawT = std::remove_reference_t; + if constexpr (stored_method_constant::value == store_method::object) { + new(p) RawT(static_cast(t)); + *stored_args = p; + p += sizeof(RawT); + } + ++stored_args; + return store_objs(stored_args, p, static_cast(args)...); +} + +template +inline char* store_bufs(void** stored_args, char* p) { return p; } + +template +inline char* store_bufs(void** stored_args, char* p, T t, Args... args) { + using RawT = std::remove_reference_t; + if constexpr (stored_method_constant::value == store_method::buffer) { + *stored_args = p; + //p = copy_buffer(p, t); + } + ++stored_args; + return store_bufs(stored_args, p, static_cast(args)...); +} + + +template struct index_list {}; + +// Collects internal details for generating index ranges [MIN, MAX) +inline namespace detail +{ + // Induction step + template + struct range_builder : public range_builder {}; + + // Base step + template struct range_builder { typedef index_list type; }; +} + +// Meta-function that returns a [MIN, MAX) index range +template +using index_range = typename range_builder::type; + +template +inline auto make_format_entry(void* buf, const S& format_str, void* const (&stored_args)[sizeof...(Args)], index_list, Args&&... args) { + using Char = char_t; + using Context = buffer_context; + using entry = format_entry...>; + return new(buf) entry { format_str, std::forward(stored_args[Indice] == 0 ? args : *reinterpret_cast*>(stored_args[Indice]))... }; +} + + +template > +inline size_t store_format_entry(void* buf, const S& format_str, Args&&... args) { + using Context = buffer_context; + using entry = format_entry...>; + using dtor = stored_objs_dtor; + char* pentry = reinterpret_cast(buf); + char* pobjs = pentry + sizeof(entry); // objects will be stored starting here + void* stored_args[sizeof...(Args)] = {0}; // if an object is stored, it has a pointer other than 0 + char* pbufs = store_objs(stored_args, pobjs, std::forward(args)...); + char* pend = store_bufs(stored_args, pbufs, std::forward(args)...); + + //auto p = new(buf) entry{ format_str, args... }; + auto p = make_format_entry(buf, format_str, stored_args, index_range<0, sizeof...(Args)>(), std::forward(args)...); + p->dtor_ = dtor::value ? dtor::destruct : nullptr; + return pend - pentry; +} + + + +template inline +void print_format_entry(basic_format_entry& entry) { + auto& full_entry = static_cast&>(entry); + vprint(entry.format_, {entry.desc_, full_entry.arg_store_.data_.args_}); ///// args_ or args + 1 depends on desc_ & detail::has_named_args_bit + entry.destruct(); +} + + +struct format_queue { + +}; + +} +using namespace fmtlog; + + +#include + + + +int main() { + std::cout << sizeof(basic_format_entry) << std::endl; + auto entry = mk_format_entry("The answer is {}\n", 42); + print_format_entry(entry); + + char buf[2000]; + size_t size = store_format_entry(buf, "The answer is {}{}\n", std::to_string(4), 2); + std::cout << size << std::endl; + auto& entryref = entry; + print_format_entry(reinterpret_cast(buf[0])); + + std::cout << &entry << "\n" << &(entry.arg_store_) << "\n" << sizeof(entry.arg_store_) << std::endl; +} From 9c6004535acfe1cfbe2533b77970eec2dde7cb35 Mon Sep 17 00:00:00 2001 From: yumeyao Date: Fri, 26 Feb 2021 19:39:55 +0800 Subject: [PATCH 03/13] essential fmtlog done --- include/fmt/test.cpp | 244 +++++++++++++++++++++++++++++++------------ 1 file changed, 179 insertions(+), 65 deletions(-) diff --git a/include/fmt/test.cpp b/include/fmt/test.cpp index 42b89857..405299fb 100644 --- a/include/fmt/test.cpp +++ b/include/fmt/test.cpp @@ -1,5 +1,6 @@ #define FMT_HEADER_ONLY #include "format.h" +#include namespace fmtlog { using namespace fmt; @@ -28,42 +29,19 @@ template struct format_entry : basic_format using entry = basic_format_entry; format_arg_store arg_store_; - // char stored_objs_[0]; - // char stored_bufs_[]; template FMT_CONSTEXPR format_entry(const S& format_str, const Args&... args) : entry(to_string_view(format_str)), arg_store_(args...) { entry::desc_ = arg_store_.desc; } - - }; - - -template struct obj_store; - - - - - - -template > -inline auto mk_format_entry(const S& format_str, Args&&... args) -> format_entry, remove_reference_t...> { - return { format_str, args... }; -} - -template > -inline size_t write_format_entry(void* buf, const S& format_str, Args&&... args) { - using entry = format_entry, remove_reference_t...>; - auto p = new(buf) entry{ format_str, args... }; - (void) p; - return sizeof(entry); -} - - - - +// +// A stored entry looks like: +// ----------------------------------------------------------------------- +// | basic_format_entry | arg_store | stored_objs... | stored_buffers... | +// ----------------------------------------------------------------------- +// enum class store_method { numeric, // stored by libfmt as numeric value, no need for extra storage @@ -72,6 +50,10 @@ enum class store_method { constexpr_str // compile-time string, can be stored as pointer directly (requires c++20 is_constant_evaluated()) }; +template using store_method_constant = std::integral_constant; +using store_as_object = store_method_constant; +using store_as_buffer = store_method_constant; + template using stored_as_numeric = std::integral_constant; template @@ -81,25 +63,39 @@ template struct is_basic_string using stored_as_string_object = std::integral_constant::value && is_basic_string>::value && std::is_rvalue_reference::value>; -template , Context>> +struct custom_store_method_checker { + template , typename Formatter = typename Context::template formatter_type> + static std::enable_if_t::value, std::tuple(), std::declval()))>> test(int); + template + static std::tuple test(...); +}; + +template +struct custom_store_method { + using transformed_type = typename std::tuple_element<1, decltype(custom_store_method_checker::template test(0))>::type; + static constexpr bool custom_store = std::tuple_element<0, decltype(custom_store_method_checker::template test(0))>::type::value && !std::is_same_v; + using store_type = std::conditional_t, store_as_object, store_as_buffer>; +}; + +template , Context>> struct stored_method_constant : std::integral_constant::value ? store_method::numeric : - stored_as_string::value ? (stored_as_string_object::value ? store_method::object : store_method::buffer) : - store_method::object> {}; + stored_as_string::value ? (stored_as_string_object::value ? store_method::object : store_method::buffer) : + // store_method::object> {}; + custom_store_method::store_type::value> {}; -struct stored_objs_dtor_base : std::false_type { +struct stored_objs_dtor_base : std::integral_constant { static void destruct(void* p) {} }; -template -struct stored_objs_dtor_gen : std::true_type { +template +struct stored_objs_dtor_gen : std::integral_constant { static void destruct(void* p) { + reinterpret_cast((reinterpret_cast(p) + offset))->~RawT(); Base::destruct(p); - reinterpret_cast((reinterpret_cast(p) + offset))->~T(); } }; - template struct stored_objs_dtor_select { using RawT = std::remove_reference_t; @@ -111,11 +107,134 @@ struct stored_objs_dtor_select { template struct stored_objs_dtor_select { using type = Base; }; - template using stored_objs_dtor = typename stored_objs_dtor_select::type; +// Collects internal details for generating index ranges [MIN, MAX) +inline namespace detail +{ + template struct index_list {}; + // Induction step + template + struct range_builder : public range_builder {}; + + // Base step + template struct range_builder { typedef index_list type; }; +} + +// Meta-function that returns a [MIN, MAX) index range +template +using index_range = typename range_builder::type; + +template +struct arg_transformer { + using arg_tuple = std::tuple; + template using arg_at = typename std::tuple_element::type; + using type_tuple = std::tuple...>; + template using type_at = typename std::tuple_element::type; + template using size_at = std::integral_constant)>; + template using objsize_at = std::conditional_t, Context>::value == store_method::object, size_at, std::integral_constant>; + template struct objsizesum_at : std::integral_constant, objsizesum_at>::value + objsize_at::value> {}; + template using objoffset_at = std::conditional_t, objsizesum_at>; +}; + +template , Context>> +using transformed_arg_type = std::conditional_t::custom_store, typename custom_store_method::transformed_type, + std::conditional_t::value && !stored_as_string_object::value, basic_string_view, std::add_const_t>&> + >; + +template +struct format_entry_constructor { + using Entry = format_entry>...>; + using Dtor = stored_objs_dtor; + using Trans = arg_transformer; + + template + static size_t construct(void* buf, const S& format_str, Args... args) { + return format_entry_constructor(buf, format_str, range(), std::forward(args)...).get_total_size(); + } + +private: + using range = typename range_builder<0, sizeof...(Args)>::type; + template + constexpr format_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { + auto p = new(buf) Entry(format_str, store(std::forward(args))...); + p->dtor_ = Dtor::value ? Dtor::destruct : nullptr; + } + + template using arg_at = typename Trans::template arg_at; + template transformed_arg_type, Context> store(typename Trans::template arg_at arg) { + using Arg = typename Trans::template arg_at; + using select_store_method = custom_store_method; + using MappedType = fmt::detail::mapped_type_constant, Context>; + if constexpr (select_store_method::custom_store == true) { + using Formatter = typename Context::template formatter_type>; + + // fmt::print("store custom arg {} @ {}\n", N, (void*)pBuffer); + return Formatter::store(pBuffer, std::forward(arg)); + } + else if constexpr (stored_as_string::value && !stored_as_string_object::value) { + // fmt::print("store string arg {} @ {}({})\n", N, (void*)pBuffer, fmt::detail::arg_mapper().map(std::forward(arg))); + return copy_string(pBuffer, fmt::detail::arg_mapper().map(std::forward(arg))); + } + else if constexpr (stored_as_numeric::value) { + return std::forward(arg); + } + else { + char* const pobjs = pEntry + sizeof(Entry); + char* const pobj = pobjs + Trans::template objoffset_at::value; + using Type = typename Trans::template type_at; + auto p = new(pobj) Type(std::forward(arg)); + return *p; + } + } + + static basic_string_view copy_string(char*& pBuffer, const typename Context::char_type* cstr) { + if constexpr (std::is_same_v) { + wchar_t* pStart = reinterpret_cast(pBuffer); + wchar_t* pEnd = wcpcpy(pStart, cstr); + pBuffer = reinterpret_cast(pEnd); + // fmt::print("copied wstring ({}) @ {}\n", basic_string_view(pStart, pEnd - pStart), (void*)pStart); + return basic_string_view(pStart, pEnd - pStart); + } + else { + char* pStart = pBuffer; + char* pEnd = stpcpy(pStart, cstr); + pBuffer = pEnd; + // fmt::print("copied string ({}) @ {}\n", basic_string_view(pStart, pEnd - pStart), (void*)pStart); + return basic_string_view(pStart, pEnd - pStart); + } + } + static basic_string_view copy_string(char*& pBuffer, basic_string_view sv) { + typename Context::char_type* pStart = reinterpret_cast(pBuffer); + size_t size = sizeof(typename Context::char_type) * sv.size(); + std::memcpy(pStart, sv.data(), size); + pBuffer += size; + return basic_string_view(pStart, sv.size()); + } + + static constexpr char* get_buffer_store(void* buf) { + char* const pentry = reinterpret_cast(buf); // entry will be constructed here + char* const pobjs = pentry + sizeof(Entry); // objects will be stored starting here + char* const pbufs = pobjs + get_obj_size(); // buffers will be stored starting here + // fmt::print("creating custom arg entry @ {}, {}, {}\n", (void*)pentry, (void*)pobjs, (void*)pbufs); + return pbufs; + } + static constexpr size_t get_obj_size() { return Trans::template objsizesum_at::value; } + constexpr size_t get_total_size() const { return pBuffer - pEntry; } + char* const pEntry; + char* pBuffer; +}; + +template > +inline size_t store_format_entry(void* buf, const S& format_str, Args&&... args) { + using Context = buffer_context; + using Constructor = format_entry_constructor; + return Constructor::construct(buf, format_str, std::forward(args)...); +} + +#if 0 template inline char* store_objs(void** stored_args, char* p) { return p; } @@ -145,24 +264,6 @@ inline char* store_bufs(void** stored_args, char* p, T t, Args... args) { return store_bufs(stored_args, p, static_cast(args)...); } - -template struct index_list {}; - -// Collects internal details for generating index ranges [MIN, MAX) -inline namespace detail -{ - // Induction step - template - struct range_builder : public range_builder {}; - - // Base step - template struct range_builder { typedef index_list type; }; -} - -// Meta-function that returns a [MIN, MAX) index range -template -using index_range = typename range_builder::type; - template inline auto make_format_entry(void* buf, const S& format_str, void* const (&stored_args)[sizeof...(Args)], index_list, Args&&... args) { using Char = char_t; @@ -171,7 +272,6 @@ inline auto make_format_entry(void* buf, const S& format_str, void* const (&stor return new(buf) entry { format_str, std::forward(stored_args[Indice] == 0 ? args : *reinterpret_cast*>(stored_args[Indice]))... }; } - template > inline size_t store_format_entry(void* buf, const S& format_str, Args&&... args) { using Context = buffer_context; @@ -188,8 +288,7 @@ inline size_t store_format_entry(void* buf, const S& format_str, Args&&... args) p->dtor_ = dtor::value ? dtor::destruct : nullptr; return pend - pentry; } - - +#endif template inline void print_format_entry(basic_format_entry& entry) { @@ -203,6 +302,20 @@ struct format_queue { }; +template > +inline auto mk_format_entry(const S& format_str, Args&&... args) -> format_entry, remove_reference_t...> { + return { format_str, args... }; +} + +template > +inline size_t write_format_entry(void* buf, const S& format_str, Args&&... args) { + using entry = format_entry, remove_reference_t...>; + auto p = new(buf) entry{ format_str, args... }; + (void) p; + return sizeof(entry); +} + + } using namespace fmtlog; @@ -212,15 +325,16 @@ using namespace fmtlog; int main() { - std::cout << sizeof(basic_format_entry) << std::endl; + fmt::print("entry header: {}\n", sizeof(basic_format_entry)); auto entry = mk_format_entry("The answer is {}\n", 42); print_format_entry(entry); char buf[2000]; - size_t size = store_format_entry(buf, "The answer is {}{}\n", std::to_string(4), 2); - std::cout << size << std::endl; - auto& entryref = entry; - print_format_entry(reinterpret_cast(buf[0])); + size_t size = store_format_entry(buf, "This {} is {}{}\n", "answer", std::to_string(4), 2); + fmt::print("entry size: {}\n", size); + auto& entryref1 = entry; + auto& entryref = reinterpret_cast(buf[0]); + print_format_entry(entryref); - std::cout << &entry << "\n" << &(entry.arg_store_) << "\n" << sizeof(entry.arg_store_) << std::endl; + std::cout << &entryref << "\n" << &(entryref.arg_store_) << "\n" << sizeof(entryref.arg_store_) << std::endl; } From 8c12b95a2040fc2d3d118654159ce99ba703960b Mon Sep 17 00:00:00 2001 From: yumeyao Date: Mon, 1 Mar 2021 10:23:59 +0800 Subject: [PATCH 04/13] adapt named args --- include/fmt/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/test.cpp b/include/fmt/test.cpp index 405299fb..631166fb 100644 --- a/include/fmt/test.cpp +++ b/include/fmt/test.cpp @@ -293,7 +293,7 @@ inline size_t store_format_entry(void* buf, const S& format_str, Args&&... args) template inline void print_format_entry(basic_format_entry& entry) { auto& full_entry = static_cast&>(entry); - vprint(entry.format_, {entry.desc_, full_entry.arg_store_.data_.args_}); ///// args_ or args + 1 depends on desc_ & detail::has_named_args_bit + vprint(entry.format_, {entry.desc_, &full_entry.arg_store_.data_.args_[entry.desc_ & fmt::detail::has_named_args_bit ? 1 : 0]}); entry.destruct(); } From 91c5d66003bf746d504a97a210a8870b7326187e Mon Sep 17 00:00:00 2001 From: yumeyao Date: Mon, 1 Mar 2021 10:31:26 +0800 Subject: [PATCH 05/13] rip to fmtlog.h --- include/fmt/fmtlog.h | 247 +++++++++++++++++++++++++++++++++ include/fmt/test.cpp | 315 +------------------------------------------ 2 files changed, 249 insertions(+), 313 deletions(-) create mode 100644 include/fmt/fmtlog.h diff --git a/include/fmt/fmtlog.h b/include/fmt/fmtlog.h new file mode 100644 index 00000000..16b94341 --- /dev/null +++ b/include/fmt/fmtlog.h @@ -0,0 +1,247 @@ +#ifndef FMT_FMTLOG_H_ +#define FMT_FMTLOG_H_ + +#include "format.h" +#include + +namespace fmtlog { +using namespace fmt; + +template struct basic_format_entry { + using char_type = typename Context::char_type; + using format_arg = basic_format_arg; + using arg_destructor = void (*)(void *p); + + basic_string_view format_; + unsigned long long desc_; + arg_destructor dtor_; + + void destruct() { + if (dtor_) { + fmt::print("calling dtor\n"); + dtor_(this); + } else { + fmt::print("dtor is empty\n"); + } + } + FMT_CONSTEXPR basic_format_entry(basic_string_view format) : format_(format), desc_(0), dtor_(0) {} +}; + +template struct format_entry : basic_format_entry { + using entry = basic_format_entry; + + format_arg_store arg_store_; + + template + FMT_CONSTEXPR format_entry(const S& format_str, const Args&... args) : entry(to_string_view(format_str)), arg_store_(args...) { + entry::desc_ = arg_store_.desc; + } +}; + +// +// A stored entry looks like: +// ----------------------------------------------------------------------- +// | basic_format_entry | arg_store | stored_objs... | stored_buffers... | +// ----------------------------------------------------------------------- +// + +enum class store_method { + numeric, // stored by libfmt as numeric value, no need for extra storage + object, // stored by libfmt as object, copy/move construct the object manually (requires properly calling destructor) + buffer, // string/hexdump, store the string/binary trivially as buffer. + constexpr_str // compile-time string, can be stored as pointer directly (requires c++20 is_constant_evaluated()) +}; + +template using store_method_constant = std::integral_constant; +using store_as_object = store_method_constant; +using store_as_buffer = store_method_constant; + +template +using stored_as_numeric = std::integral_constant; +template +using stored_as_string = std::integral_constant; +template struct is_basic_string : std::false_type {}; +template struct is_basic_string> : std::true_type {}; +template +using stored_as_string_object = std::integral_constant::value && is_basic_string>::value && std::is_rvalue_reference::value>; + +struct custom_store_method_checker { + template , typename Formatter = typename Context::template formatter_type> + static std::enable_if_t::value, std::tuple(), std::declval()))>> test(int); + template + static std::tuple test(...); +}; + +template +struct custom_store_method { + using transformed_type = typename std::tuple_element<1, decltype(custom_store_method_checker::template test(0))>::type; + static constexpr bool custom_store = std::tuple_element<0, decltype(custom_store_method_checker::template test(0))>::type::value && !std::is_same_v; + using store_type = std::conditional_t, store_as_object, store_as_buffer>; +}; + +template , Context>> +struct stored_method_constant : std::integral_constant::value ? store_method::numeric : + stored_as_string::value ? (stored_as_string_object::value ? store_method::object : store_method::buffer) : + // store_method::object> {}; + custom_store_method::store_type::value> {}; + +struct stored_objs_dtor_base : std::integral_constant { + static void destruct(void* p) {} +}; + +template +struct stored_objs_dtor_gen : std::integral_constant { + static void destruct(void* p) { + reinterpret_cast((reinterpret_cast(p) + offset))->~RawT(); + Base::destruct(p); + } +}; + +template +struct stored_objs_dtor_select { + using RawT = std::remove_reference_t; + using type = std::conditional_t::value == store_method::object, + typename stored_objs_dtor_select, Args...>::type, + typename stored_objs_dtor_select::type>; +}; + +template +struct stored_objs_dtor_select { using type = Base; }; + +template +using stored_objs_dtor = typename stored_objs_dtor_select::type; + +// Collects internal details for generating index ranges [MIN, MAX) +inline namespace detail +{ + template struct index_list {}; + + // Induction step + template + struct range_builder : public range_builder {}; + + // Base step + template struct range_builder { typedef index_list type; }; +} + +// Meta-function that returns a [MIN, MAX) index range +template +using index_range = typename range_builder::type; + +template +struct arg_transformer { + using arg_tuple = std::tuple; + template using arg_at = typename std::tuple_element::type; + using type_tuple = std::tuple...>; + template using type_at = typename std::tuple_element::type; + template using size_at = std::integral_constant)>; + template using objsize_at = std::conditional_t, Context>::value == store_method::object, size_at, std::integral_constant>; + template struct objsizesum_at : std::integral_constant, objsizesum_at>::value + objsize_at::value> {}; + template using objoffset_at = std::conditional_t, objsizesum_at>; +}; + +template , Context>> +using transformed_arg_type = std::conditional_t::custom_store, typename custom_store_method::transformed_type, + std::conditional_t::value && !stored_as_string_object::value, basic_string_view, std::add_const_t>&> + >; + +template +struct format_entry_constructor { + using Entry = format_entry>...>; + using Dtor = stored_objs_dtor; + using Trans = arg_transformer; + + template + static size_t construct(void* buf, const S& format_str, Args... args) { + return format_entry_constructor(buf, format_str, range(), std::forward(args)...).get_total_size(); + } + +private: + using range = typename range_builder<0, sizeof...(Args)>::type; + template + constexpr format_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { + auto p = new(buf) Entry(format_str, store(std::forward(args))...); + p->dtor_ = Dtor::value ? Dtor::destruct : nullptr; + } + + template using arg_at = typename Trans::template arg_at; + template transformed_arg_type, Context> store(typename Trans::template arg_at arg) { + using Arg = typename Trans::template arg_at; + using select_store_method = custom_store_method; + using MappedType = fmt::detail::mapped_type_constant, Context>; + if constexpr (select_store_method::custom_store == true) { + using Formatter = typename Context::template formatter_type>; + + // fmt::print("store custom arg {} @ {}\n", N, (void*)pBuffer); + return Formatter::store(pBuffer, std::forward(arg)); + } + else if constexpr (stored_as_string::value && !stored_as_string_object::value) { + // fmt::print("store string arg {} @ {}({})\n", N, (void*)pBuffer, fmt::detail::arg_mapper().map(std::forward(arg))); + return copy_string(pBuffer, fmt::detail::arg_mapper().map(std::forward(arg))); + } + else if constexpr (stored_as_numeric::value) { + return std::forward(arg); + } + else { + char* const pobjs = pEntry + sizeof(Entry); + char* const pobj = pobjs + Trans::template objoffset_at::value; + using Type = typename Trans::template type_at; + auto p = new(pobj) Type(std::forward(arg)); + return *p; + } + } + + static basic_string_view copy_string(char*& pBuffer, const typename Context::char_type* cstr) { + if constexpr (std::is_same_v) { + wchar_t* pStart = reinterpret_cast(pBuffer); + wchar_t* pEnd = wcpcpy(pStart, cstr); + pBuffer = reinterpret_cast(pEnd); + // fmt::print("copied wstring ({}) @ {}\n", basic_string_view(pStart, pEnd - pStart), (void*)pStart); + return basic_string_view(pStart, pEnd - pStart); + } + else { + char* pStart = pBuffer; + char* pEnd = stpcpy(pStart, cstr); + pBuffer = pEnd; + // fmt::print("copied string ({}) @ {}\n", basic_string_view(pStart, pEnd - pStart), (void*)pStart); + return basic_string_view(pStart, pEnd - pStart); + } + } + static basic_string_view copy_string(char*& pBuffer, basic_string_view sv) { + typename Context::char_type* pStart = reinterpret_cast(pBuffer); + size_t size = sizeof(typename Context::char_type) * sv.size(); + std::memcpy(pStart, sv.data(), size); + pBuffer += size; + return basic_string_view(pStart, sv.size()); + } + + static constexpr char* get_buffer_store(void* buf) { + char* const pentry = reinterpret_cast(buf); // entry will be constructed here + char* const pobjs = pentry + sizeof(Entry); // objects will be stored starting here + char* const pbufs = pobjs + get_obj_size(); // buffers will be stored starting here + // fmt::print("creating custom arg entry @ {}, {}, {}\n", (void*)pentry, (void*)pobjs, (void*)pbufs); + return pbufs; + } + static constexpr size_t get_obj_size() { return Trans::template objsizesum_at::value; } + constexpr size_t get_total_size() const { return pBuffer - pEntry; } + char* const pEntry; + char* pBuffer; +}; + +template > +inline size_t store_format_entry(void* buf, const S& format_str, Args&&... args) { + using Context = buffer_context; + using Constructor = format_entry_constructor; + return Constructor::construct(buf, format_str, std::forward(args)...); +} + +template inline +void print_format_entry(basic_format_entry& entry) { + auto& full_entry = static_cast&>(entry); + vprint(entry.format_, {entry.desc_, &full_entry.arg_store_.data_.args_[entry.desc_ & fmt::detail::has_named_args_bit ? 1 : 0]}); + entry.destruct(); +} + +} +#endif \ No newline at end of file diff --git a/include/fmt/test.cpp b/include/fmt/test.cpp index 631166fb..b7fdc05f 100644 --- a/include/fmt/test.cpp +++ b/include/fmt/test.cpp @@ -1,324 +1,13 @@ #define FMT_HEADER_ONLY -#include "format.h" -#include +#include "fmtlog.h" -namespace fmtlog { -using namespace fmt; - -template struct basic_format_entry { - using char_type = typename Context::char_type; - using format_arg = basic_format_arg; - using arg_destructor = void (*)(void *p); - - basic_string_view format_; - unsigned long long desc_; - arg_destructor dtor_; - - void destruct() { - if (dtor_) { - fmt::print("calling dtor\n"); - dtor_(this); - } else { - fmt::print("dtor is empty\n"); - } - } - FMT_CONSTEXPR basic_format_entry(basic_string_view format) : format_(format), desc_(0), dtor_(0) {} -}; - -template struct format_entry : basic_format_entry { - using entry = basic_format_entry; - - format_arg_store arg_store_; - - template - FMT_CONSTEXPR format_entry(const S& format_str, const Args&... args) : entry(to_string_view(format_str)), arg_store_(args...) { - entry::desc_ = arg_store_.desc; - } -}; - -// -// A stored entry looks like: -// ----------------------------------------------------------------------- -// | basic_format_entry | arg_store | stored_objs... | stored_buffers... | -// ----------------------------------------------------------------------- -// - -enum class store_method { - numeric, // stored by libfmt as numeric value, no need for extra storage - object, // stored by libfmt as object, copy/move construct the object manually (requires properly calling destructor) - buffer, // string/hexdump, store the string/binary trivially as buffer. - constexpr_str // compile-time string, can be stored as pointer directly (requires c++20 is_constant_evaluated()) -}; - -template using store_method_constant = std::integral_constant; -using store_as_object = store_method_constant; -using store_as_buffer = store_method_constant; - -template -using stored_as_numeric = std::integral_constant; -template -using stored_as_string = std::integral_constant; -template struct is_basic_string : std::false_type {}; -template struct is_basic_string> : std::true_type {}; -template -using stored_as_string_object = std::integral_constant::value && is_basic_string>::value && std::is_rvalue_reference::value>; - -struct custom_store_method_checker { - template , typename Formatter = typename Context::template formatter_type> - static std::enable_if_t::value, std::tuple(), std::declval()))>> test(int); - template - static std::tuple test(...); -}; - -template -struct custom_store_method { - using transformed_type = typename std::tuple_element<1, decltype(custom_store_method_checker::template test(0))>::type; - static constexpr bool custom_store = std::tuple_element<0, decltype(custom_store_method_checker::template test(0))>::type::value && !std::is_same_v; - using store_type = std::conditional_t, store_as_object, store_as_buffer>; -}; - -template , Context>> -struct stored_method_constant : std::integral_constant::value ? store_method::numeric : - stored_as_string::value ? (stored_as_string_object::value ? store_method::object : store_method::buffer) : - // store_method::object> {}; - custom_store_method::store_type::value> {}; - -struct stored_objs_dtor_base : std::integral_constant { - static void destruct(void* p) {} -}; - -template -struct stored_objs_dtor_gen : std::integral_constant { - static void destruct(void* p) { - reinterpret_cast((reinterpret_cast(p) + offset))->~RawT(); - Base::destruct(p); - } -}; - -template -struct stored_objs_dtor_select { - using RawT = std::remove_reference_t; - using type = std::conditional_t::value == store_method::object, - typename stored_objs_dtor_select, Args...>::type, - typename stored_objs_dtor_select::type>; -}; - -template -struct stored_objs_dtor_select { using type = Base; }; - -template -using stored_objs_dtor = typename stored_objs_dtor_select::type; - -// Collects internal details for generating index ranges [MIN, MAX) -inline namespace detail -{ - template struct index_list {}; - - // Induction step - template - struct range_builder : public range_builder {}; - - // Base step - template struct range_builder { typedef index_list type; }; -} - -// Meta-function that returns a [MIN, MAX) index range -template -using index_range = typename range_builder::type; - -template -struct arg_transformer { - using arg_tuple = std::tuple; - template using arg_at = typename std::tuple_element::type; - using type_tuple = std::tuple...>; - template using type_at = typename std::tuple_element::type; - template using size_at = std::integral_constant)>; - template using objsize_at = std::conditional_t, Context>::value == store_method::object, size_at, std::integral_constant>; - template struct objsizesum_at : std::integral_constant, objsizesum_at>::value + objsize_at::value> {}; - template using objoffset_at = std::conditional_t, objsizesum_at>; -}; - -template , Context>> -using transformed_arg_type = std::conditional_t::custom_store, typename custom_store_method::transformed_type, - std::conditional_t::value && !stored_as_string_object::value, basic_string_view, std::add_const_t>&> - >; - -template -struct format_entry_constructor { - using Entry = format_entry>...>; - using Dtor = stored_objs_dtor; - using Trans = arg_transformer; - - template - static size_t construct(void* buf, const S& format_str, Args... args) { - return format_entry_constructor(buf, format_str, range(), std::forward(args)...).get_total_size(); - } - -private: - using range = typename range_builder<0, sizeof...(Args)>::type; - template - constexpr format_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { - auto p = new(buf) Entry(format_str, store(std::forward(args))...); - p->dtor_ = Dtor::value ? Dtor::destruct : nullptr; - } - - template using arg_at = typename Trans::template arg_at; - template transformed_arg_type, Context> store(typename Trans::template arg_at arg) { - using Arg = typename Trans::template arg_at; - using select_store_method = custom_store_method; - using MappedType = fmt::detail::mapped_type_constant, Context>; - if constexpr (select_store_method::custom_store == true) { - using Formatter = typename Context::template formatter_type>; - - // fmt::print("store custom arg {} @ {}\n", N, (void*)pBuffer); - return Formatter::store(pBuffer, std::forward(arg)); - } - else if constexpr (stored_as_string::value && !stored_as_string_object::value) { - // fmt::print("store string arg {} @ {}({})\n", N, (void*)pBuffer, fmt::detail::arg_mapper().map(std::forward(arg))); - return copy_string(pBuffer, fmt::detail::arg_mapper().map(std::forward(arg))); - } - else if constexpr (stored_as_numeric::value) { - return std::forward(arg); - } - else { - char* const pobjs = pEntry + sizeof(Entry); - char* const pobj = pobjs + Trans::template objoffset_at::value; - using Type = typename Trans::template type_at; - auto p = new(pobj) Type(std::forward(arg)); - return *p; - } - } - - static basic_string_view copy_string(char*& pBuffer, const typename Context::char_type* cstr) { - if constexpr (std::is_same_v) { - wchar_t* pStart = reinterpret_cast(pBuffer); - wchar_t* pEnd = wcpcpy(pStart, cstr); - pBuffer = reinterpret_cast(pEnd); - // fmt::print("copied wstring ({}) @ {}\n", basic_string_view(pStart, pEnd - pStart), (void*)pStart); - return basic_string_view(pStart, pEnd - pStart); - } - else { - char* pStart = pBuffer; - char* pEnd = stpcpy(pStart, cstr); - pBuffer = pEnd; - // fmt::print("copied string ({}) @ {}\n", basic_string_view(pStart, pEnd - pStart), (void*)pStart); - return basic_string_view(pStart, pEnd - pStart); - } - } - static basic_string_view copy_string(char*& pBuffer, basic_string_view sv) { - typename Context::char_type* pStart = reinterpret_cast(pBuffer); - size_t size = sizeof(typename Context::char_type) * sv.size(); - std::memcpy(pStart, sv.data(), size); - pBuffer += size; - return basic_string_view(pStart, sv.size()); - } - - static constexpr char* get_buffer_store(void* buf) { - char* const pentry = reinterpret_cast(buf); // entry will be constructed here - char* const pobjs = pentry + sizeof(Entry); // objects will be stored starting here - char* const pbufs = pobjs + get_obj_size(); // buffers will be stored starting here - // fmt::print("creating custom arg entry @ {}, {}, {}\n", (void*)pentry, (void*)pobjs, (void*)pbufs); - return pbufs; - } - static constexpr size_t get_obj_size() { return Trans::template objsizesum_at::value; } - constexpr size_t get_total_size() const { return pBuffer - pEntry; } - char* const pEntry; - char* pBuffer; -}; - -template > -inline size_t store_format_entry(void* buf, const S& format_str, Args&&... args) { - using Context = buffer_context; - using Constructor = format_entry_constructor; - return Constructor::construct(buf, format_str, std::forward(args)...); -} - -#if 0 -template -inline char* store_objs(void** stored_args, char* p) { return p; } - -template -inline char* store_objs(void** stored_args, char* p, T t, Args... args) { - using RawT = std::remove_reference_t; - if constexpr (stored_method_constant::value == store_method::object) { - new(p) RawT(static_cast(t)); - *stored_args = p; - p += sizeof(RawT); - } - ++stored_args; - return store_objs(stored_args, p, static_cast(args)...); -} - -template -inline char* store_bufs(void** stored_args, char* p) { return p; } - -template -inline char* store_bufs(void** stored_args, char* p, T t, Args... args) { - using RawT = std::remove_reference_t; - if constexpr (stored_method_constant::value == store_method::buffer) { - *stored_args = p; - //p = copy_buffer(p, t); - } - ++stored_args; - return store_bufs(stored_args, p, static_cast(args)...); -} - -template -inline auto make_format_entry(void* buf, const S& format_str, void* const (&stored_args)[sizeof...(Args)], index_list, Args&&... args) { - using Char = char_t; - using Context = buffer_context; - using entry = format_entry...>; - return new(buf) entry { format_str, std::forward(stored_args[Indice] == 0 ? args : *reinterpret_cast*>(stored_args[Indice]))... }; -} - -template > -inline size_t store_format_entry(void* buf, const S& format_str, Args&&... args) { - using Context = buffer_context; - using entry = format_entry...>; - using dtor = stored_objs_dtor; - char* pentry = reinterpret_cast(buf); - char* pobjs = pentry + sizeof(entry); // objects will be stored starting here - void* stored_args[sizeof...(Args)] = {0}; // if an object is stored, it has a pointer other than 0 - char* pbufs = store_objs(stored_args, pobjs, std::forward(args)...); - char* pend = store_bufs(stored_args, pbufs, std::forward(args)...); - - //auto p = new(buf) entry{ format_str, args... }; - auto p = make_format_entry(buf, format_str, stored_args, index_range<0, sizeof...(Args)>(), std::forward(args)...); - p->dtor_ = dtor::value ? dtor::destruct : nullptr; - return pend - pentry; -} -#endif - -template inline -void print_format_entry(basic_format_entry& entry) { - auto& full_entry = static_cast&>(entry); - vprint(entry.format_, {entry.desc_, &full_entry.arg_store_.data_.args_[entry.desc_ & fmt::detail::has_named_args_bit ? 1 : 0]}); - entry.destruct(); -} - - -struct format_queue { - -}; +using namespace fmtlog; template > inline auto mk_format_entry(const S& format_str, Args&&... args) -> format_entry, remove_reference_t...> { return { format_str, args... }; } -template > -inline size_t write_format_entry(void* buf, const S& format_str, Args&&... args) { - using entry = format_entry, remove_reference_t...>; - auto p = new(buf) entry{ format_str, args... }; - (void) p; - return sizeof(entry); -} - - -} -using namespace fmtlog; - #include From 36d14b8ce31700f8f86df7f3a7538aa6d697a176 Mon Sep 17 00:00:00 2001 From: yumeyao Date: Tue, 2 Mar 2021 14:40:33 +0800 Subject: [PATCH 06/13] add an extra check if the user mis-declares formatter::store()'s first arguments as non-reference --- include/fmt/fmtlog.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/fmt/fmtlog.h b/include/fmt/fmtlog.h index 16b94341..2ffe6654 100644 --- a/include/fmt/fmtlog.h +++ b/include/fmt/fmtlog.h @@ -67,7 +67,9 @@ using stored_as_string_object = std::integral_constant, typename Formatter = typename Context::template formatter_type> - static std::enable_if_t::value, std::tuple(), std::declval()))>> test(int); + static std::enable_if_t::value, std::tuple(), std::declval()))>> test(double); + template , typename Formatter = typename Context::template formatter_type> + static std::enable_if_t::value, std::tuple(), std::declval()))>> test(int); template static std::tuple test(...); }; From c4a6d485846761feebc425f3917758240f0cae2d Mon Sep 17 00:00:00 2001 From: yumeyao Date: Wed, 3 Mar 2021 11:30:43 +0800 Subject: [PATCH 07/13] refactor fmtlog with libfmt compatible public apis for format_entry --- include/fmt/fmtlog.h | 106 +++++++++++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 25 deletions(-) diff --git a/include/fmt/fmtlog.h b/include/fmt/fmtlog.h index 2ffe6654..d5b2e23e 100644 --- a/include/fmt/fmtlog.h +++ b/include/fmt/fmtlog.h @@ -8,23 +8,44 @@ namespace fmtlog { using namespace fmt; template struct basic_format_entry { +protected: using char_type = typename Context::char_type; - using format_arg = basic_format_arg; + using format_arg = fmt::detail::value; using arg_destructor = void (*)(void *p); basic_string_view format_; unsigned long long desc_; arg_destructor dtor_; - void destruct() { - if (dtor_) { - fmt::print("calling dtor\n"); - dtor_(this); - } else { - fmt::print("dtor is empty\n"); - } - } FMT_CONSTEXPR basic_format_entry(basic_string_view format) : format_(format), desc_(0), dtor_(0) {} + const format_arg* get_format_args() const; + + template + using enable_out = std::enable_if_t::value, T>; +public: + void destruct() { if (dtor_) dtor_(this); } + + // libfmt public APIs + std::basic_string format() const { return fmt::vformat(format_, {desc_, get_format_args()}); } + + template + auto format_to(OutIt out) const -> enable_out { + return fmt::vformat_to(out, format_, {desc_, get_format_args()}); + } + + template + auto format_to_n(OutIt out, size_t n) const -> enable_out> { + return fmt::vformat_to(fmt::detail::truncating_iterator(out, n), format_, {desc_, get_format_args()}); + } + + size_t formatted_size() const { + fmt::detail::counting_buffer<> buf; + format_to(buf); + return buf.count(); + } + + void print(std::FILE* file = stdout) const { return fmt::vprint(file, format_, {desc_, get_format_args()}); } + }; template struct format_entry : basic_format_entry { @@ -36,8 +57,15 @@ template struct format_entry : basic_format FMT_CONSTEXPR format_entry(const S& format_str, const Args&... args) : entry(to_string_view(format_str)), arg_store_(args...) { entry::desc_ = arg_store_.desc; } + FMT_CONSTEXPR void set_dtor(typename entry::arg_destructor dtor) { this->dtor_ = dtor; } }; +template +inline const typename basic_format_entry::format_arg* basic_format_entry::get_format_args() const { + auto& entry = static_cast&>(*this); + return &entry.arg_store_.data_.args_[entry.desc_ & fmt::detail::has_named_args_bit ? 1 : 0]; +} + // // A stored entry looks like: // ----------------------------------------------------------------------- @@ -45,6 +73,8 @@ template struct format_entry : basic_format // ----------------------------------------------------------------------- // +namespace detail { + enum class store_method { numeric, // stored by libfmt as numeric value, no need for extra storage object, // stored by libfmt as object, copy/move construct the object manually (requires properly calling destructor) @@ -115,17 +145,14 @@ template using stored_objs_dtor = typename stored_objs_dtor_select::type; // Collects internal details for generating index ranges [MIN, MAX) -inline namespace detail -{ - template struct index_list {}; +template struct index_list {}; - // Induction step - template - struct range_builder : public range_builder {}; +// Induction step +template +struct range_builder : public range_builder {}; - // Base step - template struct range_builder { typedef index_list type; }; -} +// Base step +template struct range_builder { typedef index_list type; }; // Meta-function that returns a [MIN, MAX) index range template @@ -164,7 +191,7 @@ private: template constexpr format_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { auto p = new(buf) Entry(format_str, store(std::forward(args))...); - p->dtor_ = Dtor::value ? Dtor::destruct : nullptr; + p->set_dtor(Dtor::value ? Dtor::destruct : nullptr); } template using arg_at = typename Trans::template arg_at; @@ -231,19 +258,48 @@ private: char* pBuffer; }; +template +struct entry_destruct_sentry { + using Entry = basic_format_entry; + entry_destruct_sentry(Entry& entry) : entry_(entry) {} + ~entry_destruct_sentry() { entry_.destruct(); } + Entry& entry_; +}; + +} + template > inline size_t store_format_entry(void* buf, const S& format_str, Args&&... args) { using Context = buffer_context; - using Constructor = format_entry_constructor; + using Constructor = detail::format_entry_constructor; return Constructor::construct(buf, format_str, std::forward(args)...); } -template inline -void print_format_entry(basic_format_entry& entry) { - auto& full_entry = static_cast&>(entry); - vprint(entry.format_, {entry.desc_, &full_entry.arg_store_.data_.args_[entry.desc_ & fmt::detail::has_named_args_bit ? 1 : 0]}); - entry.destruct(); +template +inline auto format_entry_to_string(basic_format_entry& entry) -> decltype(entry.format()) { + detail::entry_destruct_sentry _(entry); + return entry.format(); } +template +inline auto format_entry_to(OutIt out, basic_format_entry& entry) -> decltype(entry.format_to(out)) { + detail::entry_destruct_sentry _(entry); + return entry.format_to(out); +} + +template +inline auto format_entry_to_n(OutIt out, size_t n, basic_format_entry& entry) -> decltype(entry.format_to_n(out, n)) { + detail::entry_destruct_sentry _(entry); + return entry.format_to(out, n); +} + +template +inline void print_format_entry(std::FILE* f, basic_format_entry& entry) { + detail::entry_destruct_sentry _(entry); + entry.print(f); +} + +template inline void print_format_entry(basic_format_entry& entry) { print_format_entry(stdout, entry); } + } #endif \ No newline at end of file From 014dbed50782cd88da20e775860f62184bc99487 Mon Sep 17 00:00:00 2001 From: yumeyao Date: Wed, 3 Mar 2021 14:18:16 +0800 Subject: [PATCH 08/13] Fix when arg size > 15 and named_arg is present. --- include/fmt/fmtlog.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/include/fmt/fmtlog.h b/include/fmt/fmtlog.h index d5b2e23e..b1492320 100644 --- a/include/fmt/fmtlog.h +++ b/include/fmt/fmtlog.h @@ -10,7 +10,7 @@ using namespace fmt; template struct basic_format_entry { protected: using char_type = typename Context::char_type; - using format_arg = fmt::detail::value; + using format_arg = typename basic_format_args::format_arg; using arg_destructor = void (*)(void *p); basic_string_view format_; @@ -62,8 +62,17 @@ template struct format_entry : basic_format template inline const typename basic_format_entry::format_arg* basic_format_entry::get_format_args() const { + union obfuscated_args { + const fmt::detail::value* values_; + const format_arg* args_; + intptr_t pointer_; // more efficient to add integer with size, as the compiler is able to avoid emitting branch + } args; auto& entry = static_cast&>(*this); - return &entry.arg_store_.data_.args_[entry.desc_ & fmt::detail::has_named_args_bit ? 1 : 0]; + args.values_ = entry.arg_store_.data_.args_; + if (entry.desc_ & fmt::detail::has_named_args_bit) { + args.pointer_ += (desc_ & fmt::detail::is_unpacked_bit) ? sizeof(*args.args_) : sizeof(*args.values_); + } + return args.args_; } // From c94c5595be13d4859f8947d9e823989605c5ab75 Mon Sep 17 00:00:00 2001 From: yumeyao Date: Sat, 24 Apr 2021 21:47:25 +0800 Subject: [PATCH 09/13] rename fmtlog to async --- include/fmt/{fmtlog.h => async.h} | 100 ++++++++++++++---------------- include/fmt/test.cpp | 16 ++--- 2 files changed, 54 insertions(+), 62 deletions(-) rename include/fmt/{fmtlog.h => async.h} (72%) diff --git a/include/fmt/fmtlog.h b/include/fmt/async.h similarity index 72% rename from include/fmt/fmtlog.h rename to include/fmt/async.h index b1492320..644e1fb1 100644 --- a/include/fmt/fmtlog.h +++ b/include/fmt/async.h @@ -4,10 +4,9 @@ #include "format.h" #include -namespace fmtlog { -using namespace fmt; +namespace fmt { -template struct basic_format_entry { +template struct basic_async_entry { protected: using char_type = typename Context::char_type; using format_arg = typename basic_format_args::format_arg; @@ -17,60 +16,66 @@ protected: unsigned long long desc_; arg_destructor dtor_; - FMT_CONSTEXPR basic_format_entry(basic_string_view format) : format_(format), desc_(0), dtor_(0) {} + FMT_CONSTEXPR basic_async_entry(basic_string_view format) : format_(format), desc_(0), dtor_(0) {} const format_arg* get_format_args() const; template - using enable_out = std::enable_if_t::value, T>; -public: + using enable_out = std::enable_if_t::value, T>; + void destruct() { if (dtor_) dtor_(this); } +public: + struct dtor_sentry { + dtor_sentry(basic_async_entry& entry) : entry_(entry) {} + ~dtor_sentry() { entry_.destruct(); } + basic_async_entry& entry_; + }; // libfmt public APIs - std::basic_string format() const { return fmt::vformat(format_, {desc_, get_format_args()}); } + std::basic_string format() const { return vformat(format_, {desc_, get_format_args()}); } template auto format_to(OutIt out) const -> enable_out { - return fmt::vformat_to(out, format_, {desc_, get_format_args()}); + return vformat_to(out, format_, {desc_, get_format_args()}); } template auto format_to_n(OutIt out, size_t n) const -> enable_out> { - return fmt::vformat_to(fmt::detail::truncating_iterator(out, n), format_, {desc_, get_format_args()}); + return vformat_to(detail::truncating_iterator(out, n), format_, {desc_, get_format_args()}); } size_t formatted_size() const { - fmt::detail::counting_buffer<> buf; + detail::counting_buffer<> buf; format_to(buf); return buf.count(); } - void print(std::FILE* file = stdout) const { return fmt::vprint(file, format_, {desc_, get_format_args()}); } + void print(std::FILE* file = stdout) const { return vprint(file, format_, {desc_, get_format_args()}); } }; -template struct format_entry : basic_format_entry { - using entry = basic_format_entry; +template struct async_entry : basic_async_entry { + using entry = basic_async_entry; format_arg_store arg_store_; template - FMT_CONSTEXPR format_entry(const S& format_str, const Args&... args) : entry(to_string_view(format_str)), arg_store_(args...) { + FMT_CONSTEXPR async_entry(const S& format_str, const Args&... args) : entry(to_string_view(format_str)), arg_store_(args...) { entry::desc_ = arg_store_.desc; } FMT_CONSTEXPR void set_dtor(typename entry::arg_destructor dtor) { this->dtor_ = dtor; } }; template -inline const typename basic_format_entry::format_arg* basic_format_entry::get_format_args() const { +inline const typename basic_async_entry::format_arg* basic_async_entry::get_format_args() const { union obfuscated_args { - const fmt::detail::value* values_; + const detail::value* values_; const format_arg* args_; intptr_t pointer_; // more efficient to add integer with size, as the compiler is able to avoid emitting branch } args; - auto& entry = static_cast&>(*this); + auto& entry = static_cast&>(*this); args.values_ = entry.arg_store_.data_.args_; - if (entry.desc_ & fmt::detail::has_named_args_bit) { - args.pointer_ += (desc_ & fmt::detail::is_unpacked_bit) ? sizeof(*args.args_) : sizeof(*args.values_); + if (entry.desc_ & detail::has_named_args_bit) { + args.pointer_ += (desc_ & detail::is_unpacked_bit) ? sizeof(*args.args_) : sizeof(*args.values_); } return args.args_; } @@ -78,7 +83,7 @@ inline const typename basic_format_entry::format_arg* basic_format_entr // // A stored entry looks like: // ----------------------------------------------------------------------- -// | basic_format_entry | arg_store | stored_objs... | stored_buffers... | +// | basic_async_entry | arg_store | stored_objs... | stored_buffers... | // ----------------------------------------------------------------------- // @@ -96,9 +101,9 @@ using store_as_object = store_method_constant; using store_as_buffer = store_method_constant; template -using stored_as_numeric = std::integral_constant; +using stored_as_numeric = std::integral_constant; template -using stored_as_string = std::integral_constant; +using stored_as_string = std::integral_constant; template struct is_basic_string : std::false_type {}; template struct is_basic_string> : std::true_type {}; template @@ -120,7 +125,7 @@ struct custom_store_method { using store_type = std::conditional_t, store_as_object, store_as_buffer>; }; -template , Context>> +template , Context>> struct stored_method_constant : std::integral_constant::value ? store_method::numeric : stored_as_string::value ? (stored_as_string_object::value ? store_method::object : store_method::buffer) : @@ -179,26 +184,26 @@ struct arg_transformer { template using objoffset_at = std::conditional_t, objsizesum_at>; }; -template , Context>> +template , Context>> using transformed_arg_type = std::conditional_t::custom_store, typename custom_store_method::transformed_type, std::conditional_t::value && !stored_as_string_object::value, basic_string_view, std::add_const_t>&> >; template -struct format_entry_constructor { - using Entry = format_entry>...>; +struct async_entry_constructor { + using Entry = async_entry>...>; using Dtor = stored_objs_dtor; using Trans = arg_transformer; template static size_t construct(void* buf, const S& format_str, Args... args) { - return format_entry_constructor(buf, format_str, range(), std::forward(args)...).get_total_size(); + return async_entry_constructor(buf, format_str, range(), std::forward(args)...).get_total_size(); } private: using range = typename range_builder<0, sizeof...(Args)>::type; template - constexpr format_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { + constexpr async_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { auto p = new(buf) Entry(format_str, store(std::forward(args))...); p->set_dtor(Dtor::value ? Dtor::destruct : nullptr); } @@ -207,16 +212,14 @@ private: template transformed_arg_type, Context> store(typename Trans::template arg_at arg) { using Arg = typename Trans::template arg_at; using select_store_method = custom_store_method; - using MappedType = fmt::detail::mapped_type_constant, Context>; + using MappedType = detail::mapped_type_constant, Context>; if constexpr (select_store_method::custom_store == true) { using Formatter = typename Context::template formatter_type>; - // fmt::print("store custom arg {} @ {}\n", N, (void*)pBuffer); return Formatter::store(pBuffer, std::forward(arg)); } else if constexpr (stored_as_string::value && !stored_as_string_object::value) { - // fmt::print("store string arg {} @ {}({})\n", N, (void*)pBuffer, fmt::detail::arg_mapper().map(std::forward(arg))); - return copy_string(pBuffer, fmt::detail::arg_mapper().map(std::forward(arg))); + return copy_string(pBuffer, detail::arg_mapper().map(std::forward(arg))); } else if constexpr (stored_as_numeric::value) { return std::forward(arg); @@ -235,14 +238,12 @@ private: wchar_t* pStart = reinterpret_cast(pBuffer); wchar_t* pEnd = wcpcpy(pStart, cstr); pBuffer = reinterpret_cast(pEnd); - // fmt::print("copied wstring ({}) @ {}\n", basic_string_view(pStart, pEnd - pStart), (void*)pStart); return basic_string_view(pStart, pEnd - pStart); } else { char* pStart = pBuffer; char* pEnd = stpcpy(pStart, cstr); pBuffer = pEnd; - // fmt::print("copied string ({}) @ {}\n", basic_string_view(pStart, pEnd - pStart), (void*)pStart); return basic_string_view(pStart, pEnd - pStart); } } @@ -258,7 +259,6 @@ private: char* const pentry = reinterpret_cast(buf); // entry will be constructed here char* const pobjs = pentry + sizeof(Entry); // objects will be stored starting here char* const pbufs = pobjs + get_obj_size(); // buffers will be stored starting here - // fmt::print("creating custom arg entry @ {}, {}, {}\n", (void*)pentry, (void*)pobjs, (void*)pbufs); return pbufs; } static constexpr size_t get_obj_size() { return Trans::template objsizesum_at::value; } @@ -267,48 +267,40 @@ private: char* pBuffer; }; -template -struct entry_destruct_sentry { - using Entry = basic_format_entry; - entry_destruct_sentry(Entry& entry) : entry_(entry) {} - ~entry_destruct_sentry() { entry_.destruct(); } - Entry& entry_; -}; - } template > -inline size_t store_format_entry(void* buf, const S& format_str, Args&&... args) { +inline size_t store_async_entry(void* buf, const S& format_str, Args&&... args) { using Context = buffer_context; - using Constructor = detail::format_entry_constructor; + using Constructor = detail::async_entry_constructor; return Constructor::construct(buf, format_str, std::forward(args)...); } template -inline auto format_entry_to_string(basic_format_entry& entry) -> decltype(entry.format()) { - detail::entry_destruct_sentry _(entry); +inline auto async_entry_to_string(basic_async_entry& entry) -> decltype(entry.format()) { + typename basic_async_entry::dtor_sentry _(entry); return entry.format(); } template -inline auto format_entry_to(OutIt out, basic_format_entry& entry) -> decltype(entry.format_to(out)) { - detail::entry_destruct_sentry _(entry); +inline auto async_entry_to(OutIt out, basic_async_entry& entry) -> decltype(entry.format_to(out)) { + typename basic_async_entry::dtor_sentry _(entry); return entry.format_to(out); } template -inline auto format_entry_to_n(OutIt out, size_t n, basic_format_entry& entry) -> decltype(entry.format_to_n(out, n)) { - detail::entry_destruct_sentry _(entry); +inline auto async_entry_to_n(OutIt out, size_t n, basic_async_entry& entry) -> decltype(entry.format_to_n(out, n)) { + typename basic_async_entry::dtor_sentry _(entry); return entry.format_to(out, n); } template -inline void print_format_entry(std::FILE* f, basic_format_entry& entry) { - detail::entry_destruct_sentry _(entry); +inline void print_async_entry(std::FILE* f, basic_async_entry& entry) { + typename basic_async_entry::dtor_sentry _(entry); entry.print(f); } -template inline void print_format_entry(basic_format_entry& entry) { print_format_entry(stdout, entry); } +template inline void print_async_entry(basic_async_entry& entry) { print_async_entry(stdout, entry); } } #endif \ No newline at end of file diff --git a/include/fmt/test.cpp b/include/fmt/test.cpp index b7fdc05f..133c421a 100644 --- a/include/fmt/test.cpp +++ b/include/fmt/test.cpp @@ -1,10 +1,10 @@ #define FMT_HEADER_ONLY -#include "fmtlog.h" +#include "async.h" -using namespace fmtlog; +using namespace fmt; template > -inline auto mk_format_entry(const S& format_str, Args&&... args) -> format_entry, remove_reference_t...> { +inline auto mk_async_entry(const S& format_str, Args&&... args) -> async_entry, remove_reference_t...> { return { format_str, args... }; } @@ -14,16 +14,16 @@ inline auto mk_format_entry(const S& format_str, Args&&... args) -> format_entry int main() { - fmt::print("entry header: {}\n", sizeof(basic_format_entry)); - auto entry = mk_format_entry("The answer is {}\n", 42); - print_format_entry(entry); + fmt::print("entry header: {}\n", sizeof(basic_async_entry)); + auto entry = mk_async_entry("The answer is {}\n", 42); + print_async_entry(entry); char buf[2000]; - size_t size = store_format_entry(buf, "This {} is {}{}\n", "answer", std::to_string(4), 2); + size_t size = store_async_entry(buf, "This {} is {}{}\n", "answer", std::to_string(4), 2); fmt::print("entry size: {}\n", size); auto& entryref1 = entry; auto& entryref = reinterpret_cast(buf[0]); - print_format_entry(entryref); + print_async_entry(entryref); std::cout << &entryref << "\n" << &(entryref.arg_store_) << "\n" << sizeof(entryref.arg_store_) << std::endl; } From bfd84f66beb0dccdd1b1fcf3218ab872e8ba0a04 Mon Sep 17 00:00:00 2001 From: yumeyao Date: Sat, 24 Apr 2021 22:36:09 +0800 Subject: [PATCH 10/13] clean up --- include/fmt/async.h | 91 ++++++++++++++++++++++++++------------------ include/fmt/test.cpp | 12 ++---- 2 files changed, 58 insertions(+), 45 deletions(-) diff --git a/include/fmt/async.h b/include/fmt/async.h index 644e1fb1..9938d7c7 100644 --- a/include/fmt/async.h +++ b/include/fmt/async.h @@ -1,5 +1,5 @@ -#ifndef FMT_FMTLOG_H_ -#define FMT_FMTLOG_H_ +#ifndef FMT_ASYNC_H_ +#define FMT_ASYNC_H_ #include "format.h" #include @@ -20,7 +20,7 @@ protected: const format_arg* get_format_args() const; template - using enable_out = std::enable_if_t::value, T>; + using enable_out = enable_if_t::value, T>; void destruct() { if (dtor_) dtor_(this); } public: @@ -39,7 +39,7 @@ public: } template - auto format_to_n(OutIt out, size_t n) const -> enable_out> { + auto format_to(OutIt out, size_t n) const -> enable_out> { return vformat_to(detail::truncating_iterator(out, n), format_, {desc_, get_format_args()}); } @@ -86,8 +86,10 @@ inline const typename basic_async_entry::format_arg* basic_async_entry< // | basic_async_entry | arg_store | stored_objs... | stored_buffers... | // ----------------------------------------------------------------------- // - +namespace async { namespace detail { +namespace detail = fmt::detail; +template using decay_t = typename std::decay::type; enum class store_method { numeric, // stored by libfmt as numeric value, no need for extra storage @@ -107,13 +109,13 @@ using stored_as_string = std::integral_constant struct is_basic_string : std::false_type {}; template struct is_basic_string> : std::true_type {}; template -using stored_as_string_object = std::integral_constant::value && is_basic_string>::value && std::is_rvalue_reference::value>; +using stored_as_string_object = std::integral_constant::value && is_basic_string>::value && std::is_rvalue_reference::value>; struct custom_store_method_checker { - template , typename Formatter = typename Context::template formatter_type> - static std::enable_if_t::value, std::tuple(), std::declval()))>> test(double); - template , typename Formatter = typename Context::template formatter_type> - static std::enable_if_t::value, std::tuple(), std::declval()))>> test(int); + template , typename Formatter = typename Context::template formatter_type> + static enable_if_t::value, std::tuple(), std::declval()))>> test(double); + template , typename Formatter = typename Context::template formatter_type> + static enable_if_t::value, std::tuple(), std::declval()))>> test(int); template static std::tuple test(...); }; @@ -121,11 +123,11 @@ struct custom_store_method_checker { template struct custom_store_method { using transformed_type = typename std::tuple_element<1, decltype(custom_store_method_checker::template test(0))>::type; - static constexpr bool custom_store = std::tuple_element<0, decltype(custom_store_method_checker::template test(0))>::type::value && !std::is_same_v; - using store_type = std::conditional_t, store_as_object, store_as_buffer>; + static constexpr bool custom_store = std::tuple_element<0, decltype(custom_store_method_checker::template test(0))>::type::value && !std::is_same::value; + using store_type = conditional_t::value, store_as_object, store_as_buffer>; }; -template , Context>> +template , Context>> struct stored_method_constant : std::integral_constant::value ? store_method::numeric : stored_as_string::value ? (stored_as_string_object::value ? store_method::object : store_method::buffer) : @@ -146,10 +148,10 @@ struct stored_objs_dtor_gen : std::integral_constant struct stored_objs_dtor_select { - using RawT = std::remove_reference_t; - using type = std::conditional_t::value == store_method::object, - typename stored_objs_dtor_select, Args...>::type, - typename stored_objs_dtor_select::type>; + using RawT = remove_reference_t; + using type = conditional_t::value == store_method::object, + typename stored_objs_dtor_select, Args...>::type, + typename stored_objs_dtor_select::type>; }; template @@ -176,22 +178,22 @@ template struct arg_transformer { using arg_tuple = std::tuple; template using arg_at = typename std::tuple_element::type; - using type_tuple = std::tuple...>; + using type_tuple = std::tuple...>; template using type_at = typename std::tuple_element::type; template using size_at = std::integral_constant)>; - template using objsize_at = std::conditional_t, Context>::value == store_method::object, size_at, std::integral_constant>; - template struct objsizesum_at : std::integral_constant, objsizesum_at>::value + objsize_at::value> {}; - template using objoffset_at = std::conditional_t, objsizesum_at>; + template using objsize_at = conditional_t, Context>::value == store_method::object, size_at, std::integral_constant>; + template struct objsizesum_at : std::integral_constant, objsizesum_at>::value + objsize_at::value> {}; + template using objoffset_at = conditional_t, objsizesum_at>; }; -template , Context>> -using transformed_arg_type = std::conditional_t::custom_store, typename custom_store_method::transformed_type, - std::conditional_t::value && !stored_as_string_object::value, basic_string_view, std::add_const_t>&> +template , Context>> +using transformed_arg_type = conditional_t::custom_store, typename custom_store_method::transformed_type, + conditional_t::value && !stored_as_string_object::value, basic_string_view, std::add_const_t>&> >; template struct async_entry_constructor { - using Entry = async_entry>...>; + using Entry = async_entry>...>; using Dtor = stored_objs_dtor; using Trans = arg_transformer; @@ -212,9 +214,9 @@ private: template transformed_arg_type, Context> store(typename Trans::template arg_at arg) { using Arg = typename Trans::template arg_at; using select_store_method = custom_store_method; - using MappedType = detail::mapped_type_constant, Context>; + using MappedType = detail::mapped_type_constant, Context>; if constexpr (select_store_method::custom_store == true) { - using Formatter = typename Context::template formatter_type>; + using Formatter = typename Context::template formatter_type>; return Formatter::store(pBuffer, std::forward(arg)); } @@ -234,7 +236,7 @@ private: } static basic_string_view copy_string(char*& pBuffer, const typename Context::char_type* cstr) { - if constexpr (std::is_same_v) { + if constexpr (std::is_same::value) { wchar_t* pStart = reinterpret_cast(pBuffer); wchar_t* pEnd = wcpcpy(pStart, cstr); pBuffer = reinterpret_cast(pEnd); @@ -267,40 +269,57 @@ private: char* pBuffer; }; -} +} // namespace detail template > -inline size_t store_async_entry(void* buf, const S& format_str, Args&&... args) { +inline size_t store(void* buf, const S& format_str, Args&&... args) { using Context = buffer_context; using Constructor = detail::async_entry_constructor; return Constructor::construct(buf, format_str, std::forward(args)...); } template -inline auto async_entry_to_string(basic_async_entry& entry) -> decltype(entry.format()) { +inline auto format(basic_async_entry& entry) -> decltype(entry.format()) { typename basic_async_entry::dtor_sentry _(entry); return entry.format(); } template -inline auto async_entry_to(OutIt out, basic_async_entry& entry) -> decltype(entry.format_to(out)) { +inline auto format_to(basic_async_entry& entry, OutIt out) -> decltype(entry.format_to(out)) { typename basic_async_entry::dtor_sentry _(entry); return entry.format_to(out); } template -inline auto async_entry_to_n(OutIt out, size_t n, basic_async_entry& entry) -> decltype(entry.format_to_n(out, n)) { +inline auto format_to(basic_async_entry& entry, OutIt out, size_t n) -> decltype(entry.format_to_n(out, n)) { typename basic_async_entry::dtor_sentry _(entry); return entry.format_to(out, n); } template -inline void print_async_entry(std::FILE* f, basic_async_entry& entry) { +inline void print(basic_async_entry& entry, std::FILE* f = stdout) { typename basic_async_entry::dtor_sentry _(entry); entry.print(f); } -template inline void print_async_entry(basic_async_entry& entry) { print_async_entry(stdout, entry); } +// TODO should we add wrappers like this? +// template +// inline auto format(void* entry) -> decltype(format(std::declval&>())) { +// return format(*reinterpret_cast*>(entry)); +// } +// inline auto wformat(void* entry) -> decltype(format(entry)) { return format(entry); } +} // namespace async + +template > +inline auto make_async_entry(const S& format_str, Args&&... args) -> async_entry, remove_reference_t...> { + return { format_str, args... }; } -#endif \ No newline at end of file + +template > +inline size_t store_async_entry(void* buf, const S& format_str, Args&&... args) { + return async::store(buf, format_str, std::forward(args)...); +} + +} // namespace fmt +#endif // FMT_CORE_H_ diff --git a/include/fmt/test.cpp b/include/fmt/test.cpp index 133c421a..ddfbc87a 100644 --- a/include/fmt/test.cpp +++ b/include/fmt/test.cpp @@ -3,27 +3,21 @@ using namespace fmt; -template > -inline auto mk_async_entry(const S& format_str, Args&&... args) -> async_entry, remove_reference_t...> { - return { format_str, args... }; -} - - #include int main() { fmt::print("entry header: {}\n", sizeof(basic_async_entry)); - auto entry = mk_async_entry("The answer is {}\n", 42); - print_async_entry(entry); + auto entry = make_async_entry("The answer is {}\n", 42); + async::print(entry); char buf[2000]; size_t size = store_async_entry(buf, "This {} is {}{}\n", "answer", std::to_string(4), 2); fmt::print("entry size: {}\n", size); auto& entryref1 = entry; auto& entryref = reinterpret_cast(buf[0]); - print_async_entry(entryref); + async::print(entryref); std::cout << &entryref << "\n" << &(entryref.arg_store_) << "\n" << sizeof(entryref.arg_store_) << std::endl; } From 2703cb0be6d8b4c9c550db2b9e7fe30622e0cdbe Mon Sep 17 00:00:00 2001 From: yumeyao Date: Sun, 25 Apr 2021 00:05:39 +0800 Subject: [PATCH 11/13] add test for async and fix some pre-c++17 compatibility issues in async --- include/fmt/async.h | 7 +-- include/fmt/test.cpp | 23 ---------- test/CMakeLists.txt | 1 + test/async-test.cc | 103 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 26 deletions(-) delete mode 100644 include/fmt/test.cpp create mode 100644 test/async-test.cc diff --git a/include/fmt/async.h b/include/fmt/async.h index 9938d7c7..4f81df61 100644 --- a/include/fmt/async.h +++ b/include/fmt/async.h @@ -90,6 +90,7 @@ namespace async { namespace detail { namespace detail = fmt::detail; template using decay_t = typename std::decay::type; +template using add_const_t = typename std::add_const::type; enum class store_method { numeric, // stored by libfmt as numeric value, no need for extra storage @@ -188,7 +189,7 @@ struct arg_transformer { template , Context>> using transformed_arg_type = conditional_t::custom_store, typename custom_store_method::transformed_type, - conditional_t::value && !stored_as_string_object::value, basic_string_view, std::add_const_t>&> + conditional_t::value && !stored_as_string_object::value, basic_string_view, add_const_t>&> >; template @@ -205,7 +206,7 @@ struct async_entry_constructor { private: using range = typename range_builder<0, sizeof...(Args)>::type; template - constexpr async_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { + FMT_CONSTEXPR async_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { auto p = new(buf) Entry(format_str, store(std::forward(args))...); p->set_dtor(Dtor::value ? Dtor::destruct : nullptr); } @@ -257,7 +258,7 @@ private: return basic_string_view(pStart, sv.size()); } - static constexpr char* get_buffer_store(void* buf) { + static FMT_CONSTEXPR char* get_buffer_store(void* buf) { char* const pentry = reinterpret_cast(buf); // entry will be constructed here char* const pobjs = pentry + sizeof(Entry); // objects will be stored starting here char* const pbufs = pobjs + get_obj_size(); // buffers will be stored starting here diff --git a/include/fmt/test.cpp b/include/fmt/test.cpp deleted file mode 100644 index ddfbc87a..00000000 --- a/include/fmt/test.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#define FMT_HEADER_ONLY -#include "async.h" - -using namespace fmt; - -#include - - - -int main() { - fmt::print("entry header: {}\n", sizeof(basic_async_entry)); - auto entry = make_async_entry("The answer is {}\n", 42); - async::print(entry); - - char buf[2000]; - size_t size = store_async_entry(buf, "This {} is {}{}\n", "answer", std::to_string(4), 2); - fmt::print("entry size: {}\n", size); - auto& entryref1 = entry; - auto& entryref = reinterpret_cast(buf[0]); - async::print(entryref); - - std::cout << &entryref << "\n" << &(entryref.arg_store_) << "\n" << sizeof(entryref.arg_store_) << std::endl; -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7ae5659d..2c1aa605 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -88,6 +88,7 @@ function(add_fmt_test name) endfunction() add_fmt_test(assert-test) +add_fmt_test(async-test) add_fmt_test(chrono-test) add_fmt_test(color-test) add_fmt_test(core-test) diff --git a/test/async-test.cc b/test/async-test.cc new file mode 100644 index 00000000..0ab6a8bf --- /dev/null +++ b/test/async-test.cc @@ -0,0 +1,103 @@ + +#ifdef WIN32 +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include "fmt/async.h" +#include "gtest-extra.h" + +#define TWENTY_ARGS "{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} " +static const char multiple_brackets[] = TWENTY_ARGS; +static fmt::string_view get_format_string(size_t num_args) { return fmt::string_view(&multiple_brackets[(20 - num_args)*3], num_args * 3); } + +namespace trivial_entry_test { +template +inline void make_async_entry_test(Args&&... args) { + std::string formatted = fmt::format(std::forward(args)...); + // format_arg_store containing named_args are not copy-constructible, auto&& is required. + auto&& entry = fmt::make_async_entry(std::forward(args)...); + EXPECT_EQ(formatted, fmt::async::format(entry)); +} + +template +inline void make_async_entry_test_args(Args&&... args) { + make_async_entry_test(get_format_string(sizeof...(args)), std::forward(args)...); +} + +void make_async_entry_and_alter(const std::string& s) { + std::string str = s; + std::string formatted = fmt::format("{}", str); + auto entry = fmt::make_async_entry("{}", str); + str.front() = formatted.front() = '#'; + str.back() = formatted.back() = '#'; + EXPECT_EQ(formatted, fmt::format("{}", str)); + EXPECT_EQ(formatted, fmt::async::format(entry)); +} +} + +namespace stored_entry_test { +static char buf[1 * 1024 * 1024] = {}; // 1M should be enough for this test +static fmt::basic_async_entry& entry = reinterpret_cast&>(buf); + +template +inline void make_async_entry_test(Args&&... args) { + std::string formatted = fmt::format(std::forward(args)...); + // FIXME: how to test entry_size? + size_t entry_size = fmt::store_async_entry(buf, std::forward(args)...); + EXPECT_EQ(formatted, fmt::async::format(entry)); +} + +template +inline void make_async_entry_test_args(Args&&... args) { + make_async_entry_test(get_format_string(sizeof...(args)), std::forward(args)...); +} + +void make_async_entry_and_alter(const std::string& s) { + std::string str = s; + std::string formatted = fmt::format("{}", str); + size_t entry_size = fmt::store_async_entry(buf, "{}", str); + str.front() = str.back() = '#'; + EXPECT_EQ(formatted, fmt::async::format(entry)); + formatted.front() = formatted.back() = '#'; + EXPECT_EQ(formatted, fmt::format("{}", str)); +} +} + +TEST(AsyncTest, TrivialEntry) { + using namespace trivial_entry_test; + // basic test + make_async_entry_test("The answer is {}", 42); + // index + make_async_entry_test("The answer of {2}*{1} is {0}", 42, 6, 7); + + // named args + using namespace fmt::literals; + make_async_entry_test("The answer of {}*{a} is {product}", 6, "product"_a=42, "a"_a=7); + + // long arg list (>=16, as max_packed_args = 15) + make_async_entry_test_args(short(1), (unsigned short)2, 3, 4U, 5L, 6UL, 7LL, 8ULL, 9.0F, 10.0, 11, 12, 13, 14, 15, 16, 17, 18); + make_async_entry_test(TWENTY_ARGS "{narg}", 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,0/*ignore*/,"narg"_a="bingo"); + + // this API copies only reference. + make_async_entry_and_alter("[change me]"); +} + + +TEST(AsyncTest, StoredEntry) { + using namespace stored_entry_test; + // basic test + make_async_entry_test("The answer is {}", 42); + // index + make_async_entry_test("The answer of {2}*{1} is {0}", 42, 6, 7); + + // named args + using namespace fmt::literals; + // make_async_entry_test("The answer of {}*{a} is {product}", 6, "product"_a=42, "a"_a=7); + + // long arg list (>=16, as max_packed_args = 15) + make_async_entry_test_args(short(1), (unsigned short)2, 3, 4U, 5L, 6UL, 7LL, 8ULL, 9.0F, 10.0, 11, 12, 13, 14, 15, 16, 17, 18); + // make_async_entry_test(TWENTY_ARGS "{narg}", 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,0/*ignore*/,"narg"_a="bingo"); + + // this API copies only reference. + make_async_entry_and_alter("[change me]"); +} From f6c96a9b42e844ade67a51e0d80f0a10213df6fd Mon Sep 17 00:00:00 2001 From: yumeyao Date: Sun, 25 Apr 2021 23:47:21 +0800 Subject: [PATCH 12/13] use index_sequence in constructor. generate dtor in constructor class. uppercase cleanup --- include/fmt/async.h | 123 +++++++++++++++++++++----------------------- test/async-test.cc | 25 ++++++++- 2 files changed, 84 insertions(+), 64 deletions(-) diff --git a/include/fmt/async.h b/include/fmt/async.h index 4f81df61..cd96f72c 100644 --- a/include/fmt/async.h +++ b/include/fmt/async.h @@ -91,6 +91,9 @@ namespace detail { namespace detail = fmt::detail; template using decay_t = typename std::decay::type; template using add_const_t = typename std::add_const::type; +template struct disjunction : std::false_type {}; +template struct disjunction : B1 {}; +template struct disjunction : conditional_t> {}; enum class store_method { numeric, // stored by libfmt as numeric value, no need for extra storage @@ -135,45 +138,29 @@ struct stored_method_constant : std::integral_constant {}; custom_store_method::store_type::value> {}; -struct stored_objs_dtor_base : std::integral_constant { - static void destruct(void* p) {} +// Check for integer_sequence +#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900 +template +using integer_sequence = std::integer_sequence; +template using index_sequence = std::index_sequence; +template using make_index_sequence = std::make_index_sequence; +#else +template struct integer_sequence { + using value_type = T; + + static FMT_CONSTEXPR size_t size() { return sizeof...(N); } }; -template -struct stored_objs_dtor_gen : std::integral_constant { - static void destruct(void* p) { - reinterpret_cast((reinterpret_cast(p) + offset))->~RawT(); - Base::destruct(p); - } -}; +template using index_sequence = integer_sequence; -template -struct stored_objs_dtor_select { - using RawT = remove_reference_t; - using type = conditional_t::value == store_method::object, - typename stored_objs_dtor_select, Args...>::type, - typename stored_objs_dtor_select::type>; -}; +template +struct make_integer_sequence : make_integer_sequence {}; +template +struct make_integer_sequence : integer_sequence {}; -template -struct stored_objs_dtor_select { using type = Base; }; - -template -using stored_objs_dtor = typename stored_objs_dtor_select::type; - -// Collects internal details for generating index ranges [MIN, MAX) -template struct index_list {}; - -// Induction step -template -struct range_builder : public range_builder {}; - -// Base step -template struct range_builder { typedef index_list type; }; - -// Meta-function that returns a [MIN, MAX) index range -template -using index_range = typename range_builder::type; +template +using make_index_sequence = make_integer_sequence; +#endif template struct arg_transformer { @@ -194,50 +181,60 @@ using transformed_arg_type = conditional_t::cu template struct async_entry_constructor { - using Entry = async_entry>...>; - using Dtor = stored_objs_dtor; - using Trans = arg_transformer; - template static size_t construct(void* buf, const S& format_str, Args... args) { return async_entry_constructor(buf, format_str, range(), std::forward(args)...).get_total_size(); } private: - using range = typename range_builder<0, sizeof...(Args)>::type; + using entry = async_entry>...>; + using trans = arg_transformer; + using char_type = typename Context::char_type; + using range = make_index_sequence; + template - FMT_CONSTEXPR async_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { - auto p = new(buf) Entry(format_str, store(std::forward(args))...); - p->set_dtor(Dtor::value ? Dtor::destruct : nullptr); + FMT_CONSTEXPR async_entry_constructor(void* buf, const S& format_str, index_sequence, Args... args) : pentry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { + auto p = new(buf) entry(format_str, store(std::forward(args))...); + if (disjunction...>::value) p->set_dtor(destructor::dtor); } - template using arg_at = typename Trans::template arg_at; - template transformed_arg_type, Context> store(typename Trans::template arg_at arg) { - using Arg = typename Trans::template arg_at; + template using arg_at = typename trans::template arg_at; + template using type_at = typename trans::template type_at; + + template using need_destruct = std::integral_constant, Context>::value == store_method::object && std::is_destructible>::value && !std::is_trivially_destructible>::value>; + template static bool destruct(void *p, std::true_type) { reinterpret_cast*>(p + sizeof(entry) + trans::template objoffset_at::value)->~type_at(); return true; } + template static bool destruct(void *p, std::false_type) { return false; } + template struct destructor { + static void dummy(...) {} + static void dtor(void *p) { dummy(destruct(p, need_destruct())...); } + }; + + template transformed_arg_type, Context> store(arg_at arg) { + using Arg = arg_at; using select_store_method = custom_store_method; - using MappedType = detail::mapped_type_constant, Context>; + using mapped_type = detail::mapped_type_constant, Context>; if constexpr (select_store_method::custom_store == true) { using Formatter = typename Context::template formatter_type>; return Formatter::store(pBuffer, std::forward(arg)); } - else if constexpr (stored_as_string::value && !stored_as_string_object::value) { + else if constexpr (stored_as_string::value && !stored_as_string_object::value) { return copy_string(pBuffer, detail::arg_mapper().map(std::forward(arg))); } - else if constexpr (stored_as_numeric::value) { + else if constexpr (stored_as_numeric::value) { return std::forward(arg); } else { - char* const pobjs = pEntry + sizeof(Entry); - char* const pobj = pobjs + Trans::template objoffset_at::value; - using Type = typename Trans::template type_at; + char* const pobjs = pentry + sizeof(entry); + char* const pobj = pobjs + trans::template objoffset_at::value; + using Type = type_at; auto p = new(pobj) Type(std::forward(arg)); return *p; } } - static basic_string_view copy_string(char*& pBuffer, const typename Context::char_type* cstr) { - if constexpr (std::is_same::value) { + static basic_string_view copy_string(char*& pBuffer, const char_type* cstr) { + if constexpr (std::is_same::value) { wchar_t* pStart = reinterpret_cast(pBuffer); wchar_t* pEnd = wcpcpy(pStart, cstr); pBuffer = reinterpret_cast(pEnd); @@ -250,23 +247,23 @@ private: return basic_string_view(pStart, pEnd - pStart); } } - static basic_string_view copy_string(char*& pBuffer, basic_string_view sv) { - typename Context::char_type* pStart = reinterpret_cast(pBuffer); - size_t size = sizeof(typename Context::char_type) * sv.size(); + static basic_string_view copy_string(char*& pBuffer, basic_string_view sv) { + char_type* pStart = reinterpret_cast(pBuffer); + size_t size = sizeof(char_type) * sv.size(); std::memcpy(pStart, sv.data(), size); pBuffer += size; - return basic_string_view(pStart, sv.size()); + return basic_string_view(pStart, sv.size()); } static FMT_CONSTEXPR char* get_buffer_store(void* buf) { char* const pentry = reinterpret_cast(buf); // entry will be constructed here - char* const pobjs = pentry + sizeof(Entry); // objects will be stored starting here + char* const pobjs = pentry + sizeof(entry); // objects will be stored starting here char* const pbufs = pobjs + get_obj_size(); // buffers will be stored starting here return pbufs; } - static constexpr size_t get_obj_size() { return Trans::template objsizesum_at::value; } - constexpr size_t get_total_size() const { return pBuffer - pEntry; } - char* const pEntry; + static constexpr size_t get_obj_size() { return trans::template objsizesum_at::value; } + constexpr size_t get_total_size() const { return pBuffer - pentry; } + char* const pentry; char* pBuffer; }; @@ -323,4 +320,4 @@ inline size_t store_async_entry(void* buf, const S& format_str, Args&&... args) } } // namespace fmt -#endif // FMT_CORE_H_ +#endif // FMT_ASYNC_H_ diff --git a/test/async-test.cc b/test/async-test.cc index 0ab6a8bf..50d8b9ff 100644 --- a/test/async-test.cc +++ b/test/async-test.cc @@ -61,6 +61,26 @@ void make_async_entry_and_alter(const std::string& s) { formatted.front() = formatted.back() = '#'; EXPECT_EQ(formatted, fmt::format("{}", str)); } + +void make_async_entry_dtor_test(std::string s) { + static void* constructed; + static void* destructed; + struct my_string : std::string { + my_string(const std::string& s) : std::string(s) { + constructed = this; + } + ~my_string() { destructed = this; } + }; + { + my_string str(s); + size_t entry_size = fmt::store_async_entry(buf, "{}", str); + fmt::async::format(entry); + } + EXPECT_NE(nullptr, constructed); + EXPECT_EQ(constructed, destructed); +}; + + } TEST(AsyncTest, TrivialEntry) { @@ -98,6 +118,9 @@ TEST(AsyncTest, StoredEntry) { make_async_entry_test_args(short(1), (unsigned short)2, 3, 4U, 5L, 6UL, 7LL, 8ULL, 9.0F, 10.0, 11, 12, 13, 14, 15, 16, 17, 18); // make_async_entry_test(TWENTY_ARGS "{narg}", 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,0/*ignore*/,"narg"_a="bingo"); - // this API copies only reference. + // this API copies buffer. make_async_entry_and_alter("[change me]"); + + // dtor + make_async_entry_dtor_test("custom string copied as object"); } From 0d67ce343b50fd59d7f3d92c88e6a1d1dc7bd56a Mon Sep 17 00:00:00 2001 From: yumeyao Date: Fri, 7 May 2021 15:34:04 +0800 Subject: [PATCH 13/13] comment out format_to_n() --- include/fmt/async.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/fmt/async.h b/include/fmt/async.h index cd96f72c..f0fa5686 100644 --- a/include/fmt/async.h +++ b/include/fmt/async.h @@ -38,10 +38,10 @@ public: return vformat_to(out, format_, {desc_, get_format_args()}); } - template - auto format_to(OutIt out, size_t n) const -> enable_out> { - return vformat_to(detail::truncating_iterator(out, n), format_, {desc_, get_format_args()}); - } + // template + // auto format_to(OutIt out, size_t n) const -> enable_out> { + // return vformat_to(detail::truncating_iterator(out, n), format_, {desc_, get_format_args()}); + // } size_t formatted_size() const { detail::counting_buffer<> buf; @@ -288,11 +288,11 @@ inline auto format_to(basic_async_entry& entry, OutIt out) -> decltype( return entry.format_to(out); } -template -inline auto format_to(basic_async_entry& entry, OutIt out, size_t n) -> decltype(entry.format_to_n(out, n)) { - typename basic_async_entry::dtor_sentry _(entry); - return entry.format_to(out, n); -} +// template +// inline auto format_to(basic_async_entry& entry, OutIt out, size_t n) -> decltype(entry.format_to_n(out, n)) { +// typename basic_async_entry::dtor_sentry _(entry); +// return entry.format_to(out, n); +// } template inline void print(basic_async_entry& entry, std::FILE* f = stdout) {