From 04fac0031c547f8eb9d89d2af33197d7cada70d8 Mon Sep 17 00:00:00 2001 From: Daniel Bertalan Date: Sat, 24 May 2025 09:06:42 +0200 Subject: [PATCH] AK: Add fast path for formatting common types This commit changes `AK::TypeErasedParameter` to store integer, floating-point and string types as well as characters and booleans directly instead of through a type-erased `void const*`. This is advantageous for binary size: - The compiler no longer needs to push both the parameter value and a pointer to it to the stack (which contains the argument array); storing just the value is enough. - Instead of instantiating `__format_value` for these types and taking its address (which generates a GOT entry and an ADRP+LDR pair in assembly), we just store a constant `TypeErasedParameter::Type` value. - The biggest saving comes from the fact that we used to instantiate a distinct `__format_value` for each length of string literal. For LibJS, this meant 69 different functions! We can now just store them as a `char const*`. I opted to use a manual tagged union instead of `Variant`: the code wouldn't be much shorter if we used a `Variant` since we'd have to handle converting the standard integer types to `u64`/`i64` and custom types to the type erased wrapper via explicit constructors anyway. And compile time overhead is smaller this way. This gives us some nice binary size savings (numbers are from arm64 macOS LibJS): FILE SIZE VM SIZE -------------- -------------- +52% +10.3Ki +52% +10.3Ki [__TEXT] +5.2% +768 +5.2% +768 [__DATA_CONST] -0.0% -7 -0.0% -7 __TEXT,__cstring -3.0% -144 -3.0% -144 __TEXT,__stubs -1.2% -176 -1.2% -176 Function Start Addresses -11.6% -432 -11.6% -432 Indirect Symbol Table -1.0% -448 -1.0% -448 Code Signature -18.1% -768 -18.1% -768 __DATA_CONST,__got -0.8% -6.83Ki -0.8% -6.83Ki Symbol Table -1.0% -11.2Ki -1.0% -11.2Ki String Table -0.9% -26.1Ki -0.9% -26.1Ki __TEXT,__text -7.2% -20.9Ki -9.6% -28.9Ki [__LINKEDIT] -1.0% -56.0Ki -1.1% -64.0Ki TOTAL --- AK/Format.cpp | 8 ++- AK/Format.h | 192 +++++++++++++++++++++++++++++++------------------- 2 files changed, 125 insertions(+), 75 deletions(-) diff --git a/AK/Format.cpp b/AK/Format.cpp index af8155e7e0..444b8e19fc 100644 --- a/AK/Format.cpp +++ b/AK/Format.cpp @@ -106,7 +106,13 @@ ErrorOr vformat_impl(TypeErasedFormatParams& params, FormatBuilder& builde auto& parameter = params.parameters().at(specifier.index); FormatParser argparser { specifier.flags }; - TRY(parameter.formatter(params, builder, argparser, parameter.value)); + TRY(parameter.visit([&](T const& value) { + if constexpr (IsSame) { + return value.formatter(params, builder, argparser, value.value); + } else { + return __format_value(params, builder, argparser, &value); + } + })); TRY(vformat_impl(params, builder, parser)); return {}; } diff --git a/AK/Format.h b/AK/Format.h index f476109d26..e616e156d5 100644 --- a/AK/Format.h +++ b/AK/Format.h @@ -53,95 +53,148 @@ concept Formattable = HasFormatter; constexpr size_t max_format_arguments = 256; +template +ErrorOr __format_value(TypeErasedFormatParams& params, FormatBuilder& builder, FormatParser& parser, void const* value) +{ + Formatter formatter; + + formatter.parse(params, parser); + return formatter.format(builder, *static_cast(value)); +} + struct TypeErasedParameter { enum class Type { - UInt8, - UInt16, - UInt32, - UInt64, - Int8, - Int16, - Int32, - Int64, - Custom + UnsignedInteger, + SignedInteger, + Boolean, + Character, + Float, + Double, + StringView, + CString, + CustomType }; - template - static consteval Type get_type_from_size() - { - if constexpr (is_unsigned) { - if constexpr (size == 1) - return Type::UInt8; - if constexpr (size == 2) - return Type::UInt16; - if constexpr (size == 4) - return Type::UInt32; - if constexpr (size == 8) - return Type::UInt64; - } else { - if constexpr (size == 1) - return Type::Int8; - if constexpr (size == 2) - return Type::Int16; - if constexpr (size == 4) - return Type::Int32; - if constexpr (size == 8) - return Type::Int64; - } + struct CustomType { + void const* value; + ErrorOr (*formatter)(TypeErasedFormatParams&, FormatBuilder&, FormatParser&, void const* value); + }; - VERIFY_NOT_REACHED(); + template + static bool const IsChar = IsOneOf; + + template + explicit constexpr TypeErasedParameter(U const& value) + requires(!IsChar && sizeof(U) <= sizeof(u64)) + : value { .as_unsigned = value } + , type { Type::UnsignedInteger } + { + } + + template + explicit constexpr TypeErasedParameter(I const& value) + requires(!IsChar && sizeof(I) <= sizeof(i64)) + : value { .as_signed = value } + , type { Type::SignedInteger } + { + } + + explicit constexpr TypeErasedParameter(bool const& value) + : value { .as_bool = value } + , type { Type::Boolean } + { + } + + explicit constexpr TypeErasedParameter(char const& value) + : value { .as_char = value } + , type { Type::Character } + { + } + + explicit constexpr TypeErasedParameter(float const& value) + : value { .as_float = value } + , type { Type::Float } + { + } + + explicit constexpr TypeErasedParameter(double const& value) + : value { .as_double = value } + , type { Type::Double } + { + } + + explicit constexpr TypeErasedParameter(StringView const& value) + : value { .as_string_view = value } + , type { Type::StringView } + { + } + + explicit constexpr TypeErasedParameter(char const* value) + : value { .as_c_string = value } + , type { Type::CString } + { } template - static consteval Type get_type() + explicit constexpr TypeErasedParameter(T const& value) + : value { .as_custom_type = { &value, __format_value } } + , type { Type::CustomType } { - if constexpr (IsIntegral) - return get_type_from_size>(); - else - return Type::Custom; } template constexpr auto visit(Visitor&& visitor) const { switch (type) { - case TypeErasedParameter::Type::UInt8: - return visitor(*static_cast(value)); - case TypeErasedParameter::Type::UInt16: - return visitor(*static_cast(value)); - case TypeErasedParameter::Type::UInt32: - return visitor(*static_cast(value)); - case TypeErasedParameter::Type::UInt64: - return visitor(*static_cast(value)); - case TypeErasedParameter::Type::Int8: - return visitor(*static_cast(value)); - case TypeErasedParameter::Type::Int16: - return visitor(*static_cast(value)); - case TypeErasedParameter::Type::Int32: - return visitor(*static_cast(value)); - case TypeErasedParameter::Type::Int64: - return visitor(*static_cast(value)); - default: - TODO(); + case Type::UnsignedInteger: + return visitor(value.as_unsigned); + case Type::SignedInteger: + return visitor(value.as_signed); + case Type::Boolean: + return visitor(value.as_bool); + case Type::Character: + return visitor(value.as_char); + case Type::Float: + return visitor(value.as_float); + case Type::Double: + return visitor(value.as_double); + case Type::StringView: + return visitor(value.as_string_view); + case Type::CString: + return visitor(value.as_c_string); + case Type::CustomType: + return visitor(value.as_custom_type); } + VERIFY_NOT_REACHED(); } constexpr size_t to_size() const { - return visit([](T value) { - if constexpr (sizeof(T) > sizeof(size_t)) - VERIFY(value < NumericLimits::max()); - if constexpr (IsSigned) + return visit([](T value) -> size_t { + if constexpr (IsSame) + return static_cast(value); + + if constexpr (IsSame) { VERIFY(value >= 0); - return static_cast(value); + return static_cast(value); + } + + TODO(); }); } - // FIXME: Getters and setters. - - void const* value; + union { + u64 as_unsigned; + i64 as_signed; + bool as_bool; + char as_char; + float as_float; + double as_double; + StringView as_string_view; + char const* as_c_string; + CustomType as_custom_type; + } value; Type type; - ErrorOr (*formatter)(TypeErasedFormatParams&, FormatBuilder&, FormatParser&, void const* value); }; class FormatBuilder { @@ -293,15 +346,6 @@ private: TypeErasedParameter m_parameters[0]; }; -template -ErrorOr __format_value(TypeErasedFormatParams& params, FormatBuilder& builder, FormatParser& parser, void const* value) -{ - Formatter formatter; - - formatter.parse(params, parser); - return formatter.format(builder, *static_cast(value)); -} - template class VariadicFormatParams : public TypeErasedFormatParams { public: @@ -309,7 +353,7 @@ public: explicit VariadicFormatParams(Parameters const&... parameters) : TypeErasedFormatParams(sizeof...(Parameters)) - , m_parameter_storage { TypeErasedParameter { ¶meters, TypeErasedParameter::get_type(), __format_value }... } + , m_parameter_storage { TypeErasedParameter { parameters }... } { constexpr bool any_debug_formatters = (is_debug_only_formatter>() || ...); static_assert(!any_debug_formatters || allow_debug_formatters == AllowDebugOnlyFormatters::Yes,