Project
Loading...
Searching...
No Matches
Decoder.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12#include "AODNative/Decoder.h"
13#include "AODNative/Codecs.h"
14#include "AODNative/Errors.h"
15#include "Endian.h"
16#include "Cursor.h"
17#include <atomic>
18#include <bit>
19#include <climits>
20#include <cstring>
21#include <exception>
22#include <mutex>
23#include <optional>
24#include <thread>
25
26namespace o2::aod::native
27{
28
29namespace
30{
31// Fixed part of a TBasket key, after which come the class name, name and title.
32constexpr size_t kBasketHeaderTail = 19;
33
34template <typename T>
35void byteswapAll(uint8_t* data, size_t count)
36{
37 for (size_t i = 0; i < count; ++i) {
38 T v;
39 std::memcpy(&v, data + i * sizeof(T), sizeof(T));
40 v = bswap(v);
41 std::memcpy(data + i * sizeof(T), &v, sizeof(T));
42 }
43}
44} // namespace
45
46void byteswapInPlace(uint8_t* data, size_t count, size_t typeSize)
47{
48 if constexpr (std::endian::native == std::endian::big) {
49 return;
50 }
51 switch (typeSize) {
52 case 2:
53 byteswapAll<uint16_t>(data, count);
54 break;
55 case 4:
56 byteswapAll<uint32_t>(data, count);
57 break;
58 case 8:
59 byteswapAll<uint64_t>(data, count);
60 break;
61 }
62}
63
67 std::span<const uint8_t> plain;
68 std::optional<PayloadDecompressor> compressed;
69 size_t border = 0;
70 size_t consumed = 0;
71
72 void read(uint8_t* dst, size_t n)
73 {
74 if (compressed) {
75 compressed->read(dst, n);
76 } else {
77 std::memcpy(dst, plain.data() + consumed, n);
78 }
79 consumed += n;
80 }
81
83 void readAll(uint8_t* dst, size_t n)
84 {
85 if (compressed) {
86 compressed->readPrefix(dst, n);
87 consumed += n;
88 } else {
89 read(dst, n);
90 }
91 }
92};
93
95 : mSource(source), mOptions(options), mUrl(source.url())
96{
97}
98
99void BranchDecoder::checkBaskets(BranchInfo const& branch) const
100{
101 int64_t next = 0;
102 for (auto& b : branch.baskets) {
103 if (b.firstEntry != next || b.nEntries < 0) {
104 throw CorruptData(mUrl, b.seek, "baskets of branch " + branch.name + " do not cover its entries contiguously");
105 }
106 next += b.nEntries;
107 }
108 if (next != branch.entries) {
109 throw CorruptData(mUrl, 0, "baskets of branch " + branch.name + " hold " + std::to_string(next) + " entries, but the branch has " + std::to_string(branch.entries));
110 }
111}
112
113template <typename F>
114void BranchDecoder::forEachBasket(BranchInfo const& branch, bool parallel, F&& fn)
115{
116 checkBaskets(branch);
117 auto task = [this, &branch, &fn](size_t i) {
118 auto const& ref = branch.baskets[i];
119 BasketValues values;
120 std::vector<uint8_t> storage; // only when the source cannot be mapped
121 if (ref.embedded) {
122 values.plain = ref.embeddedPayload;
123 values.border = ref.embeddedPayload.size();
124 } else {
125 auto bytes = mSource.tryMap(ref.seek, ref.nbytes);
126 if (bytes.empty()) {
127 if (ref.seek > mSource.size() || ref.nbytes > mSource.size() - ref.seek) {
128 throw CorruptData(mUrl, ref.seek, "basket of branch " + branch.name + " past the end of the file");
129 }
130 storage.resize(ref.nbytes);
131 mSource.readInto(ref.seek, ref.nbytes, storage.data());
132 bytes = storage;
133 }
134 Cursor c{bytes, mUrl, ref.seek};
135 auto nbytes = c.u32();
136 c.i16(); // key fVersion
137 auto objlen = c.u32();
138 c.u32(); // fDatime
139 auto keylen = c.u16();
140 if (nbytes != ref.nbytes || keylen < kBasketHeaderTail || keylen > nbytes) {
141 c.fail("basket header of branch " + branch.name + " does not match its TBranch");
142 }
143 c.seek(keylen - kBasketHeaderTail);
144 c.u16(); // fVersion
145 c.i32(); // fBufferSize
146 c.i32(); // fNevBufSize
147 auto nevbuf = c.i32();
148 auto last = c.i32();
149 if (nevbuf != ref.nEntries || last < keylen || (uint32_t)(last - keylen) > objlen) {
150 c.fail("basket header of branch " + branch.name + " does not match its TBranch");
151 }
152 values.border = last - keylen;
153 auto payload = bytes.subspan(keylen);
154 if (objlen != nbytes - keylen) {
155 values.compressed.emplace(payload, objlen, mUrl, ref.seek + keylen);
156 } else {
157 values.plain = payload;
158 }
159 }
160 fn(i, values);
161 };
162
163 auto n = branch.baskets.size();
164 int threads = parallel ? std::min<int>(mOptions.threads, n) : 1;
165 if (threads <= 1) {
166 for (size_t i = 0; i < n; ++i) {
167 task(i);
168 }
169 return;
170 }
171 // Baskets write disjoint slices of the destination, so no locking is needed.
172 std::atomic<size_t> next = 0;
173 std::exception_ptr error;
174 std::mutex errorMutex;
175 std::vector<std::thread> workers;
176 for (int t = 0; t < threads; ++t) {
177 workers.emplace_back([&] {
178 for (size_t i; (i = next.fetch_add(1)) < n;) {
179 try {
180 task(i);
181 } catch (...) {
182 std::lock_guard lock{errorMutex};
183 if (!error) {
184 error = std::current_exception();
185 }
186 next = n;
187 }
188 }
189 });
190 }
191 for (auto& w : workers) {
192 w.join();
193 }
194 if (error) {
195 std::rethrow_exception(error);
196 }
197}
198
199namespace
200{
201void checkNeeded(std::string const& url, BranchInfo const& branch, BasketRef const& ref, size_t needed, size_t border)
202{
203 if (needed > border) {
204 throw CorruptData(url, ref.seek, "basket of branch " + branch.name + " starting from " + std::to_string(ref.firstEntry) + " holds " + std::to_string(border) + " bytes, but " + std::to_string(needed) + " are needed");
205 }
206}
207} // namespace
208
209void BranchDecoder::decodeValues(BranchInfo const& branch, uint8_t* dst, size_t nbytes)
210{
211 auto elementSize = leafElementSize(branch.leafClass);
212 size_t rowBytes = elementSize * branch.fixedLen;
213 if (nbytes != branch.entries * rowBytes) {
214 throw FormatError("destination of " + std::to_string(nbytes) + " bytes for branch " + branch.name + " which needs " + std::to_string(branch.entries * rowBytes));
215 }
216 forEachBasket(branch, true, [&](size_t i, BasketValues& values) {
217 auto const& ref = branch.baskets[i];
218 size_t needed = ref.nEntries * rowBytes;
219 checkNeeded(mUrl, branch, ref, needed, values.border);
220 auto* out = dst + ref.firstEntry * rowBytes;
221 values.readAll(out, needed);
222 byteswapInPlace(out, needed / elementSize, elementSize);
223 });
224}
225
226void BranchDecoder::decodeVLAValues(BranchInfo const& branch, std::span<const int32_t> offsets, uint8_t* dst, size_t nbytes)
227{
228 auto elementSize = leafElementSize(branch.leafClass);
229 if (offsets.size() != (size_t)branch.entries + 1) {
230 throw FormatError("branch " + branch.name + " has " + std::to_string(branch.entries) + " entries, but " + std::to_string(offsets.size()) + " offsets were given");
231 }
232 if (nbytes != (size_t)offsets.back() * elementSize) {
233 throw FormatError("destination of " + std::to_string(nbytes) + " bytes for branch " + branch.name + " which needs " + std::to_string(offsets.back() * elementSize));
234 }
235 forEachBasket(branch, true, [&](size_t i, BasketValues& values) {
236 auto const& ref = branch.baskets[i];
237 auto begin = offsets[ref.firstEntry];
238 auto end = offsets[ref.firstEntry + ref.nEntries];
239 if (end < begin) {
240 throw CorruptData(mUrl, ref.seek, "invalid offset range for branch " + branch.name);
241 }
242 size_t needed = (size_t)(end - begin) * elementSize;
243 checkNeeded(mUrl, branch, ref, needed, values.border);
244 auto* out = dst + (size_t)begin * elementSize;
245 values.readAll(out, needed);
246 byteswapInPlace(out, needed / elementSize, elementSize);
247 });
248}
249
250void BranchDecoder::decodeBooleans(BranchInfo const& branch, uint8_t* bitmap, size_t nbytes)
251{
252 size_t total = branch.entries * branch.fixedLen;
253 if (nbytes * 8 < total) {
254 throw FormatError("bitmap of " + std::to_string(nbytes) + " bytes for " + std::to_string(total) + " booleans of branch " + branch.name);
255 }
256 std::memset(bitmap, 0, nbytes);
257 std::vector<uint8_t> window(std::min(kBooleanWindow, total));
258 // The value index is global across baskets, so a basket boundary can fall
259 // inside a byte of the bitmap: decode serially.
260 size_t index = 0;
261 forEachBasket(branch, false, [&](size_t i, BasketValues& values) {
262 auto const& ref = branch.baskets[i];
263 size_t remaining = (size_t)ref.nEntries * branch.fixedLen;
264 checkNeeded(mUrl, branch, ref, remaining, values.border);
265 while (remaining > 0) {
266 auto k = std::min(remaining, window.size());
267 values.read(window.data(), k);
268 for (size_t j = 0; j < k; ++j, ++index) {
269 bitmap[index / 8] |= (uint8_t)((window[j] != 0) << (index % 8));
270 }
271 remaining -= k;
272 }
273 });
274}
275
276std::vector<int32_t> BranchDecoder::decodeOffsets(BranchInfo const& sizeBranch)
277{
278 if (sizeBranch.leafClass != 'I' || sizeBranch.fixedLen != 1) {
279 throw UnsupportedLayout("size branch " + sizeBranch.name + " is not a scalar 32-bit integer");
280 }
281 // Decode the sizes after the leading 0, then prefix-sum in place.
282 std::vector<int32_t> offsets(sizeBranch.entries + 1);
283 decodeValues(sizeBranch, (uint8_t*)(offsets.data() + 1), sizeBranch.entries * sizeof(int32_t));
284 int64_t total = 0;
285 offsets[0] = 0;
286 for (int64_t i = 1; i <= sizeBranch.entries; ++i) {
287 total += (uint32_t)offsets[i];
288 // Arrow lists use 32 bit offsets, a larger total can only come from corrupted sizes.
289 if (total > INT32_MAX) {
290 throw CorruptData(mUrl, 0, "invalid sizes for branch " + sizeBranch.name + ": offsets overflow at entry " + std::to_string(i - 1));
291 }
292 offsets[i] = (int32_t)total;
293 }
294 return offsets;
295}
296
297} // namespace o2::aod::native
std::string url
int32_t i
char error[CURL_ERROR_SIZE]
uint32_t j
Definition RawData.h:0
uint32_t c
Definition RawData.h:2
size_t bytes
void decodeVLAValues(BranchInfo const &branch, std::span< const int32_t > offsets, uint8_t *dst, size_t nbytes)
Definition Decoder.cxx:226
std::vector< int32_t > decodeOffsets(BranchInfo const &sizeBranch)
Decodes a <col>_size branch into entries + 1 int32 offsets.
Definition Decoder.cxx:276
void decodeValues(BranchInfo const &branch, uint8_t *dst, size_t nbytes)
Definition Decoder.cxx:209
BranchDecoder(ByteSource &source, Options const &options)
Definition Decoder.cxx:94
static constexpr size_t kBooleanWindow
Size of the boolean window, the only decode-side scratch memory.
Definition Decoder.h:35
void decodeBooleans(BranchInfo const &branch, uint8_t *bitmap, size_t nbytes)
Definition Decoder.cxx:250
virtual uint64_t size() const =0
virtual void readInto(uint64_t off, size_t len, void *dst)=0
Throws IoError, or CorruptData when the range is past the end of the file.
virtual std::span< const uint8_t > tryMap(uint64_t off, size_t len)
A view of the bytes without copying them, if the backend can provide it.
Definition ByteSource.h:64
GLint GLint GLsizei GLint border
Definition glcorearb.h:275
GLdouble n
Definition glcorearb.h:1982
GLint GLsizei count
Definition glcorearb.h:399
GLuint GLsizei const GLuint const GLintptr * offsets
Definition glcorearb.h:2595
GLuint GLuint end
Definition glcorearb.h:469
const GLdouble * v
Definition glcorearb.h:832
GLuint index
Definition glcorearb.h:781
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLenum GLenum dst
Definition glcorearb.h:1767
GLenum GLsizei GLsizei GLint * values
Definition glcorearb.h:1576
GLboolean * data
Definition glcorearb.h:298
GLint ref
Definition glcorearb.h:291
GLubyte GLubyte GLubyte GLubyte w
Definition glcorearb.h:852
size_t leafElementSize(char leafClass)
Size in bytes of one value of a leaf class on disk.
Definition Objects.cxx:503
constexpr T bswap(T v)
Definition Endian.h:22
void byteswapInPlace(uint8_t *data, size_t count, size_t typeSize)
Byteswaps count big-endian values of typeSize bytes in place.
Definition Decoder.cxx:46
WorkflowSpec parallel(DataProcessorSpec original, size_t maxIndex, std::function< void(DataProcessorSpec &, size_t id)> amendCallback)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
void readAll(uint8_t *dst, size_t n)
The first n value bytes as the only read: lets zstd decode in place.
Definition Decoder.cxx:83
void read(uint8_t *dst, size_t n)
Definition Decoder.cxx:72
std::optional< PayloadDecompressor > compressed
Definition Decoder.cxx:68
std::vector< BasketRef > baskets
Definition Objects.h:46