mirror of
https://github.com/ETLCPP/etl.git
synced 2026-10-28 18:07:10 +08:00
Optimize format.h (#1588)
Dispatch only the argument types which are actually stated. For everything else, don't generate the code anymore. Detemplatize parse_format_spec(). Collapse long double and double to double on targets where they are same size. This helps prevent duplicate code generation.
This commit is contained in:
parent
155df46889
commit
c8f610a69d
@ -652,6 +652,197 @@ namespace etl
|
||||
struct is_formattable : etl::bool_constant<has_formatter_parse<T>::value && has_formatter_format<T>::value>
|
||||
{
|
||||
};
|
||||
|
||||
//*************************************************************************
|
||||
// Compile-time argument mask.
|
||||
//
|
||||
// basic_format_arg stores its value in a variant with a fixed set of
|
||||
// alternatives, and etl::visit dispatches over every one of them. That
|
||||
// would instantiate etl::formatter for all alternatives - including the
|
||||
// whole floating point formatting chain - even for a call that only ever
|
||||
// passes an int.
|
||||
//
|
||||
// The mask records which alternatives can actually occur for a given pack
|
||||
// of argument types. format_visitor turns the unreachable alternatives into
|
||||
// empty functions, so their formatters are never instantiated and the
|
||||
// linker never sees the code.
|
||||
//
|
||||
// A single table, arg_type_mask, drives both halves: it builds the mask
|
||||
// from the argument pack, and it tells the visitor which bit an alternative
|
||||
// corresponds to.
|
||||
//
|
||||
// Soundness: the mask is only ever narrowed for argument types that are
|
||||
// recognised explicitly below. Anything else - including user-defined types
|
||||
// and pointers other than void* - falls back to mask_all, which reproduces
|
||||
// the unmasked behaviour exactly.
|
||||
//*************************************************************************
|
||||
typedef unsigned int arg_mask_t;
|
||||
|
||||
static ETL_CONSTANT arg_mask_t mask_monostate = 1U << 0;
|
||||
static ETL_CONSTANT arg_mask_t mask_bool = 1U << 1;
|
||||
static ETL_CONSTANT arg_mask_t mask_char = 1U << 2;
|
||||
static ETL_CONSTANT arg_mask_t mask_int = 1U << 3;
|
||||
static ETL_CONSTANT arg_mask_t mask_uint = 1U << 4;
|
||||
static ETL_CONSTANT arg_mask_t mask_llong = 1U << 5;
|
||||
static ETL_CONSTANT arg_mask_t mask_ullong = 1U << 6;
|
||||
static ETL_CONSTANT arg_mask_t mask_float = 1U << 7;
|
||||
static ETL_CONSTANT arg_mask_t mask_double = 1U << 8;
|
||||
static ETL_CONSTANT arg_mask_t mask_ldouble = 1U << 9;
|
||||
static ETL_CONSTANT arg_mask_t mask_cstring = 1U << 10;
|
||||
static ETL_CONSTANT arg_mask_t mask_string_view = 1U << 11;
|
||||
static ETL_CONSTANT arg_mask_t mask_voidp = 1U << 12;
|
||||
static ETL_CONSTANT arg_mask_t mask_all = ~static_cast<arg_mask_t>(0);
|
||||
|
||||
// Maps a type onto the set of alternatives a value of that type can be
|
||||
// stored as. Mirrors the converting constructors of basic_format_arg.
|
||||
//
|
||||
// This serves both directions of the problem. For a user argument type it
|
||||
// gives the alternatives that argument may end up in, which is how the
|
||||
// per-call mask is built. For a variant alternative it degenerates to that
|
||||
// alternative's own bit, which is how format_visitor tests reachability.
|
||||
//
|
||||
// The primary template is deliberately conservative: an unrecognised type
|
||||
// yields mask_all, so it is never wrongly excluded.
|
||||
template <typename T, typename Enable = void>
|
||||
struct arg_type_mask
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_all;
|
||||
};
|
||||
|
||||
// Not constructible as a user argument; listed so that the table covers
|
||||
// every variant alternative. Every mask contains this bit regardless, as
|
||||
// the args_mask recursion bottoms out at mask_monostate.
|
||||
template <>
|
||||
struct arg_type_mask<etl::monostate, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_monostate;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct arg_type_mask<bool, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_bool;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<char, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_char;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<signed char, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_int;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<unsigned char, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_uint;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<short, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_int;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<unsigned short, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_uint;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<int, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_int;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<unsigned int, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_uint;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<long int, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_llong;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<unsigned long int, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_ullong;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<long long int, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_llong;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<unsigned long long int, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_ullong;
|
||||
};
|
||||
#if ETL_USING_FORMAT_FLOATING_POINT
|
||||
template <>
|
||||
struct arg_type_mask<float, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_float;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<double, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_double;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<long double, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_ldouble;
|
||||
};
|
||||
#endif
|
||||
template <>
|
||||
struct arg_type_mask<char*, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_cstring;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<const char*, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_cstring;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<etl::string_view, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_string_view;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<void*, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_voidp;
|
||||
};
|
||||
template <>
|
||||
struct arg_type_mask<const void*, void>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_voidp;
|
||||
};
|
||||
|
||||
// etl::string<N>, etl::istring and friends are stored as a string_view.
|
||||
template <typename T>
|
||||
struct arg_type_mask<T, etl::enable_if_t<etl::is_base_of<etl::ibasic_string<char>, T>::value>>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_string_view;
|
||||
};
|
||||
|
||||
// The empty alternative is always reachable, so monostate is always set.
|
||||
template <typename... Ts>
|
||||
struct args_mask;
|
||||
|
||||
template <>
|
||||
struct args_mask<>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = mask_monostate;
|
||||
};
|
||||
|
||||
template <typename T, typename... Rest>
|
||||
struct args_mask<T, Rest...>
|
||||
{
|
||||
static ETL_CONSTANT arg_mask_t value = arg_type_mask<etl::decay_t<T>>::value | args_mask<Rest...>::value;
|
||||
};
|
||||
} // namespace private_format
|
||||
|
||||
template <class Context>
|
||||
@ -1206,11 +1397,11 @@ namespace etl
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class OutputIt>
|
||||
void parse_format_spec(format_parse_context& parse_ctx, format_context<OutputIt>& fmt_context)
|
||||
// Not templated on the output iterator: the parse phase only reads the
|
||||
// parse context and writes the format spec, so a single instantiation is
|
||||
// shared by every output iterator type.
|
||||
inline void parse_format_spec(format_parse_context& parse_ctx, format_spec_t& format_spec)
|
||||
{
|
||||
auto& format_spec = fmt_context.format_spec;
|
||||
|
||||
format_spec = format_spec_t(); // reset format_spec to defaults
|
||||
|
||||
format_spec.index = parse_num(parse_ctx); // optional explicit index
|
||||
@ -1536,6 +1727,19 @@ namespace etl
|
||||
}
|
||||
|
||||
#if ETL_USING_FORMAT_FLOATING_POINT
|
||||
//***********************************
|
||||
// On many targets (e.g. ARM EABI, MSVC) 'long double' has exactly the same
|
||||
// representation as 'double'. There, formatting a long double through the
|
||||
// 'double' instantiation is value-preserving, and avoids emitting a second,
|
||||
// byte-identical copy of the whole floating point formatting chain.
|
||||
// On targets where the two differ (e.g. x86 80-bit) 'long double' is kept.
|
||||
typedef etl::conditional_t<(sizeof(long double) == sizeof(double))
|
||||
&& (etl::numeric_limits<long double>::digits == etl::numeric_limits<double>::digits)
|
||||
&& (etl::numeric_limits<long double>::max_exponent == etl::numeric_limits<double>::max_exponent)
|
||||
&& (etl::numeric_limits<long double>::min_exponent == etl::numeric_limits<double>::min_exponent),
|
||||
double, long double>
|
||||
long_double_format_type;
|
||||
|
||||
#if ETL_NOT_USING_FORMAT_LONG_DOUBLE_MATH
|
||||
//***********************************
|
||||
// Math function wrappers to handle toolchains that don't provide
|
||||
@ -2004,7 +2208,7 @@ namespace etl
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class OutputIt>
|
||||
template <class OutputIt, arg_mask_t Mask = mask_all>
|
||||
struct format_visitor
|
||||
{
|
||||
using output_iterator = OutputIt;
|
||||
@ -2019,11 +2223,12 @@ namespace etl
|
||||
template <typename T>
|
||||
void operator()(T value)
|
||||
{
|
||||
formatter<T> f;
|
||||
format_parse_context::iterator it = f.parse(parse_ctx);
|
||||
parse_ctx.advance_to(it);
|
||||
OutputIt fit = f.format(value, fmt_ctx);
|
||||
fmt_ctx.advance_to(fit);
|
||||
// T is a variant alternative here, so arg_type_mask<T> is that
|
||||
// alternative's own bit.
|
||||
// Tag dispatch rather than 'if constexpr' so that the formatter for an
|
||||
// unreachable alternative is not instantiated in C++11/14 either.
|
||||
typedef etl::conditional_t<(arg_type_mask<T>::value & Mask) != 0, etl::true_type, etl::false_type> reachable;
|
||||
dispatch(value, reachable());
|
||||
}
|
||||
|
||||
// for user-defined types routed through basic_format_arg::handle
|
||||
@ -2034,6 +2239,25 @@ namespace etl
|
||||
|
||||
format_parse_context& parse_ctx;
|
||||
format_context<OutputIt>& fmt_ctx;
|
||||
|
||||
private:
|
||||
|
||||
template <typename T>
|
||||
void dispatch(T value, etl::true_type)
|
||||
{
|
||||
formatter<T> f;
|
||||
format_parse_context::iterator it = f.parse(parse_ctx);
|
||||
parse_ctx.advance_to(it);
|
||||
OutputIt fit = f.format(value, fmt_ctx);
|
||||
fmt_ctx.advance_to(fit);
|
||||
}
|
||||
|
||||
// This alternative cannot be reached for the argument types of this call,
|
||||
// so no formatter is instantiated for it.
|
||||
template <typename T>
|
||||
void dispatch(T, etl::false_type)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <class OutputIt>
|
||||
@ -2645,7 +2869,8 @@ namespace etl
|
||||
template <class OutputIt>
|
||||
typename format_context<OutputIt>::iterator format(long double arg, format_context<OutputIt>& fmt_ctx)
|
||||
{
|
||||
return private_format::format_aligned_floating<OutputIt, long double>(arg, fmt_ctx);
|
||||
typedef private_format::long_double_format_type type;
|
||||
return private_format::format_aligned_floating<OutputIt, type>(static_cast<type>(arg), fmt_ctx);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
@ -2715,12 +2940,15 @@ namespace etl
|
||||
}
|
||||
};
|
||||
|
||||
template <class OutputIt>
|
||||
// Mask is a compile-time set of the basic_format_arg alternatives that the
|
||||
// caller can actually supply. It only removes unreachable code paths; the
|
||||
// default of mask_all formats every alternative, as before.
|
||||
template <class OutputIt, private_format::arg_mask_t Mask = private_format::mask_all>
|
||||
OutputIt vformat_to(OutputIt out, etl::string_view fmt, format_args<OutputIt> args)
|
||||
{
|
||||
format_parse_context parse_context(fmt, args.size());
|
||||
format_context<OutputIt> fmt_context(out, args);
|
||||
private_format::format_visitor<OutputIt> v(parse_context, fmt_context);
|
||||
private_format::format_visitor<OutputIt, Mask> v(parse_context, fmt_context);
|
||||
|
||||
while (parse_context.begin() != parse_context.end())
|
||||
{
|
||||
@ -2736,7 +2964,7 @@ namespace etl
|
||||
}
|
||||
else
|
||||
{
|
||||
private_format::parse_format_spec<OutputIt>(parse_context, fmt_context);
|
||||
private_format::parse_format_spec(parse_context, fmt_context.format_spec);
|
||||
|
||||
// Resolve nested replacement fields for width/precision
|
||||
private_format::resolve_nested_replacements<OutputIt>(fmt_context.format_spec, args);
|
||||
@ -2774,14 +3002,14 @@ namespace etl
|
||||
OutputIt format_to(OutputIt out, format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
auto the_args{make_format_args<OutputIt>(args...)};
|
||||
return vformat_to(etl::move(out), fmt.get(), format_args<OutputIt>(the_args));
|
||||
return vformat_to<OutputIt, private_format::args_mask<Args...>::value>(etl::move(out), fmt.get(), format_args<OutputIt>(the_args));
|
||||
}
|
||||
|
||||
template <typename OutputIt, class WrapperIt = private_format::limit_iterator<OutputIt>, class... Args>
|
||||
OutputIt format_to_n(OutputIt out, size_t n, format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
auto the_args{make_format_args<WrapperIt>(args...)};
|
||||
return vformat_to(WrapperIt(out, n), fmt.get(), format_args<WrapperIt>(the_args)).get();
|
||||
return vformat_to<WrapperIt, private_format::args_mask<Args...>::value>(WrapperIt(out, n), fmt.get(), format_args<WrapperIt>(the_args)).get();
|
||||
}
|
||||
|
||||
// non std in the following, specific to etl
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user