Skip to content

Marker base classes

Benoit Daloze edited this page Apr 1, 2014 · 10 revisions

(Note: see also:

).

Transient

When a data type inherits from Transient, the corresponding type's isTransient() method will report true.

Transient is used to implement dataflow. When a node is transient, almost all methods will return OpResult::waitFor(...), to let the emulator suspend on the variable and preempt the running thread. Transient data types must also implement the DataflowVariable interface, which allows the variable to become non-transient by binding a concrete value to it.

Transient is only used by the "types" Variable, Unbound, ReadOnly (aka futures) and FailedValue. There is no reason to inherit Transient in other custom data types.

StoredAs<T>

When a data type Foo inherits from StoredAs<U>, a new specialization of the Storage template will be created:

template <>
class Storage<Foo> {
public:
  typedef U Type;
};

Without this specialization, the default Storage<Foo>::Type will be aliased to DefaultStorage<Foo>. This type tag directly affects how the MemWord of a node is used.

With the default storage, the MemWord is a pointer to the implementation, initialized as (storage.hh, T is Foo):

    T* val = new (vm) T(vm, std::forward<Args>(args)...);
    value.init<T*>(vm, val);

where the args are the arguments provided when calling Foo::build(vm, args...). Therefore, the data type must provide a constructor of the form

    Foo(VM vm, /*other arguments*/...);

Meanwhile, with a StoredAs<T>, the MemWord is used to store U directly:

    value.alloc<U>(vm);
    T::create(value.get<U>(), vm, std::forward<Args>(args)...);

and the instance of Foo is reconstructed every time from U when it is needed:

    return T(value.get<U>());

Therefore, the implementation must provide the following two methods:

    explicit Foo(U value);
    static void create(U& dest, VM vm, /*other arguments*/...);
    static void create(U& dest, VM vm, GR gr, Foo self);
 // ^ this includes the graph replicator constructor (note Foo without &)

and the constructor must be extremely cheap.

StoredWithArrayOf<T>

When a data type Foo inherits from StoredWithArrayOf<U>, a new specialization of the Storage template will be created:

template <>
class Storage<Foo> {
public:
  typedef ImplWithArray<Foo, U> Type;
};

Like StoredAs<U>, this base class modifies how the type is stored. It assumes that values of this type will always be initialized with a fixed and known array length, therefore it is unsuitable for storing a dynamic array.

When the node is initialized with length L, one continuous memory block will be allocated, large enough to contain the implementation instance, and L U's directly after the implementation.

The data type's constructor must be of the form:

    Foo(VM vm, size_t elementCount, /*other arguments*/...);
 // ^ this includes:
 // Foo(VM vm, size_t elementCount, GR gr, Foo& from);

Such a data type Foo automatically receives from its parent DataType<Foo> the three following methods, that allow to access the array:

    size_t getArraySize();
    StaticArray<U> getElementsArray();
    U& getElements(size_t index);

WithValueBehavior

When a data type inherits from WithValueBehavior, the type will behave like a value, i.e., the getStructuralBehavior() method will return sbValue.

There are 2 effects for using value behavior. Firstly, it means that the data type must implement the ValueEquatable interface, which is needed in unification.

Secondly, the generated sClone() methods of the type will be different. Without WithValueBehavior, the sClone() methods are generated as (assuming without StoredWithArrayOf<U>, and ignoring the assertions for brevity):

void TypeInfoOf<Foo>::sClone(SC sc, RichNode from, StableNode& to) const {
  to.init(sc->vm, from);
}

with it, the methods change to:

void TypeInfoOf<Foo>::sClone(SC sc, RichNode from, StableNode& to) const {
  Self fromAsSelf = from;
  to.init(sc->vm, Foo::build(sc->vm, sc, fromAsSelf));
}

This means the space cloning method will go through the graph replicator, instead of creating a new node and copy-construct the node there.

WithStructuralBehavior

When a data type inherits from WithStructuralBehavior, the type will behave like a structure, i.e. the getStructuralBehavior() method will return sbStructural.

This behavior is again used in unification and space cloning like WithValueBehavior. The data type must implement the StructuralEquatable interface, and the generated sClone() methods will go through the graph replicator.

WithVariableBehavior<n>

When a data type inherits from WithVariableBehavior<n>, the type will behave like a variable with binding priority n, i.e. the getStructuralBehavior() method will return sbVariable, and getBindingPriority() will return n.

This behavior only affects unification. Space cloning will not go though the graph replicator.

The binding priority determines the data flow direction when two variables are bound together. Implementations with variable behavior must be Transient.

The higher priority variable will be bound (assigned) to the lower priority value, i.e. High = Low. Currently the priorities are assigned as:

Priority Type
10 FailedValue
80 ReadOnly (futures)
85 ReflectiveVariable
90 Variable
100 Unbound

Like Transient, there is no reason to create a custom data type for non-VM developers beyond these types.

WithHome

When a data type inherits from WithHome, it will know the home space, and the generated sClone() will be modified to like this (ignoring the unstable node part):

void Foo::sClone(SC sc, RichNode from, StableNode& to) const {
  if (from.as<Foo>().home()->shouldBeCloned()) {
    to.init(sc->vm, Foo::build(sc->vm, sc, from.access<Foo>()));
  } else {
    to.init(sc->vm, from);
  }
}

WithHome is not just an marker class. It also defines several constructors, so the data type's constructor should also call this base class's constructor.

  WithHome(SpaceRef home): _home(home) {}
  WithHome(VM vm);
  WithHome(VM vm, GR gr, WithHome& from);

The VM constructor will initialize the home to the current space of the VM. The graph replicator constructor will copy from.home() to itself using the gr.

WithHome is used for "space-local" data types. Basically all stateful data types must be space-local. For instance, Array, Cell and Dictionary inherit from WithHome, to ensure mutation operators like arrayPut() is only done when the VM is in the array's home space. To facilitate this, the WithHome class defines a protected method bool isHomedInCurrentSpace(VM) for subclasses to check.

Variables (e.g., Variable, OptVar) must also be space-local, so that binding a variable in subspace is implemented as a speculative binding, that is not visible to the parent space.

The front-end of Space is discussed in http://www.mozart-oz.org/documentation/system/node45.html.

BasedOn<T>

When a data type inherits from BasedOn<U>, the base class of the generated type info class (i.e., TypeInfoOf<Foo>) will be U instead of TypeInfo. U itself should inherit from TypeInfo.

This is only used in the implementations of the obscure types GRedToUnstable and GRedToStable. Normally there is no point to inherit from classes other than TypeInfo.

NoAutoGCollect

When a data type inherits from NoAutoGCollect, the gCollect() methods in TypeInfoOf<Foo> will no longer be generated. Because the gCollect() method is implemented in the type info class (TypeInfoOf<Foo>), one could provide the gCollect() method by making an intermediate class FooBase between TypeInfo and TypeInfoOf<Foo>. This means one should also inherit from BasedOn<FooBase> when NoAutoGCollect is used.

NoAutoSClone

Same as above, but for the sClone() methods.


The VM also provides some non-marker base classes in datatypeshelpers-decl.hh.

LiteralHelper<Self>

It is inherited by literals (Atom, names, Unit, Boolean). A literal is basically an empty record. This base class will:

  1. Implement the Literal interface and identify itself as a literal.
  2. Implement the Dottable interface, not having any valid feature.
  3. Implement the RecordLike interface, identifying itself as a tuple and a record, with 0 width, and itself as the label.

IntegerDottableHelper<Self>

It is inherited by data types that need to implement the Dottable interface with integer features only (e.g., Tuple, Cons, Array). This base class provides the implementation Dottable interface. Subclasses must define the following two methods, visible to the IntegerDottableHelper class:

  bool isValidFeature(Self self, VM vm, nativeint feature);
  void getValueAt(Self self, VM vm, nativeint feature, UnstableNode& result);

author: https://github.com/kennytm