| // Copyright 2020 Google LLC |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // 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. |
| |
| #include "third_party/highway/hwy/contrib/image/image.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> // std::swap |
| |
| #include "third_party/highway/hwy/aligned_allocator.h" |
| #include "third_party/highway/hwy/base.h" |
| #include "third_party/highway/hwy/per_target.h" |
| |
| namespace hwy { |
| |
| size_t ImageBase::VectorSize() { |
| // Do not cache result - must return the current value, which may be greater |
| // than the first call if it was subject to DisableTargets! |
| return VectorBytes(); |
| } |
| |
| size_t ImageBase::BytesPerRow(const size_t xsize, const size_t sizeof_t) { |
| const size_t vec_size = VectorSize(); |
| // Check for integer overflow in xsize * sizeof_t. |
| if (HWY_LIKELY(xsize != 0) && sizeof_t > SIZE_MAX / xsize) { |
| HWY_ABORT("ImageBase::BytesPerRow overflow: xsize=%zu, sizeof_t=%zu", |
| xsize, sizeof_t); |
| } |
| size_t valid_bytes = xsize * sizeof_t; |
| |
| // Allow unaligned accesses starting at the last valid value - this may raise |
| // msan errors unless the user calls InitializePaddingForUnalignedAccesses. |
| // Skip for the scalar case because no extra lanes will be loaded. |
| if (vec_size != 1) { |
| HWY_DASSERT(vec_size >= sizeof_t); |
| valid_bytes += vec_size - sizeof_t; |
| } |
| |
| // Round up to vector and cache line size. |
| const size_t align = HWY_MAX(vec_size, HWY_ALIGNMENT); |
| size_t bytes_per_row = RoundUpTo(valid_bytes, align); |
| |
| // During the lengthy window before writes are committed to memory, CPUs |
| // guard against read after write hazards by checking the address, but |
| // only the lower 11 bits. We avoid a false dependency between writes to |
| // consecutive rows by ensuring their sizes are not multiples of 2 KiB. |
| // Avoid2K prevents the same problem for the planes of an Image3. |
| if (bytes_per_row % HWY_ALIGNMENT == 0) { |
| bytes_per_row += align; |
| } |
| |
| HWY_DASSERT(bytes_per_row % align == 0); |
| return bytes_per_row; |
| } |
| |
| ImageBase::ImageBase(const size_t xsize, const size_t ysize, |
| const size_t sizeof_t) |
| : xsize_(static_cast<uint32_t>(xsize)), |
| ysize_(static_cast<uint32_t>(ysize)), |
| bytes_(nullptr, AlignedFreer(&AlignedFreer::DoNothing, nullptr)) { |
| HWY_ASSERT(sizeof_t == 1 || sizeof_t == 2 || sizeof_t == 4 || sizeof_t == 8); |
| // Validate dimensions fit in uint32_t to prevent silent truncation. |
| HWY_ASSERT(xsize <= UINT32_MAX); |
| HWY_ASSERT(ysize <= UINT32_MAX); |
| |
| bytes_per_row_ = 0; |
| // Dimensions can be zero, e.g. for lazily-allocated images. Only allocate |
| // if nonzero, because "zero" bytes still have padding/bookkeeping overhead. |
| if (xsize != 0 && ysize != 0) { |
| bytes_per_row_ = BytesPerRow(xsize, sizeof_t); |
| // Check for integer overflow in allocation size. |
| if (bytes_per_row_ > SIZE_MAX / ysize) { |
| HWY_ABORT("ImageBase: allocation overflow (%zu * %zu exceeds size_t)", |
| bytes_per_row_, ysize); |
| } |
| bytes_ = AllocateAligned<uint8_t>(bytes_per_row_ * ysize); |
| HWY_ASSERT(bytes_.get() != nullptr); |
| InitializePadding(sizeof_t, Padding::kRoundUp); |
| } |
| } |
| |
| ImageBase::ImageBase(const size_t xsize, const size_t ysize, |
| const size_t bytes_per_row, void* const aligned) |
| : xsize_(static_cast<uint32_t>(xsize)), |
| ysize_(static_cast<uint32_t>(ysize)), |
| bytes_per_row_(bytes_per_row), |
| bytes_(static_cast<uint8_t*>(aligned), |
| AlignedFreer(&AlignedFreer::DoNothing, nullptr)) { |
| const size_t vec_size = VectorSize(); |
| HWY_ASSERT(bytes_per_row % vec_size == 0); |
| HWY_ASSERT(reinterpret_cast<uintptr_t>(aligned) % vec_size == 0); |
| } |
| |
| void ImageBase::InitializePadding(const size_t sizeof_t, Padding padding) { |
| #if HWY_IS_MSAN || HWY_IDE |
| if (xsize_ == 0 || ysize_ == 0) return; |
| |
| const size_t vec_size = VectorSize(); // Bytes, independent of sizeof_t! |
| if (vec_size == 1) return; // Scalar mode: no padding needed |
| |
| const size_t valid_size = xsize_ * sizeof_t; |
| const size_t initialize_size = padding == Padding::kRoundUp |
| ? RoundUpTo(valid_size, vec_size) |
| : valid_size + vec_size - sizeof_t; |
| if (valid_size == initialize_size) return; |
| |
| for (size_t y = 0; y < ysize_; ++y) { |
| uint8_t* HWY_RESTRICT row = static_cast<uint8_t*>(VoidRow(y)); |
| #if defined(__clang__) && (__clang_major__ <= 6) |
| // There's a bug in msan in clang-6 when handling AVX2 operations. This |
| // workaround allows tests to pass on msan, although it is slower and |
| // prevents msan warnings from uninitialized images. |
| hwy::ZeroBytes(row, initialize_size); |
| #else |
| hwy::ZeroBytes(row + valid_size, initialize_size - valid_size); |
| #endif // clang6 |
| } |
| #else |
| (void)sizeof_t; |
| (void)padding; |
| #endif // HWY_IS_MSAN |
| } |
| |
| void ImageBase::Swap(ImageBase& other) { |
| std::swap(xsize_, other.xsize_); |
| std::swap(ysize_, other.ysize_); |
| std::swap(bytes_per_row_, other.bytes_per_row_); |
| std::swap(bytes_, other.bytes_); |
| } |
| |
| } // namespace hwy |