-
Notifications
You must be signed in to change notification settings - Fork 96
Marker base classes
(Note: see also:
- https://github.com/mozart/mozart2/wiki/Marker-base-classes
- https://github.com/mozart/mozart2/wiki/Raw-stuff-about-the-object-model
).
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.
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.
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);
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.
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.
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.
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.
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
.
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.
Same as above, but for the sClone()
methods.
The VM also provides some non-marker base classes in datatypeshelpers-decl.hh
.
It is inherited by literals (Atom
, names, Unit
, Boolean
). A literal is basically an empty record. This base class will:
- Implement the
Literal
interface and identify itself as a literal. - Implement the
Dottable
interface, not having any valid feature. - Implement the
RecordLike
interface, identifying itself as a tuple and a record, with 0 width, and itself as the label.
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