SST  15.1.0
StructuralSimulationToolkit
serialize.h
1 // Copyright 2009-2025 NTESS. Under the terms
2 // of Contract DE-NA0003525 with NTESS, the U.S.
3 // Government retains certain rights in this software.
4 //
5 // Copyright (c) 2009-2025, NTESS
6 // All rights reserved.
7 //
8 // This file is part of the SST software package. For license
9 // information, see the LICENSE file in the top level directory of the
10 // distribution.
11 
12 #ifndef SST_CORE_SERIALIZATION_SERIALIZE_H
13 #define SST_CORE_SERIALIZATION_SERIALIZE_H
14 
15 #include "sst/core/from_string.h"
16 #include "sst/core/serialization/objectMap.h"
17 #include "sst/core/serialization/serializer.h"
18 #include "sst/core/warnmacros.h"
19 
20 #define SST_INCLUDING_SERIALIZE_H
21 #include "sst/core/serialization/impl/serialize_utility.h"
22 #undef SST_INCLUDING_SERIALIZE_H
23 
24 #include <atomic>
25 #include <cstdint>
26 #include <iostream>
27 #include <type_traits>
28 #include <typeinfo>
29 
30 /**
31  Macro used in serialize_impl<> template specializations to
32  correctly friend the serialize<> template. The operator() function
33  should be private in the serialize_impl<> implementations.
34 
35  NOTE: requires a semicolon after the call
36  */
37 #define SST_FRIEND_SERIALIZE() \
38  template <class SER> \
39  friend class pvt::serialize
40 
41 namespace SST {
42 
43 /**
44  Types for options passed to serialization. Putting in a higher
45  namespace to ease use by shortening the fully qualified names
46 **/
47 
48 /**
49  Type used to pass serialization options
50 */
51 using ser_opt_t = uint32_t;
52 
53 struct SerOption
54 {
55  /**
56  Options use to control how serialization acts for specific
57  elements that are serialized
58  */
59  enum Options : ser_opt_t {
60  // No set options
61  none = 0,
62  // Used to track the address of a non-pointer object when pointer
63  // tracking is on
64  as_ptr = 1u << 1,
65  // Used to pass the as_ptr option to the contents of a container
66  // when serialized
67  as_ptr_elem = 1u << 2,
68  // Used to specify a variable should be read-only in mapping mode
69  map_read_only = 1u << 3,
70  // Used to specify a variable should not be available in
71  // mapping mode
72  no_map = 1u << 4,
73  // User defined options are not currently supported
74  };
75 
76  // Function to test if an options for serialization is
77  // set. Function marked constexpr so that it can evaluate at
78  // compile time if all values are available. Return type must
79  // match SST::Core::Serialization::ser_opt_t
80  static constexpr bool is_set(ser_opt_t flags, SerOption::Options option)
81  {
82  return flags & static_cast<ser_opt_t>(option);
83  }
84 };
85 
86 namespace Core::Serialization {
87 
88 template <typename T>
89 void sst_ser_object(serializer& ser, T&& obj, ser_opt_t options, const char* name);
90 
91 // get_ptr() returns reference to argument if it's a pointer, else address of argument
92 template <typename T>
93 constexpr decltype(auto)
94 get_ptr(T& t)
95 {
96  if constexpr ( std::is_pointer_v<T> )
97  return t;
98  else
99  return &t;
100 }
101 
102 
103 /**
104  Base serialize class.
105 
106  This class also acts as the default case, which if hit will check
107  to see if this is an otherwise unhandled non-polymorphic class. If
108  it is, it will just attempt to call the serialize_order() function.
109  All other instances are partial specializations of this class and
110  do all the real serialization.
111  */
112 template <typename T, typename = void>
114 {
115  static_assert(std::is_class_v<std::remove_pointer_t<T>>,
116  "Trying to serialize a non-class type (or a pointer to one) with the default serialize_impl.");
117 
118  static_assert(std::is_final_v<std::remove_pointer_t<T>> || !std::is_polymorphic_v<std::remove_pointer_t<T>>,
119  "The type or the pointed-to-type is polymorphic and non-final, so serialize_impl cannot know the runtime type "
120  "of the object being serialized. Polymorphic classes need to inherit from the serializable class and call the "
121  "ImplementSerializable() macro. For classes marked final, this is not necessary.");
122 
123  static_assert(has_serialize_order_v<std::remove_pointer_t<T>>,
124  "The type or the pointed-to-type does not have a serialize_impl specialization, nor a public "
125  "serialize_order() method. This means that there is not a serialize_impl class specialization for "
126  "the type or a pointer to the type, and so the default serialize_impl is used which requires a "
127  "public serialize_order() method to be defined inside of the class type.");
128 
129 public:
130  void operator()(T& t, serializer& ser, ser_opt_t UNUSED(options))
131  {
132  const auto& tPtr = get_ptr(t);
133  const auto mode = ser.mode();
134  if ( mode == serializer::MAP ) {
135  if ( !tPtr ) return; // No need to map a nullptr
136  ObjectMap* map = new ObjectMapClass(tPtr, typeid(T).name());
137  ser.mapper().report_object_map(map);
138  ser.mapper().map_hierarchy_start(ser.getMapName(), map);
139  }
140  else if constexpr ( std::is_pointer_v<T> ) {
141  if ( mode == serializer::UNPACK ) {
142  t = new std::remove_pointer_t<T>();
143  ser.unpacker().report_new_pointer(reinterpret_cast<uintptr_t>(t));
144  }
145  }
146 
147  // class needs to define a serialize_order() method
148  tPtr->serialize_order(ser);
149 
150  if ( mode == serializer::MAP ) ser.mapper().map_hierarchy_end();
151  }
152 };
153 
154 namespace pvt {
155 
156 /**
157  Serialization "gateway" object called by sst_ser_object(). All
158  serializations must come through these template instances in order
159  for pointer tracking to be controlled at one point. The actual
160  serialization will happen in serialize_impl classes.
161  */
162 template <class T>
164 {
165  template <class U>
166  friend void SST::Core::Serialization::sst_ser_object(serializer& ser, U&& obj, ser_opt_t options, const char* name);
167 
168  void operator()(T& t, serializer& ser, ser_opt_t options) { return serialize_impl<T>()(t, ser, options); }
169 
170  /**
171  This will track the pointer to the object if pointer tracking
172  is turned on. Otherwise, it will just serialize normally. This
173  is only useful if you have a non-pointer struct/class where
174  other pieces of code will have pointers to it (e.g. a set/map
175  contains the actual struct and other places point to the data's
176  location)
177  */
178  void serialize_and_track_pointer(T& t, serializer& ser, ser_opt_t options)
179  {
180  if ( !ser.is_pointer_tracking_enabled() ) return serialize_impl<T>()(t, ser, options);
181 
182  // Get the pointer to the data that will be uesd for tracking.
183  uintptr_t ptr = reinterpret_cast<uintptr_t>(&t);
184 
185  switch ( ser.mode() ) {
186  case serializer::SIZER:
187  // Check to see if the pointer has already been seen. If
188  // so, then we error since the non-pointer version of this
189  // data needs to be serialized before any of the pointers
190  // that point to it.
191  if ( ser.sizer().check_pointer_sizer(ptr) ) {
192  // TODO Error
193  }
194 
195  // Always put the pointer in
196  ser.size(ptr);
197  // Count the data for the object
198  serialize_impl<T>()(t, ser, options);
199  break;
200  case serializer::PACK:
201  // Already checked serialization order during sizing, so
202  // don't need to do it here
203  ser.packer().check_pointer_pack(ptr);
204  // Always put the pointer in
205  ser.pack(ptr);
206  // Serialize the data for the object
207  serialize_impl<T>()(t, ser, options);
208  break;
209  case serializer::UNPACK:
210  // Checked order on serialization, so don't need to do it
211  // here
212 
213  // Get the stored pointer to use as a tag for future
214  // references to the data
215  uintptr_t ptr_stored;
216  ser.unpack(ptr_stored);
217 
218  // Now add this to the tracking data
219  ser.unpacker().report_real_pointer(ptr_stored, ptr);
220 
221  serialize_impl<T>()(t, ser, options);
222  break;
223  case serializer::MAP:
224  serialize_impl<T>()(t, ser, options);
225  break;
226  }
227  }
228 };
229 
230 template <class T>
231 class serialize<T*>
232 {
233  template <class U>
234  friend void SST::Core::Serialization::sst_ser_object(serializer& ser, U&& obj, ser_opt_t options, const char* name);
235  void operator()(T*& t, serializer& ser, ser_opt_t options)
236  {
237  // We are a pointer, need to see if tracking is turned on
238  if ( !ser.is_pointer_tracking_enabled() ) {
239  switch ( ser.mode() ) {
240  case serializer::UNPACK:
241  t = nullptr; // Nullify the pointer in case it was null
242  [[fallthrough]];
243  default:
244  {
245  // We will always get/put a bool to tell whether or not this is nullptr.
246  bool nonnull = t != nullptr;
247  ser.primitive(nonnull);
248 
249  // If not nullptr, serialize the object
250  if ( nonnull ) serialize_impl<T*>()(t, ser, options);
251  break;
252  }
253  case serializer::MAP:
254  break; // If this version of serialize gets called in mapping mode, there is nothing to do
255  }
256  return;
257  }
258 
259  uintptr_t ptr = reinterpret_cast<uintptr_t>(t);
260  if ( nullptr == t ) ptr = 0;
261 
262  switch ( ser.mode() ) {
263  case serializer::SIZER:
264  // Always put the pointer in
265  ser.size(ptr);
266 
267  // If this is a nullptr, then we are done
268  if ( 0 == ptr ) return;
269 
270  // If we haven't seen this yet, also need to serialize the
271  // object
272  if ( !ser.sizer().check_pointer_sizer(ptr) ) {
273  serialize_impl<T*>()(t, ser, options);
274  }
275  break;
276  case serializer::PACK:
277  // Always put the pointer in
278  ser.pack(ptr);
279 
280  // Nothing else to do if this is nullptr
281  if ( 0 == ptr ) return;
282 
283  if ( !ser.packer().check_pointer_pack(ptr) ) {
284  serialize_impl<T*>()(t, ser, options);
285  }
286  break;
287  case serializer::UNPACK:
288  {
289  // Get the ptr and check to see if we've already deserialized
290  uintptr_t ptr_stored;
291  ser.unpack(ptr_stored);
292 
293  // Check to see if this was a nullptr
294  if ( 0 == ptr_stored ) {
295  t = nullptr;
296  return;
297  }
298 
299  uintptr_t real_ptr = ser.unpacker().check_pointer_unpack(ptr_stored);
300  if ( real_ptr ) {
301  // Already deserialized, so just return pointer
302  t = reinterpret_cast<T*>(real_ptr);
303  }
304  else {
305  serialize_impl<T*>()(t, ser, options);
306  ser.unpacker().report_real_pointer(ptr_stored, reinterpret_cast<uintptr_t>(t));
307  }
308  break;
309  }
310  case serializer::MAP:
311  {
312  ObjectMap* map = ser.mapper().check_pointer_map(reinterpret_cast<uintptr_t>(t));
313  if ( map != nullptr ) {
314  // If we've already seen this object, just add the
315  // existing ObjectMap to the parent.
316  ser.mapper().map_existing_object(ser.getMapName(), map);
317  }
318  else {
319  serialize_impl<T*>()(t, ser, options);
320  }
321  break;
322  }
323  }
324  }
325 };
326 
327 } // namespace pvt
328 
329 // All serialization must go through this function to ensure
330 // everything works correctly
331 //
332 // A universal/forwarding reference is used for obj so that it can match rvalue wrappers like
333 // SST::Core::Serialization::array(ary, size) but then it is used as an lvalue so that it
334 // matches serialization functions which only take lvalue references.
335 template <class TREF>
336 void
337 sst_ser_object(serializer& ser, TREF&& obj, ser_opt_t options, const char* name)
338 {
339  // TREF is an lvalue reference when obj argument is an lvalue reference, so we remove the reference
340  using T = std::remove_reference_t<TREF>;
341 
342  // We will check for the "fast" path (i.e. event serialization for synchronizations). We can detect this by
343  // seeing if pointer tracking is turned off, because it is turned on for both checkpointing and mapping mode.
344  if ( !ser.is_pointer_tracking_enabled() ) {
345  // Options are wiped out since none apply in this case
346  return pvt::serialize<T>()(obj, ser, SerOption::none);
347  }
348 
349  // Mapping mode
350  if ( ser.mode() == serializer::MAP ) {
351  ObjectMapContext context(ser, name);
352  // Check to see if we are NOMAP
353  if ( SerOption::is_set(options, SerOption::no_map) ) return;
354 
355  pvt::serialize<T>()(obj, ser, options);
356  return;
357  }
358 
359  if constexpr ( !std::is_pointer_v<T> ) {
360  // as_ptr is only valid for non-pointers
361  if ( SerOption::is_set(options, SerOption::as_ptr) ) {
362  pvt::serialize<T>().serialize_and_track_pointer(obj, ser, options);
363  }
364  else {
365  pvt::serialize<T>()(obj, ser, options);
366  }
367  }
368  else {
369  // For pointer types, just call serialize
370  pvt::serialize<T>()(obj, ser, options);
371  }
372 }
373 
374 // A universal/forwarding reference is used for obj so that it can match rvalue wrappers like
375 // SST::Core::Serialization::array(ary, size) but then it is used as an lvalue so that it
376 // matches serialization functions which only take lvalue references.
377 template <class T>
378 [[deprecated("The ser& syntax for serialization has been deprecated and will be removed in SST 16. Please use the "
379  "SST_SER macro for serializing data. The macro supports additional options to control the details of "
380  "serialization. See the SerOption enum for details.")]]
381 void
382 operator&(serializer& ser, T&& obj)
383 {
384  SST::Core::Serialization::sst_ser_object(ser, obj, SerOption::no_map, "");
385 }
386 
387 template <class T>
388 [[deprecated("The ser| syntax for serialization has been deprecated and will be removed in SST 16. Please use the "
389  "SST_SER macro with the SerOption::as_ptr flag for serializing data. The macro supports additional "
390  "options to control the details of serialization. See the SerOption enum for details.")]]
391 void
392 operator|(serializer& ser, T&& obj)
393 {
394  SST::Core::Serialization::sst_ser_object(ser, obj, SerOption::no_map | SerOption::as_ptr, "");
395 }
396 
397 
398 // Serialization macros for checkpoint/debug serialization
399 #define SST_SER(obj, ...) \
400  SST::Core::Serialization::sst_ser_object( \
401  ser, (obj), SST::Core::Serialization::pvt::sst_ser_or_helper(__VA_ARGS__), #obj)
402 
403 #define SST_SER_NAME(obj, name, ...) \
404  SST::Core::Serialization::sst_ser_object( \
405  ser, (obj), SST::Core::Serialization::pvt::sst_ser_or_helper(__VA_ARGS__), name)
406 
407 
408 // #define SST_SER_AS_PTR(obj) (ser | (obj));
409 
410 namespace pvt {
411 template <typename... Args>
412 constexpr ser_opt_t
413 sst_ser_or_helper(Args... args)
414 {
415  return (SerOption::none | ... | args); // Fold expression to perform logical OR
416 }
417 
418 } // namespace pvt
419 } // namespace Core::Serialization
420 } // namespace SST
421 
422 // These includes have guards to print warnings if they are included independent of this file.
423 // Set the #define that will disable the warnings.
424 #define SST_INCLUDING_SERIALIZE_H
425 #include "sst/core/serialization/impl/serialize_adapter.h"
426 #include "sst/core/serialization/impl/serialize_aggregate.h"
427 #include "sst/core/serialization/impl/serialize_array.h"
428 #include "sst/core/serialization/impl/serialize_atomic.h"
429 #include "sst/core/serialization/impl/serialize_bitset.h"
430 #include "sst/core/serialization/impl/serialize_insertable.h"
431 #include "sst/core/serialization/impl/serialize_optional.h"
432 // Disable until more stable across platforms
433 // #include "sst/core/serialization/impl/serialize_shared_ptr.h"
434 #include "sst/core/serialization/impl/serialize_string.h"
435 #include "sst/core/serialization/impl/serialize_trivial.h"
436 #include "sst/core/serialization/impl/serialize_tuple.h"
437 #include "sst/core/serialization/impl/serialize_unique_ptr.h"
438 #include "sst/core/serialization/impl/serialize_valarray.h"
439 #include "sst/core/serialization/impl/serialize_variant.h"
440 
441 // Reenble warnings for including the above file independent of this file.
442 #undef SST_INCLUDING_SERIALIZE_H
443 
444 #endif // SST_CORE_SERIALIZATION_SERIALIZE_H
This class is basically a wrapper for objects to declare the order in which their members should be s...
Definition: serializer.h:42
Definition: serialize.h:53
Base serialize class.
Definition: serialize.h:113
Definition: action.cc:18
Base class for objects created by the serializer mapping mode used to map the variables for objects...
Definition: objectMap.h:158
ObjectMap object for non-fundamental, non-container types.
Definition: objectMap.h:675
ObjectMap context which is saved in a virtual stack when name or other information changes...
Definition: serializer.h:155
Serialization "gateway" object called by sst_ser_object().
Definition: serialize.h:163
Options
Options use to control how serialization acts for specific elements that are serialized.
Definition: serialize.h:59