From ac9c2bc4920f1853a51e42af5fa8814537cff6bd Mon Sep 17 00:00:00 2001 From: asynts Date: Sat, 25 Jul 2020 16:00:26 +0200 Subject: [PATCH] AK: Implement Span which represents a contiguous sequence of objects. This makes it possible to pass one object rather than pointer and length individually. --- AK/Forward.h | 9 +++ AK/Span.h | 130 ++++++++++++++++++++++++++++++++++++++++++ AK/Tests/Span.cpp | 126 ++++++++++++++++++++++++++++++++++++++++ AK/Vector.h | 4 ++ Userland/allocate.cpp | 16 +++--- 5 files changed, 277 insertions(+), 8 deletions(-) create mode 100644 AK/Span.h create mode 100644 AK/Tests/Span.cpp diff --git a/AK/Forward.h b/AK/Forward.h index b58e887a22b..f42d36c9fdb 100644 --- a/AK/Forward.h +++ b/AK/Forward.h @@ -49,6 +49,12 @@ class FlyString; class Utf32View; class Utf8View; +template +class Span; + +using ReadonlyBytes = Span; +using Bytes = Span; + template class Atomic; @@ -113,6 +119,7 @@ using AK::Badge; using AK::Bitmap; using AK::BufferStream; using AK::ByteBuffer; +using AK::Bytes; using AK::CircularQueue; using AK::DebugLogStream; using AK::DoublyLinkedList; @@ -131,9 +138,11 @@ using AK::NonnullOwnPtr; using AK::NonnullRefPtr; using AK::Optional; using AK::OwnPtr; +using AK::ReadonlyBytes; using AK::RefPtr; using AK::SharedBuffer; using AK::SinglyLinkedList; +using AK::Span; using AK::String; using AK::StringBuilder; using AK::StringImpl; diff --git a/AK/Span.h b/AK/Span.h new file mode 100644 index 00000000000..bf46986cd0f --- /dev/null +++ b/AK/Span.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include + +namespace AK { + +template +class Span { +public: + using Iterator = T*; + using ConstIterator = const T*; + + static_assert(!IsPointer::value); + + ALWAYS_INLINE Span() = default; + ALWAYS_INLINE Span(T* values, size_t size) + : m_values(values) + , m_size(size) + { + ASSERT(!Checked::addition_would_overflow((uintptr_t)values, size * sizeof(T))); + } + ALWAYS_INLINE Span(const Span& other) + : m_values(other.m_values) + , m_size(other.m_size) + { + } + ALWAYS_INLINE Span(const Span>& other) + : m_values(other.m_values) + , m_size(other.m_size) + { + } + + ALWAYS_INLINE const T* data() const { return m_values; } + ALWAYS_INLINE T* data() { return m_values; } + + ALWAYS_INLINE ConstIterator begin() const + { + return m_values; + } + ALWAYS_INLINE ConstIterator end() const + { + return begin() + m_size; + } + + ALWAYS_INLINE Iterator begin() + { + return m_values; + } + ALWAYS_INLINE Iterator end() + { + return begin() + m_size; + } + + ALWAYS_INLINE size_t size() const { return m_size; } + + ALWAYS_INLINE bool is_empty() const { return m_size == 0; } + + ALWAYS_INLINE Span subspan(size_t start, size_t size) const + { + ASSERT(start + size <= m_size); + return { m_values + start, size }; + } + + ALWAYS_INLINE const T& at(size_t index) const + { + ASSERT(index < m_size); + return m_values[index]; + } + ALWAYS_INLINE T& at(size_t index) + { + ASSERT(index < m_size); + return m_values[index]; + } + + ALWAYS_INLINE T& operator[](size_t index) const + { + return m_values[index]; + } + ALWAYS_INLINE T& operator[](size_t index) + { + return m_values[index]; + } + + ALWAYS_INLINE T& operator=(const T& other) + { + m_size = other.m_size; + m_values = other.m_values; + } + +protected: + T* m_values { nullptr }; + size_t m_size { 0 }; +}; + +using ReadonlyBytes = Span; +using Bytes = Span; + +} + +using AK::Bytes; +using AK::ReadonlyBytes; +using AK::Span; diff --git a/AK/Tests/Span.cpp b/AK/Tests/Span.cpp new file mode 100644 index 00000000000..ef0c48a9b0d --- /dev/null +++ b/AK/Tests/Span.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +TEST_CASE(default_constructor_is_empty) +{ + Span span; + EXPECT(span.is_empty()); +} + +TEST_CASE(span_works_with_constant_types) +{ + const u8 buffer[4] { 1, 2, 3, 4 }; + ReadonlyBytes bytes { buffer, 4 }; + + EXPECT(AK::IsConst::Type>::value); + EXPECT_EQ(bytes[2], 3); +} + +TEST_CASE(span_works_with_mutable_types) +{ + u8 buffer[4] { 1, 2, 3, 4 }; + Bytes bytes { buffer, 4 }; + + EXPECT_EQ(bytes[2], 3); + ++bytes[2]; + EXPECT_EQ(bytes[2], 4); +} + +TEST_CASE(iterator_behaves_like_loop) +{ + u8 buffer[256]; + for (int idx = 0; idx < 256; ++idx) { + buffer[idx] = static_cast(idx); + } + + Bytes bytes { buffer, 256 }; + size_t idx = 0; + for (auto iter = bytes.begin(); iter < bytes.end(); ++iter) { + EXPECT_EQ(*iter, buffer[idx]); + + ++idx; + } +} + +TEST_CASE(modifying_is_possible) +{ + int values_before[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + int values_after[8] = { 7, 6, 5, 4, 3, 2, 1, 0 }; + + Span span { values_before, 8 }; + for (auto& value : span) { + value = 8 - value; + } + + for (int idx = 0; idx < 8; ++idx) { + EXPECT_EQ(values_before[idx], values_after[idx]); + } +} + +TEST_CASE(at_and_index_operator_return_same_value) +{ + u8 buffer[256]; + for (int idx = 0; idx < 256; ++idx) { + buffer[idx] = static_cast(idx); + } + + Bytes bytes { buffer, 256 }; + for (int idx = 0; idx < 256; ++idx) { + EXPECT_EQ(buffer[idx], bytes[idx]); + EXPECT_EQ(bytes[idx], bytes.at(idx)); + } +} + +TEST_CASE(can_subspan_whole_span) +{ + u8 buffer[16]; + Bytes bytes { buffer, 16 }; + + Bytes subspan = bytes.subspan(0, 16); + + EXPECT_EQ(subspan.data(), buffer); + EXPECT_EQ(subspan.size(), 16u); +} + +TEST_CASE(can_subspan_as_intended) +{ + const u16 buffer[8] { 1, 2, 3, 4, 5, 6, 7, 8 }; + + Span span { buffer, 8 }; + auto subspan = span.subspan(3, 2); + + EXPECT_EQ(subspan.size(), 2u); + EXPECT_EQ(subspan[0], 4); + EXPECT_EQ(subspan[1], 5); +} + +TEST_MAIN(Span) diff --git a/AK/Vector.h b/AK/Vector.h index 9d525c80ccd..393b9d8b359 100644 --- a/AK/Vector.h +++ b/AK/Vector.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -186,6 +187,9 @@ public: m_size = other.size(); } + Span span() { return { data(), size() }; } + Span span() const { return { data(), size() }; } + // FIXME: What about assigning from a vector with lower inline capacity? Vector& operator=(Vector&& other) { diff --git a/Userland/allocate.cpp b/Userland/allocate.cpp index 12d81e44b07..b047f51d19f 100644 --- a/Userland/allocate.cpp +++ b/Userland/allocate.cpp @@ -37,14 +37,14 @@ void usage(void) exit(1); } -enum Unit { Bytes, +enum class Unit { Bytes, KiloBytes, MegaBytes }; int main(int argc, char** argv) { int count = 50; - Unit unit = MegaBytes; + auto unit = Unit::MegaBytes; if (argc >= 2) { auto number = String(argv[1]).to_uint(); @@ -56,22 +56,22 @@ int main(int argc, char** argv) if (argc >= 3) { if (strcmp(argv[2], "B") == 0) - unit = Bytes; + unit = Unit::Bytes; else if (strcmp(argv[2], "KB") == 0) - unit = KiloBytes; + unit = Unit::KiloBytes; else if (strcmp(argv[2], "MB") == 0) - unit = MegaBytes; + unit = Unit::MegaBytes; else usage(); } switch (unit) { - case Bytes: + case Unit::Bytes: break; - case KiloBytes: + case Unit::KiloBytes: count *= 1024; break; - case MegaBytes: + case Unit::MegaBytes: count *= 1024 * 1024; break; }