<< Prev | - Up - |
In this chapter, we implement class
Celloid
s: an extension class for objects that behave essentially like cells; they have content which can be accessed (read) and assigned (written). The new challenge here is twofold: (1) during garbage collection the content of a class
Celloid
must also be copied, (2) we must ensure that only a local class
Celloid
can be mutated (for a non-local one, we should signal an error).
A class
Celloid
is situated. What does situated mean? Simply that the object resides in one specific constraint store, aka a computation space. If class
Celloid
C
resides in space S1, and S2 is a subspace of S1 (i. e. is subordinated to S1), it is meaningful for a thread in S2 to access C
since any constraint (therefore value) from S1 are visible in S2, but it is generally not meaningful for a thread in S2 to assign C
since constraints (therefore values) specific to S2 are not visible from S1. Our implementation will enforce the appropriate restrictions.
Again, we subclass class
OZ_Extension
.
#include "mozart.h"
class Celloid : public OZ_Extension {
public:
OZ_Term content;
Celloid(OZ_Term t):content(t){}
static int id;
virtual int getIdV() { return id; }
virtual OZ_Term typeV() { return OZ_atom("celloid"); }
virtual OZ_Extension* gCollectV();
virtual OZ_Extension* sCloneV();
virtual void gCollectRecurseV();
virtual void sCloneRecurseV();
virtual OZ_Term printV(int depth = 10);
};
The celloid_new
builtin takes one input argument t
, creates a new celloid whose content is initialized to t
, boxes it and returns the result.
OZ_BI_define(celloid_new,1,1)
{
OZ_declareTerm(0,t);
OZ_RETURN(OZ_extension(new Celloid(t)));
}
OZ_BI_end
The definitions here are similar to the ones presented earlier for the class
Counter
class.
int Celloid::id;
inline OZ_Boolean OZ_isCelloid(OZ_Term t)
{
t = OZ_deref(t);
return OZ_isExtension(t) &&
OZ_getExtension(t)->getIdV()==Celloid::id;
}
OZ_BI_define(celloid_is,1,1)
{
OZ_declareDetTerm(0,t);
OZ_RETURN_BOOL(OZ_isCelloid(t));
}
OZ_BI_end
Again this is similar to the class
Counter
class: we define an unboxing function and a convenience macro.
inline Celloid* OZ_CelloidToC(OZ_Term t)
{
return (Celloid*) OZ_getExtension(OZ_deref(t));
}
#define OZ_declareCelloid(ARG,VAR) \
OZ_declareType(ARG,VAR,Celloid*,"celloid",OZ_isCelloid,OZ_CelloidToC)
First, we provide an access builtin that retrieves the content of the celloid.
OZ_BI_define(celloid_access,1,1)
{
OZ_declareCelloid(0,c);
OZ_RETURN(c->content);
}
OZ_BI_end
Second, we provide an assign builtin that sets the content of the celloid. This operation should only be allowed for a thread executing in the home space of the celloid. For a thread executing in a subordinated space, an exception will be raised.
OZ_BI_define(celloid_assign,2,0)
{
OZ_declareCelloid(0,c);
OZ_declareTerm(1,t);
if (c->isLocal()) { c->content=t; return PROCEED; }
else return OZ_raiseErrorC("celloid",3,OZ_atom("nonLocal"),
OZ_in(0),OZ_in(1));
}
OZ_BI_end
virtual member function isLocal()
indicates whether the current space is the home space of the celloid. If yes, we set the content to the given argument; if no, we raise an error. OZ_in(
n)
refers to the nth input argument of the builtin.
We provide here only minimal printing support.
OZ_Term Celloid::printV(int depth = 10)
{
return OZ_atom("<celloid>");
}
The first part of garbage collection is as before: we create a new instance of class
Celloid
initialized with the current content of the celloid that is being copied by gc.
OZ_Extension* Celloid::gCollectV() { return new Celloid(content); }
The second part involves recursively copying the content of the celloid. This is implemented in a different virtual function:
void Celloid::gCollectRecurseV() { OZ_gCollect(&content); }
The procedure OZ_gCollect(OZ_Term*)
performs the gc copy and update of its argument.
You may wonder: why not perform the recursive copy of the content in gCollectV()
itself. Under no circumstances should you do this! It would break essential invariants in the garbage collector. GC copy must proceed in these 2 phases:
gCollectV()
creates a new instance (on the to heap) and initializes it with the current contents of the object being gced.
gCollectRecurseV()
is at some subsequent point invoked on the new instance and should perform the gc copy and update of its contents.
Cloning is used to produce a copy of a computation space. It has the same structure, and the underlying implementation in fact shares most of the code with, garbage collection.
OZ_Extension* Celloid::sCloneV() { return new Celloid(content); }
void Celloid::sCloneRecurseV() { OZ_sClone(&content); }
Again, we proceed as before:
OZ_C_proc_interface * oz_init_module(void)
{
static OZ_C_proc_interface table[] = {
{"new",1,1,celloid_new},
{"is",1,1,celloid_is},
{"access",1,1,celloid_access},
{"assign",2,0,celloid_assign},
{0,0,0,0}
};
Celloid::id = OZ_getUniqueId();
return table;
}
Assuming the code above is put in file celloid.cc
, we first compile and then create a DLL as follows
oztool c++ -c celloid.cc
oztool ld celloid.o -o celloid.so-`oztool platform`
Here, we hardly need an Oz wrapper module. Unlike for counter objects, we don't need to register celloid for finalization: there are no resources off the heap. However, we can provide a nice error print formatter for the case when an access is attempted from without the celloid's home space.
functor
import
Celloid(new:New is:Is access:Access assign:Assign)
at 'celloid.so{native}'
Error(registerFormatter)
export
New Is Access Assign
define
fun {CelloidFormatter E}
T = 'Celloid Error'
in
case E of celloid(nonLocal C V) then
error(kind: T
msg: 'Attempted assign on non local celloid'
items: [hint(l:'Operation' m:'Celloid.assign')
hint(l:'Celloid' m:oz(C))
hint(l:'Value' m:oz(V))])
else
error(kind: T
items: [line(oz(E))])
end
end
{Error.registerFormatter celloid CelloidFormatter}
end
<< Prev | - Up - |