diff --git a/include/eepp/config.hpp b/include/eepp/config.hpp index 427e3776d..0437178b2 100644 --- a/include/eepp/config.hpp +++ b/include/eepp/config.hpp @@ -316,6 +316,12 @@ typedef signed long long Int64; typedef unsigned long long Uint64; #endif +#if defined( __x86_64__ ) || defined( _M_X64 ) + #define EE_ARCH_X86_64 +#elif defined( __aarch64__ ) || defined( _M_ARM64 ) + #define EE_ARCH_ARM64 +#endif + #if defined( EE_LINUX_64 ) || defined( EE_SPARC_64 ) || defined( __osf__ ) || \ ( defined( _WIN64 ) && !defined( _XBOX ) ) || defined( __64BIT__ ) || defined( __LP64 ) || \ defined( __LP64__ ) || defined( _LP64 ) || defined( _ADDR64 ) || defined( _CRAYC ) diff --git a/include/eepp/system/cpu.hpp b/include/eepp/system/cpu.hpp new file mode 100644 index 000000000..5f5856832 --- /dev/null +++ b/include/eepp/system/cpu.hpp @@ -0,0 +1,17 @@ +#ifndef EE_SYSTEM_CPU_HPP +#define EE_SYSTEM_CPU_HPP + +#include + +namespace EE { namespace System { + +class EE_API CPU { + public: + static bool hasAVX2(); + + static bool hasNEON(); +}; + +}} // namespace EE::System + +#endif // EE_SYSTEM_CPU_HPP diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 7968b5f66..2b1b6e1ae 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -1,6 +1,6 @@ #include #include -// #include +#include #include #define FTS_FUZZY_MATCH_IMPLEMENTATION @@ -29,6 +29,17 @@ #include #endif +#if defined( EE_ARCH_X86_64 ) + #if defined( _MSC_VER ) + #include + #elif defined( __GNUC__ ) || defined( __clang__ ) + #include + #include + #endif +#elif defined( EE_ARCH_ARM64 ) + #include +#endif + namespace EE { template static bool _fromString( T& t, const std::string& s, int base = 10 ) { @@ -1377,23 +1388,67 @@ bool String::contains( const String& needle ) const { return String::contains( *this, needle ); } +namespace { + +#ifdef EE_ARCH_X86_64 +#if defined( __GNUC__ ) || defined( __clang__ ) +__attribute__( ( target( "avx2" ) ) ) +#endif +bool isAsciiAVX2( const char32_t* data, size_t len, uint32_t limit ) { + const char32_t* end = data + len; + const __m256i maskVec = _mm256_set1_epi32( ~limit ); + + while ( data + 8 <= end ) { + __m256i chunk = _mm256_loadu_si256( (const __m256i*)data ); + if ( _mm256_testz_si256( chunk, maskVec ) == 0 ) + return false; + data += 8; + } + + while ( data < end ) { + if ( *data > limit ) + return false; + data++; + } + return true; +} +#endif + +#ifdef EE_ARCH_ARM64 +bool isAsciiNEON( const char32_t* data, size_t len, uint32_t limit ) { + const char32_t* end = data + len; + const uint32x4_t maskVec = vdupq_n_u32( ~limit ); + + while ( data + 4 <= end ) { + uint32x4_t chunk = vld1q_u32( (const uint32_t*)data ); + uint32x4_t tst = vtstq_u32( chunk, maskVec ); + if ( vmaxvq_u32( tst ) != 0 ) + return false; + data += 4; + } + + while ( data < end ) { + if ( *data > limit ) + return false; + data++; + } + return true; +} +#endif + +} // namespace + template static inline bool isAsciiTpl( const StringType& str ) { - // #ifdef EE_STD_SIMD - // std::size_t i = 0; - // std::size_t len = str.size(); - // auto data = str.data(); - // using simd_type = simd::native_simd; - // constexpr size_t simd_size = simd_type::size(); - // const simd_type ascii_limit = Limit; - // for ( ; i + simd_size - 1 < len; i += simd_size ) { - // simd_type chunk; - // chunk.copy_from( &data[i], simd::element_aligned ); - // auto mask = chunk > ascii_limit; - // if ( simd::any_of( mask ) ) - // return false; - // } - // #endif +#ifdef EE_ARCH_X86_64 + if ( System::CPU::hasAVX2() ) { + return isAsciiAVX2( (const char32_t*)str.data(), str.size(), Limit ); + } +#elif defined( EE_ARCH_ARM64 ) + if ( System::CPU::hasNEON() ) { + return isAsciiNEON( (const char32_t*)str.data(), str.size(), Limit ); + } +#endif for ( const auto& codepoint : str ) if ( codepoint > Limit ) diff --git a/src/eepp/network/http.cpp b/src/eepp/network/http.cpp index bee91d2f8..3c295f479 100644 --- a/src/eepp/network/http.cpp +++ b/src/eepp/network/http.cpp @@ -1519,8 +1519,8 @@ Uint64 Http::downloadAsyncRequest( const Http::AsyncResponseCallback& cb, Lock l( mThreadsMutex ); mThreads.push_back( thread ); } - return id; #endif + return id; } const IpAddress& Http::getHost() const { diff --git a/src/eepp/system/cpu.cpp b/src/eepp/system/cpu.cpp new file mode 100644 index 000000000..04b2a9a19 --- /dev/null +++ b/src/eepp/system/cpu.cpp @@ -0,0 +1,45 @@ +#include + +#ifdef EE_ARCH_X86_64 +#if defined( _MSC_VER ) +#define COMPILER_MSVC 1 +#include +#elif ( defined( __GNUC__ ) || defined( __clang__ ) ) +#define COMPILER_GCC_CLANG 1 +#include +#endif +#endif + +namespace EE { namespace System { + +bool CPU::hasAVX2() { +#ifdef EE_ARCH_X86_64 + static bool isAVX2 = []() { +#if defined( COMPILER_MSVC ) + int cpuInfo[4]; + __cpuid( cpuInfo, 0 ); + if ( cpuInfo[0] < 7 ) + return false; + __cpuid( cpuInfo, 7 ); + return ( cpuInfo[1] & ( 1 << 5 ) ) != 0; +#elif defined( COMPILER_GCC_CLANG ) + return __builtin_cpu_supports( "avx2" ); +#else + return false; +#endif + }(); + return isAVX2; +#else + return false; +#endif +} + +bool CPU::hasNEON() { +#ifdef EE_ARCH_ARM64 + return true; // NEON is mandatory in AArch64 +#else + return false; +#endif +} + +}} // namespace EE::System diff --git a/src/tests/unit_tests/stringsoperations.cpp b/src/tests/unit_tests/stringsoperations.cpp new file mode 100644 index 000000000..2b40d5e36 --- /dev/null +++ b/src/tests/unit_tests/stringsoperations.cpp @@ -0,0 +1,165 @@ +#include "utest.h" +#include + +using namespace std::literals; + +using namespace EE; + +UTEST( String, isAscii ) { + // Empty string + EXPECT_TRUE( String::isAscii( String::View( U"" ) ) ); + + // Simple short ASCII string + String strAscii( "Hello World" ); + EXPECT_TRUE( strAscii.isAscii() ); + + // String with non-ASCII at the end + String strNonAsciiEnd( "Hello world\u0080" ); + EXPECT_FALSE( strNonAsciiEnd.isAscii() ); + + // String with non-ASCII at the beginning + String strNonAsciiBegin( "\u0080Hello world" ); + EXPECT_FALSE( strNonAsciiBegin.isAscii() ); + + // String with non-ASCII in the middle + String strNonAsciiMid( "Hello \u0080 world" ); + EXPECT_FALSE( strNonAsciiMid.isAscii() ); + + // Test boundary around 127 + String str127; + str127 += (String::StringBaseType)127; + EXPECT_TRUE( str127.isAscii() ); + + String str128; + str128 += (String::StringBaseType)128; + EXPECT_FALSE( str128.isAscii() ); + + // Test SIMD chunk boundaries (assumed 8 elements for AVX2, 4 for NEON) + // We'll test lengths around 4, 8, 16, 32 to cover various chunk alignments + + // 1. Exact chunks + 0 remainder + { + // 32 chars (4x8 AVX2, 8x4 NEON) + String longAscii( "01234567890123456789012345678901" ); + EXPECT_TRUE( longAscii.isAscii() ); + + // 32 chars with invalid at last position 31 + String longNonAscii = longAscii; + longNonAscii[31] = 129; + EXPECT_FALSE( longNonAscii.isAscii() ); + + // 32 chars with invalid at first position 0 + longNonAscii = longAscii; + longNonAscii[0] = 129; + EXPECT_FALSE( longNonAscii.isAscii() ); + } + + // 2. Exact chunks + remainder + { + // 33 chars (one element remainder) + String longAscii( "01234567890123456789012345678901A" ); + EXPECT_TRUE( longAscii.isAscii() ); + + // invalid at remainder + String longNonAscii = longAscii; + longNonAscii[32] = 130; + EXPECT_FALSE( longNonAscii.isAscii() ); + } + + // 3. Just below chunk size (7 chars) + { + String shortAscii( "0123456" ); + EXPECT_TRUE( shortAscii.isAscii() ); + + String shortNonAscii = shortAscii; + shortNonAscii[6] = 131; + EXPECT_FALSE( shortNonAscii.isAscii() ); + } + + // Large string verification + { + String largeAscii; + for ( int i = 0; i < 1024; ++i ) + largeAscii += "A"; + EXPECT_TRUE( largeAscii.isAscii() ); + + String largeNonAscii = largeAscii; + largeNonAscii[512] = 200; // fail in the middle + EXPECT_FALSE( largeNonAscii.isAscii() ); + } +} + +UTEST( String, isLatin1 ) { + // Empty string + EXPECT_TRUE( String::isLatin1( String::View( U"" ) ) ); + + // ASCII is also Latin1 + String strAscii( "Hello World" ); + EXPECT_TRUE( strAscii.isLatin1() ); + + // Latin1 characters (128-255) + String strLatin1; + strLatin1 += (String::StringBaseType)0xFF; // 255 + EXPECT_TRUE( strLatin1.isLatin1() ); + + // Non-Latin1 (>255) + String strNonLatin1; + strNonLatin1 += (String::StringBaseType)0x100; // 256 + EXPECT_FALSE( strNonLatin1.isLatin1() ); + + // Boundary Check + String str255; + str255 += (String::StringBaseType)255; + EXPECT_TRUE( str255.isLatin1() ); + + // Complex string with Latin1 chars + String complexLatin1 = String::fromUtf8( "Héllø Wørld"sv ); // Assuming these are in Latin1 range + // Note: 'ø' is 0xF8 (248), 'é' is 0xE9 (233). Both in Latin1. + EXPECT_TRUE( complexLatin1.isLatin1() ); + + // Verify SIMD paths for isLatin1 (uses same template logic but limit=255) + { + // 32 chars of 255 + String longLatin1( 32, (String::StringBaseType)255 ); + EXPECT_TRUE( longLatin1.isLatin1() ); + + } +} + +UTEST( String, isAsciiHighBit ) { + // Test comparison safety (unsigned vs signed issue) + // 0x80000000 is a very large number, definitely not ASCII. + // If signed comparison was used, it might be interpreted as negative and thus < 127. + String strHigh; + strHigh += (String::StringBaseType)0x80000000; + EXPECT_FALSE( strHigh.isAscii() ); + + String strHigh2; + strHigh2 += (String::StringBaseType)0xFFFFFFFF; + EXPECT_FALSE( strHigh2.isAscii() ); + + // Mixed with ASCII + String strMixed = "Hello"; + strMixed += (String::StringBaseType)0x80000000; + EXPECT_FALSE( strMixed.isAscii() ); +} + +UTEST( String, isAsciiPatterns ) { + // Alternating + String alt; + for (int i = 0; i < 100; i++) { + alt += (i % 2 == 0) ? 'a' : (char)128; + } + EXPECT_FALSE( alt.isAscii() ); + + // Block of invalid in middle of valid + String block(100, 'a'); + for(int i=40; i<60; i++) block[i] = 200; + EXPECT_FALSE( block.isAscii() ); +} + +UTEST( String, isLatin1HighBit ) { + String strHigh; + strHigh += (String::StringBaseType)0x80000000; + EXPECT_FALSE( strHigh.isLatin1() ); +}