* Types Everything piece of data in ici is an object. Integers, strings, sets, files, functions - they're all objects as far as ici, and the user, is concerned. Objects are represented in memory by the object_t structure. This structure defines a standard header for all objects and contains a type code, reference count (which is not what you're thinking), a set of per-object flags and a pointer to a data structure describing the type-specific operations for the particular type of object. ** Object header data structure The object_t type has the following definition, struct object_t { char o_tcode; char o_flags; char o_nrefs; char o_pad; type_t *o_type; }; The fields have the following meanings, o_tcode This is a type code that specifies the type of a certain number of object types. Only a small number of types are represented via this, most set this to TC_OTHER and use the o_type pointer to determine the object type. The reason for the limited set has to do with a particular implementation decision inside the interpreter. Codes are formed to represent expressions and these codes have been limited to 16 bits. The type encoding needs to be small to provide room for the operator's code and there are two type codes required and, potentially, lots of operators. Details, details... o_flags A set of per-object flags. The lower nibble is reserved for ici's use but the upper four bits are available on a per-type basis. The interpreter defines two flags, O_MARK and O_ATOM. O_MARK is used during the mark phase of garbage collection and indicates an object that is in use, i.e., not garbage. The O_ATOM flag indicates an object is atomic and that it, (a) may not be modified, and (b) is found in the atom pool. Certain extensions, e.g., object serialisation, also define "system" object flags so as not to impinge on other types (although it is strictly not necessary to do so). o_nrefs This is a reference type field. But is not the type of reference count you're thinking of. It is NOT the primary mechanism of ici's garbage collector but assists it. Ici's mark phase of its GC is relatively efficient. Each type defines a per-type mark function that is responsible for marking all objects referenced from objects of that type, e.g., arrays store references to a number of objects, the array-specific mark function is required to mark all objects contained in the array. It does this by calling their mark functions and causing the object graph reachable from that object to be marked. To avoid infinite loops the O_MARK flag is set PRIOR to any mark functions being invoked. This allows objects to directly or indirectly contain references to themselves. So back to o_nrefs... What has this go to do with anything? Well because of the mark phase there is no need for any explicit reference count between ici objects, e.g., objects stored in an array don't get their reference count incremented as the array mark operation takes care of finding referenced objects. So the o_nrefs field. What is it? It is used to tell the GC of the number of NON-ici references to this object, e.g., a C pointer being used in some native code or some such usage. If o_nrefs is non-zero the object is not available for collection as there is some non-ici reference to it. This is often required when native code requires references to objects to persist over potential collection points (typically allocation of a new object). Additionally the o_nrefs field is used to indicate the total number of references to an atomic object. In this case it is a traditional reference count but its use is identical to the first form. Phew! o_pad Well there's an extra byte left over in the first word of the header. Find a use. Some people have... In some environments it is common to have very large programs, consider a page description language, it may process huge machine-generated programs to describe the typically very large number of objects on a page. So? So when ici is parsing it maintains lots of references to atomic objects such as integers or floating point numbers or strings. More references than can be represented in 8 bits. In this case the o_nrefs field is raised to 16 bits and the problem is pushed further away. The consequence is that you can't have single expressions that have more than 255 references to a single atomic object (in current ici). E.g., function calls may not have 255 parameters with the same, atomic, value or a fatal interpreter error is raised. o_type This is a pointer to a type_t structure (obvious so far) that defines the type-specific operations for this type. Types are described in detail below so I'll avoid too much here. It suffices to say that objects of the same type will have the same values in their o_tcode and o_type fields and that o_type fields never change, type's have a static representation by the address of their descriptor structure. ** Object defintion As stated all objects start with the object_t type. Most objects also require extra information. This is done by defining a structure to represent the type, ensuring its first field is an object_t, and following that with the type's fields. ** Type definition Types are defined by a structure, a type_t, that specifies a collection of functions to perform the type specific operations on objects of that type. Object's include a pointer to the particular type_t for their type. Additionally the type_t structure includes a string, the type's name which is also the result of applying the typeof() function to objects of that type. Objects are accessed via a set of functions. Each type must provide these functions (methods). mark Mark the object and any referenced objects during garbage collection. Returns the amount of memory in use by the object and children. free De-allocate the memory allocated to an object of that type and free any resources used. compare Compare two objects of that type for equality. copy Create a copy of an object. hash Compute a hash value for this object. fetch Fetch a sub-object from the type. assign Assign a value to a sub-object. *** Mark *** Free *** Compare *** Copy *** Hash *** Fetch *** Assign ** Pre-defined type functions Many types share the same behaviour and the ici interpreter contains functions that provide this behaviour, many of ici's own types are implemented via these functions. *** free_simple *** cmp_unique *** copy_simple *** hash_unique *** fetch_simple *** assign_simple * Atomic Types A type should be atomic when the data encapsulated by the type is constant. E.g., integers, strings, file descriptors etc... ** Rules An atomic type MUST have a compare function and MUST not use cmp_unique (if it's atomic then how can each object be unique anyway?). The hash function for an atomic type must be constant. That is the hash value must not depend on object state that may be altered. * Object allocation Object's are typically dynamically allocated and managed by the garbage collecting object allocator. Sometimes, however, objects are allocated statically or dynamically allocated but not registered with the garbage collector. These cases are rare and most objects are garbage collected. Ici's garbage collector is efficient but requires that C programmers correctly understand the construction of ici objects in order to avoid memory leaks or inadvertant re-use. The rules are straightforward and rigorously applied so once learned are easy to implement given ici's internal consistency.