cfad47cfa3/tads3/vmobj.cpp
Commiter: Nikos Chantziaras
Author: Nikos Chantziaras
Revision: cfad47cfa3
File Size: 94.8 KB
(June 01, 2009 20:54 UTC) Almost 3 years ago
Initial commit.
Showing without highlighting since it looks like a big file and may slow your browser - show with highlighting
Show/hide line numbers#ifdef RCSID
static char RCSid[] =
"$Header: d:/cvsroot/tads/tads3/VMOBJ.CPP,v 1.4 1999/07/11 00:46:58 MJRoberts Exp $";
#endif
/*
* Copyright (c) 1998, 2002 Michael J. Roberts. All Rights Reserved.
*
* Please see the accompanying license file, LICENSE.TXT, for information
* on using and copying this software.
*/
/*
Name
vmobj.cpp - VM object manager
Function
Notes
Modified
10/28/98 MJRoberts - Creation
*/
#include <stdlib.h>
#include <memory.h>
#include <assert.h>
#include "t3std.h"
#include "vmtype.h"
#include "vmobj.h"
#include "vmstack.h"
#include "vmundo.h"
#include "vmrun.h"
#include "vmfile.h"
#include "vmmeta.h"
#include "vmlst.h"
#include "vmstr.h"
#include "vmintcls.h"
#include "vmpool.h"
#include "vmfunc.h"
#include "vmpredef.h"
#include "vmhash.h"
#include "vmtobj.h"
/* ------------------------------------------------------------------------ */
/*
* Base fixed-size object entry implementation
*/
/*
* Metaclass registration object for the root object class. Note that a
* root object can never be instantiated; this entry is purely for the
* use of the type system.
*/
static CVmMetaclassRoot metaclass_reg_obj;
CVmMetaclass *CVmObject::metaclass_reg_ = &metaclass_reg_obj;
/* function table */
int (CVmObject::
*CVmObject::func_table_[])(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *argc,
vm_prop_id_t prop, vm_obj_id_t *source_obj) =
{
&CVmObject::getp_undef,
&CVmObject::getp_of_kind,
&CVmObject::getp_sclist,
&CVmObject::getp_propdef,
&CVmObject::getp_proptype,
&CVmObject::getp_get_prop_list,
&CVmObject::getp_get_prop_params,
&CVmObject::getp_is_class,
&CVmObject::getp_propinh,
&CVmObject::getp_is_transient
};
/*
* Allocate space for an object from a page table, given the object ID.
* The caller must allocate the object ID prior to new'ing the object;
* operator new will store the memory for the new object in the object
* slot the caller allocated.
*/
void *CVmObject::operator new(size_t siz, VMG_ vm_obj_id_t obj_id)
{
/*
* The size must be the size of an object entry. This size never
* changes, even for subclasses of the object type, since all
* variable-size data must be stored in the variable-size portion of
* the object. Here we are only concerned with allocating the
* fixed-size object descriptor.
*/
assert(siz == sizeof(CVmObject));
/* return the memory contained in the object entry */
return G_obj_table->get_obj(obj_id);
}
/*
* Determine if this object is an instance of the given object. By
* default, we will simply check to see if the given object is the
* IntrinsicClass instance that represents our metaclass or one of its
* superclasses.
*/
int CVmObject::is_instance_of(VMG_ vm_obj_id_t obj)
{
vm_meta_entry_t *entry;
/*
* we can only be an instance of the object if the object is an
* IntrinsicClass instance, since by default we have only intrinsic
* classes among our superclasses
*/
if (!CVmObjClass::is_intcls_obj(vmg_ obj))
return FALSE;
/*
* look up my metaclass in the metaclass dependency table, and
* determine if my dependency table entry's record of the
* IntrinsicClass object for the metaclass matches the given object
*/
entry = (G_meta_table
->get_entry_from_reg(get_metaclass_reg()->get_reg_idx()));
/*
* if we have an entry, ask our superclass object if it is an
* instance of the given object; otherwise, we must not be an
* instance
*/
if (entry != 0)
{
/* if this is our direct superclass, we're an instance */
if (entry->class_obj_ == obj)
return TRUE;
/* if there's no intrinsic class object, return false */
if (entry->class_obj_ == VM_INVALID_OBJ)
return FALSE;
/* ask the superclass if it inherits from the given object */
return vm_objp(vmg_ entry->class_obj_)->is_instance_of(vmg_ obj);
}
else
{
/*
* no metaclass table entry - we can't really make any
* determination, so indicate that we're not an instance
*/
return FALSE;
}
}
/*
* Get the nth superclass. By default, an object's superclass is
* represented by the intrinsic class object for the metaclass.
*/
vm_obj_id_t CVmObject::get_superclass(VMG_ vm_obj_id_t /*self*/,
int sc_idx) const
{
vm_meta_entry_t *entry;
/* we only have one superclass */
if (sc_idx != 0)
return VM_INVALID_OBJ;
/* look up the metaclass entry */
entry = (G_meta_table
->get_entry_from_reg(get_metaclass_reg()->get_reg_idx()));
/* return the IntrinsicClass object that represents this metaclass */
return (entry != 0 ? entry->class_obj_ : VM_INVALID_OBJ);
}
/*
* Get a property
*/
int CVmObject::get_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval,
vm_obj_id_t self, vm_obj_id_t *source_obj, uint *argc)
{
uint func_idx;
/* translate the property index to an index into our function table */
func_idx = G_meta_table
->prop_to_vector_idx(metaclass_reg_->get_reg_idx(), prop);
/* if we find it, the source object is the 'Object' intrinsic class */
*source_obj = metaclass_reg_->get_class_obj(vmg0_);
/* call the appropriate function */
return (this->*func_table_[func_idx])(vmg_ self, retval, argc,
prop, source_obj);
}
/*
* Inherit a property
*/
int CVmObject::inh_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval,
vm_obj_id_t self, vm_obj_id_t orig_target_obj,
vm_obj_id_t defining_obj, vm_obj_id_t *source_obj,
uint *argc)
{
uint func_idx;
/*
* We're inheriting. This is never called from native code, as native
* code does its inheriting directly through C++ calls to base class
* native code; hence, we can only be called from a byte-code modifier
* object.
*
* First, try looking for a native implementation. We can reach this
* point if a byte-code object overrides an intrinsic method, then
* inherits from the byte-code override.
*/
func_idx = G_meta_table
->prop_to_vector_idx(metaclass_reg_->get_reg_idx(), prop);
if (func_idx != 0)
{
/* the source object is the 'Object' intrinsic class */
*source_obj = metaclass_reg_->get_class_obj(vmg0_);
/* call the native implementation */
return (this->*func_table_[func_idx])(vmg_ self, retval, argc,
prop, source_obj);
}
/*
* We didn't find it among the intrinsic methods, so look at the
* modifier objects.
*/
return find_modifier_prop(vmg_ prop, retval, self, orig_target_obj,
defining_obj, source_obj, argc);
}
/*
* Get a property that isn't defined in our property table
*/
int CVmObject::getp_undef(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *argc,
vm_prop_id_t prop, vm_obj_id_t *source_obj)
{
/*
* We didn't find a native implementation of the method, but there's
* still one more place to look: the "modifier" object for our class
* tree. Modifier objects are byte-code objects that can provide
* implementations of methods that add to intrinsic classes (modifiers
* can't override intrinsic methods, but they can add new methods).
*
* Since we're looking for a property on an initial get-property call
* (not an inheritance call), we don't yet have a defining object to
* find and skip in the inheritance tree, so use VM_INVALID_OBJ as the
* defining object. In addition, we're directly calling the method, so
* the target object is the same as the 'self' object.
*/
return find_modifier_prop(vmg_ prop, retval, self, self,
VM_INVALID_OBJ, source_obj, argc);
}
/*
* Find a modifier property.
*/
int CVmObject::find_modifier_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval,
vm_obj_id_t self,
vm_obj_id_t orig_target_obj,
vm_obj_id_t defining_obj,
vm_obj_id_t *source_obj,
uint *argc)
{
vm_meta_entry_t *entry;
int found_def_obj;
/* we haven't yet found the defining superclass */
found_def_obj = FALSE;
/* get my metaclass from the dependency table */
entry = (G_meta_table
->get_entry_from_reg(get_metaclass_reg()->get_reg_idx()));
/*
* if there's an associated intrinsic class object, check to see if
* it provides a user modifier object for this intrinsic class
*/
while (entry != 0 && entry->class_obj_ != VM_INVALID_OBJ)
{
vm_obj_id_t mod_obj;
/* ask the intrinsic class object for the user modifier object */
mod_obj = ((CVmObjClass *)vm_objp(vmg_ entry->class_obj_))
->get_mod_obj();
/*
* If we have a defining object, we must ignore objects in the
* superclass tree until we find the defining object. Therefore,
* scan up the superclass tree for mod_obj and see if we can find
* the defining object; when we find it, we can start looking at
* objects for real at the defining object's superclass.
*
* (Superclasses in modifier objects aren't real superclasses,
* because modifier objects are classless. Instead, the superclass
* list simply implements the 'modify' chain.)
*/
if (mod_obj != VM_INVALID_OBJ
&& defining_obj != VM_INVALID_OBJ && !found_def_obj)
{
/*
* if the defining object isn't among the byte-code
* superclasses of the modifier object, we must skip this
* entire intrinsic class and move to the intrinsic superclass
*/
if (mod_obj == defining_obj
|| vm_objp(vmg_ mod_obj)->is_instance_of(vmg_ defining_obj))
{
/*
* the defining object is among my modifier family - this
* means that this is the intrinsic superclass where we
* found the modifier method
*/
found_def_obj = TRUE;
}
else
{
/*
* The current defining object is not part of the modifier
* chain for this intrinsic class, so we've already skipped
* past this point in the intrinsic superclass tree on past
* inheritances. Simply move to the next intrinsic class
* and look at its modifier.
*/
goto next_intrinsic_sc;
}
}
/*
* If there's a modifier object, send the property request to it.
* We are effectively delegating the method call to the modifier
* object, so we must use the "inherited property" call, not the
* plain get_prop() call: 'self' is the original self, but the
* target object is the intrinsic class modifier object.
*/
if (mod_obj != VM_INVALID_OBJ
&& vm_objp(vmg_ mod_obj)->inh_prop(
vmg_ prop, retval, self, mod_obj, defining_obj,
source_obj, argc))
return TRUE;
/* we didn't find it in this object, so look at its super-metaclass */
next_intrinsic_sc:
if (entry->meta_->get_supermeta_reg() != 0)
{
/* get the super-metaclass ID */
entry = (G_meta_table
->get_entry_from_reg(entry->meta_
->get_supermeta_reg()
->get_reg_idx()));
/*
* if we've already found the previous defining intrinsic
* class, we can forget about the previous defining modifier
* object now: since we're moving to a new intrinsic
* superclass, we will have no superclass relation to the
* previous defining object in the new modifier family, so we
* can simply use the next definition of the property we find
*/
if (found_def_obj)
defining_obj = VM_INVALID_OBJ;
}
else
{
/* no super-metaclass - give up */
break;
}
}
/* we don't have a modifier object, so the property is undefined */
return FALSE;
}
/*
* property evaluator - ofKind
*/
int CVmObject::getp_of_kind(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *argc,
vm_prop_id_t, vm_obj_id_t *)
{
vm_val_t sc;
static CVmNativeCodeDesc desc(1);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* pop the superclass, and make sure it's an object */
G_stk->pop(&sc);
if (sc.typ != VM_OBJ)
err_throw(VMERR_OBJ_VAL_REQD);
/* check for an identity test */
if (sc.val.obj == self)
{
/* x.ofKind(x) == true */
retval->set_true();
}
else
{
/* check to see if the object is a superclass of ours */
retval->set_logical(is_instance_of(vmg_ sc.val.obj));
}
/* handled */
return TRUE;
}
/*
* property evaluator - isClass
*/
int CVmObject::getp_is_class(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *argc,
vm_prop_id_t, vm_obj_id_t *)
{
static CVmNativeCodeDesc desc(0);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* indicate whether or not we're a class object */
retval->set_logical(is_class_object(vmg_ self));
/* handled */
return TRUE;
}
/*
* property evaluator - isTransient
*/
int CVmObject::getp_is_transient(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *argc,
vm_prop_id_t, vm_obj_id_t *)
{
static CVmNativeCodeDesc desc(0);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* indicate whether or not we're transient */
retval->set_logical(G_obj_table->is_obj_transient(self));
/* handled */
return TRUE;
}
/*
* property evaluator - getSuperclassList
*/
int CVmObject::getp_sclist(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *argc,
vm_prop_id_t, vm_obj_id_t *)
{
size_t sc_cnt;
vm_obj_id_t lst_obj;
CVmObjList *lstp;
size_t i;
static CVmNativeCodeDesc desc(0);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* push a self-reference for GC protection */
G_interpreter->push_obj(vmg_ self);
/* get the number of superclasses */
sc_cnt = get_superclass_count(vmg_ self);
/* allocate a list for the results */
lst_obj = CVmObjList::create(vmg_ FALSE, sc_cnt);
lstp = (CVmObjList *)vm_objp(vmg_ lst_obj);
/* build the superclass list */
for (i = 0 ; i < sc_cnt ; ++i)
{
vm_val_t ele_val;
/* get this superclass */
ele_val.set_obj(get_superclass(vmg_ self, i));
/* set the list element */
lstp->cons_set_element(i, &ele_val);
}
/* discard our GC protection */
G_stk->discard();
/* return the list */
retval->set_obj(lst_obj);
/* handled */
return TRUE;
}
/*
* property evaluator - propDefined
*/
int CVmObject::getp_propdef(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *in_argc,
vm_prop_id_t, vm_obj_id_t *)
{
uint argc = (in_argc != 0 ? *in_argc : 0);
vm_val_t val;
int flags;
int found;
vm_prop_id_t prop;
vm_obj_id_t source_obj;
static CVmNativeCodeDesc desc(1, 1);
/* check arguments */
if (get_prop_check_argc(retval, in_argc, &desc))
return TRUE;
/* pop the property address to test */
G_interpreter->pop_prop(vmg_ &val);
prop = val.val.prop;
/* if we have the flag argument, get it; otherwise, use the default */
if (argc >= 2)
{
/* get the flag value */
G_interpreter->pop_int(vmg_ &val);
flags = (int)val.val.intval;
}
else
{
/* use the default flags */
flags = VMOBJ_PROPDEF_ANY;
}
/* presume we won't find a valid source object */
source_obj = VM_INVALID_OBJ;
/* look up the property */
found = get_prop(vmg_ prop, &val, self, &source_obj, 0);
/*
* If we found a result, check to see if it's an intrinsic class
* modifier object. If it is, replace it with its intrinsic class:
* modifier objects are invisible through the reflection mechanism, and
* appear to be the actual intrinsic classes they modify.
*/
if (found && CVmObjIntClsMod::is_intcls_mod_obj(vmg_ source_obj))
source_obj = find_intcls_for_mod(vmg_ self, source_obj);
/* check the flags */
switch(flags)
{
case VMOBJ_PROPDEF_ANY:
/* return true if the property is defined */
retval->set_logical(found);
break;
case VMOBJ_PROPDEF_DIRECTLY:
/* return true if the property is defined directly */
retval->set_logical(found && source_obj == self);
break;
case VMOBJ_PROPDEF_INHERITS:
/* return true if the property is inherited only */
retval->set_logical(found && source_obj != self);
break;
case VMOBJ_PROPDEF_GET_CLASS:
/* return the defining class, or nil if it's not defined */
if (found)
{
/*
* If we got a valid source object, return it. If we didn't
* get a valid source object, but we found the property,
* return 'self' as the result; this isn't exactly right, but
* this should only be possible when the source object is an
* intrinsic class for which no intrinsic class object is
* defined, in which case the best we can do is provide 'self'
* as the answer.
*/
retval->set_obj(source_obj != VM_INVALID_OBJ ? source_obj : self);
}
else
{
/* didn't find it - the return value is nil */
retval->set_nil();
}
break;
default:
/* other flags are invalid */
err_throw(VMERR_BAD_VAL_BIF);
break;
}
/* handled */
return TRUE;
}
/*
* Find the intrinsic class which the given modifier object modifies. This
* can only be used with a modifier that modifies my intrinsic class or one
* of its intrinsic superclasses.
*/
vm_obj_id_t CVmObject::find_intcls_for_mod(VMG_ vm_obj_id_t self,
vm_obj_id_t mod_obj)
{
vm_meta_entry_t *entry;
/* get my metaclass from the dependency table */
entry = (G_meta_table
->get_entry_from_reg(get_metaclass_reg()->get_reg_idx()));
/*
* if there's an intrinsic class object for the metaclass, ask it to do
* the work
*/
if (entry != 0 && entry->class_obj_ != VM_INVALID_OBJ)
return (((CVmObjClass *)vm_objp(vmg_ entry->class_obj_))
->find_mod_src_obj(vmg_ entry->class_obj_, mod_obj));
/* there's no intrinsic metaclass, so we can't find what we need */
return VM_INVALID_OBJ;
}
/*
* property evaluator - propInherited
*/
int CVmObject::getp_propinh(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *in_argc,
vm_prop_id_t, vm_obj_id_t *)
{
uint argc = (in_argc != 0 ? *in_argc : 0);
vm_val_t val;
int flags;
int found;
vm_prop_id_t prop;
vm_obj_id_t source_obj;
vm_obj_id_t orig_target_obj;
vm_obj_id_t defining_obj;
static CVmNativeCodeDesc desc(3, 1);
/* check arguments */
if (get_prop_check_argc(retval, in_argc, &desc))
return TRUE;
/* pop the property address to test */
G_interpreter->pop_prop(vmg_ &val);
prop = val.val.prop;
/* get the original target object */
G_interpreter->pop_obj(vmg_ &val);
orig_target_obj = val.val.obj;
/* get the defining object */
G_interpreter->pop_obj(vmg_ &val);
defining_obj = val.val.obj;
/* if we have the flag argument, get it; otherwise, use the default */
if (argc >= 4)
{
/* get the flag value */
G_interpreter->pop_int(vmg_ &val);
flags = (int)val.val.intval;
}
else
{
/* use the default flags */
flags = VMOBJ_PROPDEF_ANY;
}
/* presume we won't find a valid source object */
source_obj = VM_INVALID_OBJ;
/* look up the property */
found = inh_prop(vmg_ prop, &val, self, orig_target_obj, defining_obj,
&source_obj, 0);
/* check the flags */
switch(flags)
{
case VMOBJ_PROPDEF_ANY:
/* return true if the property is defined */
retval->set_logical(found);
break;
case VMOBJ_PROPDEF_GET_CLASS:
/* return the defining class, or nil if it's not defined */
if (found)
{
/* return the source object, or 'self' if we didn't find one */
retval->set_obj(source_obj != VM_INVALID_OBJ ? source_obj : self);
}
else
{
/* didn't find it - the return value is nil */
retval->set_nil();
}
break;
default:
/* other flags are invalid */
err_throw(VMERR_BAD_VAL_BIF);
break;
}
/* handled */
return TRUE;
}
/*
* property evaluator - propType
*/
int CVmObject::getp_proptype(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *argc,
vm_prop_id_t, vm_obj_id_t *)
{
vm_val_t val;
vm_prop_id_t prop;
vm_obj_id_t source_obj;
static CVmNativeCodeDesc desc(1);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* pop the property address to test */
G_interpreter->pop_prop(vmg_ &val);
prop = val.val.prop;
/* get the property value */
if (!get_prop(vmg_ prop, &val, self, &source_obj, 0))
{
/* the property isn't defined on the object - the result is nil */
retval->set_nil();
}
else
{
/* set the return value to the property's datatype value */
retval->set_datatype(vmg_ &val);
}
/* handled */
return TRUE;
}
/*
* property evaluator - getPropList
*/
int CVmObject::getp_get_prop_list(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *argc,
vm_prop_id_t, vm_obj_id_t *)
{
static CVmNativeCodeDesc desc(0);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* push a self-reference for gc protection */
G_stk->push()->set_obj(self);
/* build my property list */
build_prop_list(vmg_ self, retval);
/* discard the gc protection */
G_stk->discard();
/* handled */
return TRUE;
}
/*
* Build a list of properties directly defined by this instance
*/
void CVmObject::build_prop_list(VMG_ vm_obj_id_t /*self*/, vm_val_t *retval)
{
/*
* by default, object instances have no directly defined properties,
* so create and return an empty list
*/
retval->set_obj(CVmObjList::create(vmg_ FALSE, (size_t)0));
}
/*
* property evaluator - getPropParams
*/
int CVmObject::getp_get_prop_params(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *argc,
vm_prop_id_t, vm_obj_id_t *)
{
vm_val_t val;
vm_prop_id_t prop;
vm_obj_id_t source_obj;
int min_args, opt_args, varargs;
static CVmNativeCodeDesc desc(1);
CVmObjList *lst;
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* pop the property address to test */
G_interpreter->pop_prop(vmg_ &val);
prop = val.val.prop;
/* push a self-reference while we're working */
G_stk->push()->set_obj(self);
/* get the property value */
if (!get_prop(vmg_ prop, &val, self, &source_obj, 0))
{
/* no such method - no arguments */
min_args = opt_args = 0;
varargs = FALSE;
}
else if (val.typ == VM_CODEOFS)
{
CVmFuncPtr func;
/* get the function header for the code offset */
func.set((const uchar *)G_code_pool->get_ptr(val.val.ofs));
/*
* Get the argument information from the function header. Note
* that p-code methods cannot have optional arguments.
*/
min_args = func.get_min_argc();
opt_args = 0;
varargs = func.is_varargs();
/*
* re-evaluate the currently executing method's entry pointer, to
* ensure that the current code page is the most recently used
* page, in case we're on a swapping system (note that we don't
* attempt to retranslate the pointer, since we assume that we
* didn't actually swap it out just now - we assume that we have
* enough cache space to keep at least two pages in memory at
* once, and since the currently-executing code page should have
* been the most recently used page before our translation just
* above, it should not have been swapped out)
*/
G_interpreter->touch_entry_ptr_page(vmg0_);
}
else if (val.typ == VM_NATIVE_CODE)
{
/* get the arguments from the native code descriptor */
min_args = val.val.native_desc->min_argc_;
opt_args = val.val.native_desc->opt_argc_;
varargs = val.val.native_desc->varargs_;
}
else
{
/* it's not a function - no arguments */
min_args = opt_args = 0;
varargs = FALSE;
}
/*
* Allocate our return list. We need three elements: [minArgs,
* optionalArgs, isVarargs].
*/
retval->set_obj(CVmObjList::create(vmg_ FALSE, 3));
/* get the list object, properly cast */
lst = (CVmObjList *)vm_objp(vmg_ retval->val.obj);
/* set the minimum argument count */
val.set_int(min_args);
lst->cons_set_element(0, &val);
/* set the optional argument count */
val.set_int(opt_args);
lst->cons_set_element(1, &val);
/* set the varargs flag */
val.set_logical(varargs);
lst->cons_set_element(2, &val);
/* discard our self-reference */
G_stk->discard();
/* handled */
return TRUE;
}
/*
* Call a static property
*/
int CVmObject::call_stat_prop(VMG_ vm_val_t *retval, const uchar **pc_ptr,
uint *argc, vm_prop_id_t prop)
{
/* not handled */
return FALSE;
}
/* ------------------------------------------------------------------------ */
/*
* CVmMetaclass implementation
*/
/*
* Get a metaclass's super-metaclass. We'll look up our super-metaclass
* in the metaclass registration table and return the IntrinsicClass
* object we find referenced there.
*/
vm_obj_id_t CVmMetaclass::get_supermeta(VMG_ int idx) const
{
vm_meta_entry_t *entry;
/* we only have one supermetaclass */
if (idx != 0)
return VM_INVALID_OBJ;
/* if I don't have a supermetaclass at all, return nil */
if (get_supermeta_reg() == 0)
return VM_INVALID_OBJ;
/* look up my supermetaclass entry */
entry = (G_meta_table->get_entry_from_reg(
get_supermeta_reg()->get_reg_idx()));
/* return the IntrinsicClass object that represents this metaclass */
return (entry != 0 ? entry->class_obj_ : VM_INVALID_OBJ);
}
/*
* Determine if I'm an instance of the given object. Most metaclasses
* inherit directly from CVmObject, so we'll return true only if the
* object is the CVmObject IntrinsicClass object
*/
int CVmMetaclass::is_meta_instance_of(VMG_ vm_obj_id_t obj) const
{
vm_meta_entry_t *entry;
CVmMetaclass *sc;
/* iterate over my supermetaclasses */
for (sc = get_supermeta_reg() ; sc != 0 ; sc = sc->get_supermeta_reg())
{
/* look up the metaclass entry for this supermetaclass */
entry = (G_meta_table->get_entry_from_reg(sc->get_reg_idx()));
/*
* if the object matches the current superclass's IntrinsicClass
* object, we're a subclass of that object; otherwise we're not
*/
if (entry != 0 && entry->class_obj_ == obj)
return TRUE;
}
/* it's not one of my superclasses */
return FALSE;
}
/*
* Get my intrinsic class object
*/
vm_obj_id_t CVmMetaclass::get_class_obj(VMG0_) const
{
vm_meta_entry_t *entry;
/* get my metacalss entry */
entry = G_meta_table->get_entry_from_reg(get_reg_idx());
/* if we found our entry, return the class from the entry */
return (entry != 0 ? entry->class_obj_ : VM_INVALID_OBJ);
}
/* ------------------------------------------------------------------------ */
/*
* object table implementation
*/
/*
* allocate object table
*/
void CVmObjTable::init()
{
/* allocate the initial set of page slots */
page_slots_ = 10;
pages_ = (CVmObjPageEntry **)t3malloc(page_slots_ * sizeof(*pages_));
/* if that failed, throw an error */
if (pages_ == 0)
err_throw(VMERR_OUT_OF_MEMORY);
/* no pages are in use yet */
pages_used_ = 0;
/* there are no free objects yet */
first_free_ = 0;
/* there's nothing in the GC work queue yet */
gc_queue_head_ = VM_INVALID_OBJ;
/* nothing in the finalizer work queue yet */
finalize_queue_head_ = VM_INVALID_OBJ;
/* we haven't allocated anything yet */
allocs_since_gc_ = 0;
/*
* Set the upper limit for allocations between garbage collection.
* We want to choose this number so that we balance the time it
* takes to collect garbage against the memory it consumes to leave
* it uncollected.
*/
max_allocs_between_gc_ = 1000;
/* enable the garbage collector */
gc_enabled_ = TRUE;
/* there are no saved image data pointers yet */
image_ptr_head_ = 0;
image_ptr_tail_ = 0;
image_ptr_last_cnt_ = 0;
/* no global pages yet */
globals_ = 0;
/* no global variables yet */
global_var_head_ = 0;
/* create the post_load_init() request table */
post_load_init_table_ = new CVmHashTable(128, new CVmHashFuncCS(), TRUE);
}
/*
* delete object table
*/
CVmObjTable::~CVmObjTable()
{
}
/*
* Delete the table. (We need to separate this out into a method so
* that we can get access to the global variables.)
*/
void CVmObjTable::delete_obj_table(VMG0_)
{
size_t i;
vm_image_ptr_page *ip_page;
vm_image_ptr_page *ip_next;
/* delete all entries in the post-load initialization table */
post_load_init_table_->delete_all_entries();
/* go through the pages and delete the entries */
for (i = 0 ; i < pages_used_ ; ++i)
{
int j;
CVmObjPageEntry *entry;
/* delete all of the objects on the page */
for (j = 0, entry = pages_[i] ; j < VM_OBJ_PAGE_CNT ; ++j, ++entry)
{
/* if this entry is still in use, delete it */
if (!entry->free_)
entry->get_vm_obj()->notify_delete(vmg_ entry->in_root_set_);
}
}
/* delete each page we've allocated */
for (i = 0 ; i < pages_used_ ; ++i)
{
/* delete this page */
t3free(pages_[i]);
}
/* free the master page table */
t3free(pages_);
/* we no longer have any pages */
pages_ = 0;
page_slots_ = 0;
pages_used_ = 0;
/* delete each object image data pointer page */
for (ip_page = image_ptr_head_ ; ip_page != 0 ; ip_page = ip_next)
{
/* remember the next page (before we delete this one) */
ip_next = ip_page->next_;
/* delete this page */
t3free(ip_page);
}
/* delete the linked list of globals */
if (globals_ != 0)
{
delete globals_;
globals_ = 0;
}
/*
* delete any left-over global variables (these should always be
* deleted by subsystems before we get here, but this is the last
* chance, so clean them up manually)
*/
while (global_var_head_ != 0)
delete_global_var(global_var_head_);
/* delete the post-load initialization table */
delete post_load_init_table_;
post_load_init_table_ = 0;
}
/*
* Enable or disable garbage collection
*/
int CVmObjTable::enable_gc(VMG_ int enable)
{
int old_enable;
/* remember the old status for returning */
old_enable = gc_enabled_;
/* set the new status */
gc_enabled_ = enable;
/*
* if we're enabling GC after it was previously disabled, check to
* see if we should perform a GC pass now (but don't count this as a
* separate allocation)
*/
if (!old_enable && enable)
alloc_check_gc(vmg_ FALSE);
/* return the previous status */
return old_enable;
}
/*
* allocate a new object ID
*/
vm_obj_id_t CVmObjTable::alloc_obj(VMG_ int in_root_set,
int can_have_refs, int can_have_weak_refs)
{
vm_obj_id_t ret;
CVmObjPageEntry *entry;
/* count the allocation and maybe perform garbage collection */
alloc_check_gc(vmg_ TRUE);
/* if the free list is empty, allocate a new page of object entries */
if (first_free_ == VM_INVALID_OBJ)
alloc_new_page();
/* remember the first item in the free list - this is our result */
ret = first_free_;
/* get the object table entry for the ID */
entry = get_entry(ret);
/* unlink new entry from the free list */
first_free_ = entry->next_obj_;
if (entry->next_obj_ != VM_INVALID_OBJ)
get_entry(entry->next_obj_)->ptr_.prev_free_ = entry->ptr_.prev_free_;
/* initialize the entry */
init_entry_for_alloc(ret, entry, in_root_set,
can_have_refs, can_have_weak_refs);
/* return the free object */
return ret;
}
/*
* Run garbage collection before allocating an object
*/
void CVmObjTable::gc_before_alloc(VMG0_)
{
/* run a full garbage collection pass */
gc_pass_init(vmg0_);
gc_pass_finish(vmg0_);
}
/*
* Allocate an object with a particular ID
*/
void CVmObjTable::alloc_obj_with_id(vm_obj_id_t id, int in_root_set,
int can_have_refs, int can_have_weak_refs)
{
CVmObjPageEntry *entry;
/*
* if the page containing the given object ID hasn't been allocated,
* allocate pages until it's available
*/
while (id >= pages_used_ * VM_OBJ_PAGE_CNT)
alloc_new_page();
/* get the object table entry for the ID */
entry = get_entry(id);
/* if the desired object ID is already taken, it's an error */
if (!entry->free_)
err_throw(VMERR_OBJ_IN_USE);
/* unlink the item - set the previous item's forward pointer... */
if (entry->ptr_.prev_free_ == VM_INVALID_OBJ)
first_free_ = entry->next_obj_;
else
get_entry(entry->ptr_.prev_free_)->next_obj_ = entry->next_obj_;
/* ...and the next items back pointer */
if (entry->next_obj_ != VM_INVALID_OBJ)
get_entry(entry->next_obj_)->ptr_.prev_free_ = entry->ptr_.prev_free_;
/* initialize the entry for allocation */
init_entry_for_alloc(id, entry, in_root_set,
can_have_refs, can_have_weak_refs);
}
/*
* Initialize an object table entry that we've just allocated
*/
void CVmObjTable::init_entry_for_alloc(vm_obj_id_t id,
CVmObjPageEntry *entry,
int in_root_set,
int can_have_refs,
int can_have_weak_refs)
{
/* mark the entry as in use */
entry->free_ = FALSE;
/* no undo savepoint has been created since the object was created */
entry->in_undo_ = FALSE;
/* mark the object as being in the root set if appropriate */
entry->in_root_set_ = in_root_set;
/* presume it's an ordinary persistent object */
entry->transient_ = FALSE;
/* presume it won't need post-load initialization */
entry->requested_post_load_init_ = FALSE;
/* set the GC characteristics as requested */
entry->can_have_refs_ = can_have_refs;
entry->can_have_weak_refs_ = can_have_weak_refs;
/*
* Mark the object as initially unreachable and unfinalizable. It's
* not necessarily really unreachable at this point, but we mark it
* as such because the garbage collector hasn't explicitly traced
* the object to be reachable. The initial conditions for garbage
* collection are that all objects not in the root set and not
* finalizable are marked as unreachable; since we're not in a gc
* pass right now (we can't be - memory cannot be allocated during a
* gc pass), we know that we must establish initial gc conditions
* for the next time we start a gc pass.
*/
entry->reachable_ = VMOBJ_UNREACHABLE;
entry->finalize_state_ = VMOBJ_UNFINALIZABLE;
/* add it to the GC work queue for the next GC pass */
if (in_root_set)
add_to_gc_queue(id, entry, VMOBJ_REACHABLE);
}
#if 0 // moved to in-line in header, since it's called *a lot*
/*
* get the page entry for a given ID
*/
CVmObjPageEntry *CVmObjTable::get_entry(vm_obj_id_t id) const
{
size_t main_idx;
size_t page_idx;
/* get the index of the page in the main array */
main_idx = (size_t)(id >> VM_OBJ_PAGE_CNT_LOG2);
/* get the index within the page */
page_idx = (size_t)(id & (VM_OBJ_PAGE_CNT - 1));
/* get the object */
return &pages_[main_idx][page_idx];
}
#endif
/*
* Delete an object, given the object table entry.
*/
void CVmObjTable::delete_entry(VMG_ vm_obj_id_t id, CVmObjPageEntry *entry)
{
/* mark the object table entry as free */
entry->free_ = TRUE;
/* it's not in the root set if it's free */
entry->in_root_set_ = FALSE;
/*
* notify the object that it's being deleted - this will let the
* object release any additional resources (such as variable-size
* heap space) that it's holding
*/
entry->get_vm_obj()->notify_delete(vmg_ FALSE);
/*
* remove any post-load initialization request for the object, if it
* ever requested post-load initialization
*/
if (entry->requested_post_load_init_)
remove_post_load_init(id);
/* link this object into the head of the free list */
entry->next_obj_ = first_free_;
/* link the previous head back to this object */
if (first_free_ != VM_INVALID_OBJ)
get_entry(first_free_)->ptr_.prev_free_ = id;
/* this object doesn't have a previous entry */
entry->ptr_.prev_free_ = VM_INVALID_OBJ;
/* it's now the first entry in the list */
first_free_ = id;
}
/*
* allocate a new page of objects
*/
void CVmObjTable::alloc_new_page()
{
size_t i;
vm_obj_id_t id;
CVmObjPageEntry *entry;
/* first, make sure we have room in the master page list */
if (pages_used_ == page_slots_)
{
/* increase the number of page slots */
page_slots_ += 10;
/* allocate space for the increased number of slots */
pages_ = (CVmObjPageEntry **)t3realloc(
pages_, page_slots_ * sizeof(*pages_));
}
/* allocate a new page */
pages_[pages_used_] =
(CVmObjPageEntry *)t3malloc(VM_OBJ_PAGE_CNT * sizeof(*pages_[0]));
/* if that failed, throw an error */
if (pages_[pages_used_] == 0)
err_throw(VMERR_OUT_OF_MEMORY);
/*
* initialize the new page to be entirely free - add each element of
* the page to the free list
*/
entry = pages_[pages_used_];
i = 0;
id = pages_used_ * VM_OBJ_PAGE_CNT;
/*
* if this is the start of the very first page, leave off the first
* object, since its ID is invalid
*/
if (id == VM_INVALID_OBJ)
{
/* mark the object as free so we don't try to free it later */
entry->free_ = TRUE;
/* don't put the invalid object in the free list */
++i;
++entry;
++id;
}
/* loop over each object in this page */
for ( ; i < VM_OBJ_PAGE_CNT ; ++i, ++entry, ++id)
{
/* set the forward pointer in this object */
entry->next_obj_ = first_free_;
/* set the back pointer in the previous object in the list */
if (first_free_ != VM_INVALID_OBJ)
get_entry(first_free_)->ptr_.prev_free_ = id;
/* there's nothing before this entry yet */
entry->ptr_.prev_free_ = VM_INVALID_OBJ;
/* this is now the head of the list */
first_free_ = id;
/* mark it free */
entry->free_ = TRUE;
/* presume it's not part of the root set */
entry->in_root_set_ = FALSE;
/*
* mark it initially unreachable and finalized, since it's not
* even allocated yet
*/
entry->reachable_ = VMOBJ_UNREACHABLE;
entry->finalize_state_ = VMOBJ_FINALIZED;
}
/* count the new page we've allocated */
++pages_used_;
}
/*
* Add an object to the list of machine globals.
*/
void CVmObjTable::add_to_globals(vm_obj_id_t obj)
{
CVmObjGlobPage *pg;
/*
* if this is a root set object, there is no need to mark it as
* global, since it is inherently uncollectable as an image file
* object to begin with
*/
if (get_entry(obj)->in_root_set_)
return;
/* if we have any global pages allocated, try adding to the head page */
if (globals_ != 0 && globals_->add_entry(obj))
{
/* we successfully added it to the head page - we're done */
return;
}
/*
* either the head page is full, or we haven't allocated any global
* pages at all yet; in either case, allocate a new page and link it
* at the head of our list
*/
pg = new CVmObjGlobPage();
pg->nxt_ = globals_;
globals_ = pg;
/*
* add the object to the new page - it must fit, since the new page is
* empty
*/
globals_->add_entry(obj);
}
/*
* Collect all garbage. This does a complete garbage collection pass,
* returning only after all unreachable objects have been collected. If
* incremental garbage collection is not required, the caller can simply
* invoke this routine to do the entire operation in a single call.
*/
void CVmObjTable::gc_full(VMG0_)
{
/*
* run the initial pass to mark globally-reachable objects, then run
* the garbage collector to completion
*/
gc_pass_init(vmg0_);
gc_pass_finish(vmg0_);
}
/*
* Garbage collector - initialize. Add all globally-reachable objects
* to the work queue.
*
* We assume that the following initial conditions hold: all objects
* except root set objects are marked as unreferenced, and all root set
* objects are marked as referenced; all root set objects are in the GC
* work queue. So, we don't need to worry about finding root objects or
* initializing the other objects at this point.
*/
void CVmObjTable::gc_pass_init(VMG0_)
{
/*
* reset the allocations-since-gc counter, since this is now the
* last gc pass, and we obviously haven't performed any allocations
* since this gc pass yet
*/
allocs_since_gc_ = 0;
/* trace objects reachable from the stack */
gc_trace_stack(vmg0_);
/* trace objects reachable from imports */
gc_trace_imports(vmg0_);
/* trace objects reachable from machine globals */
gc_trace_globals(vmg0_);
/*
* Process undo records - for each undo record, mark any referenced
* objects as reachable. Undo records are part of the root set.
*/
G_undo->gc_mark_refs(vmg0_);
}
/*
* Garbage collection - continue processing the work queue. This
* processes a set of entries from the work queue. This routine can be
* used for incremental garbage collection: after calling
* gc_pass_init(), the caller can repeatedly invoke this routine until
* it returns false. Since this routine will return after a short time
* even if there's more work left to do, other operations (such as
* processing user input) can be interleaved in a single thread with
* garbage collection.
*
* The actual number of entries that we process is configurable at VM
* compile-time via VM_GC_WORK_INCREMENT. The point of running the GC
* incrementally is to allow GC work to be interleaved with long-running
* user I/O operations (such as reading a line of text from the
* keyboard) in the foreground thread, so the work increment should be
* chosen so that each call to this routine completes quickly enough
* that the user will perceive no delay.
*
* Returns true if more work remains to be done, false if not. The
* caller should invoke this routine repeatedly until it returns false.
*/
int CVmObjTable::gc_pass_continue(VMG_ int trace_transient)
{
int cnt;
/*
* keep going until we exhaust the queue or run for our maximum
* number of iterations
*/
for (cnt = VM_GC_WORK_INCREMENT ;
cnt != 0 && gc_queue_head_ != VM_INVALID_OBJ ; --cnt)
{
vm_obj_id_t cur;
CVmObjPageEntry *entry;
/* get the next item from the work queue */
cur = gc_queue_head_;
/* get this object's entry */
entry = get_entry(cur);
/* remove this entry from the work queue */
gc_queue_head_ = entry->next_obj_;
/*
* Tell this object to mark its references. Mark the referenced
* objects with the same state as this object, if they're not
* already marked with a stronger state.
*
* If we're not tracing transients, do not trace this object if
* it's transient.
*/
if (trace_transient || !entry->transient_)
entry->get_vm_obj()->mark_refs(vmg_ entry->reachable_);
}
/*
* return true if there's more work to do; there's more work to do
* if we have any objects left in the gc work queue
*/
return gc_queue_head_ != VM_INVALID_OBJ;
}
/*
* Finish garbage collection. We'll finish any work remaining in the
* work queue, so this is safe to call at any time after gc_pass_init(),
* after any number of calls (even zero) to gc_pass_continue().
*/
void CVmObjTable::gc_pass_finish(VMG0_)
{
CVmObjPageEntry **pg;
CVmObjPageEntry *entry;
size_t i;
size_t j;
vm_obj_id_t id;
/*
* Make sure we're done processing the work queue -- keep calling
* gc_pass_continue() until it indicates that it's finished. If
* we're skipping finalizers, stop as soon as the state structure
* indicates that we've started running finalizers.
*/
gc_trace_work_queue(vmg_ TRUE);
/*
* We've now marked everything that's reachable from the root set as
* VMOBJ_REACHABLE. We can therefore determine the set of objects
* that are newly 'finalizable' - an object becomes finalizable when
* it was previously 'unfinalizable' and is not reachable, because we
* can finalize an object any time after it first becomes unreachable.
* So, scan all objects for eligibility for the 'finalizable'
* transition, and make the transition in those objects.
*/
for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i)
{
/* go through each entry on this page */
for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; --j, ++entry, ++id)
{
/*
* if this entry is not free and is not in the root set, check
* to see if its finalization status is changing
*/
if (!entry->free_ && !entry->in_root_set_)
{
/*
* If the entry is not reachable, and was previously
* unfinalizable, it is now finalizable.
*
* Note that an object must actually be reachable to avoid
* become finalizable at this point. Not only do
* unreachable objects become finalizable, but
* 'f-reachable' objects do, too, since these are only
* reachable from other finalizable objects.
*/
if (entry->reachable_ != VMOBJ_REACHABLE
&& entry->finalize_state_ == VMOBJ_UNFINALIZABLE)
{
/*
* This object is newly finalizable. If it has no
* finalizer, the object can go directly to the
* 'finalized' state; otherwise, add it to the queue
* of objects with pending finalizers.
*/
if (entry->get_vm_obj()->has_finalizer(vmg_ id))
{
/*
* This object is not reachable from the root set
* and was previously unfinalizable. Make the
* object finalizable.
*/
entry->finalize_state_ = VMOBJ_FINALIZABLE;
}
else
{
/*
* the entry has no finalizer, so we can make this
* object 'finalized' immediately
*/
entry->finalize_state_ = VMOBJ_FINALIZED;
}
}
/*
* If this object is finalizable, add it to the work queue
* in state "f-reachable." We must mark everything this
* object references, directly or indirectly, as
* f-reachable, which we'll do with another pass through
* the gc queue momentarily.
*/
if (entry->finalize_state_ == VMOBJ_FINALIZABLE)
{
/*
* this object and all of the objects it references
* are "f-reachable"
*/
add_to_gc_queue(id, entry, VMOBJ_F_REACHABLE);
}
}
}
}
/*
* During the scan above, we put all of the finalizable objects in the
* work queue in reachability state 'f-reachable'. (Actually,
* finalizable objects that were fully reachable were not put in the
* work queue, because they are in a stronger reachability state that
* we've already fully scanned.) Trace the work queue so that we mark
* everything reachable indirectly from an f-reachable object as also
* being at least f-reachable.
*/
gc_trace_work_queue(vmg_ TRUE);
/*
* We have now marked everything that's fully reachable as being in
* state 'reachable', and everything that's reachable from a
* finalizable object as being in state 'f-reachable'. Anything that
* is still in state 'unreachable' is garbage and can be collected.
*/
for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i)
{
/* go through each entry on this page */
for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; --j, ++entry, ++id)
{
/* if it's not already free, process it */
if (!entry->free_)
{
/* if the object is deletable, delete it */
if (entry->is_deletable())
{
/*
* This object is completely unreachable, and it has
* already been finalized. This means there is no
* possibility that the object could ever become
* reachable again, hence we can discard the object.
*/
delete_entry(vmg_ id, entry);
}
else
{
/*
* The object is reachable, so keep it around. Since
* it's staying around, and since we know which
* objects we're deleting and which are staying, we
* can ask this object to remove all of its "stale
* weak references" - that is, weak references to
* objects that we're about to delete. Don't bother
* notifying the object if it's incapable of keeping
* weak references.
*/
if (entry->can_have_weak_refs_)
entry->get_vm_obj()->remove_stale_weak_refs(vmg0_);
/*
* If this object is finalizable, put it in the
* finalizer queue, so that we can run its finalizer
* when we're done scanning the table.
*/
if (entry->finalize_state_ == VMOBJ_FINALIZABLE)
add_to_finalize_queue(id, entry);
/*
* restore initial conditions for this object, so that
* we're properly set up for the next GC pass
*/
gc_set_init_conditions(id, entry);
}
}
}
}
/*
* Go through the undo records and clear any stale weak references
* contained in the undo list.
*/
G_undo->gc_remove_stale_weak_refs(vmg0_);
/*
* All of the finalizable objects are now in the finalizer queue. Run
* through the finalizer queue and run each such object's finalizer.
*/
run_finalizers(vmg0_);
}
/*
* Trace all objects reachable from the work queue.
*/
void CVmObjTable::gc_trace_work_queue(VMG_ int trace_transient)
{
/*
* trace everything reachable directly from the work queue, until we
* exhaust the queue
*/
while (gc_pass_continue(vmg_ trace_transient)) ;
}
/*
* Garbage collection: trace objects reachable from the stack
*/
void CVmObjTable::gc_trace_stack(VMG0_)
{
size_t i;
size_t depth;
vm_val_t *val;
/*
* Process the stack. For each stack element that refers to an
* object, mark the object as referenced and add it to the work
* queue.
*
* Note that it makes no difference in what order we process the
* stack elements; we go from depth down to 0 merely as a trivial
* micro-optimization to avoid evaluating the stack depth on every
* iteration of the loop.
*/
for (i = 0, depth = G_stk->get_depth() ; i < depth ; ++i)
{
/*
* If this element refers to an object, and the object hasn't
* already been marked as referenced, mark it as reachable and
* add it to the work queue.
*
* Note that we only have to worry about objects here. We don't
* have to worry about constant lists, even though they can
* contain object references, because any object reference in a
* constant list must be a root set object, and we've already
* processed all root set objects.
*/
val = G_stk->get(i);
if (val->typ == VM_OBJ && val->val.obj != VM_INVALID_OBJ)
add_to_gc_queue(val->val.obj, VMOBJ_REACHABLE);
}
}
/*
* Trace objects reachable from imports
*/
void CVmObjTable::gc_trace_imports(VMG0_)
{
/*
* generate the list of object imports; for each one, if we have a
* valid object for the import, mark it as reachable
*/
#define VM_IMPORT_OBJ(sym, mem) \
if (G_predef->mem != VM_INVALID_OBJ) \
add_to_gc_queue(G_predef->mem, VMOBJ_REACHABLE);
#define VM_NOIMPORT_OBJ(sym, mem) VM_IMPORT_OBJ(sym, mem)
#include "vmimport.h"
}
/*
* Garbage collection: trace objects reachable from the machine globals
*/
void CVmObjTable::gc_trace_globals(VMG0_)
{
CVmObjGlobPage *pg;
vm_val_t *val;
vm_globalvar_t *var;
/* trace each page of globals */
for (pg = globals_ ; pg != 0 ; pg = pg->nxt_)
{
size_t i;
vm_obj_id_t *objp;
/* trace each item on this page */
for (objp = pg->objs_, i = pg->used_ ; i != 0 ; ++objp, --i)
{
/* trace this global */
add_to_gc_queue(*objp, VMOBJ_REACHABLE);
}
}
/* the return value register (R0) is a machine global */
val = G_interpreter->get_r0();
if (val->typ == VM_OBJ && val->val.obj != VM_INVALID_OBJ)
add_to_gc_queue(val->val.obj, VMOBJ_REACHABLE);
/* trace the global variables defined by other subsystems */
for (var = global_var_head_ ; var != 0 ; var = var->nxt)
{
/* if this global variable contains an object, trace it */
if (var->val.typ == VM_OBJ && var->val.val.obj != VM_INVALID_OBJ)
add_to_gc_queue(var->val.val.obj, VMOBJ_REACHABLE);
}
}
#if 0 // moved inline, as it's small and is called a fair amount
/*
* Set the initial conditions for an object, in preparation for the next
* GC pass.
*/
void CVmObjTable::gc_set_init_conditions(vm_obj_id_t id,
CVmObjPageEntry *entry)
{
/*
* Mark the object as unreachable -- at the start of each GC pass,
* all non-root-set objects must be marked unreachable.
*/
entry->reachable_ = VMOBJ_UNREACHABLE;
/*
* If it's in the root set, add it to the GC work queue -- all
* root-set objects must be in the work queue and marked as reachable
* at the start of each GC pass.
*
* If the object is not in the root set, check to see if it's
* finalizable. If so, add it to the finalizer queue, so that we
* eventually run its finalizer.
*/
if (entry->in_root_set_)
add_to_gc_queue(id, entry, VMOBJ_REACHABLE);
}
#endif
/*
* Run finalizers
*/
void CVmObjTable::run_finalizers(VMG0_)
{
/* keep going until we run out of work or reach our work limit */
while (finalize_queue_head_ != VM_INVALID_OBJ)
{
CVmObjPageEntry *entry;
vm_obj_id_t id;
/* get the next object from the queue */
id = finalize_queue_head_;
/* get the entry */
entry = get_entry(id);
/* remove the entry form the queue */
finalize_queue_head_ = entry->next_obj_;
/* mark the object as finalized */
entry->finalize_state_ = VMOBJ_FINALIZED;
/*
* the entry is no longer in any queue, so we must mark it as
* unreachable -- this ensures that the initial conditions are
* correct for the next garbage collection pass, since all
* objects not in the work queue must be marked as unreachable
* (it doesn't matter whether the object is actually reachable;
* the garbage collector will make that determination when it
* next runs)
*/
entry->reachable_ = VMOBJ_UNREACHABLE;
/* invoke its finalizer */
entry->get_vm_obj()->invoke_finalizer(vmg_ id);
}
}
/*
* Receive notification that we're creating a new undo savepoint
*/
void CVmObjTable::notify_new_savept()
{
CVmObjPageEntry **pg;
CVmObjPageEntry *entry;
size_t i;
size_t j;
vm_obj_id_t id;
/* go through each page of objects */
for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i)
{
/* go through each entry on this page */
for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; --j, ++entry, ++id)
{
/* if this entry is active, tell it about the new savepoint */
if (!entry->free_)
{
/*
* the object existed at the start of this savepoint, so
* it must keep undo information throughout the savepoint
*/
entry->in_undo_ = TRUE;
/* notify the object of the new savepoint */
entry->get_vm_obj()->notify_new_savept();
}
}
}
}
/*
* Apply undo
*/
void CVmObjTable::apply_undo(VMG_ CVmUndoRecord *rec)
{
/* tell the object to apply the undo */
if (rec->obj != VM_INVALID_OBJ)
get_obj(rec->obj)->apply_undo(vmg_ rec);
}
/*
* Scan all objects and add metaclass entries to the metaclass
* dependency table for any metaclasses of which there are existing
* instances.
*/
void CVmObjTable::add_metadeps_for_instances(VMG0_)
{
CVmObjPageEntry **pg;
size_t i;
vm_obj_id_t id;
/* go through each page in the object table */
for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i)
{
size_t j;
CVmObjPageEntry *entry;
/* start at the start of the page, but skip object ID = 0 */
j = VM_OBJ_PAGE_CNT;
entry = *pg;
/* go through each entry on this page */
for ( ; j > 0 ; --j, ++entry, ++id)
{
/* if this entry is in use, add its metaclass if necessary */
if (!entry->free_)
G_meta_table->add_entry_if_new(
entry->get_vm_obj()->get_metaclass_reg()->get_reg_idx(),
0, VM_INVALID_PROP, VM_INVALID_PROP);
}
}
}
/* ------------------------------------------------------------------------ */
/*
* creation
*/
CVmObjFixup::CVmObjFixup(ulong entry_cnt)
{
uint i;
/* remember the number of entries */
cnt_ = entry_cnt;
/* no entries are used yet */
used_ = 0;
/* if we have no entries, there's nothing to do */
if (cnt_ == 0)
{
arr_ = 0;
pages_ = 0;
return;
}
/* calculate the number of subarrays we need */
pages_ = (entry_cnt + VMOBJFIXUP_SUB_SIZE - 1) / VMOBJFIXUP_SUB_SIZE;
/* allocate the necessary number of subarrays */
arr_ = (obj_fixup_entry **)t3malloc(pages_ * sizeof(arr_[0]));
/* allocate the subarrays */
for (i = 0 ; i < pages_ ; ++i)
{
size_t cur_cnt;
/*
* allocate a full page, except for the last page, which might be
* only partially used
*/
cur_cnt = VMOBJFIXUP_SUB_SIZE;
if (i + 1 == pages_)
cur_cnt = ((entry_cnt - 1) % VMOBJFIXUP_SUB_SIZE) + 1;
/* allocate it */
arr_[i] = (obj_fixup_entry *)t3malloc(cur_cnt * sizeof(arr_[i][i]));
}
}
/*
* deletion
*/
CVmObjFixup::~CVmObjFixup()
{
uint i;
/* if we never allocated an array, there's nothing to do */
if (arr_ == 0)
return;
/* delete each subarray */
for (i = 0 ; i < pages_ ; ++i)
t3free(arr_[i]);
/* delete the main array */
t3free(arr_);
}
/*
* add a fixup
*/
void CVmObjFixup::add_fixup(vm_obj_id_t old_id, vm_obj_id_t new_id)
{
obj_fixup_entry *entry;
/* allocate the next available entry */
entry = get_entry(used_++);
/* store it */
entry->old_id = old_id;
entry->new_id = new_id;
}
/*
* translate an ID
*/
vm_obj_id_t CVmObjFixup::get_new_id(VMG_ vm_obj_id_t old_id)
{
obj_fixup_entry *entry;
/*
* if it's a root-set object, don't bother even trying to translate
* it, because root-set objects have stable ID's that never change on
* saving or restoring
*/
if (G_obj_table->is_obj_id_valid(old_id)
&& G_obj_table->is_obj_in_root_set(old_id))
return old_id;
/* find the entry by the object ID */
entry = find_entry(old_id);
/*
* if we found it, return the new ID; otherwise, return the old ID
* unchanged, since the ID evidently doesn't require mapping
*/
return (entry != 0 ? entry->new_id : old_id);
}
/*
* find an entry given the old object ID
*/
obj_fixup_entry *CVmObjFixup::find_entry(vm_obj_id_t old_id)
{
ulong lo;
ulong hi;
ulong cur;
/* do a binary search for the entry */
lo = 0;
hi = cnt_ - 1;
while (lo <= hi)
{
obj_fixup_entry *entry;
/* split the difference */
cur = lo + (hi - lo)/2;
entry = get_entry(cur);
/* is it the one we're looking for? */
if (entry->old_id == old_id)
{
/* it's the one - return the entry */
return entry;
}
else if (old_id > entry->old_id)
{
/* we need to go higher */
lo = (cur == lo ? cur + 1 : cur);
}
else
{
/* we need to go lower */
hi = (cur == hi ? cur - 1 : cur);
}
}
/* didn't find it */
return 0;
}
/*
* Fix a DATAHOLDER value
*/
void CVmObjFixup::fix_dh(VMG_ char *dh)
{
/* if it's an object, translate the ID */
if (vmb_get_dh_type(dh) == VM_OBJ)
{
vm_obj_id_t id;
/* get the object value */
id = vmb_get_dh_obj(dh);
/* translate it */
id = get_new_id(vmg_ id);
/*
* if it's invalid, set the dataholder value to nil; otherwise,
* set it to the new object ID
*/
if (id == VM_INVALID_OBJ)
vmb_put_dh_nil(dh);
else
vmb_put_dh_obj(dh, id);
}
}
/*
* Fix up an array of DATAHOLDER values.
*/
void CVmObjFixup::fix_dh_array(VMG_ char *arr, size_t cnt)
{
/* scan the array of dataholders */
for ( ; cnt != 0 ; --cnt, arr += VMB_DATAHOLDER)
fix_dh(vmg_ arr);
}
/*
* Fix a portable VMB_OBJECT_ID field
*/
void CVmObjFixup::fix_vmb_obj(VMG_ char *p)
{
vm_obj_id_t id;
/* get the old ID */
id = vmb_get_objid(p);
/* fix it up */
id = get_new_id(vmg_ id);
/* store it back */
vmb_put_objid(p, id);
}
/*
* Fix an array of portable VMB_OBJECT_ID fields
*/
void CVmObjFixup::fix_vmb_obj_array(VMG_ char *p, size_t cnt)
{
/* scan the array */
for ( ; cnt != 0 ; --cnt, p += VMB_OBJECT_ID)
fix_vmb_obj(vmg_ p);
}
/* ------------------------------------------------------------------------ */
/*
* Save/restore
*/
/*
* table-of-contents flags
*/
/* object is transient (so the object isn't saved to the state file) */
#define VMOBJ_TOC_TRANSIENT 0x0001
/*
* Save state to a file
*/
void CVmObjTable::save(VMG_ CVmFile *fp)
{
CVmObjPageEntry **pg;
CVmObjPageEntry *entry;
size_t i;
size_t j;
vm_obj_id_t id;
size_t toc_cnt;
size_t save_cnt;
long toc_cnt_pos;
long end_pos;
/*
* Before we save, perform a full GC pass. This will ensure that we
* have removed all objects that are referenced only weakly, and
* cleaned up the weak references to them; this is important because
* we don't trace weak references for the purposes of calculating the
* set of objects that must be saved, and hence won't save any objects
* that are only weakly referenced, which would leave dangling
* references in the saved state if those weak references weren't
* cleaned up before the objects containing them are saved.
*/
gc_full(vmg0_);
/*
* Make sure that all of the metaclasses that we are actually using
* are in the metaclass dependency table. We store the table in the
* file, because the table provides the mapping from file-local
* metaclass ID's to actual metaclasses; we must make sure that the
* table is complete (i.e., contains an entry for each metaclass of
* which there is an instance) before storing the table.
*/
add_metadeps_for_instances(vmg0_);
/* save the metaclass table */
G_meta_table->write_to_file(fp);
/*
* Figure out what objects we need to save. We only need to save
* objects that are directly reachable from the root object set, from
* the imports, or from the globals.
*
* We don't need to save objects that are only accessible from the
* undo, because we don't save any undo information in the file. We
* also don't need to save any objects that are reachable only from
* the stack, since the stack is inherently transient.
*
* Note that we don't need to trace from transient objects, since we
* won't be saving the transient objects and thus won't need to save
* anything referenced only from transient objects.
*
* So, we merely trace objects reachable from the imports, globals,
* and work queue. At any time between GC passes, the work queue
* contains the complete list of root-set objects, hence we can simply
* trace from the current work queue.
*/
gc_trace_imports(vmg0_);
gc_trace_globals(vmg0_);
gc_trace_work_queue(vmg_ FALSE);
/*
* Before we save the objects themselves, save a table of contents of
* the dynamically-allocated objects to be saved. This table of
* contents will allow us to fix up references to objects on reloading
* the file with the new object numbers we assign them at that time.
* First, write a placeholder for the table of contents entry count.
*
* Note that we must store the table of contents in ascending order of
* object ID. This happens naturally, since we scan the table in
* order of object ID.
*/
toc_cnt = 0;
save_cnt = 0;
toc_cnt_pos = fp->get_pos();
fp->write_int4(0);
/* now scan the object pages and save the table of contents */
for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i)
{
/* scan all objects on this page */
for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; -- j, ++entry, ++id)
{
/*
* If the entry is currently reachable, and it was dynamically
* allocated (which means it's not in the root set), then add
* it to the table of contents. Note that we won't
* necessarily be saving the object, because the object could
* be transient - but if so, then we still want the record of
* the transient object, so we'll know on reloading that the
* object is no longer available.
*/
if (!entry->free_
&& entry->reachable_ == VMOBJ_REACHABLE
&& !entry->in_root_set_)
{
ulong flags;
/* set up the flags */
flags = 0;
if (entry->transient_)
flags |= VMOBJ_TOC_TRANSIENT;
/* write the object ID and flags */
fp->write_int4(id);
fp->write_int4(flags);
/* count it */
++toc_cnt;
}
/* if it's saveable, count it among the saveable objects */
if (entry->is_saveable())
++save_cnt;
}
}
/* go back and fix up the size prefix for the table of contents */
end_pos = fp->get_pos();
fp->set_pos(toc_cnt_pos);
fp->write_int4(toc_cnt);
fp->set_pos(end_pos);
/* write the saveable object count, which we calculated above */
fp->write_int4(save_cnt);
/* scan all object pages, and save each reachable object */
for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i)
{
/* scan all objects on this page */
for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; -- j, ++entry, ++id)
{
/* if this object is saveable, save it */
if (entry->is_saveable())
{
uint idx;
char buf[2];
/* write the object ID */
fp->write_int4(id);
/* store the root-set flag */
buf[0] = (entry->in_root_set_ != 0);
/* store the dependency table index */
idx = entry->get_vm_obj()->
get_metaclass_reg()->get_reg_idx();
buf[1] = (char)G_meta_table->get_dependency_index(idx);
/* write the data */
fp->write_bytes(buf, 2);
/* save the metaclass-specific state */
entry->get_vm_obj()->save_to_file(vmg_ fp);
}
/*
* restore this object to the appropriate conditions in
* preparation for the next GC pass, so that we leave things
* as we found them -- saving the VM's state thus has no
* effect on the VM's state
*/
gc_set_init_conditions(id, entry);
}
}
}
/*
* Restore state from a file
*/
int CVmObjTable::restore(VMG_ CVmFile *fp, CVmObjFixup **fixups)
{
int err;
ulong cnt;
/* presume we won't create a fixup table */
*fixups = 0;
/* load the metaclass table */
if ((err = G_meta_table->read_from_file(fp)) != 0)
return err;
/*
* Reset all objects to the initial image file load state. Note that
* we wait until after we've read the metaclass table to reset the
* objects, because any intrinsic class objects in the root set will
* need to re-initialize their presence in the metaclass table, which
* they can't do until after the metaclass table has itself been
* reloaded.
*/
G_obj_table->reset_to_image(vmg0_);
/* read the size of the table of contents */
cnt = fp->read_uint4();
/* allocate the fixup table */
*fixups = new CVmObjFixup(cnt);
/* read the fixup table */
for ( ; cnt != 0 ; --cnt)
{
vm_obj_id_t old_id;
vm_obj_id_t new_id;
ulong flags;
/* read the next entry */
old_id = (vm_obj_id_t)fp->read_uint4();
flags = fp->read_uint4();
/*
* Allocate a new object ID for this entry. If the object was
* transient, then it won't actually have been saved, so we must
* fix up references to the object to nil.
*/
if (!(flags & VMOBJ_TOC_TRANSIENT))
new_id = vm_new_id(vmg_ FALSE);
else
new_id = VM_INVALID_OBJ;
/*
* Add the entry. Note that the table of contents is stored in
* ascending order of old ID (i.e., the ID's in the saved state
* file's numbering system); this is the same ordering required by
* the fixup table, so we can simply read entries from the file
* and add them directly to the fixup table.
*/
(*fixups)->add_fixup(old_id, new_id);
}
/* read the number of saved objects */
cnt = fp->read_uint4();
/* read the objects */
for ( ; cnt != 0 ; --cnt)
{
char buf[2];
vm_obj_id_t id;
int in_root_set;
uint meta_idx;
CVmObject *obj;
/* read the original object ID */
id = (vm_obj_id_t)fp->read_uint4();
/* read the root-set flag and dependency table index */
fp->read_bytes(buf, 2);
in_root_set = buf[0];
meta_idx = (uchar)buf[1];
/*
* if it's not in the root set, we need to create a new object;
* otherwise, the object must already exist, since it came from
* the object file
*/
if (in_root_set)
{
/*
* make sure the object is valid - since it's supposedly in
* the root set, it must already exist
*/
if (!is_obj_id_valid(id) || get_entry(id)->free_)
return VMERR_SAVED_OBJ_ID_INVALID;
}
else
{
/*
* the object was dynamically allocated, so it will have a new
* object number - fix up the object ID to the new numbering
* system
*/
id = (*fixups)->get_new_id(vmg_ id);
/* create the object */
G_meta_table->create_for_restore(vmg_ meta_idx, id);
}
/* read the object's data */
obj = get_obj(id);
obj->restore_from_file(vmg_ id, fp, *fixups);
}
/* success */
return 0;
}
/*
* Save an image data pointer
*/
void CVmObjTable::save_image_pointer(vm_obj_id_t obj_id, const char *ptr,
size_t siz)
{
vm_image_ptr *slot;
/* allocate a new page if we're out of slots on the current page */
if (image_ptr_head_ == 0)
{
/* no pages yet - allocate the first page */
image_ptr_head_ = (vm_image_ptr_page *)
t3malloc(sizeof(vm_image_ptr_page));
if (image_ptr_head_ == 0)
err_throw(VMERR_OUT_OF_MEMORY);
/* it's also the last page */
image_ptr_tail_ = image_ptr_head_;
image_ptr_tail_->next_ = 0;
/* no slots used on this page yet */
image_ptr_last_cnt_ = 0;
}
else if (image_ptr_last_cnt_ == VM_IMAGE_PTRS_PER_PAGE)
{
/* the last page is full - allocate another one */
image_ptr_tail_->next_ = (vm_image_ptr_page *)
t3malloc(sizeof(vm_image_ptr_page));
if (image_ptr_tail_->next_ == 0)
err_throw(VMERR_OUT_OF_MEMORY);
/* it's the new last page */
image_ptr_tail_ = image_ptr_tail_->next_;
image_ptr_tail_->next_ = 0;
/* no slots used on this page yet */
image_ptr_last_cnt_ = 0;
}
/* get the next available slot */
slot = &image_ptr_tail_->ptrs_[image_ptr_last_cnt_];
/* save the data */
slot->obj_id_ = obj_id;
slot->image_data_ptr_ = ptr;
slot->image_data_len_ = siz;
/* count the new record */
++image_ptr_last_cnt_;
}
/*
* Reset to initial image file state
*/
void CVmObjTable::reset_to_image(VMG0_)
{
CVmObjPageEntry **pg;
CVmObjPageEntry *entry;
size_t i;
size_t j;
vm_obj_id_t id;
vm_image_ptr_page *ip_page;
/*
* Drop all undo information. Since we're resetting to the initial
* state, the undo for our outgoing state will no longer be
* relevant.
*/
G_undo->drop_undo(vmg0_);
/* delete all of the globals */
if (globals_ != 0)
{
delete globals_;
globals_ = 0;
}
/*
* Go through the object table and reset each non-transient object in
* the root set to its initial conditions.
*/
for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i)
{
/* scan all objects on this page */
for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; --j, ++entry, ++id)
{
/*
* if it's not free, and it's in the root set, and it's not
* transient, reset it
*/
if (!entry->free_ && entry->in_root_set_ && !entry->transient_)
{
/*
* This object is part of the root set, so it's part of
* the state immediately after loading the image. Reset
* the object to its load file conditions.
*/
entry->get_vm_obj()->reset_to_image(vmg_ id);
}
}
}
/*
* Go through all of the objects for which we've explicitly saved the
* original image file location, and ask them to reset using the image
* data.
*/
for (ip_page = image_ptr_head_ ; ip_page != 0 ; ip_page = ip_page->next_)
{
size_t cnt;
vm_image_ptr *slot;
/*
* get the count for this page - if this is the last page, it's
* the last page counter; otherwise, it's a full page, since we
* fill up each page before creating a new one
*/
if (ip_page->next_ == 0)
cnt = image_ptr_last_cnt_;
else
cnt = VM_IMAGE_PTRS_PER_PAGE;
/* go through the records on the page */
for (slot = ip_page->ptrs_ ; cnt != 0 ; --cnt, ++slot)
{
/* it this object is non-transient, reload it */
if (!get_entry(slot->obj_id_)->transient_)
{
/* reload it using the saved image data */
vm_objp(vmg_ slot->obj_id_)
->reload_from_image(vmg_ slot->obj_id_,
slot->image_data_ptr_,
slot->image_data_len_);
}
}
}
}
/*
* Create a global variable
*/
vm_globalvar_t *CVmObjTable::create_global_var()
{
vm_globalvar_t *var;
/* create the new variable */
var = new vm_globalvar_t();
/* initialize the variable's value to nil */
var->val.set_nil();
/* link it into our list of globals */
var->nxt = global_var_head_;
var->prv = 0;
if (global_var_head_ != 0)
global_var_head_->prv = var;
global_var_head_ = var;
/* return the variable */
return var;
}
/*
* Delete a global variable
*/
void CVmObjTable::delete_global_var(vm_globalvar_t *var)
{
/* unlink it from the list of globals */
if (var->nxt != 0)
var->nxt->prv = var->prv;
if (var->prv != 0)
var->prv->nxt = var->nxt;
else
global_var_head_ = var->nxt;
/* delete the memory */
delete var;
}
/* ------------------------------------------------------------------------ */
/*
* post-load initialization status
*/
enum pli_stat_t
{
PLI_UNINITED, /* not yet initialized */
PLI_IN_PROGRESS, /* initialization in progress */
PLI_INITED /* initialization completed */
};
/*
* Post-load initialization hash table entry. We use the object ID,
* treating the binary representation as a string of bytes, as the hash
* key.
*/
class CVmHashEntryPLI: public CVmHashEntryCS
{
public:
CVmHashEntryPLI(vm_obj_id_t id)
: CVmHashEntryCS((char *)&id, sizeof(id), TRUE)
{
/*
* remember our object ID for easy access (technically, it's stored
* as our key value as well, so this is redundant; but it's
* transformed into a block of bytes for the key, so it's easier to
* keep a separate copy of the true type)
*/
id_ = id;
/* initialize our status */
status = PLI_UNINITED;
}
/* our object ID */
vm_obj_id_t id_;
/* status for current operation */
pli_stat_t status;
};
/*
* Request post-load initialization.
*/
void CVmObjTable::request_post_load_init(vm_obj_id_t obj)
{
CVmHashEntryPLI *entry;
/* check for an existing entry - if there's not one already, add one */
entry = (CVmHashEntryPLI *)
post_load_init_table_->find((char *)&obj, sizeof(obj));
if (entry == 0)
{
/* it's not there yet - add a new entry */
post_load_init_table_->add(new CVmHashEntryPLI(obj));
/* mark the object as having requested post-load initialization */
get_entry(obj)->requested_post_load_init_ = TRUE;
}
}
/*
* Explicitly invoke post-load initialization on a given object
*/
void CVmObjTable::ensure_post_load_init(VMG_ vm_obj_id_t obj)
{
CVmHashEntryPLI *entry;
/* find the entry */
entry = (CVmHashEntryPLI *)
post_load_init_table_->find((char *)&obj, sizeof(obj));
/* if we found it, invoke its initialization method */
if (entry != 0)
call_post_load_init(vmg_ entry);
}
/*
* Invoke post-load initialization on the given object, if appropriate
*/
void CVmObjTable::call_post_load_init(VMG_ CVmHashEntryPLI *entry)
{
/* check the status */
switch (entry->status)
{
case PLI_UNINITED:
/*
* It's not yet initialized, so we need to initialize it now. Mark
* it as having its initialization in progress.
*/
entry->status = PLI_IN_PROGRESS;
/*
* push the entry on the stack to protect it from gc while we're
* initializing it
*/
G_stk->push()->set_obj(entry->id_);
/* invoke its initialization */
vm_objp(vmg_ entry->id_)->post_load_init(vmg_ entry->id_);
/* mark the object as having completed initialization */
entry->status = PLI_INITED;
/* discard our GC protection */
G_stk->discard();
/* done */
break;
case PLI_IN_PROGRESS:
/*
* The object is in the process of being initialized. This must
* mean that the object's initializer is calling us indirectly,
* probably through another object's initializer that it invoked
* explicitly as a dependency, which in turn means that we have a
* circular dependency. This is illegal, so abort with an error.
*/
err_throw(VMERR_CIRCULAR_INIT);
break;
case PLI_INITED:
/* it's already been initialized, so there's nothing to do */
break;
}
}
/*
* Remove a post-load initialization record
*/
void CVmObjTable::remove_post_load_init(vm_obj_id_t obj)
{
CVmHashEntryPLI *entry;
/* find the entry */
entry = (CVmHashEntryPLI *)
post_load_init_table_->find((char *)&obj, sizeof(obj));
/* if we found the entry, remove it from the table and delete it */
if (entry != 0)
{
/* remove it */
post_load_init_table_->remove(entry);
/* delete it */
delete entry;
/* mark the entry as no longer being registered for post-load init */
get_entry(obj)->requested_post_load_init_ = FALSE;
}
}
/*
* post-load initialization enumeration context
*/
struct pli_enum_ctx
{
vm_globals *globals;
};
/*
* Invoke post-load initialization on all objects that have requested it.
* This must be called after initial program load, restarts, and restore
* operations.
*/
void CVmObjTable::do_all_post_load_init(VMG0_)
{
pli_enum_ctx ctx;
/* set up our context */
ctx.globals = VMGLOB_ADDR;
/* first, mark all entries as having status 'uninitialized' */
post_load_init_table_->enum_entries(&pli_status_cb, &ctx);
/* next, invoke the initializer method for each entry */
post_load_init_table_->enum_entries(&pli_invoke_cb, &ctx);
}
/*
* post-load initialization enumeration callback: mark entries as having
* status 'uninitialized'
*/
void CVmObjTable::pli_status_cb(void *ctx0, CVmHashEntry *entry0)
{
CVmHashEntryPLI *entry = (CVmHashEntryPLI *)entry0;
/* mark the entry as having status 'uninitialized' */
entry->status = PLI_UNINITED;
}
/*
* post-load initialization enumeration callback: mark entries as having
* status 'uninitialized'
*/
void CVmObjTable::pli_invoke_cb(void *ctx0, CVmHashEntry *entry0)
{
pli_enum_ctx *ctx = (pli_enum_ctx *)ctx0;
CVmHashEntryPLI *entry = (CVmHashEntryPLI *)entry0;
VMGLOB_PTR(ctx->globals);
/* invoke post-load initialization on the object */
call_post_load_init(vmg_ entry);
}
/* ------------------------------------------------------------------------ */
/*
* Memory manager implementation
*/
CVmMemory::CVmMemory(VMG_ CVmVarHeap *varheap)
{
/* remember our variable-size heap */
varheap_ = varheap;
/* initialize the variable-size heap */
varheap_->init(vmg0_);
}
/* ------------------------------------------------------------------------ */
/*
* Hybrid cell/malloc memory manager - cell sub-block manager
*/
/*
* Allocate an object. Since we always allocate blocks of a fixed size,
* we can ignore the size parameter; the heap manager will only route us
* requests that fit in our blocks.
*/
CVmVarHeapHybrid_hdr *CVmVarHeapHybrid_head::alloc(size_t)
{
CVmVarHeapHybrid_hdr *ret;
/* if there isn't an entry, allocate another array */
if (first_free_ == 0)
{
CVmVarHeapHybrid_array *arr;
size_t real_cell_size;
char *p;
size_t cnt;
/*
* Allocate another page. We need space for the array header
* itself, plus space for all of the cells. We want page_count_
* cells, each of size cell_size_ plus the size of the per-cell
* header, rounded to the worst-case alignment size for the
* platform.
*/
real_cell_size = osrndsz(cell_size_
+ osrndsz(sizeof(CVmVarHeapHybrid_hdr)));
arr = (CVmVarHeapHybrid_array *)
t3malloc(sizeof(CVmVarHeapHybrid_array)
+ (page_count_ * real_cell_size));
/* if that failed, throw an error */
if (arr == 0)
err_throw(VMERR_OUT_OF_MEMORY);
/* link the array into the master list */
arr->next_array = mem_mgr_->first_array_;
mem_mgr_->first_array_ = arr;
/*
* Build the free list. Each cell goes into the free list; the
* 'next' pointer is stored in the data area of the cell.
*/
for (p = arr->mem, cnt = page_count_ ; cnt > 0 ;
p += real_cell_size, --cnt)
{
/* link this one into the free list */
*(void **)p = first_free_;
first_free_ = (void *)p;
}
}
/* remember the return value */
ret = (CVmVarHeapHybrid_hdr *)first_free_;
/*
* when we initialized or last freed this entry, we stored a pointer
* to the next item in the free list in the object's data - retrieve
* this pointer now, and update our free list head to point to the
* next item
*/
first_free_ = *(void **)first_free_;
/* fill in the block's pointer to the allocating heap (i.e., this) */
ret->block = this;
/* return the item */
return ret;
}
/*
* Reallocate
*/
void *CVmVarHeapHybrid_head::realloc(CVmVarHeapHybrid_hdr *mem, size_t siz,
CVmObject *obj)
{
void *new_mem;
/*
* if the new block fits in our cell size, return the original
* memory unchanged; note that we must adjust the pointer so that we
* return the client-visible portion
*/
if (siz <= cell_size_)
return (void *)(mem + 1);
/*
* The memory won't fit in our cell size, so not only can't we
* re-use the existing cell, but we can't allocate the memory from
* our own sub-block at all. Allocate an entirely new block from
* the heap manager.
*/
new_mem = mem_mgr_->alloc_mem(siz, obj);
/*
* Copy the old cell's contents to the new memory. Note that the
* user-visible portion of the old cell starts immediately after the
* header; don't copy the old header, since it's not applicable to
* the new object. Note also that we got a pointer directly to the
* user-visible portion of the new object, so we don't need to make
* any adjustments to the new pointer.
*/
memcpy(new_mem, (void *)(mem + 1), cell_size_);
/* free the old memory */
free(mem);
/* return the new memory */
return new_mem;
}
/*
* Release memory
*/
void CVmVarHeapHybrid_head::free(CVmVarHeapHybrid_hdr *mem)
{
/* link the block into our free list */
*(void **)mem = first_free_;
first_free_ = (void *)mem;
}
/* ------------------------------------------------------------------------ */
/*
* Hybrid cell/malloc heap manager
*/
/*
* construct
*/
CVmVarHeapHybrid::CVmVarHeapHybrid()
{
/* set the cell heap count */
cell_heap_cnt_ = 5;
/* allocate our cell heap pointer array */
cell_heaps_ = (CVmVarHeapHybrid_head **)
t3malloc(cell_heap_cnt_ * sizeof(CVmVarHeapHybrid_head *));
/* if that failed, throw an error */
if (cell_heaps_ == 0)
err_throw(VMERR_OUT_OF_MEMORY);
/*
* Allocate our cell heaps. Set up the heaps so that the pages run
* about 32k each.
*/
cell_heaps_[0] = new CVmVarHeapHybrid_head(this, 32, 850);
cell_heaps_[1] = new CVmVarHeapHybrid_head(this, 64, 400);
cell_heaps_[2] = new CVmVarHeapHybrid_head(this, 128, 200);
cell_heaps_[3] = new CVmVarHeapHybrid_head(this, 256, 100);
cell_heaps_[4] = new CVmVarHeapHybrid_head(this, 512, 50);
/* allocate our malloc heap manager */
malloc_heap_ = new CVmVarHeapHybrid_malloc();
/* we haven't allocated any cell array pages yet */
first_array_ = 0;
}
/*
* delete
*/
CVmVarHeapHybrid::~CVmVarHeapHybrid()
{
size_t i;
/* delete our cell heaps */
for (i = 0 ; i < cell_heap_cnt_ ; ++i)
delete cell_heaps_[i];
/* delete the cell heap pointer array */
t3free(cell_heaps_);
/* delete all of the arrays */
while (first_array_ != 0)
{
CVmVarHeapHybrid_array *nxt;
/* remember the next one */
nxt = first_array_->next_array;
/* delete this one */
t3free(first_array_);
/* move on to the next one */
first_array_ = nxt;
}
/* delete the malloc-based subheap manager */
delete malloc_heap_;
}
/*
* allocate memory
*/
void *CVmVarHeapHybrid::alloc_mem(size_t siz, CVmObject *)
{
CVmVarHeapHybrid_head **subheap;
size_t i;
/* scan for a cell-based subheap that can handle the request */
for (i = 0, subheap = cell_heaps_ ; i < cell_heap_cnt_ ; ++i, ++subheap)
{
/*
* If it will fit in this one's cell size, allocate it from this
* subheap. Note that we must adjust the return pointer so that
* it points to the caller-visible portion of the block returned
* from the subheap, which immediately follows the internal
* header.
*/
if (siz <= (*subheap)->get_cell_size())
return (void *)((*subheap)->alloc(siz) + 1);
}
/*
* We couldn't find a cell-based manager that can handle a block
* this large. Allocate the block from the default malloc heap.
* Note that the caller-visible block is the part that immediately
* follows our internal header, so we must adjust the return pointer
* accordingly.
*/
return (void *)(malloc_heap_->alloc(siz) + 1);
}
/*
* reallocate memory
*/
void *CVmVarHeapHybrid::realloc_mem(size_t siz, void *mem,
CVmObject *obj)
{
CVmVarHeapHybrid_hdr *hdr;
/*
* get the block header, which immediately precedes the
* caller-visible block
*/
hdr = ((CVmVarHeapHybrid_hdr *)mem) - 1;
/*
* read the header to get the block manager that originally
* allocated the memory, and ask it to reallocate the memory
*/
return hdr->block->realloc(hdr, siz, obj);
}
/*
* free memory
*/
void CVmVarHeapHybrid::free_mem(void *mem)
{
CVmVarHeapHybrid_hdr *hdr;
/*
* get the block header, which immediately precedes the
* caller-visible block
*/
hdr = ((CVmVarHeapHybrid_hdr *)mem) - 1;
/*
* read the header to get the block manager that originally
* allocated the memory, and ask it to free the memory
*/
hdr->block->free(hdr);
}
|