diff --git a/externals/coda-oss/UnitTest/UnitTest.vcxproj b/externals/coda-oss/UnitTest/UnitTest.vcxproj index 5f55f6360..064502651 100644 --- a/externals/coda-oss/UnitTest/UnitTest.vcxproj +++ b/externals/coda-oss/UnitTest/UnitTest.vcxproj @@ -259,6 +259,10 @@ true true + + true + true + true true diff --git a/externals/coda-oss/UnitTest/UnitTest.vcxproj.filters b/externals/coda-oss/UnitTest/UnitTest.vcxproj.filters index e85d2fcd0..37d7c36cf 100644 --- a/externals/coda-oss/UnitTest/UnitTest.vcxproj.filters +++ b/externals/coda-oss/UnitTest/UnitTest.vcxproj.filters @@ -219,6 +219,9 @@ re + + mt + diff --git a/externals/coda-oss/UnitTest/mt.cpp b/externals/coda-oss/UnitTest/mt.cpp index 2de6f17fd..6190f8aa4 100644 --- a/externals/coda-oss/UnitTest/mt.cpp +++ b/externals/coda-oss/UnitTest/mt.cpp @@ -24,4 +24,8 @@ TEST_CLASS(work_sharing_balanced_runnable_1d_test){ public: #include "mt/unittests/work_sharing_balanced_runnable_1d_test.cpp" }; +TEST_CLASS(test_mt_byte_swap){ public: +#include "mt/unittests/test_mt_byte_swap.cpp" +}; + } \ No newline at end of file diff --git a/externals/coda-oss/UnitTest/pch.h b/externals/coda-oss/UnitTest/pch.h index 8229e0b47..2396089d4 100644 --- a/externals/coda-oss/UnitTest/pch.h +++ b/externals/coda-oss/UnitTest/pch.h @@ -75,6 +75,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,7 @@ #include #include #include +#include #include #include #include diff --git a/externals/coda-oss/modules/c++/coda-oss-lite.vcxproj b/externals/coda-oss/modules/c++/coda-oss-lite.vcxproj index f50ac4a19..d5e4e5cf8 100644 --- a/externals/coda-oss/modules/c++/coda-oss-lite.vcxproj +++ b/externals/coda-oss/modules/c++/coda-oss-lite.vcxproj @@ -149,6 +149,7 @@ + @@ -191,6 +192,8 @@ + + @@ -228,6 +231,7 @@ + @@ -421,14 +425,12 @@ DynamicLibrary true v143 - true DynamicLibrary false v143 true - true diff --git a/externals/coda-oss/modules/c++/coda-oss-lite.vcxproj.filters b/externals/coda-oss/modules/c++/coda-oss-lite.vcxproj.filters index 75b551471..a8119ff83 100644 --- a/externals/coda-oss/modules/c++/coda-oss-lite.vcxproj.filters +++ b/externals/coda-oss/modules/c++/coda-oss-lite.vcxproj.filters @@ -735,6 +735,18 @@ config + + sys + + + sys + + + mt + + + sys + diff --git a/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/CPlusPlus.h b/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/CPlusPlus.h index f28d64236..b912e9c3e 100644 --- a/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/CPlusPlus.h +++ b/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/CPlusPlus.h @@ -63,11 +63,12 @@ #endif // CODA_OSS_cplusplus // Define a few macros as that's less verbose than testing against a version number +// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros #define CODA_OSS_cpp11 (CODA_OSS_cplusplus >= 201103L) #define CODA_OSS_cpp14 (CODA_OSS_cplusplus >= 201402L) #define CODA_OSS_cpp17 (CODA_OSS_cplusplus >= 201703L) #define CODA_OSS_cpp20 (CODA_OSS_cplusplus >= 202002L) -#define CODA_OSS_cpp23 0 +#define CODA_OSS_cpp23 (CODA_OSS_cplusplus >= 202302L) #if !CODA_OSS_cpp14 #error "Must compile with C++14 or greater." diff --git a/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/bit.h b/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/bit.h index 99045bba1..32ef77862 100644 --- a/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/bit.h +++ b/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/bit.h @@ -22,6 +22,14 @@ #define CODA_OSS_coda_oss_bit_h_INCLUDED_ #pragma once +#include +#include +#ifdef __GNUC__ +#include // "These functions are GNU extensions." +#endif + +#include + #include "coda_oss/namespace_.h" namespace coda_oss { @@ -38,6 +46,58 @@ namespace coda_oss native = __BYTE_ORDER__ #endif }; + + // https://en.cppreference.com/w/cpp/numeric/byteswap + namespace details + { + // Overloads for common types + inline constexpr uint8_t byteswap(uint8_t val) noexcept + { + return val; // no-op + } + #if defined(_MSC_VER) + // These routines should generate a single instruction; see + // https://devblogs.microsoft.com/cppblog/a-tour-of-4-msvc-backend-improvements/ + inline uint16_t byteswap(uint16_t val) + { + return _byteswap_ushort(val); + } + inline uint32_t byteswap(uint32_t val) + { + return _byteswap_ulong(val); + } + inline uint64_t byteswap(uint64_t val) + { + return _byteswap_uint64(val); + } + #elif defined(__GNUC__) + inline uint16_t byteswap(uint16_t val) + { + return bswap_16(val); + } + inline uint32_t byteswap(uint32_t val) + { + return bswap_32(val); + } + inline uint64_t byteswap(uint64_t val) + { + return bswap_64(val); + } + #else + #error "No platform-specific byteswap()" // TODO: do something else? + #endif + } + + template + inline T byteswap(T n) noexcept + { + // "std::byteswap participates in overload resolution only if T satisfies integral, i.e., T is an integer type. The program is + // ill-formed if T has padding bits." + static_assert(std::is_integral::value, "T must be integral"); + + using unsigned_t = std::make_unsigned_t; // "Since C++14" https://en.cppreference.com/w/cpp/types/make_unsigned + return details::byteswap(static_cast(n)); + } } #endif // CODA_OSS_coda_oss_bit_h_INCLUDED_ diff --git a/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/cstddef.h b/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/cstddef.h index e6e19a8ad..c8c51ed42 100644 --- a/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/cstddef.h +++ b/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/cstddef.h @@ -22,7 +22,10 @@ #define CODA_OSS_coda_oss_cstddef_h_INCLUDED_ #pragma once +#include + #include +#include // Need a fairly decent C++ compiler to use the real GSL. This brings in more than // we really need for span (e.g., gsl::narrow()), but it keeps things simple. @@ -37,5 +40,6 @@ namespace coda_oss enum class byte : unsigned char {}; #endif // GSL_BYTE_H } +static_assert(!std::is_same::value, "'coda_oss::byte' should be a unique type."); #endif // CODA_OSS_coda_oss_cstddef_h_INCLUDED_ diff --git a/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/span.h b/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/span.h index c33375c3e..b225f35fe 100644 --- a/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/span.h +++ b/externals/coda-oss/modules/c++/coda_oss/include/coda_oss/span.h @@ -25,6 +25,7 @@ #include "coda_oss/namespace_.h" #include "coda_oss/span_.h" +#include "coda_oss/cstddef.h" // byte // Need a fairly decent C++ compiler to use the real GSL. This brings in more than // we really need for span (e.g., gsl::narrow()), but it keeps things simple. @@ -37,6 +38,26 @@ namespace coda_oss #else // no gsl::span, use our own using details::span; #endif // GSL_SPAN_H + +// https://en.cppreference.com/w/cpp/container/span/as_bytes +template +span as_bytes(span s) noexcept +{ + const void* const p = s.data(); + return span(static_cast(p), s.size_bytes()); +} +template +span as_bytes(span s) noexcept +{ + return as_bytes(span(s.data(), s.size())); +} +template +span as_writable_bytes(span s) noexcept +{ + void* const p = s.data(); + return span(static_cast(p), s.size_bytes()); +} + } #endif // CODA_OSS_coda_oss_span_h_INCLUDED_ diff --git a/externals/coda-oss/modules/c++/mem/unittests/test_Span.cpp b/externals/coda-oss/modules/c++/mem/unittests/test_Span.cpp index 472a87992..28a76ad20 100644 --- a/externals/coda-oss/modules/c++/mem/unittests/test_Span.cpp +++ b/externals/coda-oss/modules/c++/mem/unittests/test_Span.cpp @@ -26,6 +26,7 @@ #include #include +#include #include "TestCase.h" @@ -94,8 +95,43 @@ TEST_CASE(testGslNarrow) TEST_THROWS(gsl::narrow(d)); } +TEST_CASE(test_sys_make_span) +{ + int i = 314; + int* const p = &i; + auto s = sys::make_span(p, 1); + TEST_ASSERT(s.data() == p); + TEST_ASSERT_EQ(s[0], i); + s[0] = 123; + TEST_ASSERT_EQ(i, 123); + s[0] = 314; + + const int* const q = &i; + auto cs = sys::make_span(q, 1); + TEST_ASSERT(cs.data() == q); + TEST_ASSERT_EQ(cs[0], i); + //cs[0] = 123; // cs = span + TEST_ASSERT_EQ(i, 314); + + std::vector v{314}; + s = sys::make_span(v); + TEST_ASSERT(s.data() == v.data()); + TEST_ASSERT_EQ(s[0], v[0]); + s[0] = 123; + TEST_ASSERT_EQ(v[0], 123); + s[0] = 314; + + const std::vector& u = v; + cs = sys::make_span(u); + TEST_ASSERT(cs.data() == u.data()); + TEST_ASSERT_EQ(cs[0], u[0]); + // cs[0] = 123; // cs = span + TEST_ASSERT_EQ(u[0], 314); +} + TEST_MAIN( TEST_CHECK(testSpanBuffer); TEST_CHECK(testSpanVector); TEST_CHECK(testGslNarrow); + TEST_CHECK(test_sys_make_span); ) diff --git a/externals/coda-oss/modules/c++/mt/include/mt/ThreadedByteSwap.h b/externals/coda-oss/modules/c++/mt/include/mt/ThreadedByteSwap.h new file mode 100644 index 000000000..0b97e1171 --- /dev/null +++ b/externals/coda-oss/modules/c++/mt/include/mt/ThreadedByteSwap.h @@ -0,0 +1,90 @@ +#ifndef CODA_OSS_mt_ThreadedByteSwap_h_INCLUDED_ +#define CODA_OSS_mt_ThreadedByteSwap_h_INCLUDED_ +#pragma once + +#include + +#include "sys/ByteSwap.h" + +#include "ThreadPlanner.h" +#include "ThreadGroup.h" + +namespace mt +{ +/* + * Threaded byte-swapping + * + * \param buffer Buffer to swap (contents will be overridden) + * \param elemSize Size of each element in 'buffer' + * \param numElements Number of elements in 'buffer' + * \param numThreads Number of threads to use for byte-swapping + */ +inline void threadedByteSwap(void* buffer, size_t elemSize, size_t numElements, size_t numThreads) +{ + if (numThreads <= 1) + { + sys::byteSwap(buffer, elemSize, numElements); + } + else + { + mt::ThreadGroup threads; + const mt::ThreadPlanner planner(numElements, numThreads); + + size_t threadNum(0); + size_t startElement(0); + size_t numElementsThisThread(0); + while (planner.getThreadInfo(threadNum++, startElement, numElementsThisThread)) + { + auto thread = std::make_unique( + buffer, + elemSize, + startElement, + numElementsThisThread); + + threads.createThread(thread.release()); + } + threads.joinAll(); + } +} + +/* + * Threaded byte-swapping and copy + * + * \param buffer Buffer to swap + * \param elemSize Size of each element in 'buffer' + * \param numElements Number of elements in 'buffer' + * \param numThreads Number of threads to use for byte-swapping + * \param outputBuffer buffer to write into + */ +inline void threadedByteSwap(const void* buffer, size_t elemSize, size_t numElements, size_t numThreads, void* outputBuffer) +{ + if (numThreads <= 1) + { + sys::byteSwap(buffer, elemSize, numElements, outputBuffer); + } + else + { + mt::ThreadGroup threads; + const mt::ThreadPlanner planner(numElements, numThreads); + + size_t threadNum(0); + size_t startElement(0); + size_t numElementsThisThread(0); + while (planner.getThreadInfo(threadNum++, startElement, numElementsThisThread)) + { + auto thread = std::make_unique( + buffer, + elemSize, + startElement, + numElementsThisThread, + outputBuffer); + + threads.createThread(thread.release()); + } + threads.joinAll(); + + } +} +} + +#endif // CODA_OSS_mt_ThreadedByteSwap_h_INCLUDED_ diff --git a/externals/coda-oss/modules/c++/mt/unittests/test_mt_byte_swap.cpp b/externals/coda-oss/modules/c++/mt/unittests/test_mt_byte_swap.cpp new file mode 100644 index 000000000..d2099cf83 --- /dev/null +++ b/externals/coda-oss/modules/c++/mt/unittests/test_mt_byte_swap.cpp @@ -0,0 +1,72 @@ +/* ========================================================================= + * This file is part of mt-c++ + * ========================================================================= + * + * (C) Copyright 2004 - 2017, MDA Information Systems LLC + * + * sys-c++ is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; If not, + * see . + * + */ + +#include "TestCase.h" + +#include + +#include +#include // std::byte +#include + +#include + +static std::vector make_origValues(size_t NUM_PIXELS) +{ + ::srand(334); + + std::vector retval(NUM_PIXELS); + for (size_t ii = 0; ii < NUM_PIXELS; ++ii) + { + const auto value = static_cast(::rand()) / RAND_MAX * + std::numeric_limits::max(); + retval[ii] = static_cast(value); + } + return retval; +} + +TEST_CASE(testThreadedByteSwap) +{ + constexpr size_t NUM_PIXELS = 10000; + const auto origValues = make_origValues(NUM_PIXELS); + + constexpr size_t numThreads = 4; + + // Byte swap the old-fashioned way + auto values1(origValues); + mt::threadedByteSwap(values1.data(), sizeof(values1[0]), NUM_PIXELS, numThreads); + + // Byte swap into output buffer + std::vector swappedValues2(origValues.size()); + mt::threadedByteSwap(origValues.data(), sizeof(origValues[0]), NUM_PIXELS, numThreads, swappedValues2.data()); + + // Everything should match + for (size_t ii = 0; ii < NUM_PIXELS; ++ii) + { + TEST_ASSERT_EQ(values1[ii], swappedValues2[ii]); + } +} + +TEST_MAIN( + TEST_CHECK(testThreadedByteSwap); + ) + \ No newline at end of file diff --git a/externals/coda-oss/modules/c++/std/include/std/bit b/externals/coda-oss/modules/c++/std/include/std/bit index d564c0020..dbcc0b6c5 100644 --- a/externals/coda-oss/modules/c++/std/include/std/bit +++ b/externals/coda-oss/modules/c++/std/include/std/bit @@ -28,7 +28,7 @@ // Make it (too?) easy for clients to get our various std:: implementations #ifndef CODA_OSS_NO_std_endian #if CODA_OSS_cpp20 - // Some implementations cliam to be C++20 w/o + // Some implementations claim to be C++20 w/o #if __has_include() // __has_include is C++17 #include #define CODA_OSS_NO_std_endian 1 // provided by implementation, probably C++20 @@ -47,4 +47,22 @@ namespace std // This is slightly uncouth: we're not supposed to augment "std". } #endif // CODA_OSS_NO_std_endian +// Make it (too?) easy for clients to get our various std:: implementations +#ifndef CODA_OSS_NO_std_byteswap + #if CODA_OSS_cpp23 + #include + #define CODA_OSS_NO_std_byteswap 1 // provided by implementation, probably C++23 + #endif + #ifndef CODA_OSS_NO_std_byteswap + #define CODA_OSS_NO_std_byteswap 0 // <= C++20, use our own + #endif +#endif + +#if !CODA_OSS_NO_std_byteswap +namespace std // This is slightly uncouth: we're not supposed to augment "std". +{ + using coda_oss::byteswap; +} +#endif // CODA_OSS_NO_std_byteswap + #endif // CODA_OSS_std_bit_INCLUDED_ diff --git a/externals/coda-oss/modules/c++/std/include/std/cstddef b/externals/coda-oss/modules/c++/std/include/std/cstddef index 56700ec71..10ba7deac 100644 --- a/externals/coda-oss/modules/c++/std/include/std/cstddef +++ b/externals/coda-oss/modules/c++/std/include/std/cstddef @@ -22,7 +22,10 @@ #define CODA_OSS_std_cstddef_INCLUDED_ #pragma once +#include + #include +#include #include "coda_oss/cstddef.h" #include "coda_oss/CPlusPlus.h" @@ -41,6 +44,7 @@ namespace std // This is slightly uncouth: we're not supposed to augment "std". { using coda_oss::byte; } +static_assert(!std::is_same::value, "'std::byte' should be a unique type."); #endif // !CODA_OSS_NO_std_byte #endif // CODA_OSS_std_cstddef_INCLUDED_ \ No newline at end of file diff --git a/externals/coda-oss/modules/c++/std/include/std/span b/externals/coda-oss/modules/c++/std/include/std/span index ffa0d1df9..819befe15 100644 --- a/externals/coda-oss/modules/c++/std/include/std/span +++ b/externals/coda-oss/modules/c++/std/include/std/span @@ -39,6 +39,9 @@ namespace std // This is slightly uncouth: we're not supposed to augment "std". { using coda_oss::span; + + using coda_oss::as_bytes; + using coda_oss::as_writable_bytes; } #endif // CODA_OSS_DEFINE_std_span_ diff --git a/externals/coda-oss/modules/c++/sys/include/sys/ByteSwap.h b/externals/coda-oss/modules/c++/sys/include/sys/ByteSwap.h new file mode 100644 index 000000000..d8145dcce --- /dev/null +++ b/externals/coda-oss/modules/c++/sys/include/sys/ByteSwap.h @@ -0,0 +1,118 @@ +/* ========================================================================= + * This file is part of sys-c++ + * ========================================================================= + * + * (C) Copyright 2004 - 2014, MDA Information Systems LLC + * (C) Copyright 2023, Maxar Technologies, Inc. + * + * sys-c++ is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; If not, + * see . + * + */ + +#ifndef CODA_OSS_sys_ByteSwap_h_INCLUDED_ +#define CODA_OSS_sys_ByteSwap_h_INCLUDED_ +#pragma once + +#include +#include + +#include +#include + +#include "config/Exports.h" + +#include "ByteSwapValue.h" +#include "Runnable.h" + +namespace sys +{ +/*! + * Swap bytes in-place. Note that a complex pixel + * is equivalent to two floats so elemSize and numElems + * must be adjusted accordingly. + * + * \param [inout] buffer to transform + * \param elemSize + * \param numElems + */ +coda_oss::span CODA_OSS_API byteSwap(coda_oss::spanbuffer, size_t elemSize); +void CODA_OSS_API byteSwap(void* buffer, size_t elemSize, size_t numElems); + +/*! + * Swap bytes into output buffer. Note that a complex pixel + * is equivalent to two floats so elemSize and numElems + * must be adjusted accordingly. + * + * \param buffer to transform + * \param elemSize + * \param numElems + * \param[out] outputBuffer buffer to write swapped elements to + */ +coda_oss::span CODA_OSS_API byteSwap(coda_oss::span buffer, + size_t elemSize, coda_oss::span outputBuffer); +void CODA_OSS_API byteSwap(const void* buffer, size_t elemSize, size_t numElems, void* outputBuffer); + +struct ByteSwapRunnable final : public sys::Runnable +{ + ByteSwapRunnable(void* buffer, size_t elemSize, size_t startElement, size_t numElements) : + mBuffer(static_cast(buffer) + startElement * elemSize), + mElemSize(elemSize), mNumElements(numElements) + { + } + void run() override + { + byteSwap(mBuffer, mElemSize, mNumElements); + } + + virtual ~ByteSwapRunnable() = default; + ByteSwapRunnable(const ByteSwapRunnable&) = delete; + ByteSwapRunnable& operator=(const ByteSwapRunnable&) = delete; + ByteSwapRunnable(ByteSwapRunnable&&) = delete; + ByteSwapRunnable& operator=(ByteSwapRunnable&&) = delete; + +private: + void* const mBuffer; + const size_t mElemSize; + const size_t mNumElements; +}; + +struct ByteSwapCopyRunnable final : public sys::Runnable +{ + ByteSwapCopyRunnable(const void* buffer, size_t elemSize, size_t startElement, size_t numElements, void* outputBuffer) : + mBuffer(static_cast(buffer) + startElement * elemSize), + mElemSize(elemSize), mNumElements(numElements), + mOutputBuffer(static_cast(outputBuffer) + startElement * elemSize) + { + } + void run() override + { + byteSwap(mBuffer, mElemSize, mNumElements, mOutputBuffer); + } + + virtual ~ByteSwapCopyRunnable() = default; + ByteSwapCopyRunnable(const ByteSwapCopyRunnable&) = delete; + ByteSwapCopyRunnable& operator=(const ByteSwapCopyRunnable&) = delete; + ByteSwapCopyRunnable(ByteSwapCopyRunnable&&) = delete; + ByteSwapCopyRunnable& operator=(ByteSwapCopyRunnable&&) = delete; + +private: + const void* const mBuffer; + const size_t mElemSize; + const size_t mNumElements; + void* const mOutputBuffer; +}; + +} +#endif // CODA_OSS_sys_ByteSwap_h_INCLUDED_ diff --git a/externals/coda-oss/modules/c++/sys/include/sys/ByteSwapValue.h b/externals/coda-oss/modules/c++/sys/include/sys/ByteSwapValue.h new file mode 100644 index 000000000..57cffe3f3 --- /dev/null +++ b/externals/coda-oss/modules/c++/sys/include/sys/ByteSwapValue.h @@ -0,0 +1,196 @@ +/* ========================================================================= + * This file is part of sys-c++ + * ========================================================================= + * + * (C) Copyright 2004 - 2014, MDA Information Systems LLC + * (C) Copyright 2021, Maxar Technologies, Inc. + * + * sys-c++ is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; If not, + * see . + * + */ + +#ifndef CODA_OSS_sys_ByteSwapValue_h_INCLUDED_ +#define CODA_OSS_sys_ByteSwapValue_h_INCLUDED_ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config/Exports.h" + +#include "Span.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace sys +{ + /*! + * Swap bytes for a single value into output buffer. API is `span` rather than `void*` since + * for a single value we know the size. These "low level" routines may be less efficient than + * the templates since it's not possible to specialize on a specific size. + * + * \param buffer to transform + * \param[out] outputBuffer buffer to write swapped elements to + */ + coda_oss::span CODA_OSS_API byteSwap( + coda_oss::span pIn, coda_oss::span outPtr); + + namespace details + { + template + inline auto swapUIntBytes(coda_oss::span inBytes, coda_oss::span outBytes, + std::nothrow_t) noexcept + { + static_assert(std::is_unsigned::value, "TUInt must be 'unsigned'"); + assert(sizeof(TUInt) == inBytes.size()); + assert(inBytes.size() == outBytes.size()); + + const void* const pIn_ = inBytes.data(); + auto const pIn = static_cast(pIn_); + void* const pOut_ = outBytes.data(); + auto const pOut = static_cast(pOut_); + + *pOut = coda_oss::byteswap(*pIn); // at long last, swap the bytes + + // Give the raw byte-swapped bytes back to the caller for easy serialization + return as_bytes(pOut); + } + template + inline auto swapUIntBytes(coda_oss::span inBytes, coda_oss::span outBytes) + { + if (sizeof(TUInt) != inBytes.size()) + { + throw std::invalid_argument("'inBytes.size() != sizeof(TUInt)"); + } + if (inBytes.size() != outBytes.size()) + { + throw std::invalid_argument("'inBytes.size() != outBytes.size()"); + } + return swapUIntBytes(inBytes, outBytes, std::nothrow); + } + + // This is a template so that we can have specializations for different sizes. + // By specializing on `size_t`, a `float` can be "cast" to `uint32_t` (via + // `std::byte`) for byte-swapping. + template + inline auto swapBytes(coda_oss::span inBytes, coda_oss::span outBytes) + { + if (elemSize != inBytes.size()) + { + throw std::invalid_argument("'inBytes.size() != elemSize"); + } + return sys::byteSwap(inBytes, outBytes); // size that wasn't specialized + } + + // avoid copy-paste errors + #define CODA_OSS_define_swapBytes_specialization_(T) template <> inline auto swapBytes \ + (coda_oss::span inBytes, coda_oss::span outBytes) { return swapUIntBytes(inBytes, outBytes); } + CODA_OSS_define_swapBytes_specialization_(uint8_t) // no `;`, it's not needed and generates a -Wpedantic warning + CODA_OSS_define_swapBytes_specialization_(uint16_t) + CODA_OSS_define_swapBytes_specialization_(uint32_t) + CODA_OSS_define_swapBytes_specialization_(uint64_t) + #undef CODA_OSS_define_swapBytes_specialization_ + + template + inline constexpr bool is_byte_swappable() noexcept + { + // Trying to byte-swap anything other than integers is likely to cause + // problems (or at least confusion): + // * `struct`s have padding that should be ignored. + // * each individual member of a `struct` should be byte-swaped + // * byte-swaped `float` or `double` bits are nonsense + return (std::is_integral::value || std::is_enum::value) || !std::is_compound::value; + } + } + + /*! + * Function to swap one element irrespective of size. + * Returns the raw byte-swapped bytes for easy serialization. + */ + template + inline auto swapBytes(coda_oss::span inBytes, coda_oss::span outBytes) + { + static_assert(details::is_byte_swappable(), "T should not be a 'struct'"); + return details::swapBytes(inBytes, outBytes); + } + template + inline auto swapBytes(T in, coda_oss::span outBytes) + { + return swapBytes(as_bytes(in), outBytes); + } + template + inline auto swapBytes(T in) + { + std::vector retval; + retval.resize(sizeof(T)); + std::ignore = swapBytes(in, make_span(retval)); + return retval; + } + + // Reverse the above: turn `span` back to T after byte-swapping + template + inline auto swapBytes(coda_oss::span in) + { + // Don't want to cast the swapped bytes in `in` to T* as they might not be valid; + // e.g., a byte-swapped `float` could be garbage. + T retval; + swapBytes(in, as_writable_bytes(retval)); + return retval; + } + + /*! + * Function to swap one element irrespective of size. The inplace + * buffer function should be preferred. + * + * To specialize complex float, first include the complex library + * \code + #include + * \endcode + * + * Then put an overload in as specified below: + * \code + template std::complex byteSwap(std::complex val) + { + std::complex out(byteSwap(val.real()), + byteSwap(val.imag())); + return out; + } + * \endcode + * + */ + template inline T byteSwap(T val) + { + T out; + std::ignore = swapBytes(val, as_writable_bytes(out)); + return out; + } +} +#endif // CODA_OSS_sys_ByteSwapValue_h_INCLUDED_ diff --git a/externals/coda-oss/modules/c++/sys/include/sys/Conf.h b/externals/coda-oss/modules/c++/sys/include/sys/Conf.h index 28d396c5c..df36708bb 100644 --- a/externals/coda-oss/modules/c++/sys/include/sys/Conf.h +++ b/externals/coda-oss/modules/c++/sys/include/sys/Conf.h @@ -66,23 +66,16 @@ #include #include #include -#include -#ifdef __GNUC__ -#include // "These functions are GNU extensions." -#endif #include #include #include #include #include -#include -#include -#include "coda_oss/span.h" -#include #include "str/Format.h" #include "sys/TimeStamp.h" +#include "sys/ByteSwap.h" /* Dance around the compiler to figure out */ @@ -205,205 +198,9 @@ namespace sys * RISC architectures we are big-endian. */ bool CODA_OSS_API isBigEndianSystem(); + bool CODA_OSS_API isLittleEndianSystem(); - /*! - * Swap bytes in-place. Note that a complex pixel - * is equivalent to two floats so elemSize and numElems - * must be adjusted accordingly. - * - * \param [inout] buffer to transform - * \param elemSize - * \param numElems - */ - void CODA_OSS_API byteSwap_(void* buffer, size_t elemSize, size_t numElems); - template - inline void byteSwap(T* buffer, size_t elemSize, size_t numElems) - { - // Trying to byte-swap structs can result in garbage because of padding. - static_assert(std::is_arithmetic::value || std::is_enum::value, "can only byte-swap numbers."); - if (elemSize != sizeof(T)) - { - throw std::invalid_argument("sizeof(T) != elemSize"); - } - byteSwap_(buffer, elemSize, numElems); - } - template - inline void byteSwap(coda_oss::span buffer) - { - constexpr auto elemSize = sizeof(T); - const auto numElems = buffer.size(); - byteSwap(buffer.data(), elemSize, numElems); - } - template - inline void byteSwap(coda_oss::span> buffer) - { - void* pBuffer = buffer.data(); - const coda_oss::span buffer_(static_cast(pBuffer), buffer.size() * 2); // real and imag - byteSwap(buffer_); - } - - inline void byteSwapV(void* buffer, unsigned short elemSize, size_t numElems) // existing API - { - byteSwap_(buffer, elemSize, numElems); - } - - /*! - * Swap bytes into output buffer. Note that a complex pixel - * is equivalent to two floats so elemSize and numElems - * must be adjusted accordingly. - * - * \param buffer to transform - * \param elemSize - * \param numElems - * \param[out] outputBuffer buffer to write swapped elements to - */ - void CODA_OSS_API byteSwap_(const void* buffer, size_t elemSize, size_t numElems, void* outputBuffer); - template - inline void byteSwap(const T* buffer, size_t elemSize, size_t numElems, - U* outputBuffer) // e.g., "unsigned int" && "int" - { - // Trying to byte-swap structs can result in garbage because of padding. - static_assert(std::is_arithmetic::value || std::is_enum::value, "can only byte-swap numbers."); - static_assert(std::is_arithmetic::value || std::is_enum::value, "can only byte-swap numbers."); - static_assert(sizeof(T) > 1, "byte-swapping a single-byte value makes no sense."); - //static_assert(sizeof(T) == sizeof(U), "sizeof(T) != sizeof(U)."); // outputBuffer could be std::byte - if (elemSize != sizeof(T)) - { - throw std::invalid_argument("sizeof(T) != elemSize"); - } - byteSwap_(buffer, elemSize, numElems, outputBuffer); - } - template - inline void byteSwap(coda_oss::span buffer, coda_oss::span outputBuffer) // e.g., "unsigned int" && "int" - { - const auto numElems = buffer.size(); - if (numElems != outputBuffer.size()) - { - throw std::invalid_argument("buffer.size() != outputBuffer.size()"); - } - constexpr auto elemSize = sizeof(T); - byteSwap(buffer.data(), elemSize, numElems, outputBuffer.data()); - } - template - inline void byteSwap(coda_oss::span> buffer, coda_oss::span> outputBuffer) - { - const void* pBuffer = buffer.data(); - const coda_oss::span buffer_(static_cast(pBuffer), buffer.size() * 2); // real and imag - void* pOutputBuffer = outputBuffer.data(); - const coda_oss::span outputBuffer_(static_cast(pOutputBuffer), outputBuffer.size() * 2); // real and imag - - byteSwap(buffer_, outputBuffer_); - } - - inline void byteSwapV(const void* buffer, unsigned short elemSize, size_t numElems, void* outputBuffer) // existing API - { - byteSwap_(buffer, elemSize, numElems, outputBuffer); - } - - /*! - * Function to swap one element irrespective of size. The inplace - * buffer function should be preferred. - * - * To specialize complex float, first include the complex library - * \code - #include - * \endcode - * - * Then put an overload in as specified below: - * \code - template std::complex byteSwap(std::complex val) - { - std::complex out(byteSwap(val.real()), - byteSwap(val.imag())); - return out; - } - * \endcode - * - */ - template inline T byteSwap_(T val) - { - // Trying to byte-swap structs can result in garbage because of padding. - static_assert(std::is_arithmetic::value || std::is_enum::value, "can only byte-swap numbers"); - - constexpr auto size = sizeof(T); - T out; - - unsigned char* cOut = reinterpret_cast(&out); - unsigned char* cIn = reinterpret_cast(&val); - for (size_t i = 0, j = size - 1; i < j; ++i, --j) - { - cOut[i] = cIn[j]; - cOut[j] = cIn[i]; - } - return out; - } - template inline T byteSwap(T val) - { - return byteSwap_(val); - } - inline uint8_t byteSwap(uint8_t val) - { - return val; // no-op - } -#if defined(_MSC_VER) - // These routines should geneerate a single instruction; see https://devblogs.microsoft.com/cppblog/a-tour-of-4-msvc-backend-improvements/ - inline uint16_t byteSwap(uint16_t val) - { - return _byteswap_ushort(val); - } - inline uint32_t byteSwap(uint32_t val) - { - return _byteswap_ulong(val); - } - inline uint64_t byteSwap(uint64_t val) - { - return _byteswap_uint64(val); - } -#elif defined(__GNUC__) - inline uint16_t byteSwap(uint16_t val) - { - return bswap_16(val); - } - inline uint32_t byteSwap(uint32_t val) - { - return bswap_32(val); - } - inline uint64_t byteSwap(uint64_t val) - { - return bswap_64(val); - } -#endif - template - inline T byteSwapValue_(T val) - { - static_assert(sizeof(T) > 1, "byte-swapping a single-byte value makes no sense."); - static_assert(sizeof(T) == sizeof(TUInt), "sizeof(T) != sizeof()"); - static_assert(std::is_unsigned::value, "TUInt must be 'unsigned'"); - - const void* pVal = &val; - const auto pUInt = static_cast(pVal); - const auto result = byteSwap(*pUInt); - - const void* pResult = &result; - const auto pRetval = static_cast(pResult); - return *pRetval; - } - inline float byteSwap(float val) - { - return byteSwapValue_(val); - } - inline double byteSwap(double val) - { - return byteSwapValue_(val); - } - template - inline std::complex byteSwap(std::complex v) - { - std::complex retval{byteSwap(v.real()), byteSwap(v.imag())}; - return retval; - } - /*! * Method to create a block of memory on an alignment * boundary specified by the user. @@ -459,7 +256,6 @@ namespace sys #endif } - } // https://en.wikipedia.org/wiki/Year_2038_problem diff --git a/externals/coda-oss/modules/c++/sys/include/sys/Span.h b/externals/coda-oss/modules/c++/sys/include/sys/Span.h new file mode 100644 index 000000000..23b72d02c --- /dev/null +++ b/externals/coda-oss/modules/c++/sys/include/sys/Span.h @@ -0,0 +1,188 @@ +/* ========================================================================= + * This file is part of sys-c++ + * ========================================================================= + * + * (C) Copyright 2004 - 2014, MDA Information Systems LLC + * (C) Copyright 2023, Maxar Technologies, Inc. + * + * sys-c++ is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; If not, + * see . + * + */ + +#ifndef CODA_OSS_sys_Span_h_INCLUDED_ +#define CODA_OSS_sys_Span_h_INCLUDED_ +#pragma once + +#include +#include +#include +#include + +namespace sys // not "mem", it depends on sys. +{ + +// Creating a `span` is verbose w/o deduction guidelines in C++17. +// Some overloads to ease the pain. +template +inline coda_oss::span make_span(const T* ptr, size_t sz) noexcept +{ + return coda_oss::span(ptr, sz); +} +template +inline coda_oss::span make_const_span(T* ptr, size_t sz) noexcept +{ + return coda_oss::span(ptr, sz); +} +template +inline coda_oss::span make_writable_span(T* ptr, size_t sz) noexcept // c.f., as_writable_bytes() +{ + return coda_oss::span(ptr, sz); +} +template +inline coda_oss::span make_span(T* ptr, size_t sz) noexcept +{ + return make_writable_span(ptr, sz); +} + +template +inline auto make_span(const void* ptr, size_t sz) noexcept +{ + return make_span(static_cast(ptr), sz); +} +template +inline auto make_const_span(void* ptr, size_t sz) noexcept +{ + return make_const_span(static_cast(ptr), sz); +} +template +inline auto make_span(void* ptr, size_t sz) noexcept +{ + return make_writable_span(static_cast(ptr), sz); +} + +template +inline auto make_const_span(coda_oss::span v) noexcept // turn span into span +{ + return make_const_span(v.data(), v.size()); +} + +template +inline auto make_span(const std::vector& v) noexcept +{ + return make_span(v.data(), v.size()); +} +template +inline auto make_const_span(std::vector& v) noexcept +{ + return make_const_span(v.data(), v.size()); +} +template +inline auto make_span(std::vector& v) noexcept +{ + return make_writable_span(v.data(), v.size()); +} + +template +inline auto make_span(const std::array& v) noexcept +{ + return make_span(v.data(), v.size()); +} +template +inline auto make_const_span(std::array& v) noexcept +{ + return make_const_span(v.data(), v.size()); +} +template +inline auto make_span(std::array& v) noexcept +{ + return make_writable_span(v.data(), v.size()); +} + +template +inline auto make_span(const T (&a)[N]) noexcept +{ + return make_span(a, N); +} +template +inline auto make_const_span(T (&a)[N]) noexcept +{ + return make_const_span(a, N); +} +template +inline auto make_span(T (&a)[N]) noexcept +{ + return make_writable_span(a, N); +} + +// Calling as_bytes() or as_writable_bytes() requires a span, which as +// noted above is a nuisance to create w/o C++17 +template +inline auto as_bytes(const T* ptr, size_t sz) noexcept +{ + return coda_oss::as_bytes(make_span(ptr, sz)); +} +template +inline auto as_writable_bytes(T* ptr, size_t sz) noexcept +{ + return coda_oss::as_writable_bytes(make_writable_span(ptr, sz)); +} + +template +inline auto as_bytes(const std::vector& v) noexcept +{ + return as_bytes(v.data(), v.size()); +} +template +inline auto as_writable_bytes(std::vector& v) noexcept +{ + return as_writable_bytes(v.data(), v.size()); +} + +template +inline auto as_bytes(const std::array& v) noexcept +{ + return as_bytes(v.data(), v.size()); +} +template +inline auto as_writable_bytes(std::array& v) noexcept +{ + return as_writable_bytes(v.data(), v.size()); +} + +template +inline auto as_bytes(const T (&a)[N]) noexcept +{ + return as_bytes(a, N); +} +template +inline auto as_writable_bytes(T (&a)[N]) noexcept +{ + return as_writable_bytes(a, N); +} + +// "cast" a single value to bytes +template +inline auto as_bytes(const T& v) noexcept +{ + return as_bytes(&v, 1); +} +template +inline auto as_writable_bytes(T& v) noexcept +{ + return as_writable_bytes(&v, 1); +} + +} +#endif // CODA_OSS_sys_Span_h_INCLUDED_ diff --git a/externals/coda-oss/modules/c++/sys/source/Conf.cpp b/externals/coda-oss/modules/c++/sys/source/Conf.cpp index 414374d5a..bf29dd69b 100644 --- a/externals/coda-oss/modules/c++/sys/source/Conf.cpp +++ b/externals/coda-oss/modules/c++/sys/source/Conf.cpp @@ -19,6 +19,7 @@ * see . * */ +#include "sys/Conf.h" #include @@ -26,49 +27,100 @@ #include #include #include - -#include "sys/Conf.h" #include "coda_oss/bit.h" +#include "coda_oss/cstddef.h" +#include "coda_oss/span.h" -using endian = coda_oss::endian; +#include "sys/Span.h" // https://en.cppreference.com/w/cpp/types/endian -inline bool is_big_endian() +using endian = coda_oss::endian; +template +inline bool is_big_endian_() +{ + throw std::logic_error("Mixed-endian not supported."); +} +template<> +inline constexpr bool is_big_endian_() +{ + return true; +} +template <> +inline constexpr bool is_big_endian_() +{ + return false; +} +constexpr inline bool is_big_endian() +{ + return is_big_endian_(); +} + +// Want to explicitly test against both endian::bit and endian::little; i.e., +// because of "mixed" endianness, little may not the same as !big +template +inline bool is_little_endian_() { - auto endianness = endian::native; // "conditional expression is constant" - if (endianness == endian::big) - { - return true; - } - if (endianness == endian::little) - { - return false; - } throw std::logic_error("Mixed-endian not supported."); } +template <> +inline constexpr bool is_little_endian_() +{ + return false; +} +template <> +inline constexpr bool is_little_endian_() +{ + return true; +} +constexpr inline bool is_little_endian() +{ + return is_little_endian_(); +} + constexpr inline bool is_big_or_little_endian() { - return (endian::native == endian::big) || (endian::native == endian::little) ? true : false; + return is_big_endian() || is_little_endian(); } -inline bool isBigEndianSystem() +inline bool testIsBigEndianSystem() { // This is an endian test int intVal = 1; unsigned char* endianTest = (unsigned char*)&intVal; return endianTest[0] != 1; } -bool sys::isBigEndianSystem() +inline auto isBigEndianSystem_() { static_assert(is_big_or_little_endian(), "Mixed-endian not supported."); - const auto retval = ::isBigEndianSystem(); + const auto retval = testIsBigEndianSystem(); if (retval != is_big_endian()) { throw std::logic_error("endian values don't agree!"); } return retval; } - +inline auto isLittleEndianSystem_() +{ + static_assert(is_big_or_little_endian(), "Mixed-endian not supported."); + const auto retval = !testIsBigEndianSystem(); + if (retval != is_little_endian()) + { + throw std::logic_error("endian values don't agree!"); + } + return retval; +} +bool sys::isBigEndianSystem() +{ + auto const retval = isBigEndianSystem_(); + assert(!retval == isLittleEndianSystem_()); + return retval; +} +bool sys::isLittleEndianSystem() +{ + auto const retval = isLittleEndianSystem_(); + assert(!retval == isBigEndianSystem_()); + return retval; +} /*! * Swap bytes in-place. Note that a complex pixel @@ -79,9 +131,67 @@ bool sys::isBigEndianSystem() * \param elemSize * \param numElems */ -void sys::byteSwap_(void* buffer, size_t elemSize, size_t numElems) +template +inline void byteSwap_n_(void *buffer_, size_t numElems) { - byteSwap_(buffer, elemSize, numElems, buffer); + static_assert(std::is_unsigned::value, "TUInt must be 'unsigned'"); + const auto buffer = sys::make_span(buffer_, numElems); + assert(buffer.size_bytes() == sizeof(TUInt) * numElems); + + for (auto& v : buffer) + { + v = sys::byteSwap(v); + } +} +template +inline void byteSwap_n(void *buffer, size_t elemSize, size_t numElems) +{ + if (sizeof(TUInt) != elemSize) + { + throw std::invalid_argument("'elemSize' != sizeof(TUInt)"); + } + byteSwap_n_(buffer, numElems); +} +void sys::byteSwap(void* buffer, size_t elemSize, size_t numElems) +{ + if ((buffer == nullptr) || (elemSize < 2) || (numElems == 0)) + return; + + switch (elemSize) + { + case sizeof(uint16_t): return byteSwap_n(buffer, elemSize, numElems); + case sizeof(uint32_t): return byteSwap_n(buffer, elemSize, numElems); + case sizeof(uint64_t): return byteSwap_n(buffer, elemSize, numElems); + default: break; + } + + auto const bufferPtr = static_cast(buffer); + const auto half = elemSize >> 1; + size_t offset = 0, innerOff = 0, innerSwap = 0; + for (size_t i = 0; i < numElems; ++i, offset += elemSize) + { + for (size_t j = 0; j < half; ++j) + { + innerOff = offset + j; + innerSwap = offset + elemSize - 1 - j; + + std::swap(bufferPtr[innerOff], bufferPtr[innerSwap]); + } + } +} +coda_oss::span sys::byteSwap(coda_oss::span buffer, size_t elemSize) +{ + if ((buffer.size() == 0) || (elemSize < 2)) + return sys::make_const_span(buffer); + + size_t const numElems = buffer.size() / elemSize; + if ((numElems * elemSize) != buffer.size()) + { + throw std::invalid_argument("'buffer' is not a multiple of 'elemSize'"); + } + + byteSwap(buffer.data(), elemSize, numElems); + return sys::make_const_span(buffer); } /*! @@ -95,61 +205,106 @@ void sys::byteSwap_(void* buffer, size_t elemSize, size_t numElems) * \param[out] outputBuffer buffer to write swapped elements to */ template -inline void byteSwap_n(const void *buffer_, size_t elemSize, size_t numElems, void *outputBuffer_) +inline void byteSwap_n_(const void *buffer_, size_t numElems, void *outputBuffer_) { static_assert(std::is_unsigned::value, "TUInt must be 'unsigned'"); - using value_type = TUInt; - assert(sizeof(value_type) == elemSize); - std::ignore = elemSize; - const coda_oss::span buffer(static_cast(buffer_), numElems); - assert(buffer.size_bytes() == elemSize * numElems); - const coda_oss::span outputBuffer(static_cast(outputBuffer_), numElems); + const auto buffer = sys::make_span(buffer_, numElems); + assert(buffer.size_bytes() == sizeof(TUInt) * numElems); + const auto outputBuffer = sys::make_span(outputBuffer_, numElems); - std::transform(buffer.begin(), buffer.end(), outputBuffer.begin(), [](const auto& v) { return sys::byteSwap(v); }); + const auto byteSwap = [](const auto& v) { return sys::byteSwap(v); }; + std::transform(buffer.begin(), buffer.end(), outputBuffer.begin(), byteSwap); } - -void sys::byteSwap_(const void* buffer, - size_t elemSize, - size_t numElems, - void* outputBuffer) +template +inline void byteSwap_n(const void *buffer, size_t elemSize, size_t numElems, void *outputBuffer) { - if (!numElems || !buffer || !outputBuffer) - { - return; - } - - if (elemSize == 2) + if (sizeof(TUInt) != elemSize) { - return byteSwap_n(buffer, elemSize, numElems, outputBuffer); + throw std::invalid_argument("'elemSize' != sizeof(TUInt)"); } - if (elemSize == 4) + byteSwap_n_(buffer, numElems, outputBuffer); +} +void sys::byteSwap(const void* buffer, size_t elemSize, size_t numElems, void* outputBuffer) +{ + if ((numElems == 0) || (buffer == nullptr) || (outputBuffer == nullptr)) { - return byteSwap_n(buffer, elemSize, numElems, outputBuffer); + return; } - if (elemSize == 8) + + switch (elemSize) { - return byteSwap_n(buffer, elemSize, numElems, outputBuffer); + case 1: std::ignore = memcpy(outputBuffer, buffer, elemSize * numElems); return; + case 2: return byteSwap_n(buffer, elemSize, numElems, outputBuffer); + case 4: return byteSwap_n(buffer, elemSize, numElems, outputBuffer); + case 8: return byteSwap_n(buffer, elemSize, numElems, outputBuffer); + default: break; } - const sys::byte* bufferPtr = static_cast(buffer); - sys::byte* outputBufferPtr = static_cast(outputBuffer); + auto const bufferPtr = static_cast(buffer); + auto const outputBufferPtr = static_cast(outputBuffer); const auto half = elemSize >> 1; size_t offset = 0; - for (size_t ii = 0; ii < numElems; ++ii, offset += elemSize) { - for (unsigned short jj = 0; jj < half; ++jj) + for (size_t jj = 0; jj < half; ++jj) { const size_t innerOff = offset + jj; const size_t innerSwap = offset + elemSize - 1 - jj; - // could be the same buffer, see overload above - const auto bufferInner = bufferPtr[innerSwap]; - const auto bufferOff = bufferPtr[innerOff]; - outputBufferPtr[innerOff] = bufferInner; - outputBufferPtr[innerSwap] = bufferOff; + outputBufferPtr[innerOff] = bufferPtr[innerSwap]; + outputBufferPtr[innerSwap] = bufferPtr[innerOff]; } } } +coda_oss::span sys::byteSwap(coda_oss::span buffer, + size_t elemSize, coda_oss::span outputBuffer) +{ + if ((buffer.size() == 0) || (outputBuffer.size() == 0)) + { + return sys::make_const_span(outputBuffer); + } + + size_t const numElems = buffer.size() / elemSize; + if ((numElems * elemSize) != buffer.size()) + { + throw std::invalid_argument("'buffer' is not a multiple of 'elemSize'"); + } + if (buffer.size() != outputBuffer.size()) + { + throw std::invalid_argument("'buffer' and 'outputBuffer' are different sizes'"); + } + + byteSwap(buffer.data(), elemSize, numElems, outputBuffer.data()); + return sys::make_const_span(outputBuffer); +} + + coda_oss::span sys::byteSwap( + coda_oss::span inPtr, + coda_oss::span outPtr) +{ + if (inPtr.size() != outPtr.size()) + { + throw std::invalid_argument("'size of byte buffers must match"); + } + + const auto elemSize = inPtr.size(); + switch (elemSize) + { + case sizeof(uint8_t): return details::swapUIntBytes(inPtr, outPtr, std::nothrow); + case sizeof(uint16_t): return details::swapUIntBytes(inPtr, outPtr, std::nothrow); + case sizeof(uint32_t): return details::swapUIntBytes(inPtr, outPtr, std::nothrow); + case sizeof(uint64_t): return details::swapUIntBytes(inPtr, outPtr, std::nothrow); + default: break; + } + + for (size_t ii = 0, jj = elemSize - 1; ii < jj; ++ii, --jj) + { + outPtr[ii] = inPtr[jj]; + outPtr[jj] = inPtr[ii]; + } + + // Give the raw byte-swapped bytes back to the caller for easy serialization + return make_const_span(outPtr); +} diff --git a/externals/coda-oss/modules/c++/sys/unittests/test_byte_swap.cpp b/externals/coda-oss/modules/c++/sys/unittests/test_byte_swap.cpp index 6ef347cb3..954872bb0 100644 --- a/externals/coda-oss/modules/c++/sys/unittests/test_byte_swap.cpp +++ b/externals/coda-oss/modules/c++/sys/unittests/test_byte_swap.cpp @@ -22,14 +22,14 @@ #include "TestCase.h" -#include - #include +#include #include // std::endian -#include // std::byte +#include #include #include +#include TEST_CASE(testEndianness) { @@ -72,29 +72,32 @@ TEST_CASE(testEndianness) } } -TEST_CASE(testByteSwap) +static std::vector make_origValues(size_t NUM_PIXELS) { ::srand(334); - static const size_t NUM_PIXELS = 10000; - std::vector origValues(NUM_PIXELS); + std::vector retval(NUM_PIXELS); for (size_t ii = 0; ii < NUM_PIXELS; ++ii) { const auto value = static_cast(::rand()) / RAND_MAX * - std::numeric_limits::max(); - origValues[ii] = static_cast(value); + std::numeric_limits::max(); + retval[ii] = static_cast(value); } + return retval; +} - // Byte swap the old-fashioned way - std::vector values1(origValues); - sys::byteSwap(&values1[0], sizeof(sys::Uint64_T), NUM_PIXELS); +TEST_CASE(testByteSwap) +{ + constexpr size_t NUM_PIXELS = 10000; + const auto origValues = make_origValues(NUM_PIXELS); + // Byte swap the old-fashioned way + auto values1(origValues); + sys::byteSwap(values1.data(), sizeof(uint64_t), NUM_PIXELS); // Byte swap into output buffer - const std::vector values2(origValues); - std::vector swappedValues2(values2.size()); - sys::byteSwap(&values2[0], sizeof(sys::Uint64_T), NUM_PIXELS, - &swappedValues2[0]); + std::vector swappedValues2(origValues.size()); + sys::byteSwap(origValues.data(), sizeof(uint64_t), NUM_PIXELS, swappedValues2.data()); // Everything should match for (size_t ii = 0; ii < NUM_PIXELS; ++ii) @@ -103,6 +106,7 @@ TEST_CASE(testByteSwap) } } +// 0xnn is an `int` which can't be used to initialize std::byte w/o a cast #define CODA_OSS_define_byte(v) constexpr static std::byte v = static_cast(0 ## v) CODA_OSS_define_byte(x00); CODA_OSS_define_byte(x11); @@ -127,263 +131,133 @@ static constexpr std::byte four_bytes[]{x00, x11, xEE, xFF}; static constexpr std::byte eight_bytes[]{x00, x11, x22, x33, xCC, xDD, xEE, xFF}; static constexpr std::byte sixteen_bytes[]{x00, x11, x22, x33, x44, x55, x66, x77, x88, x99, xAA, xBB, xCC, xDD, xEE, xFF}; -TEST_CASE(testByteSwapUInt16) +template +static void testByteSwapValues_(const std::string& testName, const void* pBytes) { - const void* pBytes = &(two_bytes[0]); - auto pUInt16 = static_cast(pBytes); - auto swap = sys::byteSwap(*pUInt16); - TEST_ASSERT_NOT_EQ(*pUInt16, swap); - const void* pResult_ = &swap; - auto pResultBytes = static_cast(pResult_); - TEST_ASSERT(pResultBytes[0] == two_bytes[1]); - TEST_ASSERT(pResultBytes[1] == two_bytes[0]); - swap = sys::byteSwap(swap); // swap back - TEST_ASSERT_EQ(*pUInt16, swap); - - // array swap from input to output - pBytes = &(sixteen_bytes[0]); - const auto buffer = static_cast(pBytes); - std::array outputBuffer; - sys::byteSwap(buffer, sizeof(uint16_t), outputBuffer.size(), outputBuffer.data()); - for (auto&& v : outputBuffer) - { - swap = sys::byteSwap(v); - TEST_ASSERT_NOT_EQ(v, swap); - swap = sys::byteSwap(swap); // swap back - TEST_ASSERT_EQ(v, swap); - } + auto pUInt = static_cast(pBytes); + auto swap = sys::byteSwap(*pUInt); + TEST_ASSERT_NOT_EQ(*pUInt, swap); - // in-place swap - sys::byteSwap(outputBuffer.data(), sizeof(uint16_t), outputBuffer.size()); - pBytes = outputBuffer.data(); - pResultBytes = static_cast(pBytes); - for (size_t i=0; i<16; i++) - { - TEST_ASSERT(pResultBytes[i] == sixteen_bytes[i]); - } -} - -TEST_CASE(testByteSwapUInt32) -{ - const void* pBytes = &(four_bytes[0]); - auto pUInt32 = static_cast(pBytes); - auto swap = sys::byteSwap(*pUInt32); - TEST_ASSERT_NOT_EQ(*pUInt32, swap); const void* pResult_ = &swap; - auto pResultBytes = static_cast(pResult_); - TEST_ASSERT(pResultBytes[0] == four_bytes[3]); - TEST_ASSERT(pResultBytes[1] == four_bytes[2]); - TEST_ASSERT(pResultBytes[2] == four_bytes[1]); - TEST_ASSERT(pResultBytes[3] == four_bytes[0]); - swap = sys::byteSwap(swap); // swap back - TEST_ASSERT_EQ(*pUInt32, swap); - - // array swap from input to output - pBytes = &(sixteen_bytes[0]); - const auto buffer = static_cast(pBytes); - std::array outputBuffer; - sys::byteSwap(buffer, sizeof(uint32_t), outputBuffer.size(), outputBuffer.data()); - for (auto&& v : outputBuffer) - { - swap = sys::byteSwap(v); - TEST_ASSERT_NOT_EQ(v, swap); - swap = sys::byteSwap(swap); // swap back - TEST_ASSERT_EQ(v, swap); - } - - // in-place swap - sys::byteSwap(outputBuffer.data(), sizeof(uint32_t), outputBuffer.size()); - pBytes = outputBuffer.data(); - pResultBytes = static_cast(pBytes); - for (size_t i=0; i<16; i++) + auto const pResultBytes = static_cast(pResult_); + auto const pValueBytes = static_cast(pBytes); + for (size_t i = 0, j = sizeof(TUInt); i < sizeof(TUInt) && j > 0; i++, j--) { - TEST_ASSERT(pResultBytes[i] == sixteen_bytes[i]); + TEST_ASSERT(pResultBytes[i] == pValueBytes[j-1]); } -} -TEST_CASE(testByteSwapUInt64) -{ - const void* pBytes = &(eight_bytes[0]); - auto pUInt64 = static_cast(pBytes); - auto swap = sys::byteSwap(*pUInt64); - TEST_ASSERT_NOT_EQ(*pUInt64, swap); - const void* pResult_ = &swap; - auto pResultBytes = static_cast(pResult_); - TEST_ASSERT(pResultBytes[0] == eight_bytes[7]); - TEST_ASSERT(pResultBytes[1] == eight_bytes[6]); - TEST_ASSERT(pResultBytes[2] == eight_bytes[5]); - TEST_ASSERT(pResultBytes[3] == eight_bytes[4]); - TEST_ASSERT(pResultBytes[4] == eight_bytes[3]); - TEST_ASSERT(pResultBytes[5] == eight_bytes[2]); - TEST_ASSERT(pResultBytes[6] == eight_bytes[1]); - TEST_ASSERT(pResultBytes[7] == eight_bytes[0]); swap = sys::byteSwap(swap); // swap back - TEST_ASSERT_EQ(*pUInt64, swap); - - // array swap from input to output - pBytes = &(sixteen_bytes[0]); - const auto buffer = static_cast(pBytes); - std::array outputBuffer; - sys::byteSwap(buffer, sizeof(uint64_t), outputBuffer.size(), outputBuffer.data()); - for (auto&& v : outputBuffer) - { - swap = sys::byteSwap(v); - TEST_ASSERT_NOT_EQ(v, swap); - swap = sys::byteSwap(swap); // swap back - TEST_ASSERT_EQ(v, swap); - } + TEST_ASSERT_EQ(*pUInt, swap); - // in-place swap - sys::byteSwap(outputBuffer.data(), sizeof(uint64_t), outputBuffer.size()); - pBytes = outputBuffer.data(); - pResultBytes = static_cast(pBytes); - for (size_t i=0; i<16; i++) - { - TEST_ASSERT(pResultBytes[i] == sixteen_bytes[i]); - } -} + // swap as an "array" of one value + sys::byteSwap(pUInt, sizeof(TUInt), 1, &swap); + TEST_ASSERT_NOT_EQ(*pUInt, swap); + sys::byteSwap(&swap, sizeof(TUInt), 1); // swap back + TEST_ASSERT_EQ(*pUInt, swap); -TEST_CASE(testByteSwapFloat) -{ - static_assert(sizeof(float) == sizeof(uint32_t), "sizeof(float) != sizeof(uint32_t)"); - - const float v = 3.141592654f; - const void* pVoid = &v; - auto pFloat = static_cast(pVoid); - auto swap = sys::byteSwap(*pFloat); - // The swapped bits could be nonsense as a `float`, so comparing might not work - //TEST_ASSERT_NOT_EQ(*pFloat, swap); - const void* pResult_ = &swap; - auto pResultBytes = static_cast(pResult_); - auto pValueBytes = static_cast(pVoid); - TEST_ASSERT(pResultBytes[0] == pValueBytes[3]); - TEST_ASSERT(pResultBytes[1] == pValueBytes[2]); - TEST_ASSERT(pResultBytes[2] == pValueBytes[1]); - TEST_ASSERT(pResultBytes[3] == pValueBytes[0]); - swap = sys::byteSwap(swap); // swap back - TEST_ASSERT_EQ(*pFloat, swap); - - // array swap from input to output - const float values[] = {1.0f, 2.0f, 3.0f, 4.0f}; - pVoid = &(values[0]); - const auto buffer = static_cast(pVoid); - std::array outputBuffer; - sys::byteSwap(buffer, sizeof(float), outputBuffer.size(), outputBuffer.data()); - for (size_t i = 0; i 0; i++, j--) { - // can't test swapped bytes against anything; might not be a valid `float` - swap = sys::byteSwap(outputBuffer[i]); - TEST_ASSERT_EQ(values[i], swap); - } - - // in-place swap - sys::byteSwap(outputBuffer.data(), sizeof(float), outputBuffer.size()); - for (size_t i = 0; i < outputBuffer.size(); i++) - { - TEST_ASSERT_EQ(values[i], outputBuffer[i]); + TEST_ASSERT(resultBytes[i] == pValueBytes[j - 1]); } } - -TEST_CASE(testByteSwapDouble) +TEST_CASE(testByteSwapValues) { - using value_type = double; - static_assert(sizeof(value_type) == sizeof(uint64_t), "sizeof(value_type) != sizeof(uint64_t)"); - - const value_type v = 3.141592654; - const void* pVoid = &v; - auto pDouble = static_cast(pVoid); - auto swap = sys::byteSwap(*pDouble); - // The swapped bits could be nonsense as a `double`, so comparing might not work - //TEST_ASSERT_NOT_EQ(*pDouble, swap); - const void* pResult_ = &swap; - auto pResultBytes = static_cast(pResult_); - auto pValueBytes = static_cast(pVoid); - TEST_ASSERT(pResultBytes[0] == pValueBytes[7]); - TEST_ASSERT(pResultBytes[1] == pValueBytes[6]); - TEST_ASSERT(pResultBytes[2] == pValueBytes[5]); - TEST_ASSERT(pResultBytes[3] == pValueBytes[4]); - TEST_ASSERT(pResultBytes[4] == pValueBytes[3]); - TEST_ASSERT(pResultBytes[5] == pValueBytes[2]); - TEST_ASSERT(pResultBytes[6] == pValueBytes[1]); - TEST_ASSERT(pResultBytes[7] == pValueBytes[0]); - swap = sys::byteSwap(swap); // swap back - TEST_ASSERT_EQ(*pDouble, swap); - - // array swap from input to output - const std::vector values = {1.0, 2.0}; - const std::span buffer(values.data(), values.size()); - std::array outputBuffer; - const std::span outputBuffer_(outputBuffer.data(), outputBuffer.size()); - sys::byteSwap(buffer, outputBuffer_); - for (size_t i = 0; i < outputBuffer_.size(); i++) - { - // can't test swapped bytes against anything; might not be a valid `double` - swap = sys::byteSwap(outputBuffer_[i]); - TEST_ASSERT_EQ(values[i], swap); - } - - // in-place swap - sys::byteSwap(outputBuffer_); - for (size_t i = 0; i < outputBuffer_.size(); i++) - { - TEST_ASSERT_EQ(values[i], outputBuffer_[i]); - } + testByteSwapValues_(testName, two_bytes); + testByteSwapValues_(testName, four_bytes); + testByteSwapValues_(testName, eight_bytes); } -TEST_CASE(testByteSwapCxFloat) +TEST_CASE(testByteSwap12) { - using value_type = std::complex; - static_assert(sizeof(value_type) == sizeof(uint64_t), "sizeof(value_type) != sizeof(uint64_t)"); - - const value_type v = {1.1f, -9.9f}; - const void* pVoid = &v; - auto pCxFloat = static_cast(pVoid); - auto swap = sys::byteSwap(*pCxFloat); - // The swapped bits could be nonsense as a `double`, so comparing might not work - //TEST_ASSERT_NOT_EQ(*pDouble, swap); - const void* pResult_ = &swap; - auto pResultBytes = static_cast(pResult_); - auto pValueBytes = static_cast(pVoid); - TEST_ASSERT(pResultBytes[0] == pValueBytes[3]); - TEST_ASSERT(pResultBytes[1] == pValueBytes[2]); - TEST_ASSERT(pResultBytes[2] == pValueBytes[1]); - TEST_ASSERT(pResultBytes[3] == pValueBytes[0]); + // test a goofy element size + static constexpr std::byte twelve_bytes[]{ + x00, x11, x22, x33, x44, x55, + x99, xAA, xBB, xDD, xEE, xFF}; + const auto pValueBytes = sys::as_bytes(twelve_bytes); + + std::vector swappedValues(12); + auto pResultBytes = sys::make_span(swappedValues); + + auto elemSize = 12; + auto numElements = swappedValues.size() / elemSize; + sys::byteSwap(twelve_bytes, elemSize, numElements, pResultBytes.data()); + TEST_ASSERT(pResultBytes[0] == pValueBytes[11]); + TEST_ASSERT(pResultBytes[1] == pValueBytes[10]); + TEST_ASSERT(pResultBytes[2] == pValueBytes[9]); + TEST_ASSERT(pResultBytes[3] == pValueBytes[8]); TEST_ASSERT(pResultBytes[4] == pValueBytes[7]); TEST_ASSERT(pResultBytes[5] == pValueBytes[6]); TEST_ASSERT(pResultBytes[6] == pValueBytes[5]); TEST_ASSERT(pResultBytes[7] == pValueBytes[4]); - swap = sys::byteSwap(swap); // swap back - TEST_ASSERT_EQ(*pCxFloat, swap); - - // array swap from input to output - const std::vector values = {{1.1f, -9.9f}, {-22.22f, 88.88f}}; - const std::span buffer(values.data(), values.size()); - std::array outputBuffer; - const std::span outputBuffer_(outputBuffer.data(), outputBuffer.size()); - sys::byteSwap(buffer, outputBuffer_); - for (size_t i = 0; i(pValueBytes, pResultBytes); + TEST_ASSERT(result[0] == pValueBytes[11]); + TEST_ASSERT(result[1] == pValueBytes[10]); + TEST_ASSERT(result[2] == pValueBytes[9]); + TEST_ASSERT(result[3] == pValueBytes[8]); + TEST_ASSERT(result[4] == pValueBytes[7]); + TEST_ASSERT(result[5] == pValueBytes[6]); + TEST_ASSERT(result[6] == pValueBytes[5]); + TEST_ASSERT(result[7] == pValueBytes[4]); + TEST_ASSERT(result[8] == pValueBytes[3]); + TEST_ASSERT(result[9] == pValueBytes[2]); + TEST_ASSERT(result[10] == pValueBytes[1]); + TEST_ASSERT(result[11] == pValueBytes[0]); + + + elemSize = 6; // note that an ODD size doesn't work correctly + numElements = swappedValues.size() / elemSize; + sys::byteSwap(twelve_bytes, elemSize, numElements, swappedValues.data()); + TEST_ASSERT(pResultBytes[0] == pValueBytes[5]); + TEST_ASSERT(pResultBytes[1] == pValueBytes[4]); + TEST_ASSERT(pResultBytes[2] == pValueBytes[3]); + TEST_ASSERT(pResultBytes[3] == pValueBytes[2]); + TEST_ASSERT(pResultBytes[4] == pValueBytes[1]); + TEST_ASSERT(pResultBytes[5] == pValueBytes[0]); + + TEST_ASSERT(pResultBytes[6] == pValueBytes[11]); + TEST_ASSERT(pResultBytes[7] == pValueBytes[10]); + TEST_ASSERT(pResultBytes[8] == pValueBytes[9]); + TEST_ASSERT(pResultBytes[9] == pValueBytes[8]); + TEST_ASSERT(pResultBytes[10] == pValueBytes[7]); + TEST_ASSERT(pResultBytes[11] == pValueBytes[6]); + + sys::byteSwap(swappedValues.data(), elemSize, numElements); // swap back + for (size_t i = 0; i < swappedValues.size(); i++) { - // can't test swapped bytes against anything; might not be a valid `double` - swap = sys::byteSwap(outputBuffer_[i]); - TEST_ASSERT_EQ(values[i], swap); + TEST_ASSERT(pResultBytes[i] == pValueBytes[i]); } +} - // in-place swap - sys::byteSwap(outputBuffer_); - for (size_t i = 0; i < outputBuffer_.size(); i++) - { - TEST_ASSERT_EQ(values[i], outputBuffer_[i]); - } +template +static inline void six_byteSwap(const void* in, T& out) +{ + auto const inBytes = sys::make_span(in, sizeof(T)); + out = sys::swapBytes(inBytes); +} +TEST_CASE(testSixByteSwap) +{ + const int i = 123; + int i_swapped; + six_byteSwap(&i, i_swapped); + TEST_ASSERT_NOT_EQ(i, i_swapped); + + int result; + six_byteSwap(&i_swapped, result); + TEST_ASSERT_EQ(i, result); } TEST_MAIN( TEST_CHECK(testEndianness); TEST_CHECK(testByteSwap); - TEST_CHECK(testByteSwapUInt16); - TEST_CHECK(testByteSwapUInt32); - TEST_CHECK(testByteSwapUInt64); - TEST_CHECK(testByteSwapFloat); - TEST_CHECK(testByteSwapDouble); - TEST_CHECK(testByteSwapCxFloat); + TEST_CHECK(testByteSwapValues); + TEST_CHECK(testByteSwap12); + TEST_CHECK(testSixByteSwap); ) - \ No newline at end of file diff --git a/externals/coda-oss/modules/python/sys/source/generated/coda_sys_wrap.cxx b/externals/coda-oss/modules/python/sys/source/generated/coda_sys_wrap.cxx index f4b4930c0..16000dd61 100644 --- a/externals/coda-oss/modules/python/sys/source/generated/coda_sys_wrap.cxx +++ b/externals/coda-oss/modules/python/sys/source/generated/coda_sys_wrap.cxx @@ -3566,7 +3566,7 @@ SWIGINTERN PyObject *_wrap_byteSwap__SWIG_0(PyObject *SWIGUNUSEDPARM(self), PyOb SWIG_exception_fail(SWIG_ArgError(ecode3), "in method '" "byteSwap" "', argument " "3"" of type '" "size_t""'"); } arg3 = static_cast< size_t >(val3); - sys::byteSwapV(arg1, arg2, arg3); + sys::byteSwap(arg1, arg2, arg3); resultobj = SWIG_Py_Void(); return resultobj; fail: @@ -3610,7 +3610,7 @@ SWIGINTERN PyObject *_wrap_byteSwap__SWIG_1(PyObject *SWIGUNUSEDPARM(self), PyOb if (!SWIG_IsOK(res4)) { SWIG_exception_fail(SWIG_ArgError(res4), "in method '" "byteSwap" "', argument " "4"" of type '" "void *""'"); } - sys::byteSwapV((void const *)arg1,arg2,arg3,arg4); + sys::byteSwap((void const *)arg1,arg2,arg3,arg4); resultobj = SWIG_Py_Void(); return resultobj; fail: