/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 7 End-User License Agreement and JUCE Privacy Policy. End User License Agreement: www.juce.com/juce-7-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { namespace dsp { namespace SIMDRegister_test_internal { template struct RandomPrimitive {}; template struct RandomPrimitive::value>::type> { static type next (Random& random) { return static_cast (std::is_signed::value ? (random.nextFloat() * 16.0) - 8.0 : (random.nextFloat() * 8.0)); } }; template struct RandomPrimitive::value>::type> { static type next (Random& random) { return static_cast (random.nextInt64()); } }; template struct RandomValue { static type next (Random& random) { return RandomPrimitive::next (random); } }; template struct RandomValue> { static std::complex next (Random& random) { return {RandomPrimitive::next (random), RandomPrimitive::next (random)}; } }; template static void fillVec (type* dst, Random& random) { std::generate_n (dst, SIMDRegister::SIMDNumElements, [&] { return RandomValue::next (random); }); } // Avoid visual studio warning template static type safeAbs (type a) { return static_cast (std::abs (static_cast (a))); } template static type safeAbs (std::complex a) { return std::abs (a); } template static double difference (type a) { return static_cast (safeAbs (a)); } template static double difference (type a, type b) { return difference (a - b); } } // namespace SIMDRegister_test_internal // These tests need to be strictly run on all platforms supported by JUCE as the // SIMD code is highly platform dependent. class SIMDRegisterUnitTests : public UnitTest { public: template struct Tag {}; SIMDRegisterUnitTests() : UnitTest ("SIMDRegister UnitTests", UnitTestCategories::dsp) {} //============================================================================== // Some helper classes template static bool allValuesEqualTo (const SIMDRegister& vec, const type scalar) { #ifdef _MSC_VER __declspec(align(sizeof (SIMDRegister))) type elements[SIMDRegister::SIMDNumElements]; #else type elements[SIMDRegister::SIMDNumElements] __attribute__((aligned(sizeof (SIMDRegister)))); #endif vec.copyToRawArray (elements); // as we do not want to rely on the access operator we cast this to a primitive pointer for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) if (elements[i] != scalar) return false; return true; } template static bool vecEqualToArray (const SIMDRegister& vec, const type* array) { HeapBlock vecElementsStorage (SIMDRegister::SIMDNumElements * 2); auto* ptr = SIMDRegister::getNextSIMDAlignedPtr (vecElementsStorage.getData()); vec.copyToRawArray (ptr); for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) { double delta = SIMDRegister_test_internal::difference (ptr[i], array[i]); if (delta > 1e-4) { DBG ("a: " << SIMDRegister_test_internal::difference (ptr[i]) << " b: " << SIMDRegister_test_internal::difference (array[i]) << " difference: " << delta); return false; } } return true; } template static void copy (SIMDRegister& vec, const type* ptr) { if (SIMDRegister::isSIMDAligned (ptr)) { vec = SIMDRegister::fromRawArray (ptr); } else { for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) vec[i] = ptr[i]; } } //============================================================================== // Some useful operations to test struct Addition { template static void inplace (typeOne& a, const typeTwo& b) { a += b; } template static typeOne outofplace (const typeOne& a, const typeTwo& b) { return a + b; } }; struct Subtraction { template static void inplace (typeOne& a, const typeTwo& b) { a -= b; } template static typeOne outofplace (const typeOne& a, const typeTwo& b) { return a - b; } }; struct Multiplication { template static void inplace (typeOne& a, const typeTwo& b) { a *= b; } template static typeOne outofplace (const typeOne& a, const typeTwo& b) { return a * b; } }; struct BitAND { template static void inplace (typeOne& a, const typeTwo& b) { a &= b; } template static typeOne outofplace (const typeOne& a, const typeTwo& b) { return a & b; } }; struct BitOR { template static void inplace (typeOne& a, const typeTwo& b) { a |= b; } template static typeOne outofplace (const typeOne& a, const typeTwo& b) { return a | b; } }; struct BitXOR { template static void inplace (typeOne& a, const typeTwo& b) { a ^= b; } template static typeOne outofplace (const typeOne& a, const typeTwo& b) { return a ^ b; } }; //============================================================================== // the individual tests struct InitializationTest { template static void run (UnitTest& u, Random& random, Tag) { u.expect (allValuesEqualTo (SIMDRegister::expand (static_cast (23)), 23)); { #ifdef _MSC_VER __declspec(align(sizeof (SIMDRegister))) type elements[SIMDRegister::SIMDNumElements]; #else type elements[SIMDRegister::SIMDNumElements] __attribute__((aligned(sizeof (SIMDRegister)))); #endif SIMDRegister_test_internal::fillVec (elements, random); SIMDRegister a (SIMDRegister::fromRawArray (elements)); u.expect (vecEqualToArray (a, elements)); SIMDRegister b (a); a *= static_cast (2); u.expect (vecEqualToArray (b, elements)); } } }; struct AccessTest { template static void run (UnitTest& u, Random& random, Tag) { // set-up SIMDRegister a; type array [SIMDRegister::SIMDNumElements]; SIMDRegister_test_internal::fillVec (array, random); // Test non-const access operator for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) a[i] = array[i]; u.expect (vecEqualToArray (a, array)); // Test const access operator const SIMDRegister& b = a; for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) u.expect (b[i] == array[i]); } }; template struct OperatorTests { template static void run (UnitTest& u, Random& random, Tag) { for (int n = 0; n < 100; ++n) { // set-up SIMDRegister a (static_cast (0)); SIMDRegister b (static_cast (0)); SIMDRegister c (static_cast (0)); type array_a [SIMDRegister::SIMDNumElements]; type array_b [SIMDRegister::SIMDNumElements]; type array_c [SIMDRegister::SIMDNumElements]; SIMDRegister_test_internal::fillVec (array_a, random); SIMDRegister_test_internal::fillVec (array_b, random); SIMDRegister_test_internal::fillVec (array_c, random); copy (a, array_a); copy (b, array_b); copy (c, array_c); // test in-place with both params being vectors for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) Operation::template inplace (array_a[i], array_b[i]); Operation::template inplace, SIMDRegister> (a, b); u.expect (vecEqualToArray (a, array_a)); u.expect (vecEqualToArray (b, array_b)); SIMDRegister_test_internal::fillVec (array_a, random); SIMDRegister_test_internal::fillVec (array_b, random); SIMDRegister_test_internal::fillVec (array_c, random); copy (a, array_a); copy (b, array_b); copy (c, array_c); // test in-place with one param being scalar for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) Operation::template inplace (array_b[i], static_cast (2)); Operation::template inplace, type> (b, 2); u.expect (vecEqualToArray (a, array_a)); u.expect (vecEqualToArray (b, array_b)); // set-up again SIMDRegister_test_internal::fillVec (array_a, random); SIMDRegister_test_internal::fillVec (array_b, random); SIMDRegister_test_internal::fillVec (array_c, random); copy (a, array_a); copy (b, array_b); copy (c, array_c); // test out-of-place with both params being vectors for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) array_c[i] = Operation::template outofplace (array_a[i], array_b[i]); c = Operation::template outofplace, SIMDRegister> (a, b); u.expect (vecEqualToArray (a, array_a)); u.expect (vecEqualToArray (b, array_b)); u.expect (vecEqualToArray (c, array_c)); // test out-of-place with one param being scalar for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) array_c[i] = Operation::template outofplace (array_b[i], static_cast (2)); c = Operation::template outofplace, type> (b, 2); u.expect (vecEqualToArray (a, array_a)); u.expect (vecEqualToArray (b, array_b)); u.expect (vecEqualToArray (c, array_c)); } } }; template struct BitOperatorTests { template static void run (UnitTest& u, Random& random, Tag) { typedef typename SIMDRegister::vMaskType vMaskType; typedef typename SIMDRegister::MaskType MaskType; for (int n = 0; n < 100; ++n) { // Check flip sign bit and using as a union { type array_a [SIMDRegister::SIMDNumElements]; union ConversionUnion { inline ConversionUnion() : floatVersion (static_cast (0)) {} inline ~ConversionUnion() {} SIMDRegister floatVersion; vMaskType intVersion; } a, b; vMaskType bitmask = vMaskType::expand (static_cast (1) << (sizeof (MaskType) - 1)); SIMDRegister_test_internal::fillVec (array_a, random); copy (a.floatVersion, array_a); copy (b.floatVersion, array_a); Operation::template inplace, vMaskType> (a.floatVersion, bitmask); Operation::template inplace (b.intVersion, bitmask); #ifdef _MSC_VER __declspec(align(sizeof (SIMDRegister))) type elements[SIMDRegister::SIMDNumElements]; #else type elements[SIMDRegister::SIMDNumElements] __attribute__((aligned(sizeof (SIMDRegister)))); #endif b.floatVersion.copyToRawArray (elements); u.expect (vecEqualToArray (a.floatVersion, elements)); } // set-up SIMDRegister a, c; vMaskType b; MaskType array_a [SIMDRegister::SIMDNumElements]; MaskType array_b [SIMDRegister::SIMDNumElements]; MaskType array_c [SIMDRegister::SIMDNumElements]; type float_a [SIMDRegister::SIMDNumElements]; type float_c [SIMDRegister::SIMDNumElements]; SIMDRegister_test_internal::fillVec (float_a, random); SIMDRegister_test_internal::fillVec (array_b, random); SIMDRegister_test_internal::fillVec (float_c, random); memcpy (array_a, float_a, sizeof (type) * SIMDRegister::SIMDNumElements); memcpy (array_c, float_c, sizeof (type) * SIMDRegister::SIMDNumElements); copy (a, float_a); copy (b, array_b); copy (c, float_c); // test in-place with both params being vectors for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) Operation::template inplace (array_a[i], array_b[i]); memcpy (float_a, array_a, sizeof (type) * SIMDRegister::SIMDNumElements); Operation::template inplace, vMaskType> (a, b); u.expect (vecEqualToArray (a, float_a)); u.expect (vecEqualToArray (b, array_b)); SIMDRegister_test_internal::fillVec (float_a, random); SIMDRegister_test_internal::fillVec (array_b, random); SIMDRegister_test_internal::fillVec (float_c, random); memcpy (array_a, float_a, sizeof (type) * SIMDRegister::SIMDNumElements); memcpy (array_c, float_c, sizeof (type) * SIMDRegister::SIMDNumElements); copy (a, float_a); copy (b, array_b); copy (c, float_c); // test in-place with one param being scalar for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) Operation::template inplace (array_a[i], static_cast (9)); memcpy (float_a, array_a, sizeof (type) * SIMDRegister::SIMDNumElements); Operation::template inplace, MaskType> (a, static_cast (9)); u.expect (vecEqualToArray (a, float_a)); u.expect (vecEqualToArray (b, array_b)); // set-up again SIMDRegister_test_internal::fillVec (float_a, random); SIMDRegister_test_internal::fillVec (array_b, random); SIMDRegister_test_internal::fillVec (float_c, random); memcpy (array_a, float_a, sizeof (type) * SIMDRegister::SIMDNumElements); memcpy (array_c, float_c, sizeof (type) * SIMDRegister::SIMDNumElements); copy (a, float_a); copy (b, array_b); copy (c, float_c); // test out-of-place with both params being vectors for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) { array_c[i] = Operation::template outofplace (array_a[i], array_b[i]); } memcpy (float_a, array_a, sizeof (type) * SIMDRegister::SIMDNumElements); memcpy (float_c, array_c, sizeof (type) * SIMDRegister::SIMDNumElements); c = Operation::template outofplace, vMaskType> (a, b); u.expect (vecEqualToArray (a, float_a)); u.expect (vecEqualToArray (b, array_b)); u.expect (vecEqualToArray (c, float_c)); // test out-of-place with one param being scalar for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) array_c[i] = Operation::template outofplace (array_a[i], static_cast (9)); memcpy (float_a, array_a, sizeof (type) * SIMDRegister::SIMDNumElements); memcpy (float_c, array_c, sizeof (type) * SIMDRegister::SIMDNumElements); c = Operation::template outofplace, MaskType> (a, static_cast (9)); u.expect (vecEqualToArray (a, float_a)); u.expect (vecEqualToArray (b, array_b)); u.expect (vecEqualToArray (c, float_c)); } } }; struct CheckComparisonOps { template static void run (UnitTest& u, Random& random, Tag) { typedef typename SIMDRegister::vMaskType vMaskType; typedef typename SIMDRegister::MaskType MaskType; for (int i = 0; i < 100; ++i) { // set-up type array_a [SIMDRegister::SIMDNumElements]; type array_b [SIMDRegister::SIMDNumElements]; MaskType array_eq [SIMDRegister::SIMDNumElements]; MaskType array_neq [SIMDRegister::SIMDNumElements]; MaskType array_lt [SIMDRegister::SIMDNumElements]; MaskType array_le [SIMDRegister::SIMDNumElements]; MaskType array_gt [SIMDRegister::SIMDNumElements]; MaskType array_ge [SIMDRegister::SIMDNumElements]; SIMDRegister_test_internal::fillVec (array_a, random); SIMDRegister_test_internal::fillVec (array_b, random); // do check for (size_t j = 0; j < SIMDRegister::SIMDNumElements; ++j) { array_eq [j] = (array_a[j] == array_b[j]) ? static_cast (-1) : 0; array_neq [j] = (array_a[j] != array_b[j]) ? static_cast (-1) : 0; array_lt [j] = (array_a[j] < array_b[j]) ? static_cast (-1) : 0; array_le [j] = (array_a[j] <= array_b[j]) ? static_cast (-1) : 0; array_gt [j] = (array_a[j] > array_b[j]) ? static_cast (-1) : 0; array_ge [j] = (array_a[j] >= array_b[j]) ? static_cast (-1) : 0; } SIMDRegister a (static_cast (0)); SIMDRegister b (static_cast (0)); vMaskType eq, neq, lt, le, gt, ge; copy (a, array_a); copy (b, array_b); eq = SIMDRegister::equal (a, b); neq = SIMDRegister::notEqual (a, b); lt = SIMDRegister::lessThan (a, b); le = SIMDRegister::lessThanOrEqual (a, b); gt = SIMDRegister::greaterThan (a, b); ge = SIMDRegister::greaterThanOrEqual (a, b); u.expect (vecEqualToArray (eq, array_eq )); u.expect (vecEqualToArray (neq, array_neq)); u.expect (vecEqualToArray (lt, array_lt )); u.expect (vecEqualToArray (le, array_le )); u.expect (vecEqualToArray (gt, array_gt )); u.expect (vecEqualToArray (ge, array_ge )); do { SIMDRegister_test_internal::fillVec (array_a, random); SIMDRegister_test_internal::fillVec (array_b, random); } while (std::equal (array_a, array_a + SIMDRegister::SIMDNumElements, array_b)); copy (a, array_a); copy (b, array_b); u.expect (a != b); u.expect (b != a); u.expect (! (a == b)); u.expect (! (b == a)); SIMDRegister_test_internal::fillVec (array_a, random); copy (a, array_a); copy (b, array_a); u.expect (a == b); u.expect (b == a); u.expect (! (a != b)); u.expect (! (b != a)); type scalar = a[0]; a = SIMDRegister::expand (scalar); u.expect (a == scalar); u.expect (! (a != scalar)); scalar--; u.expect (a != scalar); u.expect (! (a == scalar)); } } }; struct CheckMultiplyAdd { template static void run (UnitTest& u, Random& random, Tag) { // set-up type array_a [SIMDRegister::SIMDNumElements]; type array_b [SIMDRegister::SIMDNumElements]; type array_c [SIMDRegister::SIMDNumElements]; type array_d [SIMDRegister::SIMDNumElements]; SIMDRegister_test_internal::fillVec (array_a, random); SIMDRegister_test_internal::fillVec (array_b, random); SIMDRegister_test_internal::fillVec (array_c, random); SIMDRegister_test_internal::fillVec (array_d, random); // check for (size_t i = 0; i < SIMDRegister::SIMDNumElements; ++i) array_d[i] = array_a[i] + (array_b[i] * array_c[i]); SIMDRegister a, b, c, d; copy (a, array_a); copy (b, array_b); copy (c, array_c); d = SIMDRegister::multiplyAdd (a, b, c); u.expect (vecEqualToArray (d, array_d)); } }; struct CheckMinMax { template static void run (UnitTest& u, Random& random, Tag) { for (int i = 0; i < 100; ++i) { type array_a [SIMDRegister::SIMDNumElements]; type array_b [SIMDRegister::SIMDNumElements]; type array_min [SIMDRegister::SIMDNumElements]; type array_max [SIMDRegister::SIMDNumElements]; for (size_t j = 0; j < SIMDRegister::SIMDNumElements; ++j) { array_a[j] = static_cast (random.nextInt (127)); array_b[j] = static_cast (random.nextInt (127)); } for (size_t j = 0; j < SIMDRegister::SIMDNumElements; ++j) { array_min[j] = (array_a[j] < array_b[j]) ? array_a[j] : array_b[j]; array_max[j] = (array_a[j] > array_b[j]) ? array_a[j] : array_b[j]; } SIMDRegister a (static_cast (0)); SIMDRegister b (static_cast (0)); SIMDRegister vMin (static_cast (0)); SIMDRegister vMax (static_cast (0)); copy (a, array_a); copy (b, array_b); vMin = jmin (a, b); vMax = jmax (a, b); u.expect (vecEqualToArray (vMin, array_min)); u.expect (vecEqualToArray (vMax, array_max)); copy (vMin, array_a); copy (vMax, array_a); vMin = SIMDRegister::min (a, b); vMax = SIMDRegister::max (a, b); u.expect (vecEqualToArray (vMin, array_min)); u.expect (vecEqualToArray (vMax, array_max)); } } }; struct CheckSum { template static void run (UnitTest& u, Random& random, Tag) { type array [SIMDRegister::SIMDNumElements]; SIMDRegister_test_internal::fillVec (array, random); using AddedType = decltype (type{} + type{}); const auto sumCheck = (type) std::accumulate (std::begin (array), std::end (array), AddedType{}); SIMDRegister a; copy (a, array); u.expect (SIMDRegister_test_internal::difference (sumCheck, a.sum()) < 1e-4); } }; struct CheckAbs { template static void run (UnitTest& u, Random& random, Tag) { type inArray[SIMDRegister::SIMDNumElements]; type outArray[SIMDRegister::SIMDNumElements]; SIMDRegister_test_internal::fillVec (inArray, random); SIMDRegister a; copy (a, inArray); a = SIMDRegister::abs (a); auto calcAbs = [] (type x) -> type { return x >= type (0) ? x : type (-x); }; for (size_t j = 0; j < SIMDRegister::SIMDNumElements; ++j) outArray[j] = calcAbs (inArray[j]); u.expect (vecEqualToArray (a, outArray)); } }; struct CheckTruncate { template static void run (UnitTest& u, Random& random, Tag) { type inArray[SIMDRegister::SIMDNumElements]; type outArray[SIMDRegister::SIMDNumElements]; SIMDRegister_test_internal::fillVec (inArray, random); SIMDRegister a; copy (a, inArray); a = SIMDRegister::truncate (a); for (size_t j = 0; j < SIMDRegister::SIMDNumElements; ++j) outArray[j] = (type) (int) inArray[j]; u.expect (vecEqualToArray (a, outArray)); } }; struct CheckBoolEquals { template static void run (UnitTest& u, Random& random, Tag) { bool is_signed = std::is_signed::value; type array [SIMDRegister::SIMDNumElements]; auto value = is_signed ? static_cast ((random.nextFloat() * 16.0) - 8.0) : static_cast (random.nextFloat() * 8.0); std::fill (array, array + SIMDRegister::SIMDNumElements, value); SIMDRegister a, b; copy (a, array); u.expect (a == value); u.expect (! (a != value)); value += 1; u.expect (a != value); u.expect (! (a == value)); SIMDRegister_test_internal::fillVec (array, random); copy (a, array); copy (b, array); u.expect (a == b); u.expect (! (a != b)); SIMDRegister_test_internal::fillVec (array, random); copy (b, array); u.expect (a != b); u.expect (! (a == b)); } }; //============================================================================== template void runTestFloatingPoint (const char* unitTestName, TheTest) { beginTest (unitTestName); Random random = getRandom(); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag{}); } //============================================================================== template void runTestForAllTypes (const char* unitTestName, TheTest) { beginTest (unitTestName); Random random = getRandom(); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag> {}); TheTest::run (*this, random, Tag>{}); } template void runTestNonComplex (const char* unitTestName, TheTest) { beginTest (unitTestName); Random random = getRandom(); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag{}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag{}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag{}); } template void runTestSigned (const char* unitTestName, TheTest) { beginTest (unitTestName); Random random = getRandom(); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag {}); TheTest::run (*this, random, Tag{}); TheTest::run (*this, random, Tag{}); TheTest::run (*this, random, Tag{}); } void runTest() { runTestForAllTypes ("InitializationTest", InitializationTest{}); runTestForAllTypes ("AccessTest", AccessTest{}); runTestForAllTypes ("AdditionOperators", OperatorTests{}); runTestForAllTypes ("SubtractionOperators", OperatorTests{}); runTestForAllTypes ("MultiplicationOperators", OperatorTests{}); runTestForAllTypes ("BitANDOperators", BitOperatorTests{}); runTestForAllTypes ("BitOROperators", BitOperatorTests{}); runTestForAllTypes ("BitXOROperators", BitOperatorTests{}); runTestNonComplex ("CheckComparisons", CheckComparisonOps{}); runTestNonComplex ("CheckBoolEquals", CheckBoolEquals{}); runTestNonComplex ("CheckMinMax", CheckMinMax{}); runTestForAllTypes ("CheckMultiplyAdd", CheckMultiplyAdd{}); runTestForAllTypes ("CheckSum", CheckSum{}); runTestSigned ("CheckAbs", CheckAbs{}); runTestFloatingPoint ("CheckTruncate", CheckTruncate{}); } }; static SIMDRegisterUnitTests SIMDRegisterUnitTests; } // namespace dsp } // namespace juce