Browse Source

Merge pull request #105928 from Ivorforce/cowdata-reserve-capacity

Core: Add `reserve` function to `Array`, `Vector`, and `String`
Thaddeus Crews 1 week ago
parent
commit
62933b683e

+ 22 - 0
core/os/memory.h

@@ -220,6 +220,28 @@ _FORCE_INLINE_ void memnew_arr_placement(T *p_start, size_t p_num) {
 	}
 }
 
+// Convenient alternative to a loop copy pattern.
+template <typename T>
+_FORCE_INLINE_ void copy_arr_placement(T *p_dst, const T *p_src, size_t p_num) {
+	if constexpr (std::is_trivially_copyable_v<T>) {
+		memcpy((uint8_t *)p_dst, (uint8_t *)p_src, p_num * sizeof(T));
+	} else {
+		for (size_t i = 0; i < p_num; i++) {
+			memnew_placement(p_dst + i, T(p_src[i]));
+		}
+	}
+}
+
+// Convenient alternative to a loop destructor pattern.
+template <typename T>
+_FORCE_INLINE_ void destruct_arr_placement(T *p_dst, size_t p_num) {
+	if constexpr (!std::is_trivially_destructible_v<T>) {
+		for (size_t i = 0; i < p_num; i++) {
+			p_dst[i].~T();
+		}
+	}
+}
+
 /**
  * Wonders of having own array functions, you can actually check the length of
  * an allocated-with memnew_arr() array

+ 2 - 2
core/string/ustring.cpp

@@ -1253,7 +1253,7 @@ Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_
 
 	String buffer = *this;
 	while (true) {
-		int idx;
+		int idx = 0;
 		int end = findmk(p_splitters, from, &idx);
 		int spl_len = 1;
 		if (end < 0) {
@@ -1308,7 +1308,7 @@ Vector<int> String::split_ints_mk(const Vector<String> &p_splitters, bool p_allo
 	int len = length();
 
 	while (true) {
-		int idx;
+		int idx = 0;
 		int end = findmk(p_splitters, from, &idx);
 		int spl_len = 1;
 		if (end < 0) {

+ 9 - 4
core/string/ustring.h

@@ -177,10 +177,10 @@ class [[nodiscard]] CharStringT {
 public:
 	_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
 	_FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); }
-	_FORCE_INLINE_ const T *get_data() const { return ptr() ? ptr() : &_null; }
+	_FORCE_INLINE_ const T *get_data() const { return size() ? ptr() : &_null; }
 
 	_FORCE_INLINE_ int size() const { return _cowdata.size(); }
-	_FORCE_INLINE_ int length() const { return ptr() ? size() - 1 : 0; }
+	_FORCE_INLINE_ int length() const { return size() ? size() - 1 : 0; }
 	_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
 
 	_FORCE_INLINE_ operator Span<T>() const { return Span(ptr(), length()); }
@@ -297,10 +297,10 @@ public:
 
 	_FORCE_INLINE_ char32_t *ptrw() { return _cowdata.ptrw(); }
 	_FORCE_INLINE_ const char32_t *ptr() const { return _cowdata.ptr(); }
-	_FORCE_INLINE_ const char32_t *get_data() const { return ptr() ? ptr() : &_null; }
+	_FORCE_INLINE_ const char32_t *get_data() const { return size() ? ptr() : &_null; }
 
 	_FORCE_INLINE_ int size() const { return _cowdata.size(); }
-	_FORCE_INLINE_ int length() const { return ptr() ? size() - 1 : 0; }
+	_FORCE_INLINE_ int length() const { return size() ? size() - 1 : 0; }
 	_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
 
 	_FORCE_INLINE_ operator Span<char32_t>() const { return Span(ptr(), length()); }
@@ -317,6 +317,11 @@ public:
 	/// New characters are not initialized, and should be set by the caller.
 	Error resize_uninitialized(int64_t p_size) { return _cowdata.resize<false>(p_size); }
 
+	Error reserve(int64_t p_size) {
+		ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER);
+		return _cowdata.reserve(p_size);
+	}
+
 	_FORCE_INLINE_ const char32_t &operator[](int p_index) const {
 		if (unlikely(p_index == _cowdata.size())) {
 			return _null;

+ 292 - 151
core/templates/cowdata.h

@@ -40,9 +40,16 @@
 
 static_assert(std::is_trivially_destructible_v<std::atomic<uint64_t>>);
 
+// Silences false-positive warnings.
 GODOT_GCC_WARNING_PUSH
 GODOT_GCC_WARNING_IGNORE("-Wplacement-new") // Silence a false positive warning (see GH-52119).
 GODOT_GCC_WARNING_IGNORE("-Wmaybe-uninitialized") // False positive raised when using constexpr.
+GODOT_GCC_WARNING_IGNORE("-Warray-bounds")
+GODOT_GCC_WARNING_IGNORE("-Wrestrict")
+GODOT_GCC_PRAGMA(GCC diagnostic warning "-Wstringop-overflow=0") // Can't "ignore" this for some reason.
+#ifdef WINDOWS_ENABLED
+GODOT_GCC_PRAGMA(GCC diagnostic warning "-Wdangling-pointer=0") // Can't "ignore" this for some reason.
+#endif
 
 template <typename T>
 class CowData {
@@ -52,15 +59,16 @@ public:
 	static constexpr USize MAX_INT = INT64_MAX;
 
 private:
-	// Alignment:  ↓ max_align_t           ↓ USize          ↓ max_align_t
-	//             ┌────────────────────┬──┬─────────────┬──┬───────────...
-	//             │ SafeNumeric<USize> │░░│ USize       │░░│ T[]
-	//             │ ref. count         │░░│ data size   │░░│ data
-	//             └────────────────────┴──┴─────────────┴──┴───────────...
-	// Offset:     ↑ REF_COUNT_OFFSET      ↑ SIZE_OFFSET    ↑ DATA_OFFSET
+	// Alignment:  ↓ max_align_t           ↓ USize          ↓ USize            ↓ max_align_t
+	//             ┌────────────────────┬──┬───────────────┬──┬─────────────┬──┬───────────...
+	//             │ SafeNumeric<USize> │░░│ USize         │░░│ USize       │░░│ T[]
+	//             │ ref. count         │░░│ data capacity │░░│ data size   │░░│ data
+	//             └────────────────────┴──┴───────────────┴──┴─────────────┴──┴───────────...
+	// Offset:     ↑ REF_COUNT_OFFSET      ↑ CAPACITY_OFFSET  ↑ SIZE_OFFSET    ↑ DATA_OFFSET
 
 	static constexpr size_t REF_COUNT_OFFSET = 0;
-	static constexpr size_t SIZE_OFFSET = Memory::get_aligned_address(REF_COUNT_OFFSET + sizeof(SafeNumeric<USize>), alignof(USize));
+	static constexpr size_t CAPACITY_OFFSET = Memory::get_aligned_address(REF_COUNT_OFFSET + sizeof(SafeNumeric<USize>), alignof(USize));
+	static constexpr size_t SIZE_OFFSET = Memory::get_aligned_address(CAPACITY_OFFSET + sizeof(USize), alignof(USize));
 	static constexpr size_t DATA_OFFSET = Memory::get_aligned_address(SIZE_OFFSET + sizeof(USize), alignof(max_align_t));
 
 	mutable T *_ptr = nullptr;
@@ -71,22 +79,21 @@ private:
 		return (T *)(p_ptr + DATA_OFFSET);
 	}
 
+	/// Note: Assumes _ptr != nullptr.
 	_FORCE_INLINE_ SafeNumeric<USize> *_get_refcount() const {
-		if (!_ptr) {
-			return nullptr;
-		}
-
 		return (SafeNumeric<USize> *)((uint8_t *)_ptr - DATA_OFFSET + REF_COUNT_OFFSET);
 	}
 
+	/// Note: Assumes _ptr != nullptr.
 	_FORCE_INLINE_ USize *_get_size() const {
-		if (!_ptr) {
-			return nullptr;
-		}
-
 		return (USize *)((uint8_t *)_ptr - DATA_OFFSET + SIZE_OFFSET);
 	}
 
+	/// Note: Assumes _ptr != nullptr.
+	_FORCE_INLINE_ USize *_get_capacity() const {
+		return (USize *)((uint8_t *)_ptr - DATA_OFFSET + CAPACITY_OFFSET);
+	}
+
 	_FORCE_INLINE_ static USize _get_alloc_size(USize p_elements) {
 		return next_power_of_2(p_elements * (USize)sizeof(T));
 	}
@@ -121,22 +128,29 @@ private:
 	void _ref(const CowData *p_from);
 	void _ref(const CowData &p_from);
 
-	// Ensures that the backing buffer is at least p_size wide, and that this CowData instance is
-	// the only reference to it. The buffer is populated with as many element copies from the old
-	// array as possible.
-	// It is the responsibility of the caller to populate newly allocated space up to p_size.
-	Error _fork_allocate(USize p_size);
-	Error _copy_on_write() { return _fork_allocate(size()); }
-
-	// Allocates a backing array of the given capacity. The reference count is initialized to 1.
-	// It is the responsibility of the caller to populate the array and the new size property.
-	Error _alloc(USize p_alloc_size);
-
-	// Re-allocates the backing array to the given capacity. The reference count is initialized to 1.
-	// It is the responsibility of the caller to populate the array and the new size property.
-	// The caller must also make sure there are no other references to the data, as pointers may
-	// be invalidated.
-	Error _realloc(USize p_alloc_size);
+	/// Allocates a backing array of the given capacity. The reference count is initialized to 1, size to 0.
+	/// It is the responsibility of the caller to:
+	/// - Ensure _ptr == nullptr
+	/// - Ensure p_capacity > 0
+	Error _alloc(USize p_capacity);
+
+	/// Re-allocates the backing array to the given capacity.
+	/// It is the responsibility of the caller to:
+	/// - Ensure we are the only owner of the backing array
+	/// - Ensure p_capacity > 0
+	Error _realloc(USize p_capacity);
+	Error _realloc_bytes(USize p_bytes);
+
+	/// Create a new buffer and copies over elements from the old buffer.
+	/// Elements are inserted first from the start, then a gap is left uninitialized, and then elements are inserted from the back.
+	/// It is the responsibility of the caller to:
+	/// - Construct elements in the gap.
+	/// - Ensure size() >= p_size_from_start and size() >= p_size_from_back.
+	/// - Ensure p_min_capacity is enough to hold all elements.
+	[[nodiscard]] Error _copy_to_new_buffer(USize p_min_capacity, USize p_size_from_start, USize p_gap, USize p_size_from_back);
+
+	/// Ensure we are the only owners of the backing buffer.
+	[[nodiscard]] Error _copy_on_write();
 
 public:
 	void operator=(const CowData<T> &p_from) { _ref(p_from); }
@@ -151,7 +165,8 @@ public:
 	}
 
 	_FORCE_INLINE_ T *ptrw() {
-		_copy_on_write();
+		// If forking fails, we can only crash.
+		CRASH_COND(_copy_on_write());
 		return _ptr;
 	}
 
@@ -159,27 +174,25 @@ public:
 		return _ptr;
 	}
 
-	_FORCE_INLINE_ Size size() const {
-		USize *size = (USize *)_get_size();
-		if (size) {
-			return *size;
-		} else {
-			return 0;
-		}
-	}
+	_FORCE_INLINE_ Size size() const { return !_ptr ? 0 : *_get_size(); }
+	_FORCE_INLINE_ USize capacity() const { return !_ptr ? 0 : *_get_capacity(); }
+	_FORCE_INLINE_ USize refcount() const { return !_ptr ? 0 : *_get_refcount(); }
 
 	_FORCE_INLINE_ void clear() { _unref(); }
-	_FORCE_INLINE_ bool is_empty() const { return _ptr == nullptr; }
+	_FORCE_INLINE_ bool is_empty() const { return size() == 0; }
 
 	_FORCE_INLINE_ void set(Size p_index, const T &p_elem) {
 		ERR_FAIL_INDEX(p_index, size());
-		_copy_on_write();
+		// TODO Returning the error would be more appropriate.
+		CRASH_COND(_copy_on_write());
 		_ptr[p_index] = p_elem;
 	}
 
 	_FORCE_INLINE_ T &get_m(Size p_index) {
 		CRASH_BAD_INDEX(p_index, size());
-		_copy_on_write();
+		// If we fail to fork, all we can do is crash,
+		// since the caller may write incorrectly to the unforked array.
+		CRASH_COND(_copy_on_write());
 		return _ptr[p_index];
 	}
 
@@ -189,33 +202,15 @@ public:
 		return _ptr[p_index];
 	}
 
-	template <bool p_initialize = true>
+	template <bool p_init = false>
 	Error resize(Size p_size);
 
-	_FORCE_INLINE_ void remove_at(Size p_index) {
-		ERR_FAIL_INDEX(p_index, size());
-		T *p = ptrw();
-		Size len = size();
-		for (Size i = p_index; i < len - 1; i++) {
-			p[i] = std::move(p[i + 1]);
-		}
+	Error reserve(USize p_min_capacity);
 
-		resize(len - 1);
-	}
+	_FORCE_INLINE_ void remove_at(Size p_index);
 
-	Error insert(Size p_pos, const T &p_val) {
-		Size new_size = size() + 1;
-		ERR_FAIL_INDEX_V(p_pos, new_size, ERR_INVALID_PARAMETER);
-		Error err = resize(new_size);
-		ERR_FAIL_COND_V(err, err);
-		T *p = ptrw();
-		for (Size i = new_size - 1; i > p_pos; i--) {
-			p[i] = std::move(p[i - 1]);
-		}
-		p[p_pos] = p_val;
-
-		return OK;
-	}
+	Error insert(Size p_pos, const T &p_val);
+	Error push_back(const T &p_val);
 
 	_FORCE_INLINE_ operator Span<T>() const { return Span<T>(ptr(), size()); }
 	_FORCE_INLINE_ Span<T> span() const { return operator Span<T>(); }
@@ -236,8 +231,7 @@ void CowData<T>::_unref() {
 		return;
 	}
 
-	SafeNumeric<USize> *refc = _get_refcount();
-	if (refc->decrement() > 0) {
+	if (_get_refcount()->decrement() > 0) {
 		// Data is still in use elsewhere.
 		_ptr = nullptr;
 		return;
@@ -251,17 +245,16 @@ void CowData<T>::_unref() {
 	//          observe it through a reference to us. In this case, it may try to access the buffer,
 	//          which is illegal after some of the elements in it have already been destructed, and
 	//          may lead to a segmentation fault.
-	USize current_size = *_get_size();
+	USize current_size = size();
 	T *prev_ptr = _ptr;
 	_ptr = nullptr;
 
-	if constexpr (!std::is_trivially_destructible_v<T>) {
-		for (USize i = 0; i < current_size; ++i) {
-			prev_ptr[i].~T();
-		}
-	}
+	destruct_arr_placement(prev_ptr, current_size);
 
-	// Free memory.
+	// Safety check; none of the destructors should have added elements during destruction.
+	DEV_ASSERT(!_ptr);
+
+	// Free Memory.
 	Memory::free_static((uint8_t *)prev_ptr - DATA_OFFSET, false);
 
 #ifdef DEBUG_ENABLED
@@ -272,86 +265,133 @@ void CowData<T>::_unref() {
 }
 
 template <typename T>
-Error CowData<T>::_fork_allocate(USize p_size) {
-	if (p_size == 0) {
-		// Wants to clean up.
+void CowData<T>::remove_at(Size p_index) {
+	const Size prev_size = size();
+	ERR_FAIL_INDEX(p_index, prev_size);
+
+	if (prev_size == 1) {
+		// Removing the only element.
 		_unref();
-		return OK;
+		return;
 	}
 
-	USize alloc_size;
-	ERR_FAIL_COND_V(!_get_alloc_size_checked(p_size, &alloc_size), ERR_OUT_OF_MEMORY);
+	const USize new_size = prev_size - 1;
 
-	const USize prev_size = size();
+	if (_get_refcount()->get() == 1) {
+		// We're the only owner; remove in-place.
 
-	if (!_ptr) {
-		// We had no data before; just allocate a new array.
-		const Error error = _alloc(alloc_size);
-		if (error) {
-			return error;
-		}
-	} else if (_get_refcount()->get() == 1) {
-		// Resize in-place.
-		// NOTE: This case is not just an optimization, but required, as some callers depend on
-		//       `_copy_on_write()` calls not changing the pointer after the first fork
-		//       (e.g. mutable iterators).
-		if (p_size == prev_size) {
-			// We can shortcut here; we don't need to do anything.
-			return OK;
-		}
+		// Destruct the element, then relocate the rest one down.
+		_ptr[p_index].~T();
+		memmove((void *)(_ptr + p_index), (void *)(_ptr + p_index + 1), (new_size - p_index) * sizeof(T));
 
-		// Destroy extraneous elements.
-		if constexpr (!std::is_trivially_destructible_v<T>) {
-			for (USize i = prev_size; i > p_size; i--) {
-				_ptr[i - 1].~T();
-			}
+		// Shrink buffer if necessary.
+		const USize new_alloc_size = _get_alloc_size(new_size);
+		const USize prev_alloc_size = _get_alloc_size(capacity());
+		if (new_alloc_size < prev_alloc_size) {
+			Error err = _realloc_bytes(new_alloc_size);
+			CRASH_COND(err);
 		}
 
-		if (alloc_size != _get_alloc_size(prev_size)) {
-			const Error error = _realloc(alloc_size);
+		*_get_size() = new_size;
+	} else {
+		// Remove by forking.
+		Error err = _copy_to_new_buffer(new_size, p_index, 0, new_size - p_index);
+		CRASH_COND(err);
+	}
+}
+
+template <typename T>
+Error CowData<T>::insert(Size p_pos, const T &p_val) {
+	const Size new_size = size() + 1;
+	ERR_FAIL_INDEX_V(p_pos, new_size, ERR_INVALID_PARAMETER);
+
+	if (!_ptr) {
+		_alloc(1);
+		*_get_size() = 1;
+	} else if (_get_refcount()->get() == 1) {
+		if ((USize)new_size > capacity()) {
+			// Need to grow.
+			const Error error = _realloc(new_size);
 			if (error) {
-				// Out of memory; the current array is still valid though.
 				return error;
 			}
 		}
-	} else {
-		// Resize by forking.
-
-		// Create a temporary CowData to hold ownership over our _ptr.
-		// It will be used to copy elements from the old buffer over to our new buffer.
-		// At the end of the block, it will be automatically destructed by going out of scope.
-		const CowData prev_data;
-		prev_data._ptr = _ptr;
-		_ptr = nullptr;
 
-		const Error error = _alloc(alloc_size);
+		// Relocate elements one position up.
+		memmove((void *)(_ptr + p_pos + 1), (void *)(_ptr + p_pos), (size() - p_pos) * sizeof(T));
+		*_get_size() = new_size;
+	} else {
+		// Insert new element by forking.
+		// Use the max of capacity and new_size, to ensure we don't accidentally shrink after reserve.
+		const USize new_capacity = MAX(capacity(), (USize)new_size);
+		const Error error = _copy_to_new_buffer(new_capacity, p_pos, 1, size() - p_pos);
 		if (error) {
-			// On failure to allocate, just give up the old data and return.
-			// We could recover our old pointer from prev_data, but by just dropping our data, we
-			// consciously invite early failure for the case that the caller does not handle this
-			// case gracefully.
 			return error;
 		}
+	}
 
-		// Copy over elements.
-		const USize copied_element_count = MIN(prev_size, p_size);
-		if (copied_element_count > 0) {
-			if constexpr (std::is_trivially_copyable_v<T>) {
-				memcpy((uint8_t *)_ptr, (uint8_t *)prev_data._ptr, copied_element_count * sizeof(T));
-			} else {
-				for (USize i = 0; i < copied_element_count; i++) {
-					memnew_placement(&_ptr[i], T(prev_data._ptr[i]));
-				}
+	// Create the new element at the given index.
+	memnew_placement(_ptr + p_pos, T(p_val));
+
+	return OK;
+}
+
+template <typename T>
+Error CowData<T>::push_back(const T &p_val) {
+	const Size new_size = size() + 1;
+
+	if (!_ptr) {
+		// Grow by allocating.
+		_alloc(1);
+		*_get_size() = 1;
+	} else if (_get_refcount()->get() == 1) {
+		// Grow in-place.
+		if ((USize)new_size > capacity()) {
+			// Need to grow.
+			const Error error = _realloc(new_size);
+			if (error) {
+				return error;
 			}
 		}
+
+		*_get_size() = new_size;
+	} else {
+		// Grow by forking.
+		// Use the max of capacity and new_size, to ensure we don't accidentally shrink after reserve.
+		const USize new_capacity = MAX(capacity(), (USize)new_size);
+		const Error error = _copy_to_new_buffer(new_capacity, size(), 1, 0);
+		if (error) {
+			return error;
+		}
 	}
 
-	// Set our new size.
-	*_get_size() = p_size;
+	// Create the new element at the given index.
+	memnew_placement(_ptr + new_size - 1, T(p_val));
 
 	return OK;
 }
 
+template <typename T>
+Error CowData<T>::reserve(USize p_min_capacity) {
+	ERR_FAIL_COND_V_MSG(p_min_capacity < (USize)size(), ERR_INVALID_PARAMETER, "reserve() called with a capacity smaller than the current size. This is likely a mistake.");
+
+	if (p_min_capacity <= capacity()) {
+		// No need to reserve more, we already have (at least) the right size.
+		return OK;
+	}
+
+	if (!_ptr) {
+		// Initial allocation.
+		return _alloc(p_min_capacity);
+	} else if (_get_refcount()->get() == 1) {
+		// Grow in-place.
+		return _realloc(p_min_capacity);
+	} else {
+		// Grow by forking.
+		return _copy_to_new_buffer(p_min_capacity, size(), 0, 0);
+	}
+}
+
 template <typename T>
 template <bool p_initialize>
 Error CowData<T>::resize(Size p_size) {
@@ -359,41 +399,102 @@ Error CowData<T>::resize(Size p_size) {
 
 	const Size prev_size = size();
 	if (p_size == prev_size) {
+		// Caller wants to stay the same size, so we do nothing.
 		return OK;
 	}
 
-	const Error error = _fork_allocate(p_size);
-	if (error) {
-		return error;
-	}
+	if (p_size > prev_size) {
+		// Caller wants to grow.
 
-	if constexpr (p_initialize) {
-		if (p_size > prev_size) {
+		if (!_ptr) {
+			// Grow by allocating.
+			const Error error = _alloc(p_size);
+			if (error) {
+				return error;
+			}
+		} else if (_get_refcount()->get() == 1) {
+			// Grow in-place.
+			if ((USize)p_size > capacity()) {
+				const Error error = _realloc(p_size);
+				if (error) {
+					return error;
+				}
+			}
+		} else {
+			// Grow by forking.
+			const Error error = _copy_to_new_buffer(p_size, prev_size, 0, 0);
+			if (error) {
+				return error;
+			}
+		}
+
+		// Construct new elements.
+		if constexpr (p_initialize) {
 			memnew_arr_placement(_ptr + prev_size, p_size - prev_size);
 		}
+		*_get_size() = p_size;
+
+		return OK;
 	} else {
-		static_assert(std::is_trivially_destructible_v<T>);
-	}
+		// Caller wants to shrink.
 
-	return OK;
+		if (p_size == 0) {
+			_unref();
+			return OK;
+		} else if (_get_refcount()->get() == 1) {
+			// Shrink in-place.
+			destruct_arr_placement(_ptr + p_size, prev_size - p_size);
+
+			// Shrink buffer if necessary.
+			const USize new_alloc_size = _get_alloc_size(p_size);
+			const USize prev_alloc_size = _get_alloc_size(capacity());
+			if (new_alloc_size < prev_alloc_size) {
+				Error err = _realloc_bytes(new_alloc_size);
+				CRASH_COND(err);
+			}
+
+			*_get_size() = p_size;
+			return OK;
+		} else {
+			// Shrink by forking.
+			return _copy_to_new_buffer(p_size, p_size, 0, 0);
+		}
+	}
 }
 
 template <typename T>
-Error CowData<T>::_alloc(USize p_alloc_size) {
-	uint8_t *mem_new = (uint8_t *)Memory::alloc_static(p_alloc_size + DATA_OFFSET, false);
+Error CowData<T>::_alloc(USize p_min_capacity) {
+	DEV_ASSERT(!_ptr);
+
+	USize alloc_size;
+	ERR_FAIL_COND_V(!_get_alloc_size_checked(p_min_capacity, &alloc_size), ERR_OUT_OF_MEMORY);
+
+	uint8_t *mem_new = (uint8_t *)Memory::alloc_static(alloc_size + DATA_OFFSET, false);
 	ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY);
 
 	_ptr = _get_data_ptr(mem_new);
 
 	// If we alloc, we're guaranteed to be the only reference.
 	new (_get_refcount()) SafeNumeric<USize>(1);
+	*_get_size() = 0;
+	// The actual capacity is whatever we can stuff into the alloc_size.
+	*_get_capacity() = alloc_size / sizeof(T);
 
 	return OK;
 }
 
 template <typename T>
-Error CowData<T>::_realloc(USize p_alloc_size) {
-	uint8_t *mem_new = (uint8_t *)Memory::realloc_static(((uint8_t *)_ptr) - DATA_OFFSET, p_alloc_size + DATA_OFFSET, false);
+Error CowData<T>::_realloc(USize p_min_capacity) {
+	USize bytes;
+	ERR_FAIL_COND_V(!_get_alloc_size_checked(p_min_capacity, &bytes), ERR_OUT_OF_MEMORY);
+	return _realloc_bytes(bytes);
+}
+
+template <typename T>
+Error CowData<T>::_realloc_bytes(USize p_bytes) {
+	DEV_ASSERT(_ptr);
+
+	uint8_t *mem_new = (uint8_t *)Memory::realloc_static(((uint8_t *)_ptr) - DATA_OFFSET, p_bytes + DATA_OFFSET, false);
 	ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY);
 
 	_ptr = _get_data_ptr(mem_new);
@@ -401,10 +502,55 @@ Error CowData<T>::_realloc(USize p_alloc_size) {
 	// If we realloc, we're guaranteed to be the only reference.
 	// So the reference was 1 and was copied to be 1 again.
 	DEV_ASSERT(_get_refcount()->get() == 1);
+	// The size was also copied from the previous allocation.
+	// The actual capacity is whatever we can stuff into the alloc_size.
+	*_get_capacity() = p_bytes / sizeof(T);
+
+	return OK;
+}
+
+template <typename T>
+Error CowData<T>::_copy_to_new_buffer(USize p_min_capacity, USize p_size_from_start, USize p_gap, USize p_size_from_back) {
+	DEV_ASSERT(p_min_capacity >= p_size_from_start + p_size_from_back + p_gap);
+	DEV_ASSERT((USize)size() >= p_size_from_start && (USize)size() >= p_size_from_back);
+
+	// Create a temporary CowData to hold ownership over our _ptr.
+	// It will be used to copy elements from the old buffer over to our new buffer.
+	// At the end of the block, it will be automatically destructed by going out of scope.
+	const CowData prev_data;
+	prev_data._ptr = _ptr;
+	_ptr = nullptr;
+
+	const Error error = _alloc(p_min_capacity);
+	if (error) {
+		// On failure to allocate, recover the old data and return the error.
+		_ptr = prev_data._ptr;
+		prev_data._ptr = nullptr;
+		return error;
+	}
+
+	// Copy over elements.
+	copy_arr_placement(_ptr, prev_data._ptr, p_size_from_start);
+	copy_arr_placement(
+			_ptr + p_size_from_start + p_gap,
+			prev_data._ptr + prev_data.size() - p_size_from_back,
+			p_size_from_back);
+	*_get_size() = p_size_from_start + p_gap + p_size_from_back;
 
 	return OK;
 }
 
+template <typename T>
+Error CowData<T>::_copy_on_write() {
+	if (!_ptr || _get_refcount()->get() == 1) {
+		// Nothing to do.
+		return OK;
+	}
+
+	// Fork to become the only reference.
+	return _copy_to_new_buffer(capacity(), size(), 0, 0);
+}
+
 template <typename T>
 void CowData<T>::_ref(const CowData *p_from) {
 	_ref(*p_from);
@@ -429,15 +575,10 @@ void CowData<T>::_ref(const CowData &p_from) {
 
 template <typename T>
 CowData<T>::CowData(std::initializer_list<T> p_init) {
-	Error err = resize(p_init.size());
-	if (err != OK) {
-		return;
-	}
+	CRASH_COND(_alloc(p_init.size()));
 
-	Size i = 0;
-	for (const T &element : p_init) {
-		set(i++, element);
-	}
+	copy_arr_placement(_ptr, p_init.begin(), p_init.size());
+	*_get_size() = p_init.size();
 }
 
 GODOT_GCC_WARNING_POP

+ 1 - 7
core/templates/local_vector.h

@@ -323,13 +323,7 @@ public:
 		ret.resize(count);
 		T *w = ret.ptrw();
 		if (w) {
-			if constexpr (std::is_trivially_copyable_v<T>) {
-				memcpy(w, data, sizeof(T) * count);
-			} else {
-				for (U i = 0; i < count; i++) {
-					w[i] = data[i];
-				}
-			}
+			copy_arr_placement(w, data, count);
 		}
 		return ret;
 	}

+ 8 - 10
core/templates/vector.h

@@ -64,13 +64,14 @@ class Vector {
 public:
 	VectorWriteProxy<T> write;
 	typedef typename CowData<T>::Size Size;
+	typedef typename CowData<T>::USize USize;
 
 private:
 	CowData<T> _cowdata;
 
 public:
 	// Must take a copy instead of a reference (see GH-31736).
-	bool push_back(T p_elem);
+	_FORCE_INLINE_ bool push_back(T p_elem) { return _cowdata.push_back(p_elem); }
 	_FORCE_INLINE_ bool append(const T &p_elem) { return push_back(p_elem); } //alias
 	void fill(T p_elem);
 
@@ -89,6 +90,7 @@ public:
 	_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
 	_FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); }
 	_FORCE_INLINE_ Size size() const { return _cowdata.size(); }
+	_FORCE_INLINE_ USize capacity() const { return _cowdata.capacity(); }
 
 	_FORCE_INLINE_ operator Span<T>() const { return _cowdata.span(); }
 	_FORCE_INLINE_ Span<T> span() const { return _cowdata.span(); }
@@ -119,6 +121,11 @@ public:
 		return _cowdata.template resize<false>(p_size);
 	}
 
+	Error reserve(Size p_size) {
+		ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER);
+		return _cowdata.reserve(p_size);
+	}
+
 	_FORCE_INLINE_ const T &operator[](Size p_index) const { return _cowdata.get(p_index); }
 	// Must take a copy instead of a reference (see GH-31736).
 	Error insert(Size p_pos, T p_val) { return _cowdata.insert(p_pos, p_val); }
@@ -351,15 +358,6 @@ void Vector<T>::append_array(Vector<T> p_other) {
 	}
 }
 
-template <typename T>
-bool Vector<T>::push_back(T p_elem) {
-	Error err = resize(size() + 1);
-	ERR_FAIL_COND_V(err, true);
-	set(size() - 1, p_elem);
-
-	return false;
-}
-
 template <typename T>
 void Vector<T>::fill(T p_elem) {
 	T *p = ptrw();

+ 5 - 0
core/variant/array.cpp

@@ -313,6 +313,11 @@ Error Array::resize(int p_new_size) {
 	return err;
 }
 
+Error Array::reserve(int p_new_size) {
+	ERR_FAIL_COND_V_MSG(_p->read_only, ERR_LOCKED, "Array is in read-only state.");
+	return _p->array.reserve(p_new_size);
+}
+
 Error Array::insert(int p_pos, const Variant &p_value) {
 	ERR_FAIL_COND_V_MSG(_p->read_only, ERR_LOCKED, "Array is in read-only state.");
 	Variant value = p_value;

+ 1 - 0
core/variant/array.h

@@ -135,6 +135,7 @@ public:
 	_FORCE_INLINE_ void append(const Variant &p_value) { push_back(p_value); } //for python compatibility
 	void append_array(const Array &p_array);
 	Error resize(int p_new_size);
+	Error reserve(int p_new_size);
 
 	Error insert(int p_pos, const Variant &p_value);
 	void remove_at(int p_pos);

+ 12 - 24
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs

@@ -538,7 +538,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != IntPtr.Zero ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_string_size(in this);
         }
     }
 
@@ -764,12 +764,6 @@ namespace Godot.NativeInterop
 
             // There are more fields here, but we don't care as we never store this in C#
 
-            public readonly int Size
-            {
-                [MethodImpl(MethodImplOptions.AggressiveInlining)]
-                get => _arrayVector.Size;
-            }
-
             public readonly unsafe bool IsReadOnly
             {
                 [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -782,12 +776,6 @@ namespace Godot.NativeInterop
         {
             private IntPtr _writeProxy;
             public unsafe godot_variant* _ptr;
-
-            public readonly unsafe int Size
-            {
-                [MethodImpl(MethodImplOptions.AggressiveInlining)]
-                get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
-            }
         }
 
         public readonly unsafe godot_variant* Elements
@@ -805,7 +793,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _p != null ? _p->Size : 0;
+            get => (int)NativeFuncs.godotsharp_array_size(in this);
         }
 
         public readonly unsafe bool IsReadOnly
@@ -936,7 +924,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_packed_byte_array_size(in this);
         }
     }
 
@@ -967,7 +955,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_packed_int32_array_size(in this);
         }
     }
 
@@ -998,7 +986,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_packed_int64_array_size(in this);
         }
     }
 
@@ -1029,7 +1017,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_packed_float32_array_size(in this);
         }
     }
 
@@ -1060,7 +1048,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_packed_float64_array_size(in this);
         }
     }
 
@@ -1091,7 +1079,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_packed_string_array_size(in this);
         }
     }
 
@@ -1122,7 +1110,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_packed_vector2_array_size(in this);
         }
     }
 
@@ -1153,7 +1141,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_packed_vector3_array_size(in this);
         }
     }
 
@@ -1185,7 +1173,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_packed_vector4_array_size(in this);
         }
     }
 
@@ -1217,7 +1205,7 @@ namespace Godot.NativeInterop
         public readonly unsafe int Size
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
+            get => (int)NativeFuncs.godotsharp_packed_color_array_size(in this);
         }
     }
 

+ 26 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs

@@ -606,5 +606,31 @@ namespace Godot.NativeInterop
         // Object
 
         public static partial void godotsharp_object_to_string(IntPtr ptr, out godot_string r_str);
+
+        // Vector
+
+        public static partial long godotsharp_string_size(in godot_string p_self);
+
+        public static partial long godotsharp_packed_byte_array_size(in godot_packed_byte_array p_self);
+
+        public static partial long godotsharp_packed_int32_array_size(in godot_packed_int32_array p_self);
+
+        public static partial long godotsharp_packed_int64_array_size(in godot_packed_int64_array p_self);
+
+        public static partial long godotsharp_packed_float32_array_size(in godot_packed_float32_array p_self);
+
+        public static partial long godotsharp_packed_float64_array_size(in godot_packed_float64_array p_self);
+
+        public static partial long godotsharp_packed_string_array_size(in godot_packed_string_array p_self);
+
+        public static partial long godotsharp_packed_vector2_array_size(in godot_packed_vector2_array p_self);
+
+        public static partial long godotsharp_packed_vector3_array_size(in godot_packed_vector3_array p_self);
+
+        public static partial long godotsharp_packed_vector4_array_size(in godot_packed_vector4_array p_self);
+
+        public static partial long godotsharp_packed_color_array_size(in godot_packed_color_array p_self);
+
+        public static partial long godotsharp_array_size(in godot_array p_self);
     }
 }

+ 60 - 0
modules/mono/glue/runtime_interop.cpp

@@ -1568,6 +1568,54 @@ void godotsharp_object_to_string(Object *p_ptr, godot_string *r_str) {
 }
 #endif
 
+int64_t godotsharp_string_size(const String *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_packed_byte_array_size(const PackedByteArray *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_packed_int32_array_size(const PackedInt32Array *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_packed_int64_array_size(const PackedInt64Array *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_packed_float32_array_size(const PackedFloat32Array *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_packed_float64_array_size(const PackedFloat64Array *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_packed_string_array_size(const PackedStringArray *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_packed_vector2_array_size(const PackedVector2Array *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_packed_vector3_array_size(const PackedVector3Array *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_packed_vector4_array_size(const PackedVector4Array *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_packed_color_array_size(const PackedColorArray *p_self) {
+	return p_self->size();
+}
+
+int64_t godotsharp_array_size(const Array *p_self) {
+	return p_self->size();
+}
+
 // The order in this array must match the declaration order of
 // the methods in 'GodotSharp/Core/NativeInterop/NativeFuncs.cs'.
 static const void *unmanaged_callbacks[]{
@@ -1796,6 +1844,18 @@ static const void *unmanaged_callbacks[]{
 	(void *)godotsharp_var_to_str,
 	(void *)godotsharp_err_print_error,
 	(void *)godotsharp_object_to_string,
+	(void *)godotsharp_string_size,
+	(void *)godotsharp_packed_byte_array_size,
+	(void *)godotsharp_packed_int32_array_size,
+	(void *)godotsharp_packed_int64_array_size,
+	(void *)godotsharp_packed_float32_array_size,
+	(void *)godotsharp_packed_float64_array_size,
+	(void *)godotsharp_packed_string_array_size,
+	(void *)godotsharp_packed_vector2_array_size,
+	(void *)godotsharp_packed_vector3_array_size,
+	(void *)godotsharp_packed_vector4_array_size,
+	(void *)godotsharp_packed_color_array_size,
+	(void *)godotsharp_array_size,
 };
 
 const void **godotsharp::get_runtime_interop_funcs(int32_t &r_size) {