Coverage Report

Created: 2026-06-30 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
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
22.1k
void* MemoryManagerAlloc(const JxlMemoryManager* memory_manager, size_t size) {
35
22.1k
  return memory_manager->alloc(memory_manager->opaque, size);
36
22.1k
}
37
38
22.1k
void MemoryManagerFree(const JxlMemoryManager* memory_manager, void* address) {
39
22.1k
  memory_manager->free(memory_manager->opaque, address);
40
22.1k
}
41
42
Status MemoryManagerInit(JxlMemoryManager* self,
43
22.1k
                         const JxlMemoryManager* memory_manager) {
44
22.1k
  if (memory_manager) {
45
22.1k
    *self = *memory_manager;
46
22.1k
  } else {
47
0
    memset(self, 0, sizeof(*self));
48
0
  }
49
22.1k
  bool is_default_alloc = (self->alloc == nullptr);
50
22.1k
  bool is_default_free = (self->free == nullptr);
51
22.1k
  if (is_default_alloc != is_default_free) {
52
0
    return false;
53
0
  }
54
22.1k
  if (is_default_alloc) self->alloc = jxl::MemoryManagerDefaultAlloc;
55
22.1k
  if (is_default_free) self->free = jxl::MemoryManagerDefaultFree;
56
57
22.1k
  return true;
58
22.1k
}
59
60
33.2M
StatusOr<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
236k
    return 0;
64
236k
  }
65
66
32.9M
  const size_t vec_size = MaxVectorSize();
67
32.9M
  size_t valid_bytes;
68
32.9M
  if (!SafeMul(xsize, sizeof_t, valid_bytes)) {
69
0
    return JXL_FAILURE("Image dimensions are too large");
70
0
  }
71
72
  // Allow unaligned accesses starting at the last valid value.
73
  // NB: in scalar build `vec_size` is `4` (one `float`); still worth checking.
74
32.9M
  if (vec_size != 0) {
75
    // NB: in scalar build when T is "double" we "extra" would be negative.
76
32.9M
    JXL_ENSURE(vec_size >= sizeof_t);
77
32.9M
    size_t extra = vec_size - sizeof_t;
78
32.9M
    if (!SafeAdd(valid_bytes, extra, valid_bytes)) {
79
0
      return JXL_FAILURE("Image dimensions are too large");
80
0
    }
81
32.9M
  }
82
83
  // Round up to vector and cache line size.
84
32.9M
  const size_t align = std::max(vec_size, memory_manager_internal::kAlignment);
85
32.9M
  JXL_ENSURE((align & (align - 1)) == 0);
86
32.9M
  size_t bytes_per_row;
87
32.9M
  if (!SafeRoundUpTo(valid_bytes, align, bytes_per_row)) {
88
0
    return JXL_FAILURE("Image dimensions are too large");
89
0
  }
90
91
  // During the lengthy window before writes are committed to memory, CPUs
92
  // guard against read after write hazards by checking the address, but
93
  // only the lower 11 bits. We avoid a false dependency between writes to
94
  // consecutive rows by ensuring their sizes are not multiples of 2 KiB.
95
  // Avoid2K prevents the same problem for the planes of an Image3.
96
32.9M
  if (bytes_per_row % memory_manager_internal::kAlias == 0) {
97
112k
    if (!SafeAdd(bytes_per_row, align, bytes_per_row)) {
98
0
      return JXL_FAILURE("Image dimensions are too large");
99
0
    }
100
112k
  }
101
102
32.9M
  JXL_DASSERT(bytes_per_row % align == 0);
103
32.9M
  return bytes_per_row;
104
32.9M
}
105
106
StatusOr<AlignedMemory> AlignedMemory::Create(JxlMemoryManager* memory_manager,
107
33.6M
                                              size_t size, size_t pre_padding) {
108
33.6M
  JXL_ENSURE(pre_padding <= memory_manager_internal::kAlias);
109
33.6M
  size_t allocation_size;
110
33.6M
  if (!SafeAdd<size_t>(size, pre_padding, allocation_size) ||
111
33.6M
      !SafeAdd(allocation_size, memory_manager_internal::kAlias,
112
33.6M
               allocation_size)) {
113
0
    return JXL_FAILURE("Requested allocation is too large");
114
0
  }
115
33.6M
  JXL_ENSURE(memory_manager);
116
33.6M
  void* allocated =
117
33.6M
      memory_manager->alloc(memory_manager->opaque, allocation_size);
118
33.6M
  if (allocated == nullptr) {
119
6
    return JXL_FAILURE("Allocation failed");
120
6
  }
121
33.6M
  return AlignedMemory(memory_manager, allocated, pre_padding);
122
33.6M
}
123
124
AlignedMemory::AlignedMemory(JxlMemoryManager* memory_manager, void* allocation,
125
                             size_t pre_padding)
126
33.6M
    : allocation_(allocation), memory_manager_(memory_manager) {
127
  // Congruence to `offset` (mod kAlias) reduces cache conflicts and load/store
128
  // stalls, especially with large allocations that would otherwise have similar
129
  // alignments.
130
33.6M
  static std::atomic<uint32_t> next_group{0};
131
33.6M
  size_t group =
132
33.6M
      static_cast<size_t>(next_group.fetch_add(1, std::memory_order_relaxed));
133
33.6M
  group &= (memory_manager_internal::kNumAlignmentGroups - 1);
134
33.6M
  size_t offset = memory_manager_internal::kAlignment * group;
135
136
  // Actual allocation.
137
33.6M
  uintptr_t address = reinterpret_cast<uintptr_t>(allocation) + pre_padding;
138
139
  // Aligned address, but might land before allocation (50%/50%) or not have
140
  // enough pre-padding.
141
33.6M
  uintptr_t aligned_address =
142
33.6M
      (address & ~(memory_manager_internal::kAlias - 1)) + offset;
143
33.6M
  if (aligned_address < address)
144
20.1M
    aligned_address += memory_manager_internal::kAlias;
145
146
33.6M
  address_ = reinterpret_cast<void*>(aligned_address);  // NOLINT
147
33.6M
}
148
149
138M
AlignedMemory::AlignedMemory(AlignedMemory&& other) noexcept {
150
138M
  allocation_ = other.allocation_;
151
138M
  memory_manager_ = other.memory_manager_;
152
138M
  address_ = other.address_;
153
138M
  other.memory_manager_ = nullptr;
154
138M
}
155
156
46.0M
AlignedMemory& AlignedMemory::operator=(AlignedMemory&& other) noexcept {
157
46.0M
  if (this == &other) return *this;
158
46.0M
  if (memory_manager_ && allocation_) {
159
1.60M
    memory_manager_->free(memory_manager_->opaque, allocation_);
160
1.60M
  }
161
46.0M
  allocation_ = other.allocation_;
162
46.0M
  memory_manager_ = other.memory_manager_;
163
46.0M
  address_ = other.address_;
164
46.0M
  other.memory_manager_ = nullptr;
165
46.0M
  return *this;
166
46.0M
}
167
168
213M
AlignedMemory::~AlignedMemory() {
169
213M
  if (memory_manager_ == nullptr) return;
170
32.0M
  memory_manager_->free(memory_manager_->opaque, allocation_);
171
32.0M
}
172
173
}  // namespace jxl