From 75bc471965311d46ba708c865710073ef67ee3cf Mon Sep 17 00:00:00 2001 From: Lukas Tenbrink Date: Fri, 7 Mar 2025 13:16:32 +0100 Subject: [PATCH] Add `is_zero_constructible` to denote if a type can be semi-trivially constructed with all 0 bytes. Optimize `CowData` and `LocalVector` resize for zero constructible types. Mark several compatible types as `is_zero_constructible`. --- core/math/aabb.h | 3 +++ core/math/audio_frame.h | 3 +++ core/math/face3.h | 3 +++ core/math/plane.h | 3 +++ core/math/rect2.h | 3 +++ core/math/rect2i.h | 3 +++ core/math/vector2.h | 3 +++ core/math/vector2i.h | 3 +++ core/math/vector3.h | 3 +++ core/math/vector3i.h | 3 +++ core/math/vector4.h | 3 +++ core/math/vector4i.h | 3 +++ core/os/memory.h | 17 +++++++++++++++++ core/string/ustring.h | 4 ++++ core/templates/cowdata.h | 13 +++++-------- core/templates/local_vector.h | 8 +++++--- core/templates/vector.h | 4 ++++ core/typedefs.h | 18 ++++++++++++++++++ core/variant/variant.h | 4 ++++ 19 files changed, 93 insertions(+), 11 deletions(-) diff --git a/core/math/aabb.h b/core/math/aabb.h index f0bca8b313b..4086dae9122 100644 --- a/core/math/aabb.h +++ b/core/math/aabb.h @@ -495,3 +495,6 @@ AABB AABB::quantized(real_t p_unit) const { ret.quantize(p_unit); return ret; } + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/audio_frame.h b/core/math/audio_frame.h index 4dd514c65ce..b5423c1de3a 100644 --- a/core/math/audio_frame.h +++ b/core/math/audio_frame.h @@ -168,3 +168,6 @@ _ALWAYS_INLINE_ AudioFrame operator*(int32_t p_scalar, const AudioFrame &p_frame _ALWAYS_INLINE_ AudioFrame operator*(int64_t p_scalar, const AudioFrame &p_frame) { return AudioFrame(p_frame.left * p_scalar, p_frame.right * p_scalar); } + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/face3.h b/core/math/face3.h index 0aae49fcd12..0c0084af50f 100644 --- a/core/math/face3.h +++ b/core/math/face3.h @@ -236,3 +236,6 @@ bool Face3::intersects_aabb2(const AABB &p_aabb) const { } return true; } + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/plane.h b/core/math/plane.h index abcbba6b56b..19603ae2ac1 100644 --- a/core/math/plane.h +++ b/core/math/plane.h @@ -132,3 +132,6 @@ bool Plane::operator==(const Plane &p_plane) const { bool Plane::operator!=(const Plane &p_plane) const { return normal != p_plane.normal || d != p_plane.d; } + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/rect2.h b/core/math/rect2.h index 05bb5de87b4..0e507d044fe 100644 --- a/core/math/rect2.h +++ b/core/math/rect2.h @@ -371,3 +371,6 @@ struct [[nodiscard]] Rect2 { size(p_size) { } }; + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/rect2i.h b/core/math/rect2i.h index 56a5cfec2f0..d54f0e1feec 100644 --- a/core/math/rect2i.h +++ b/core/math/rect2i.h @@ -236,3 +236,6 @@ struct [[nodiscard]] Rect2i { size(p_size) { } }; + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/vector2.h b/core/math/vector2.h index 6efb53c759e..7e57e8c1012 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -326,3 +326,6 @@ _FORCE_INLINE_ Vector2 operator*(int64_t p_scalar, const Vector2 &p_vec) { typedef Vector2 Size2; typedef Vector2 Point2; + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/vector2i.h b/core/math/vector2i.h index f4a3582e5ab..344022801b2 100644 --- a/core/math/vector2i.h +++ b/core/math/vector2i.h @@ -168,3 +168,6 @@ _FORCE_INLINE_ Vector2i operator*(double p_scalar, const Vector2i &p_vector) { typedef Vector2i Size2i; typedef Vector2i Point2i; + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/vector3.h b/core/math/vector3.h index 1467e1ec453..efe46780704 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -549,3 +549,6 @@ Vector3 Vector3::reflect(const Vector3 &p_normal) const { #endif return 2.0f * p_normal * dot(p_normal) - *this; } + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/vector3i.h b/core/math/vector3i.h index 6866d4f3d83..ffcacbde2a2 100644 --- a/core/math/vector3i.h +++ b/core/math/vector3i.h @@ -334,3 +334,6 @@ bool Vector3i::operator>=(const Vector3i &p_v) const { void Vector3i::zero() { x = y = z = 0; } + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/vector4.h b/core/math/vector4.h index 1dbe5afea5f..f896c4d1c9f 100644 --- a/core/math/vector4.h +++ b/core/math/vector4.h @@ -302,3 +302,6 @@ _FORCE_INLINE_ Vector4 operator*(int32_t p_scalar, const Vector4 &p_vec) { _FORCE_INLINE_ Vector4 operator*(int64_t p_scalar, const Vector4 &p_vec) { return p_vec * p_scalar; } + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/math/vector4i.h b/core/math/vector4i.h index 84504897ebe..3eea7770764 100644 --- a/core/math/vector4i.h +++ b/core/math/vector4i.h @@ -362,3 +362,6 @@ bool Vector4i::operator>=(const Vector4i &p_v) const { void Vector4i::zero() { x = y = z = w = 0; } + +template <> +struct is_zero_constructible : std::true_type {}; diff --git a/core/os/memory.h b/core/os/memory.h index 7afa0e9c485..c71df8ff92f 100644 --- a/core/os/memory.h +++ b/core/os/memory.h @@ -34,6 +34,7 @@ #include "core/templates/safe_refcount.h" #include +#include #include #include @@ -195,6 +196,22 @@ T *memnew_arr_template(size_t p_elements) { return (T *)mem; } +// Fast alternative to a loop constructor pattern. +template +_FORCE_INLINE_ void memnew_arr_placement(T *p_start, size_t p_num) { + if constexpr (std::is_trivially_constructible_v && !p_ensure_zero) { + // Don't need to do anything :) + } else if constexpr (is_zero_constructible_v) { + // Can optimize with memset. + memset(static_cast(p_start), 0, p_num * sizeof(T)); + } else { + // Need to use a for loop. + for (size_t i = 0; i < p_num; i++) { + memnew_placement(p_start + i, T); + } + } +} + /** * Wonders of having own array functions, you can actually check the length of * an allocated-with memnew_arr() array diff --git a/core/string/ustring.h b/core/string/ustring.h index 35442aa8e8f..49bd8a39d78 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -652,6 +652,10 @@ public: } }; +// Zero-constructing String initializes _cowdata.ptr() to nullptr and thus empty. +template <> +struct is_zero_constructible : std::true_type {}; + bool operator==(const char *p_chr, const String &p_str); bool operator==(const wchar_t *p_chr, const String &p_str); bool operator!=(const char *p_chr, const String &p_str); diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h index 60280e4dc67..98815839517 100644 --- a/core/templates/cowdata.h +++ b/core/templates/cowdata.h @@ -389,14 +389,7 @@ Error CowData::resize(Size p_size) { } // construct the newly created elements - - if constexpr (!std::is_trivially_constructible_v) { - for (Size i = *_get_size(); i < p_size; i++) { - memnew_placement(&_ptr[i], T); - } - } else if (p_ensure_zero) { - memset((void *)(_ptr + current_size), 0, (p_size - current_size) * sizeof(T)); - } + memnew_arr_placement(_ptr + current_size, p_size - current_size); *_get_size() = p_size; @@ -523,3 +516,7 @@ CowData::CowData(std::initializer_list p_init) { #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif + +// Zero-constructing CowData initializes _ptr to nullptr (and thus empty). +template +struct is_zero_constructible> : std::true_type {}; diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h index 00dcfac28b3..89d35795738 100644 --- a/core/templates/local_vector.h +++ b/core/templates/local_vector.h @@ -160,9 +160,7 @@ public: CRASH_COND_MSG(!data, "Out of memory"); } if constexpr (!std::is_trivially_constructible_v && !force_trivial) { - for (U i = count; i < p_size; i++) { - memnew_placement(&data[i], T); - } + memnew_arr_placement(data + count, p_size - count); } count = p_size; } @@ -382,3 +380,7 @@ public: template using TightLocalVector = LocalVector; + +// Zero-constructing LocalVector initializes count, capacity and data to 0 and thus empty. +template +struct is_zero_constructible> : std::true_type {}; diff --git a/core/templates/vector.h b/core/templates/vector.h index c8daf957fa2..569f0c5c2c6 100644 --- a/core/templates/vector.h +++ b/core/templates/vector.h @@ -335,3 +335,7 @@ void Vector::fill(T p_elem) { p[i] = p_elem; } } + +// Zero-constructing Vector initializes CowData.ptr() to nullptr and thus empty. +template +struct is_zero_constructible> : std::true_type {}; diff --git a/core/typedefs.h b/core/typedefs.h index 322330d48e0..62a6ab9553f 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -325,3 +325,21 @@ struct BuildIndexSequence<0, Is...> : IndexSequence {}; #define ____gd_is_defined(arg1_or_junk) __gd_take_second_arg(arg1_or_junk true, false) #define ___gd_is_defined(val) ____gd_is_defined(__GDARG_PLACEHOLDER_##val) #define GD_IS_DEFINED(x) ___gd_is_defined(x) + +// Whether the default value of a type is just all-0 bytes. +// This can most commonly be exploited by using memset for these types instead of loop-construct. +// Trivially constructible types are also zero-constructible. +template +struct is_zero_constructible : std::is_trivially_constructible {}; + +template +struct is_zero_constructible : is_zero_constructible {}; + +template +struct is_zero_constructible : is_zero_constructible {}; + +template +struct is_zero_constructible : is_zero_constructible {}; + +template +inline constexpr bool is_zero_constructible_v = is_zero_constructible::value; diff --git a/core/variant/variant.h b/core/variant/variant.h index e38f7c9b0ba..eb68eea7b66 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -1021,3 +1021,7 @@ Array::ConstIterator &Array::ConstIterator::operator--() { element_ptr--; return *this; } + +// Zero-constructing Variant results in NULL. +template <> +struct is_zero_constructible : std::true_type {};