Implementing C++ byteswap functions

Implementing byteswap functions by using "backported" from C++23 std::byteswap.
Adding unittests based on GoogleTests. To enable it add `-DBUILD_TESTING=ON` to cmake.
This commit is contained in:
Azamat H. Hackimov 2024-04-23 21:10:18 +03:00
parent 86a6c139ee
commit c3e0102a4f
5 changed files with 171 additions and 106 deletions

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.19)
cmake_minimum_required(VERSION 3.20) # For using CMAKE_<LANG>_BYTE_ORDER
project(Descent3 VERSION 1.5.500)
@ -11,6 +11,18 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if(CMAKE_CXX_BYTE_ORDER STREQUAL "BIG_ENDIAN")
message(STATUS "Big Endian system detected.")
add_definitions("-DOUTRAGE_BIG_ENDIAN")
endif()
if(BUILD_TESTING)
find_package(GTest REQUIRED)
enable_testing()
include(GoogleTest)
add_subdirectory(tests)
endif()
if(NOT MSVC)
# check if this is some kind of clang (Clang, AppleClang, whatever)
# (convert compiler ID to lowercase so we match Clang, clang, AppleClang etc, regardless of case)

View File

@ -1,20 +1,21 @@
/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
* Descent 3
* Copyright (C) 2024 Parallax Software
* Copyright (C) 2024 Descent Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* $Logfile: /DescentIII/Main/lib/BYTESWAP.H $
@ -61,72 +62,53 @@
* $NoKeywords: $
*/
#ifndef _BYTESWAP_H
#define _BYTESWAP_H
#ifndef BYTESWAP_H
#define BYTESWAP_H
#include "pstypes.h"
#include <cstdint>
#include <cstdlib>
/*
#include "psendian.h"
namespace D3 {
#define SWAPSHORT(x) ( ((x) << 8) | (((ushort)(x)) >> 8) )
#define SWAPINT(x) ( ((x) << 24) | (((ulong)(x)) >> 24) | (((x) & 0x0000ff00) << 8) | (((x) & 0x00ff0000) >>
8) )
//Stupid function to trick the compiler into letting me byteswap a float
inline float SWAPFLOAT(float x)
{
int i = SWAPINT(*((int *) &(x)));
return *((float *) &(i));
// std::byteswap from C++23
template <typename T> constexpr T byteswap(T n) {
T m;
for (size_t i = 0; i < sizeof(T); i++)
reinterpret_cast<uint8_t *>(&m)[i] = reinterpret_cast<uint8_t *>(&n)[sizeof(T) - 1 - i];
return m;
}
// INTEL_ assumes the returned value will be in "Little Endian Format"
#define INTEL_INT(x) Endian_SwapInt(x)
#define INTEL_SHORT(x) Endian_SwapShort(x)
#define INTEL_FLOAT(x) Endian_SwapFloat(x)
// MOTOROLA_ assumes the returned value will be in "Big Endian Format"
#define MOTOROLA_INT(x) SWAPINT(Endian_SwapInt(x))
#define MOTOROLA_SHORT(x) SWAPSHORT(Endian_SwapShort(x))
#define MOTOROLA_FLOAT(x) SWAPFLOAT(Endian_SwapFloat(x))
*/
#define SWAPSHORT(x) (short)(0xFFFF & (((x) << 8) | (((ushort)(x)) >> 8)))
#define SWAPINT(x) (int)(((x) << 24) | (((ulong)(x)) >> 24) | (((x) & 0x0000ff00) << 8) | (((x) & 0x00ff0000) >> 8))
// Stupid function to trick the compiler into letting me byteswap a float
inline float SWAPFLOAT(float x) {
int i = SWAPINT(*((int *)&(x)));
return *((float *)&(i));
}
// Default is little endian, so change for Macintosh
#if (MACOSX && MACOSXPPC)
#define OUTRAGE_BIG_ENDIAN
#endif
#if (defined __LINUX__) && (!defined(MACOSX))
#include <endian.h>
#if BYTE_ORDER == BIG_ENDIAN
#define OUTRAGE_BIG_ENDIAN
#endif
#endif
/**
* Convert integer to/from BE order
*/
template <typename T> constexpr T convert_be(T val) {
#ifndef OUTRAGE_BIG_ENDIAN
#define INTEL_INT(x) x
#define INTEL_SHORT(x) x
#define INTEL_FLOAT(x) x
#define MOTOROLA_INT(x) SWAPINT(x)
#define MOTOROLA_SHORT(x) SWAPSHORT(x)
#define MOTOROLA_FLOAT(x) SWAPFLOAT(x)
return byteswap(val);
#else
#define INTEL_INT(x) SWAPINT(x)
#define INTEL_SHORT(x) SWAPSHORT(x)
#define INTEL_FLOAT(x) SWAPFLOAT(x)
#define MOTOROLA_INT(x) x
#define MOTOROLA_SHORT(x) x
#define MOTOROLA_FLOAT(x) x
return (val);
#endif
}
/**
* Convert integer to/from LE order
*/
template <typename T> constexpr T convert_le(T val) {
#ifndef OUTRAGE_BIG_ENDIAN
return (val);
#else
return byteswap(val);
#endif
}
} // namespace D3
// Compatibility macros. Use D3::convert_le / D3::convert_be when possible
#define INTEL_INT(x) D3::convert_le(x)
#define INTEL_SHORT(x) D3::convert_le(x)
#define INTEL_FLOAT(x) D3::convert_le(x)
#define MOTOROLA_INT(x) D3::convert_be(x)
#define MOTOROLA_SHORT(x) D3::convert_be(x)
#define MOTOROLA_FLOAT(x) D3::convert_be(x)
#endif

11
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,11 @@
add_executable(
byteswap_tests
byteswap_tests.cpp
)
target_link_libraries(
byteswap_tests
GTest::gtest_main
)
target_include_directories(byteswap_tests PRIVATE ${PROJECT_SOURCE_DIR}/lib)
gtest_discover_tests(byteswap_tests)

90
tests/byteswap_tests.cpp Normal file
View File

@ -0,0 +1,90 @@
/*
* Descent 3
* Copyright (C) 2024 Descent Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <gtest/gtest.h>
#include "byteswap.h"
// This code taken from original byteswap.h for testing float conversion
// It cannot convert negative float numbers in 64-bit systems, so testing only non-negative numbers
#define SWAPINT(x) (int)(((x) << 24) | (((ulong)(x)) >> 24) | (((x) & 0x0000ff00) << 8) | (((x) & 0x00ff0000) >> 8))
// Stupid function to trick the compiler into letting me byteswap a float
inline float SWAPFLOAT(float x) {
int i = SWAPINT(*((int *)&(x)));
return *((float *)&(i));
}
// Taken from CFILE.cpp
inline double SWAPDOUBLE(double x) {
double t;
int *sp = (int *)&x;
int *dp = (int *)&t;
dp[0] = SWAPINT(sp[1]);
dp[1] = SWAPINT(sp[0]);
x = t;
return x;
}
TEST(D3, ByteSwap) {
EXPECT_EQ(D3::byteswap((uint8_t)0x01), (uint8_t)0x01);
EXPECT_EQ(D3::byteswap((int8_t)0x01), (int16_t)0x01);
EXPECT_EQ(D3::byteswap((uint16_t)0x0123), (uint16_t)0x2301);
EXPECT_EQ(D3::byteswap((int16_t)0x0123), (int16_t)0x2301);
EXPECT_EQ(D3::byteswap((int32_t)0x01234567), (int32_t)0x67452301);
EXPECT_EQ(D3::byteswap((int64_t)0x0123456789ABCDEF), (int64_t)0xEFCDAB8967452301);
EXPECT_EQ(D3::byteswap((int32_t)(0xFF000000)), (int32_t)0xFF);
}
TEST(D3, Converting) {
#ifndef OUTRAGE_BIG_ENDIAN
EXPECT_EQ(D3::convert_le((int16_t)0x10), 0x10);
EXPECT_EQ(D3::convert_le((int32_t)0x10), 0x10);
EXPECT_EQ(D3::convert_le((int64_t)0x10), 0x10);
EXPECT_EQ(D3::convert_be((int16_t)0x10), 0x1000);
EXPECT_EQ(D3::convert_be((int32_t)0x10), 0x10000000);
EXPECT_EQ(D3::convert_be((int64_t)0x10), 0x1000000000000000);
#else
EXPECT_EQ(D3::convert_be((int16_t)0x10), 0x10);
EXPECT_EQ(D3::convert_be((int32_t)0x10), 0x10);
EXPECT_EQ(D3::convert_be((int64_t)0x10), 0x10);
EXPECT_EQ(D3::convert_le((int16_t)0x10), 0x1000);
EXPECT_EQ(D3::convert_le((int16_t)0x10), 0x10000000);
EXPECT_EQ(D3::convert_le((int16_t)0x10), 0x1000000000000000);
#endif
}
TEST(D3, FloatDoubleOperations) {
// float/double sanity checks
EXPECT_EQ(sizeof(float), 4);
EXPECT_EQ(sizeof(double), 8);
EXPECT_EQ(D3::byteswap(0.0f), SWAPFLOAT(0.0f));
EXPECT_EQ(D3::byteswap(10.0f), SWAPFLOAT(10.0f));
EXPECT_EQ(D3::byteswap(10000.0f), SWAPFLOAT(10000.0f));
EXPECT_EQ(D3::byteswap(0.0), SWAPDOUBLE(0.0));
EXPECT_EQ(D3::byteswap(10.0), SWAPDOUBLE(10.0));
EXPECT_EQ(D3::byteswap(10000.0), SWAPDOUBLE(10000.0));
}

View File

@ -24,36 +24,6 @@
namespace D3 {
// std::byteswap from C++23
template <typename T> constexpr T byteswap(T n) {
T m;
for (size_t i = 0; i < sizeof(T); i++)
reinterpret_cast<uint8_t *>(&m)[i] = reinterpret_cast<uint8_t *>(&n)[sizeof(T) - 1 - i];
return m;
}
/**
* Convert integer to/from BE order
*/
template <typename T> constexpr T convert_be(T val) {
#ifndef OUTRAGE_BIG_ENDIAN
return byteswap(val);
#else
return (val);
#endif
}
/**
* Convert integer to/from LE order
*/
template <typename T> constexpr T convert_le(T val) {
#ifndef OUTRAGE_BIG_ENDIAN
return (val);
#else
return byteswap(val);
#endif
}
template <class T>
inline std::ostream &bin_write(std::ostream &output, T value, bool is_little_endian = true, size_t n = sizeof(T)) {
value = is_little_endian ? convert_le(value) : convert_be(value);