SST  15.1.0
StructuralSimulationToolkit
sharedMap.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_SHARED_SHAREDMAP_H
13 #define SST_CORE_SHARED_SHAREDMAP_H
14 
15 #include "sst/core/shared/sharedObject.h"
16 #include "sst/core/sst_types.h"
17 
18 #include <cstddef>
19 #include <map>
20 #include <mutex>
21 #include <string>
22 #include <utility>
23 
24 namespace SST::Shared {
25 
26 /**
27  SharedMap class. The class is templated to allow for Map of any
28  non-pointer type as value. The type must be serializable.
29  */
30 template <typename keyT, typename valT>
31 class SharedMap : public SharedObject
32 {
33  static_assert(!std::is_pointer_v<valT>, "Cannot use a pointer type as value with SharedMap");
34 
35  // Forward declaration. Defined below
36  class Data;
37 
38 public:
39  SharedMap() :
40  SharedObject(),
41  published(false),
42  data(nullptr)
43  {}
44 
45  ~SharedMap()
46  {
47  // data does not need to be deleted since the
48  // SharedObjectManager owns the pointer
49  }
50 
51  /**
52  Initialize the SharedMap.
53 
54  @param obj_name Name of the object. This name is how the
55  object is uniquely identified across ranks.
56 
57  @param verify_mode Specifies how multiply written data should
58  be verified. Since the underlying map knows if the data has
59  already been written, FE_VERIFY and INIT_VERIFY simply use this
60  built-in mechanism to know when an item has previously been
61  written. When these modes are enabled, multiply written data
62  must match what was written before. When NO_VERIFY is passed,
63  no verification will occur. This is mostly useful when you can
64  guarantee that multiple elements won't write the same value and
65  you want to do in-place modifications as you initialize.
66  VERIFY_UNINITIALIZED is a reserved value and should not be
67  passed.
68 
69  @return returns the number of instances that have intialized
70  themselve before this instance on this MPI rank.
71  */
72  int initialize(const std::string& obj_name, verify_type v_type = FE_VERIFY)
73  {
74  if ( data ) {
75  Private::getSimulationOutput().fatal(
76  CALL_INFO, 1, "ERROR: called initialize() of SharedMap %s more than once\n", obj_name.c_str());
77  }
78 
79  data = manager.getSharedObjectData<Data>(obj_name);
80  int ret = incShareCount(data);
81  data->setVerify(v_type);
82  return ret;
83  }
84 
85  /*** Typedefs and functions to mimic parts of the vector API ***/
86  using const_iterator = typename std::map<keyT, valT>::const_iterator;
87  using const_reverse_iterator = typename std::map<keyT, valT>::const_reverse_iterator;
88 
89  /**
90  Get the size of the map.
91 
92  @return size of the map
93  */
94  inline size_t size() const { return data->getSize(); }
95 
96  /**
97  Tests if the map is empty.
98 
99  @return true if map is empty, false otherwise
100  */
101  inline bool empty() const { return data->map.empty(); }
102 
103  /**
104  Counts elements with a specific key. Becuase this is not a
105  multimap, it will either return 1 or 0.
106 
107  @return Count of elements with specified key
108  */
109  size_t count(const keyT& k) const { return data->map.count(k); }
110 
111  /**
112  Searches the container for an element with a key equivalent to
113  k and returns an iterator to it if found, otherwise it returns
114  an iterator to SharedMap::end().
115 
116  @param key key to search for
117  */
118  const_iterator find(const keyT& key) const { return data->map.find(key); }
119 
120  /**
121  Get const_iterator to beginning of underlying map
122  */
123  const_iterator begin() const { return data->map.cbegin(); }
124 
125  /**
126  Get const_iterator to end of underlying map
127  */
128  const_iterator end() const { return data->map.cend(); }
129 
130  /**
131  Get const_reverse_iterator to beginning of underlying map
132  */
133  const_reverse_iterator rbegin() const { return data->map.crbegin(); }
134 
135  /**
136  Get const_reverse_iterator to end of underlying map
137  */
138  const_reverse_iterator rend() const { return data->map.crend(); }
139 
140  /**
141  Returns an iterator pointing to the first element in the
142  container whose key is not considered to go before k (i.e.,
143  either it is equivalent or goes after).
144 
145  @param key key to compare to
146  */
147  inline const_iterator lower_bound(const keyT& key) const { return data->map.lower_bound(key); }
148 
149  /**
150  Returns an iterator pointing to the first element in the
151  container whose key is considered to go after k.
152 
153  @param key key to compare to
154  */
155  inline const_iterator upper_bound(const keyT& key) const { return data->map.lower_bound(key); }
156 
157  /**
158  Indicate that the calling element has written all the data it
159  plans to write. Writing to the map through this instance
160  after publish() is called will create an error.
161  */
162  void publish()
163  {
164  if ( published ) return;
165  published = true;
166  incPublishCount(data);
167  }
168 
169  /**
170  Check whether all instances of this SharedMap have called
171  publish(). NOTE: Is is possible that this could return true
172  one round, but false the next if a new instance of the
173  SharedMap was initialized but not published after the last
174  call.
175  */
176  bool isFullyPublished() { return data->isFullyPublished(); }
177 
178  /**
179  Write data to the map. This function is thread-safe, as a
180  mutex is used to ensure only one write at a time.
181 
182  @param key key of the write
183 
184  @param value value to be written
185  */
186  inline void write(const keyT& key, const valT& value)
187  {
188  if ( published ) {
189  Private::getSimulationOutput().fatal(
190  CALL_INFO, 1, "ERROR: write to SharedMap %s after publish() was called\n", data->getName().c_str());
191  }
192  return data->write(key, value);
193  }
194 
195  /**
196  Read data from the map. This returns a const reference, so is
197  read only. If the key is not in the map, an out_of_range
198  exception will be thrown.
199 
200  NOTE: This function does not use a mutex, so it is possible to
201  get invalid results if another thread is simulateously writing
202  to the map. However, after the init() phase of simulation is
203  complete (in setup() and beyond), this is always a safe
204  operation. If reading during init() and you can't guarantee
205  that all elements have already written all elements to the
206  SharedMap, use mutex_read() to guarantee thread safety.
207 
208  @param key key to read
209 
210  @return const reference to data referenced by key
211 
212  @exception std::out_of_range key is not found in map
213  */
214  inline const valT& operator[](const keyT& key) const { return data->read(key); }
215 
216  /**
217  Read data from the map. This returns a const reference, so
218  is read only. This version of read is always thread safe (@see
219  operator[]). If the key is not in the map, an out_of_range
220  exception will be thrown.
221 
222  @param key key to read
223 
224  @return const reference to data at index
225 
226  @exception std::out_of_range key is not found in map
227  */
228  inline const valT& mutex_read(const keyT& key) const { return data->mutex_read(key); }
229 
230  void serialize_order(SST::Core::Serialization::serializer& ser) override
231  {
232  SST::Shared::SharedObject::serialize_order(ser);
233  SST_SER(published);
234  bool initialized = (data != nullptr);
235  SST_SER(initialized);
236 
237  if ( !initialized ) return;
238 
239  switch ( ser.mode() ) {
240  case SST::Core::Serialization::serializer::SIZER:
241  case SST::Core::Serialization::serializer::PACK:
242  {
243  std::string name = data->getName();
244  SST_SER(name);
245  break;
246  }
247  case SST::Core::Serialization::serializer::UNPACK:
248  {
249  std::string name;
250  SST_SER(name);
251  data = manager.getSharedObjectData<Data>(name);
252  break;
253  }
254  case SST::Core::Serialization::serializer::MAP:
255  // Add your code here
256  break;
257  };
258  }
259  ImplementSerializable(SST::Shared::SharedMap<keyT, valT>)
260 
261 private:
262  bool published;
263  Data* data = nullptr;
264 
265  class Data : public SharedObjectData
266  {
267 
268  // Forward declaration. Defined below
269  class ChangeSet;
270 
271  public:
272  std::map<keyT, valT> map;
273  ChangeSet* change_set;
274  verify_type verify;
275 
276  Data() :
278  change_set(nullptr),
279  verify(VERIFY_UNINITIALIZED)
280  {}
281  explicit Data(const std::string& name) :
282  SharedObjectData(name),
283  change_set(nullptr),
284  verify(VERIFY_UNINITIALIZED)
285  {
286  if ( Private::getNumRanks().rank > 1 ) {
287  change_set = new ChangeSet(name);
288  }
289  }
290 
291  ~Data()
292  {
293  if ( change_set ) delete change_set;
294  }
295 
296  void setVerify(verify_type v_type)
297  {
298  if ( v_type != verify && verify != VERIFY_UNINITIALIZED ) {
299  Private::getSimulationOutput().fatal(
300  CALL_INFO, 1, "ERROR: Two different verify_types specified for SharedMap %s\n", name.c_str());
301  }
302  verify = v_type;
303  if ( change_set ) change_set->setVerify(v_type);
304  }
305 
306  size_t getSize() const { return map.size(); }
307 
308  void update_write(const keyT& key, const valT& value)
309  {
310  // Don't need to mutex because this is only ever called
311  // from one thread at a time, with barrier before and
312  // after, or from write(), which does mutex.
313  auto success = map.insert(std::make_pair(key, value));
314  if ( !success.second ) {
315  // Wrote to a key that already existed
316  if ( verify != NO_VERIFY && value != success.first->second ) {
317  Private::getSimulationOutput().fatal(
318  CALL_INFO, 1, "ERROR: wrote two different values to same key in SharedMap %s\n", name.c_str());
319  }
320  }
321  }
322 
323  void write(const keyT& key, const valT& value)
324  {
325  std::lock_guard<std::mutex> lock(mtx);
326  check_lock_for_write("SharedMap");
327  update_write(key, value);
328  if ( change_set ) change_set->addChange(key, value);
329  }
330 
331  // Inline the read since it may be called often during run().
332  // This read is not protected from data races in the case
333  // where the map may be simulataeously written by another
334  // thread. If there is a danger of simultaneous access
335  // during init, use the mutex_read function until after the
336  // init phase.
337  inline const valT& read(const keyT& key) { return map.at(key); }
338 
339  // Mutexed read for use if you are resizing the array as you go
340  inline const valT& mutex_read(const keyT& key)
341  {
342  std::lock_guard<std::mutex> lock(mtx);
343  return map.at(key);
344  }
345 
346  // Functions inherited from SharedObjectData
347  virtual SharedObjectChangeSet* getChangeSet() override { return change_set; }
348  virtual void resetChangeSet() override { change_set->clear(); }
349 
350  void serialize_order(SST::Core::Serialization::serializer& ser) override
351  {
352  SharedObjectData::serialize_order(ser);
353  SST_SER(map);
354  }
355 
356  ImplementSerializable(SST::Shared::SharedMap<keyT, valT>::Data);
357 
358  private:
359  class ChangeSet : public SharedObjectChangeSet
360  {
361 
362  std::map<keyT, valT> changes;
363  verify_type verify;
364 
365  void serialize_order(SST::Core::Serialization::serializer& ser) override
366  {
367  SharedObjectChangeSet::serialize_order(ser);
368  SST_SER(changes);
369  SST_SER(verify);
370  }
371 
373 
374  public:
375  // For serialization
376  ChangeSet() :
377  SharedObjectChangeSet(),
378  verify(VERIFY_UNINITIALIZED)
379  {}
380  explicit ChangeSet(const std::string& name) :
381  SharedObjectChangeSet(name),
382  verify(VERIFY_UNINITIALIZED)
383  {}
384 
385  void addChange(const keyT& key, const valT& value) { changes[key] = value; }
386 
387  void setVerify(verify_type v_type) { verify = v_type; }
388 
389  void applyChanges(SharedObjectDataManager* manager) override
390  {
391  auto data = manager->getSharedObjectData<Data>(getName());
392  data->setVerify(verify);
393  for ( auto x : changes ) {
394  data->update_write(x.first, x.second);
395  }
396  }
397 
398  void clear() override { changes.clear(); }
399  };
400  };
401 };
402 
403 } // namespace SST::Shared
404 
405 #endif // SST_CORE_SHARED_SHAREDMAP_H
const_iterator lower_bound(const keyT &key) const
Returns an iterator pointing to the first element in the container whose key is not considered to go ...
Definition: sharedMap.h:147
const valT & mutex_read(const keyT &key) const
Read data from the map.
Definition: sharedMap.h:228
This class is basically a wrapper for objects to declare the order in which their members should be s...
Definition: serializer.h:42
const_iterator find(const keyT &key) const
Searches the container for an element with a key equivalent to k and returns an iterator to it if fou...
Definition: sharedMap.h:118
Definition: sharedObject.h:276
const_iterator begin() const
Get const_iterator to beginning of underlying map.
Definition: sharedMap.h:123
void lock()
Called by the core when writing to shared regions is no longer allowed.
Definition: sharedObject.h:192
bool isFullyPublished()
Checks to see if all instances of this SharedObject have called publish().
Definition: sharedObject.h:115
bool isFullyPublished()
Check whether all instances of this SharedMap have called publish().
Definition: sharedMap.h:176
const_iterator end() const
Get const_iterator to end of underlying map.
Definition: sharedMap.h:128
int initialize(const std::string &obj_name, verify_type v_type=FE_VERIFY)
Initialize the SharedMap.
Definition: sharedMap.h:72
SharedMap class.
Definition: sharedMap.h:31
verify_type
Enum of verify types.
Definition: sharedObject.h:283
virtual void resetChangeSet() override
Resets the changeset for this data on this rank.
Definition: sharedMap.h:348
Definition: sharedMap.h:265
size_t count(const keyT &k) const
Counts elements with a specific key.
Definition: sharedMap.h:109
size_t size() const
Get the size of the map.
Definition: sharedMap.h:94
const_reverse_iterator rend() const
Get const_reverse_iterator to end of underlying map.
Definition: sharedMap.h:138
Definition: sharedArray.h:24
void write(const keyT &key, const valT &value)
Write data to the map.
Definition: sharedMap.h:186
const valT & operator[](const keyT &key) const
Read data from the map.
Definition: sharedMap.h:214
const_reverse_iterator rbegin() const
Get const_reverse_iterator to beginning of underlying map.
Definition: sharedMap.h:133
const_iterator upper_bound(const keyT &key) const
Returns an iterator pointing to the first element in the container whose key is considered to go afte...
Definition: sharedMap.h:155
virtual SharedObjectChangeSet * getChangeSet() override
Gets the changeset for this data on this rank.
Definition: sharedMap.h:347
void publish()
Indicate that the calling element has written all the data it plans to write.
Definition: sharedMap.h:162
bool empty() const
Tests if the map is empty.
Definition: sharedMap.h:101
const std::string & getName()
Get the name of the SharedObject for this data.
Definition: sharedObject.h:105
This is the base class for holding data on changes made to the shared data on each rank...
Definition: sharedObject.h:48
Base class for holding SharedObject data.
Definition: sharedObject.h:93