/src/libjxl/lib/jxl/memory_manager_internal.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | #include "lib/jxl/memory_manager_internal.h" |
7 | | |
8 | | #include <jxl/memory_manager.h> |
9 | | |
10 | | #include <algorithm> |
11 | | #include <atomic> |
12 | | #include <cstddef> |
13 | | #include <cstdio> |
14 | | #include <cstdlib> |
15 | | #include <cstring> // memcpy |
16 | | #include <hwy/base.h> // kMaxVectorSize |
17 | | |
18 | | #include "lib/jxl/base/common.h" |
19 | | #include "lib/jxl/base/status.h" |
20 | | #include "lib/jxl/simd_util.h" |
21 | | |
22 | | namespace jxl { |
23 | | |
24 | | namespace { |
25 | | |
26 | 0 | void* MemoryManagerDefaultAlloc(void* opaque, size_t size) { |
27 | 0 | return malloc(size); |
28 | 0 | } |
29 | | |
30 | 0 | void MemoryManagerDefaultFree(void* opaque, void* address) { free(address); } |
31 | | |
32 | | } // namespace |
33 | | |
34 | 11.0k | void* MemoryManagerAlloc(const JxlMemoryManager* memory_manager, size_t size) { |
35 | 11.0k | return memory_manager->alloc(memory_manager->opaque, size); |
36 | 11.0k | } |
37 | | |
38 | 11.0k | void MemoryManagerFree(const JxlMemoryManager* memory_manager, void* address) { |
39 | 11.0k | memory_manager->free(memory_manager->opaque, address); |
40 | 11.0k | } |
41 | | |
42 | | Status MemoryManagerInit(JxlMemoryManager* self, |
43 | 11.0k | const JxlMemoryManager* memory_manager) { |
44 | 11.0k | if (memory_manager) { |
45 | 11.0k | *self = *memory_manager; |
46 | 11.0k | } else { |
47 | 0 | memset(self, 0, sizeof(*self)); |
48 | 0 | } |
49 | 11.0k | bool is_default_alloc = (self->alloc == nullptr); |
50 | 11.0k | bool is_default_free = (self->free == nullptr); |
51 | 11.0k | if (is_default_alloc != is_default_free) { |
52 | 0 | return false; |
53 | 0 | } |
54 | 11.0k | if (is_default_alloc) self->alloc = jxl::MemoryManagerDefaultAlloc; |
55 | 11.0k | if (is_default_free) self->free = jxl::MemoryManagerDefaultFree; |
56 | | |
57 | 11.0k | return true; |
58 | 11.0k | } |
59 | | |
60 | 33.2M | size_t BytesPerRow(const size_t xsize, const size_t sizeof_t) { |
61 | | // Special case: we don't allow any ops -> don't need extra padding/ |
62 | 33.2M | if (xsize == 0) { |
63 | 106k | return 0; |
64 | 106k | } |
65 | | |
66 | 33.1M | const size_t vec_size = MaxVectorSize(); |
67 | 33.1M | size_t valid_bytes = xsize * sizeof_t; |
68 | | |
69 | | // Allow unaligned accesses starting at the last valid value. |
70 | | // Skip for the scalar case because no extra lanes will be loaded. |
71 | 33.1M | if (vec_size != 0) { |
72 | 33.1M | valid_bytes += vec_size - sizeof_t; |
73 | 33.1M | } |
74 | | |
75 | | // Round up to vector and cache line size. |
76 | 33.1M | const size_t align = std::max(vec_size, memory_manager_internal::kAlignment); |
77 | 33.1M | size_t bytes_per_row = RoundUpTo(valid_bytes, align); |
78 | | |
79 | | // During the lengthy window before writes are committed to memory, CPUs |
80 | | // guard against read after write hazards by checking the address, but |
81 | | // only the lower 11 bits. We avoid a false dependency between writes to |
82 | | // consecutive rows by ensuring their sizes are not multiples of 2 KiB. |
83 | | // Avoid2K prevents the same problem for the planes of an Image3. |
84 | 33.1M | if (bytes_per_row % memory_manager_internal::kAlias == 0) { |
85 | 3.78k | bytes_per_row += align; |
86 | 3.78k | } |
87 | | |
88 | 33.1M | JXL_DASSERT(bytes_per_row % align == 0); |
89 | 33.1M | return bytes_per_row; |
90 | 33.2M | } |
91 | | |
92 | | StatusOr<AlignedMemory> AlignedMemory::Create(JxlMemoryManager* memory_manager, |
93 | 33.1M | size_t size, size_t pre_padding) { |
94 | 33.1M | JXL_ENSURE(pre_padding <= memory_manager_internal::kAlias); |
95 | 33.1M | size_t allocation_size = size + pre_padding + memory_manager_internal::kAlias; |
96 | 33.1M | if (size > allocation_size) { |
97 | 0 | return JXL_FAILURE("Requested allocation is too large"); |
98 | 0 | } |
99 | 33.1M | JXL_ENSURE(memory_manager); |
100 | 33.1M | void* allocated = |
101 | 33.1M | memory_manager->alloc(memory_manager->opaque, allocation_size); |
102 | 33.1M | if (allocated == nullptr) { |
103 | 5 | return JXL_FAILURE("Allocation failed"); |
104 | 5 | } |
105 | 33.1M | return AlignedMemory(memory_manager, allocated, pre_padding); |
106 | 33.1M | } |
107 | | |
108 | | AlignedMemory::AlignedMemory(JxlMemoryManager* memory_manager, void* allocation, |
109 | | size_t pre_padding) |
110 | 33.1M | : allocation_(allocation), memory_manager_(memory_manager) { |
111 | | // Congruence to `offset` (mod kAlias) reduces cache conflicts and load/store |
112 | | // stalls, especially with large allocations that would otherwise have similar |
113 | | // alignments. |
114 | 33.1M | static std::atomic<uint32_t> next_group{0}; |
115 | 33.1M | size_t group = |
116 | 33.1M | static_cast<size_t>(next_group.fetch_add(1, std::memory_order_relaxed)); |
117 | 33.1M | group &= (memory_manager_internal::kNumAlignmentGroups - 1); |
118 | 33.1M | size_t offset = memory_manager_internal::kAlignment * group; |
119 | | |
120 | | // Actual allocation. |
121 | 33.1M | uintptr_t address = reinterpret_cast<uintptr_t>(allocation) + pre_padding; |
122 | | |
123 | | // Aligned address, but might land before allocation (50%/50%) or not have |
124 | | // enough pre-padding. |
125 | 33.1M | uintptr_t aligned_address = |
126 | 33.1M | (address & ~(memory_manager_internal::kAlias - 1)) + offset; |
127 | 33.1M | if (aligned_address < address) |
128 | 13.4M | aligned_address += memory_manager_internal::kAlias; |
129 | | |
130 | 33.1M | address_ = reinterpret_cast<void*>(aligned_address); // NOLINT |
131 | 33.1M | } |
132 | | |
133 | 134M | AlignedMemory::AlignedMemory(AlignedMemory&& other) noexcept { |
134 | 134M | allocation_ = other.allocation_; |
135 | 134M | memory_manager_ = other.memory_manager_; |
136 | 134M | address_ = other.address_; |
137 | 134M | other.memory_manager_ = nullptr; |
138 | 134M | } |
139 | | |
140 | 38.2M | AlignedMemory& AlignedMemory::operator=(AlignedMemory&& other) noexcept { |
141 | 38.2M | if (this == &other) return *this; |
142 | 38.2M | if (memory_manager_ && allocation_) { |
143 | 806k | memory_manager_->free(memory_manager_->opaque, allocation_); |
144 | 806k | } |
145 | 38.2M | allocation_ = other.allocation_; |
146 | 38.2M | memory_manager_ = other.memory_manager_; |
147 | 38.2M | address_ = other.address_; |
148 | 38.2M | other.memory_manager_ = nullptr; |
149 | 38.2M | return *this; |
150 | 38.2M | } |
151 | | |
152 | 204M | AlignedMemory::~AlignedMemory() { |
153 | 204M | if (memory_manager_ == nullptr) return; |
154 | 32.3M | memory_manager_->free(memory_manager_->opaque, allocation_); |
155 | 32.3M | } |
156 | | |
157 | | } // namespace jxl |