SubComponent Migration Guide

Version Information


The new SubComponent ELI and loading mechanisms described here were introduced in SST 9.0 and the old mechanisms were deprecated. Backward compatibility is removed in SST 10.0.

Overview


Several changes were made to SST SubComponents to enable better control over them from the Python configuration, improve their documentation, and increase capability. The major changes include:

The following sections include a basic migration guide as well as a more detailed description of the changes and new features.

Basic Migration Guide


This section gives the basic (i.e., “change X to Y”) steps for making existing SubComponents compatible with SST 9.0 and later. For more detail on the whys and hows, see the detailed information later in this guide. There are two parts to the migration: updating SubComponents to use the new constructrors/ELI (steps 1-3) and updating Components/SubComponents to load SubComponents via the new loading functions (steps 4-5). In SST 9.x, both the ‘old’ and ‘new’ SubComponent APIs are available and so the updates may be done one at a time and SubComponent-by-SubComponent. Further, steps 1-3 can be taken before steps 4-5 or vice versa. In SST 10.0, the old API is no longer available and all steps must be taken before the code will compile. This guide assumes SST 9.x unless otherwise noted.

Updating SubComponents


These steps apply to all SubComponents

Step 1: Eliminate use of parent pointer

Remove any instance of a SubComponent using the ‘parent’ pointer. Remove any instance of the SubComponent constructor using its Component* parameter. Many Component API calls are now available directly to SubComponents (e.g., registerStatistic).

Step 2: Register SubComponent APIs

Add the new SST_ELI_REGISTER_SUBCOMPONENT_API macro to SubComponent base classes (e.g., the class that inherits from SubComponent). The macro takes the fully qualified, unquoted name of the API class and should be placed in a public section of the class header.

SST_ELI_REGISTER_SUBCOMPONENT_API(fully_qualified_name_of_class)

A SubComponent may be its own API (e.g., have both an SST_ELI_REGISTER_SUBCOMPONENT_API and SST_ELI_REGISTER_SUBCOMPONENT macro in it).

Add a new SubComponent constructor. The old one is still required for backward compatibility in SST 9.0 and 9.1 but may be removed in SST 10.0 and later. If you previously had:

MySubComponent(Component* comp, Params& params) : SubComponent(comp) { ... }

change it to:

MySubComponent(ComponentId_t id, Params& params) : SubComponent(id) { ... }

Example:

namespace SST {
namespace MemHierarchy {
class ReplacementPolicy : public SubComponent {
    public:
        SST_ELI_REGISTER_SUBCOMPONENT_API(SST::MemHierarchy::ReplacementPolicy)

        // This constructor can be deleted in SST 10.0 and later
        ReplacementPolicy(Component* comp, Params& params) : SubComponent(comp) { ... } 
        
        // ADD this constructor 
        ReplacementPolicy(ComponentId_t id, Params& params) : SubComponent(id) { ... }
        
        // Rest of class definition
};

} }

Step 3: Update SubComponent ELI and Constructors

Update the SST_ELI_REGISTER_SUBCOMPONENT macro in each SubComponent. The new defintion is identical to the old except that the API field (last field) must now be the fully qualified, unquoted name of the SubComponent API class. To differentiate the defintions, SST 9.0 changed the new macro name to “SST_ELI_REGISTER_SUBCOMPONENT_DERIVED”. Important: The API must exactly match one registered with the SST_ELI_REGISTER_SUBCOMPONENT_API macro or registration will fail.

Where a SubComponent had:

SST_ELI_REGISTER_SUBCOMPONENT(class_name, "library", "subcomponent_name", SST_ELI_ELEMENT_VERSION(Maj,Min0,Min1), "description", "API")

add ‘_DERIVED’ to the name and remove the quotes on ‘API’:

SST_ELI_REGISTER_SUBCOMPONENT_DERIVED(class_name, "library", "subcomponent_name", SST_ELI_ELEMENT_VERSION(Maj,Min0,Min1), "description", API)

Finally, add a new constructor with a ComponentId_t parameter instead of a Component pointer.

Example:

class LRU : public ReplacementPolicy {
    public:
        
        // For SST 9.0 and later
        SST_ELI_REGISTER_SUBCOMPONENT_DERIVED(LRU, "memHierarchy", "replacement.lru", SST_ELI_ELEMENT_VERSION(1,0,0), "Least-recently-used cache replacement policy", SST::MemHierarchy::ReplacementPolicy)

        // This one can be deleted for SST 10.0 and later
        LRU(Component* comp, Params& params) : ReplacementPolicy(comp, params) { ... }

        // ADD this new constructor
        LRU(ComponentId_t id, Params& params) : ReplacementPolicy(id, params) { ... }
};

Updating SubComponent Loading


These steps apply to any Component or SubComponent that loads a SubComponent.

Step 4: Add SubComponent Slot Documentation

SubComponentSlots were added prior to SST 9.0 but elements may not have added the documentation. Skip this step if the documentation already exists. The new SubComponent loading functions take a 'slot name' which should be documented in the loading element’s ELI, similar to documenting parameters, ports, and statistics.

Place the following macro in a public section of the Component or SubComponent header. SST_ELI_DOCUMENT_SUBCOMPONENT_SLOTS(...)

Pass a list of each SubComponent that a Component/SubComponent loads to the macro using the following triple: { “slotname”, “description”, “name of base API class” } A slot can load multiple SubComponents with the same API, so you may declare a single slot for a group of SubComponents if it makes sense to do so. The triple is the same.

Example:

class Cache : public SST::Component {
    public:
    // Other ELI

    SST_ELI_DOCUMENT_SUBCOMPONENT_SLOTS(
        {"replacement", "Replacement policy for the cache.", "SST::MemHierarchy::ReplacementPolicy"},
        {"listener", "Cache listener(s) for statistics, tracing, etc.", "SST::MemHierarchy::CacheListener"}
    )

    // Class definition
};

Step 5: New SubCompponent Loading Functions

This step describes the method for changing the SubComponent loading function to get the same behavior as was available pre-SST 9.0. However, the loading functions support a number of new features which may be useful. See the latter part of this guide for more information.

If the Component/SubComponent previously used the function:

SubCompType* subcomp = dynamic_cast<SubCompType*>(loadSubComponent("subcomponent", this, params));

replace the function with:

SubCompType* subcomp = loadAnonymousSubComponent<SubComponentType>("subcomponent", "slotname", 0, ComponentInfo::INSERT_STATS | ComponentInfo::SHARE_PORTS, params);

where ‘slotname’ should be the slot you are loading the SubComponent into and ‘0’ is the index in the slot that will be populated with this SubComponent. The ComponentInfo flags above most closely replicate pre-SST 9.0 SubComponent behavior, see the detailed description for more information.

If the Component/SubComponent previously used the function:

SubCompType* subcomp = dynamic_cast<SubCompType*>(loadNamedSubComponent("slotname"));

replace the function with:

SubCompType* subcomp = loadUserDefinedSubComponent<SubCompType>("slotname");

If the Component/SubComponent previously used syntax:

SubComponentSlotInfo* info = getSubComponentSlotInfo("slotname");
info->createAll(subCompVector);

replace this with:

SubComponentSlotInfo* info = getSubComponentSlotInfo("slotname");
info->createAll<SubCompType>(subCompVector, ComponentInfo::SHARE_STATS);

Step 6: Clean Up

If you are using SST 10.0 or later you may remove the old Component* constructors from SubComponents. If you are using SST 9.0 or 9.1, leave the old constructors as the SubComponent class still requires the old definition for backwards compatibility. If you expect all elements that load your SubComponent to use the new loading functions, you can have the old constructor fail (e.g., call output.fatal()).

Other deprecations and changes

Detailed SubComponent Guide


SubComponents are classes that are designed to be dynamically loadable into Components or other SubComponents. For example, a network endpoint might have a ‘network interface’ SubComponent, and load a different interface SubComponent depending on which network Component the endpoint is connected to. Or, a cache might have an interface to a replacement policy SubComponent, such that the user can select the replacement policy at runtime from any SST library that implements a compatible ReplacementPolicy SubComponent. In SST 9.0, SubComponents underwent some changes. The key changes are:

SubComponent Element Library Information (ELI)

Key changes: Added new API registration macro, changed interface parameter in the SubComponent registration macro

SubComponents must inherit from the core class SubComponent defined in sst/core/subcomponent.h, or must inherit from another class derived from SubComponent. Both SubComponents and the API which they implement need to be registered with the ELI database via macros in a public section of the class header.

To register an API use this macro: SST_ELI_REGISTER_SUBCOMPONENT_API(class)

Where class refers to the fully qualified, unquoted class name of the API.

To register a SubComponent use this macro:

SST_ELI_REGISTER_SUBCOMPONENT_DERIVED(class, "library", "name", version, "description", interface)

Where the parameters are:

Note that the old name of this macro was SST_ELI_REGISTER_SUBCOMPONENT. As of SST 9.0, SST_ELI_REGISTER_SUBCOMPONENT refers to the old defintion where the ‘interface’ parameter is a string literal and SST_ELI_REGISTER_SUBCOMPONENT_DERIVED refers to the new definition where the interface parameter is unquoted.

Notes:

Example of two ‘CacheListener’ SubComponents which implement the SST::MemHierarchy::CacheListener API:

namespace SST {
namespace MemHierarchy {
class CacheListener : public SubComponent {
public:
    SST_ELI_REGISTER_SUBCOMPONENT_API(SST::MemHierarchy::CacheListener)

    SST_ELI_REGISTER_SUBCOMPONENT_DERIVED(CacheListener, "memHierarchy", "emptyCacheListener", SST_ELI_ELEMENT_VERSION(1,0,0),
            "Default (empty) cache listener.", SST::MemHierarchy::CacheListener)

    // Rest of class
};
} } // End namespaces
class CacheTracer : public CacheListener {
public:
    SST_ELI_REGISTER_SUBCOMPONENT_DERIVED(CacheTracer, "memHierarchy", "tracer", SST_ELI_ELEMENT_VERSION(1,0,0),
            "Generates a trace of cache accesses", SST::MemHierarchy::CacheListener)

    // Rest of class
};

SubComponent Constructor

Key Changes: Eliminated parent pointer, changed SubComponent constructor

In SST 9.0, the SubComponent constructor changed to take a ComponentId_t instead of a Component pointer. In SST 9.0 and 9.1, the old constructor is still required to be present for backward compatibility, but using it will cause warnings. As of SST 10.0 the old constructor is no longer needed or used. The new constructor for SubComponents is:

MySubComponent(ComponentId_t id, Params& params) : SubComponent(id) { ... }

The ComponentId_t will be a unique identifier for the SubComponent instance and it can be masked to get the identifier of the (ultimate) Component in which a SubComponent is loaded. That is, if a Component A loads SubComponent B which in turn loads SubComponent C, then A, B, and C each receive unique identifiers in their constructors. Further, B and C can mask their identifier to get A’s identifier. However, C cannot determine B’s identifier from its own. SubComponents migrating to SST 9.0 from older releases may need to eliminate their use of the Component pointer that was previously passed into their constructor. Additionally, the ‘parent’ pointer that was previously a protected member of the SubComponent base class is a private member in SST 10.0. Therefore SubComponents need to eliminate their use of that pointer as well. Partially the change eliminates inconsistency about ‘parent’ for SubComponents loaded into Components compared to those loaded into SubComponents, and partially the change was made to enforce stricter (safer) boundaries between parent and child.

Example:

class RequestGenerator : public SubComponent {
public:
    SST_ELI_REGISTER_SUBCOMPONENT_API(SST::Miranda::RequestGenerator)

    RequestGenerator(ComponentId_t id, Params& params) : SubComponent(id) { }
};

Variable arguments

SubComponent constructors support additional arguments. The arguments must be registered as part of the SubComponent API and the constructor of a derived SubComponent must match that of its API.

The API registration syntax is:

SST_ELI_REGISTER_SUBCOMPONENT_API(class, type0, ...)

where type0, type1, etc. is the type of each additional parameter.

In the SubComponent constructor, the additional parameters should be placed after the Params parameter:

MySubComponent(ComponentId_t id, Params& params, type0 arg0, ...);

Example of a constructor that takes two additional parameters:

class ReplacementPolicy : public SubComponent {
public:
    SST_ELI_REGISTER_SUBCOMPONENT_API(SST::MemHierarchy::ReplacementPolicy, uint64_t, uint64_t);

    ReplacementPolicy(ComponentId_t id, Params& params, uint64_t cachelines, uint64_t associativity) { }
};

class LRU : public ReplacementPolicy {
public:
    SST_ELI_REGISTER_SUBCOMPONENT_DERVIED(LRU, "memHierarchy", "replacement.lru", SST_ELI_ELEMENT_VERSION(1,0,0),
        "Least-recently-used replacement policy", SST::MemHierarchy::ReplacementPolicy)

    LRU(ComponentId_t id, Params& params, uint64_t cachelines, uint64_t associativity) : ReplacementPolicy(id, params, cachelines, associativity) { ... }
};

Loading SubComponents

SubComponents can be loaded from two sources: the SST Python input configuration (User-Defined SubComponent, previously called ‘named subcomponent’) or from a Component or SubComponent (Anonymous SubComponent, previously called ‘subcomponent’). Changes were made in SST 9.0 to improve user-defined SubComponents, make user-defined and anonymous SubComponents more consistent, and fix issues with nesting SubComponents.

Key changes: All SubComponents load into ‘slots’, new SubComponent loading functions make statistic and port sharing explicit

User-Defined versus Anonymous SubComponents

User-Defined SubComponents are loaded from the Python configuration. From a configuration standpoint, they have many similarities to Components: they receive their parameters from the Python, they can have their own ports, etc. In contrast, an Anonymous SubComponent is one that is not loaded from the Python configuration, but by a Component/SubComponent (“Element”). The user cannot directly control which SubComponent is loaded or its parameters. The loading Element supplies the parameters, the SubComponent cannot have its own port, etc. The same SubComponent can be loaded either way; this is a decision made by the loading Element (SubComponent/Component). If needed however, a SubComponent can query how it was loaded. For example, a SubComponent might use its own port if it was loaded as a User-Defined SubComponent, but its parent’s port if it was loaded as an Anonymous SubComponent. For flexibility, we recommend that SubComponents be loaded as user-defined whenever reasonable. A common pattern is to check whether a user-defined subcomponent exists and if not, load a default SubComponent anonymously.

An important difference between user-defined and anonymous SubComponents is their ability to have their own ports. User-defined SubComponents exist in the Python, and therefore the SST config graph, so they may own their own ports. Thus user-defined SubComponents can be linked to other elements through ports they have defined themselves. Anonymous SubComponents are not defined until later and therefore are limited to using a parent’s port.

SubComponent Slots

Prior to SST 9.0, only user-defined (named) SubComponents need to be loaded into a slot, as of 9.0, all SubComponents are loaded into a slot. Slots are defined in the Component or SubComponent that loads a SubComponent using the macro:

SST_ELI_DOCUMENT_SUBCOMPONENT_SLOTS(...)

The macro takes a list of slots which are each defined statically via the triple:

{ “slotname”, “description”, “name of base API class” }

where the parameters are:

Multiple SubComponents may be loaded into a single slot. For example, a router may have a ‘PortController’ slot which can be populated with as many port controllers as the router needs. There is no difference in documenting a slot that is intended to take one SubComponent versus those intended to take multiple, and the same slot can be used for loading anonymous and user-defined SubComponents. Building off the ReplacementPolicy and CacheListener examples in previous sections, a Cache could declare slots as follows:

class Cache : public Component {
    public:
        // Other ELI registration/documentation

        SST_ELI_DOCUMENT_SUBCOMPONENT_SLOTS(
            {"replacement", "The replacement policy used by the cache", "SST::MemHierarchy::ReplacementPolicy"},
            {"listener", "Cache listener(s) for statistics, profiling, tracing, etc.", "SST::MemHierarchy::CacheListener"}
        )
};

Functions for Loading SubComponents

Information about the set of SubComponents defined in each slot is contained in a SubComponentSlotInfo object. This object contains a vector of the SubComponents defined on a slot. In the special case of loading a single user-defined SubComponent into a slot, SST provides wrapper functions that avoid the need to directly query the SubComponentSlotInfo object. Thus there are three methods for loading SubComponents: querying the SubComponentSlotInfo object to load user-define SubComponents, using the special case load functions to load a single user-defined Subcomponent, and loading an anonymous SubComponent.

Method 1: Use the SubComponentSlotInfo object to load one or more user-defined SubComponents in a slot

The following code demonstrates how to obtain the SubComponentSlotInfo object for a slot named ‘slotname’, and iterate through its SubComponent vector, loading any SubComponents that were defined in the Python.

SubComponentSlotInfo* info = getSubComponentSlotInfo(slotname);
if (info) { // at least one SubComponent was loaded
    for (int i = 0; i <= info->getMaxPopulatedSlotNumber(); i++) {
        if (info->isPopulated(i)) { // check for holes in the vector
            subcomp[i] = info->create<SubCompType>(i, SHARE_FLAGS);
        }
    }
}

The function getSubComponentSlotInfo("slotname") returns the SubComponentSlotInfo if any SubComponent was defined on the slot, or nullptr otherwise. Several functions can be used to query the SubComponentSlotInfo vector.

int getMaxPopulatedSlotNumber() Returns the highest populated index in the SubComponent vector

bool isAllPopulated() Returns whether all indices from 0 to the highest populated index are populated.

bool isPopulated(int slot_num) Returns whether index ‘slot_num’ is populated.

Method 2: Load a single user-defined SubComponent into a slot

There are some special case functions that facilitate loading a single user-defined SubComponent into a slot. If multiple SubComponents were defined on a slot, these functions capture the SubComponent loaded in slot number 0 only.

SubCompType* subcomp = loadUserDefinedSubComponent<SubCompType>("slotname");

SubCompType* subcomp = loadUserDefinedSubComponent<SubCompType>("slotname", SHARE_FLAGS);

SubCompType* subcomp = loadUserDefinedSubComponent<SubCompType>("slotname", SHARE_FLAGS, constructorArg0, constructorArg1, ...);

These functions call the SubComponent’s constructor and return a pointer to the SubComponent if found or nullptr if the Python configuration did not provide a SubComponent for the requested “slotname”. If the configuration defined multiple SubComponents on the slot, these functions will return only the SubComponent that was defined on the slot’s index 0. SHARE_FLAGS may be set to ComponentInfo::SHARE_NONE or one or more of ComponentInfo::SHARE_STATS, ComponentInfo::SHARE_PORTS, and ComponentInfo::INSERT_STATS. The flags are defined in the next section, and default to SHARE_NONE. Any extra constructor arguments are passed after the SHARE_FLAGS. The parameters passed to the SubComponent’s constructor will be those added to the SubComponent in the Python configuration (i.e., they are not visible to the loading Element).

Option 3: Load one or more anonymous SubComponents into a slot

A SubComponent that was not declared in the Python input is called an anonymous SubComponent. Elements parameterize and load these SubComponents themselves. The loading functions for such SubComponents are:

SubCompType* subcomp = loadAnonymousSubComponent<SubCompType>("subcomponent", "slotname", slotindex, SHARE_FLAGS, params);

SubCompType* subcomp = loadAnonymousSubComponent<SubCompType>("subcomponent", "slotname", slotindex, SHARE_FLAGS, params, constructorArg0, constructorArg1, ...);

where the arguments are:

These functions call the SubComponent’s constructor and return the SubComponent.

ComponentInfo Sharing Flags

SubComponents often share ports and/or statistics with their parents. For example, a SubComponent may manage a port that is documented (via SST_ELI_DOCUMENT_PORTS) in a parent Component. Or, a SubComponent may declare its own statistics but because it has no name (was loaded anonymously), there is no object to attach those statistics to and so they are attributed to the SubComponent’s parent instead. The new loading functions explicitly declare what kind of sharing is allowed, and this controls how name clashes between parent and child are resolved (e.g., both parent and child document a statistic with the same name).

The flags are:

By default, user SubComponents use SHARE_NONE; anonymous SubComponents do not have a default. To approximate the behavior of pre-SST9.0 SubComponents, use ComponentInfo::SHARE_PORTS | ComponentInfo::INSERT_STATS.

Defining SubComponents in the Python Configuration

To define which SubComponent to load into a paritcular slot, call `setSubComponent(“slotname”, “lib.subcomponent”) on the Component or SubComponent that owns “slotname”. Parameters can then be declared directly on the SubComponent, and if the SubComponent has ports, those ports can be connected to links. For example, in the Miranda library, CPUs load a SubComponent into their ‘generator’ slot to defines the memory access pattern to execute. The Python input might look like this:

cpu = sst.Component("cpu", "miranda.BaseCPU")
gen = cpu.setSubComponent("generator", "miranda.SingleStreamGenerator")
gen.addParams({
    "verbose" : 0,
    "startat" : 3,
    "count" : 50000,
    "max_address" : 512000
})

To load multiple SubComponents into a slot, setSubComponent can take an additional slotnum parameter such as:

cache = sst.Component("cache", "memHierarchy.Cache")

listener0 = cache.setSubComponent("listener", "mylib.mylistener0", 0)
listener1 = cache.setSubComponent("listener", "mylib.mylistener1", 1)
listener2 = cache.setSubComponent("listener", "mylib.mylistener1", 2)
listener3 = cache.setSubComponent("listener", "mylib.mylistener1", 3)