Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/sst/core/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ nobase_dist_sst_HEADERS = \
serialization/impl/serialize_insertable.h \
serialization/impl/serialize_tuple.h \
serialization/impl/serialize_utility.h \
serialization/impl/serialize_variant.h \
serialization/impl/mapper.h \
serialization/impl/packer.h \
serialization/impl/sizer.h \
Expand Down
114 changes: 114 additions & 0 deletions src/sst/core/serialization/impl/serialize_variant.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2009-2025 NTESS. Under the terms
// of Contract DE-NA0003525 with NTESS, the U.S.
// Government retains certain rights in this software.
//
// Copyright (c) 2009-2025, NTESS
// All rights reserved.
//
// This file is part of the SST software package. For license
// information, see the LICENSE file in the top level directory of the
// distribution.

#ifndef SST_CORE_SERIALIZATION_IMPL_SERIALIZE_VARIANT_H
#define SST_CORE_SERIALIZATION_IMPL_SERIALIZE_VARIANT_H

#ifndef SST_INCLUDING_SERIALIZE_H
#warning \
"The header file sst/core/serialization/impl/serialize_variant.h should not be directly included as it is not part of the stable public API. The file is included in sst/core/serialization/serialize.h"
#endif

#include "sst/core/serialization/serializer.h"

#include <utility>
#include <variant>

namespace SST::Core::Serialization {

// Serialize std::variant
template <typename... Types>
class serialize_impl<std::variant<Types...>>
{
void operator()(std::variant<Types...>& obj, serializer& ser, ser_opt_t UNUSED(options))
{
size_t index = std::variant_npos;
switch ( ser.mode() ) {
case serializer::SIZER:
index = obj.index();
ser.size(index);
break;

case serializer::PACK:
index = obj.index();
ser.pack(index);
break;

case serializer::UNPACK:
ser.unpack(index);

// Set the active variant to index. The variant must be default-constructible.
// We cannot portably restore valueless_by_exception but we do nothing in that case.
if ( index != std::variant_npos ) set_index<std::index_sequence_for<Types...>>[index](obj);
break;

case serializer::MAP:
{
// TODO -- how to handle mapping of std::variant ?
return;
}
}

// std::visit instantiates the generic lambda [&](auto& x) { SST_SER(x); } for each variant
// Types... in obj, and calls the one corresponding to obj's currently held variant

// Serialize the currently active variant if it's not valueless_by_exception
if ( index != std::variant_npos ) std::visit([&](auto& x) { SST_SER(x); }, obj);
}

// Table of functions to set the active variant
// Primary definition is std::nullptr_t to cause a compilation error if no template specialization matches
template <typename>
static constexpr std::nullptr_t set_index {};

SST_FRIEND_SERIALIZE();
};

// clang-format off
//
// Table of functions to set the active variant
//
// This defines a partial specialization of the serialize_impl<std::variant<Types...>>::set_index static member variable
// template, which takes a single "type" template argument. This specialization matches std::index_sequence<INDEX...>
// representing a sequence of subscripts 0, 1, 2, ..., sizeof...(Types)-1 generated by std::index_sequence_for<Types...>
//
// This set_index partial specialization is a constexpr array of function pointers initialized by a list of lambda
// expressions, each of which takes a std::variant<Types...>& obj argument and calls obj.emplace<INDEX>() which changes
// the active variant of obj to the Types... entry corresponding to INDEX. The emplace<INDEX>() method is called with no
// constructor arguments, which means that the new active variant will be default-constructed. This changing of the
// active variant happens before the new variant is deserialized in-place.
//
// Although std::visit can invoke a function with the currently active std::variant, it relies on knowing the currently
// active variant as returned by index(). To CHANGE the currently active variant requires an assignment to the
// std::variant object or a call to the emplace() method.
//
// Changing the currently active variant to INDEX by calling emplace<INDEX>() requires that INDEX be a compile-time
// constant, and the effects of constructing the newly active variant are different for each of the Types... indexed by
// INDEX, and so a separate function table entry is needed for each INDEX so that the correct one can be dispatched at
// runtime.
//
// set_index[] = { obj.emplace<0>(), obj.emplace<1>(), obj.emplace<2>(), ..., obj.emplace<sizeof...(Types)-1>() }
//
// This variable template partial specialization is defined outside of serialize_impl to work around GCC Bug 71954.

template <typename... Types>
template <size_t... INDEX>
constexpr void (*
serialize_impl<std::variant<Types...>>::set_index<std::index_sequence<INDEX...>>[]
)( std::variant<Types...>& )
= {
[](std::variant<Types...>& obj) { obj.template emplace<INDEX>(); }...
};
// clang-format on

} // namespace SST::Core::Serialization

#endif // SST_CORE_SERIALIZATION_IMPL_SERIALIZE_VARIANT_H
1 change: 1 addition & 0 deletions src/sst/core/serialization/serialize.h
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ sst_ser_or_helper(Args... args)
#include "sst/core/serialization/impl/serialize_insertable.h"
#include "sst/core/serialization/impl/serialize_string.h"
#include "sst/core/serialization/impl/serialize_tuple.h"
#include "sst/core/serialization/impl/serialize_variant.h"

// Reenble warnings for including the above file independent of this
// file.
Expand Down
70 changes: 70 additions & 0 deletions src/sst/core/testElements/coreTest_Serialization.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@
#include "sst/core/serialization/impl/serialize_utility.h"
#include "sst/core/warnmacros.h"

#include <array>
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <set>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>
#include <vector>

namespace SST::CoreTestSerialization {
Expand Down Expand Up @@ -223,6 +226,39 @@ checkContainerSerializeDeserialize(T*& data)
return true;
};

// std::variant
auto checkVariant = [](auto& data, auto& result) {
using T = std::decay_t<decltype(data)>;
using R = std::decay_t<decltype(result)>;

if constexpr ( !std::is_same_v<T, R> ) {
// ignore the cases where T != R at compile-time, since they are excluded by index() runtime equality test
return false;
}
else if constexpr ( std::is_same_v<T, std::string> || std::is_arithmetic_v<T> || std::is_enum_v<T> ) {
return data == result;
}
else if constexpr ( std::is_same_v<T, std::vector<int>> ) {
if ( data.size() != result.size() ) return false;
for ( size_t i = 0; i < data.size(); ++i )
if ( data[i] != result[i] ) return false;
return true;
}
else {
static_assert(sizeof(T) == 0, "Unsupported type in checkVariant()");
}
};

template <typename... Types>
bool
checkVariantSerializeDeserialize(std::variant<Types...>& data)
{
std::variant<Types...> result;
serializeDeserialize(data, result);
if ( result.index() != data.index() ) return false;
return std::visit(checkVariant, data, result);
};

// Arrays

template <typename>
Expand Down Expand Up @@ -846,6 +882,40 @@ coreTestSerialization::coreTestSerialization(ComponentId_t id, Params& params) :
if ( !passed ) out.output("ERROR: unordered_multiset<int32_t>* did not serialize/deserialize properly\n");
delete umultiset_in;
}
else if ( test == "variant" ) {
std::variant<std::vector<int>, double, std::string> var;
for ( int ntry = 0; ntry < 5; ++ntry ) {
bool passed = false;

// Generate random variant each try
switch ( rng->generateNextUInt32() % std::variant_size_v<decltype(var)> ) {
case 0:
{
var = std::vector<int>(rng->generateNextUInt32() % 1000);
for ( auto& e : std::get<0>(var) )
e = rng->generateNextInt32();
passed = checkVariantSerializeDeserialize(var);
break;
}
case 1:
{
var = double(rng->generateNextInt32());
passed = checkVariantSerializeDeserialize(var);
break;
}
case 2:
{
std::string str;
size_t len = rng->generateNextUInt32() % 100;
for ( size_t i = 0; i < len; ++i )
str += "0123456789"[rng->generateNextUInt32() % 10];
var = str;
passed = checkVariantSerializeDeserialize(var);
}
}
if ( !passed ) out.output("ERROR: std::variant<...> did not serialize/deserialize properly\n");
}
}
else if ( test == "map_to_vector" ) {

// Containers to other containers
Expand Down
3 changes: 3 additions & 0 deletions tests/testsuite_default_Serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def test_Serialization_atomic(self):
def test_Serialization_complexcontainer(self):
self.serialization_test_template("complexcontainer")

def test_Serialization_variant(self):
self.serialization_test_template("variant")

#####
def serialization_test_template(self, testtype, default_reffile = True):

Expand Down
Loading