-
Notifications
You must be signed in to change notification settings - Fork 96
Marker base classes
(Note: see also https://github.com/mozart/mozart2/wiki/Raw-stuff-about-the-object-model).
When the implementation inherits from Copiable
, the generated data type will report true
in the isCopiable()
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.isCopiable())
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 Copiable
, it:
- the entire size of the storage must fit into a MemWord (64 bits), and
- the storage must not require special treatment (e.g. reference counting) after copying.
See StoredAs<T>
for discussion of storage.
This pretty much limits Copiable
to primitive types (Boolean
, SmallInt
, Float
) or pointers to global memory (Atom
, BuiltinProcedure
).
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.
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.
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);
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.
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.
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.
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.
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
.
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.
Same as above, but for the sClone()
methods.
author: https://github.com/kennytm