SST 15.0
Structural Simulation Toolkit
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#include <atomic>
21#include <iostream>
22#include <type_traits>
23#include <typeinfo>
24
25/**
26 Macro used in serialize_impl<> template specializations to
27 correctly friend the serialize<> template. The operator() function
28 should be private in the serialize_impl<> implemenations.
29
30 NOTE: requires a semicolon after the call
31 */
32#define SST_FRIEND_SERIALIZE() \
33 template <class SER> \
34 friend class pvt::serialize
35
36namespace SST {
37
38/**
39 Types for options passed to serialization. Putting in a higher
40 namespace to ease use by shortneing the fully qualified names
41**/
42
43/**
44 Type used to pass serilaization options
45*/
46using ser_opt_t = uint32_t;
47
49{
50 /**
51 Options use to control how serialization acts for specific
52 elements that are serialized
53 */
54 enum Options : ser_opt_t {
55 // No set options
56 none = 0,
57 // Used to track the address of a non-pointer object when pointer
58 // tracking is on
59 as_ptr = 1u << 1,
60 // Used to pass the as_ptr option to the contents of a container
61 // when serialized
62 as_ptr_elem = 1u << 2,
63 // Used to specify a variable should be read-only in mapping mode
64 map_read_only = 1u << 3,
65 // Used to specify a variable should not be available in
66 // mapping mode
67 no_map = 1u << 4,
68 // User defined options are not currently supported
69 };
70
71 // Function to test if an options for serialization is
72 // set. Function marked constexpr so that it can evaluate at
73 // compile time if all values are available. Return type must
74 // match SST::Core::Serialization::ser_opt_t
75 static constexpr bool is_set(ser_opt_t flags, SerOption::Options option)
76 {
77 return flags & static_cast<ser_opt_t>(option);
78 }
79};
80
81namespace Core::Serialization {
82
83template <typename T>
84void sst_ser_object(serializer& ser, T&& obj, ser_opt_t options = SerOption::none, const char* name = nullptr);
85
86
87// get_ptr() returns reference to argument if it's a pointer, else address of argument
88template <typename T>
89constexpr decltype(auto)
90get_ptr(T& t)
91{
92 if constexpr ( std::is_pointer_v<T> )
93 return t;
94 else
95 return &t;
96}
97
98
99/**
100 Base serialize class.
101
102 This class also acts as the default case, which if hit will check
103 to see if this is an otherwise unhandled non-polymorphic class. If
104 it is, it will just attempt to call the serialize_order() function.
105 All other instances are partial specializations of this class and
106 do all the real serialization.
107 */
108template <typename T, typename = void>
110{
111 template <typename, typename = void>
112 struct has_serialize_order : std::false_type
113 {};
114
115 template <typename U>
116 struct has_serialize_order<U,
117 std::void_t<decltype(std::declval<U>().serialize_order(std::declval<serializer&>()))>> : std::true_type
118 {};
119
120 static_assert(std::is_class_v<std::remove_pointer_t<T>> && !std::is_polymorphic_v<std::remove_pointer_t<T>>,
121 "Trying to serialize an object that is not serializable.");
122
123 static_assert(has_serialize_order<std::remove_pointer_t<T>>::value,
124 "Serializable class does not have serialize_order() method");
125
126public:
127 void operator()(T& t, serializer& ser, ser_opt_t UNUSED(options))
128 {
129 const auto& tPtr = get_ptr(t);
130 const auto mode = ser.mode();
131 if ( mode == serializer::MAP ) {
132 if ( !tPtr ) return; // No need to map a nullptr
133 auto* map = new ObjectMapClass(tPtr, typeid(T).name());
134 ser.report_object_map(map);
135 ser.mapper().map_hierarchy_start(ser.getMapName(), map);
136 }
137 else if constexpr ( std::is_pointer_v<T> ) {
138 if ( mode == serializer::UNPACK ) {
139 t = new std::remove_pointer_t<T>();
140 ser.report_new_pointer(reinterpret_cast<uintptr_t>(t));
141 }
142 }
143
144 // class needs to define a serialize_order() method
145 tPtr->serialize_order(ser);
146
147 if ( mode == serializer::MAP ) ser.mapper().map_hierarchy_end();
148 }
149};
150
151namespace pvt {
152
153/**
154 Serialization "gateway" object called by sst_ser_object(). All
155 serializations must come thorugh these template instances in order
156 for pointer tracking to be controlled at one point. The actual
157 serialization will happen in serialize_impl classes.
158 */
159template <class T>
161{
162 template <class U>
163 friend void SST::Core::Serialization::sst_ser_object(serializer& ser, U&& obj, ser_opt_t options, const char* name);
164
165 void operator()(T& t, serializer& ser, ser_opt_t options) { return serialize_impl<T>()(t, ser, options); }
166
167 /**
168 This will track the pointer to the object if pointer tracking
169 is turned on. Otherwise, it will just serialize normally. This
170 is only useful if you have a non-pointer struct/class where
171 other pieces of code will have pointers to it (e.g. a set/map
172 contains the actual struct and other places point to the data's
173 location)
174 */
175 void serialize_and_track_pointer(T& t, serializer& ser, ser_opt_t options)
176 {
177 if ( !ser.is_pointer_tracking_enabled() ) return serialize_impl<T>()(t, ser, options);
178
179 // Get the pointer to the data that will be uesd for tracking.
180 uintptr_t ptr = reinterpret_cast<uintptr_t>(&t);
181
182 switch ( ser.mode() ) {
183 case serializer::SIZER:
184 // Check to see if the pointer has already been seen. If
185 // so, then we error since the non-pointer version of this
186 // data needs to be serialized before any of the pointers
187 // that point to it.
188 if ( ser.check_pointer_pack(ptr) ) {
189 // Error
190 }
191
192 // Always put the pointer in
193 ser.size(ptr);
194 // Count the data for the object
195 serialize_impl<T>()(t, ser, options);
196 break;
197 case serializer::PACK:
198 // Already checked serialization order during sizing, so
199 // don't need to do it here
200 ser.check_pointer_pack(ptr);
201 // Always put the pointer in
202 ser.pack(ptr);
203 // Serialize the data for the object
204 serialize_impl<T>()(t, ser, options);
205 break;
206 case serializer::UNPACK:
207 // Checked order on serialization, so don't need to do it
208 // here
209
210 // Get the stored pointer to use as a tag for future
211 // references to the data
212 uintptr_t ptr_stored;
213 ser.unpack(ptr_stored);
214
215 // Now add this to the tracking data
216 ser.report_real_pointer(ptr_stored, ptr);
217
218 serialize_impl<T>()(t, ser, options);
219 break;
220 case serializer::MAP:
221 serialize_impl<T>()(t, ser, options);
222 break;
223 }
224 }
225};
226
227template <class T>
228class serialize<T*>
229{
230 template <class U>
231 friend void SST::Core::Serialization::sst_ser_object(serializer& ser, U&& obj, ser_opt_t options, const char* name);
232 void operator()(T*& t, serializer& ser, ser_opt_t options)
233 {
234 // We are a pointer, need to see if tracking is turned on
235 if ( !ser.is_pointer_tracking_enabled() ) {
236 // Handle nullptr
237 char null_char = (nullptr == t ? 0 : 1);
238 switch ( ser.mode() ) {
239 case serializer::SIZER:
240 // We will always put in a char to tell whether or not
241 // this is nullptr.
242 ser.size(null_char);
243
244 // If this is a nullptr, then we are done
245 if ( null_char == 0 ) return;
246
247 // Not nullptr, so we need to serialize the object
248 serialize_impl<T*>()(t, ser, options);
249 break;
250 case serializer::PACK:
251 // We will always put in a char to tell whether or not
252 // this is nullptr.
253 ser.pack(null_char);
254
255 // If this is a nullptr, then we are done
256 if ( null_char == 0 ) return;
257
258 // Not nullptr, so we need to serialize the object
259 serialize_impl<T*>()(t, ser, options);
260 break;
261 case serializer::UNPACK:
262 {
263 // Get the ptr and check to see if we've already deserialized
264 ser.unpack(null_char);
265
266 // Check to see if this was a nullptr
267 if ( 0 == null_char ) {
268 t = nullptr;
269 return;
270 }
271 // Not nullptr, so deserialize
272 serialize_impl<T*>()(t, ser, options);
273 }
274 case serializer::MAP:
275 // If this version of serialize gets called in mapping
276 // mode, there is nothing to do
277 break;
278 }
279 return;
280 }
281
282 uintptr_t ptr = reinterpret_cast<uintptr_t>(t);
283 if ( nullptr == t ) ptr = 0;
284
285 switch ( ser.mode() ) {
286 case serializer::SIZER:
287 // Always put the pointer in
288 ser.size(ptr);
289
290 // If this is a nullptr, then we are done
291 if ( 0 == ptr ) return;
292
293 // If we haven't seen this yet, also need to serialize the
294 // object
295 if ( !ser.check_pointer_pack(ptr) ) {
296 serialize_impl<T*>()(t, ser, options);
297 }
298 break;
299 case serializer::PACK:
300 // Always put the pointer in
301 ser.pack(ptr);
302
303 // Nothing else to do if this is nullptr
304 if ( 0 == ptr ) return;
305
306 if ( !ser.check_pointer_pack(ptr) ) {
307 serialize_impl<T*>()(t, ser, options);
308 }
309 break;
310 case serializer::UNPACK:
311 {
312 // Get the ptr and check to see if we've already deserialized
313 uintptr_t ptr_stored;
314 ser.unpack(ptr_stored);
315
316 // Check to see if this was a nullptr
317 if ( 0 == ptr_stored ) {
318 t = nullptr;
319 return;
320 }
321
322 uintptr_t real_ptr = ser.check_pointer_unpack(ptr_stored);
323 if ( real_ptr ) {
324 // Already deserialized, so just return pointer
325 t = reinterpret_cast<T*>(real_ptr);
326 }
327 else {
328 serialize_impl<T*>()(t, ser, options);
329 ser.report_real_pointer(ptr_stored, reinterpret_cast<uintptr_t>(t));
330 }
331 break;
332 }
333 case serializer::MAP:
334 {
335 ObjectMap* map = ser.check_pointer_map(reinterpret_cast<uintptr_t>(t));
336 if ( map != nullptr ) {
337 // If we've already seen this object, just add the
338 // existing ObjectMap to the parent.
339 ser.mapper().map_existing_object(ser.getMapName(), map);
340 }
341 else {
342 serialize_impl<T*>()(t, ser, options);
343 }
344 break;
345 }
346 }
347 }
348};
349
350} // namespace pvt
351
352/**
353 Version of serialize that works for arithmetic and enum types.
354 */
355
356template <class T>
357class serialize_impl<T, std::enable_if_t<std::is_arithmetic_v<T> || std::is_enum_v<T>>>
358{
359public:
360 void operator()(T& t, serializer& ser, ser_opt_t options)
361 {
362 if ( ser.mode() == serializer::MAP ) {
363 auto* obj_map = new ObjectMapFundamental<T>(&t);
364 if ( SerOption::is_set(options, SerOption::map_read_only) ) {
365 ser.mapper().setNextObjectReadOnly();
366 }
367 ser.mapper().map_primitive(ser.getMapName(), obj_map);
368 }
369 else {
370 ser.primitive(t);
371 }
372 }
373 SST_FRIEND_SERIALIZE();
374};
375
376/**
377 Version of serialize that works for pointers to arithmetic and enum types.
378 Note that the pointer tracking happens at a higher level, and only if it is
379 turned on. If it is not turned on, then this only copies the value pointed
380 to into the buffer. If multiple objects point to the same location, they
381 will each have an independent copy after deserialization.
382 */
383template <class T>
384class serialize_impl<T*, std::enable_if_t<std::is_arithmetic_v<T> || std::is_enum_v<T>>>
385{
386 void operator()(T*& t, serializer& ser, ser_opt_t UNUSED(options))
387 {
388 switch ( ser.mode() ) {
389 case serializer::SIZER:
390 ser.primitive(*t);
391 break;
392 case serializer::PACK:
393 ser.primitive(*t);
394 break;
395 case serializer::UNPACK:
396 t = new T();
397 ser.primitive(*t);
398 break;
399 case serializer::MAP:
400 {
401 auto* obj_map = new ObjectMapFundamental<T>(t);
402 if ( SerOption::is_set(options, SerOption::map_read_only) ) {
403 ser.mapper().setNextObjectReadOnly();
404 }
405 ser.mapper().map_primitive(ser.getMapName(), obj_map);
406 break;
407 }
408 }
409 }
410 SST_FRIEND_SERIALIZE();
411};
412
413
414// All serialization must go through this function to ensure
415// everything works correctly
416//
417// A universal/forwarding reference is used for obj so that it can match rvalue wrappers like
418// SST::Core::Serialization::array(ary, size) but then it is used as an lvalue so that it
419// matches serialization functions which only take lvalue references.
420template <class T>
421void
422sst_ser_object(serializer& ser, T&& obj, ser_opt_t options, const char* name)
423{
424 // We will check for the "fast" path (i.e. event serialization for
425 // synchronizations). We can detect this by see if pointer
426 // tracking is turned off, because it is turned on for both
427 // checkpointing and mapping mode.
428 if ( !ser.is_pointer_tracking_enabled() ) {
429 // Options are wiped out since none apply in this case
430 return pvt::serialize<std::remove_reference_t<T>>()(obj, ser, 0);
431 }
432
433 // Mapping mode
434 if ( ser.mode() == serializer::MAP ) {
435 ObjectMapContext context(ser, name);
436 // Check to see if we are NOMAP
437 if ( SerOption::is_set(options, SerOption::no_map) ) return;
438
439 pvt::serialize<std::remove_reference_t<T>>()(obj, ser, options);
440 return;
441 }
442
443 if constexpr ( !std::is_pointer_v<std::remove_reference_t<T>> ) {
444 // as_ptr is only valid for non-pointers
445 if ( SerOption::is_set(options, SerOption::as_ptr) ) {
446 pvt::serialize<std::remove_reference_t<T>>().serialize_and_track_pointer(obj, ser, options);
447 }
448 else {
449 pvt::serialize<std::remove_reference_t<T>>()(obj, ser, options);
450 }
451 }
452 else {
453 // For pointer types, just call serialize
454 pvt::serialize<std::remove_reference_t<T>>()(obj, ser, options);
455 }
456}
457
458// A universal/forwarding reference is used for obj so that it can match rvalue wrappers like
459// SST::Core::Serialization::array(ary, size) but then it is used as an lvalue so that it
460// matches serialization functions which only take lvalue references.
461template <class T>
462[[deprecated(
463 "The ser& format for serialization has been deprecated and will be removed in SST 16. Please use SST_SER macro "
464 "for serializing data. The macro "
465 "supports additional options to control the details of serialization. See SerOption enum for details.")]]
466void
467operator&(serializer& ser, T&& obj)
468{
469 SST::Core::Serialization::sst_ser_object(ser, obj, SerOption::no_map);
470}
471
472template <class T>
473[[deprecated("The ser| format for serialization has been deprecated and will be removed in SST 16. Please use SST_SER "
474 "macro with the "
475 "SerOption::as_ptr flag for serializing data. The macro supports additional options to control the "
476 "details of serialization. See SerOption enum for details.")]]
477void
478operator|(serializer& ser, T&& obj)
479{
480 SST::Core::Serialization::sst_ser_object(ser, obj, SerOption::no_map | SerOption::as_ptr);
481}
482
483
484// Serialization macros for checkpoint/debug serialization
485#define SST_SER(obj, ...) \
486 SST::Core::Serialization::sst_ser_object( \
487 ser, (obj), SST::Core::Serialization::pvt::sst_ser_or_helper(__VA_ARGS__), #obj)
488
489#define SST_SER_NAME(obj, name, ...) \
490 SST::Core::Serialization::sst_ser_object( \
491 ser, (obj), SST::Core::Serialization::pvt::sst_ser_or_helper(__VA_ARGS__), name)
492
493
494// #define SST_SER_AS_PTR(obj) (ser | (obj));
495
496namespace pvt {
497template <typename... Args>
498constexpr ser_opt_t
499sst_ser_or_helper(Args... args)
500{
501 return (SerOption::none | ... | args); // Fold expression to perform logical OR
502}
503
504} // namespace pvt
505} // namespace Core::Serialization
506} // namespace SST
507
508// These includes have guards to print warnings if they are included
509// independent of this file. Set the #define that will disable the
510// warnings.
511#define SST_INCLUDING_SERIALIZE_H
512#include "sst/core/serialization/impl/serialize_adapter.h"
513#include "sst/core/serialization/impl/serialize_array.h"
514#include "sst/core/serialization/impl/serialize_atomic.h"
515#include "sst/core/serialization/impl/serialize_insertable.h"
516#include "sst/core/serialization/impl/serialize_string.h"
517#include "sst/core/serialization/impl/serialize_tuple.h"
518
519// Reenble warnings for including the above file independent of this
520// file.
521#undef SST_INCLUDING_SERIALIZE_H
522
523#endif // SST_CORE_SERIALIZATION_SERIALIZE_H
ObjectMap object for non-fundamental, non-container types.
Definition objectMap.h:609
ObjectMap context which is saved in a virtual stack when name or other information changes.
Definition serializer.h:278
ObjectMap representing fundamental types, and classes treated as fundamental types.
Definition objectMap.h:753
Base class for objects created by the serializer mapping mode used to map the variables for objects.
Definition objectMap.h:112
Serialization "gateway" object called by sst_ser_object().
Definition serialize.h:161
Base serialize class.
Definition serialize.h:110
This class is basically a wrapper for objects to declare the order in which their members should be s...
Definition serializer.h:45
Definition serialize.h:49
Options
Options use to control how serialization acts for specific elements that are serialized.
Definition serialize.h:54