diff --git a/Source/UnitTest/cmake_core.cmake b/Source/UnitTest/cmake_core.cmake index a467354a..7ccc51d4 100644 --- a/Source/UnitTest/cmake_core.cmake +++ b/Source/UnitTest/cmake_core.cmake @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # ---------------------------------------------------------------------------- -# Copyright 2020-2025 Arm Limited +# Copyright 2020-2026 Arm Limited # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy @@ -43,7 +43,8 @@ target_sources(${ASTCENC_TEST} PRIVATE test_simd.cpp test_softfloat.cpp - test_decode.cpp) + test_decode.cpp + test_encode.cpp) target_include_directories(${ASTCENC_TEST} PRIVATE @@ -190,6 +191,30 @@ elseif(${ASTCENC_ISA_SIMD} MATCHES "avx2") endif() +if(${ASTCENC_ASAN}) + target_compile_options(${ASTCENC_TEST} + PRIVATE + $<${is_gnu_fe}:-fsanitize=address> + $<${is_gnu_fe}:-fno-sanitize-recover=all>) + + target_link_options(${ASTCENC_TEST} + PRIVATE + $<${is_gnu_fe}:-fsanitize=address> + $<${is_clang}:-fuse-ld=lld>) +endif() + +if(${ASTCENC_UBSAN}) + target_compile_options(${ASTCENC_TEST} + PRIVATE + $<${is_gnu_fe}:-fsanitize=undefined> + $<${is_gnu_fe}:-fno-sanitize-recover=all>) + + target_link_options(${ASTCENC_TEST} + PRIVATE + $<${is_gnu_fe}:-fsanitize=undefined> + $<${is_clang}:-fuse-ld=lld>) +endif() + target_link_libraries(${ASTCENC_TEST} PRIVATE gtest_main) diff --git a/Source/UnitTest/test_decode.cpp b/Source/UnitTest/test_decode.cpp index d258ba08..2ad2f77a 100644 --- a/Source/UnitTest/test_decode.cpp +++ b/Source/UnitTest/test_decode.cpp @@ -28,109 +28,6 @@ namespace astcenc { -/** @brief Input overflow tests for xy * z. */ -TEST(compress, overflow_in_z) -{ - astcenc_error status; - astcenc_config config; - astcenc_context* context; - - static const astcenc_swizzle swizzle { - ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A - }; - - astcenc_config_init(ASTCENC_PRF_LDR, 4, 4, 1, ASTCENC_PRE_MEDIUM, 0, &config); - status = astcenc_context_alloc(&config, 1, &context, nullptr); - EXPECT_EQ(status, ASTCENC_SUCCESS); - - // Arrays are too short, but should never be touched - uint8_t data[1]; - uint8_t input[1]; - - astcenc_image image; - // x * y always fits in 64-bit size_t, but xy * z will overflow - image.dim_x = 0xFFFFFFFFu; - image.dim_y = 0xFFFFFFFFu; - image.dim_z = 0xFFFFFFFFu; - image.data_type = ASTCENC_TYPE_U8; - uint8_t* slices = input; - image.data = reinterpret_cast(&slices); - - status = astcenc_compress_image(context, &image, &swizzle, data, -1, 0); - EXPECT_EQ(status, ASTCENC_ERR_BAD_PARAM); - - astcenc_context_free(context); -} - -/** @brief Input overflow tests for xyz * 16. */ -TEST(compress, overflow_in_16) -{ - astcenc_error status; - astcenc_config config; - astcenc_context* context; - - static const astcenc_swizzle swizzle { - ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A - }; - - astcenc_config_init(ASTCENC_PRF_LDR, 4, 4, 1, ASTCENC_PRE_MEDIUM, 0, &config); - status = astcenc_context_alloc(&config, 1, &context, nullptr); - EXPECT_EQ(status, ASTCENC_SUCCESS); - - // Arrays are too short, but should never be touched - uint8_t data[1]; - uint8_t input[1]; - - astcenc_image image; - // xyz (in blocks) always fits in 64-bit size_t, but xyz * 16 will overflow - image.dim_x = 0x80000000u; - image.dim_y = 0x80000000u; - image.dim_z = 0x00000010u; - image.data_type = ASTCENC_TYPE_U8; - uint8_t* slices = input; - image.data = reinterpret_cast(&slices); - - status = astcenc_compress_image(context, &image, &swizzle, data, -1, 0); - EXPECT_EQ(status, ASTCENC_ERR_BAD_PARAM); - - astcenc_context_free(context); -} - - -/** @brief Input data buffer overrun tests. */ -TEST(compress, data_buffer_exceeded) -{ - astcenc_error status; - astcenc_config config; - astcenc_context* context; - - static const astcenc_swizzle swizzle { - ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A - }; - - astcenc_config_init(ASTCENC_PRF_LDR, 4, 4, 1, ASTCENC_PRE_MEDIUM, 0, &config); - status = astcenc_context_alloc(&config, 1, &context, nullptr); - EXPECT_EQ(status, ASTCENC_SUCCESS); - - // Arrays are too short, but should never be touched - uint8_t data[1]; - uint8_t input[1]; - - astcenc_image image; - image.dim_x = 0x4u; - image.dim_y = 0x4u; - image.dim_z = 0x1u; - image.data_type = ASTCENC_TYPE_U8; - uint8_t* slices = input; - image.data = reinterpret_cast(&slices); - - // Data size is 1 byte too short so this should error - status = astcenc_compress_image(context, &image, &swizzle, data, 15, 0); - EXPECT_EQ(status, ASTCENC_ERR_OUT_OF_MEM); - - astcenc_context_free(context); -} - /** @brief Input overflow tests for xy * z. */ TEST(decompress, overflow_in_z) { diff --git a/Source/UnitTest/test_encode.cpp b/Source/UnitTest/test_encode.cpp new file mode 100644 index 00000000..9e3aa45a --- /dev/null +++ b/Source/UnitTest/test_encode.cpp @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +// ---------------------------------------------------------------------------- +// Copyright 2025-2026 Arm Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// ---------------------------------------------------------------------------- + +/** + * @brief Unit tests for the vectorized SIMD functionality. + */ + +#include + +#include "gtest/gtest.h" + +#include "../astcenc.h" + +namespace astcenc +{ + +/** @brief Input overflow tests for xy * z. */ +TEST(compress, overflow_in_z) +{ + astcenc_error status; + astcenc_config config; + astcenc_context* context; + + static const astcenc_swizzle swizzle { + ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A + }; + + astcenc_config_init(ASTCENC_PRF_LDR, 4, 4, 1, ASTCENC_PRE_MEDIUM, 0, &config); + status = astcenc_context_alloc(&config, 1, &context, nullptr); + EXPECT_EQ(status, ASTCENC_SUCCESS); + + // Arrays are too short, but should never be touched + uint8_t data[1]; + uint8_t input[1]; + + astcenc_image image; + // x * y always fits in 64-bit size_t, but xy * z will overflow + image.dim_x = 0xFFFFFFFFu; + image.dim_y = 0xFFFFFFFFu; + image.dim_z = 0xFFFFFFFFu; + image.data_type = ASTCENC_TYPE_U8; + uint8_t* slices = input; + image.data = reinterpret_cast(&slices); + + status = astcenc_compress_image(context, &image, &swizzle, data, -1, 0); + EXPECT_EQ(status, ASTCENC_ERR_BAD_PARAM); + + astcenc_context_free(context); +} + +/** @brief Input overflow tests for xyz * 16. */ +TEST(compress, overflow_in_16) +{ + astcenc_error status; + astcenc_config config; + astcenc_context* context; + + static const astcenc_swizzle swizzle { + ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A + }; + + astcenc_config_init(ASTCENC_PRF_LDR, 4, 4, 1, ASTCENC_PRE_MEDIUM, 0, &config); + status = astcenc_context_alloc(&config, 1, &context, nullptr); + EXPECT_EQ(status, ASTCENC_SUCCESS); + + // Arrays are too short, but should never be touched + uint8_t data[1]; + uint8_t input[1]; + + astcenc_image image; + // xyz (in blocks) always fits in 64-bit size_t, but xyz * 16 will overflow + image.dim_x = 0x80000000u; + image.dim_y = 0x80000000u; + image.dim_z = 0x00000010u; + image.data_type = ASTCENC_TYPE_U8; + uint8_t* slices = input; + image.data = reinterpret_cast(&slices); + + status = astcenc_compress_image(context, &image, &swizzle, data, -1, 0); + EXPECT_EQ(status, ASTCENC_ERR_BAD_PARAM); + + astcenc_context_free(context); +} + + +/** @brief Input data buffer overrun tests. */ +TEST(compress, data_buffer_exceeded) +{ + astcenc_error status; + astcenc_config config; + astcenc_context* context; + + static const astcenc_swizzle swizzle { + ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A + }; + + astcenc_config_init(ASTCENC_PRF_LDR, 4, 4, 1, ASTCENC_PRE_MEDIUM, 0, &config); + status = astcenc_context_alloc(&config, 1, &context, nullptr); + EXPECT_EQ(status, ASTCENC_SUCCESS); + + // Arrays are too short, but should never be touched + uint8_t data[1]; + uint8_t input[1]; + + astcenc_image image; + image.dim_x = 0x4u; + image.dim_y = 0x4u; + image.dim_z = 0x1u; + image.data_type = ASTCENC_TYPE_U8; + uint8_t* slices = input; + image.data = reinterpret_cast(&slices); + + // Data size is 1 byte too short so this should error + status = astcenc_compress_image(context, &image, &swizzle, data, 15, 0); + EXPECT_EQ(status, ASTCENC_ERR_OUT_OF_MEM); + + astcenc_context_free(context); +} + +} diff --git a/Source/astcenc_vecmathlib_avx2_8.h b/Source/astcenc_vecmathlib_avx2_8.h index 8776b9cb..7cd34c31 100644 --- a/Source/astcenc_vecmathlib_avx2_8.h +++ b/Source/astcenc_vecmathlib_avx2_8.h @@ -142,8 +142,13 @@ struct vint8 */ ASTCENC_SIMD_INLINE explicit vint8(const uint8_t *p) { - // _mm_loadu_si64 would be nicer syntax, but missing on older GCC - m = _mm256_cvtepu8_epi32(_mm_cvtsi64_si128(*reinterpret_cast(p))); + // _mm_loadu_si64 would be nicer syntax, but missing on older GCC and + // generates broken code on GCC 11.x before 11.3, so use this to + // generate alignment-safe and aliasing-safe alternative + uint64_t tmp; + std::memcpy(&tmp, p, sizeof(tmp)); + + m = _mm256_cvtepu8_epi32(_mm_cvtsi64_si128(tmp)); } /** diff --git a/Source/astcenc_vecmathlib_sse_4.h b/Source/astcenc_vecmathlib_sse_4.h index d91faa2e..823b41a9 100644 --- a/Source/astcenc_vecmathlib_sse_4.h +++ b/Source/astcenc_vecmathlib_sse_4.h @@ -207,8 +207,13 @@ struct vint4 */ ASTCENC_SIMD_INLINE explicit vint4(const uint8_t *p) { - // _mm_loadu_si32 would be nicer syntax, but missing on older GCC - __m128i t = _mm_cvtsi32_si128(*reinterpret_cast(p)); + // _mm_loadu_si32 would be nicer syntax, but missing on older GCC and + // generates broken code on GCC 11.x before 11.3, so use this to + // generate alignment-safe and aliasing-safe alternative + int32_t tmp; + std::memcpy(&tmp, p, sizeof(tmp)); + + __m128i t = _mm_cvtsi32_si128(tmp); #if ASTCENC_SSE >= 41 m = _mm_cvtepu8_epi32(t); diff --git a/Source/cmake_core.cmake b/Source/cmake_core.cmake index 3fe12267..daf06f48 100644 --- a/Source/cmake_core.cmake +++ b/Source/cmake_core.cmake @@ -196,7 +196,6 @@ macro(astcenc_set_properties ASTCENC_TARGET_NAME ASTCENC_VENEER_TYPE) PRIVATE $<${is_gnu_fe}:-fsanitize=address> $<${is_clang}:-fuse-ld=lld>) - endif() if(${ASTCENC_UBSAN})