-
Notifications
You must be signed in to change notification settings - Fork 499
Expand file tree
/
Copy pathFlatObject.h
More file actions
589 lines (500 loc) · 20.5 KB
/
FlatObject.h
File metadata and controls
589 lines (500 loc) · 20.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
// All rights not expressly granted are reserved.
//
// This software is distributed under the terms of the GNU General Public
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
//
// In applying this license CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
/// \file FlatObject.h
/// \brief Definition of FlatObject class
///
/// \author Sergey Gorbunov <sergey.gorbunov@cern.ch>
#ifndef ALICEOW_GPUCOMMON_TPCFASTTRANSFORMATION_FLATOBJECT_H
#define ALICEOW_GPUCOMMON_TPCFASTTRANSFORMATION_FLATOBJECT_H
#if !defined(GPUCA_GPUCODE_DEVICE)
#include <cstddef>
#include <memory>
#include <cstring>
#include <cassert>
#endif
#include "GPUCommonDef.h"
#include "GPUCommonRtypes.h"
#include "GPUCommonLogger.h"
// #define GPUCA_GPUCODE // uncomment to test "GPU" mode
namespace o2
{
namespace gpu
{
///
/// The FlatObject class represents base class for flat objects.
/// Objects may contain variable-size data, stored in a buffer.
/// The data may contain pointers to the buffer inside.
/// The buffer can be internal, placed in mFlatBufferContainer, or external.
///
/// Important:
/// All methods of the FlatObject are marked "protected" in order to not let users to call them directly.
/// They all should be reimplemented in daughter classes,
/// because only daughter class knows how to reset all pointers in the data buffer.
/// This is an unusual decision. Normally, to avoid confusion one should just mark methods of a base class virtual or abstract.
/// But this right solution invokes use of a virtual table and complicates porting objects to other machines.
/// Therefore: no virtual functions but protected methods.
///
/// == Object construction.
///
/// This base class performs some basic operations, like setting initialisation flags,
/// allocating / releasing memory etc. As no virtual methods are involved,
/// the rest should be done manually in daughter classes.
///
/// It is assumed, that a daughter object may be complicated
/// and can not be constructed by just a call of its c++ constructor.
/// May be it needs to wait for something else to be initialized first. Like a database or so.
///
/// Therefore the object may find itself in some intermediate half-constructed state,
/// where its data stays in private temporary arrays and can not be yet copied to the flat buffer.
///
/// To deal with it, some extra control on the initialization flow is provided.
///
/// The object can be constructed either
/// a) by calling
/// void startConstruction();
/// ... do something ..
/// void finishConstruction( int32_t flatBufferSize );
///
/// b) or by cloning from another constructed(!) object:
/// obj.CloneFromObject(..)
///
/// A new obect is by default in "Not Constructed" state, operations with its buffer will cause an error.
///
///
/// == Making an internal buffer external
//
/// option a)
/// std::unique_ptr<char[]> p = obj.releaseInternalBuffer();
/// ..taking care on p..
///
/// option b)
/// std::unique_ptr<char[]> p( new char[obj.GetBufferSize()] );
/// obj.moveBufferTo( p.get() );
/// ..taking care on p..
///
/// option c)
/// std::unique_ptr<char[]> p( new char[obj.GetBufferSize()] );
/// obj.cloneFromObject(obj, p.get() );
/// ..taking care on p..
///
/// === Making an external buffer internal
///
/// option a)
/// obj.cloneFromObject( obj, nullptr );
///
/// option b)
/// obj.moveBufferTo( nullptr );
///
/// == Moving an object to other machine.
///
/// This only works when the buffer is external.
/// The object and its buffer are supposed to be bit-wise ported to a new place.
/// And they need to find each other there.
/// There are 2 options:
///
/// option a) The new buffer location is only known after the transport. In this case call:
/// obj.setActualBufferAddress( new buffer address )
/// from the new place.
///
/// option b) The new buffer location is known before the transport (case of porting to GPU). In this case call:
/// obj.setFutureBufferAddress( char* futureFlatBufferPtr );
/// before the transport. The object will be ready-to-use right after the porting.
///
class FlatObject
{
public:
/// _____________ Constructors / destructors __________________________
/// Default constructor / destructor
#ifndef GPUCA_GPUCODE
FlatObject() = default; // No object derrived from FlatObject should be created on the GPU
~FlatObject();
FlatObject(const FlatObject&) = delete;
FlatObject& operator=(const FlatObject&) = delete;
#else
FlatObject() = delete;
#endif
#ifndef GPUCA_GPUCODE // code invisible on GPU
template <typename T>
T* resizeArray(T*& ptr, int32_t oldSize, int32_t newSize, T* newPtr = nullptr)
{
// Resize array pointed by ptr. T must be a POD class.
// If the non-null newPtr is provided, use it instead of allocating a new one.
// In this case it is up to the user to ensure that it has at least newSize slots allocated.
// Return original array pointer, so that the user can manage previously allocate memory
if (oldSize < 0) {
oldSize = 0;
}
if (newSize > 0) {
if (!newPtr) {
newPtr = new T[newSize];
}
int32_t mcp = std::min(newSize, oldSize);
if (mcp) {
assert(ptr);
std::memmove(newPtr, ptr, mcp * sizeof(T));
}
if (newSize > oldSize) {
std::memset(newPtr + mcp, 0, (newSize - oldSize) * sizeof(T));
}
}
T* oldPtr = ptr;
ptr = newPtr;
return oldPtr;
}
template <typename T>
T** resizeArray(T**& ptr, int32_t oldSize, int32_t newSize, T** newPtr = nullptr)
{
// Resize array of pointers pointed by ptr.
// If the non-null newPtr is provided, use it instead of allocating a new one.
// In this case it is up to the user to ensure that it has at least newSize slots allocated.
// Return original array pointer, so that the user can manage previously allocate memory
if (oldSize < 0) {
oldSize = 0;
}
if (newSize > 0) {
if (!newPtr) {
newPtr = new T*[newSize];
}
int32_t mcp = std::min(newSize, oldSize);
std::memmove(newPtr, ptr, mcp * sizeof(T*));
if (newSize > oldSize) {
std::memset(newPtr + mcp, 0, (newSize - oldSize) * sizeof(T*));
}
}
T** oldPtr = ptr;
ptr = newPtr;
return oldPtr;
}
#endif //! GPUCA_GPUCODE
protected:
/// _____________ Memory alignment __________________________
/// Gives minimal alignment in bytes required for the class object
static constexpr size_t getClassAlignmentBytes() { return 8; }
/// Gives minimal alignment in bytes required for the flat buffer
static constexpr size_t getBufferAlignmentBytes() { return 8; }
/// _____________ Construction _________
/// Starts the construction procedure. A daughter class should reserve temporary memory.
void startConstruction();
/// Finishes construction: creates internal flat buffer.
/// A daughter class should put all created variable-size members to this buffer
///
void finishConstruction(int32_t flatBufferSize);
/// Initializes from another object, copies data to newBufferPtr
/// When newBufferPtr==nullptr, an internal container will be created, the data will be copied there.
/// A daughter class should relocate pointers inside the buffer.
///
#ifndef GPUCA_GPUCODE
void cloneFromObject(const FlatObject& obj, char* newFlatBufferPtr);
#endif // !GPUCA_GPUCODE
/// _____________ Methods for making the data buffer external __________________________
// Returns an unique pointer to the internal buffer with all the rights. Makes the internal container variable empty.
char* releaseInternalBuffer();
/// Sets buffer pointer to the new address, move the buffer content there.
/// A daughter class must relocate all the pointers inside th buffer
#ifndef GPUCA_GPUCODE
void moveBufferTo(char* newBufferPtr);
#endif // !GPUCA_GPUCODE
/// _____________ Methods for moving the class with its external buffer to another location __________________________
/// Sets the actual location of the flat buffer after it has been moved (i.e. to another maschine)
/// It sets mFlatBufferPtr to actualFlatBufferPtr.
/// A daughter class should later update all the pointers inside the buffer to the new location.
///
void setActualBufferAddress(char* actualFlatBufferPtr);
/// Sets a future location of the external flat buffer before moving it to this location (i.e. when copying to GPU).
///
/// The object can be used immidiatelly after the move, call of setActualFlatBufferAddress() is not needed.
///
/// A daughter class should already relocate all the pointers inside the current buffer to the future location.
/// It should not touch memory in the future location, since it may be not yet available.
///
/// !!! Information about the actual buffer location will be lost.
/// !!! Most of the class methods may be called only after the buffer will be moved to its new location.
/// !!! To undo call setActualFlatBufferAddress()
///
void setFutureBufferAddress(char* futureFlatBufferPtr);
/// _______________ Utilities _______________________________________________
public:
/// Set the object to NotConstructed state, release the buffer
void destroy();
/// Gives size of the flat buffer
GPUdi() size_t getFlatBufferSize() const { return mFlatBufferSize; }
/// Gives pointer to the flat buffer
GPUdi() const char* getFlatBufferPtr() const { return mFlatBufferPtr; }
/// Tells if the object is constructed
bool isConstructed() const { return (mConstructionMask & (uint32_t)ConstructionState::Constructed); }
/// Tells if the buffer is internal
bool isBufferInternal() const { return ((mFlatBufferPtr != nullptr) && (mFlatBufferPtr == mFlatBufferContainer)); }
// Adopt an external pointer as internal buffer
void adoptInternalBuffer(char* buf);
// Hard reset of internal pointer to nullptr without deleting (needed copying an object without releasing)
void clearInternalBufferPtr();
/// _______________ Generic utilities _______________________________________________
public:
/// Increases given size to achieve required alignment
static constexpr size_t alignSize(size_t sizeBytes, size_t alignmentBytes)
{
auto res = sizeBytes % alignmentBytes;
return res ? sizeBytes + (alignmentBytes - res) : sizeBytes;
}
/// Relocates a pointer inside a buffer to the new buffer address
template <class T>
static T* relocatePointer(const char* oldBase, char* newBase, const T* ptr)
{
return (ptr != nullptr) ? reinterpret_cast<T*>(newBase + (reinterpret_cast<const char*>(ptr) - oldBase)) : nullptr;
}
#if !defined(GPUCA_GPUCODE) && !defined(GPUCA_STANDALONE) // code invisible on GPU
/// write a child class object to the file
template <class T, class TFile>
static int32_t writeToFile(T& obj, TFile& outf, const char* name);
/// read a child class object from the file
template <class T, class TFile>
static T* readFromFile(TFile& inpf, const char* name);
#endif
#if !defined(GPUCA_GPUCODE) // code invisible on GPU
/// Test the flat object functionality for a child class T
template <class T>
static std::string stressTest(T& obj);
/// Print the content of the flat buffer
void printC() const;
#endif //! GPUCA_GPUCODE
protected:
/// _______________ Data members _______________________________________________
/// Enumeration of construction states
enum ConstructionState : uint32_t {
NotConstructed = 0x0, ///< the object is not constructed
Constructed = 0x1, ///< the object is constructed, temporary memory is released
InProgress = 0x2 ///< construction started: temporary memory is reserved
};
int32_t mFlatBufferSize = 0; ///< size of the flat buffer
uint32_t mConstructionMask = ConstructionState::NotConstructed; ///< mask for constructed object members, first two bytes are used by this class
char* mFlatBufferContainer = nullptr; //[mFlatBufferSize] Optional container for the flat buffer
char* mFlatBufferPtr = nullptr; //! Pointer to the flat buffer
ClassDefNV(FlatObject, 1);
};
/// ==================================================================================================
/// NoFlatObject: minimal drop-in base for use in POD / read-only contexts (e.g. TPCFastTransformPOD).
/// Provides only the data and methods that are genuinely needed at runtime.
/// No construction lifecycle, no owned buffer, no ROOT ClassDef.
/// Use as the FlatBase template parameter: Spline2D<float, 3, NoFlatObject>
class NoFlatObject
{
public:
int32_t mFlatBufferSize = 0;
static constexpr size_t getClassAlignmentBytes() { return 8; }
static constexpr size_t getBufferAlignmentBytes() { return 8; }
GPUdi() size_t getFlatBufferSize() const { return mFlatBufferSize; }
};
/// ========================================================================================================
///
/// Inline implementations of methods
///
/// ========================================================================================================
#ifndef GPUCA_GPUCODE // code invisible on GPU
inline FlatObject::~FlatObject()
{
destroy();
}
inline void FlatObject::startConstruction()
{
/// Starts the construction procedure. A daughter class should reserve temporary memory.
destroy();
mConstructionMask = ConstructionState::InProgress;
}
inline void FlatObject::destroy()
{
/// Set the object to NotConstructed state, release the buffer
mFlatBufferSize = 0;
delete[] mFlatBufferContainer;
mFlatBufferPtr = mFlatBufferContainer = nullptr;
mConstructionMask = ConstructionState::NotConstructed;
}
inline void FlatObject::finishConstruction(int32_t flatBufferSize)
{
/// Finishes construction: creates internal flat buffer.
/// A daughter class should put all created variable-size members to this buffer
assert(mConstructionMask & (uint32_t)ConstructionState::InProgress);
mFlatBufferSize = flatBufferSize;
mFlatBufferPtr = mFlatBufferContainer = new char[mFlatBufferSize];
memset((void*)mFlatBufferPtr, 0, mFlatBufferSize); // just to avoid random behavior in case of bugs
mConstructionMask = (uint32_t)ConstructionState::Constructed; // clear other possible construction flags
}
inline void FlatObject::cloneFromObject(const FlatObject& obj, char* newFlatBufferPtr)
{
/// Initializes from another object, copies data to newBufferPtr
/// When newBufferPtr==nullptr, the internal container will be created, the data will be copied there.
/// obj can be *this (provided it does not own its buffer AND the external buffer is provided, which means
// that we want to relocate the obj to external buffer)
assert(obj.isConstructed());
// providing *this with internal buffer as obj makes sens only if we want to conver it to object with PROVIDED external buffer
assert(!(!newFlatBufferPtr && obj.mFlatBufferPtr == mFlatBufferPtr && obj.isBufferInternal()));
char* oldPtr = resizeArray(mFlatBufferPtr, mFlatBufferSize, obj.mFlatBufferSize, newFlatBufferPtr);
if (isBufferInternal()) {
delete[] oldPtr; // delete old buffer if owned
}
mFlatBufferSize = obj.mFlatBufferSize;
mFlatBufferContainer = newFlatBufferPtr ? nullptr : mFlatBufferPtr; // external buffer is not provided, make object to own the buffer
std::memcpy(mFlatBufferPtr, obj.mFlatBufferPtr, obj.mFlatBufferSize);
mConstructionMask = (uint32_t)ConstructionState::Constructed;
}
inline void FlatObject::moveBufferTo(char* newFlatBufferPtr)
{
/// sets buffer pointer to the new address, move the buffer content there.
if (newFlatBufferPtr == mFlatBufferContainer) {
return;
}
resizeArray(mFlatBufferPtr, mFlatBufferSize, mFlatBufferSize, newFlatBufferPtr);
delete[] mFlatBufferContainer;
mFlatBufferContainer = nullptr;
if (!newFlatBufferPtr) { // resizeArray has created own array
mFlatBufferContainer = mFlatBufferPtr;
}
}
template <class T>
inline std::string FlatObject::stressTest(T& obj)
{
/// Test the flat object functionality for an object of a child class T
/// the obj is modified here. Check if it is functional after the test.
///
std::string err;
if (!obj.isConstructed()) {
return "tested object is not constructed!";
}
T tst;
tst.cloneFromObject(obj, nullptr);
if (!tst.isConstructed() || !tst.isBufferInternal()) {
return "error at cloneFromObject()!";
}
obj.destroy();
char* buf0 = tst.releaseInternalBuffer();
char* buf1 = new char[tst.getFlatBufferSize()];
char* buf2 = new char[tst.getFlatBufferSize()];
std::memcpy(buf1, tst.getFlatBufferPtr(), tst.getFlatBufferSize());
tst.setActualBufferAddress(buf1);
delete[] buf0;
tst.setFutureBufferAddress(buf2);
std::memcpy(buf2, buf1, tst.getFlatBufferSize());
delete[] buf1;
if (tst.isBufferInternal()) {
return err = "error, buffer should be external!";
}
tst.adoptInternalBuffer(buf2);
if (!tst.isBufferInternal()) {
return err = "error, buffer should be internal!";
}
obj.cloneFromObject(tst, nullptr);
if (!obj.isBufferInternal()) {
return err = "error, buffer should be internal!";
}
return err;
}
inline void FlatObject::printC() const
{
/// Print the content of the flat buffer
bool lfdone = false;
for (int32_t i = 0; i < mFlatBufferSize; i++) {
uint8_t v = mFlatBufferPtr[i];
lfdone = false;
printf("0x%02x ", v);
if (i && ((i + 1) % 20) == 0) {
printf("\n");
lfdone = true;
}
}
if (!lfdone) {
printf("\n");
}
}
#endif // GPUCA_GPUCODE
#if !defined(GPUCA_GPUCODE) && !defined(GPUCA_STANDALONE) // code invisible on GPU
template <class T, class TFile>
inline int32_t FlatObject::writeToFile(T& obj, TFile& outf, const char* name)
{
/// store to file
assert(obj.isConstructed());
if (outf.IsZombie()) {
LOG(error) << "Failed to write to file " << outf.GetName();
return -1;
}
bool isBufferExternal = !obj.isBufferInternal();
if (isBufferExternal) {
obj.adoptInternalBuffer(obj.mFlatBufferPtr);
}
outf.WriteObjectAny(&obj, T::Class(), name);
if (isBufferExternal) {
obj.clearInternalBufferPtr();
}
return 0;
}
template <class T, class TFile>
inline T* FlatObject::readFromFile(TFile& inpf, const char* name)
{
/// read from file
if (inpf.IsZombie()) {
LOG(error) << "Failed to read from file " << inpf.GetName();
return nullptr;
}
T* pobj = reinterpret_cast<T*>(inpf.GetObjectChecked(name, T::Class()));
if (!pobj) {
LOG(error) << "Failed to load " << name << " from " << inpf.GetName();
return nullptr;
}
if (pobj->mFlatBufferSize > 0 && pobj->mFlatBufferContainer == nullptr) {
LOG(error) << "Failed to load " << name << " from " << inpf.GetName() << ": empty flat buffer container";
return nullptr;
}
pobj->setActualBufferAddress(pobj->mFlatBufferContainer);
return pobj;
}
#endif // GPUCA_GPUCODE || GPUCA_STANDALONE
#ifndef GPUCA_GPUCODE_DEVICE
inline char* FlatObject::releaseInternalBuffer()
{
// returns an pointer to the internal buffer. Makes the internal container variable empty.
char* contPtr = mFlatBufferContainer;
mFlatBufferContainer = nullptr;
return contPtr;
}
inline void FlatObject::adoptInternalBuffer(char* buf)
{
// buf becomes the new internal buffer, after it was already set as new setActualBufferAddress
assert((mFlatBufferPtr == buf));
mFlatBufferContainer = buf;
}
inline void FlatObject::clearInternalBufferPtr()
{
// we just release the internal buffer ressetting it to nullptr
mFlatBufferContainer = nullptr;
}
inline void FlatObject::setActualBufferAddress(char* actualFlatBufferPtr)
{
/// Sets the actual location of the external flat buffer after it has been moved (i.e. to another maschine)
///
/// It sets mFlatBufferPtr to actualFlatBufferPtr.
/// A daughter class should update all the pointers inside the buffer in the new location.
mFlatBufferPtr = actualFlatBufferPtr;
}
inline void FlatObject::setFutureBufferAddress(char* futureFlatBufferPtr)
{
/// Sets a future location of the external flat buffer before moving it to this location.
///
/// A daughter class should already reset all the pointers inside the current buffer to the future location
/// without touching memory in the future location.
assert(!isBufferInternal());
mFlatBufferPtr = futureFlatBufferPtr;
#ifndef GPUCA_GPUCODE // code invisible on GPU
delete[] mFlatBufferContainer; // for a case..
#endif // !GPUCA_GPUCODE
mFlatBufferContainer = nullptr;
}
#endif // GPUCA_GPUCODE_DEVICE
} // namespace gpu
} // namespace o2
#endif