SST 16.0.0
Structural Simulation Toolkit
serialize.h
1// Copyright 2009-2026 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-2026, 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
41namespace 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*/
51using ser_opt_t = uint32_t;
52
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
86namespace Core::Serialization {
87
88namespace pvt {
89
90template <typename T>
91void sst_ser_object(serializer& ser, T&& obj, ser_opt_t options, const char* name);
92
93// Proxy struct which represents a bit reference wrapper similar to std::reference_wrapper.
94// This proxy is needed in order for us to partially specialize serialize_impl for the
95// std::bitset<N>::reference and std::vector<bool>::reference types in mapping mode.
96template <typename T>
97struct bit_reference_wrapper
98{
99 typename T::reference ref;
100 explicit bit_reference_wrapper(typename T::reference ref) :
101 ref(ref)
102 {}
103 bit_reference_wrapper(const bit_reference_wrapper&) = default;
104 bit_reference_wrapper& operator=(const bit_reference_wrapper&) = delete;
105 ~bit_reference_wrapper() = default;
106};
107
108} // namespace pvt
109
110// get_ptr() returns reference to argument if it's a pointer, else address of argument
111template <typename T>
112constexpr decltype(auto)
113get_ptr(T& t)
114{
115 if constexpr ( std::is_pointer_v<T> )
116 return t;
117 else
118 return &t;
119}
120
121/**
122 Base serialize class.
123
124 This class also acts as the default case, which if hit will check
125 to see if this is an otherwise unhandled non-polymorphic class. If
126 it is, it will just attempt to call the serialize_order() function.
127 All other instances are partial specializations of this class and
128 do all the real serialization.
129 */
130template <typename T, typename = void>
132{
133 static_assert(std::is_class_v<std::remove_pointer_t<T>>,
134 "Trying to serialize a non-class type (or a pointer to one) with the default serialize_impl.");
135
136 static_assert(std::is_final_v<std::remove_pointer_t<T>> || !std::is_polymorphic_v<std::remove_pointer_t<T>>,
137 "The type or the pointed-to-type is polymorphic and non-final, so serialize_impl cannot know the runtime type "
138 "of the object being serialized. Polymorphic classes need to inherit from the serializable class and call the "
139 "ImplementSerializable() macro. For classes marked final, this is not necessary.");
140
141 static_assert(has_serialize_order_v<std::remove_pointer_t<T>>,
142 "The type or the pointed-to-type does not have a serialize_impl specialization, nor a public "
143 "serialize_order() method. This means that there is not a serialize_impl class specialization for "
144 "the type or a pointer to the type, and so the default serialize_impl is used which requires a "
145 "public serialize_order() method to be defined inside of the class type.");
146
147public:
148 void operator()(T& t, serializer& ser, ser_opt_t UNUSED(options))
149 {
150 const auto& tPtr = get_ptr(t);
151 const auto mode = ser.mode();
152 if ( mode == serializer::MAP ) {
153 if ( !tPtr ) return; // No need to map a nullptr
154 ObjectMap* map = new ObjectMapClass(tPtr, typeid(T).name());
155 ser.mapper().report_object_map(map);
156 ser.mapper().map_hierarchy_start(ser.getMapName(), map);
157 }
158 else if constexpr ( std::is_pointer_v<T> ) {
159 if ( mode == serializer::UNPACK ) {
160 t = new std::remove_pointer_t<T>();
161 ser.unpacker().report_new_pointer(reinterpret_cast<uintptr_t>(t));
162 }
163 }
164
165 // class needs to define a serialize_order() method
166 tPtr->serialize_order(ser);
167
168 if ( mode == serializer::MAP ) ser.mapper().map_hierarchy_end();
169 }
170};
171
172namespace pvt {
173
174/**
175 Serialization "gateway" object called by sst_ser_object(). All
176 serializations must come through these template instances in order
177 for pointer tracking to be controlled at one point. The actual
178 serialization will happen in serialize_impl classes.
179 */
180template <class T>
182{
183 template <class U>
184 friend void sst_ser_object(serializer& ser, U&& obj, ser_opt_t options, const char* name);
185
186 void operator()(T& t, serializer& ser, ser_opt_t options) { return serialize_impl<T>()(t, ser, options); }
187
188 /**
189 This will track the pointer to the object if pointer tracking
190 is turned on. Otherwise, it will just serialize normally. This
191 is only useful if you have a non-pointer struct/class where
192 other pieces of code will have pointers to it (e.g. a set/map
193 contains the actual struct and other places point to the data's
194 location)
195 */
196 void serialize_and_track_pointer(T& t, serializer& ser, ser_opt_t options)
197 {
198 if ( !ser.is_pointer_tracking_enabled() ) return serialize_impl<T>()(t, ser, options);
199
200 // Get the pointer to the data that will be uesd for tracking.
201 uintptr_t ptr = reinterpret_cast<uintptr_t>(&t);
202
203 switch ( ser.mode() ) {
204 case serializer::SIZER:
205 // Check to see if the pointer has already been seen. If
206 // so, then we error since the non-pointer version of this
207 // data needs to be serialized before any of the pointers
208 // that point to it.
209 if ( ser.sizer().check_pointer_sizer(ptr) ) {
210 // TODO Error
211 }
212
213 // Always put the pointer in
214 ser.size(ptr);
215 // Count the data for the object
216 serialize_impl<T>()(t, ser, options);
217 break;
218 case serializer::PACK:
219 // Already checked serialization order during sizing, so
220 // don't need to do it here
221 ser.packer().check_pointer_pack(ptr);
222 // Always put the pointer in
223 ser.pack(ptr);
224 // Serialize the data for the object
225 serialize_impl<T>()(t, ser, options);
226 break;
227 case serializer::UNPACK:
228 // Checked order on serialization, so don't need to do it
229 // here
230
231 // Get the stored pointer to use as a tag for future
232 // references to the data
233 uintptr_t ptr_stored;
234 ser.unpack(ptr_stored);
235
236 // Now add this to the tracking data
237 ser.unpacker().report_real_pointer(ptr_stored, ptr);
238
239 serialize_impl<T>()(t, ser, options);
240 break;
241 case serializer::MAP:
242 serialize_impl<T>()(t, ser, options);
243 break;
244 }
245 }
246};
247
248template <class T>
249class serialize<T*>
250{
251 template <class U>
252 friend void sst_ser_object(serializer& ser, U&& obj, ser_opt_t options, const char* name);
253 void operator()(T*& t, serializer& ser, ser_opt_t options)
254 {
255 // We are a pointer, need to see if tracking is turned on
256 if ( !ser.is_pointer_tracking_enabled() ) {
257 switch ( ser.mode() ) {
258 case serializer::UNPACK:
259 t = nullptr; // Nullify the pointer in case it was null
260 [[fallthrough]];
261 default:
262 {
263 // We will always get/put a bool to tell whether or not this is nullptr.
264 bool nonnull = t != nullptr;
265 ser.primitive(nonnull);
266
267 // If not nullptr, serialize the object
268 if ( nonnull ) serialize_impl<T*>()(t, ser, options);
269 break;
270 }
271 case serializer::MAP:
272 break; // If this version of serialize gets called in mapping mode, there is nothing to do
273 }
274 return;
275 }
276
277 uintptr_t ptr = reinterpret_cast<uintptr_t>(t);
278 if ( nullptr == t ) ptr = 0;
279
280 switch ( ser.mode() ) {
281 case serializer::SIZER:
282 // Always put the pointer in
283 ser.size(ptr);
284
285 // If this is a nullptr, then we are done
286 if ( 0 == ptr ) return;
287
288 // If we haven't seen this yet, also need to serialize the
289 // object
290 if ( !ser.sizer().check_pointer_sizer(ptr) ) {
291 serialize_impl<T*>()(t, ser, options);
292 }
293 break;
294 case serializer::PACK:
295 // Always put the pointer in
296 ser.pack(ptr);
297
298 // Nothing else to do if this is nullptr
299 if ( 0 == ptr ) return;
300
301 if ( !ser.packer().check_pointer_pack(ptr) ) {
302 serialize_impl<T*>()(t, ser, options);
303 }
304 break;
305 case serializer::UNPACK:
306 {
307 // Get the ptr and check to see if we've already deserialized
308 uintptr_t ptr_stored;
309 ser.unpack(ptr_stored);
310
311 // Check to see if this was a nullptr
312 if ( 0 == ptr_stored ) {
313 t = nullptr;
314 return;
315 }
316
317 uintptr_t real_ptr = ser.unpacker().check_pointer_unpack(ptr_stored);
318 if ( real_ptr ) {
319 // Already deserialized, so just return pointer
320 t = reinterpret_cast<T*>(real_ptr);
321 }
322 else {
323 serialize_impl<T*>()(t, ser, options);
324 ser.unpacker().report_real_pointer(ptr_stored, reinterpret_cast<uintptr_t>(t));
325 }
326 break;
327 }
328 case serializer::MAP:
329 {
330 ObjectMap* map = ser.mapper().check_pointer_map(reinterpret_cast<uintptr_t>(t));
331 if ( map != nullptr ) {
332 // If we've already seen this object, just add the
333 // existing ObjectMap to the parent.
334 ser.mapper().map_existing_object(ser.getMapName(), map);
335 }
336 else {
337 serialize_impl<T*>()(t, ser, options);
338 }
339 break;
340 }
341 }
342 }
343};
344
345// All serialization must go through this function to ensure
346// everything works correctly
347//
348// A universal/forwarding reference is used for obj so that it can match rvalue wrappers like
349// SST::Core::Serialization::array(ary, size) but then it is used as an lvalue so that it
350// matches serialization functions which only take lvalue references.
351template <class TREF>
352void
353sst_ser_object(serializer& ser, TREF&& obj, ser_opt_t options, const char* name)
354{
355 // TREF is an lvalue reference when obj argument is an lvalue reference, so we remove the reference
356 using T = std::remove_reference_t<TREF>;
357
358 // We will check for the "fast" path (i.e. event serialization for synchronizations). We can detect this by
359 // seeing if pointer tracking is turned off, because it is turned on for both checkpointing and mapping mode.
360 if ( !ser.is_pointer_tracking_enabled() ) {
361 // Options are wiped out since none apply in this case
362 pvt::serialize<T>()(obj, ser, SerOption::none);
363 }
364 else if ( ser.mode() == serializer::MAP ) {
365 // Mapping mode
366 // Check to see if we are NOMAP
367 if ( !SerOption::is_set(options, SerOption::no_map) ) {
368 ObjectMapContext context(ser, name);
369 pvt::serialize<T>()(obj, ser, options);
370 }
371 }
372 else if constexpr ( !std::is_pointer_v<T> ) {
373 // as_ptr is only valid for non-pointers
374 if ( SerOption::is_set(options, SerOption::as_ptr) ) {
375 pvt::serialize<T>().serialize_and_track_pointer(obj, ser, options);
376 }
377 else {
378 pvt::serialize<T>()(obj, ser, options);
379 }
380 }
381 else {
382 // For pointer types, just call serialize
383 pvt::serialize<T>()(obj, ser, options);
384 }
385}
386
387} // namespace pvt
388
389
390// Serialization macros for checkpoint/debug serialization
391#define SST_SER(obj, ...) \
392 SST::Core::Serialization::pvt::sst_ser_object( \
393 ser, (obj), SST::Core::Serialization::pvt::sst_ser_or_helper(__VA_ARGS__), #obj)
394
395#define SST_SER_NAME(obj, name, ...) \
396 SST::Core::Serialization::pvt::sst_ser_object( \
397 ser, (obj), SST::Core::Serialization::pvt::sst_ser_or_helper(__VA_ARGS__), name)
398
399namespace pvt {
400template <typename... Args>
401constexpr ser_opt_t
402sst_ser_or_helper(Args... args)
403{
404 return (SerOption::none | ... | args); // Fold expression to perform logical OR
405}
406
407} // namespace pvt
408
409// Serialize an object and return an ObjectMap which represents it
410template <typename T>
412ObjectMapSerialization(T&& obj)
413{
414 ObjectMapClass root;
415 serializer ser;
416 ser.enable_pointer_tracking();
417 ser.start_mapping(&root);
418 SST_SER_NAME(obj, "_proxy_object_");
419 ObjectMap* ret = root.findVariable("_proxy_object_");
420 if ( ret ) ret->incRefCount();
421 return ret;
422}
423
424} // namespace Core::Serialization
425} // namespace SST
426
427// These includes have guards to print warnings if they are included independent of this file.
428// Set the #define that will disable the warnings.
429#define SST_INCLUDING_SERIALIZE_H
430#include "sst/core/serialization/impl/serialize_adapter.h"
431#include "sst/core/serialization/impl/serialize_aggregate.h"
432#include "sst/core/serialization/impl/serialize_array.h"
433#include "sst/core/serialization/impl/serialize_atomic.h"
434#include "sst/core/serialization/impl/serialize_bitset.h"
435#include "sst/core/serialization/impl/serialize_insertable.h"
436#include "sst/core/serialization/impl/serialize_optional.h"
437#include "sst/core/serialization/impl/serialize_shared_ptr.h"
438#include "sst/core/serialization/impl/serialize_string.h"
439#include "sst/core/serialization/impl/serialize_trivial.h"
440#include "sst/core/serialization/impl/serialize_tuple.h"
441#include "sst/core/serialization/impl/serialize_unique_ptr.h"
442#include "sst/core/serialization/impl/serialize_valarray.h"
443#include "sst/core/serialization/impl/serialize_variant.h"
444
445// Reenble warnings for including the above file independent of this file.
446#undef SST_INCLUDING_SERIALIZE_H
447
448#endif // SST_CORE_SERIALIZATION_SERIALIZE_H
ObjectMap object for non-fundamental, non-container types.
Definition objectMap.h:729
ObjectMap context which is saved in a virtual stack when name or other information changes.
Definition serializer.h:158
Base class for objects created by the serializer mapping mode used to map the variables for objects.
Definition objectMap.h:188
void incRefCount()
Increment the reference counter for this ObjectMap.
Definition objectMap.h:332
ObjectMap * findVariable(const std::string &name, bool confirm=false) const
Find a variable in this object map.
Definition objectMap.cc:228
Serialization "gateway" object called by sst_ser_object().
Definition serialize.h:182
Base serialize class.
Definition serialize.h:132
This class is basically a wrapper for objects to declare the order in which their members should be s...
Definition serializer.h:43
Definition serialize.h:54
Options
Options use to control how serialization acts for specific elements that are serialized.
Definition serialize.h:59