/src/libjxl/lib/jxl/memory_manager_internal.cc
Line | Count | Source |
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 | 34.8M | void* MemoryManagerDefaultAlloc(void* opaque, size_t size) { |
27 | 34.8M | return malloc(size); |
28 | 34.8M | } |
29 | | |
30 | 34.8M | void MemoryManagerDefaultFree(void* opaque, void* address) { free(address); } |
31 | | |
32 | | } // namespace |
33 | | |
34 | 50.1k | void* MemoryManagerAlloc(const JxlMemoryManager* memory_manager, size_t size) { |
35 | 50.1k | return memory_manager->alloc(memory_manager->opaque, size); |
36 | 50.1k | } |
37 | | |
38 | 50.1k | void MemoryManagerFree(const JxlMemoryManager* memory_manager, void* address) { |
39 | 50.1k | memory_manager->free(memory_manager->opaque, address); |
40 | 50.1k | } |
41 | | |
42 | | Status MemoryManagerInit(JxlMemoryManager* self, |
43 | 111k | const JxlMemoryManager* memory_manager) { |
44 | 111k | if (memory_manager) { |
45 | 50.1k | *self = *memory_manager; |
46 | 61.0k | } else { |
47 | 61.0k | memset(self, 0, sizeof(*self)); |
48 | 61.0k | } |
49 | 111k | bool is_default_alloc = (self->alloc == nullptr); |
50 | 111k | bool is_default_free = (self->free == nullptr); |
51 | 111k | if (is_default_alloc != is_default_free) { |
52 | 0 | return false; |
53 | 0 | } |
54 | 111k | if (is_default_alloc) self->alloc = jxl::MemoryManagerDefaultAlloc; |
55 | 111k | if (is_default_free) self->free = jxl::MemoryManagerDefaultFree; |
56 | | |
57 | 111k | return true; |
58 | 111k | } |
59 | | |
60 | 30.0M | 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 | 30.0M | if (xsize == 0) { |
63 | 548k | return 0; |
64 | 548k | } |
65 | | |
66 | 29.5M | const size_t vec_size = MaxVectorSize(); |
67 | 29.5M | 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 | 29.5M | if (vec_size != 0) { |
72 | 29.5M | valid_bytes += vec_size - sizeof_t; |
73 | 29.5M | } |
74 | | |
75 | | // Round up to vector and cache line size. |
76 | 29.5M | const size_t align = std::max(vec_size, memory_manager_internal::kAlignment); |
77 | 29.5M | 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 | 29.5M | if (bytes_per_row % memory_manager_internal::kAlias == 0) { |
85 | 24.0k | bytes_per_row += align; |
86 | 24.0k | } |
87 | | |
88 | 29.5M | JXL_DASSERT(bytes_per_row % align == 0); |
89 | 29.5M | return bytes_per_row; |
90 | 29.5M | } |
91 | | |
92 | | StatusOr<AlignedMemory> AlignedMemory::Create(JxlMemoryManager* memory_manager, |
93 | 34.7M | size_t size, size_t pre_padding) { |
94 | 34.7M | JXL_ENSURE(pre_padding <= memory_manager_internal::kAlias); |
95 | 34.7M | size_t allocation_size; |
96 | 34.7M | if (!SafeAdd<size_t>(size, pre_padding, allocation_size) || |
97 | 34.7M | !SafeAdd(allocation_size, memory_manager_internal::kAlias, |
98 | 34.7M | allocation_size)) { |
99 | 0 | return JXL_FAILURE("Requested allocation is too large"); |
100 | 0 | } |
101 | 34.7M | JXL_ENSURE(memory_manager); |
102 | 34.7M | void* allocated = |
103 | 34.7M | memory_manager->alloc(memory_manager->opaque, allocation_size); |
104 | 34.7M | if (allocated == nullptr) { |
105 | 1.28k | return JXL_FAILURE("Allocation failed"); |
106 | 1.28k | } |
107 | 34.7M | return AlignedMemory(memory_manager, allocated, pre_padding); |
108 | 34.7M | } |
109 | | |
110 | | AlignedMemory::AlignedMemory(JxlMemoryManager* memory_manager, void* allocation, |
111 | | size_t pre_padding) |
112 | 34.7M | : allocation_(allocation), memory_manager_(memory_manager) { |
113 | | // Congruence to `offset` (mod kAlias) reduces cache conflicts and load/store |
114 | | // stalls, especially with large allocations that would otherwise have similar |
115 | | // alignments. |
116 | 34.7M | static std::atomic<uint32_t> next_group{0}; |
117 | 34.7M | size_t group = |
118 | 34.7M | static_cast<size_t>(next_group.fetch_add(1, std::memory_order_relaxed)); |
119 | 34.7M | group &= (memory_manager_internal::kNumAlignmentGroups - 1); |
120 | 34.7M | size_t offset = memory_manager_internal::kAlignment * group; |
121 | | |
122 | | // Actual allocation. |
123 | 34.7M | uintptr_t address = reinterpret_cast<uintptr_t>(allocation) + pre_padding; |
124 | | |
125 | | // Aligned address, but might land before allocation (50%/50%) or not have |
126 | | // enough pre-padding. |
127 | 34.7M | uintptr_t aligned_address = |
128 | 34.7M | (address & ~(memory_manager_internal::kAlias - 1)) + offset; |
129 | 34.7M | if (aligned_address < address) |
130 | 17.1M | aligned_address += memory_manager_internal::kAlias; |
131 | | |
132 | 34.7M | address_ = reinterpret_cast<void*>(aligned_address); // NOLINT |
133 | 34.7M | } |
134 | | |
135 | 148M | AlignedMemory::AlignedMemory(AlignedMemory&& other) noexcept { |
136 | 148M | allocation_ = other.allocation_; |
137 | 148M | memory_manager_ = other.memory_manager_; |
138 | 148M | address_ = other.address_; |
139 | 148M | other.memory_manager_ = nullptr; |
140 | 148M | } |
141 | | |
142 | 288M | AlignedMemory& AlignedMemory::operator=(AlignedMemory&& other) noexcept { |
143 | 288M | if (this == &other) return *this; |
144 | 288M | if (memory_manager_ && allocation_) { |
145 | 5.43M | memory_manager_->free(memory_manager_->opaque, allocation_); |
146 | 5.43M | } |
147 | 288M | allocation_ = other.allocation_; |
148 | 288M | memory_manager_ = other.memory_manager_; |
149 | 288M | address_ = other.address_; |
150 | 288M | other.memory_manager_ = nullptr; |
151 | 288M | return *this; |
152 | 288M | } |
153 | | |
154 | 323M | AlignedMemory::~AlignedMemory() { |
155 | 323M | if (memory_manager_ == nullptr) return; |
156 | 29.3M | memory_manager_->free(memory_manager_->opaque, allocation_); |
157 | 29.3M | } |
158 | | |
159 | | } // namespace jxl |