From 3f8bcf2f61ee2df74d65d9bc82b163a4858e6a53 Mon Sep 17 00:00:00 2001 From: Madeline Busig Date: Mon, 4 Mar 2024 20:44:58 -0700 Subject: [PATCH] Add cmake files and initial memcpy/string implementation --- CMakeLists.txt | 17 +++ include/mtl/string.hpp | 243 ++++++++++++++++++++++++++++++++++++++++ include/mtl/utility.hpp | 21 ++++ src/memcpy.s | 213 +++++++++++++++++++++++++++++++++++ src/string.cpp | 217 +++++++++++++++++++++++++++++++++++ 5 files changed, 711 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 include/mtl/string.hpp create mode 100644 include/mtl/utility.hpp create mode 100644 src/memcpy.s create mode 100644 src/string.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4c2508d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.5) + +project(mtl LANGUAGES CXX C ASM) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(TARGET_SRC_FILES src/memcpy.s src/string.cpp) +set(TARGET_PUB_INCLUDE_FILES include/utility.hpp include/string.hpp) + +add_library(${PROJECT_NAME} STATIC ${TARGET_SRC_FILES} ${TARGET_PUB_INCLUDE_FILES}) + +target_include_directories(${PROJECT_NAME} PUBLIC "include") + +set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER "${TARGET_PUB_INCLUDE_FILES}") + +install(TARGETS ${PROJECT_NAME} + LIBRARY DESTINATION lib + PUBLIC_HEADER DESTINATION include) diff --git a/include/mtl/string.hpp b/include/mtl/string.hpp new file mode 100644 index 0000000..5d92b7b --- /dev/null +++ b/include/mtl/string.hpp @@ -0,0 +1,243 @@ +#pragma once + +#include +#include +#include + +#include "utility.hpp" + +namespace mtl { + +/** + * \brief Generic string interface + * + * Interface for mtl::string and mtl::string_ext. + * Implements most functions from std::string, see std::string documentation + * for function descriptions. Some operations are unimplemented due to the + * lack of dynamic memory usage. + */ +class istring { +protected: + char* m_str; + size_t m_size; + size_t m_capacity; + +public: + typedef char* iterator; + typedef const char* const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + static constexpr const size_t npos = -1; + + istring(char* _str, size_t _capacity, size_t _size); + + // Data functions + + // If the new string does not fit, it will be truncated to the capacity + istring& assign(const istring& str); + istring& assign(const istring& str, size_t pos, size_t count = npos); + istring& assign(const char* str, size_t count); + istring& assign(const char* str); + istring& assign(size_t count, char ch); + template + istring& assign(T first, T last) { + m_size = 0; + T it = first; + while (it != last && m_size < m_capacity) { + m_str[m_size] = *it; + ++m_size; + ++it; + } + + return *this; + } + + istring& operator=(const istring& rhs); + istring& operator=(const char* rhs); + istring& operator=(char rhs); + + // Element access functions + + char& at(size_t i); + const char& at(size_t i) const; + + char& operator[](size_t i); + const char& operator[](size_t i) const; + + char& front(); + const char& front() const; + + char& back(); + const char& back() const; + + char* data(); + const char* data() const; + const char* c_str() const; + + // Iterator functions + + iterator begin(); + const_iterator begin() const; + const_iterator cbegin() const; + + iterator end(); + const_iterator end() const; + const_iterator cend() const; + + reverse_iterator rbegin(); + const_reverse_iterator rbegin() const; + const_reverse_iterator crbegin() const; + + reverse_iterator rend(); + const_reverse_iterator rend() const; + const_reverse_iterator crend() const; + + // Capacity + + bool empty() const; + + size_t size() const; + size_t length() const; + + size_t capacity() const; + + // Modifiers + + void clear(); + + // Insert from self only works if index is after input range + istring& insert(size_t index, const istring& str); + istring& insert(size_t index, const istring& str, size_t s_index, size_t count = npos); + istring& insert(size_t index, const char* str); + istring& insert(size_t index, const char* str, size_t count); + istring& insert(size_t index, size_t count, char ch); + iterator insert(const_iterator pos, char ch); + iterator insert(const_iterator pos, size_t count, char ch); + template + iterator insert(const_iterator pos, T first, T last) { + } + + istring& erase(size_t index = 0, size_t count = npos); + iterator erase(const_iterator pos); + iterator erase(const_iterator first, const_iterator last); + + void push_back(char ch); + void pop_back(); + + istring& append(const istring& str); + istring& append(const istring& str, size_t s_index, size_t count = npos); + istring& append(const char* str); + istring& append(const char* str, size_t count); + istring& append(size_t count, char ch); + template + istring& append(T first, T last) { + } + + istring& operator+=(char ch); + istring& operator+=(const char* str); + istring& operator+=(const istring& str); + + istring& replace(size_t pos, size_t count, const istring& str); + istring& replace(const_iterator first, const_iterator last, const istring& str); + istring& replace(size_t pos, size_t count, const istring& str, size_t pos2, size_t count2 = npos); + istring& replace(size_t pos, size_t count, const char* str, size_t count2); + istring& replace(const_iterator first, const_iterator last, const char* str, size_t count2); + istring& replace(size_t pos, size_t count, const char* str); + istring& replace(const_iterator first, const_iterator last, const char* str); + istring& replace(size_t pos, size_t count, size_t count2, char ch); + istring& replace(const_iterator first, const_iterator last, size_t count2, char ch); + template + istring& replace(const_iterator first, const_iterator last, T first2, T last2); + + size_t copy(char* dest, size_t count, size_t pos = 0) const; + + void resize(size_t count); + void resize(size_t count, char ch); + + // Search + + size_t find(const istring& str, size_t pos = 0) const; + size_t find(const char* str, size_t pos, size_t count) const; + size_t find(const char* str, size_t pos = 0) const; + size_t find(char ch, size_t pos = 0) const; + + size_t rfind(const istring& str, size_t pos = npos) const; + size_t rfind(const char* str, size_t pos, size_t count) const; + size_t rfind(const char* str, size_t pos = npos) const; + size_t rfind(char ch, size_t pos = npos) const; + + size_t find_first_of(const istring& str, size_t pos = 0) const; + size_t find_first_of(const char* str, size_t pos, size_t count) const; + size_t find_first_of(const char* str, size_t pos = 0) const; + size_t find_first_of(char ch, size_t pos = 0) const; + + size_t find_first_not_of(const istring& str, size_t pos = 0) const; + size_t find_first_not_of(const char* str, size_t pos, size_t count) const; + size_t find_first_not_of(const char* str, size_t pos = 0) const; + size_t find_first_not_of(char ch, size_t pos = 0) const; + + size_t find_last_of(const istring& str, size_t pos = npos) const; + size_t find_last_of(const char* str, size_t pos, size_t count) const; + size_t find_last_of(const char* str, size_t pos = npos) const; + size_t find_last_of(char ch, size_t pos = npos) const; + + size_t find_last_not_of(const istring& str, size_t pos = npos) const; + size_t find_last_not_of(const char* str, size_t pos, size_t count) const; + size_t find_last_not_of(const char* str, size_t pos = npos) const; + size_t find_last_not_of(char ch, size_t pos = npos) const; + + // Comparison + + int32_t compare(const istring& str) const; + int32_t compare(size_t pos, size_t count, const istring& str) const; + int32_t compare(size_t pos1, size_t count1, istring& str, size_t pos2, size_t count2 = npos) const; + int32_t compare(const char* str) const; + int32_t compare(size_t pos, size_t count, const char* str) const; + int32_t compare(size_t pos1, size_t count1, const char* str, size_t count2) const; + + friend bool operator==(const istring& lhs, const istring& rhs); + friend bool operator!=(const istring& lhs, const istring& rhs); + friend bool operator<(const istring& lhs, const istring& rhs); + friend bool operator<=(const istring& lhs, const istring& rhs); + friend bool operator>(const istring& lhs, const istring& rhs); + friend bool operator>=(const istring& lhs, const istring& rhs); + + friend bool operator==(const istring& lhs, const char* rhs); + friend bool operator==(const char* lhs, const istring& rhs); + friend bool operator!=(const istring& lhs, const char* rhs); + friend bool operator!=(const char* lhs, const istring& rhs); + friend bool operator<(const istring& lhs, const char* rhs); + friend bool operator<(const char* lhs, const istring& rhs); + friend bool operator<=(const istring& lhs, const char* rhs); + friend bool operator<=(const char* lhs, const istring& rhs); + friend bool operator>(const istring& lhs, const char* rhs); + friend bool operator>(const char* lhs, const istring& rhs); + friend bool operator>=(const istring& lhs, const char* rhs); + friend bool operator>=(const char* lhs, const istring& rhs); +}; + +template +class string : public istring { +private: + char m_buf[C + 1]; + +public: + string() : istring(m_buf, C, 0) {} + string(const char* str) : istring(m_buf, C, 0) { + m_size = std::strlen(str); + if (m_size > C) { m_size = m_capacity; } + + mtl::memcpy(m_buf, str, m_size); + m_str[m_size] = '\0'; + } +}; + +class string_ext : public istring { +public: + string_ext(char* buf, size_t buf_size); + string_ext(const char&) = delete; + + using istring::operator=; +}; +} // namespace ml diff --git a/include/mtl/utility.hpp b/include/mtl/utility.hpp new file mode 100644 index 0000000..9977424 --- /dev/null +++ b/include/mtl/utility.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +extern "C" { +void mtl_memcpy32(void* dst, const void* src, size_t num8); +void mtl_dumbcpy16(void* dst, const void* src, size_t num8); +void mtl_dumbcpy(void* dst, const void* src, size_t num8); +void mtl_hybridcpy(void* dst, const void* src, size_t num8); +void mtl_rmemcpy32(void* dst, const void* src, size_t num8); +void mtl_rdumbcpy16(void* dst, const void* src, size_t num8); +void mtl_rdumbcpy(void* dst, const void* src, size_t num8); +void mtl_rhybridcpy(void* dst, const void* src, size_t num8); +void mtl_hybridmove(void* dst, const void* src, size_t num8); +} + +namespace mtl { + constexpr auto memcpy = mtl_hybridcpy; + constexpr auto memmove = mtl_hybridmove; +} diff --git a/src/memcpy.s b/src/memcpy.s new file mode 100644 index 0000000..5124d03 --- /dev/null +++ b/src/memcpy.s @@ -0,0 +1,213 @@ +// Load as ARM instructions into GBA IWRAM section. +// IWRAM section has a 32bit buffer instead of 16bit, so it is +// able to load arm instructions faster. +.section .iwram, "ax", %progbits +.arm +.align 2 + +// Copies num_byte from dst to src. +// dst and src !! MUST BE WORD ALIGNED !! +// however, num_byte does not need to be aligned +// r0, r1: dst, src +// r2: num_byte +.global mtl_memcpy32 +.type mtl_memcpy32 STT_FUNC +mtl_memcpy32: +// r12 = num_residual_byte +and r12, r2, #31 +// r2 = num_chunk +lsrs r2, r2, #5 +// Skip chunk copy if there are no chunks +beq .Lword_process +.Lchunk_process: +// Preserve local variables of calling function +push {r4-r10} +.Lchunk_copy: + // Load 8 word chunk from src into registers, increment src after + ldmia r1!, {r3-r10} + // Store 8 word chunk from register into dst, increment dst after + stmia r0!, {r3-r10} + // Copy again if more than zero chunks left + subs r2, r2, #1 + bhi .Lchunk_copy +// Restore local variables of calling function +pop {r4-r10} +.Lword_process: +// r2 = num_word +lsrs r2, r12, #2 +// Skip word copy if there are no words +beq .Lbyte_process +.Lword_copy: + // Load word from src into register, increment src after + ldr r3, [r1], #4 + // Store word from register into dst, increment dst after + str r3, [r0], #4 + // Copy again if more than zero words left + subs r2, r2, #1 + bhi .Lword_copy +.Lbyte_process: +// r12 = num_residual_byte +and r12, r12, #3 +.Lbyte_copy: + // Decrement byte count ahead of time, by checking the carry bit + // during load/store/branch, we can avoid an unnecessary branch. + // !REMEMBER, carry = !borrow. Carry is set when subtraction does + // not underflow. IE. num of bytes was > 0 + subs r12, r12, #1 + // Load byte from src into register, increment src after + ldrcsb r3, [r1], #1 + // Load byte from register into dst, increment dst after + strcsb r3, [r0], #1 + // Copy again if more bytes left, checks for zero and carry bit set + bhi .Lbyte_copy +// Return +bx lr + +.global mtl_dumbcpy16 +.type mtl_dumbcpy16 STT_FUNC +mtl_dumbcpy16: +// r12 = has residual byte +and r12, r2, #1 +// r2 = num_hword +lsr r2, r2, #1 +.Lhword_copy: + // Copy half words + subs r2, r2, #1 + ldrcsh r3, [r1], #2 + strcsh r3, [r0], #2 + bhi .Lhword_copy +// Copy residual byte if needed +cmp r12, #0 +ldrneb r3, [r1] +strneb r3, [r0] +bx lr + +// Performs a generic byte-by-byte memcpy. +// Still faster than std::memcpy due to being put in IWRAM +// r0, r1: dst, src +// r2: num_bytes +.global mtl_dumbcpy +.type mtl_dumbcpy STT_FUNC +mtl_dumbcpy: + subs r2, r2, #1 + ldrcsb r3, [r1], #1 + strcsb r3, [r0], #1 + bhi mtl_dumbcpy +bx lr + +// Calls mtl_memcpy32 if src and dst are word aligned, +// otherwise calls mtl_dumbcpy +.global mtl_hybridcpy +.type mtl_hybridcpy STT_FUNC +mtl_hybridcpy: +orr r3, r0, r1 +ands r12, r3, #1 +bne mtl_dumbcpy +ands r12, r3, #2 +bne mtl_dumbcpy16 +b mtl_memcpy32 + +// asdf +.global mtl_rmemcpy32 +.type mtl_rmemcpy32 STT_FUNC +mtl_rmemcpy32: +// Move to last byte of src and dst +add r0, r2 +add r1, r2 +sub r0, #1 +sub r1, #1 +// r12 = num residual bytes +and r12, r2, #3 +.Lrbyte_copy: + subs r12, #1 + ldrcsb r3, [r1], #-1 + strcsb r3, [r0], #-1 + bhi .Lrbyte_copy +// r12 = num residual words +lsr r12, r2, #2 +// Move to the beginning of the current word +sub r0, #3 +sub r1, #3 +.Lrword_copy: + subs r12, #1 + ldrcs r3, [r1], #-4 + strcs r3, [r0], #-4 + bhi .Lrword_copy +// r2 = num chunks +lsr r2, #5 +// Preserve local variables +push {r4-r10} +.Lrchunk_copy: + subs r2, #1 + ldmcsda r1!, {r3-r10} + stmcsda r0!, {r3-r10} + bhi .Lrchunk_copy +// Restore local variables +pop {r4-r10} +bx lr + + +// Performs a copy a halfword at a time, in reverse. +.global mtl_rdumbcpy16 +.type mtl_rdumbcpy16 STT_FUNC +mtl_rdumbcpy16: +// Move to last byte of src and dst +add r0, r2 +add r1, r2 +sub r0, #1 +sub r1, #1 +// r12 = has residual byte +and r12, r2, #1 +// Copy residual byte if there is one +ldrneb r3, [r1], #-1 +strneb r3, [r0], #-1 +// r2 = num of half words +lsrs r2, #1 +.Lrhword_copy: + subs r2, #1 + ldrcsh r3, [r1], #-2 + strcsh r3, [r0], #-2 + bhi .Lrhword_copy +bx lr + + +// Performs a generic byte-by-byte memcpy in reverse. +// This allows a safe copy when the dst and src overlap, +// and the destination is after the source +.global mtl_rdumbcpy +.type mtl_rdumbcpy STT_FUNC +mtl_rdumbcpy: +add r0, r2 +add r1, r2 +sub r0, #1 +sub r1, #1 +.Lcpy: + subs r2, #1 + ldrcsb r3, [r1], #-1 + strcsb r3, [r0], #-1 + bhi .Lcpy +bx lr + +// Performs a reverse copy, choosing a function depending on the alignment +.global mtl_rhybridcpy +.type mtl_rhybridcpy STT_FUNC +mtl_rhybridcpy: +orr r3, r0, r1 +// Dumb copy if only byte aligned, discard result +ands r12, r3, #1 +bne mtl_rdumbcpy +// Dumb copy by halfword if only halfword aligned +ands r12, r3, #2 +bne mtl_rdumbcpy16 +// Otherwise it's safe to copy by word +b mtl_rmemcpy32 + +.global mtl_hybridmove +.type mtl_hybridmove STT_FUNC +mtl_hybridmove: +cmp r0, r1 +bhi mtl_rhybridcpy +blo mtl_hybridcpy +// src and dst are the same, no need to copy +bx lr + diff --git a/src/string.cpp b/src/string.cpp new file mode 100644 index 0000000..4974627 --- /dev/null +++ b/src/string.cpp @@ -0,0 +1,217 @@ +#include "mtl/string.hpp" + +#include +#include + +#include "mtl/utility.hpp" + +namespace mtl { +istring::istring(char* _str, size_t _capacity, size_t _size) + : m_str(_str), m_capacity(_capacity), m_size(_size) {} + +/* + * ===== assign ===== + */ + +istring& istring::assign(const istring& str) { + if (&str == this) { return *this; } + + m_size = str.m_size; + if (m_size > m_capacity) { m_size = m_capacity; } + + std::memcpy(m_str, str.m_str, m_size); + m_str[m_size] = '\0'; + + return *this; +} +istring& istring::assign(const istring& str, size_t pos, size_t count) { + if (pos + count > str.m_size) { m_size = str.m_size - pos; } + else { m_size = count; } + + if (m_size > m_capacity) { m_size = m_capacity; } + + // str may overlap + std::memmove(m_str, str.m_str + pos, m_size); + m_str[m_size] = '\0'; + + return *this; +} +istring& istring::assign(const char* str, size_t count) { + m_size = count; + if (m_size > m_capacity) { m_size = m_capacity; } + + // str may overlap + std::memmove(m_str, str, m_size); + m_str[m_size] = '\0'; + + return *this; +} +istring& istring::assign(const char* str) { + m_size = std::strlen(str); + if (m_size > m_capacity) { m_size = m_capacity; } + + // str may overlap + mtl::memcpy(m_str, str, m_size); + m_str[m_size] = '\0'; + + return *this; +} +istring& istring::assign(size_t count, char ch) { + m_size = count; + if (m_size > m_capacity) { m_size = m_capacity; } + + std::memset(m_str, ch, m_size); + + return *this; +} + +/* + * ===== operator= ===== + */ + +istring& istring::operator=(const istring& rhs) { + return assign(rhs); +} +istring& istring::operator=(const char* rhs) { + return assign(rhs); +} +istring& istring::operator=(char rhs) { + return assign(1, rhs); +} + +/* + * ===== access ===== + */ + +char& istring::at(size_t i) { + return m_str[i]; +} +const char& istring::at(size_t i) const { + return m_str[i]; +} + +char& istring::operator[](size_t i) { + return m_str[i]; +} +const char& istring::operator[](size_t i) const { + return m_str[i]; +} + +char& istring::front() { + return *m_str; +} +const char& istring::front() const { + return *m_str; +} + +char& istring::back() { + return m_str[m_size]; +} +const char& istring::back() const { + return m_str[m_size]; +} + +char* istring::data() { + return m_str; +} +const char* istring::data() const { + return m_str; +} +const char* istring::c_str() const { + return m_str; +} + +/* + * ===== iterators ===== + */ + +istring::iterator istring::begin() { + return m_str; +} +istring::const_iterator istring::begin() const { + return m_str; +} +istring::const_iterator istring::cbegin() const { + return m_str; +} + +istring::iterator istring::end() { + return m_str + m_size; +} +istring::const_iterator istring::end() const { + return m_str + m_size; +} +istring::const_iterator istring::cend() const { + return m_str + m_size; +} + +istring::reverse_iterator istring::rbegin() { + return reverse_iterator(m_str + m_size); +} +istring::const_reverse_iterator istring::rbegin() const { + return reverse_iterator(m_str + m_size); +} +istring::const_reverse_iterator istring::crbegin() const { + return reverse_iterator(m_str + m_size); +} + +istring::reverse_iterator istring::rend() { + return reverse_iterator(m_str); +} +istring::const_reverse_iterator istring::rend() const { + return reverse_iterator(m_str); +} +istring::const_reverse_iterator istring::crend() const { + return reverse_iterator(m_str); +} + +/* + * ===== capacity ===== + */ + +bool istring::empty() const { + return m_size > 1; +} + +size_t istring::size() const { + return m_size; +} +size_t istring::length() const { + return m_size; +} + +size_t istring::capacity() const { + return m_capacity; +} + +void istring::clear() { + m_size = 0; +} + +istring& istring::insert(size_t index, const istring& str) { + char* substr = m_str + index; + + size_t space = m_capacity - index; + if (space > str.m_size) { // there is room to move + size_t space_to_move = space - str.m_size; + size_t items_to_move = m_size - index; + // only move items if there is room, and they exist + size_t num_to_move = std::min(items_to_move, space_to_move); + mtl::memmove(substr + str.m_size, substr, num_to_move); + } + + // only copy the string if there is room + size_t num_to_copy = std::min(str.m_size, space); + mtl::memcpy(substr, str.m_str, num_to_copy); + + m_size += num_to_copy; + m_str[m_size] = '\0'; + + return *this; +} + + +string_ext::string_ext(char* buf, size_t buf_size) : istring(buf, buf_size - 1, 0) {} + + +} //namespace ml