Skip to content

Marker base classes

Sébastien Doeraene edited this page Jun 11, 2012 · 10 revisions

(Note: see also https://github.com/mozart/mozart2/wiki/Raw-stuff-about-the-object-model).

Copyable

When the implementation inherits from Copyable, the generated data type will report true in the isCopyable() method.

The actual semantic is that, when a stable or unstable node is initialized or copied, the underlying node

void StableNode::init(VM vm, StableNode& from) {
  if (from.isCopyable())
    node = from.node;
  else
    node.make<Reference>(vm, &from);
}

Recall that a node is just a POD consisting of the type, and a union (MemWord) at 64-bit size (on x86-64, x86 and ARM) to store the implementation. Therefore, in order to be Copyable, it:

  1. the entire size of the storage must fit into a MemWord (64 bits), and
  2. the storage must not require special treatment (e.g. reference counting) after copying.

See StoredAs<T> for discussion of storage.

This pretty much limits Copyable to primitive types (Boolean, SmallInt, Float) or pointers to global memory (Atom, BuiltinProcedure).

Transient

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

Transient is used to implement data-flow. 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 implementation 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 (futures) and FailedValue. There is no reason to inherit Transient in other custom datatypes.

StoredAs<T>

When an implementation inherits from StoredAs<T>, a new specialization of the Storage template will be created:

template <>
class Storage<Foo> {
public:
  typedef T 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):

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

where the args are the arguments provided when calling .make<Foo>(vm, args...). Therefore, the implementation must provide a constructor of the form

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

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

    value.init(vm, Implementation<Foo>::build(vm, std::forward<Args>(args)...));

and the implementation is reconstructed every time from T when it is needed:

    return Implementation<Foo>(value.get<T>());

Therefore, the implementation must provide the following two methods:

    static T build(VM vm, /*other arguments*/...);
 // ^ this includes the graph replicator constructor:
 // static T build(VM vm, GR gr, Self self);
    Implementation(T value);

and the constructor must be extremely cheap.

StoredWithArrayOf<T>

When an implementation inherits from StoredWithArrayOf<T>, a new specialization of the Storage template will be created:

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

Like StoredAs<T>, this base class modifies how the implementation is stored. It assumes the implementation 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 T's directly after the implementation.

The implementation's constructor must be of the form:

    Implementation(VM vm, size_t elementCount, StaticArray<T> uninitializedArray,
                   /*other arguments*/...);
 // ^ this includes:
 // Implementation(VM vm, size_t elementCount, StaticArray<T> uninitializedArray, GR gr, Self from);

WithValueBehavior

When an implementation 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 the implementation must apply the ValueEquatable interface, which is needed in unification.

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

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

with it, the methods changed to:

void Foo::sClone(SC sc, RichNode from, StableNode& to) const {
  Self fromAsSelf = from;
  to.make<Foo>(sc->vm, sc, fromAsSelf);
}
void Foo::sClone(SC sc, RichNode from, UnstableNode& to) const {
  Self fromAsSelf = from;
  to.make<Foo>(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 an implementation inherits from WithValueBehavior, 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 implementation must apply the StructuralEquatable interface, and the generated sClone() methods will go through the graph replicator.

WithVariableBehavior<n>

When an implementation 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 lower priority variable will be bound (assigned) to the higher priority value, i.e. High = Low. Currently the priorities are assigned as:

Priority Type
10 FailedValue
80 ReadOnly (futures)
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 an implementation 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 {
  Self fromAsSelf = from;
  if (fromAsSelf->home()->shouldBeCloned()) {
    to.make<Foo>(sc->vm, sc, fromAsSelf);
  } else {
    to.init(sc->vm, from);
  }
}

This base class is introduced in a more recent commit f404da2, which is not documented in the official wiki.

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

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

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

WithHome is can be used as "space-local" data type. For instance, Array, Cell and Dictionary inherit 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.

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

BasedOn<T>

When an implementation inherits from BasedOn<T>, the generated data type's base class will not be Type, but T.

This is only used in the implementations of the obscure types GCedToUnstable, GCedToStable and ReifiedThread. Normally there is no point to inherit from classes other than Type.

NoAutoGCollect

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

NoAutoSClone

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

author: https://github.com/kennytm